mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-16 23:42:39 +01:00
Refactor context-keeping
Improves reliability and thoroughness of blocking on all browsers.
This commit is contained in:
parent
edd30ec53b
commit
fa3666f85d
@ -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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
@ -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'
|
||||
|
@ -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":
|
||||
|
@ -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;}\
|
||||
|
@ -19,7 +19,6 @@
|
||||
<script src="js/dynamic-net-filtering.js"></script>
|
||||
<script src="js/static-net-filtering.js"></script>
|
||||
<script src="js/cosmetic-filtering.js"></script>
|
||||
<script src="js/hnswitches.js"></script>
|
||||
<script src="js/ublock.js"></script>
|
||||
<script src="js/messaging.js"></script>
|
||||
<script src="js/profiler.js"></script>
|
||||
|
@ -1,81 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<link rel="stylesheet" href="css/common.css" type="text/css">
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
font-size: large;
|
||||
text-align: center;
|
||||
}
|
||||
body > div {
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
body > div > p {
|
||||
margin: 4px 0;
|
||||
}
|
||||
body > div > p:first-child {
|
||||
margin: 1.5em 0 0 0;
|
||||
}
|
||||
.code {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
display: inline-block;
|
||||
font-family: monospace;
|
||||
padding: 2px 4px;
|
||||
word-break: break-all;
|
||||
}
|
||||
button {
|
||||
cursor: pointer;
|
||||
margin: 0 1em 0.25em 1em;
|
||||
padding: 0.25em 0.5em;
|
||||
font-size: inherit;
|
||||
}
|
||||
#warningSign {
|
||||
margin: 1e, 0;
|
||||
opacity: 1;
|
||||
pointer-events: none;
|
||||
width: 100%;
|
||||
}
|
||||
#warningSign > span {
|
||||
color: #f2a500;
|
||||
font-size: 180px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- http://commons.wikimedia.org/wiki/File:Caution_sign_used_on_roads_pn.svg
|
||||
Public domain. I removed the shadow.
|
||||
--><div id="warningSign"><span class="fa"></span></div>
|
||||
<div>
|
||||
<p data-i18n="docblockedPrompt1"></p>
|
||||
<p class="what code"></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p data-i18n="docblockedPrompt2"></p>
|
||||
<p id="why" class="code"></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p><button id="back" data-i18n="docblockedBack" type="button"></button>
|
||||
<button id="bye" data-i18n="docblockedClose" type="button"></button></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p id="proceed"></p>
|
||||
<p><button id="proceedTemporary" data-i18n="docblockedDisableTemporary" type="button"></button>
|
||||
<button id="proceedPermanent" data-i18n="docblockedDisablePermanent" type="button"></button></p>
|
||||
</div>
|
||||
|
||||
<div style="display: none;">
|
||||
<span id="proceedTemplate"><span></span><span class="code"></span><span></span></span>
|
||||
</div>
|
||||
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/document-blocked.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
@ -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();
|
||||
|
||||
/******************************************************************************/
|
@ -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,
|
||||
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 || '',
|
||||
|
@ -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.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 = '';
|
||||
|
@ -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': '',
|
||||
|
@ -75,12 +75,6 @@
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
µBlock.saveHostnameSwitches = function() {
|
||||
this.XAL.keyvalSetOne('hostnameSwitchesString', this.hnSwitches.toString());
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
µBlock.saveWhitelist = function() {
|
||||
var bin = {
|
||||
'netWhitelist': this.stringFromWhitelist(this.netWhitelist)
|
||||
|
598
src/js/tab.js
598
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
|
||||
<https://github.com/chrisaljoudi/uBlock/issues/516>
|
||||
|
||||
- a network request for a secondary resource is labeled with a tab id for
|
||||
which no root document was pulled for that tab.
|
||||
<https://github.com/chrisaljoudi/uBlock/issues/1001>
|
||||
|
||||
- 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.
|
||||
<https://github.com/chrisaljoudi/uBlock/issues/1205>
|
||||
<https://github.com/chrisaljoudi/uBlock/issues/1140>
|
||||
|
||||
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 <https://github.com/chrisaljoudi/uBlock/issues/516>.
|
||||
|
||||
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 <https://github.com/chrisaljoudi/uBlock/issues/1205>. 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
|
||||
<https://github.com/chrisaljoudi/uBlock/issues/1001>.
|
||||
|
||||
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);
|
||||
|
@ -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 };
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
@ -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;
|
||||
}
|
||||
// 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);
|
||||
|
||||
var requestHostname = details.hostname;
|
||||
details.responseHeaders.push({
|
||||
'name': 'Content-Security-Policy',
|
||||
'value': "script-src 'unsafe-eval' *"
|
||||
});
|
||||
|
||||
// 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
|
||||
return { 'responseHeaders': details.responseHeaders };
|
||||
};
|
||||
} else {
|
||||
context = pageStore;
|
||||
var onRootFrameHeadersReceived = function(details) {
|
||||
var tabId = details.tabId;
|
||||
var requestURL = details.url;
|
||||
var requestHostname = details.hostname;
|
||||
var µb = µBlock;
|
||||
|
||||
// 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;
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
})();
|
||||
|
Loading…
Reference in New Issue
Block a user