Browse Source

refactoring, fixing script/frame blocking on FF, etc., more to do

pull/2/head
gorhill 10 years ago
parent
commit
7cd060a15f
  1. 39
      platform/firefox/vapi-background.js
  2. 24
      src/js/async.js
  3. 10
      src/js/background.js
  4. 18
      src/js/cookies.js
  5. 38
      src/js/info.js
  6. 107
      src/js/messaging.js
  7. 53
      src/js/pagestats.js
  8. 12
      src/js/start.js
  9. 381
      src/js/tab.js
  10. 143
      src/js/traffic.js

39
platform/firefox/vapi-background.js

@ -1002,9 +1002,10 @@ var httpObserver = {
ABORT: Components.results.NS_BINDING_ABORTED, ABORT: Components.results.NS_BINDING_ABORTED,
ACCEPT: Components.results.NS_SUCCEEDED, ACCEPT: Components.results.NS_SUCCEEDED,
// Request types: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIContentPolicy#Constants // Request types: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIContentPolicy#Constants
MAIN_FRAME: Ci.nsIContentPolicy.TYPE_DOCUMENT,
VALID_CSP_TARGETS: 1 << Ci.nsIContentPolicy.TYPE_DOCUMENT |
1 << Ci.nsIContentPolicy.TYPE_SUBDOCUMENT,
frameTypeMap: {
6: 'main_frame',
7: 'sub_frame'
},
typeMap: { typeMap: {
1: 'other', 1: 'other',
2: 'script', 2: 'script',
@ -1128,13 +1129,15 @@ var httpObserver = {
return true; return true;
} }
/*if ( result.redirectUrl ) {
channel.redirectionLimit = 1;
channel.redirectTo(
Services.io.newURI(result.redirectUrl, null, null)
);
// For the time being, will block instead of redirecting
// TODO: figure a better way of blocking embedded frames.
// Maybe blocking network requests, then having a content script
// revisit the DOM to replace blocked frame with something more
// friendly. Will see.
if ( typeof result === 'object' && result.redirectUrl ) {
channel.cancel(this.ABORT);
return true; return true;
}*/
}
} }
var onBeforeSendHeaders = vAPI.net.onBeforeSendHeaders; var onBeforeSendHeaders = vAPI.net.onBeforeSendHeaders;
@ -1158,7 +1161,7 @@ var httpObserver = {
} }
var URI = channel.URI; var URI = channel.URI;
var channelData, result;
var channelData, type, result;
if ( topic === 'http-on-examine-response' ) { if ( topic === 'http-on-examine-response' ) {
if ( !(channel instanceof Ci.nsIWritablePropertyBag) ) { if ( !(channel instanceof Ci.nsIWritablePropertyBag) ) {
@ -1175,7 +1178,8 @@ var httpObserver = {
return; return;
} }
if ( (1 << channelData[4] & this.VALID_CSP_TARGETS) === 0 ) {
type = this.frameTypeMap[channelData[4]];
if ( !type ) {
return; return;
} }
@ -1191,7 +1195,9 @@ var httpObserver = {
hostname: URI.asciiHost, hostname: URI.asciiHost,
parentFrameId: channelData[1], parentFrameId: channelData[1],
responseHeaders: result ? [{name: topic, value: result}] : [], responseHeaders: result ? [{name: topic, value: result}] : [],
statusLine: channel.responseStatus.toString(),
tabId: channelData[3], tabId: channelData[3],
type: type,
url: URI.asciiSpec url: URI.asciiSpec
}); });
@ -1469,7 +1475,7 @@ vAPI.toolbarButton.init = function() {
'font-size: 9px;', 'font-size: 9px;',
'font-weight: bold;', 'font-weight: bold;',
'color: #fff;', 'color: #fff;',
'background: #666;',
'background: #000;',
'content: attr(badge);', 'content: attr(badge);',
'}' '}'
); );
@ -1503,7 +1509,7 @@ vAPI.toolbarButton.init = function() {
this.CUIEvents.updateBadgeStyle = function() { this.CUIEvents.updateBadgeStyle = function() {
var css = [ var css = [
'background: #666',
'background: #000',
'color: #fff' 'color: #fff'
].join(';'); ].join(';');
@ -1592,18 +1598,19 @@ vAPI.toolbarButton.onBeforeCreated = function(doc) {
if ( updateTimer ) { if ( updateTimer ) {
return; return;
} }
updateTimer = setTimeout(resizePopup, 10); updateTimer = setTimeout(resizePopup, 10);
}; };
var resizePopup = function() { var resizePopup = function() {
updateTimer = null; updateTimer = null;
var body = iframe.contentDocument.body; var body = iframe.contentDocument.body;
panel.parentNode.style.maxWidth = 'none'; panel.parentNode.style.maxWidth = 'none';
// We set a limit for height
var height = Math.min(body.clientHeight, 600);
// https://github.com/chrisaljoudi/uBlock/issues/730 // https://github.com/chrisaljoudi/uBlock/issues/730
// Voodoo programming: this recipe works // Voodoo programming: this recipe works
panel.style.height = iframe.style.height = body.clientHeight.toString() + 'px';
panel.style.height = iframe.style.height = height.toString() + 'px';
panel.style.width = iframe.style.width = body.clientWidth.toString() + 'px'; panel.style.width = iframe.style.width = body.clientWidth.toString() + 'px';
if ( iframe.clientHeight !== body.clientHeight || iframe.clientWidth !== body.clientWidth ) {
if ( iframe.clientHeight !== height || iframe.clientWidth !== body.clientWidth ) {
delayedResize(); delayedResize();
} }
}; };

24
src/js/async.js

@ -133,31 +133,13 @@ return asyncJobManager;
// Update visual of extension icon. // Update visual of extension icon.
// A time out is used to coalesce adjacent requests to update badge. // A time out is used to coalesce adjacent requests to update badge.
µMatrix.updateBadgeAsync = (function(){
var µm = µMatrix;
// Cache callback definition, it was a bad idea to define this one inside
// updateBadgeAsync
var updateBadge = function(tabId) {
var µm = µMatrix;
var pageStore = µm.pageStatsFromTabId(tabId);
µMatrix.updateBadgeAsync = function(tabId) {
var pageStore = this.pageStoreFromTabId(tabId);
if ( pageStore ) { if ( pageStore ) {
pageStore.updateBadge(tabId);
return;
pageStore.updateBadge();
} }
vAPI.setIcon(tabId, null, '?');
}; };
var updateBadgeAsync = function(tabId) {
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
return;
}
µm.asyncJobs.add('updateBadge-' + tabId, tabId, updateBadge, 500);
};
return updateBadgeAsync;
})();
/******************************************************************************/ /******************************************************************************/
// Notify whoever care that url stats have changed (they need to // Notify whoever care that url stats have changed (they need to

10
src/js/background.js

@ -98,17 +98,13 @@ return {
// urls stats are kept on the back burner while waiting to be reactivated // urls stats are kept on the back burner while waiting to be reactivated
// in a tab or another. // in a tab or another.
pageStats: {}, // TODO: rename
pageStores: {},
pageStoreCemetery: {},
// A map of redirects, to allow reverse lookup of redirects from landing // A map of redirects, to allow reverse lookup of redirects from landing
// page, so that redirection can be reported to the user. // page, so that redirection can be reported to the user.
redirectRequests: {}, redirectRequests: {},
// tabs are used to redirect stats collection to a specific url stats
// structure.
pageUrlToTabId: {},
tabIdToPageUrl: {},
// page url => permission scope // page url => permission scope
tMatrix: null, tMatrix: null,
pMatrix: null, pMatrix: null,
@ -129,7 +125,7 @@ return {
// record what chromium is doing behind the scene // record what chromium is doing behind the scene
behindTheSceneURL: 'http://chromium-behind-the-scene/', behindTheSceneURL: 'http://chromium-behind-the-scene/',
behindTheSceneTabId: 0x7FFFFFFF,
behindTheSceneTabId: vAPI.noTabId,
behindTheSceneMaxReq: 250, behindTheSceneMaxReq: 250,
behindTheSceneScope: 'chromium-behind-the-scene', behindTheSceneScope: 'chromium-behind-the-scene',

18
src/js/cookies.js

@ -197,8 +197,7 @@ var recordPageCookiesAsync = function(pageStats) {
if ( !pageStats ) { if ( !pageStats ) {
return; return;
} }
var pageURL = µm.pageUrlFromPageStats(pageStats);
recordPageCookiesQueue[pageURL] = pageStats;
recordPageCookiesQueue[pageStats.pageUrl] = pageStats;
µm.asyncJobs.add( µm.asyncJobs.add(
'cookieHunterPageRecord', 'cookieHunterPageRecord',
null, null,
@ -220,6 +219,10 @@ var cookieLogEntryBuilder = [
]; ];
var recordPageCookie = function(pageStore, cookieKey) { var recordPageCookie = function(pageStore, cookieKey) {
if ( vAPI.isBehindTheSceneTabId(pageStore.tabId) ) {
return;
}
var cookieEntry = cookieDict[cookieKey]; var cookieEntry = cookieDict[cookieKey];
var block = µm.mustBlock(pageStore.pageHostname, cookieEntry.hostname, 'cookie'); var block = µm.mustBlock(pageStore.pageHostname, cookieEntry.hostname, 'cookie');
@ -263,8 +266,7 @@ var removePageCookiesAsync = function(pageStats) {
if ( !pageStats ) { if ( !pageStats ) {
return; return;
} }
var pageURL = µm.pageUrlFromPageStats(pageStats);
removePageCookiesQueue[pageURL] = pageStats;
removePageCookiesQueue[pageStats.pageUrl] = pageStats;
µm.asyncJobs.add( µm.asyncJobs.add(
'cookieHunterPageRemove', 'cookieHunterPageRemove',
null, null,
@ -508,13 +510,13 @@ vAPI.cookies.onChanged = function(changeInfo) {
// Go through all pages and update if needed, as one cookie can be used // Go through all pages and update if needed, as one cookie can be used
// by many web pages, so they need to be recorded for all these pages. // by many web pages, so they need to be recorded for all these pages.
var pageStores = µm.pageStats;
var pageStores = µm.pageStores;
var pageStore; var pageStore;
for ( var pageURL in pageStores ) {
if ( pageStores.hasOwnProperty(pageURL) === false ) {
for ( var tabId in pageStores ) {
if ( pageStores.hasOwnProperty(tabId) === false ) {
continue; continue;
} }
pageStore = pageStores[pageURL];
pageStore = pageStores[tabId];
if ( !cookieMatchDomains(cookieKey, pageStore.allHostnamesString) ) { if ( !cookieMatchDomains(cookieKey, pageStore.allHostnamesString) ) {
continue; continue;
} }

38
src/js/info.js

@ -31,7 +31,7 @@
var messager = vAPI.messaging.channel('info.js'); var messager = vAPI.messaging.channel('info.js');
var targetUrl = 'all';
var targetTabId = null;
var maxRequests = 500; var maxRequests = 500;
var cachedUserSettings = {}; var cachedUserSettings = {};
@ -55,7 +55,7 @@ function updateRequestData(callback) {
}; };
var request = { var request = {
what: 'getRequestLogs', what: 'getRequestLogs',
pageURL: targetUrl !== 'all' ? targetUrl : null
tabId: targetTabId
}; };
messager.send(request, onResponseReceived); messager.send(request, onResponseReceived);
} }
@ -65,7 +65,7 @@ function updateRequestData(callback) {
function clearRequestData() { function clearRequestData() {
var request = { var request = {
what: 'clearRequestLogs', what: 'clearRequestLogs',
pageURL: targetUrl !== 'all' ? targetUrl : null
tabId: targetTabId
}; };
messager.send(request); messager.send(request);
} }
@ -108,7 +108,7 @@ var renderLocalized = function(id, map) {
/******************************************************************************/ /******************************************************************************/
function renderPageUrls() { function renderPageUrls() {
var onResponseReceived = function(r) {
var onResponseReceived = function(result) {
var i, n; var i, n;
var select = uDom('#selectPageUrls'); var select = uDom('#selectPageUrls');
@ -118,25 +118,25 @@ function renderPageUrls() {
n = builtinOptions.length; n = builtinOptions.length;
for ( i = 0; i < n; i++ ) { for ( i = 0; i < n; i++ ) {
option = builtinOptions.at(i).clone(); option = builtinOptions.at(i).clone();
if ( option.val() === targetUrl ) {
if ( option.val() === targetTabId ) {
option.attr('selected', true); option.attr('selected', true);
} }
select.append(option); select.append(option);
} }
var pageURLs = r.pageURLs.sort();
var pageURL, option;
n = pageURLs.length;
var entries = result.pageURLs.sort();
var entry, pageURL, option;
n = entries.length;
for ( i = 0; i < n; i++ ) { for ( i = 0; i < n; i++ ) {
pageURL = pageURLs[i];
entry = entries[i];
// Behind-the-scene entry is always present, no need to recreate it // Behind-the-scene entry is always present, no need to recreate it
if ( pageURL === r.behindTheSceneURL ) {
if ( entry.pageURL === result.behindTheSceneURL ) {
continue; continue;
} }
option = uDom('<option>'); option = uDom('<option>');
option.val(pageURL);
option.text(pageURL);
if ( pageURL === targetUrl ) {
option.val(entry.tabId);
option.text(entry.pageURL);
if ( entry.pageURL === targetTabId ) {
option.attr('selected', true); option.attr('selected', true);
} }
select.append(option); select.append(option);
@ -154,10 +154,10 @@ function renderPageUrls() {
function renderStats() { function renderStats() {
var onResponseReceived = function(r) { var onResponseReceived = function(r) {
if ( !r.pageNetStats ) { if ( !r.pageNetStats ) {
targetUrl = 'all';
targetTabId = null;
} }
var requestStats = targetUrl === 'all' ? r.globalNetStats : r.pageNetStats;
var requestStats = targetTabId ? r.pageNetStats : r.globalNetStats;
var blockedStats = requestStats.blocked; var blockedStats = requestStats.blocked;
var allowedStats = requestStats.allowed; var allowedStats = requestStats.allowed;
@ -197,7 +197,7 @@ function renderStats() {
messager.send({ messager.send({
what: 'getStats', what: 'getStats',
pageURL: targetUrl === 'all' ? null : targetUrl
tabId: targetTabId
}, },
onResponseReceived onResponseReceived
); );
@ -363,8 +363,8 @@ function renderTransientData(internal) {
/******************************************************************************/ /******************************************************************************/
function targetUrlChangeHandler() {
targetUrl = this[this.selectedIndex].value;
function targetTabIdChangeHandler() {
targetTabId = this[this.selectedIndex].value;
renderStats(); renderStats();
updateRequests(); updateRequests();
} }
@ -381,7 +381,7 @@ var installEventHandlers = function() {
uDom('#refreshRequests').on('click', updateRequests); uDom('#refreshRequests').on('click', updateRequests);
uDom('#clearRequests').on('click', clearRequests); uDom('#clearRequests').on('click', clearRequests);
uDom('input[id^="show-"][type="checkbox"]').on('change', changeFilterHandler); uDom('input[id^="show-"][type="checkbox"]').on('change', changeFilterHandler);
uDom('#selectPageUrls').on('change', targetUrlChangeHandler);
uDom('#selectPageUrls').on('change', targetTabIdChangeHandler);
uDom('#max-logged-requests').on('change', function(){ changeValueHandler(uDom(this), 'maxLoggedRequests', 0, 999); }); uDom('#max-logged-requests').on('change', function(){ changeValueHandler(uDom(this), 'maxLoggedRequests', 0, 999); });
// https://github.com/gorhill/httpswitchboard/issues/197 // https://github.com/gorhill/httpswitchboard/issues/197

107
src/js/messaging.js

@ -156,18 +156,18 @@ var matrixSnapshot = function(tabId, details) {
} }
}; };
var tabContext = µm.tabContextManager.lookup(tabId);
// Allow examination of behind-the-scene requests // Allow examination of behind-the-scene requests
// TODO: Not portable
if ( details.tabURL ) {
if ( details.tabURL.lastIndexOf(vAPI.getURL(''), 0) === 0 ) {
tabId = µm.behindTheSceneTabId;
} else if ( details.tabURL === µm.behindTheSceneURL ) {
if (
tabContext.rawURL.lastIndexOf(vAPI.getURL(''), 0) === 0 ||
tabContext.rawURL === µm.behindTheSceneURL
) {
tabId = µm.behindTheSceneTabId; tabId = µm.behindTheSceneTabId;
} }
}
var pageStore = µm.pageStatsFromTabId(tabId);
if ( !pageStore ) {
var pageStore = µm.pageStoreFromTabId(tabId);
if ( pageStore === null ) {
return r; return r;
} }
@ -371,8 +371,8 @@ var contentScriptSummaryHandler = function(tabId, details) {
if ( !details || !details.locationURL ) { if ( !details || !details.locationURL ) {
return; return;
} }
var pageURL = µm.pageUrlFromTabId(tabId);
var pageStats = µm.pageStatsFromPageUrl(pageURL);
var pageStore = µm.pageStoreFromTabId(tabId);
var pageURL = pageStore.pageUrl;
var µmuri = µm.URI.set(details.locationURL); var µmuri = µm.URI.set(details.locationURL);
var frameURL = µmuri.normalizedURI(); var frameURL = µmuri.normalizedURI();
var frameHostname = µmuri.hostname; var frameHostname = µmuri.hostname;
@ -384,7 +384,7 @@ var contentScriptSummaryHandler = function(tabId, details) {
// scripts // scripts
// https://github.com/gorhill/httpswitchboard/issues/25 // https://github.com/gorhill/httpswitchboard/issues/25
if ( pageStats && inlineScriptBlocked ) {
if ( pageStore && inlineScriptBlocked ) {
urls = details.scriptSources; urls = details.scriptSources;
for ( url in urls ) { for ( url in urls ) {
if ( !urls.hasOwnProperty(url) ) { if ( !urls.hasOwnProperty(url) ) {
@ -394,8 +394,9 @@ var contentScriptSummaryHandler = function(tabId, details) {
url = frameURL + '{inline_script}'; url = frameURL + '{inline_script}';
} }
r = µm.filterRequest(pageURL, 'script', url); r = µm.filterRequest(pageURL, 'script', url);
pageStats.recordRequest('script', url, r !== false, r);
pageStore.recordRequest('script', url, r !== false, r);
} }
pageStore.updateBadgeAsync();
} }
// TODO: as of 2014-05-26, not sure this is needed anymore, since µMatrix // TODO: as of 2014-05-26, not sure this is needed anymore, since µMatrix
@ -403,28 +404,29 @@ var contentScriptSummaryHandler = function(tabId, details) {
// this code was put in). // this code was put in).
// plugins // plugins
// https://github.com/gorhill/httpswitchboard/issues/25 // https://github.com/gorhill/httpswitchboard/issues/25
if ( pageStats ) {
if ( pageStore ) {
urls = details.pluginSources; urls = details.pluginSources;
for ( url in urls ) { for ( url in urls ) {
if ( !urls.hasOwnProperty(url) ) { if ( !urls.hasOwnProperty(url) ) {
continue; continue;
} }
r = µm.filterRequest(pageURL, 'plugin', url); r = µm.filterRequest(pageURL, 'plugin', url);
pageStats.recordRequest('plugin', url, r !== false, r);
pageStore.recordRequest('plugin', url, r !== false, r);
} }
pageStore.updateBadgeAsync();
} }
// https://github.com/gorhill/httpswitchboard/issues/181 // https://github.com/gorhill/httpswitchboard/issues/181
µm.onPageLoadCompleted(pageURL);
µm.onPageLoadCompleted(tabId);
}; };
/******************************************************************************/ /******************************************************************************/
var contentScriptLocalStorageHandler = function(pageURL) {
var contentScriptLocalStorageHandler = function(tabId, pageURL) {
var µmuri = µm.URI.set(pageURL); var µmuri = µm.URI.set(pageURL);
var response = µm.mustBlock(µm.scopeFromURL(pageURL), µmuri.hostname, 'cookie'); var response = µm.mustBlock(µm.scopeFromURL(pageURL), µmuri.hostname, 'cookie');
µm.recordFromPageUrl(
pageURL,
µm.recordFromTabId(
tabId,
'cookie', 'cookie',
µmuri.rootURL() + '/{localStorage}', µmuri.rootURL() + '/{localStorage}',
response response
@ -452,13 +454,11 @@ var onMessage = function(request, sender, callback) {
switch ( request.what ) { switch ( request.what ) {
case 'contentScriptHasLocalStorage': case 'contentScriptHasLocalStorage':
response = contentScriptLocalStorageHandler(request.url);
µm.updateBadgeAsync(tabId);
response = contentScriptLocalStorageHandler(tabId, request.url);
break; break;
case 'contentScriptSummary': case 'contentScriptSummary':
contentScriptSummaryHandler(tabId, request); contentScriptSummaryHandler(tabId, request);
µm.updateBadgeAsync(tabId);
break; break;
case 'checkScriptBlacklisted': case 'checkScriptBlacklisted':
@ -699,22 +699,46 @@ vAPI.messaging.listen('hosts-files.js', onMessage);
(function() { (function() {
var µm = µMatrix;
/******************************************************************************/
var getTabURLs = function() {
var pageURLs = [];
var pageStores = µm.pageStores;
for ( var tabId in pageStores ) {
if ( pageStores.hasOwnProperty(tabId) === false ) {
continue;
}
pageURLs.push({
tabId: tabId,
pageURL: pageStores[tabId].pageUrl
});
}
return {
pageURLs: pageURLs,
behindTheSceneURL: µm.behindTheSceneURL
};
};
/******************************************************************************/ /******************************************************************************/
// map(pageURL) => array of request log entries // map(pageURL) => array of request log entries
var getRequestLog = function(pageURL) {
var getRequestLog = function(tabId) {
var requestLogs = {}; var requestLogs = {};
var pageStores = µMatrix.pageStats;
var pageURLs = pageURL ? [pageURL] : Object.keys(pageStores);
var pageStore, pageRequestLog, logEntries, j, logEntry;
var pageStores = µm.pageStores;
var tabIds = tabId ? [tabId] : Object.keys(pageStores);
var pageStore, pageURL, pageRequestLog, logEntries, j, logEntry;
for ( var i = 0; i < pageURLs.length; i++ ) {
pageURL = pageURLs[i];
pageStore = pageStores[pageURL];
for ( var i = 0; i < tabIds.length; i++ ) {
pageStore = pageStores[tabIds[i]];
if ( !pageStore ) { if ( !pageStore ) {
continue; continue;
} }
pageURL = pageStore.pageUrl;
pageRequestLog = []; pageRequestLog = [];
logEntries = pageStore.requests.getLoggedRequests(); logEntries = pageStore.requests.getLoggedRequests();
j = logEntries.length; j = logEntries.length;
@ -733,23 +757,23 @@ var getRequestLog = function(pageURL) {
/******************************************************************************/ /******************************************************************************/
var clearRequestLog = function(pageURL) {
var pageStores = µMatrix.pageStats;
var pageURLs = pageURL ? [pageURL] : Object.keys(pageStores);
var clearRequestLog = function(tabId) {
var pageStores = µm.pageStores;
var tabIds = tabId ? [tabId] : Object.keys(pageStores);
var pageStore; var pageStore;
for ( var i = 0; i < pageURLs.length; i++ ) {
if ( pageStore = pageStores[pageURLs[i]] ) {
pageStore.requests.clearLogBuffer();
for ( var i = 0; i < tabIds.length; i++ ) {
pageStore = pageStores[tabIds[i]];
if ( !pageStore ) {
continue;
} }
pageStore.requests.clearLogBuffer();
} }
}; };
/******************************************************************************/ /******************************************************************************/
var onMessage = function(request, sender, callback) { var onMessage = function(request, sender, callback) {
var µm = µMatrix;
// Async // Async
switch ( request.what ) { switch ( request.what ) {
default: default:
@ -761,14 +785,11 @@ var onMessage = function(request, sender, callback) {
switch ( request.what ) { switch ( request.what ) {
case 'getPageURLs': case 'getPageURLs':
response = {
pageURLs: Object.keys(µm.pageUrlToTabId),
behindTheSceneURL: µm.behindTheSceneURL
};
response = getTabURLs();
break; break;
case 'getStats': case 'getStats':
var pageStore = µm.pageStats[request.pageURL];
var pageStore = µm.pageStores[request.tabId];
response = { response = {
globalNetStats: µm.requestStats, globalNetStats: µm.requestStats,
pageNetStats: pageStore ? pageStore.requestStats : null, pageNetStats: pageStore ? pageStore.requestStats : null,
@ -782,11 +803,11 @@ var onMessage = function(request, sender, callback) {
break; break;
case 'getRequestLogs': case 'getRequestLogs':
response = getRequestLog(request.pageURL);
response = getRequestLog(request.tabId);
break; break;
case 'clearRequestLogs': case 'clearRequestLogs':
clearRequestLog(request.pageURL);
clearRequestLog(request.tabId);
break; break;
default: default:

53
src/js/pagestats.js

@ -445,28 +445,30 @@ var pageStoreJunkyard = [];
/******************************************************************************/ /******************************************************************************/
var pageStoreFactory = function(pageUrl) {
var pageStoreFactory = function(tabContext) {
var entry = pageStoreJunkyard.pop(); var entry = pageStoreJunkyard.pop();
if ( entry ) { if ( entry ) {
return entry.init(pageUrl);
return entry.init(tabContext);
} }
return new PageStore(pageUrl);
return new PageStore(tabContext);
}; };
/******************************************************************************/ /******************************************************************************/
function PageStore(pageUrl) {
function PageStore(tabContext) {
this.requestStats = new WebRequestStats(); this.requestStats = new WebRequestStats();
this.off = false; this.off = false;
this.init(pageUrl);
this.init(tabContext);
} }
/******************************************************************************/ /******************************************************************************/
PageStore.prototype.init = function(pageUrl) {
this.pageUrl = pageUrl;
this.pageHostname = µm.URI.hostnameFromURI(pageUrl);
this.pageDomain = µm.URI.domainFromHostname(this.pageHostname) || this.pageHostname;
PageStore.prototype.init = function(tabContext) {
this.tabId = tabContext.tabId;
this.rawUrl = tabContext.rawURL;
this.pageUrl = tabContext.normalURL;
this.pageHostname = tabContext.rootHostname;
this.pageDomain = tabContext.rootDomain;
this.pageScriptBlocked = false; this.pageScriptBlocked = false;
this.thirdpartyScript = false; this.thirdpartyScript = false;
this.requests = µm.PageRequestStats.factory(); this.requests = µm.PageRequestStats.factory();
@ -477,8 +479,8 @@ PageStore.prototype.init = function(pageUrl) {
this.distinctRequestCount = 0; this.distinctRequestCount = 0;
this.perLoadAllowedRequestCount = 0; this.perLoadAllowedRequestCount = 0;
this.perLoadBlockedRequestCount = 0; this.perLoadBlockedRequestCount = 0;
this.boundCount = 0;
this.obsoleteAfter = 0;
this.incinerationTimer = null;
this.updateBadgeTimer = null;
return this; return this;
}; };
@ -486,12 +488,24 @@ PageStore.prototype.init = function(pageUrl) {
PageStore.prototype.dispose = function() { PageStore.prototype.dispose = function() {
this.requests.dispose(); this.requests.dispose();
this.rawUrl = '';
this.pageUrl = ''; this.pageUrl = '';
this.pageHostname = ''; this.pageHostname = '';
this.pageDomain = ''; this.pageDomain = '';
this.domains = {}; this.domains = {};
this.allHostnamesString = ' '; this.allHostnamesString = ' ';
this.state = {}; this.state = {};
if ( this.incinerationTimer !== null ) {
clearTimeout(this.incinerationTimer);
this.incinerationTimer = null;
}
if ( this.updateBadgeTimer !== null ) {
clearTimeout(this.updateBadgeTimer);
this.updateBadgeTimer = null;
}
if ( pageStoreJunkyard.length < 8 ) { if ( pageStoreJunkyard.length < 8 ) {
pageStoreJunkyard.push(this); pageStoreJunkyard.push(this);
} }
@ -550,13 +564,16 @@ PageStore.prototype.recordRequest = function(type, url, block) {
/******************************************************************************/ /******************************************************************************/
// Update badge, incrementally
// Update badge
// rhill 2013-11-09: well this sucks, I can't update icon/badge // rhill 2013-11-09: well this sucks, I can't update icon/badge
// incrementally, as chromium overwrite the icon at some point without // incrementally, as chromium overwrite the icon at some point without
// notifying me, and this causes internal cached state to be out of sync. // notifying me, and this causes internal cached state to be out of sync.
PageStore.prototype.updateBadge = function(tabId) {
PageStore.prototype.updateBadgeAsync = (function() {
var updateBadge = function() {
this.updateBadgeTimer = null;
var iconId = null; var iconId = null;
var badgeStr = ''; var badgeStr = '';
var total = this.perLoadAllowedRequestCount + this.perLoadBlockedRequestCount; var total = this.perLoadAllowedRequestCount + this.perLoadBlockedRequestCount;
@ -566,9 +583,17 @@ PageStore.prototype.updateBadge = function(tabId) {
iconId = greenSize < squareSize/2 ? Math.ceil(greenSize) : Math.floor(greenSize); iconId = greenSize < squareSize/2 ? Math.ceil(greenSize) : Math.floor(greenSize);
badgeStr = µm.formatCount(this.distinctRequestCount); badgeStr = µm.formatCount(this.distinctRequestCount);
} }
vAPI.setIcon(tabId, iconId, badgeStr);
vAPI.setIcon(this.tabId, iconId, badgeStr);
}; };
return function() {
if ( this.updateBadgeTimer === null ) {
this.updateBadgeTimer = setTimeout(updateBadge.bind(this), 500);
}
};
})();
/******************************************************************************/ /******************************************************************************/
return { return {

12
src/js/start.js

@ -31,10 +31,8 @@
(function() { (function() {
var µm = µMatrix; var µm = µMatrix;
var pageStore = µm.createPageStore(µm.behindTheSceneURL);
µm.pageUrlToTabId[µm.behindTheSceneURL] = µm.behindTheSceneTabId;
µm.tabIdToPageUrl[µm.behindTheSceneTabId] = µm.behindTheSceneURL;
pageStore.boundCount += 1;
var tabContext = µm.tabContextManager.mustLookup(vAPI.noTabId);
µm.pageStores[vAPI.noTabId] = µm.PageStore.factory(tabContext);
})(); })();
/******************************************************************************/ /******************************************************************************/
@ -95,11 +93,13 @@
// This needs to be done when the PSL is loaded // This needs to be done when the PSL is loaded
var bindTabs = function(tabs) { var bindTabs = function(tabs) {
var tab;
var i = tabs.length; var i = tabs.length;
// console.debug('start.js > binding %d tabs', i); // console.debug('start.js > binding %d tabs', i);
while ( i-- ) { while ( i-- ) {
µm.tabContextManager.commit(tabs[i].id, tabs[i].url);
µm.bindTabToPageStats(tabs[i].id, tabs[i].url);
tab = tabs[i];
µm.tabContextManager.commit(tab.id, tab.url);
µm.bindTabToPageStats(tab.id);
} }
µm.webRequest.start(); µm.webRequest.start();
}; };

381
src/js/tab.js

@ -55,13 +55,13 @@ var µm = µMatrix;
return uri.normalizedURI(); return uri.normalizedURI();
} }
var url = 'http://' + scheme + '-scheme/';
var url = scheme + '-scheme';
if ( uri.hostname !== '' ) { if ( uri.hostname !== '' ) {
url += uri.hostname + '/';
url = uri.hostname + '.' + url;
} }
return url;
return 'http://' + url + '/';
}; };
/******************************************************************************/ /******************************************************************************/
@ -198,7 +198,7 @@ housekeep itself.
this.rawURL = this.stack[this.stack.length - 1]; this.rawURL = this.stack[this.stack.length - 1];
this.normalURL = µm.normalizePageURL(this.tabId, this.rawURL); this.normalURL = µm.normalizePageURL(this.tabId, this.rawURL);
this.rootHostname = µm.URI.hostnameFromURI(this.normalURL); this.rootHostname = µm.URI.hostnameFromURI(this.normalURL);
this.rootDomain = µm.URI.domainFromHostname(this.rootHostname);
this.rootDomain = µm.URI.domainFromHostname(this.rootHostname) || this.rootHostname;
} }
}; };
@ -264,7 +264,7 @@ housekeep itself.
// Find a tab context for a specific tab. If none is found, attempt to // Find a tab context for a specific tab. If none is found, attempt to
// fix this. When all fail, the behind-the-scene context is returned. // fix this. When all fail, the behind-the-scene context is returned.
var lookup = function(tabId, url) {
var mustLookup = function(tabId, url) {
var entry; var entry;
if ( url !== undefined ) { if ( url !== undefined ) {
entry = push(tabId, url); entry = push(tabId, url);
@ -315,8 +315,8 @@ housekeep itself.
} }
}; };
var exists = function(tabId) {
return tabContexts[tabId] !== undefined;
var lookup = function(tabId) {
return tabContexts[tabId] || null;
}; };
// Behind-the-scene tab context // Behind-the-scene tab context
@ -350,7 +350,7 @@ housekeep itself.
unpush: unpush, unpush: unpush,
commit: commit, commit: commit,
lookup: lookup, lookup: lookup,
exists: exists,
mustLookup: mustLookup,
createContext: createContext createContext: createContext
}; };
})(); })();
@ -365,9 +365,15 @@ vAPI.tabs.onNavigation = function(details) {
if ( details.frameId !== 0 ) { if ( details.frameId !== 0 ) {
return; return;
} }
var tabContext = µm.tabContextManager.commit(details.tabId, details.url);
var pageStore = µm.bindTabToPageStats(details.tabId, 'afterNavigate');
// This actually can happen
var tabId = details.tabId;
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
return;
}
µm.tabContextManager.commit(tabId, details.url);
µm.bindTabToPageStats(tabId, 'commit');
}; };
/******************************************************************************/ /******************************************************************************/
@ -381,9 +387,14 @@ vAPI.tabs.onUpdated = function(tabId, changeInfo, tab) {
return; return;
} }
// This actually can happen
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
return;
}
if ( changeInfo.url ) { if ( changeInfo.url ) {
µm.tabContextManager.commit(tabId, changeInfo.url); µm.tabContextManager.commit(tabId, changeInfo.url);
µm.bindTabToPageStats(tabId, 'tabUpdated');
µm.bindTabToPageStats(tabId, 'updated');
} }
// rhill 2013-12-23: Compute state after whole page is loaded. This is // rhill 2013-12-23: Compute state after whole page is loaded. This is
@ -396,7 +407,7 @@ vAPI.tabs.onUpdated = function(tabId, changeInfo, tab) {
// unblocked when user un-blacklist the hostname. // unblocked when user un-blacklist the hostname.
// https://github.com/gorhill/httpswitchboard/issues/198 // https://github.com/gorhill/httpswitchboard/issues/198
if ( changeInfo.status === 'complete' ) { if ( changeInfo.status === 'complete' ) {
var pageStats = µm.pageStatsFromTabId(tabId);
var pageStats = µm.pageStoreFromTabId(tabId);
if ( pageStats ) { if ( pageStats ) {
pageStats.state = µm.computeTabState(tabId); pageStats.state = µm.computeTabState(tabId);
} }
@ -406,10 +417,10 @@ vAPI.tabs.onUpdated = function(tabId, changeInfo, tab) {
/******************************************************************************/ /******************************************************************************/
vAPI.tabs.onClosed = function(tabId) { vAPI.tabs.onClosed = function(tabId) {
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
return;
}
µm.unbindTabFromPageStats(tabId);
// I could incinerate all the page stores in the crypt associated with the
// tab id, but this will be done anyway once all incineration timers
// elapse. Let's keep it simple: they can all just rot a bit more before
// incineration.
}; };
/******************************************************************************/ /******************************************************************************/
@ -419,173 +430,201 @@ vAPI.tabs.registerListeners();
/******************************************************************************/ /******************************************************************************/
/******************************************************************************/ /******************************************************************************/
// Create a new page url stats store (if not already present)
µm.createPageStore = function(pageURL) {
// https://github.com/gorhill/httpswitchboard/issues/303
// At this point, the URL has been page-URL-normalized
// do not create stats store for urls which are of no interest
if ( pageURL.search(/^https?/) !== 0 ) {
return;
}
var pageStore = null;
if ( this.pageStats.hasOwnProperty(pageURL) ) {
pageStore = this.pageStats[pageURL];
}
if ( pageStore === null ) {
pageStore = this.PageStore.factory(pageURL);
// These counters are used so that icon presents an overview of how
// much allowed/blocked.
pageStore.perLoadAllowedRequestCount =
pageStore.perLoadBlockedRequestCount = 0;
this.pageStats[pageURL] = pageStore;
}
// TODO: revisit code, need to account for those web pages for which the
// URL changes with the content only updated
if ( pageStore.pageUrl !== pageURL ) {
pageStore.init(pageURL);
}
return pageStore;
};
/******************************************************************************/
// Create an entry for the tab if it doesn't exist // Create an entry for the tab if it doesn't exist
µm.bindTabToPageStats = function(tabId, context) { µm.bindTabToPageStats = function(tabId, context) {
if ( vAPI.isBehindTheSceneTabId(tabId) === false ) {
this.updateBadgeAsync(tabId);
}
// Do not create a page store for URLs which are of no interests // Do not create a page store for URLs which are of no interests
if ( µm.tabContextManager.exists(tabId) === false ) {
this.unbindTabFromPageStats(tabId);
return null;
// Example: dev console
var tabContext = this.tabContextManager.lookup(tabId);
if ( tabContext === null ) {
throw new Error('Unmanaged tab id: ' + tabId);
} }
var tabContext = µm.tabContextManager.lookup(tabId);
var rawURL = tabContext.rawURL;
// rhill 2013-11-24: Never ever rebind chromium-behind-the-scene
// virtual tab.
// https://github.com/gorhill/httpswitchboard/issues/67
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
return this.pageStores[tabId];
}
// https://github.com/gorhill/httpswitchboard/issues/303 // https://github.com/gorhill/httpswitchboard/issues/303
// Don't rebind pages blocked by µMatrix. // Don't rebind pages blocked by µMatrix.
var blockedRootFramePrefix = this.webRequest.blockedRootFramePrefix; var blockedRootFramePrefix = this.webRequest.blockedRootFramePrefix;
if ( rawURL.lastIndexOf(blockedRootFramePrefix, 0) === 0 ) {
if ( tabContext.rawURL.lastIndexOf(blockedRootFramePrefix, 0) === 0 ) {
return null; return null;
} }
var pageStore;
var pageURL = tabContext.normalURL;
var normalURL = tabContext.normalURL;
var pageStore = this.pageStores[tabId] || null;
// The previous page URL, if any, associated with the tab // The previous page URL, if any, associated with the tab
if ( this.tabIdToPageUrl.hasOwnProperty(tabId) ) {
var previousPageURL = this.tabIdToPageUrl[tabId];
if ( pageStore !== null ) {
// No change, do not rebind // No change, do not rebind
if ( previousPageURL === pageURL ) {
return this.pageStats[pageURL];
if ( pageStore.pageUrl === normalURL ) {
return pageStore;
}
// Do not change anything if it's weak binding -- typically when
// binding from network request handler.
if ( context === 'weak' ) {
return pageStore;
} }
// https://github.com/gorhill/uMatrix/issues/37 // https://github.com/gorhill/uMatrix/issues/37
// Just rebind whenever possible: the URL changed, but the document maybe is the same.
// Just rebind whenever possible: the URL changed, but the document
// maybe is the same.
// Example: Google Maps, Github // Example: Google Maps, Github
// https://github.com/gorhill/uMatrix/issues/72 // https://github.com/gorhill/uMatrix/issues/72
// Need to double-check that the new scope is same as old scope // Need to double-check that the new scope is same as old scope
if ( context === 'pageUpdated' ) {
pageStore = this.pageStats[previousPageURL];
if ( pageStore.pageHostname === this.hostnameFromURL(pageURL) ) {
pageStore.pageUrl = pageURL;
delete this.pageStats[previousPageURL];
this.pageStats[pageURL] = pageStore;
delete this.pageUrlToTabId[previousPageURL];
this.pageUrlToTabId[pageURL] = tabId;
this.tabIdToPageUrl[tabId] = pageURL;
if ( context === 'updated' && pageStore.pageHostname === tabContext.rootHostname ) {
pageStore.rawURL = tabContext.rawURL;
pageStore.normalURL = normalURL;
return pageStore; return pageStore;
} }
}
// We won't be reusing this page store.
this.unbindTabFromPageStats(tabId);
} }
pageStore = this.createPageStore(pageURL, context);
// Try to resurrect first.
pageStore = this.resurrectPageStore(tabId, normalURL);
if ( pageStore === null ) {
pageStore = this.PageStore.factory(tabContext);
}
this.pageStores[tabId] = pageStore;
// console.debug('tab.js > bindTabToPageStats(): dispatching traffic in tab id %d to page store "%s"', tabId, pageUrl); // console.debug('tab.js > bindTabToPageStats(): dispatching traffic in tab id %d to page store "%s"', tabId, pageUrl);
// rhill 2013-11-24: Never ever rebind chromium-behind-the-scene
// virtual tab.
// https://github.com/gorhill/httpswitchboard/issues/67
if ( tabId === this.behindTheSceneTabId ) {
// https://github.com/gorhill/uMatrix/issues/37
pageStore.updateBadgeAsync();
return pageStore; return pageStore;
};
/******************************************************************************/
µm.unbindTabFromPageStats = function(tabId) {
// Never unbind behind-the-scene page store.
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
return;
} }
// https://github.com/gorhill/uMatrix/issues/37
this.updateBadgeAsync(tabId);
var pageStore = this.pageStores[tabId] || null;
if ( pageStore === null ) {
return;
}
delete this.pageStores[tabId];
this.unbindTabFromPageStats(tabId);
if ( pageStore.incinerationTimer ) {
clearTimeout(pageStore.incinerationTimer);
pageStore.incinerationTimer = null;
}
// rhill 2014-02-08: Do not create an entry if no page store
// exists (like when visiting about:blank)
// https://github.com/gorhill/httpswitchboard/issues/186
if ( !pageStore ) {
if ( this.pageStoreCemetery.hasOwnProperty(tabId) === false ) {
this.pageStoreCemetery[tabId] = {};
}
var pageStoreCrypt = this.pageStoreCemetery[tabId];
var pageURL = pageStore.pageUrl;
pageStoreCrypt[pageURL] = pageStore;
pageStore.incinerationTimer = setTimeout(
this.incineratePageStore.bind(this, tabId, pageURL),
4 * 60 * 1000
);
};
/******************************************************************************/
µm.resurrectPageStore = function(tabId, pageURL) {
if ( this.pageStoreCemetery.hasOwnProperty(tabId) === false ) {
return null; return null;
} }
var pageStoreCrypt = this.pageStoreCemetery[tabId];
if ( pageStoreCrypt.hasOwnProperty(pageURL) === false ) {
return null;
}
var pageStore = pageStoreCrypt[pageURL];
if ( pageStore.incinerationTimer !== null ) {
clearTimeout(pageStore.incinerationTimer);
pageStore.incinerationTimer = null;
}
this.pageUrlToTabId[pageURL] = tabId;
this.tabIdToPageUrl[tabId] = pageURL;
pageStore.boundCount += 1;
delete pageStoreCrypt[pageURL];
if ( Object.keys(pageStoreCrypt).length === 0 ) {
delete this.pageStoreCemetery[tabId];
}
return pageStore; return pageStore;
}; };
/******************************************************************************/ /******************************************************************************/
µm.unbindTabFromPageStats = function(tabId) {
if ( this.tabIdToPageUrl.hasOwnProperty(tabId) === false ) {
µm.incineratePageStore = function(tabId, pageURL) {
if ( this.pageStoreCemetery.hasOwnProperty(tabId) === false ) {
return; return;
} }
var pageURL = this.tabIdToPageUrl[tabId];
if ( this.pageStats.hasOwnProperty(pageURL) ) {
var pageStore = this.pageStats[pageURL];
pageStore.boundCount -= 1;
if ( pageStore.boundCount === 0 ) {
pageStore.obsoleteAfter = Date.now() + (5 * 60 * 1000);
var pageStoreCrypt = this.pageStoreCemetery[tabId];
if ( pageStoreCrypt.hasOwnProperty(pageURL) === false ) {
return;
} }
var pageStore = pageStoreCrypt[pageURL];
if ( pageStore.incinerationTimer !== null ) {
clearTimeout(pageStore.incinerationTimer);
pageStore.incinerationTimer = null;
} }
delete this.tabIdToPageUrl[tabId];
delete this.pageUrlToTabId[pageURL];
delete pageStoreCrypt[pageURL];
if ( Object.keys(pageStoreCrypt).length === 0 ) {
delete this.pageStoreCemetery[tabId];
}
pageStore.dispose();
}; };
/******************************************************************************/ /******************************************************************************/
// Log a request
µm.pageStoreFromTabId = function(tabId) {
return this.pageStores[tabId] || null;
};
µm.recordFromTabId = function(tabId, type, url, blocked) {
var pageStats = this.pageStatsFromTabId(tabId);
if ( pageStats ) {
pageStats.recordRequest(type, url, blocked);
this.updateBadgeAsync(tabId);
}
// Never return null
µm.mustPageStoreFromTabId = function(tabId) {
return this.pageStores[tabId] || this.pageStores[vAPI.noTabId];
}; };
µm.recordFromPageUrl = function(pageUrl, type, url, blocked, reason) {
var pageStats = this.pageStatsFromPageUrl(pageUrl);
if ( pageStats ) {
pageStats.recordRequest(type, url, blocked, reason);
/******************************************************************************/
// Log a request
µm.recordFromTabId = function(tabId, type, url, blocked) {
var pageStore = this.pageStoreFromTabId(tabId);
if ( pageStore ) {
pageStore.recordRequest(type, url, blocked);
pageStore.updateBadgeAsync();
} }
}; };
/******************************************************************************/ /******************************************************************************/
µm.onPageLoadCompleted = function(pageURL) {
var pageStats = this.pageStatsFromPageUrl(pageURL);
if ( !pageStats ) {
µm.onPageLoadCompleted = function(tabId) {
var pageStore = this.pageStoreFromTabId(tabId);
if ( !pageStore ) {
return; return;
} }
// https://github.com/gorhill/httpswitchboard/issues/181 // https://github.com/gorhill/httpswitchboard/issues/181
if ( pageStats.thirdpartyScript ) {
pageStats.recordRequest('script', pageURL + '{3rd-party_scripts}', pageStats.pageScriptBlocked);
if ( pageStore.thirdpartyScript ) {
pageStore.recordRequest(
'script',
pageStore.pageURL + '{3rd-party_scripts}',
pageStore.pageScriptBlocked
);
} }
}; };
@ -609,7 +648,7 @@ vAPI.tabs.registerListeners();
var i = chromeTabs.length; var i = chromeTabs.length;
while ( i-- ) { while ( i-- ) {
tabId = chromeTabs[i].id; tabId = chromeTabs[i].id;
if ( µm.tabExists(tabId) ) {
if ( µm.pageStores.hasOwnProperty(tabId) ) {
µm.smartReloadTab(tabId); µm.smartReloadTab(tabId);
} }
} }
@ -627,7 +666,7 @@ vAPI.tabs.registerListeners();
// Reload content of a tab // Reload content of a tab
µm.smartReloadTab = function(tabId) { µm.smartReloadTab = function(tabId) {
var pageStats = this.pageStatsFromTabId(tabId);
var pageStats = this.pageStoreFromTabId(tabId);
if ( !pageStats ) { if ( !pageStats ) {
//console.error('HTTP Switchboard> µMatrix.smartReloadTab(): page stats for tab id %d not found', tabId); //console.error('HTTP Switchboard> µMatrix.smartReloadTab(): page stats for tab id %d not found', tabId);
return; return;
@ -691,20 +730,8 @@ vAPI.tabs.registerListeners();
/******************************************************************************/ /******************************************************************************/
// Required since not all tabs are of interests to HTTP Switchboard.
// Examples:
// `chrome://extensions/`
// `chrome-devtools://devtools/devtools.html`
// etc.
µm.tabExists = function(tabId) {
return !!this.pageUrlFromTabId(tabId);
};
/******************************************************************************/
µm.computeTabState = function(tabId) { µm.computeTabState = function(tabId) {
var pageStats = this.pageStatsFromTabId(tabId);
var pageStats = this.pageStoreFromTabId(tabId);
if ( !pageStats ) { if ( !pageStats ) {
//console.error('tab.js > µMatrix.computeTabState(): page stats for tab id %d not found', tabId); //console.error('tab.js > µMatrix.computeTabState(): page stats for tab id %d not found', tabId);
return {}; return {};
@ -738,36 +765,8 @@ vAPI.tabs.registerListeners();
/******************************************************************************/ /******************************************************************************/
µm.pageUrlFromTabId = function(tabId) {
return this.tabIdToPageUrl[tabId];
};
µm.pageUrlFromPageStats = function(pageStats) {
if ( pageStats ) {
return pageStats.pageUrl;
}
return undefined;
};
µm.pageStatsFromTabId = function(tabId) {
var pageUrl = this.tabIdToPageUrl[tabId];
if ( pageUrl ) {
return this.pageStats[pageUrl];
}
return undefined;
};
µm.pageStatsFromPageUrl = function(pageURL) {
if ( pageURL ) {
return this.pageStats[this.normalizePageURL(pageURL)];
}
return null;
};
/******************************************************************************/
µm.resizeLogBuffers = function(size) { µm.resizeLogBuffers = function(size) {
var pageStores = this.pageStats;
var pageStores = this.pageStores;
for ( var pageURL in pageStores ) { for ( var pageURL in pageStores ) {
if ( pageStores.hasOwnProperty(pageURL) ) { if ( pageStores.hasOwnProperty(pageURL) ) {
pageStores[pageURL].requests.resizeLogBuffer(size); pageStores[pageURL].requests.resizeLogBuffer(size);
@ -783,58 +782,4 @@ vAPI.tabs.registerListeners();
/******************************************************************************/ /******************************************************************************/
// Garbage collect stale url stats entries
(function() {
var gcPageStats = function() {
var pageStore;
var now = Date.now();
for ( var pageURL in µm.pageStats ) {
if ( µm.pageStats.hasOwnProperty(pageURL) === false ) {
continue;
}
pageStore = µm.pageStats[pageURL];
if ( pageStore.boundCount !== 0 ) {
continue;
}
if ( pageStore.obsoleteAfter > now ) {
continue;
}
µm.cookieHunter.removePageCookies(pageStore);
pageStore.dispose();
delete µm.pageStats[pageURL];
}
// Prune content of chromium-behind-the-scene virtual tab
// When `suggest-as-you-type` is on in Chromium, this can lead to a
// LOT of uninteresting behind the scene requests.
pageStore = µm.pageStats[µm.behindTheSceneURL];
if ( !pageStore ) {
return;
}
var reqKeys = pageStore.requests.getRequestKeys();
if ( reqKeys.length <= µm.behindTheSceneMaxReq ) {
return;
}
reqKeys = reqKeys.sort(function(a,b){
return pageStore.requests[b] - pageStore.requests[a];
}).slice(µm.behindTheSceneMaxReq);
var iReqKey = reqKeys.length;
while ( iReqKey-- ) {
pageStore.requests.disposeOne(reqKeys[iReqKey]);
}
};
// Time somewhat arbitrary: If a web page has not been in a tab
// for some time minutes, flush its stats.
µm.asyncJobs.add(
'gcPageStats',
null,
gcPageStats,
(2.5 * 60 * 1000) | 0,
true
);
})();
/******************************************************************************/
})(); })();

143
src/js/traffic.js

@ -42,9 +42,7 @@ var rootFrameReplacement = [
'font-family:httpsb;', 'font-family:httpsb;',
'font-style:normal;', 'font-style:normal;',
'font-weight:400;', 'font-weight:400;',
'src: local("httpsb"),url("',
µMatrix.fontCSSURL,
'") format("truetype");',
'src: local("httpsb"),url("', µMatrix.fontCSSURL, '") format("truetype");',
'}', '}',
'body {', 'body {',
'margin:0;', 'margin:0;',
@ -104,9 +102,7 @@ var subFrameReplacement = [
'font-family:httpsb;', 'font-family:httpsb;',
'font-style:normal;', 'font-style:normal;',
'font-weight:400;', 'font-weight:400;',
'src:local("httpsb"),url("',
µMatrix.fontCSSURL,
'") format("truetype");',
'src:local("httpsb"),url("', µMatrix.fontCSSURL, '") format("truetype");',
'}', '}',
'body{', 'body{',
'margin:0;', 'margin:0;',
@ -202,44 +198,31 @@ var onBeforeRootFrameRequestHandler = function(details) {
µm.tabContextManager.push(tabId, requestURL); µm.tabContextManager.push(tabId, requestURL);
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
tabId = µm.behindTheSceneTabId;
} else {
µm.bindTabToPageStats(tabId);
}
var uri = µm.URI.set(details.url);
if ( uri.scheme.indexOf('http') === -1 ) {
return;
}
var requestHostname = uri.hostname;
var pageStore = µm.pageStatsFromTabId(tabId);
var tabContext = µm.tabContextManager.mustLookup(tabId);
var pageStore = µm.bindTabToPageStats(tabId, 'weak');
// Disallow request as per matrix? // Disallow request as per matrix?
var block = µm.mustBlock(pageStore.pageHostname, requestHostname, 'doc');
var block = µm.mustBlock(tabContext.rootHostname, details.hostname, 'doc');
// console.debug('onBeforeRequestHandler()> block=%s "%s": %o', block, details.url, details); // console.debug('onBeforeRequestHandler()> block=%s "%s": %o', block, details.url, details);
// whitelisted?
// Not blocked
if ( !block ) { if ( !block ) {
// rhill 2013-11-07: Senseless to do this for behind-the-scene requests. // rhill 2013-11-07: Senseless to do this for behind-the-scene requests.
// rhill 2013-12-03: Do this here only for root frames. // rhill 2013-12-03: Do this here only for root frames.
if ( tabId !== µm.behindTheSceneTabId ) {
µm.cookieHunter.recordPageCookies(pageStore); µm.cookieHunter.recordPageCookies(pageStore);
}
return; return;
} }
// blacklisted
// Blocked
// rhill 2014-01-15: Delay logging of non-blocked top `main_frame` // rhill 2014-01-15: Delay logging of non-blocked top `main_frame`
// requests, in order to ensure any potential redirects is reported // requests, in order to ensure any potential redirects is reported
// in proper chronological order. // in proper chronological order.
// https://github.com/gorhill/httpswitchboard/issues/112 // https://github.com/gorhill/httpswitchboard/issues/112
pageStore.recordRequest('doc', requestURL, block); pageStore.recordRequest('doc', requestURL, block);
µm.updateBadgeAsync(tabId);
pageStore.updateBadgeAsync();
// If it's a blacklisted frame, redirect to frame.html // If it's a blacklisted frame, redirect to frame.html
// rhill 2013-11-05: The root frame contains a link to noop.css, this // rhill 2013-11-05: The root frame contains a link to noop.css, this
@ -308,11 +291,8 @@ var onBeforeRequestHandler = function(details) {
// to scope on unknown scheme? Etc. // to scope on unknown scheme? Etc.
// https://github.com/gorhill/httpswitchboard/issues/191 // https://github.com/gorhill/httpswitchboard/issues/191
// https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275 // https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275
var tabId = details.tabId;
var pageStore = µm.pageStatsFromTabId(tabId);
if ( !pageStore ) {
pageStore = µm.pageStatsFromTabId(µm.behindTheSceneTabId);
}
var pageStore = µm.mustPageStoreFromTabId(details.tabId);
var tabId = pageStore.tabId;
// Disallow request as per temporary matrix? // Disallow request as per temporary matrix?
var block = µm.mustBlock(pageStore.pageHostname, requestHostname, requestType); var block = µm.mustBlock(pageStore.pageHostname, requestHostname, requestType);
@ -324,8 +304,7 @@ var onBeforeRequestHandler = function(details) {
// been constructed for logging purpose. Use this synthetic URL if // been constructed for logging purpose. Use this synthetic URL if
// it is available. // it is available.
pageStore.recordRequest(requestType, requestURL, block); pageStore.recordRequest(requestType, requestURL, block);
µm.updateBadgeAsync(tabId);
pageStore.updateBadgeAsync();
// whitelisted? // whitelisted?
if ( !block ) { if ( !block ) {
@ -357,7 +336,6 @@ var onBeforeRequestHandler = function(details) {
// Sanitize outgoing headers as per user settings. // Sanitize outgoing headers as per user settings.
var onBeforeSendHeadersHandler = function(details) { var onBeforeSendHeadersHandler = function(details) {
var µm = µMatrix; var µm = µMatrix;
// console.debug('onBeforeSendHeadersHandler()> "%s": %o', details.url, details); // console.debug('onBeforeSendHeadersHandler()> "%s": %o', details.url, details);
@ -369,12 +347,8 @@ var onBeforeSendHeadersHandler = function(details) {
// to scope on unknown scheme? Etc. // to scope on unknown scheme? Etc.
// https://github.com/gorhill/httpswitchboard/issues/191 // https://github.com/gorhill/httpswitchboard/issues/191
// https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275 // https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275
var tabId = details.tabId;
var pageStore = µm.pageStatsFromTabId(tabId);
if ( !pageStore ) {
tabId = µm.behindTheSceneTabId;
pageStore = µm.pageStatsFromTabId(tabId);
}
var pageStore = µm.mustPageStoreFromTabId(details.tabId);
var tabId = pageStore.tabId;
// https://github.com/gorhill/httpswitchboard/issues/342 // https://github.com/gorhill/httpswitchboard/issues/342
// Is this hyperlink auditing? // Is this hyperlink auditing?
@ -404,7 +378,7 @@ var onBeforeSendHeadersHandler = function(details) {
if ( linkAuditor !== '' ) { if ( linkAuditor !== '' ) {
var block = µm.userSettings.processHyperlinkAuditing; var block = µm.userSettings.processHyperlinkAuditing;
pageStore.recordRequest('other', requestURL + '{Ping-To:' + linkAuditor + '}', block); pageStore.recordRequest('other', requestURL + '{Ping-To:' + linkAuditor + '}', block);
µm.updateBadgeAsync(tabId);
pageStore.updateBadgeAsync();
if ( block ) { if ( block ) {
µm.hyperlinkAuditingFoiledCounter += 1; µm.hyperlinkAuditingFoiledCounter += 1;
return { 'cancel': true }; return { 'cancel': true };
@ -464,11 +438,10 @@ var foilRefererHeaders = function(µm, toHostname, details) {
// https://github.com/gorhill/httpswitchboard/issues/35 // https://github.com/gorhill/httpswitchboard/issues/35
var onHeadersReceived = function(details) { var onHeadersReceived = function(details) {
// console.debug('onHeadersReceived()> "%s": %o', details.url, details); // console.debug('onHeadersReceived()> "%s": %o', details.url, details);
// Ignore schemes other than 'http...' // Ignore schemes other than 'http...'
if ( details.url.slice(0, 4) !== 'http' ) {
if ( details.url.lastIndexOf('http', 0) !== 0 ) {
return; return;
} }
@ -484,30 +457,24 @@ var onHeadersReceived = function(details) {
/******************************************************************************/ /******************************************************************************/
var onMainDocHeadersReceived = function(details) { var onMainDocHeadersReceived = function(details) {
// https://github.com/gorhill/uMatrix/issues/145
// Check if the main_frame is a download
if ( headerValue(details.responseHeaders, 'content-disposition').lastIndexOf('attachment', 0) === 0 ) {
µb.tabContextManager.unpush(details.tabId, details.url);
}
// console.debug('onMainDocHeadersReceived()> "%s": %o', details.url, details); // console.debug('onMainDocHeadersReceived()> "%s": %o', details.url, details);
var µm = µMatrix; var µm = µMatrix;
// Do not ignore traffic outside tabs.
// https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275
var tabId = details.tabId;
if ( tabId < 0 ) {
tabId = µm.behindTheSceneTabId;
}
var µmuri = µm.URI.set(details.url);
var requestURL = µmuri.normalizedURI();
var requestScheme = µmuri.scheme;
var requestHostname = µmuri.hostname;
// rhill 2013-12-07: // rhill 2013-12-07:
// Apparently in Opera, onBeforeRequest() is triggered while the // Apparently in Opera, onBeforeRequest() is triggered while the
// URL is not yet bound to a tab (-1), which caused the code here // URL is not yet bound to a tab (-1), which caused the code here
// to not be able to lookup the pageStats. So let the code here bind
// to not be able to lookup the page store. So let the code here bind
// the page to a tab if not done yet. // the page to a tab if not done yet.
// https://github.com/gorhill/httpswitchboard/issues/75 // https://github.com/gorhill/httpswitchboard/issues/75
µm.bindTabToPageStats(tabId, requestURL);
// TODO: check this works fine on Opera
// Re-classify orphan HTTP requests as behind-the-scene requests. There is // Re-classify orphan HTTP requests as behind-the-scene requests. There is
// not much else which can be done, because there are URLs // not much else which can be done, because there are URLs
@ -516,12 +483,12 @@ var onMainDocHeadersReceived = function(details) {
// to scope on unknown scheme? Etc. // to scope on unknown scheme? Etc.
// https://github.com/gorhill/httpswitchboard/issues/191 // https://github.com/gorhill/httpswitchboard/issues/191
// https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275 // https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275
var pageStats = µm.pageStatsFromTabId(tabId);
if ( !pageStats ) {
tabId = µm.behindTheSceneTabId;
pageStats = µm.pageStatsFromTabId(tabId);
}
var pageStore = µm.mustPageStoreFromTabId(details.tabId);
var tabId = pageStore.tabId;
var µmuri = µm.URI.set(details.url);
var requestURL = µmuri.normalizedURI();
var requestScheme = µmuri.scheme;
var requestHostname = µmuri.hostname;
var headers = details.responseHeaders; var headers = details.responseHeaders;
// Simplify code paths by splitting func in two different handlers, one // Simplify code paths by splitting func in two different handlers, one
@ -530,7 +497,7 @@ var onMainDocHeadersReceived = function(details) {
// https://github.com/gorhill/httpswitchboard/issues/112 // https://github.com/gorhill/httpswitchboard/issues/112
// rhill 2014-02-10: Handle all redirects. // rhill 2014-02-10: Handle all redirects.
// https://github.com/gorhill/httpswitchboard/issues/188 // https://github.com/gorhill/httpswitchboard/issues/188
if ( /\s+30[12378]\s+/.test(details.statusLine) ) {
if ( /\b30[12378]\b/.test(details.statusLine) ) {
var i = headerIndexFromName('location', headers); var i = headerIndexFromName('location', headers);
if ( i >= 0 ) { if ( i >= 0 ) {
// rhill 2014-01-20: Be ready to handle relative URLs. // rhill 2014-01-20: Be ready to handle relative URLs.
@ -557,22 +524,22 @@ var onMainDocHeadersReceived = function(details) {
} }
while ( destinationURL = mainFrameStack.pop() ) { while ( destinationURL = mainFrameStack.pop() ) {
pageStats.recordRequest('doc', destinationURL, false);
pageStore.recordRequest('doc', destinationURL, false);
} }
µm.updateBadgeAsync(tabId);
pageStore.updateBadgeAsync();
} }
// Maybe modify inbound headers // Maybe modify inbound headers
var csp = ''; var csp = '';
// Enforce strict HTTPS? // Enforce strict HTTPS?
if ( requestScheme === 'https' && µm.tMatrix.evaluateSwitchZ('https-strict', pageStats.pageHostname) ) {
if ( requestScheme === 'https' && µm.tMatrix.evaluateSwitchZ('https-strict', pageStore.pageHostname) ) {
csp += "default-src chrome-search: data: https: wss: 'unsafe-eval' 'unsafe-inline';"; csp += "default-src chrome-search: data: https: wss: 'unsafe-eval' 'unsafe-inline';";
} }
// https://github.com/gorhill/httpswitchboard/issues/181 // https://github.com/gorhill/httpswitchboard/issues/181
pageStats.pageScriptBlocked = µm.mustBlock(pageStats.pageHostname, requestHostname, 'script');
if ( pageStats.pageScriptBlocked ) {
pageStore.pageScriptBlocked = µm.mustBlock(pageStore.pageHostname, requestHostname, 'script');
if ( pageStore.pageScriptBlocked ) {
// If javascript not allowed, say so through a `Content-Security-Policy` directive. // If javascript not allowed, say so through a `Content-Security-Policy` directive.
// console.debug('onMainDocHeadersReceived()> PAGE CSP "%s": %o', details.url, details); // console.debug('onMainDocHeadersReceived()> PAGE CSP "%s": %o', details.url, details);
csp += " script-src 'none'"; csp += " script-src 'none'";
@ -599,25 +566,13 @@ var onSubDocHeadersReceived = function(details) {
// Do not ignore traffic outside tabs. // Do not ignore traffic outside tabs.
// https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275 // https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275
var tabId = details.tabId; var tabId = details.tabId;
if ( tabId < 0 ) {
tabId = µm.behindTheSceneTabId;
}
// Re-classify orphan HTTP requests as behind-the-scene requests. There is
// not much else which can be done, because there are URLs
// which cannot be handled by HTTP Switchboard, i.e. `opera://startpage`,
// as this would lead to complications with no obvious solution, like how
// to scope on unknown scheme? Etc.
// https://github.com/gorhill/httpswitchboard/issues/191
// https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275
var pageStats = µm.pageStatsFromTabId(tabId);
if ( !pageStats ) {
tabId = µm.behindTheSceneTabId;
pageStats = µm.pageStatsFromTabId(tabId);
var tabContext = µm.tabContextManager.lookup(tabId);
if ( tabContext === null ) {
return;
} }
// Evaluate // Evaluate
if ( µm.mustAllow(pageStats.pageHostname, µm.hostnameFromURL(details.url), 'script') ) {
if ( µm.mustAllow(tabContext.rootHostname, µm.hostnameFromURL(details.url), 'script') ) {
return; return;
} }
@ -644,7 +599,7 @@ var onSubDocHeadersReceived = function(details) {
details.responseHeaders.push({ details.responseHeaders.push({
'name': 'Content-Security-Policy', 'name': 'Content-Security-Policy',
'value': 'sandbox allow-forms allow-same-origin allow-popups allow-top-navigation'
'value': "script-src 'none'"
}); });
return { responseHeaders: details.responseHeaders }; return { responseHeaders: details.responseHeaders };
@ -652,6 +607,18 @@ var onSubDocHeadersReceived = function(details) {
/******************************************************************************/ /******************************************************************************/
var headerValue = function(headers, name) {
var i = headers.length;
while ( i-- ) {
if ( headers[i].name.toLowerCase() === name ) {
return headers[i].value.trim();
}
}
return '';
};
/******************************************************************************/
var onErrorOccurredHandler = function(details) { var onErrorOccurredHandler = function(details) {
// console.debug('onErrorOccurred()> "%s": %o', details.url, details); // console.debug('onErrorOccurred()> "%s": %o', details.url, details);
var requestType = requestTypeNormalizer[details.type] || 'other'; var requestType = requestTypeNormalizer[details.type] || 'other';
@ -662,7 +629,7 @@ var onErrorOccurredHandler = function(details) {
} }
var µm = µMatrix; var µm = µMatrix;
var pageStats = µm.pageStatsFromPageUrl(details.url);
var pageStats = µm.pageStoreFromTabId(details.tabId);
if ( !pageStats ) { if ( !pageStats ) {
return; return;
} }
@ -684,7 +651,7 @@ var onErrorOccurredHandler = function(details) {
while ( destinationURL = mainFrameStack.pop() ) { while ( destinationURL = mainFrameStack.pop() ) {
pageStats.recordRequest('doc', destinationURL, false); pageStats.recordRequest('doc', destinationURL, false);
} }
µm.updateBadgeAsync(details.tabId);
pageStats.updateBadgeAsync();
}; };
/******************************************************************************/ /******************************************************************************/

Loading…
Cancel
Save