this should fix #357, #395

pull/2/head
gorhill 9 years ago
parent 72c9429d74
commit 6eaa85eee1

File diff suppressed because it is too large Load Diff

@ -129,11 +129,7 @@ var onTabsReady = function(tabs) {
// console.debug('start.js > binding %d tabs', i);
while ( i-- ) {
tab = tabs[i];
µm.tabContextManager.commit(tab.id, tab.url);
// https://github.com/gorhill/uMatrix/issues/56
// We must unbind first to flush out potentially bad domain names.
µm.unbindTabFromPageStats(tab.id);
µm.bindTabToPageStats(tab.id);
µm.tabContextManager.push(tab.id, tab.url, 'newURL');
}
onAllDone();

@ -150,7 +150,15 @@ housekeep itself.
var mostRecentRootDocURL = '';
var mostRecentRootDocURLTimestamp = 0;
var gcPeriod = 10 * 60 * 1000;
var gcPeriod = 31 * 60 * 1000; // every 31 minutes
// 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();
};
var TabContext = function(tabId) {
this.tabId = tabId;
@ -161,9 +169,8 @@ housekeep itself.
this.rootHostname =
this.rootDomain = '';
this.secure = false;
this.timer = null;
this.onTabCallback = null;
this.onTimerCallback = null;
this.commitTimer = null;
this.gcTimer = null;
tabContexts[tabId] = this;
};
@ -172,119 +179,122 @@ housekeep itself.
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
return;
}
if ( this.timer !== null ) {
clearTimeout(this.timer);
this.timer = null;
if ( this.gcTimer !== null ) {
clearTimeout(this.gcTimer);
this.gcTimer = null;
}
delete tabContexts[this.tabId];
};
TabContext.prototype.onTab = function(tab) {
if ( tab ) {
this.timer = vAPI.setTimeout(this.onTimerCallback, gcPeriod);
this.gcTimer = vAPI.setTimeout(this.onGC.bind(this), gcPeriod);
} else {
this.destroy();
}
};
TabContext.prototype.onTimer = function() {
this.timer = null;
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() {
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
return;
}
vAPI.tabs.get(this.tabId, this.onTabCallback);
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;
}
}
// 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;
}
i += 1;
if ( i < this.stack.length ) {
this.stack.length = i;
this.update();
µm.bindTabToPageStats(this.tabId, 'newURL');
}
};
// 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.
// want to flush 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 = vAPI.setTimeout(this.onTimerCallback, gcPeriod);
this.gcTimer = vAPI.setTimeout(this.onGC.bind(this), gcPeriod);
};
// Update just force all properties to be updated to match the most current
// Update just force all properties to be updated to match the most recent
// root URL.
TabContext.prototype.update = function() {
if ( this.stack.length === 0 ) {
this.rawURL =
this.normalURL =
this.scheme =
this.rootHostname =
this.rootDomain = '';
} else {
this.rawURL = this.stack[this.stack.length - 1];
this.normalURL = µm.normalizePageURL(this.tabId, this.rawURL);
this.scheme = µm.URI.schemeFromURI(this.rawURL);
this.rootHostname = µm.URI.hostnameFromURI(this.normalURL);
this.rootDomain = µm.URI.domainFromHostname(this.rootHostname) || this.rootHostname;
this.rawURL = this.normalURL = this.scheme =
this.rootHostname = this.rootDomain = '';
this.secure = false;
return;
}
this.rawURL = this.stack[this.stack.length - 1].url;
this.normalURL = µm.normalizePageURL(this.tabId, this.rawURL);
this.scheme = µm.URI.schemeFromURI(this.rawURL);
this.rootHostname = µm.URI.hostnameFromURI(this.normalURL);
this.rootDomain = µm.URI.domainFromHostname(this.rootHostname) || this.rootHostname;
this.secure = µm.URI.isSecureScheme(this.scheme);
};
// Called whenever a candidate root URL is spotted for the tab.
TabContext.prototype.push = function(url) {
TabContext.prototype.push = function(url, context) {
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
return;
}
var committed = context !== undefined;
var count = this.stack.length;
if ( count !== 0 && this.stack[count - 1] === url ) {
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();
var topEntry = this.stack[count - 1];
if ( topEntry && topEntry.url === url ) {
if ( committed ) {
topEntry.committed = true;
}
return;
}
if ( pos !== this.stack.length ) {
return;
if ( this.commitTimer !== null ) {
clearTimeout(this.commitTimer);
}
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;
if ( committed ) {
this.stack = [new StackEntry(url, true)];
} else {
this.stack.push(new StackEntry(url));
this.commitTimer = vAPI.setTimeout(this.onCommit.bind(this), 1000);
}
this.stack = [url];
this.update();
µm.bindTabToPageStats(this.tabId, context);
};
// These are to be used for the API of the tab context manager.
var push = function(tabId, url) {
var push = function(tabId, url, context) {
var entry = tabContexts[tabId];
if ( entry === undefined ) {
entry = new TabContext(tabId);
entry.autodestroy();
}
entry.push(url);
entry.push(url, context);
mostRecentRootDocURL = url;
mostRecentRootDocURLTimestamp = Date.now();
return entry;
@ -326,23 +336,6 @@ housekeep itself.
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 lookup = function(tabId) {
return tabContexts[tabId] || null;
};
@ -350,88 +343,48 @@ housekeep itself.
// Behind-the-scene tab context
(function() {
var entry = new TabContext(vAPI.noTabId);
entry.stack.push('');
entry.stack.push(new StackEntry('', true));
entry.rawURL = '';
entry.normalURL = µm.normalizePageURL(entry.tabId);
entry.rootHostname = µm.URI.hostnameFromURI(entry.normalURL);
entry.rootDomain = µm.URI.domainFromHostname(entry.rootHostname) || 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 = '';
vAPI.tabs.onNavigation = function(details) {
var tabId = details.tabId;
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
return;
}
push(tabId, details.url, 'newURL');
};
vAPI.tabs.onUpdated = function(tabId, changeInfo, tab) {
if ( typeof tab.url !== 'string' || tab.url === '' ) {
return;
}
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
return;
}
if ( changeInfo.url ) {
push(tabId, changeInfo.url, 'updateURL');
}
};
var createContext = function(tabId) {
return new Context(tabId);
vAPI.tabs.onClosed = function(tabId) {
µm.unbindTabFromPageStats(tabId);
var entry = tabContexts[tabId];
if ( entry instanceof TabContext ) {
entry.destroy();
}
};
return {
push: push,
unpush: unpush,
commit: commit,
lookup: lookup,
mustLookup: mustLookup,
createContext: createContext
mustLookup: mustLookup
};
})();
/******************************************************************************/
/******************************************************************************/
// When the DOM content of root frame is loaded, this means the tab
// content has changed.
vAPI.tabs.onNavigation = function(details) {
// This actually can happen
var tabId = details.tabId;
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
return;
}
//console.log('vAPI.tabs.onNavigation: %s %s %o', details.url, details.transitionType, details.transitionQualifiers);
µm.tabContextManager.commit(tabId, details.url);
µm.bindTabToPageStats(tabId, 'commit');
};
/******************************************************************************/
// 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;
}
// This actually can happen
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
return;
}
if ( changeInfo.url ) {
µm.tabContextManager.commit(tabId, changeInfo.url);
µm.bindTabToPageStats(tabId, 'updated');
}
};
/******************************************************************************/
vAPI.tabs.onClosed = function(tabId) {
µm.unbindTabFromPageStats(tabId);
};
/******************************************************************************/
vAPI.tabs.registerListeners();
/******************************************************************************/
@ -466,19 +419,13 @@ vAPI.tabs.registerListeners();
return pageStore;
}
// Do not change anything if it's weak binding -- typically when
// binding from network request handler.
if ( context === 'weak' ) {
return pageStore;
}
// https://github.com/gorhill/uMatrix/issues/37
// Just rebind whenever possible: the URL changed, but the document
// maybe is the same.
// Example: Google Maps, Github
// https://github.com/gorhill/uMatrix/issues/72
// Need to double-check that the new scope is same as old scope
if ( context === 'updated' && pageStore.pageHostname === tabContext.rootHostname ) {
if ( context === 'updateURL' && pageStore.pageHostname === tabContext.rootHostname ) {
pageStore.rawURL = tabContext.rawURL;
pageStore.normalURL = normalURL;
this.updateTitle(tabId);
@ -516,6 +463,7 @@ vAPI.tabs.registerListeners();
if ( pageStore === null ) {
return;
}
delete this.pageStores[tabId];
this.pageStoresToken = Date.now();

@ -43,11 +43,11 @@ var onBeforeRootFrameRequestHandler = function(details) {
var tabContext = µm.tabContextManager.mustLookup(tabId);
var rootHostname = tabContext.rootHostname;
var pageStore = µm.bindTabToPageStats(tabId);
// Disallow request as per matrix?
var block = µm.mustBlock(rootHostname, details.hostname, 'doc');
var pageStore = µm.pageStoreFromTabId(tabId);
pageStore.recordRequest('doc', requestURL, block);
µm.logger.writeOne(tabId, 'net', rootHostname, requestURL, 'doc', block);
@ -307,11 +307,7 @@ var onHeadersReceived = function(details) {
// https://github.com/gorhill/uMatrix/issues/145
// Check if the main_frame is a download
if ( requestType === 'doc' ) {
if ( headerValue(details.responseHeaders, 'content-type').lastIndexOf('application/x-', 0) === 0 ) {
µm.tabContextManager.unpush(tabId, requestURL);
} else {
µm.tabContextManager.push(tabId, requestURL);
}
µm.tabContextManager.push(tabId, requestURL);
}
var tabContext = µm.tabContextManager.lookup(tabId);

Loading…
Cancel
Save