diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js
index 1492ed0a8..0f93259d7 100644
--- a/platform/chromium/vapi-background.js
+++ b/platform/chromium/vapi-background.js
@@ -79,7 +79,7 @@ vAPI.tabs = {};
/******************************************************************************/
-vAPI.isNoTabId = function(tabId) {
+vAPI.isBehindTheSceneTabId = function(tabId) {
return tabId.toString() === '-1';
};
@@ -102,8 +102,8 @@ vAPI.tabs.registerListeners = function() {
var popupCandidates = Object.create(null);
var PopupCandidate = function(details) {
- this.targetTabId = details.tabId;
- this.openerTabId = details.sourceTabId;
+ this.targetTabId = details.tabId.toString();
+ this.openerTabId = details.sourceTabId.toString();
this.targetURL = details.url;
this.selfDestructionTimer = null;
};
@@ -222,7 +222,12 @@ vAPI.tabs.get = function(tabId, callback) {
if ( typeof tabId === 'string' ) {
tabId = parseInt(tabId, 10);
}
- chrome.tabs.get(tabId, onTabReady);
+ if ( typeof tabId !== 'number' || isNaN(tabId) ) {
+ onTabReady(null);
+ }
+ else {
+ chrome.tabs.get(tabId, onTabReady);
+ }
return;
}
var onTabReceived = function(tabs) {
@@ -719,7 +724,8 @@ vAPI.onLoadAllCompleted = function() {
var i = tabs.length, tab;
while ( i-- ) {
tab = tabs[i];
- µb.bindTabToPageStats(tab.id, tab.url);
+ µb.tabContextManager.commit(tab.id, tab.url);
+ µb.bindTabToPageStats(tab.id);
// https://github.com/chrisaljoudi/uBlock/issues/129
scriptStart(tab.id);
}
diff --git a/platform/firefox/vapi-background.js b/platform/firefox/vapi-background.js
index a0d81b173..c28483c95 100644
--- a/platform/firefox/vapi-background.js
+++ b/platform/firefox/vapi-background.js
@@ -333,7 +333,7 @@ var tabWatcher = {
/******************************************************************************/
-vAPI.isNoTabId = function(tabId) {
+vAPI.isBehindTheSceneTabId = function(tabId) {
return tabId.toString() === '-1';
};
@@ -1986,6 +1986,7 @@ vAPI.onLoadAllCompleted = function() {
var tabId = this.tabs.getTabId(tab);
var browser = getBrowserForTab(tab);
+ µb.tabContextManager.commit(tabId, browser.currentURI.asciiSpec);
µb.bindTabToPageStats(tabId, browser.currentURI.asciiSpec);
browser.messageManager.sendAsyncMessage(
location.host + '-load-completed'
diff --git a/platform/safari/vapi-background.js b/platform/safari/vapi-background.js
index 756f858f9..2c311d339 100644
--- a/platform/safari/vapi-background.js
+++ b/platform/safari/vapi-background.js
@@ -239,7 +239,7 @@
/******************************************************************************/
- vAPI.isNoTabId = function(tabId) {
+ vAPI.isBehindTheSceneTabId = function(tabId) {
return tabId.toString() === this.noTabId;
};
@@ -256,13 +256,13 @@
tabId = vAPI.tabs.getTabId(e.target);
var details = {
targetURL: url,
- targetTabId: tabId,
+ targetTabId: tabId.toString(),
openerTabId: vAPI.tabs.popupCandidate
};
+ vAPI.tabs.popupCandidate = false;
if(vAPI.tabs.onPopup(details)) {
e.preventDefault();
if(vAPI.tabs.stack[details.openerTabId]) {
- vAPI.tabs.popupCandidate = false;
vAPI.tabs.stack[details.openerTabId].activate();
}
}
@@ -731,17 +731,18 @@
}
switch(e.message.type) {
case "popup":
- vAPI.tabs.popupCandidate = vAPI.tabs.getTabId(e.target);
- if(e.message.url === "about:blank") {
+ var openerTabId = vAPI.tabs.getTabId(e.target).toString();
+ var shouldBlock = !!vAPI.tabs.onPopup({
+ targetURL: e.message.url,
+ targetTabId: "preempt",
+ openerTabId: openerTabId
+ });
+ if(shouldBlock) {
e.message = false;
- return;
}
else {
- e.message = !vAPI.tabs.onPopup({
- targetURL: e.message.url,
- targetTabId: 0,
- openerTabId: vAPI.tabs.getTabId(e.target)
- });
+ vAPI.tabs.popupCandidate = openerTabId;
+ e.message = true;
}
break;
case "popstate":
diff --git a/platform/safari/vapi-client.js b/platform/safari/vapi-client.js
index b956ffc58..085e5ce03 100644
--- a/platform/safari/vapi-client.js
+++ b/platform/safari/vapi-client.js
@@ -260,7 +260,7 @@ x.setAttribute('src', block(val, 'image') ? 'data:image/gif;base64,R0lGODlhAQABA
return x;\
};\
open = function(u) {\
-return block(u, 'popup') ? null : wo.apply(this, arguments);\
+if(block(u, 'popup')) return {}; else return wo.apply(this, arguments);\
};\
XMLHttpRequest.prototype.open = function(m, u) {\
if(block(u, 'xmlhttprequest')) {throw 'InvalidAccessError'; return;}\
diff --git a/src/background.html b/src/background.html
index 07eb25d40..80faa1108 100644
--- a/src/background.html
+++ b/src/background.html
@@ -19,7 +19,6 @@
-
diff --git a/src/document-blocked.html b/src/document-blocked.html
deleted file mode 100644
index ee06972d9..000000000
--- a/src/document-blocked.html
+++ /dev/null
@@ -1,81 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/js/async.js b/src/js/async.js
index 7ef5cb2eb..c55a77f08 100644
--- a/src/js/async.js
+++ b/src/js/async.js
@@ -187,7 +187,7 @@ return asyncJobManager;
};
var updateBadgeAsync = function(tabId) {
- if ( vAPI.isNoTabId(tabId) ) {
+ if ( vAPI.isBehindTheSceneTabId(tabId) ) {
return;
}
µb.asyncJobs.add('updateBadge-' + tabId, tabId, updateBadge, 250);
diff --git a/src/js/document-blocked.js b/src/js/document-blocked.js
deleted file mode 100644
index d548a7c0e..000000000
--- a/src/js/document-blocked.js
+++ /dev/null
@@ -1,103 +0,0 @@
-/*******************************************************************************
-
- uBlock - a browser extension to block requests.
- Copyright (C) 2015 Raymond Hill
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see {http://www.gnu.org/licenses/}.
-
- Home: https://github.com/chrisaljoudi/uBlock
-*/
-
-/* global uDom */
-
-/******************************************************************************/
-
-(function() {
-
-'use strict';
-
-/******************************************************************************/
-
-var messager = vAPI.messaging.channel('document-blocked.js');
-var details = {};
-
-(function() {
- var matches = /details=([^&]+)/.exec(window.location.search);
- if ( matches === null ) {
- return;
- }
- details = JSON.parse(atob(matches[1]));
-})();
-
-/******************************************************************************/
-
-var proceedToURL = function() {
- window.location.replace(details.url);
-};
-
-/******************************************************************************/
-
-var proceedTemporary = function() {
- messager.send({
- what: 'temporarilyWhitelistDocument',
- url: details.url
- }, proceedToURL);
-};
-
-/******************************************************************************/
-
-var proceedPermanent = function() {
- messager.send({
- what: 'toggleHostnameSwitch',
- name: 'dontBlockDoc',
- hostname: details.hn,
- state: true
- }, proceedToURL);
-};
-
-/******************************************************************************/
-
-(function() {
- var matches = /^(.*)\{\{hostname\}\}(.*)$/.exec(vAPI.i18n('docblockedProceed'));
- if ( matches === null ) {
- return;
- }
- var proceed = uDom('#proceedTemplate').clone();
- proceed.descendants('span:nth-of-type(1)').text(matches[1]);
- proceed.descendants('span:nth-of-type(2)').text(details.hn);
- proceed.descendants('span:nth-of-type(3)').text(matches[2]);
- uDom('#proceed').append(proceed);
-})();
-
-/******************************************************************************/
-
-uDom('.what').text(details.url);
-uDom('#why').text(details.why.slice(3));
-
-if ( window.history.length > 1 ) {
- uDom('#back').on('click', function() { window.history.back(); });
- uDom('#bye').css('display', 'none');
-} else {
- uDom('#bye').on('click', function() { window.close(); });
- uDom('#back').css('display', 'none');
-}
-
-uDom('#proceedTemporary').attr('href', details.url).on('click', proceedTemporary);
-uDom('#proceedPermanent').attr('href', details.url).on('click', proceedPermanent);
-
-/******************************************************************************/
-
-})();
-
-/******************************************************************************/
diff --git a/src/js/hnswitches.js b/src/js/hnswitches.js
deleted file mode 100644
index 13f1f3465..000000000
--- a/src/js/hnswitches.js
+++ /dev/null
@@ -1,283 +0,0 @@
-/*******************************************************************************
-
- uBlock - a Chromium browser extension to black/white list requests.
- Copyright (C) 2015 Raymond Hill
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see {http://www.gnu.org/licenses/}.
-
- Home: https://github.com/chrisaljoudi/uBlock
-*/
-
-/* global punycode, µBlock */
-/* jshint bitwise: false */
-
-/******************************************************************************/
-
-µBlock.HnSwitches = (function() {
-
-'use strict';
-
-/******************************************************************************/
-
-var HnSwitches = function() {
- this.reset();
-};
-
-/******************************************************************************/
-
-var switchBitOffsets = {
- 'dontBlockDoc': 0,
- 'doBlockAllPopups': 2
-};
-
-var switchStateToNameMap = {
- '1': 'true',
- '2': 'false'
-};
-
-var nameToSwitchStateMap = {
- 'true': 1,
- 'false': 2
-};
-
-/******************************************************************************/
-
-// For performance purpose, as simple tests as possible
-var reHostnameVeryCoarse = /[g-z_-]/;
-var reIPv4VeryCoarse = /\.\d+$/;
-
-// http://tools.ietf.org/html/rfc5952
-// 4.3: "MUST be represented in lowercase"
-// Also: http://en.wikipedia.org/wiki/IPv6_address#Literal_IPv6_addresses_in_network_resource_identifiers
-
-var isIPAddress = function(hostname) {
- if ( reHostnameVeryCoarse.test(hostname) ) {
- return false;
- }
- if ( reIPv4VeryCoarse.test(hostname) ) {
- return true;
- }
- return hostname.charAt(0) === '[';
-};
-
-/******************************************************************************/
-
-var toBroaderHostname = function(hostname) {
- if ( hostname === '*' ) {
- return '';
- }
- if ( isIPAddress(hostname) ) {
- return '*';
- }
- var pos = hostname.indexOf('.');
- if ( pos === -1 ) {
- return '*';
- }
- return hostname.slice(pos + 1);
-};
-
-HnSwitches.toBroaderHostname = toBroaderHostname;
-
-/******************************************************************************/
-
-HnSwitches.prototype.reset = function() {
- this.switches = {};
-};
-
-/******************************************************************************/
-
-// If value is undefined, the switch is removed
-
-HnSwitches.prototype.toggle = function(switchName, hostname, newVal) {
- var bitOffset = switchBitOffsets[switchName];
- if ( bitOffset === undefined ) {
- return false;
- }
- if ( newVal === this.evaluate(switchName, hostname) ) {
- return false;
- }
- var bits = this.switches[hostname] || 0;
- bits &= ~(3 << bitOffset);
- bits |= newVal << bitOffset;
- if ( bits === 0 ) {
- delete this.switches[hostname];
- } else {
- this.switches[hostname] = bits;
- }
- return true;
-};
-
-/******************************************************************************/
-
-HnSwitches.prototype.toggleZ = function(switchName, hostname, newState) {
- var bitOffset = switchBitOffsets[switchName];
- if ( bitOffset === undefined ) {
- return false;
- }
- var state = this.evaluateZ(switchName, hostname);
- if ( newState === state ) {
- return false;
- }
- if ( newState === undefined ) {
- newState = !state;
- }
- var bits = this.switches[hostname] || 0;
- bits &= ~(3 << bitOffset);
- if ( bits === 0 ) {
- delete this.switches[hostname];
- } else {
- this.switches[hostname] = bits;
- }
- state = this.evaluateZ(switchName, hostname);
- if ( state === newState ) {
- return true;
- }
- this.switches[hostname] = bits | ((newState ? 1 : 2) << bitOffset);
- return true;
-};
-
-/******************************************************************************/
-
-// 0 = inherit from broader scope, up to default state
-// 1 = non-default state
-// 2 = forced default state (to override a broader non-default state)
-
-HnSwitches.prototype.evaluate = function(switchName, hostname) {
- var bits = this.switches[hostname] || 0;
- if ( bits === 0 ) {
- return 0;
- }
- var bitOffset = switchBitOffsets[switchName];
- if ( bitOffset === undefined ) {
- return 0;
- }
- return (bits >> bitOffset) & 3;
-};
-
-/******************************************************************************/
-
-HnSwitches.prototype.evaluateZ = function(switchName, hostname) {
- var bitOffset = switchBitOffsets[switchName];
- if ( bitOffset === undefined ) {
- return false;
- }
- var bits;
- var s = hostname;
- for (;;) {
- bits = this.switches[s] || 0;
- if ( bits !== 0 ) {
- bits = bits >> bitOffset & 3;
- if ( bits !== 0 ) {
- return bits === 1;
- }
- }
- s = toBroaderHostname(s);
- if ( s === '' ) {
- break;
- }
- }
- return false;
-};
-
-/******************************************************************************/
-
-HnSwitches.prototype.toString = function() {
- var out = [];
- var switchName, val;
- var hostname;
- for ( hostname in this.switches ) {
- if ( this.switches.hasOwnProperty(hostname) === false ) {
- continue;
- }
- for ( switchName in switchBitOffsets ) {
- if ( switchBitOffsets.hasOwnProperty(switchName) === false ) {
- continue;
- }
- val = this.evaluate(switchName, hostname);
- if ( val === 0 ) {
- continue;
- }
- out.push(switchName + ': ' + hostname + ' ' + switchStateToNameMap[val]);
- }
- }
- return out.join('\n');
-};
-
-/******************************************************************************/
-
-HnSwitches.prototype.fromString = function(text) {
- var textEnd = text.length;
- var lineBeg = 0, lineEnd;
- var line, pos;
- var fields;
- var switchName, hostname, state;
-
- while ( lineBeg < textEnd ) {
- lineEnd = text.indexOf('\n', lineBeg);
- if ( lineEnd < 0 ) {
- lineEnd = text.indexOf('\r', lineBeg);
- if ( lineEnd < 0 ) {
- lineEnd = textEnd;
- }
- }
- line = text.slice(lineBeg, lineEnd).trim();
- lineBeg = lineEnd + 1;
-
- pos = line.indexOf('# ');
- if ( pos !== -1 ) {
- line = line.slice(0, pos).trim();
- }
- if ( line === '' ) {
- continue;
- }
-
- fields = line.split(/\s+/);
- if ( fields.length !== 3 ) {
- continue;
- }
-
- switchName = fields[0];
- pos = switchName.indexOf(':');
- if ( pos === -1 ) {
- continue;
- }
- switchName = switchName.slice(0, pos);
- if ( switchBitOffsets.hasOwnProperty(switchName) === false ) {
- continue;
- }
-
- hostname = punycode.toASCII(fields[1]);
-
- state = fields[2];
- if ( nameToSwitchStateMap.hasOwnProperty(state) === false ) {
- continue;
- }
-
- this.toggle(switchName, hostname, nameToSwitchStateMap[state]);
- }
-};
-
-/******************************************************************************/
-
-return HnSwitches;
-
-/******************************************************************************/
-
-})();
-
-/******************************************************************************/
-
-µBlock.hnSwitches = new µBlock.HnSwitches();
-
-/******************************************************************************/
diff --git a/src/js/messaging.js b/src/js/messaging.js
index d2ddcdbc4..ed8069fe5 100644
--- a/src/js/messaging.js
+++ b/src/js/messaging.js
@@ -76,7 +76,7 @@ var onMessage = function(request, sender, callback) {
break;
case 'reloadTab':
- if ( vAPI.isNoTabId(request.tabId) === false ) {
+ if ( vAPI.isBehindTheSceneTabId(request.tabId) === false ) {
vAPI.tabs.reload(request.tabId);
if ( request.select && vAPI.tabs.select ) {
vAPI.tabs.select(request.tabId);
@@ -203,6 +203,7 @@ var getFirewallRules = function(srcHostname, desHostnames) {
/******************************************************************************/
var getStats = function(tabId, tabTitle) {
+ var tabContext = µb.tabContextManager.lookup(tabId);
var r = {
advancedUserEnabled: µb.userSettings.advancedUserEnabled,
appName: vAPI.app.name,
@@ -213,39 +214,35 @@ var getStats = function(tabId, tabTitle) {
globalAllowedRequestCount: µb.localSettings.allowedRequestCount,
globalBlockedRequestCount: µb.localSettings.blockedRequestCount,
netFilteringSwitch: false,
- pageURL: '',
+ rawURL: tabContext.rawURL,
+ pageURL: tabContext.normalURL,
+ pageHostname: tabContext.rootHostname,
+ pageDomain: tabContext.rootDomain,
pageAllowedRequestCount: 0,
pageBlockedRequestCount: 0,
tabId: tabId,
tabTitle: tabTitle
};
+
var pageStore = µb.pageStoreFromTabId(tabId);
if ( pageStore ) {
- r.rawURL = pageStore.rawURL;
- r.pageURL = pageStore.pageURL;
- r.pageDomain = pageStore.pageDomain;
- r.pageHostname = pageStore.pageHostname;
r.pageBlockedRequestCount = pageStore.perLoadBlockedRequestCount;
r.pageAllowedRequestCount = pageStore.perLoadAllowedRequestCount;
r.netFilteringSwitch = pageStore.getNetFilteringSwitch();
r.hostnameDict = getHostnameDict(pageStore.hostnameToCountMap);
r.contentLastModified = pageStore.contentLastModified;
- r.firewallRules = getFirewallRules(pageStore.pageHostname, r.hostnameDict);
- r.canElementPicker = r.pageHostname.indexOf('.') !== -1;
+ r.firewallRules = getFirewallRules(tabContext.rootHostname, r.hostnameDict);
+ r.canElementPicker = tabContext.rootHostname.indexOf('.') !== -1;
r.canRequestLog = canRequestLog;
- r.doBlockAllPopups = µb.hnSwitches.evaluateZ('doBlockAllPopups', r.pageHostname);
- r.dontBlockDoc = µb.hnSwitches.evaluateZ('dontBlockDoc', r.pageHostname);
} else {
r.hostnameDict = {};
r.firewallRules = getFirewallRules();
}
- if ( r.pageHostname ) {
- r.matrixIsDirty = !µb.sessionFirewall.hasSameRules(
- µb.permanentFirewall,
- r.pageHostname,
- r.hostnameDict
- );
- }
+ r.matrixIsDirty = !µb.sessionFirewall.hasSameRules(
+ µb.permanentFirewall,
+ tabContext.rootHostname,
+ r.hostnameDict
+ );
return r;
};
@@ -449,16 +446,7 @@ var filterRequests = function(pageStore, details) {
var isBlockResult = µb.isBlockResult;
// Create evaluation context
- var context = {
- pageHostname: vAPI.punycodeHostname(details.pageHostname),
- pageDomain: µburi.domainFromHostname(details.pageHostname),
- rootHostname: pageStore.rootHostname,
- rootDomain: pageStore.rootDomain,
- requestURL: '',
- requestHostname: '',
- requestType: ''
- };
-
+ var context = pageStore.createContextFromFrameHostname(details.pageHostname);
var request;
var i = requests.length;
while ( i-- ) {
@@ -485,22 +473,18 @@ var onMessage = function(details, sender, callback) {
// Sync
var response;
- var pageStore, frameStore = false;
+ var pageStore;
if ( sender && sender.tab ) {
pageStore = µb.pageStoreFromTabId(sender.tab.id);
- var frameId = sender.frameId;
- if(frameId && frameId > 0) {
- frameStore = pageStore.getFrame(frameId);
- }
}
switch ( details.what ) {
case 'retrieveGenericCosmeticSelectors':
response = {
- shutdown: !pageStore || !pageStore.getNetFilteringSwitch() || (frameStore && !frameStore.getNetFilteringSwitch()),
+ shutdown: !pageStore || !pageStore.getNetFilteringSwitch(),
result: null
};
- if(pageStore && pageStore.getGenericCosmeticFilteringSwitch()) {
+ if ( !response.shutdown && pageStore.getGenericCosmeticFilteringSwitch() ) {
response.result = µb.cosmeticFilteringEngine.retrieveGenericSelectors(details);
}
break;
@@ -515,7 +499,7 @@ var onMessage = function(details, sender, callback) {
shutdown: !pageStore || !pageStore.getNetFilteringSwitch(),
result: null
};
- if(pageStore) {
+ if(!response.shutdown) {
response.result = filterRequests(pageStore, details);
}
break;
@@ -998,7 +982,6 @@ var backupUserData = function(callback) {
filterLists: µb.extractSelectedFilterLists(),
netWhitelist: µb.stringFromWhitelist(µb.netWhitelist),
dynamicFilteringString: µb.permanentFirewall.toString(),
- hostnameSwitchesString: µb.hnSwitches.toString(),
userFilters: details.content
};
var now = new Date();
@@ -1046,7 +1029,6 @@ var restoreUserData = function(request) {
var s = userData.dynamicFilteringString || userData.userSettings.dynamicFilteringString || '';
µb.XAL.keyvalSetOne('dynamicFilteringString', s, onCountdown);
- µb.XAL.keyvalSetOne('hostnameSwitchesString', userData.hostnameSwitchesString || '', onCountdown);
µb.assets.put('assets/user/filters.txt', userData.userFilters, onCountdown);
µb.XAL.keyvalSetMany({
lastRestoreFile: request.file || '',
diff --git a/src/js/pagestore.js b/src/js/pagestore.js
index 3685783db..f0793c4c8 100644
--- a/src/js/pagestore.js
+++ b/src/js/pagestore.js
@@ -391,6 +391,9 @@ NetFilteringResultCache.prototype.lookup = function(context) {
/******************************************************************************/
/******************************************************************************/
+// FrameStores are just for associating a
+// frame ID with a URL. pageHostname is really
+// frameHostname.
// To mitigate memory churning
var frameStoreJunkyard = [];
var frameStoreJunkyardMax = 50;
@@ -420,22 +423,13 @@ FrameStore.prototype.init = function(rootHostname, frameURL) {
this.pageURL = frameURL;
this.pageHostname = µburi.hostnameFromURI(frameURL);
this.pageDomain = µburi.domainFromHostname(this.pageHostname) || this.pageHostname;
- this.rootHostname = rootHostname;
- this.rootDomain = µburi.domainFromHostname(rootHostname) || rootHostname;
- // This is part of the filtering evaluation context
- this.requestURL = this.requestHostname = this.requestType = '';
- this.netFiltering = true;
- this.netFilteringReadTime = 0;
-
return this;
};
/******************************************************************************/
FrameStore.prototype.dispose = function() {
- this.pageHostname = this.pageDomain =
- this.rootHostname = this.rootDomain =
- this.requestURL = this.requestHostname = this.requestType = '';
+ this.pageHostname = this.pageDomain = '';
if ( frameStoreJunkyard.length < frameStoreJunkyardMax ) {
frameStoreJunkyard.push(this);
}
@@ -444,55 +438,35 @@ FrameStore.prototype.dispose = function() {
/******************************************************************************/
-FrameStore.prototype.getNetFilteringSwitch = function() {
- if ( this.netFilteringReadTime < µb.netWhitelistModifyTime ) {
- this.netFiltering = µb.getNetFilteringSwitch(this.pageURL);
- this.netFilteringReadTime = Date.now();
- }
- return this.netFiltering;
-};
-
-/******************************************************************************/
-
// To mitigate memory churning
var pageStoreJunkyard = [];
var pageStoreJunkyardMax = 10;
/******************************************************************************/
-var PageStore = function(tabId, rawURL, pageURL) {
- this.init(tabId, rawURL, pageURL);
+var PageStore = function(tabId) {
+ this.init(tabId);
};
/******************************************************************************/
-PageStore.factory = function(tabId, rawURL, pageURL) {
+PageStore.factory = function(tabId) {
var entry = pageStoreJunkyard.pop();
if ( entry === undefined ) {
- entry = new PageStore(tabId, rawURL, pageURL);
+ entry = new PageStore(tabId);
} else {
- entry.init(tabId, rawURL, pageURL);
+ entry.init(tabId);
}
return entry;
};
/******************************************************************************/
-PageStore.prototype.init = function(tabId, rawURL, pageURL) {
+PageStore.prototype.init = function(tabId) {
+ var tabContext = µb.tabContextManager.lookup(tabId);
this.tabId = tabId;
- this.rawURL = rawURL;
- this.pageURL = pageURL;
- this.pageHostname = µb.URI.hostnameFromURI(pageURL);
-
- // https://github.com/chrisaljoudi/uBlock/issues/185
- // Use hostname if no domain can be extracted
- this.pageDomain = µb.URI.domainFromHostname(this.pageHostname) || this.pageHostname;
- this.rootHostname = this.pageHostname;
- this.rootDomain = this.pageDomain;
-
- // This is part of the filtering evaluation context
- this.requestURL = this.requestHostname = this.requestType = '';
+ this.tabHostname = tabContext.rootHostname;
this.hostnameToCountMap = {};
this.contentLastModified = 0;
this.frames = {};
@@ -500,13 +474,13 @@ PageStore.prototype.init = function(tabId, rawURL, pageURL) {
this.netFilteringReadTime = 0;
this.perLoadBlockedRequestCount = 0;
this.perLoadAllowedRequestCount = 0;
- this.skipLocalMirroring = false;
this.netFilteringCache = NetFilteringResultCache.factory();
// Support `elemhide` filter option. Called at this point so the required
// context is all setup at this point.
+ var context = this.createContextFromPage();
this.skipCosmeticFiltering = µb.staticNetFilteringEngine
- .matchStringExactType(this, pageURL, 'cosmetic-filtering')
+ .matchStringExactType(context, tabContext.normalURL, 'cosmetic-filtering')
.charAt(1) === 'b';
// Preserve old buffer if there is one already, it may be in use, and
@@ -520,7 +494,7 @@ PageStore.prototype.init = function(tabId, rawURL, pageURL) {
/******************************************************************************/
-PageStore.prototype.reuse = function(rawURL, pageURL, context) {
+PageStore.prototype.reuse = function(context) {
// We can't do this: when force refreshing a page, the page store data
// needs to be reset
//if ( pageURL === this.pageURL ) {
@@ -528,8 +502,8 @@ PageStore.prototype.reuse = function(rawURL, pageURL, context) {
//}
// If the hostname changes, we can't merely just update the context.
- var pageHostname = µb.URI.hostnameFromURI(pageURL);
- if ( pageHostname !== this.pageHostname ) {
+ var tabContext = µb.tabContextManager.lookup(this.tabId);
+ if ( tabContext.rootHostname !== this.tabHostname ) {
context = '';
}
@@ -539,19 +513,16 @@ PageStore.prototype.reuse = function(rawURL, pageURL, context) {
// video thumbnail would not work, because the frame hierarchy structure
// was flushed from memory, while not really being flushed on the page.
if ( context === 'tabUpdated' ) {
- this.rawURL = rawURL;
- this.pageURL = pageURL;
-
// As part of https://github.com/chrisaljoudi/uBlock/issues/405
// URL changed, force a re-evaluation of filtering switch
this.netFilteringReadTime = 0;
-
return this;
}
+
// A new page is completely reloaded from scratch, reset all.
this.disposeFrameStores();
this.netFilteringCache = this.netFilteringCache.dispose();
- this.init(this.tabId, rawURL, pageURL);
+ this.init(this.tabId);
return this;
};
@@ -564,10 +535,6 @@ PageStore.prototype.dispose = function() {
// need to release the memory taken by these, which can amount to
// sizeable enough chunks (especially requests, through the request URL
// used as a key).
- this.rawURL = this.pageURL =
- this.pageHostname = this.pageDomain =
- this.rootHostname = this.rootDomain =
- this.requestURL = this.requestHostname = this.requestType = '';
this.hostnameToCountMap = null;
this.disposeFrameStores();
this.netFilteringCache = this.netFilteringCache.dispose();
@@ -609,34 +576,74 @@ PageStore.prototype.setFrame = function(frameId, frameURL) {
/******************************************************************************/
+PageStore.prototype.createContextFromPage = function() {
+ var context = new µb.tabContextManager.createContext(this.tabId);
+ context.pageHostname = context.rootHostname;
+ context.pageDomain = context.rootDomain;
+ return context;
+};
+
+PageStore.prototype.createContextFromFrameId = function(frameId) {
+ var context = new µb.tabContextManager.createContext(this.tabId);
+ if ( this.frames.hasOwnProperty(frameId) ) {
+ var frameStore = this.frames[frameId];
+ context.pageHostname = frameStore.pageHostname;
+ context.pageDomain = frameStore.pageDomain;
+ } else {
+ context.pageHostname = context.rootHostname;
+ context.pageDomain = context.rootDomain;
+ }
+ return context;
+};
+
+PageStore.prototype.createContextFromFrameHostname = function(frameHostname) {
+ var context = new µb.tabContextManager.createContext(this.tabId);
+ context.pageHostname = frameHostname;
+ context.pageDomain = µb.URI.domainFromHostname(frameHostname) || frameHostname;
+ return context;
+};
+
+/******************************************************************************/
+
PageStore.prototype.getNetFilteringSwitch = function() {
+ var tabContext = µb.tabContextManager.lookup(this.tabId);
+ if (
+ this.netFilteringReadTime > µb.netWhitelistModifyTime &&
+ this.netFilteringReadTime > tabContext.modifyTime
+ ) {
+ return this.netFiltering;
+ }
+
// https://github.com/chrisaljoudi/uBlock/issues/1078
// Use both the raw and normalized URLs.
- if ( this.netFilteringReadTime < µb.netWhitelistModifyTime ) {
- this.netFiltering = µb.getNetFilteringSwitch(this.pageURL);
- if ( this.netFiltering && this.rawURL !== this.pageURL ) {
- this.netFiltering = µb.getNetFilteringSwitch(this.rawURL);
- }
- this.netFilteringReadTime = Date.now();
+ this.netFiltering = µb.getNetFilteringSwitch(tabContext.normalURL);
+ if ( this.netFiltering && tabContext.rawURL !== tabContext.pageURL ) {
+ this.netFiltering = µb.getNetFilteringSwitch(tabContext.rawURL);
}
+ this.netFilteringReadTime = Date.now();
return this.netFiltering;
};
/******************************************************************************/
PageStore.prototype.getSpecificCosmeticFilteringSwitch = function() {
- return this.getNetFilteringSwitch() &&
- (µb.userSettings.advancedUserEnabled &&
- µb.sessionFirewall.mustAllowCellZY(this.rootHostname, this.rootHostname, '*')) === false;
+ if ( this.getNetFilteringSwitch() === false ) {
+ return false;
+ }
+
+ var tabContext = µb.tabContextManager.lookup(this.tabId);
+
+ return µb.userSettings.advancedUserEnabled === false ||
+ µb.sessionFirewall.mustAllowCellZY(tabContext.rootHostname, tabContext.rootHostname, '*') === false;
};
/******************************************************************************/
PageStore.prototype.getGenericCosmeticFilteringSwitch = function() {
- return this.getNetFilteringSwitch() &&
- this.skipCosmeticFiltering === false &&
- (µb.userSettings.advancedUserEnabled &&
- µb.sessionFirewall.mustAllowCellZY(this.rootHostname, this.rootHostname, '*')) === false;
+ if ( this.skipCosmeticFiltering ) {
+ return false;
+ }
+ return this.getSpecificCosmeticFilteringSwitch();
};
/******************************************************************************/
@@ -649,15 +656,8 @@ PageStore.prototype.toggleNetFilteringSwitch = function(url, scope, state) {
/******************************************************************************/
PageStore.prototype.filterRequest = function(context) {
- if(context.preNavigationHeader) { // sometimes we get inline-script queries before being
- // informed of navigation
- if(µb.getNetFilteringSwitch(context.requestURL) === false) {
- return '';
- }
- }
- if(this.getNetFilteringSwitch() === false || // if we're turned off (whitelisted)
- (typeof context.getNetFilteringSwitch === "function" && // or we're in a frame that's whitelisted
- context.getNetFilteringSwitch() === false)) {
+
+ if ( this.getNetFilteringSwitch() === false ) {
if ( collapsibleRequestTypes.indexOf(context.requestType) !== -1 ) {
this.netFilteringCache.add(context, '');
}
@@ -678,7 +678,11 @@ PageStore.prototype.filterRequest = function(context) {
// We evaluate dynamic filtering first, and hopefully we can skip
// evaluation of static filtering.
if ( µb.userSettings.advancedUserEnabled ) {
- var df = µb.sessionFirewall.evaluateCellZY(context.pageHostname, context.requestHostname, context.requestType);
+ var df = µb.sessionFirewall.evaluateCellZY(
+ context.rootHostname,
+ context.requestHostname,
+ context.requestType
+ );
if ( df.mustBlockOrAllow() ) {
result = df.toFilterString();
}
@@ -706,15 +710,7 @@ var collapsibleRequestTypes = 'image sub_frame object';
/******************************************************************************/
PageStore.prototype.filterRequestNoCache = function(context) {
- if(context.preNavigationHeader) { // sometimes we get inline-script queries before being
- // informed of navigation
- if(µb.getNetFilteringSwitch(context.requestURL) === false) {
- return '';
- }
- }
- if(this.getNetFilteringSwitch() === false || // if we're turned off (whitelisted)
- (typeof context.getNetFilteringSwitch === "function" && // or we're in a frame that's whitelisted
- context.getNetFilteringSwitch() === false)) {
+ if ( this.getNetFilteringSwitch() === false ) {
return '';
}
@@ -726,7 +722,11 @@ PageStore.prototype.filterRequestNoCache = function(context) {
// We evaluate dynamic filtering first, and hopefully we can skip
// evaluation of static filtering.
if ( µb.userSettings.advancedUserEnabled ) {
- var df = µb.sessionFirewall.evaluateCellZY(context.pageHostname, context.requestHostname, context.requestType);
+ var df = µb.sessionFirewall.evaluateCellZY(
+ context.rootHostname,
+ context.requestHostname,
+ context.requestType
+ );
if ( df.mustBlockOrAllow() ) {
result = df.toFilterString();
}
@@ -748,7 +748,7 @@ PageStore.prototype.logRequest = function(context, result) {
// be prepared to handle invalid requestHostname, I've seen this
// happen: http://./
if ( requestHostname === '' ) {
- requestHostname = context.pageHostname;
+ requestHostname = context.rootHostname;
}
var now = Date.now();
if ( this.hostnameToCountMap.hasOwnProperty(requestHostname) === false ) {
@@ -771,22 +771,6 @@ PageStore.prototype.logRequest = function(context, result) {
/******************************************************************************/
-PageStore.prototype.toMirrorURL = function(requestURL) {
- // https://github.com/chrisaljoudi/uBlock/issues/351
- // Bypass experimental features when uBlock is disabled for a site
- if ( µb.userSettings.experimentalEnabled === false ||
- this.getNetFilteringSwitch() === false ||
- this.skipLocalMirroring ) {
- return '';
- }
-
- // https://code.google.com/p/chromium/issues/detail?id=387198
- // Not all redirects will succeed, until bug above is fixed.
- return µb.mirrors.toURL(requestURL, true);
-};
-
-/******************************************************************************/
-
PageStore.prototype.updateBadge = function() {
var netFiltering = this.getNetFilteringSwitch();
var badge = '';
diff --git a/src/js/start.js b/src/js/start.js
index e1cf44b55..4dfa2d619 100644
--- a/src/js/start.js
+++ b/src/js/start.js
@@ -137,7 +137,6 @@ var onUserSettingsReady = function(fetched) {
µb.contextMenu.toggle(userSettings.contextMenuEnabled);
µb.permanentFirewall.fromString(fetched.dynamicFilteringString);
µb.sessionFirewall.assign(µb.permanentFirewall);
- µb.hnSwitches.fromString(fetched.hostnameSwitchesString);
// Remove obsolete setting
delete userSettings.logRequests;
@@ -216,7 +215,6 @@ return function() {
var fetchableProps = {
'compiledMagic': '',
'dynamicFilteringString': '',
- 'hostnameSwitchesString': '',
'lastRestoreFile': '',
'lastRestoreTime': 0,
'lastBackupFile': '',
diff --git a/src/js/storage.js b/src/js/storage.js
index b3188bf7d..6167e0422 100644
--- a/src/js/storage.js
+++ b/src/js/storage.js
@@ -75,12 +75,6 @@
/******************************************************************************/
-µBlock.saveHostnameSwitches = function() {
- this.XAL.keyvalSetOne('hostnameSwitchesString', this.hnSwitches.toString());
-};
-
-/******************************************************************************/
-
µBlock.saveWhitelist = function() {
var bin = {
'netWhitelist': this.stringFromWhitelist(this.netWhitelist)
diff --git a/src/js/tab.js b/src/js/tab.js
index 7c67350d5..09cf3c7ad 100644
--- a/src/js/tab.js
+++ b/src/js/tab.js
@@ -32,141 +32,7 @@
var µb = µBlock;
-/******************************************************************************/
-/******************************************************************************/
-
-// When the DOM content of root frame is loaded, this means the tab
-// content has changed.
-
-vAPI.tabs.onNavigation = function(details) {
- if ( details.frameId !== 0 ) {
- return;
- }
- var pageStore = µb.bindTabToPageStats(details.tabId, details.url, 'afterNavigate');
-
- // https://github.com/chrisaljoudi/uBlock/issues/630
- // The hostname of the bound document must always be present in the
- // mini-matrix. That's the best place I could find for the fix, all other
- // options had bad side-effects or complications.
- // TODO: Eventually, we will have to use an API to check whether a scheme
- // is supported as I suspect we are going to start to see `ws`, `wss`
- // as well soon.
- if ( pageStore && details.url.lastIndexOf('http', 0) === 0 ) {
- pageStore.hostnameToCountMap[pageStore.pageHostname] = 0;
- }
-};
-
-/******************************************************************************/
-
-// It may happen the URL in the tab changes, while the page's document
-// stays the same (for instance, Google Maps). Without this listener,
-// the extension icon won't be properly refreshed.
-
-vAPI.tabs.onUpdated = function(tabId, changeInfo, tab) {
- if ( !tab.url || tab.url === '' ) {
- return;
- }
- if ( !changeInfo.url ) {
- return;
- }
- µb.bindTabToPageStats(tabId, changeInfo.url, 'tabUpdated');
-};
-
-/******************************************************************************/
-
-vAPI.tabs.onClosed = function(tabId) {
- if ( tabId < 0 ) {
- return;
- }
- µb.unbindTabFromPageStats(tabId);
-};
-
-/******************************************************************************/
-
-// https://github.com/chrisaljoudi/uBlock/issues/297
-
-vAPI.tabs.onPopup = function(details) {
- //console.debug('vAPI.tabs.onPopup: details = %o', details);
-
- var pageStore = µb.pageStoreFromTabId(details.openerTabId);
- var openerURL = details.openerURL || '';
-
- if ( openerURL === '' && pageStore ) {
- openerURL = pageStore.pageURL;
- }
-
- if ( openerURL === '' ) {
- return;
- }
-
- var µburi = µb.URI;
- var openerHostname = µburi.hostnameFromURI(openerURL);
- var openerDomain = µburi.domainFromHostname(openerHostname);
-
- var targetURL = details.targetURL;
-
- // If the page URL is that of our "blocked page" URL, extract the URL of
- // the page which was blocked.
- if ( targetURL.lastIndexOf(vAPI.getURL('document-blocked.html'), 0) === 0 ) {
- var matches = /details=([^&]+)/.exec(targetURL);
- if ( matches !== null ) {
- targetURL = JSON.parse(atob(matches[1])).url;
- }
- }
-
- var context = {
- pageHostname: openerHostname,
- pageDomain: openerDomain,
- rootHostname: openerHostname,
- rootDomain: openerDomain,
- requestURL: targetURL,
- requestHostname: µb.URI.hostnameFromURI(targetURL),
- requestType: 'popup'
- };
-
- var result = '';
-
- // Check user switch first
- if ( µb.hnSwitches.evaluateZ('doBlockAllPopups', openerHostname) ) {
- result = 'ub:doBlockAllPopups true';
- }
-
- // https://github.com/chrisaljoudi/uBlock/issues/323
- // https://github.com/chrisaljoudi/uBlock/issues/1142
- // If popup OR opener URL is whitelisted, do not block the popup
- if (
- result === '' &&
- µb.getNetFilteringSwitch(openerURL) &&
- µb.getNetFilteringSwitch(targetURL)
- ) {
- result = µb.staticNetFilteringEngine.matchStringExactType(context, targetURL, 'popup');
- }
-
- // https://github.com/chrisaljoudi/uBlock/issues/91
- if ( pageStore ) {
- pageStore.logRequest(context, result);
- }
-
- // Not blocked
- if ( µb.isAllowResult(result) ) {
- return;
- }
-
- // Blocked
-
- // It is a popup, block and remove the tab.
- µb.unbindTabFromPageStats(details.targetTabId);
- vAPI.tabs.remove(details.targetTabId);
-
- return true;
-};
-
-vAPI.tabs.registerListeners();
-
-/******************************************************************************/
-/******************************************************************************/
-
-// https://github.com/chrisaljoudi/httpswitchboard/issues/303
+// https://github.com/gorhill/httpswitchboard/issues/303
// Some kind of trick going on here:
// Any scheme other than 'http' and 'https' is remapped into a fake
// URL which trick the rest of µBlock into being able to process an
@@ -176,8 +42,11 @@ vAPI.tabs.registerListeners();
// hostname. This way, for a specific scheme you can create scope with
// rules which will apply only to that scheme.
+/******************************************************************************/
+/******************************************************************************/
+
µb.normalizePageURL = function(tabId, pageURL) {
- if ( vAPI.isNoTabId(tabId) ) {
+ if ( vAPI.isBehindTheSceneTabId(tabId) ) {
return 'http://behind-the-scene/';
}
var uri = this.URI.set(pageURL);
@@ -195,19 +64,434 @@ vAPI.tabs.registerListeners();
return url;
};
+/******************************************************************************/
+/******************************************************************************
+
+To keep track from which context *exactly* network requests are made. This is
+often tricky for various reasons, and the challenge is not specific to one
+browser.
+
+The time at which a URL is assigned to a tab and the time when a network
+request for a root document is made must be assumed to be unrelated: it's all
+asynchronous. There is no guaranteed order in which the two events are fired.
+
+Also, other "anomalies" can occur:
+
+- a network request for a root document is fired without the corresponding
+tab being really assigned a new URL
+
+
+- a network request for a secondary resource is labeled with a tab id for
+which no root document was pulled for that tab.
+
+
+- a network request for a secondary resource is made without the root
+document to which it belongs being formally bound yet to the proper tab id,
+causing a bad scope to be used for filtering purpose.
+
+
+
+So the solution here is to keep a lightweight data structure which only
+purpose is to keep track as accurately as possible of which root document
+belongs to which tab. That's the only purpose, and because of this, there are
+no restrictions for when the URL of a root document can be associated to a tab.
+
+Before, the PageStore object was trying to deal with this, but it had to
+enforce some restrictions so as to not descend into one of the above issues, or
+other issues. The PageStore object can only be associated with a tab for which
+a definitive navigation event occurred, because it collects information about
+what occurred in the tab (for example, the number of requests blocked for a
+page).
+
+The TabContext objects do not suffer this restriction, and as a result they
+offer the most reliable picture of which root document URL is really associated
+to which tab. Moreover, the TabObject can undo an association from a root
+document, and automatically re-associate with the next most recent. This takes
+care of .
+
+The PageStore object no longer cache the various information about which
+root document it is currently bound. When it needs to find out, it will always
+defer to the TabContext object, which will provide the real answer. This takes
+case of . In effect, the
+master switch and dynamic filtering rules can be evaluated now properly even
+in the absence of a PageStore object, this was not the case before.
+
+Also, the TabContext object will try its best to find a good candidate root
+document URL for when none exists. This takes care of
+.
+
+The TabContext manager is self-contained, and it takes care to properly
+housekeep itself.
+
+*/
+
+µb.tabContextManager = (function() {
+ var tabContexts = Object.create(null);
+
+ // https://github.com/chrisaljoudi/uBlock/issues/1001
+ // This is to be used as last-resort fallback in case a tab is found to not
+ // be bound while network requests are fired for the tab.
+ var mostRecentRootDocURL = '';
+ var mostRecentRootDocURLTimestamp = 0;
+
+ var gcPeriod = 10 * 60 * 1000;
+
+ var TabContext = function(tabId) {
+ this.tabId = tabId.toString();
+ this.stack = [];
+ this.rawURL =
+ this.normalURL =
+ this.rootHostname =
+ this.rootDomain = '';
+ this.timer = null;
+ this.onTabCallback = null;
+ this.onTimerCallback = null;
+
+ tabContexts[tabId] = this;
+ };
+
+ TabContext.prototype.destroy = function() {
+ if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
+ return;
+ }
+ if ( this.timer !== null ) {
+ clearTimeout(this.timer);
+ this.timer = null;
+ }
+ delete tabContexts[this.tabId];
+ };
+
+ TabContext.prototype.onTab = function(tab) {
+ if ( tab ) {
+ this.timer = setTimeout(this.onTimerCallback, gcPeriod);
+ } else {
+ this.destroy();
+ }
+ };
+
+ TabContext.prototype.onTimer = function() {
+ this.timer = null;
+ if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
+ return;
+ }
+ vAPI.tabs.get(this.tabId, this.onTabCallback);
+ };
+
+ // This takes care of orphanized tab contexts. Can't be started for all
+ // contexts, as the behind-the-scene context is permanent -- so we do not
+ // want to slush it.
+ TabContext.prototype.autodestroy = function() {
+ if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
+ return;
+ }
+ this.onTabCallback = this.onTab.bind(this);
+ this.onTimerCallback = this.onTimer.bind(this);
+ this.timer = setTimeout(this.onTimerCallback, gcPeriod);
+ };
+
+ // Update just force all properties to be updated to match the most current
+ // root URL.
+ TabContext.prototype.update = function() {
+ if ( this.stack.length === 0 ) {
+ this.rawURL = this.normalURL = this.rootHostname = this.rootDomain = '';
+ } else {
+ this.rawURL = this.stack[this.stack.length - 1];
+ this.normalURL = µb.normalizePageURL(this.tabId, this.rawURL);
+ this.rootHostname = µb.URI.hostnameFromURI(this.normalURL);
+ this.rootDomain = µb.URI.domainFromHostname(this.rootHostname);
+ }
+ };
+
+ // Called whenever a candidate root URL is spotted for the tab.
+ TabContext.prototype.push = function(url) {
+ if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
+ return;
+ }
+ this.stack.push(url);
+ this.update();
+ };
+
+ // Called when a former push is a false positive:
+ // https://github.com/chrisaljoudi/uBlock/issues/516
+ TabContext.prototype.unpush = function(url) {
+ if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
+ return;
+ }
+ // We are not going to unpush if there is no other candidate, the
+ // point of unpush is to make space for a better candidate.
+ if ( this.stack.length === 1 ) {
+ return;
+ }
+ var pos = this.stack.indexOf(url);
+ if ( pos === -1 ) {
+ return;
+ }
+ this.stack.splice(pos, 1);
+ if ( this.stack.length === 0 ) {
+ this.destroy();
+ return;
+ }
+ if ( pos !== this.stack.length ) {
+ return;
+ }
+ this.update();
+ };
+
+ // This tells that the url is definitely the one to be associated with the
+ // tab, there is no longer any ambiguity about which root URL is really
+ // sitting in which tab.
+ TabContext.prototype.commit = function(url) {
+ if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
+ return;
+ }
+ this.stack = [url];
+ this.update();
+ };
+
+ // These are to be used for the API of the tab context manager.
+
+ var push = function(tabId, url) {
+ var entry = tabContexts[tabId];
+ if ( entry === undefined ) {
+ entry = new TabContext(tabId);
+ entry.autodestroy();
+ }
+ entry.push(url);
+ mostRecentRootDocURL = url;
+ mostRecentRootDocURLTimestamp = Date.now();
+ return entry;
+ };
+
+ // 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 entry;
+ if ( url !== undefined ) {
+ entry = push(tabId, url);
+ } else {
+ entry = tabContexts[tabId];
+ }
+ if ( entry !== undefined ) {
+ return entry;
+ }
+ // https://github.com/chrisaljoudi/uBlock/issues/1025
+ // Google Hangout popup opens without a root frame. So for now we will
+ // just discard that best-guess root frame if it is too far in the
+ // future, at which point it ceases to be a "best guess".
+ if ( mostRecentRootDocURL !== '' && mostRecentRootDocURLTimestamp + 500 < Date.now() ) {
+ mostRecentRootDocURL = '';
+ }
+ // https://github.com/chrisaljoudi/uBlock/issues/1001
+ // Not a behind-the-scene request, yet no page store found for the
+ // tab id: we will thus bind the last-seen root document to the
+ // unbound tab. It's a guess, but better than ending up filtering
+ // nothing at all.
+ if ( mostRecentRootDocURL !== '' ) {
+ return push(tabId, mostRecentRootDocURL);
+ }
+ // If all else fail at finding a page store, re-categorize the
+ // request as behind-the-scene. At least this ensures that ultimately
+ // the user can still inspect/filter those net requests which were
+ // about to fall through the cracks.
+ // Example: Chromium + case #12 at
+ // http://raymondhill.net/ublock/popup.html
+ return tabContexts[vAPI.noTabId];
+ };
+
+ var commit = function(tabId, url) {
+ var entry = tabContexts[tabId];
+ if ( entry === undefined ) {
+ entry = push(tabId, url);
+ } else {
+ entry.commit(url);
+ }
+ return entry;
+ };
+
+ var unpush = function(tabId, url) {
+ var entry = tabContexts[tabId];
+ if ( entry !== undefined ) {
+ entry.unpush(url);
+ }
+ };
+
+ var destroy = function(tabId) {
+ var entry = tabContexts[tabId];
+ if ( entry !== undefined ) {
+ entry.destroy();
+ }
+ };
+
+ var exists = function(tabId) {
+ return tabContexts[tabId] !== undefined;
+ };
+
+ // Behind-the-scene tab context
+ (function() {
+ var entry = new TabContext(vAPI.noTabId);
+ entry.stack.push('');
+ entry.rawURL = '';
+ entry.normalURL = µb.normalizePageURL(entry.tabId);
+ entry.rootHostname = µb.URI.hostnameFromURI(entry.normalURL);
+ entry.rootDomain = µb.URI.domainFromHostname(entry.rootHostname);
+ })();
+
+ // Context object, typically to be used to feed filtering engines.
+ var Context = function(tabId) {
+ var tabContext = lookup(tabId);
+ this.rootHostname = tabContext.rootHostname;
+ this.rootDomain = tabContext.rootDomain;
+ this.pageHostname =
+ this.pageDomain =
+ this.requestURL =
+ this.requestHostname =
+ this.requestDomain = '';
+ };
+
+ var createContext = function(tabId) {
+ return new Context(tabId);
+ };
+
+ return {
+ push: push,
+ unpush: unpush,
+ commit: commit,
+ lookup: lookup,
+ destroy: destroy,
+ exists: exists,
+ createContext: createContext
+ };
+})();
+
+/******************************************************************************/
+/******************************************************************************/
+// When the DOM content of root frame is loaded, this means the tab
+// content has changed.
+
+vAPI.tabs.onNavigation = function(details) {
+ if ( details.frameId !== 0 ) {
+ return;
+ }
+ var tabContext = µb.tabContextManager.commit(details.tabId, details.url);
+ var pageStore = µb.bindTabToPageStats(details.tabId, 'afterNavigate');
+
+
+ // https://github.com/chrisaljoudi/uBlock/issues/630
+ // The hostname of the bound document must always be present in the
+ // mini-matrix. That's the best place I could find for the fix, all other
+ // options had bad side-effects or complications.
+ // TODO: Eventually, we will have to use an API to check whether a scheme
+ // is supported as I suspect we are going to start to see `ws`, `wss`
+ // as well soon.
+ if ( pageStore && tabContext.rawURL.lastIndexOf('http', 0) === 0 ) {
+ pageStore.hostnameToCountMap[tabContext.rootHostname] = 0;
+ }
+};
+
+/******************************************************************************/
+
+// It may happen the URL in the tab changes, while the page's document
+// stays the same (for instance, Google Maps). Without this listener,
+// the extension icon won't be properly refreshed.
+
+vAPI.tabs.onUpdated = function(tabId, changeInfo, tab) {
+ if ( !tab.url || tab.url === '' ) {
+ return;
+ }
+ if ( !changeInfo.url ) {
+ return;
+ }
+ µb.tabContextManager.commit(tabId, changeInfo.url);
+ µb.bindTabToPageStats(tabId, 'tabUpdated');
+};
+
+/******************************************************************************/
+
+vAPI.tabs.onClosed = function(tabId) {
+ if ( tabId < 0 ) {
+ return;
+ }
+ µb.unbindTabFromPageStats(tabId);
+};
+
+/******************************************************************************/
+
+// https://github.com/chrisaljoudi/uBlock/issues/297
+
+vAPI.tabs.onPopup = function(details) {
+ // console.debug('vAPI.tabs.onPopup: details = %o', details);
+
+ var tabContext = µb.tabContextManager.lookup(details.openerTabId);
+ var openerURL = '';
+ if ( tabContext.tabId === details.openerTabId ) {
+ openerURL = tabContext.normalURL;
+ }
+ if ( openerURL === '' ) {
+ return;
+ }
+
+ var µburi = µb.URI;
+ var openerHostname = µburi.hostnameFromURI(openerURL);
+ var openerDomain = µburi.domainFromHostname(openerHostname);
+
+ var targetURL = details.targetURL;
+
+ var context = {
+ pageHostname: openerHostname,
+ pageDomain: openerDomain,
+ rootHostname: openerHostname,
+ rootDomain: openerDomain,
+ requestURL: targetURL,
+ requestHostname: µb.URI.hostnameFromURI(targetURL),
+ requestType: 'popup'
+ };
+
+ var result = '';
+
+ // https://github.com/chrisaljoudi/uBlock/issues/323
+ // https://github.com/chrisaljoudi/uBlock/issues/1142
+ // If popup OR opener URL is whitelisted, do not block the popup
+ if (
+ result === '' &&
+ µb.getNetFilteringSwitch(openerURL) &&
+ µb.getNetFilteringSwitch(targetURL)
+ ) {
+ result = µb.staticNetFilteringEngine.matchStringExactType(context, targetURL, 'popup');
+ }
+
+ // https://github.com/chrisaljoudi/uBlock/issues/91
+ var pageStore = µb.pageStoreFromTabId(details.openerTabId);
+ if ( pageStore ) {
+ pageStore.logRequest(context, result);
+ }
+
+ // Not blocked
+ if ( µb.isAllowResult(result) ) {
+ return;
+ }
+
+ // Blocked
+
+ // It is a popup, block and remove the tab.
+ if(details.targetTabId !== "preempt") {
+ µb.unbindTabFromPageStats(details.targetTabId);
+ vAPI.tabs.remove(details.targetTabId);
+ }
+
+ return true;
+};
+
+vAPI.tabs.registerListeners();
+
+/******************************************************************************/
/******************************************************************************/
// Create an entry for the tab if it doesn't exist.
-µb.bindTabToPageStats = function(tabId, pageURL, context) {
+µb.bindTabToPageStats = function(tabId, context) {
this.updateBadgeAsync(tabId);
-
- // https://github.com/chrisaljoudi/httpswitchboard/issues/303
- // Normalize page URL
- var normalURL = this.normalizePageURL(tabId, pageURL);
-
- // Do not create a page store for URLs which are of no interests
- if ( normalURL === '' ) {
+
+ if ( µb.tabContextManager.exists(tabId) === false ) {
this.unbindTabFromPageStats(tabId);
return null;
}
@@ -217,7 +501,7 @@ vAPI.tabs.registerListeners();
// Tab is not bound
if ( !pageStore ) {
- return this.pageStores[tabId] = this.PageStore.factory(tabId, pageURL, normalURL);
+ return this.pageStores[tabId] = this.PageStore.factory(tabId);
}
// https://github.com/chrisaljoudi/uBlock/issues/516
@@ -229,11 +513,13 @@ vAPI.tabs.registerListeners();
// Rebind according to context. We rebind even if the URL did not change,
// as maybe the tab was force-reloaded, in which case the page stats must
// be all reset.
- pageStore.reuse(pageURL, normalURL, context);
+ pageStore.reuse(context);
return pageStore;
};
+/******************************************************************************/
+
µb.unbindTabFromPageStats = function(tabId) {
//console.debug('µBlock> unbindTabFromPageStats(%d)', tabId);
var pageStore = this.pageStores[tabId];
@@ -243,20 +529,6 @@ vAPI.tabs.registerListeners();
}
};
-/******************************************************************************/
-
-µb.pageUrlFromTabId = function(tabId) {
- var pageStore = this.pageStores[tabId];
- return pageStore ? pageStore.pageURL : '';
-};
-
-µb.pageUrlFromPageStats = function(pageStats) {
- if ( pageStats ) {
- return pageStats.pageURL;
- }
- return '';
-};
-
µb.pageStoreFromTabId = function(tabId) {
return this.pageStores[tabId];
};
@@ -265,11 +537,7 @@ vAPI.tabs.registerListeners();
// Permanent page store for behind-the-scene requests. Must never be removed.
-µb.pageStores[vAPI.noTabId] = µb.PageStore.factory(
- vAPI.noTabId,
- '',
- µb.normalizePageURL(vAPI.noTabId)
-);
+µb.pageStores[vAPI.noTabId] = µb.PageStore.factory(vAPI.noTabId);
/******************************************************************************/
/******************************************************************************/
@@ -300,7 +568,7 @@ var pageStoreJanitor = function() {
for ( var i = pageStoreJanitorSampleAt; i < n; i++ ) {
tabId = tabIds[i];
// Do not remove behind-the-scene page store
- if ( vAPI.isNoTabId(tabId) ) {
+ if ( vAPI.isBehindTheSceneTabId(tabId) ) {
continue;
}
checkTab(tabId);
diff --git a/src/js/traffic.js b/src/js/traffic.js
index 2f89bff94..e38d49bde 100644
--- a/src/js/traffic.js
+++ b/src/js/traffic.js
@@ -33,16 +33,6 @@
var exports = {};
-// https://github.com/chrisaljoudi/uBlock/issues/1001
-// This is to be used as last-resort fallback in case a tab is found to not
-// be bound while network requests are fired for the tab.
-
-var mostRecentRootDocURLTimestamp = 0;
-var mostRecentRootDocURL = '';
-
-
-var documentWhitelists = Object.create(null);
-
/******************************************************************************/
// Intercept and filter web requests.
@@ -62,40 +52,22 @@ var onBeforeRequest = function(details) {
// Special treatment: behind-the-scene requests
var tabId = details.tabId;
- if ( vAPI.isNoTabId(tabId) ) {
+ if ( vAPI.isBehindTheSceneTabId(tabId) ) {
return onBeforeBehindTheSceneRequest(details);
}
// Lookup the page store associated with this tab id.
var µb = µBlock;
var pageStore = µb.pageStoreFromTabId(tabId);
- if ( (Date.now() - mostRecentRootDocURLTimestamp) >= 500 ) {
- mostRecentRootDocURL = '';
- }
if ( !pageStore ) {
- // https://github.com/chrisaljoudi/uBlock/issues/1001
- // Not a behind-the-scene request, yet no page store found for the
- // tab id: we will thus bind the last-seen root document to the
- // unbound tab. It's a guess, but better than ending up filtering
- // nothing at all.
- if ( mostRecentRootDocURL !== '' ) {
- vAPI.tabs.onNavigation({ tabId: tabId, frameId: 0, url: mostRecentRootDocURL });
- pageStore = µb.pageStoreFromTabId(tabId);
- }
- // If all else fail at finding a page store, re-categorize the
- // request as behind-the-scene. At least this ensures that ultimately
- // the user can still inspect/filter those net requests which were
- // about to fall through the cracks.
- // Example: Chromium + case #12 at
- // http://raymondhill.net/ublock/popup.html
- if ( !pageStore ) {
+ var tabContext = µb.tabContextManager.lookup(tabId);
+ if ( vAPI.isBehindTheSceneTabId(tabContext.tabId) ) {
return onBeforeBehindTheSceneRequest(details);
}
+ vAPI.tabs.onNavigation({ tabId: tabId, frameId: 0, url: tabContext.rawURL });
+ pageStore = µb.pageStoreFromTabId(tabId);
}
- // https://github.com/chrisaljoudi/uBlock/issues/114
- var requestContext = pageStore;
- var frameStore;
// https://github.com/chrisaljoudi/uBlock/issues/886
// For requests of type `sub_frame`, the parent frame id must be used
// to lookup the proper context:
@@ -105,25 +77,22 @@ var onBeforeRequest = function(details) {
// > (ref: https://developer.chrome.com/extensions/webRequest)
var isFrame = requestType === 'sub_frame';
var frameId = isFrame ? details.parentFrameId : details.frameId;
- if ( frameId > 0 ) {
- if ( frameStore = pageStore.getFrame(frameId) ) {
- requestContext = frameStore;
- }
- }
+
+ // https://github.com/chrisaljoudi/uBlock/issues/114
+ var requestContext = pageStore.createContextFromFrameId(frameId);
// Setup context and evaluate
var requestURL = details.url;
requestContext.requestURL = requestURL;
requestContext.requestHostname = details.hostname;
requestContext.requestType = requestType;
- if(!isFrame && mostRecentRootDocURL !== '') {
- requestContext.pageHostname = µb.URI.hostnameFromURI(mostRecentRootDocURL);
- }
var result = pageStore.filterRequest(requestContext);
// Possible outcomes: blocked, allowed-passthru, allowed-mirror
+ pageStore.logRequest(requestContext, result);
+
// Not blocked
if ( µb.isAllowResult(result) ) {
//console.debug('traffic.js > onBeforeRequest(): ALLOW "%s" (%o) because "%s"', details.url, details, result);
@@ -138,27 +107,12 @@ var onBeforeRequest = function(details) {
}
}
- // https://code.google.com/p/chromium/issues/detail?id=387198
- // Not all redirects will succeed, until bug above is fixed.
- // https://github.com/chrisaljoudi/uBlock/issues/540
- // Disabling local mirroring for the time being
- //var redirectURL = pageStore.toMirrorURL(requestURL);
- //if ( redirectURL !== '' ) {
- // pageStore.logRequest(requestContext, 'ma:');
- //console.debug('traffic.js > "%s" redirected to "%s..."', requestURL.slice(0, 50), redirectURL.slice(0, 50));
- // return { redirectUrl: redirectURL };
- //}
-
- pageStore.logRequest(requestContext, result);
-
return;
}
// Blocked
//console.debug('traffic.js > onBeforeRequest(): BLOCK "%s" (%o) because "%s"', details.url, details, result);
- pageStore.logRequest(requestContext, result);
-
// https://github.com/chrisaljoudi/uBlock/issues/905#issuecomment-76543649
// No point updating the badge if it's not being displayed.
if ( µb.userSettings.showIconBadge ) {
@@ -175,16 +129,12 @@ var onBeforeRequest = function(details) {
/******************************************************************************/
var onBeforeRootFrameRequest = function(details) {
+ var tabId = details.tabId;
var requestURL = details.url;
-
- mostRecentRootDocURL = requestURL;
- mostRecentRootDocURLTimestamp = Date.now();
-
- // Special handling for root document.
- // https://github.com/chrisaljoudi/uBlock/issues/1001
- // This must be executed regardless of whether the request is
- // behind-the-scene
var µb = µBlock;
+
+ µb.tabContextManager.push(tabId, requestURL);
+
var requestHostname = details.hostname;
var requestDomain = µb.URI.domainFromHostname(requestHostname);
var context = {
@@ -198,60 +148,11 @@ var onBeforeRootFrameRequest = function(details) {
};
var result = '';
-
- // If the site is whitelisted, disregard strict blocking
- if ( µb.getNetFilteringSwitch(requestURL) === false ) {
- result = 'ua:whitelisted';
- }
-
- // Permanently unrestricted?
- if ( result === '' && µb.hnSwitches.evaluateZ('dontBlockDoc', requestHostname) ) {
- result = 'ua:dontBlockDoc true';
- }
-
- // Temporarily whitelisted?
- var obsolete = documentWhitelists[requestHostname];
- if ( obsolete !== undefined ) {
- if ( obsolete > Date.now() ) {
- if ( result === '' ) {
- result = 'ta:*' + ' ' + requestHostname + ' doc allow';
- }
- } else {
- delete documentWhitelists[requestHostname];
- }
- }
-
- // Filtering
- if ( result === '' ) {
- result = µb.staticNetFilteringEngine.matchString(context);
- // https://github.com/chrisaljoudi/uBlock/issues/1128
- // Do not block if the match begins after the hostname.
- if ( result !== '' ) {
- result = toBlockDocResult(requestURL, requestHostname, result);
- }
- }
-
- // Log
- var pageStore = µb.bindTabToPageStats(details.tabId, requestURL, 'beforeRequest');
+ var pageStore = µb.bindTabToPageStats(tabId, 'beforeRequest');
if ( pageStore ) {
pageStore.logRequest(context, result);
}
-
- // Not blocked
- if ( µb.isAllowResult(result) ) {
- return;
- }
-
- // Blocked
- var query = btoa(JSON.stringify({
- url: requestURL,
- hn: requestHostname,
- why: result
- }));
-
- vAPI.tabs.replace(details.tabId, vAPI.getURL('document-blocked.html?details=') + query);
-
- return { cancel: true };
+ return;
};
/******************************************************************************/
@@ -309,9 +210,10 @@ var onBeforeBehindTheSceneRequest = function(details) {
return;
}
- pageStore.requestURL = details.url;
- pageStore.requestHostname = details.hostname;
- pageStore.requestType = details.type;
+ var context = pageStore.createContextFromPage();
+ context.requestURL = details.url;
+ context.requestHostname = details.hostname;
+ context.requestType = details.type;
// Blocking behind-the-scene requests can break a lot of stuff: prevent
// browser updates, prevent extension updates, prevent extensions from
@@ -319,10 +221,10 @@ var onBeforeBehindTheSceneRequest = function(details) {
// So we filter if and only if the "advanced user" mode is selected
var result = '';
if ( µb.userSettings.advancedUserEnabled ) {
- result = pageStore.filterRequestNoCache(pageStore);
+ result = pageStore.filterRequestNoCache(context);
}
- pageStore.logRequest(pageStore, result);
+ pageStore.logRequest(context, result);
// Not blocked
if ( µb.isAllowResult(result) ) {
@@ -343,58 +245,68 @@ var onBeforeBehindTheSceneRequest = function(details) {
var onHeadersReceived = function(details) {
// Do not interfere with behind-the-scene requests.
var tabId = details.tabId;
- if ( vAPI.isNoTabId(tabId) ) {
+ if ( vAPI.isBehindTheSceneTabId(tabId) ) {
return;
}
- var requestURL = details.url;
+ // Special handling for root document.
+ if ( details.type === 'main_frame' ) {
+ return onRootFrameHeadersReceived(details);
+ }
+ // If we reach this point, we are dealing with a sub_frame
+
// Lookup the page store associated with this tab id.
var µb = µBlock;
var pageStore = µb.pageStoreFromTabId(tabId);
if ( !pageStore ) {
- if ( details.type === 'main_frame' ) {
- pageStore = µb.bindTabToPageStats(tabId, requestURL, 'beforeRequest');
- }
- if ( !pageStore ) {
- return;
- }
+ return;
+ }
+ // Frame id of frame request is the their own id, while the request is made
+ // in the context of the parent.
+ var context = pageStore.createContextFromFrameId(details.parentFrameId);
+ context.requestURL = details.url + '{inline-script}';
+ context.requestHostname = details.hostname;
+ context.requestType = 'inline-script';
+
+ var result = pageStore.filterRequestNoCache(context);
+
+ pageStore.logRequest(context, result);
+
+ // Don't block
+ if ( µb.isAllowResult(result) ) {
+ return;
}
- // https://github.com/chrisaljoudi/uBlock/issues/384
- // https://github.com/chrisaljoudi/uBlock/issues/540
- // Disabling local mirroring for the time being
- //if ( details.parentFrameId === -1 ) {
- // pageStore.skipLocalMirroring = headerStartsWith(details.responseHeaders, 'content-security-policy') !== '';
- //}
+ µb.updateBadgeAsync(tabId);
+ details.responseHeaders.push({
+ 'name': 'Content-Security-Policy',
+ 'value': "script-src 'unsafe-eval' *"
+ });
+
+ return { 'responseHeaders': details.responseHeaders };
+};
+var onRootFrameHeadersReceived = function(details) {
+ var tabId = details.tabId;
+ var requestURL = details.url;
var requestHostname = details.hostname;
+ var µb = µBlock;
- // https://github.com/chrisaljoudi/uBlock/issues/525
- // When we are dealing with the root frame, due to fix to issue #516, it
- // is likely the root frame has not been bound yet to the tab, and thus
- // we could end up using the context of the previous page for filtering.
- // So when the request is that of a root frame, simply create an
- // artificial context, this will ensure we are properly filtering
- // inline scripts.
- var context;
- if ( details.parentFrameId === -1 ) {
- var contextDomain = µb.URI.domainFromHostname(requestHostname);
- context = {
- rootHostname: requestHostname,
- rootDomain: contextDomain,
- pageHostname: requestHostname,
- pageDomain: contextDomain,
- preNavigationHeader: true
- };
- } else {
- context = pageStore;
+ // Check if the main_frame is a download
+ // ...
+ if ( headerValue(details.responseHeaders, 'content-disposition').lastIndexOf('attachment', 0) === 0 ) {
+ µb.tabContextManager.unpush(tabId, requestURL);
}
- // Concatenating with '{inline-script}' so that the network request cache
- // can distinguish from the document itself
- // The cache should do whatever it takes to not confuse same
- // URLs-different type
+ // Lookup the page store associated with this tab id.
+ var pageStore = µb.pageStoreFromTabId(tabId);
+ if ( !pageStore ) {
+ pageStore = µb.bindTabToPageStats(tabId, 'beforeRequest');
+ }
+ // I can't think of how pageStore could be null at this point.
+
+ var context = pageStore.createContextFromPage();
context.requestURL = requestURL + '{inline-script}';
context.requestHostname = requestHostname;
context.requestType = 'inline-script';
@@ -420,6 +332,18 @@ var onHeadersReceived = 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 '';
+};
+
+/******************************************************************************/
+
vAPI.net.onBeforeRequest = {
urls: [
'http://*/*',
@@ -458,18 +382,6 @@ vAPI.net.registerListeners();
/******************************************************************************/
-exports.temporarilyWhitelistDocument = function(url) {
- var µb = µBlock;
- var hostname = µb.URI.hostnameFromURI(url);
- if ( hostname === '' ) {
- return;
- }
-
- documentWhitelists[hostname] = Date.now() + 60 * 1000;
-};
-
-/******************************************************************************/
-
return exports;
/******************************************************************************/
diff --git a/src/js/ublock.js b/src/js/ublock.js
index b1d53efa9..0fd81e495 100644
--- a/src/js/ublock.js
+++ b/src/js/ublock.js
@@ -318,12 +318,4 @@ var matchWhitelistDirective = function(url, hostname, directive) {
/******************************************************************************/
-µBlock.toggleHostnameSwitch = function(details) {
- if ( this.hnSwitches.toggleZ(details.name, details.hostname, details.state) ) {
- this.saveHostnameSwitches();
- }
-};
-
-/******************************************************************************/
-
})();