mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-26 04:12:50 +01:00
refactored tabs/context code
This commit is contained in:
parent
ef9913c711
commit
6441161329
@ -705,7 +705,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);
|
||||
}
|
||||
|
@ -1918,6 +1918,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'
|
||||
|
@ -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,40 +214,38 @@ 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.noPopups = µb.hnSwitches.evaluateZ('noPopups', r.pageHostname);
|
||||
r.noStrictBlocking = µb.hnSwitches.evaluateZ('noStrictBlocking', r.pageHostname);
|
||||
r.noCosmeticFiltering = µb.hnSwitches.evaluateZ('noCosmeticFiltering', r.pageHostname);
|
||||
r.noPopups = µb.hnSwitches.evaluateZ('noPopups', tabContext.rootHostname);
|
||||
r.noStrictBlocking = µb.hnSwitches.evaluateZ('noStrictBlocking', tabContext.rootHostname);
|
||||
r.noCosmeticFiltering = µb.hnSwitches.evaluateZ('noCosmeticFiltering', tabContext.rootHostname);
|
||||
} 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;
|
||||
};
|
||||
|
||||
@ -473,15 +472,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;
|
||||
|
@ -391,6 +391,11 @@ NetFilteringResultCache.prototype.lookup = function(context) {
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
// Frame stores are used solely to associate a URL with a frame id. The
|
||||
// name `pageHostname` is used because of historical reasons. A more
|
||||
// appropriate name is `frameHostname` -- something to do in a future
|
||||
// refactoring.
|
||||
|
||||
// To mitigate memory churning
|
||||
var frameStoreJunkyard = [];
|
||||
var frameStoreJunkyardMax = 50;
|
||||
@ -419,20 +424,13 @@ FrameStore.prototype.init = function(rootHostname, frameURL) {
|
||||
var µburi = µb.URI;
|
||||
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 = '';
|
||||
|
||||
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);
|
||||
}
|
||||
@ -448,39 +446,28 @@ 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 = {};
|
||||
@ -489,13 +476,13 @@ PageStore.prototype.init = function(tabId, rawURL, pageURL) {
|
||||
this.perLoadBlockedRequestCount = 0;
|
||||
this.perLoadAllowedRequestCount = 0;
|
||||
this.hiddenElementCount = ''; // Empty string means "unknown"
|
||||
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
|
||||
@ -509,7 +496,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 ) {
|
||||
@ -517,8 +504,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 = '';
|
||||
}
|
||||
|
||||
@ -528,19 +515,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;
|
||||
};
|
||||
|
||||
@ -553,10 +537,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();
|
||||
@ -598,36 +578,78 @@ 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.hnSwitches.evaluateZ('noCosmeticFiltering', this.rootHostname) !== true &&
|
||||
(µb.userSettings.advancedUserEnabled &&
|
||||
µb.sessionFirewall.mustAllowCellZY(this.rootHostname, this.rootHostname, '*')) === false;
|
||||
if ( this.getNetFilteringSwitch() === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var tabContext = µb.tabContextManager.lookup(this.tabId);
|
||||
|
||||
if ( µb.hnSwitches.evaluateZ('noCosmeticFiltering', tabContext.rootHostname) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return µb.userSettings.advancedUserEnabled === false ||
|
||||
µb.sessionFirewall.mustAllowCellZY(tabContext.rootHostname, tabContext.rootHostname, '*') === false;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
PageStore.prototype.getGenericCosmeticFilteringSwitch = function() {
|
||||
return this.getNetFilteringSwitch() &&
|
||||
this.skipCosmeticFiltering === false &&
|
||||
µb.hnSwitches.evaluateZ('noCosmeticFiltering', this.rootHostname) !== true &&
|
||||
(µb.userSettings.advancedUserEnabled &&
|
||||
µb.sessionFirewall.mustAllowCellZY(this.rootHostname, this.rootHostname, '*')) === false;
|
||||
if ( this.skipCosmeticFiltering ) {
|
||||
return false;
|
||||
}
|
||||
return this.getSpecificCosmeticFilteringSwitch();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
@ -662,7 +684,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.rootHostname, context.requestHostname, context.requestType);
|
||||
var df = µb.sessionFirewall.evaluateCellZY(
|
||||
context.rootHostname,
|
||||
context.requestHostname,
|
||||
context.requestType
|
||||
);
|
||||
if ( df.mustBlockOrAllow() ) {
|
||||
result = df.toFilterString();
|
||||
}
|
||||
@ -690,16 +716,7 @@ var collapsibleRequestTypes = 'image sub_frame object';
|
||||
/******************************************************************************/
|
||||
|
||||
PageStore.prototype.filterRequestNoCache = function(context) {
|
||||
|
||||
// https://github.com/chrisaljoudi/uBlock/pull/1209
|
||||
// Not ideal, but until something better is figured, this solves the issue.
|
||||
// Long term, I have some ideas I would like to test to take care of those
|
||||
// cases for which a page store may not have been definitely bound to a tab.
|
||||
if ( context.overrideStore ) {
|
||||
if ( µb.getNetFilteringSwitch(context.requestURL) === false ) {
|
||||
return '';
|
||||
}
|
||||
} else if ( this.getNetFilteringSwitch() === false ) {
|
||||
if ( this.getNetFilteringSwitch() === false ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -711,7 +728,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.rootHostname, context.requestHostname, context.requestType);
|
||||
var df = µb.sessionFirewall.evaluateCellZY(
|
||||
context.rootHostname,
|
||||
context.requestHostname,
|
||||
context.requestType
|
||||
);
|
||||
if ( df.mustBlockOrAllow() ) {
|
||||
result = df.toFilterString();
|
||||
}
|
||||
@ -733,7 +754,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 ) {
|
||||
@ -756,22 +777,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 = '';
|
||||
|
394
src/js/tab.js
394
src/js/tab.js
@ -32,6 +32,337 @@
|
||||
|
||||
var µb = µBlock;
|
||||
|
||||
// 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
|
||||
// otherwise unmanageable scheme. µBlock needs web page to have a proper
|
||||
// hostname to work properly, so just like the 'chromium-behind-the-scene'
|
||||
// fake domain name, we map unknown schemes into a fake '{scheme}-scheme'
|
||||
// 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.isBehindTheSceneTabId(tabId) ) {
|
||||
return 'http://behind-the-scene/';
|
||||
}
|
||||
var uri = this.URI.set(pageURL);
|
||||
var scheme = uri.scheme;
|
||||
if ( scheme === 'https' || scheme === 'http' ) {
|
||||
return uri.normalizedURI();
|
||||
}
|
||||
|
||||
var url = 'http://' + scheme + '-scheme/';
|
||||
|
||||
if ( uri.hostname !== '' ) {
|
||||
url += uri.hostname + '/';
|
||||
}
|
||||
|
||||
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;
|
||||
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
|
||||
};
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
@ -42,7 +373,8 @@ vAPI.tabs.onNavigation = function(details) {
|
||||
if ( details.frameId !== 0 ) {
|
||||
return;
|
||||
}
|
||||
var pageStore = µb.bindTabToPageStats(details.tabId, details.url, 'afterNavigate');
|
||||
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
|
||||
@ -51,8 +383,8 @@ vAPI.tabs.onNavigation = function(details) {
|
||||
// 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;
|
||||
if ( pageStore && tabContext.rawURL.lastIndexOf('http', 0) === 0 ) {
|
||||
pageStore.hostnameToCountMap[tabContext.rootHostname] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
@ -69,7 +401,8 @@ vAPI.tabs.onUpdated = function(tabId, changeInfo, tab) {
|
||||
if ( !changeInfo.url ) {
|
||||
return;
|
||||
}
|
||||
µb.bindTabToPageStats(tabId, changeInfo.url, 'tabUpdated');
|
||||
µb.tabContextManager.commit(tabId, changeInfo.url);
|
||||
µb.bindTabToPageStats(tabId, 'tabUpdated');
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
@ -166,50 +499,15 @@ vAPI.tabs.registerListeners();
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
// 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
|
||||
// otherwise unmanageable scheme. µBlock needs web page to have a proper
|
||||
// hostname to work properly, so just like the 'chromium-behind-the-scene'
|
||||
// fake domain name, we map unknown schemes into a fake '{scheme}-scheme'
|
||||
// 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.isBehindTheSceneTabId(tabId) ) {
|
||||
return 'http://behind-the-scene/';
|
||||
}
|
||||
var uri = this.URI.set(pageURL);
|
||||
var scheme = uri.scheme;
|
||||
if ( scheme === 'https' || scheme === 'http' ) {
|
||||
return uri.normalizedURI();
|
||||
}
|
||||
|
||||
var url = 'http://' + scheme + '-scheme/';
|
||||
|
||||
if ( uri.hostname !== '' ) {
|
||||
url += uri.hostname + '/';
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Create an entry for the tab if it doesn't exist.
|
||||
|
||||
µb.bindTabToPageStats = function(tabId, pageURL, context) {
|
||||
µb.bindTabToPageStats = function(tabId, context) {
|
||||
if ( vAPI.isBehindTheSceneTabId(tabId) === false ) {
|
||||
this.updateBadgeAsync(tabId);
|
||||
}
|
||||
|
||||
// https://github.com/gorhill/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;
|
||||
}
|
||||
@ -219,7 +517,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,7 +527,7 @@ vAPI.tabs.registerListeners();
|
||||
}
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/516
|
||||
// If context if 'beforeRequest', do not rebind
|
||||
// If context if 'beforeRequest', do not rebind, wait for confirmation.
|
||||
if ( context === 'beforeRequest' ) {
|
||||
return pageStore;
|
||||
}
|
||||
@ -237,11 +535,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];
|
||||
@ -273,11 +573,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);
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
@ -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.
|
||||
@ -70,36 +60,14 @@ var onBeforeRequest = function(details) {
|
||||
var µb = µBlock;
|
||||
var pageStore = µb.pageStoreFromTabId(tabId);
|
||||
if ( !pageStore ) {
|
||||
// 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 ( (Date.now() - mostRecentRootDocURLTimestamp) >= 500 ) {
|
||||
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 !== '' ) {
|
||||
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:
|
||||
@ -109,11 +77,9 @@ 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;
|
||||
@ -125,6 +91,8 @@ var onBeforeRequest = function(details) {
|
||||
|
||||
// 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);
|
||||
@ -139,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 ) {
|
||||
@ -176,16 +129,16 @@ var onBeforeRequest = function(details) {
|
||||
/******************************************************************************/
|
||||
|
||||
var onBeforeRootFrameRequest = function(details) {
|
||||
var tabId = details.tabId;
|
||||
var requestURL = details.url;
|
||||
var µb = µBlock;
|
||||
|
||||
mostRecentRootDocURL = requestURL;
|
||||
mostRecentRootDocURLTimestamp = Date.now();
|
||||
µb.tabContextManager.push(tabId, requestURL);
|
||||
|
||||
// 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;
|
||||
var requestHostname = details.hostname;
|
||||
var requestDomain = µb.URI.domainFromHostname(requestHostname) || requestHostname;
|
||||
var context = {
|
||||
@ -222,7 +175,7 @@ var onBeforeRootFrameRequest = function(details) {
|
||||
}
|
||||
|
||||
// Log
|
||||
var pageStore = µb.bindTabToPageStats(details.tabId, requestURL, 'beforeRequest');
|
||||
var pageStore = µb.bindTabToPageStats(tabId, 'beforeRequest');
|
||||
if ( pageStore ) {
|
||||
pageStore.logRequest(context, result);
|
||||
}
|
||||
@ -240,7 +193,7 @@ var onBeforeRootFrameRequest = function(details) {
|
||||
why: result
|
||||
}));
|
||||
|
||||
vAPI.tabs.replace(details.tabId, vAPI.getURL('document-blocked.html?details=') + query);
|
||||
vAPI.tabs.replace(tabId, vAPI.getURL('document-blocked.html?details=') + query);
|
||||
|
||||
return { cancel: true };
|
||||
};
|
||||
@ -302,9 +255,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
|
||||
@ -312,10 +266,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) ) {
|
||||
@ -340,56 +294,68 @@ var onHeadersReceived = function(details) {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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') !== '';
|
||||
//}
|
||||
// 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;
|
||||
}
|
||||
|
||||
µ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);
|
||||
// https://github.com/chrisaljoudi/uBlock/pull/1209
|
||||
// Using `overrideStore` solves the issue.
|
||||
context = {
|
||||
overrideStore: true,
|
||||
rootHostname: requestHostname,
|
||||
rootDomain: contextDomain,
|
||||
pageHostname: requestHostname,
|
||||
pageDomain: contextDomain
|
||||
};
|
||||
} 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';
|
||||
@ -415,6 +381,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://*/*',
|
||||
@ -476,6 +454,8 @@ var isTemporarilyWhitelisted = function(result, hostname) {
|
||||
return result;
|
||||
};
|
||||
|
||||
var documentWhitelists = Object.create(null);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
exports.temporarilyWhitelistDocument = function(hostname) {
|
||||
|
@ -340,6 +340,10 @@ var matchWhitelistDirective = function(url, hostname, directive) {
|
||||
|
||||
µBlock.getHiddenElementCount = function(tabId, callback) {
|
||||
callback = callback || this.noopFunc;
|
||||
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
vAPI.tabs.injectScript(tabId, { file: 'js/cosmetic-count.js' }, callback);
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user