2014-06-24 00:42:43 +02:00
|
|
|
/*******************************************************************************
|
|
|
|
|
2015-06-09 16:27:08 +02:00
|
|
|
uBlock - a browser extension to block requests.
|
|
|
|
Copyright (C) 2014-2015 Raymond Hill
|
2014-06-24 00:42:43 +02:00
|
|
|
|
|
|
|
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/gorhill/uBlock
|
|
|
|
*/
|
|
|
|
|
2015-01-02 19:42:35 +01:00
|
|
|
/* global vAPI, µBlock */
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
(function() {
|
|
|
|
|
2014-10-19 13:11:27 +02:00
|
|
|
'use strict';
|
2014-06-24 00:42:43 +02:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-01-02 19:42:35 +01:00
|
|
|
var µb = µBlock;
|
|
|
|
|
2015-04-09 00:46:08 +02:00
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
|
2015-04-27 16:54:13 +02:00
|
|
|
var fakeHostname = scheme + '-scheme';
|
2015-04-09 00:46:08 +02:00
|
|
|
|
|
|
|
if ( uri.hostname !== '' ) {
|
2015-04-27 16:54:13 +02:00
|
|
|
fakeHostname = uri.hostname + '.' + fakeHostname;
|
|
|
|
} else if ( scheme === 'about' && uri.path !== '' ) {
|
|
|
|
fakeHostname = uri.path + '.' + fakeHostname;
|
2015-04-09 00:46:08 +02:00
|
|
|
}
|
|
|
|
|
2015-04-27 16:54:13 +02:00
|
|
|
return 'http://' + fakeHostname + '/';
|
2015-04-09 00:46:08 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2015-05-28 20:47:19 +02:00
|
|
|
// A pushed entry is removed from the stack unless it is committed with
|
|
|
|
// a set time.
|
|
|
|
var StackEntry = function(url, commit) {
|
|
|
|
this.url = url;
|
|
|
|
this.committed = commit;
|
|
|
|
this.tstamp = Date.now();
|
|
|
|
};
|
|
|
|
|
2015-04-09 00:46:08 +02:00
|
|
|
var TabContext = function(tabId) {
|
2015-04-13 01:55:10 +02:00
|
|
|
this.tabId = tabId.toString();
|
2015-04-09 00:46:08 +02:00
|
|
|
this.stack = [];
|
|
|
|
this.rawURL =
|
|
|
|
this.normalURL =
|
|
|
|
this.rootHostname =
|
|
|
|
this.rootDomain = '';
|
2015-05-28 20:47:19 +02:00
|
|
|
this.commitTimer = null;
|
|
|
|
this.gcTimer = null;
|
2015-04-09 00:46:08 +02:00
|
|
|
|
|
|
|
tabContexts[tabId] = this;
|
|
|
|
};
|
|
|
|
|
|
|
|
TabContext.prototype.destroy = function() {
|
|
|
|
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
|
|
|
|
return;
|
|
|
|
}
|
2015-05-28 20:47:19 +02:00
|
|
|
if ( this.gcTimer !== null ) {
|
|
|
|
clearTimeout(this.gcTimer);
|
|
|
|
this.gcTimer = null;
|
2015-04-09 00:46:08 +02:00
|
|
|
}
|
|
|
|
delete tabContexts[this.tabId];
|
|
|
|
};
|
|
|
|
|
|
|
|
TabContext.prototype.onTab = function(tab) {
|
|
|
|
if ( tab ) {
|
2015-05-28 20:47:19 +02:00
|
|
|
this.gcTimer = vAPI.setTimeout(this.onGC.bind(this), gcPeriod);
|
2015-04-09 00:46:08 +02:00
|
|
|
} else {
|
|
|
|
this.destroy();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-05-28 20:47:19 +02:00
|
|
|
TabContext.prototype.onGC = function() {
|
|
|
|
this.gcTimer = null;
|
|
|
|
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
vAPI.tabs.get(this.tabId, this.onTab.bind(this));
|
|
|
|
};
|
|
|
|
|
|
|
|
// https://github.com/gorhill/uBlock/issues/248
|
|
|
|
// Stack entries have to be committed to stick. Non-committed stack
|
|
|
|
// entries are removed after a set delay.
|
|
|
|
TabContext.prototype.onCommit = function() {
|
2015-04-09 00:46:08 +02:00
|
|
|
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
|
|
|
|
return;
|
|
|
|
}
|
2015-05-28 20:47:19 +02:00
|
|
|
this.commitTimer = null;
|
|
|
|
// Remove uncommitted entries at the top of the stack.
|
|
|
|
var i = this.stack.length;
|
|
|
|
while ( i-- ) {
|
|
|
|
if ( this.stack[i].committed ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2015-06-05 19:37:46 +02:00
|
|
|
// https://github.com/gorhill/uBlock/issues/300
|
|
|
|
// If no committed entry was found, fall back on the bottom-most one
|
|
|
|
// as being the committed one by default.
|
|
|
|
if ( i === -1 && this.stack.length !== 0 ) {
|
|
|
|
this.stack[0].committed = true;
|
|
|
|
i = 0;
|
|
|
|
}
|
2015-05-28 20:47:19 +02:00
|
|
|
i += 1;
|
|
|
|
if ( i < this.stack.length ) {
|
|
|
|
this.stack.length = i;
|
|
|
|
this.update();
|
|
|
|
}
|
2015-04-09 00:46:08 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
// 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
|
2015-07-17 00:15:57 +02:00
|
|
|
// want to flush it.
|
2015-04-09 00:46:08 +02:00
|
|
|
TabContext.prototype.autodestroy = function() {
|
|
|
|
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
|
|
|
|
return;
|
|
|
|
}
|
2015-05-28 20:47:19 +02:00
|
|
|
this.gcTimer = vAPI.setTimeout(this.onGC.bind(this), gcPeriod);
|
2015-04-09 00:46:08 +02:00
|
|
|
};
|
|
|
|
|
2015-05-28 20:47:19 +02:00
|
|
|
// Update just force all properties to be updated to match the most recent
|
2015-04-09 00:46:08 +02:00
|
|
|
// root URL.
|
|
|
|
TabContext.prototype.update = function() {
|
|
|
|
if ( this.stack.length === 0 ) {
|
|
|
|
this.rawURL = this.normalURL = this.rootHostname = this.rootDomain = '';
|
2015-05-28 20:47:19 +02:00
|
|
|
return;
|
2015-04-09 00:46:08 +02:00
|
|
|
}
|
2015-05-28 20:47:19 +02:00
|
|
|
var stackEntry = this.stack[this.stack.length - 1];
|
|
|
|
this.rawURL = stackEntry.url;
|
|
|
|
this.normalURL = µb.normalizePageURL(this.tabId, this.rawURL);
|
|
|
|
this.rootHostname = µb.URI.hostnameFromURI(this.normalURL);
|
|
|
|
this.rootDomain = µb.URI.domainFromHostname(this.rootHostname);
|
2015-04-09 00:46:08 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
// Called whenever a candidate root URL is spotted for the tab.
|
|
|
|
TabContext.prototype.push = function(url) {
|
|
|
|
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
|
|
|
|
return;
|
|
|
|
}
|
2015-04-29 16:29:23 +02:00
|
|
|
var count = this.stack.length;
|
2015-05-28 20:47:19 +02:00
|
|
|
if ( count !== 0 && this.stack[count - 1].url === url ) {
|
2015-04-29 16:29:23 +02:00
|
|
|
return;
|
|
|
|
}
|
2015-05-28 20:47:19 +02:00
|
|
|
this.stack.push(new StackEntry(url));
|
2015-04-09 00:46:08 +02:00
|
|
|
this.update();
|
2015-05-28 20:47:19 +02:00
|
|
|
if ( this.commitTimer === null ) {
|
|
|
|
clearTimeout(this.commitTimer);
|
|
|
|
}
|
|
|
|
this.commitTimer = vAPI.setTimeout(this.onCommit.bind(this), 1000);
|
2015-04-09 00:46:08 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
// 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.
|
2015-05-28 20:47:19 +02:00
|
|
|
var i = this.stack.length;
|
|
|
|
if ( i === 1 ) {
|
2015-04-09 00:46:08 +02:00
|
|
|
return;
|
|
|
|
}
|
2015-05-28 20:47:19 +02:00
|
|
|
while ( i-- ) {
|
|
|
|
if ( this.stack[i].url !== url ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
this.stack.splice(i, 1);
|
|
|
|
if ( i === this.stack.length ) {
|
|
|
|
this.update();
|
|
|
|
}
|
2015-04-09 00:46:08 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
2015-05-28 20:47:19 +02:00
|
|
|
this.stack = [new StackEntry(url, true)];
|
2015-04-09 00:46:08 +02:00
|
|
|
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 exists = function(tabId) {
|
|
|
|
return tabContexts[tabId] !== undefined;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Behind-the-scene tab context
|
|
|
|
(function() {
|
|
|
|
var entry = new TabContext(vAPI.noTabId);
|
2015-05-28 20:47:19 +02:00
|
|
|
entry.stack.push(new StackEntry('', true));
|
2015-04-09 00:46:08 +02:00
|
|
|
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,
|
|
|
|
exists: exists,
|
|
|
|
createContext: createContext
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
2015-01-02 19:42:35 +01:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2014-10-17 21:44:19 +02:00
|
|
|
// When the DOM content of root frame is loaded, this means the tab
|
|
|
|
// content has changed.
|
2015-03-08 16:06:36 +01:00
|
|
|
|
2014-10-17 21:44:19 +02:00
|
|
|
vAPI.tabs.onNavigation = function(details) {
|
|
|
|
if ( details.frameId !== 0 ) {
|
|
|
|
return;
|
|
|
|
}
|
2015-04-09 00:46:08 +02:00
|
|
|
var tabContext = µb.tabContextManager.commit(details.tabId, details.url);
|
|
|
|
var pageStore = µb.bindTabToPageStats(details.tabId, 'afterNavigate');
|
2015-01-28 22:19:46 +01:00
|
|
|
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/630
|
2015-01-28 22:19:46 +01:00
|
|
|
// 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.
|
2015-03-15 18:14:52 +01:00
|
|
|
// TODO: Eventually, we will have to use an API to check whether a scheme
|
2015-01-28 22:19:46 +01:00
|
|
|
// is supported as I suspect we are going to start to see `ws`, `wss`
|
|
|
|
// as well soon.
|
2015-04-09 00:46:08 +02:00
|
|
|
if ( pageStore && tabContext.rawURL.lastIndexOf('http', 0) === 0 ) {
|
|
|
|
pageStore.hostnameToCountMap[tabContext.rootHostname] = 0;
|
2015-01-28 22:19:46 +01:00
|
|
|
}
|
2014-10-17 21:44:19 +02:00
|
|
|
};
|
2014-09-08 19:45:03 +02:00
|
|
|
|
2015-03-08 16:06:36 +01:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2014-10-17 21:44:19 +02:00
|
|
|
// 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.
|
2015-03-08 16:06:36 +01:00
|
|
|
|
2014-10-17 21:44:19 +02:00
|
|
|
vAPI.tabs.onUpdated = function(tabId, changeInfo, tab) {
|
|
|
|
if ( !tab.url || tab.url === '' ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ( !changeInfo.url ) {
|
|
|
|
return;
|
|
|
|
}
|
2015-04-09 00:46:08 +02:00
|
|
|
µb.tabContextManager.commit(tabId, changeInfo.url);
|
|
|
|
µb.bindTabToPageStats(tabId, 'tabUpdated');
|
2014-10-17 21:44:19 +02:00
|
|
|
};
|
2014-06-24 00:42:43 +02:00
|
|
|
|
2015-03-08 16:06:36 +01:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2014-10-17 21:44:19 +02:00
|
|
|
vAPI.tabs.onClosed = function(tabId) {
|
2015-07-17 00:15:57 +02:00
|
|
|
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
|
2014-10-17 21:44:19 +02:00
|
|
|
return;
|
|
|
|
}
|
2015-01-02 19:42:35 +01:00
|
|
|
µb.unbindTabFromPageStats(tabId);
|
2014-10-17 21:44:19 +02:00
|
|
|
};
|
2014-10-15 19:14:25 +02:00
|
|
|
|
2015-03-08 16:06:36 +01:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/297
|
2015-03-08 16:06:36 +01:00
|
|
|
|
2014-10-17 21:44:19 +02:00
|
|
|
vAPI.tabs.onPopup = function(details) {
|
2015-03-09 15:19:00 +01:00
|
|
|
//console.debug('vAPI.tabs.onPopup: details = %o', details);
|
2014-12-28 16:07:43 +01:00
|
|
|
|
2015-04-09 17:19:31 +02:00
|
|
|
var tabContext = µb.tabContextManager.lookup(details.openerTabId);
|
2015-04-13 01:55:10 +02:00
|
|
|
var openerURL = '';
|
|
|
|
if ( tabContext.tabId === details.openerTabId ) {
|
2015-04-09 17:19:31 +02:00
|
|
|
openerURL = tabContext.normalURL;
|
2015-03-08 16:06:36 +01:00
|
|
|
}
|
|
|
|
if ( openerURL === '' ) {
|
2014-10-17 21:44:19 +02:00
|
|
|
return;
|
|
|
|
}
|
2015-03-08 16:06:36 +01:00
|
|
|
|
|
|
|
var µburi = µb.URI;
|
2015-06-14 13:46:58 +02:00
|
|
|
|
|
|
|
// https://github.com/gorhill/uBlock/issues/341
|
|
|
|
// Allow popups if uBlock is turned off in opener's context.
|
|
|
|
if ( µb.getNetFilteringSwitch(openerURL) === false ) {
|
|
|
|
return;
|
|
|
|
}
|
2015-03-08 16:06:36 +01:00
|
|
|
|
|
|
|
var targetURL = details.targetURL;
|
2015-03-27 18:00:55 +01:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-14 13:46:58 +02:00
|
|
|
var openerHostname = µburi.hostnameFromURI(openerURL);
|
|
|
|
var openerDomain = µburi.domainFromHostname(openerHostname);
|
2015-03-27 19:59:17 +01:00
|
|
|
var context = {
|
|
|
|
pageHostname: openerHostname,
|
|
|
|
pageDomain: openerDomain,
|
|
|
|
rootHostname: openerHostname,
|
|
|
|
rootDomain: openerDomain,
|
|
|
|
requestURL: targetURL,
|
|
|
|
requestHostname: µb.URI.hostnameFromURI(targetURL),
|
|
|
|
requestType: 'popup'
|
|
|
|
};
|
|
|
|
|
2014-10-17 21:44:19 +02:00
|
|
|
var result = '';
|
2015-08-17 23:10:15 +02:00
|
|
|
var loggerEnabled = µb.logger.isEnabled();
|
2014-10-17 21:44:19 +02:00
|
|
|
|
2015-03-27 18:00:55 +01:00
|
|
|
// Check user switch first
|
2015-04-09 17:19:31 +02:00
|
|
|
if ( µb.hnSwitches.evaluateZ('no-popups', openerHostname) ) {
|
2015-04-25 06:28:30 +02:00
|
|
|
result = 'ub:no-popups: ' + µb.hnSwitches.z + ' true';
|
2015-03-27 18:00:55 +01:00
|
|
|
}
|
|
|
|
|
2015-08-17 23:10:15 +02:00
|
|
|
// https://github.com/gorhill/uBlock/issues/581
|
|
|
|
// Take into account popup-specific rules in dynamic URL filtering, OR
|
|
|
|
// generic allow rules.
|
|
|
|
if ( result === '' ) {
|
|
|
|
µb.sessionURLFiltering.evaluateZ(openerHostname, targetURL, 'popup');
|
|
|
|
if (
|
|
|
|
µb.sessionURLFiltering.r === 1 && µb.sessionURLFiltering.type === 'popup' ||
|
|
|
|
µb.sessionURLFiltering.r === 2
|
|
|
|
) {
|
|
|
|
result = µb.sessionURLFiltering.toFilterString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://github.com/gorhill/uBlock/issues/581
|
|
|
|
// Take into account `allow` rules in dynamic filtering: `block` rules
|
|
|
|
// are ignored, as block rules are not meant to block specific types
|
|
|
|
// like `popup` (just like with static filters).
|
|
|
|
if ( result === '' ) {
|
|
|
|
µb.sessionFirewall.evaluateCellZY(openerHostname, context.requestHostname, 'popup');
|
|
|
|
if ( µb.sessionFirewall.r === 2 ) {
|
|
|
|
result = µb.sessionFirewall.toFilterString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/323
|
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/1142
|
2015-08-17 21:09:14 +02:00
|
|
|
// Don't block if uBlock is turned off in popup's context
|
2015-08-17 23:10:15 +02:00
|
|
|
if (
|
|
|
|
result === '' &&
|
|
|
|
µb.getNetFilteringSwitch(targetURL) &&
|
|
|
|
µb.staticNetFilteringEngine.matchStringExactType(context, targetURL, 'popup') !== undefined
|
|
|
|
) {
|
|
|
|
result = µb.staticNetFilteringEngine.toResultString(loggerEnabled);
|
2014-10-17 21:44:19 +02:00
|
|
|
}
|
2014-10-15 19:14:25 +02:00
|
|
|
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/91
|
2015-08-17 23:10:15 +02:00
|
|
|
var pageStore = µb.pageStoreFromTabId(details.openerTabId);
|
2015-03-08 16:06:36 +01:00
|
|
|
if ( pageStore ) {
|
2015-03-27 19:59:17 +01:00
|
|
|
pageStore.logRequest(context, result);
|
2014-10-24 16:14:25 +02:00
|
|
|
}
|
2015-06-14 13:46:58 +02:00
|
|
|
|
2015-08-17 23:10:15 +02:00
|
|
|
if ( loggerEnabled ) {
|
2015-06-09 16:27:08 +02:00
|
|
|
µb.logger.writeOne(
|
|
|
|
details.openerTabId,
|
|
|
|
'net',
|
|
|
|
result,
|
|
|
|
'popup',
|
|
|
|
targetURL,
|
|
|
|
openerHostname,
|
|
|
|
openerHostname
|
|
|
|
);
|
|
|
|
}
|
2014-10-15 19:14:25 +02:00
|
|
|
|
2014-10-17 21:44:19 +02:00
|
|
|
// Not blocked
|
2015-01-22 19:00:59 +01:00
|
|
|
if ( µb.isAllowResult(result) ) {
|
2014-10-17 21:44:19 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Blocked
|
2015-04-16 00:45:07 +02:00
|
|
|
if ( µb.userSettings.showIconBadge ) {
|
|
|
|
µb.updateBadgeAsync(details.openerTabId);
|
|
|
|
}
|
2014-10-24 16:14:25 +02:00
|
|
|
|
2014-10-17 21:44:19 +02:00
|
|
|
// It is a popup, block and remove the tab.
|
2015-03-08 16:06:36 +01:00
|
|
|
µb.unbindTabFromPageStats(details.targetTabId);
|
|
|
|
vAPI.tabs.remove(details.targetTabId);
|
2014-12-01 20:45:00 +01:00
|
|
|
|
|
|
|
return true;
|
2014-10-17 21:44:19 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
vAPI.tabs.registerListeners();
|
2014-10-15 19:14:25 +02:00
|
|
|
|
2014-06-24 00:42:43 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
// Create an entry for the tab if it doesn't exist.
|
|
|
|
|
2015-04-09 00:46:08 +02:00
|
|
|
µb.bindTabToPageStats = function(tabId, context) {
|
2015-05-16 16:15:02 +02:00
|
|
|
this.updateBadgeAsync(tabId);
|
2014-07-07 01:14:32 +02:00
|
|
|
|
2014-07-14 17:24:59 +02:00
|
|
|
// Do not create a page store for URLs which are of no interests
|
2015-04-09 00:46:08 +02:00
|
|
|
if ( µb.tabContextManager.exists(tabId) === false ) {
|
2014-07-14 17:24:59 +02:00
|
|
|
this.unbindTabFromPageStats(tabId);
|
2014-06-24 00:42:43 +02:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2015-01-17 13:19:48 +01:00
|
|
|
// Reuse page store if one exists: this allows to guess if a tab is a popup
|
2014-06-24 00:42:43 +02:00
|
|
|
var pageStore = this.pageStores[tabId];
|
2014-08-02 17:40:27 +02:00
|
|
|
|
2015-01-17 13:19:48 +01:00
|
|
|
// Tab is not bound
|
|
|
|
if ( !pageStore ) {
|
2015-05-18 14:12:35 +02:00
|
|
|
this.updateTitle(tabId);
|
|
|
|
this.pageStoresToken = Date.now();
|
2015-08-18 17:44:24 +02:00
|
|
|
return (this.pageStores[tabId] = this.PageStore.factory(tabId));
|
2015-01-17 13:19:48 +01:00
|
|
|
}
|
|
|
|
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/516
|
2015-04-05 18:03:14 +02:00
|
|
|
// Never rebind behind-the-scene scope
|
|
|
|
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
|
|
|
|
return pageStore;
|
|
|
|
}
|
|
|
|
|
2015-01-17 13:19:48 +01:00
|
|
|
// https://github.com/gorhill/uBlock/issues/516
|
2015-04-09 00:46:08 +02:00
|
|
|
// If context if 'beforeRequest', do not rebind, wait for confirmation.
|
2015-01-17 13:19:48 +01:00
|
|
|
if ( context === 'beforeRequest' ) {
|
|
|
|
return pageStore;
|
|
|
|
}
|
|
|
|
|
2015-01-24 18:34:36 +01:00
|
|
|
// 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.
|
2015-04-09 00:46:08 +02:00
|
|
|
pageStore.reuse(context);
|
2014-06-24 00:42:43 +02:00
|
|
|
|
2015-05-18 14:12:35 +02:00
|
|
|
this.updateTitle(tabId);
|
|
|
|
this.pageStoresToken = Date.now();
|
|
|
|
|
2014-06-24 00:42:43 +02:00
|
|
|
return pageStore;
|
|
|
|
};
|
|
|
|
|
2015-04-09 00:46:08 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-01-02 19:42:35 +01:00
|
|
|
µb.unbindTabFromPageStats = function(tabId) {
|
2014-07-16 23:20:11 +02:00
|
|
|
//console.debug('µBlock> unbindTabFromPageStats(%d)', tabId);
|
2014-09-14 22:20:40 +02:00
|
|
|
var pageStore = this.pageStores[tabId];
|
|
|
|
if ( pageStore !== undefined ) {
|
|
|
|
pageStore.dispose();
|
|
|
|
delete this.pageStores[tabId];
|
2015-05-18 14:12:35 +02:00
|
|
|
this.pageStoresToken = Date.now();
|
2014-09-14 22:20:40 +02:00
|
|
|
}
|
2014-06-24 00:42:43 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-01-02 19:42:35 +01:00
|
|
|
µb.pageStoreFromTabId = function(tabId) {
|
2015-05-01 14:27:41 +02:00
|
|
|
return this.pageStores[tabId] || null;
|
2014-06-24 00:42:43 +02:00
|
|
|
};
|
|
|
|
|
2015-01-20 00:42:58 +01:00
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
// Permanent page store for behind-the-scene requests. Must never be removed.
|
|
|
|
|
2015-04-09 00:46:08 +02:00
|
|
|
µb.pageStores[vAPI.noTabId] = µb.PageStore.factory(vAPI.noTabId);
|
2015-05-16 16:15:02 +02:00
|
|
|
µb.pageStores[vAPI.noTabId].title = vAPI.i18n('logBehindTheScene');
|
2015-01-20 00:42:58 +01:00
|
|
|
|
2015-01-02 19:42:35 +01:00
|
|
|
/******************************************************************************/
|
2015-05-16 16:15:02 +02:00
|
|
|
|
|
|
|
µb.updateTitle = (function() {
|
|
|
|
var tabIdToTimer = Object.create(null);
|
|
|
|
var tabIdToTryCount = Object.create(null);
|
|
|
|
var delay = 499;
|
|
|
|
|
|
|
|
var tryNoMore = function(tabId) {
|
|
|
|
delete tabIdToTryCount[tabId];
|
|
|
|
};
|
|
|
|
|
|
|
|
var tryAgain = function(tabId) {
|
|
|
|
var count = tabIdToTryCount[tabId];
|
|
|
|
if ( count === undefined ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if ( count === 1 ) {
|
|
|
|
delete tabIdToTryCount[tabId];
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
tabIdToTryCount[tabId] = count - 1;
|
2015-05-17 19:02:56 +02:00
|
|
|
tabIdToTimer[tabId] = vAPI.setTimeout(updateTitle.bind(µb, tabId), delay);
|
2015-05-16 16:15:02 +02:00
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
var onTabReady = function(tabId, tab) {
|
|
|
|
if ( !tab ) {
|
|
|
|
return tryNoMore(tabId);
|
|
|
|
}
|
|
|
|
var pageStore = this.pageStoreFromTabId(tabId);
|
|
|
|
if ( pageStore === null ) {
|
|
|
|
return tryNoMore(tabId);
|
|
|
|
}
|
2015-05-18 14:12:35 +02:00
|
|
|
// Firefox needs this: if you detach a tab, the new tab won't have
|
|
|
|
// its rawURL set. Concretely, this causes the logger to report an
|
|
|
|
// entry to itself in the logger's tab selector.
|
|
|
|
// TODO: Investigate for a fix vAPI-side.
|
|
|
|
pageStore.rawURL = tab.url;
|
|
|
|
this.pageStoresToken = Date.now();
|
2015-05-16 16:15:02 +02:00
|
|
|
if ( !tab.title && tryAgain(tabId) ) {
|
|
|
|
return;
|
|
|
|
}
|
2015-05-26 23:16:16 +02:00
|
|
|
// https://github.com/gorhill/uMatrix/issues/225
|
|
|
|
// Sometimes title changes while page is loading.
|
|
|
|
var settled = tab.title && tab.title === pageStore.title;
|
2015-05-16 16:15:02 +02:00
|
|
|
pageStore.title = tab.title || tab.url || '';
|
2015-05-18 14:12:35 +02:00
|
|
|
this.pageStoresToken = Date.now();
|
2015-05-26 23:16:16 +02:00
|
|
|
if ( settled || !tryAgain(tabId) ) {
|
|
|
|
tryNoMore(tabId);
|
|
|
|
}
|
2015-05-16 16:15:02 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
var updateTitle = function(tabId) {
|
|
|
|
delete tabIdToTimer[tabId];
|
|
|
|
vAPI.tabs.get(tabId, onTabReady.bind(this, tabId));
|
|
|
|
};
|
|
|
|
|
|
|
|
return function(tabId) {
|
|
|
|
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ( tabIdToTimer[tabId] ) {
|
|
|
|
clearTimeout(tabIdToTimer[tabId]);
|
|
|
|
}
|
2015-05-17 19:02:56 +02:00
|
|
|
tabIdToTimer[tabId] = vAPI.setTimeout(updateTitle.bind(this, tabId), delay);
|
2015-05-16 16:15:02 +02:00
|
|
|
tabIdToTryCount[tabId] = 5;
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
2015-01-02 19:42:35 +01:00
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
// Stale page store entries janitor
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/455
|
2015-01-02 19:42:35 +01:00
|
|
|
|
|
|
|
var pageStoreJanitorPeriod = 15 * 60 * 1000;
|
|
|
|
var pageStoreJanitorSampleAt = 0;
|
|
|
|
var pageStoreJanitorSampleSize = 10;
|
|
|
|
|
|
|
|
var pageStoreJanitor = function() {
|
|
|
|
var vapiTabs = vAPI.tabs;
|
|
|
|
var tabIds = Object.keys(µb.pageStores).sort();
|
|
|
|
var checkTab = function(tabId) {
|
|
|
|
vapiTabs.get(tabId, function(tab) {
|
|
|
|
if ( !tab ) {
|
2015-04-09 17:19:31 +02:00
|
|
|
//console.error('tab.js> pageStoreJanitor(): stale page store found:', µtabId);
|
2015-01-02 19:42:35 +01:00
|
|
|
µb.unbindTabFromPageStats(tabId);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
if ( pageStoreJanitorSampleAt >= tabIds.length ) {
|
|
|
|
pageStoreJanitorSampleAt = 0;
|
|
|
|
}
|
2015-01-20 00:42:58 +01:00
|
|
|
var tabId;
|
2015-01-02 19:42:35 +01:00
|
|
|
var n = Math.min(pageStoreJanitorSampleAt + pageStoreJanitorSampleSize, tabIds.length);
|
|
|
|
for ( var i = pageStoreJanitorSampleAt; i < n; i++ ) {
|
2015-01-20 00:42:58 +01:00
|
|
|
tabId = tabIds[i];
|
|
|
|
// Do not remove behind-the-scene page store
|
2015-04-05 18:03:14 +02:00
|
|
|
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
|
2015-01-20 00:42:58 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
checkTab(tabId);
|
2015-01-02 19:42:35 +01:00
|
|
|
}
|
|
|
|
pageStoreJanitorSampleAt = n;
|
|
|
|
|
2015-05-17 19:02:56 +02:00
|
|
|
vAPI.setTimeout(pageStoreJanitor, pageStoreJanitorPeriod);
|
2015-01-02 19:42:35 +01:00
|
|
|
};
|
|
|
|
|
2015-05-17 19:02:56 +02:00
|
|
|
vAPI.setTimeout(pageStoreJanitor, pageStoreJanitorPeriod);
|
2015-01-02 19:42:35 +01:00
|
|
|
|
2014-06-24 00:42:43 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-01-02 19:42:35 +01:00
|
|
|
})();
|
|
|
|
|
|
|
|
/******************************************************************************/
|