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

pull/2/head
gorhill 9 years ago
parent cb29f10173
commit 7cd060a15f

@ -1002,9 +1002,10 @@ var httpObserver = {
ABORT: Components.results.NS_BINDING_ABORTED,
ACCEPT: Components.results.NS_SUCCEEDED,
// 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: {
1: 'other',
2: 'script',
@ -1128,13 +1129,15 @@ var httpObserver = {
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;
}*/
}
}
var onBeforeSendHeaders = vAPI.net.onBeforeSendHeaders;
@ -1158,7 +1161,7 @@ var httpObserver = {
}
var URI = channel.URI;
var channelData, result;
var channelData, type, result;
if ( topic === 'http-on-examine-response' ) {
if ( !(channel instanceof Ci.nsIWritablePropertyBag) ) {
@ -1175,7 +1178,8 @@ var httpObserver = {
return;
}
if ( (1 << channelData[4] & this.VALID_CSP_TARGETS) === 0 ) {
type = this.frameTypeMap[channelData[4]];
if ( !type ) {
return;
}
@ -1191,7 +1195,9 @@ var httpObserver = {
hostname: URI.asciiHost,
parentFrameId: channelData[1],
responseHeaders: result ? [{name: topic, value: result}] : [],
statusLine: channel.responseStatus.toString(),
tabId: channelData[3],
type: type,
url: URI.asciiSpec
});
@ -1469,7 +1475,7 @@ vAPI.toolbarButton.init = function() {
'font-size: 9px;',
'font-weight: bold;',
'color: #fff;',
'background: #666;',
'background: #000;',
'content: attr(badge);',
'}'
);
@ -1503,7 +1509,7 @@ vAPI.toolbarButton.init = function() {
this.CUIEvents.updateBadgeStyle = function() {
var css = [
'background: #666',
'background: #000',
'color: #fff'
].join(';');
@ -1592,18 +1598,19 @@ vAPI.toolbarButton.onBeforeCreated = function(doc) {
if ( updateTimer ) {
return;
}
updateTimer = setTimeout(resizePopup, 10);
};
var resizePopup = function() {
updateTimer = null;
var body = iframe.contentDocument.body;
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
// 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';
if ( iframe.clientHeight !== body.clientHeight || iframe.clientWidth !== body.clientWidth ) {
if ( iframe.clientHeight !== height || iframe.clientWidth !== body.clientWidth ) {
delayedResize();
}
};

@ -133,30 +133,12 @@ return asyncJobManager;
// Update visual of extension icon.
// 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);
if ( pageStore ) {
pageStore.updateBadge(tabId);
return;
}
vAPI.setIcon(tabId, null, '?');
};
var updateBadgeAsync = function(tabId) {
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
return;
}
µm.asyncJobs.add('updateBadge-' + tabId, tabId, updateBadge, 500);
};
return updateBadgeAsync;
})();
µMatrix.updateBadgeAsync = function(tabId) {
var pageStore = this.pageStoreFromTabId(tabId);
if ( pageStore ) {
pageStore.updateBadge();
}
};
/******************************************************************************/

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

@ -197,8 +197,7 @@ var recordPageCookiesAsync = function(pageStats) {
if ( !pageStats ) {
return;
}
var pageURL = µm.pageUrlFromPageStats(pageStats);
recordPageCookiesQueue[pageURL] = pageStats;
recordPageCookiesQueue[pageStats.pageUrl] = pageStats;
µm.asyncJobs.add(
'cookieHunterPageRecord',
null,
@ -220,6 +219,10 @@ var cookieLogEntryBuilder = [
];
var recordPageCookie = function(pageStore, cookieKey) {
if ( vAPI.isBehindTheSceneTabId(pageStore.tabId) ) {
return;
}
var cookieEntry = cookieDict[cookieKey];
var block = µm.mustBlock(pageStore.pageHostname, cookieEntry.hostname, 'cookie');
@ -263,8 +266,7 @@ var removePageCookiesAsync = function(pageStats) {
if ( !pageStats ) {
return;
}
var pageURL = µm.pageUrlFromPageStats(pageStats);
removePageCookiesQueue[pageURL] = pageStats;
removePageCookiesQueue[pageStats.pageUrl] = pageStats;
µm.asyncJobs.add(
'cookieHunterPageRemove',
null,
@ -508,13 +510,13 @@ vAPI.cookies.onChanged = function(changeInfo) {
// 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.
var pageStores = µm.pageStats;
var pageStores = µm.pageStores;
var pageStore;
for ( var pageURL in pageStores ) {
if ( pageStores.hasOwnProperty(pageURL) === false ) {
for ( var tabId in pageStores ) {
if ( pageStores.hasOwnProperty(tabId) === false ) {
continue;
}
pageStore = pageStores[pageURL];
pageStore = pageStores[tabId];
if ( !cookieMatchDomains(cookieKey, pageStore.allHostnamesString) ) {
continue;
}

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

@ -156,18 +156,18 @@ var matrixSnapshot = function(tabId, details) {
}
};
var tabContext = µm.tabContextManager.lookup(tabId);
// 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 ) {
tabId = µm.behindTheSceneTabId;
}
if (
tabContext.rawURL.lastIndexOf(vAPI.getURL(''), 0) === 0 ||
tabContext.rawURL === µm.behindTheSceneURL
) {
tabId = µm.behindTheSceneTabId;
}
var pageStore = µm.pageStatsFromTabId(tabId);
if ( !pageStore ) {
var pageStore = µm.pageStoreFromTabId(tabId);
if ( pageStore === null ) {
return r;
}
@ -371,8 +371,8 @@ var contentScriptSummaryHandler = function(tabId, details) {
if ( !details || !details.locationURL ) {
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 frameURL = µmuri.normalizedURI();
var frameHostname = µmuri.hostname;
@ -384,7 +384,7 @@ var contentScriptSummaryHandler = function(tabId, details) {
// scripts
// https://github.com/gorhill/httpswitchboard/issues/25
if ( pageStats && inlineScriptBlocked ) {
if ( pageStore && inlineScriptBlocked ) {
urls = details.scriptSources;
for ( url in urls ) {
if ( !urls.hasOwnProperty(url) ) {
@ -394,8 +394,9 @@ var contentScriptSummaryHandler = function(tabId, details) {
url = frameURL + '{inline_script}';
}
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
@ -403,28 +404,29 @@ var contentScriptSummaryHandler = function(tabId, details) {
// this code was put in).
// plugins
// https://github.com/gorhill/httpswitchboard/issues/25
if ( pageStats ) {
if ( pageStore ) {
urls = details.pluginSources;
for ( url in urls ) {
if ( !urls.hasOwnProperty(url) ) {
continue;
}
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
µm.onPageLoadCompleted(pageURL);
µm.onPageLoadCompleted(tabId);
};
/******************************************************************************/
var contentScriptLocalStorageHandler = function(pageURL) {
var contentScriptLocalStorageHandler = function(tabId, pageURL) {
var µmuri = µm.URI.set(pageURL);
var response = µm.mustBlock(µm.scopeFromURL(pageURL), µmuri.hostname, 'cookie');
µm.recordFromPageUrl(
pageURL,
µm.recordFromTabId(
tabId,
'cookie',
µmuri.rootURL() + '/{localStorage}',
response
@ -452,13 +454,11 @@ var onMessage = function(request, sender, callback) {
switch ( request.what ) {
case 'contentScriptHasLocalStorage':
response = contentScriptLocalStorageHandler(request.url);
µm.updateBadgeAsync(tabId);
response = contentScriptLocalStorageHandler(tabId, request.url);
break;
case 'contentScriptSummary':
contentScriptSummaryHandler(tabId, request);
µm.updateBadgeAsync(tabId);
break;
case 'checkScriptBlacklisted':
@ -699,22 +699,46 @@ vAPI.messaging.listen('hosts-files.js', onMessage);
(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
var getRequestLog = function(pageURL) {
var getRequestLog = function(tabId) {
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 ) {
continue;
}
pageURL = pageStore.pageUrl;
pageRequestLog = [];
logEntries = pageStore.requests.getLoggedRequests();
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;
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 µm = µMatrix;
// Async
switch ( request.what ) {
default:
@ -761,14 +785,11 @@ var onMessage = function(request, sender, callback) {
switch ( request.what ) {
case 'getPageURLs':
response = {
pageURLs: Object.keys(µm.pageUrlToTabId),
behindTheSceneURL: µm.behindTheSceneURL
};
response = getTabURLs();
break;
case 'getStats':
var pageStore = µm.pageStats[request.pageURL];
var pageStore = µm.pageStores[request.tabId];
response = {
globalNetStats: µm.requestStats,
pageNetStats: pageStore ? pageStore.requestStats : null,
@ -782,11 +803,11 @@ var onMessage = function(request, sender, callback) {
break;
case 'getRequestLogs':
response = getRequestLog(request.pageURL);
response = getRequestLog(request.tabId);
break;
case 'clearRequestLogs':
clearRequestLog(request.pageURL);
clearRequestLog(request.tabId);
break;
default:

@ -445,28 +445,30 @@ var pageStoreJunkyard = [];
/******************************************************************************/
var pageStoreFactory = function(pageUrl) {
var pageStoreFactory = function(tabContext) {
var entry = pageStoreJunkyard.pop();
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.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.thirdpartyScript = false;
this.requests = µm.PageRequestStats.factory();
@ -477,8 +479,8 @@ PageStore.prototype.init = function(pageUrl) {
this.distinctRequestCount = 0;
this.perLoadAllowedRequestCount = 0;
this.perLoadBlockedRequestCount = 0;
this.boundCount = 0;
this.obsoleteAfter = 0;
this.incinerationTimer = null;
this.updateBadgeTimer = null;
return this;
};
@ -486,12 +488,24 @@ PageStore.prototype.init = function(pageUrl) {
PageStore.prototype.dispose = function() {
this.requests.dispose();
this.rawUrl = '';
this.pageUrl = '';
this.pageHostname = '';
this.pageDomain = '';
this.domains = {};
this.allHostnamesString = ' ';
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 ) {
pageStoreJunkyard.push(this);
}
@ -550,24 +564,35 @@ 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
// incrementally, as chromium overwrite the icon at some point without
// notifying me, and this causes internal cached state to be out of sync.
PageStore.prototype.updateBadge = function(tabId) {
var iconId = null;
var badgeStr = '';
var total = this.perLoadAllowedRequestCount + this.perLoadBlockedRequestCount;
if ( total ) {
var squareSize = 19;
var greenSize = squareSize * Math.sqrt(this.perLoadAllowedRequestCount / total);
iconId = greenSize < squareSize/2 ? Math.ceil(greenSize) : Math.floor(greenSize);
badgeStr = µm.formatCount(this.distinctRequestCount);
}
vAPI.setIcon(tabId, iconId, badgeStr);
};
PageStore.prototype.updateBadgeAsync = (function() {
var updateBadge = function() {
this.updateBadgeTimer = null;
var iconId = null;
var badgeStr = '';
var total = this.perLoadAllowedRequestCount + this.perLoadBlockedRequestCount;
if ( total ) {
var squareSize = 19;
var greenSize = squareSize * Math.sqrt(this.perLoadAllowedRequestCount / total);
iconId = greenSize < squareSize/2 ? Math.ceil(greenSize) : Math.floor(greenSize);
badgeStr = µm.formatCount(this.distinctRequestCount);
}
vAPI.setIcon(this.tabId, iconId, badgeStr);
};
return function() {
if ( this.updateBadgeTimer === null ) {
this.updateBadgeTimer = setTimeout(updateBadge.bind(this), 500);
}
};
})();
/******************************************************************************/

@ -31,10 +31,8 @@
(function() {
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
var bindTabs = function(tabs) {
var tab;
var i = tabs.length;
// console.debug('start.js > binding %d tabs', 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();
};

@ -55,13 +55,13 @@ var µm = µMatrix;
return uri.normalizedURI();
}
var url = 'http://' + scheme + '-scheme/';
var url = scheme + '-scheme';
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.normalURL = µm.normalizePageURL(this.tabId, this.rawURL);
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
// fix this. When all fail, the behind-the-scene context is returned.
var lookup = function(tabId, url) {
var mustLookup = function(tabId, url) {
var entry;
if ( url !== undefined ) {
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
@ -350,7 +350,7 @@ housekeep itself.
unpush: unpush,
commit: commit,
lookup: lookup,
exists: exists,
mustLookup: mustLookup,
createContext: createContext
};
})();
@ -365,9 +365,15 @@ vAPI.tabs.onNavigation = function(details) {
if ( details.frameId !== 0 ) {
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;
}
// This actually can happen
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
return;
}
if ( 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
@ -396,7 +407,7 @@ vAPI.tabs.onUpdated = function(tabId, changeInfo, tab) {
// unblocked when user un-blacklist the hostname.
// https://github.com/gorhill/httpswitchboard/issues/198
if ( changeInfo.status === 'complete' ) {
var pageStats = µm.pageStatsFromTabId(tabId);
var pageStats = µm.pageStoreFromTabId(tabId);
if ( pageStats ) {
pageStats.state = µm.computeTabState(tabId);
}
@ -406,10 +417,10 @@ vAPI.tabs.onUpdated = function(tabId, changeInfo, tab) {
/******************************************************************************/
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
µ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
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
// Don't rebind pages blocked by µMatrix.
var blockedRootFramePrefix = this.webRequest.blockedRootFramePrefix;
if ( rawURL.lastIndexOf(blockedRootFramePrefix, 0) === 0 ) {
if ( tabContext.rawURL.lastIndexOf(blockedRootFramePrefix, 0) === 0 ) {
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
if ( this.tabIdToPageUrl.hasOwnProperty(tabId) ) {
var previousPageURL = this.tabIdToPageUrl[tabId];
if ( pageStore !== null ) {
// 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
// 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
// https://github.com/gorhill/uMatrix/issues/72
// 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;
return pageStore;
}
if ( context === 'updated' && pageStore.pageHostname === tabContext.rootHostname ) {
pageStore.rawURL = tabContext.rawURL;
pageStore.normalURL = normalURL;
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);
// rhill 2013-11-24: Never ever rebind chromium-behind-the-scene
// virtual tab.
// https://github.com/gorhill/httpswitchboard/issues/67
if ( tabId === this.behindTheSceneTabId ) {
return pageStore;
// https://github.com/gorhill/uMatrix/issues/37
pageStore.updateBadgeAsync();
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;
}
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;
};
/******************************************************************************/
µm.unbindTabFromPageStats = function(tabId) {
if ( this.tabIdToPageUrl.hasOwnProperty(tabId) === false ) {
µm.incineratePageStore = function(tabId, pageURL) {
if ( this.pageStoreCemetery.hasOwnProperty(tabId) === false ) {
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 pageStoreCrypt[pageURL];
if ( Object.keys(pageStoreCrypt).length === 0 ) {
delete this.pageStoreCemetery[tabId];
}
delete this.tabIdToPageUrl[tabId];
delete this.pageUrlToTabId[pageURL];
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;
}
// 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;
while ( i-- ) {
tabId = chromeTabs[i].id;
if ( µm.tabExists(tabId) ) {
if ( µm.pageStores.hasOwnProperty(tabId) ) {
µm.smartReloadTab(tabId);
}
}
@ -627,7 +666,7 @@ vAPI.tabs.registerListeners();
// Reload content of a tab
µm.smartReloadTab = function(tabId) {
var pageStats = this.pageStatsFromTabId(tabId);
var pageStats = this.pageStoreFromTabId(tabId);
if ( !pageStats ) {
//console.error('HTTP Switchboard> µMatrix.smartReloadTab(): page stats for tab id %d not found', tabId);
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) {
var pageStats = this.pageStatsFromTabId(tabId);
var pageStats = this.pageStoreFromTabId(tabId);
if ( !pageStats ) {
//console.error('tab.js > µMatrix.computeTabState(): page stats for tab id %d not found', tabId);
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) {
var pageStores = this.pageStats;
var pageStores = this.pageStores;
for ( var pageURL in pageStores ) {
if ( pageStores.hasOwnProperty(pageURL) ) {
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
);
})();
/******************************************************************************/
})();

@ -39,57 +39,55 @@ var rootFrameReplacement = [
'<meta charset="utf-8" />',
'<style>',
'@font-face {',
'font-family:httpsb;',
'font-style:normal;',
'font-weight:400;',
'src: local("httpsb"),url("',
µMatrix.fontCSSURL,
'") format("truetype");',
'font-family:httpsb;',
'font-style:normal;',
'font-weight:400;',
'src: local("httpsb"),url("', µMatrix.fontCSSURL, '") format("truetype");',
'}',
'body {',
'margin:0;',
'border:0;',
'padding:0;',
'font:15px httpsb,sans-serif;',
'width:100%;',
'height:100%;',
'background-color:transparent;',
'background-size:10px 10px;',
'background-image:',
'repeating-linear-gradient(',
'-45deg,',
'rgba(204,0,0,0.5),rgba(204,0,0,0.5) 24%,',
'transparent 26%,transparent 49%,',
'rgba(204,0,0,0.5) 51%,rgba(204,0,0,0.5) 74%,',
'transparent 76%,transparent',
');',
'text-align: center;',
'margin:0;',
'border:0;',
'padding:0;',
'font:15px httpsb,sans-serif;',
'width:100%;',
'height:100%;',
'background-color:transparent;',
'background-size:10px 10px;',
'background-image:',
'repeating-linear-gradient(',
'-45deg,',
'rgba(204,0,0,0.5),rgba(204,0,0,0.5) 24%,',
'transparent 26%,transparent 49%,',
'rgba(204,0,0,0.5) 51%,rgba(204,0,0,0.5) 74%,',
'transparent 76%,transparent',
');',
'text-align: center;',
'}',
'#p {',
'margin:8px;',
'padding:4px;',
'display:inline-block;',
'background-color:white;',
'margin:8px;',
'padding:4px;',
'display:inline-block;',
'background-color:white;',
'}',
'#t {',
'margin:2px;',
'border:0;',
'padding:0 2px;',
'display:inline-block;',
'margin:2px;',
'border:0;',
'padding:0 2px;',
'display:inline-block;',
'}',
'#t b {',
'padding:0 4px;',
'background-color:#eee;',
'font-weight:normal;',
'padding:0 4px;',
'background-color:#eee;',
'font-weight:normal;',
'}',
'</style>',
'<link href="{{cssURL}}?url={{originalURL}}&hostname={{hostname}}&t={{now}}" rel="stylesheet" type="text/css">',
'<title>Blocked by &mu;Matrix</title>',
'</head>',
'<body>',
'<div id="p">',
'<div id="t"><b>{{hostname}}</b> blocked by &mu;Matrix</div>',
'</div>',
'<div id="p">',
'<div id="t"><b>{{hostname}}</b> blocked by &mu;Matrix</div>',
'</div>',
'</body>',
'</html>'
].join('');
@ -100,56 +98,54 @@ var subFrameReplacement = [
'<head>',
'<meta charset="utf-8" />',
'<style>',
'@font-face{',
'font-family:httpsb;',
'font-style:normal;',
'font-weight:400;',
'src:local("httpsb"),url("',
µMatrix.fontCSSURL,
'") format("truetype");',
'@font-face{',
'font-family:httpsb;',
'font-style:normal;',
'font-weight:400;',
'src:local("httpsb"),url("', µMatrix.fontCSSURL, '") format("truetype");',
'}',
'body{',
'margin:0;',
'border:0;',
'padding:0;',
'font:13px httpsb,sans-serif;',
'margin:0;',
'border:0;',
'padding:0;',
'font:13px httpsb,sans-serif;',
'}',
'#bg{',
'border:1px dotted {{subframeColor}};',
'position:absolute;',
'top:0;',
'right:0;',
'bottom:0;',
'left:0;',
'background-color:transparent;',
'background-size:10px 10px;',
'background-image:',
'repeating-linear-gradient(',
'-45deg,',
'{{subframeColor}},{{subframeColor}} 24%,',
'transparent 25%,transparent 49%,',
'{{subframeColor}} 50%,{{subframeColor}} 74%,',
'transparent 75%,transparent',
');',
'opacity:{{subframeOpacity}};',
'text-align:center;',
'border:1px dotted {{subframeColor}};',
'position:absolute;',
'top:0;',
'right:0;',
'bottom:0;',
'left:0;',
'background-color:transparent;',
'background-size:10px 10px;',
'background-image:',
'repeating-linear-gradient(',
'-45deg,',
'{{subframeColor}},{{subframeColor}} 24%,',
'transparent 25%,transparent 49%,',
'{{subframeColor}} 50%,{{subframeColor}} 74%,',
'transparent 75%,transparent',
');',
'opacity:{{subframeOpacity}};',
'text-align:center;',
'}',
'#bg > div{',
'display:inline-block;',
'background-color:rgba(255,255,255,1);',
'display:inline-block;',
'background-color:rgba(255,255,255,1);',
'}',
'#bg > div > a {',
'padding:0 2px;',
'display:inline-block;',
'color:white;',
'background-color:{{subframeColor}};',
'text-decoration:none;',
'padding:0 2px;',
'display:inline-block;',
'color:white;',
'background-color:{{subframeColor}};',
'text-decoration:none;',
'}',
'</style>',
'<title>Blocked by &mu;Matrix</title>',
'</head>',
'<body title="&ldquo;{{hostname}}&rdquo; frame\nblocked by &mu;Matrix">',
'<div id="bg"><div><a href="{{frameSrc}}" target="_blank">{{hostname}}</a></div></div>',
'<div id="bg"><div><a href="{{frameSrc}}" target="_blank">{{hostname}}</a></div></div>',
'</body>',
'</html>'
].join('');
@ -202,44 +198,31 @@ var onBeforeRootFrameRequestHandler = function(details) {
µ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?
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);
// whitelisted?
// Not blocked
if ( !block ) {
// rhill 2013-11-07: Senseless to do this for behind-the-scene requests.
// rhill 2013-12-03: Do this here only for root frames.
if ( tabId !== µm.behindTheSceneTabId ) {
µm.cookieHunter.recordPageCookies(pageStore);
}
µm.cookieHunter.recordPageCookies(pageStore);
return;
}
// blacklisted
// Blocked
// rhill 2014-01-15: Delay logging of non-blocked top `main_frame`
// requests, in order to ensure any potential redirects is reported
// in proper chronological order.
// https://github.com/gorhill/httpswitchboard/issues/112
pageStore.recordRequest('doc', requestURL, block);
µm.updateBadgeAsync(tabId);
pageStore.updateBadgeAsync();
// If it's a blacklisted frame, redirect to frame.html
// 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.
// https://github.com/gorhill/httpswitchboard/issues/191
// 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?
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
// it is available.
pageStore.recordRequest(requestType, requestURL, block);
µm.updateBadgeAsync(tabId);
pageStore.updateBadgeAsync();
// whitelisted?
if ( !block ) {
@ -357,7 +336,6 @@ var onBeforeRequestHandler = function(details) {
// Sanitize outgoing headers as per user settings.
var onBeforeSendHeadersHandler = function(details) {
var µm = µMatrix;
// console.debug('onBeforeSendHeadersHandler()> "%s": %o', details.url, details);
@ -369,12 +347,8 @@ var onBeforeSendHeadersHandler = function(details) {
// to scope on unknown scheme? Etc.
// https://github.com/gorhill/httpswitchboard/issues/191
// 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
// Is this hyperlink auditing?
@ -404,7 +378,7 @@ var onBeforeSendHeadersHandler = function(details) {
if ( linkAuditor !== '' ) {
var block = µm.userSettings.processHyperlinkAuditing;
pageStore.recordRequest('other', requestURL + '{Ping-To:' + linkAuditor + '}', block);
µm.updateBadgeAsync(tabId);
pageStore.updateBadgeAsync();
if ( block ) {
µm.hyperlinkAuditingFoiledCounter += 1;
return { 'cancel': true };
@ -464,11 +438,10 @@ var foilRefererHeaders = function(µm, toHostname, details) {
// https://github.com/gorhill/httpswitchboard/issues/35
var onHeadersReceived = function(details) {
// console.debug('onHeadersReceived()> "%s": %o', details.url, details);
// Ignore schemes other than 'http...'
if ( details.url.slice(0, 4) !== 'http' ) {
if ( details.url.lastIndexOf('http', 0) !== 0 ) {
return;
}
@ -484,30 +457,24 @@ var onHeadersReceived = 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);
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:
// Apparently in Opera, onBeforeRequest() is triggered while the
// 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.
// 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
// 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.
// 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 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;
// 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
// rhill 2014-02-10: Handle all redirects.
// 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);
if ( i >= 0 ) {
// rhill 2014-01-20: Be ready to handle relative URLs.
@ -557,22 +524,22 @@ var onMainDocHeadersReceived = function(details) {
}
while ( destinationURL = mainFrameStack.pop() ) {
pageStats.recordRequest('doc', destinationURL, false);
pageStore.recordRequest('doc', destinationURL, false);
}
µm.updateBadgeAsync(tabId);
pageStore.updateBadgeAsync();
}
// Maybe modify inbound headers
var csp = '';
// 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';";
}
// 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.
// console.debug('onMainDocHeadersReceived()> PAGE CSP "%s": %o', details.url, details);
csp += " script-src 'none'";
@ -599,25 +566,13 @@ var onSubDocHeadersReceived = function(details) {
// 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;
}
// 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
if ( µm.mustAllow(pageStats.pageHostname, µm.hostnameFromURL(details.url), 'script') ) {
if ( µm.mustAllow(tabContext.rootHostname, µm.hostnameFromURL(details.url), 'script') ) {
return;
}
@ -644,7 +599,7 @@ var onSubDocHeadersReceived = function(details) {
details.responseHeaders.push({
'name': 'Content-Security-Policy',
'value': 'sandbox allow-forms allow-same-origin allow-popups allow-top-navigation'
'value': "script-src 'none'"
});
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) {
// console.debug('onErrorOccurred()> "%s": %o', details.url, details);
var requestType = requestTypeNormalizer[details.type] || 'other';
@ -662,7 +629,7 @@ var onErrorOccurredHandler = function(details) {
}
var µm = µMatrix;
var pageStats = µm.pageStatsFromPageUrl(details.url);
var pageStats = µm.pageStoreFromTabId(details.tabId);
if ( !pageStats ) {
return;
}
@ -684,7 +651,7 @@ var onErrorOccurredHandler = function(details) {
while ( destinationURL = mainFrameStack.pop() ) {
pageStats.recordRequest('doc', destinationURL, false);
}
µm.updateBadgeAsync(details.tabId);
pageStats.updateBadgeAsync();
};
/******************************************************************************/

Loading…
Cancel
Save