1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-07-08 04:49:12 +02:00

this adds popunder filtering support for Firefox-based browsers

This commit is contained in:
gorhill 2015-12-01 15:07:22 -05:00
parent 5f304b6751
commit 4fd71d4209
4 changed files with 187 additions and 221 deletions

View File

@ -157,7 +157,6 @@ var toChromiumTabId = function(tabId) {
vAPI.tabs.registerListeners = function() {
var onNavigationClient = this.onNavigation || noopFunc;
var onPopupClient = this.onPopup || noopFunc;
var onUpdatedClient = this.onUpdated || noopFunc;
// https://developer.chrome.com/extensions/webNavigation
@ -167,58 +166,6 @@ vAPI.tabs.registerListeners = function() {
// onDOMContentLoaded ->
// onCompleted
var popupCandidates = Object.create(null);
var PopupCandidate = function(details) {
this.targetTabId = details.tabId.toString();
this.openerTabId = details.sourceTabId.toString();
this.targetURL = details.url;
this.selfDestructionTimer = null;
};
PopupCandidate.prototype.selfDestruct = function() {
if ( this.selfDestructionTimer !== null ) {
clearTimeout(this.selfDestructionTimer);
}
delete popupCandidates[this.targetTabId];
};
PopupCandidate.prototype.launchSelfDestruction = function() {
if ( this.selfDestructionTimer !== null ) {
clearTimeout(this.selfDestructionTimer);
}
this.selfDestructionTimer = setTimeout(this.selfDestruct.bind(this), 10000);
};
var popupCandidateCreate = function(details) {
var popup = popupCandidates[details.tabId];
// This really should not happen...
if ( popup !== undefined ) {
return;
}
return (popupCandidates[details.tabId] = new PopupCandidate(details));
};
var popupCandidateTest = function(details) {
var popup = popupCandidates[details.tabId];
if ( popup === undefined ) {
return;
}
popup.targetURL = details.url;
if ( onPopupClient(popup) !== true ) {
return;
}
popup.selfDestruct();
return true;
};
var popupCandidateDestroy = function(details) {
var popup = popupCandidates[details.tabId];
if ( popup instanceof PopupCandidate ) {
popup.launchSelfDestruction();
}
};
// The chrome.webRequest.onBeforeRequest() won't be called for everything
// else than `http`/`https`. Thus, in such case, we will bind the tab as
// early as possible in order to increase the likelihood of a context
@ -233,22 +180,18 @@ vAPI.tabs.registerListeners = function() {
details.frameId = 0;
onNavigationClient(details);
}
popupCandidateCreate(details);
popupCandidateTest(details);
if ( typeof vAPI.tabs.onPopupCreated === 'function' ) {
vAPI.tabs.onPopupCreated(details.tabId.toString(), details.sourceTabId.toString());
}
};
var onBeforeNavigate = function(details) {
if ( details.frameId !== 0 ) {
return;
}
//console.debug('onBeforeNavigate: popup candidate tab id %d = "%s"', details.tabId, details.url);
popupCandidateTest(details);
};
var onUpdated = function(tabId, changeInfo, tab) {
if ( changeInfo.url && popupCandidateTest({ tabId: tabId, url: changeInfo.url }) ) {
return;
}
onUpdatedClient(tabId, changeInfo, tab);
};
@ -257,11 +200,6 @@ vAPI.tabs.registerListeners = function() {
return;
}
onNavigationClient(details);
//console.debug('onCommitted: popup candidate tab id %d = "%s"', details.tabId, details.url);
if ( popupCandidateTest(details) === true ) {
return;
}
popupCandidateDestroy(details);
};
chrome.webNavigation.onCreatedNavigationTarget.addListener(onCreatedNavigationTarget);

View File

@ -90,6 +90,7 @@ var contentObserver = {
SUB_FRAME: Ci.nsIContentPolicy.TYPE_SUBDOCUMENT,
contentBaseURI: 'chrome://' + hostName + '/content/js/',
cpMessageName: hostName + ':shouldLoad',
popupMessageName: hostName + ':shouldLoadPopup',
ignoredPopups: new WeakMap(),
uniqueSandboxId: 1,
@ -153,6 +154,38 @@ var contentObserver = {
.outerWindowID;
},
handlePopup: function(location, context) {
let openeeContext = context.contentWindow || context;
if (
typeof openeeContext.opener !== 'object' ||
openeeContext.opener === null ||
openeeContext.opener === context ||
this.ignoredPopups.has(openeeContext)
) {
return;
}
// https://github.com/gorhill/uBlock/issues/452
// Use location of top window, not that of a frame, as this
// would cause tab id lookup (necessary for popup blocking) to
// always fail.
let openerURL = openeeContext.opener.top &&
openeeContext.opener.top.location.href;
if ( openerURL === null ) {
return;
}
let messageManager = getMessageManager(openeeContext);
if ( messageManager === null ) {
return;
}
if ( typeof messageManager.sendRpcMessage === 'function' ) {
// https://bugzil.la/1092216
messageManager.sendRpcMessage(this.popupMessageName, openerURL);
} else {
// Compatibility for older versions
messageManager.sendSyncMessage(this.popupMessageName, openerURL);
}
},
// https://bugzil.la/612921
shouldLoad: function(type, location, origin, context) {
// For whatever reason, sometimes the global scope is completely
@ -170,26 +203,16 @@ var contentObserver = {
return this.ACCEPT;
}
if ( type === this.MAIN_FRAME ) {
this.handlePopup(location, context);
}
if ( !location.schemeIs('http') && !location.schemeIs('https') ) {
return this.ACCEPT;
}
let openerURL = null;
if ( type === this.MAIN_FRAME ) {
context = context.contentWindow || context;
if (
typeof context.opener === 'object' &&
context.opener !== null &&
context.opener !== context &&
this.ignoredPopups.has(context) === false
) {
// https://github.com/gorhill/uBlock/issues/452
// Use location of top window, not that of a frame, as this
// would cause tab id lookup (necessary for popup blocking) to
// always fail.
openerURL = context.opener.top && context.opener.top.location.href;
}
} else if ( type === this.SUB_FRAME ) {
context = context.contentWindow;
} else {
@ -219,7 +242,6 @@ var contentObserver = {
let details = {
frameId: isTopLevel ? 0 : this.getFrameId(context),
openerURL: openerURL,
parentFrameId: parentFrameId,
rawtype: type,
tabId: '',

View File

@ -945,17 +945,24 @@ vAPI.tabs._remove = (function() {
/******************************************************************************/
vAPI.tabs.remove = function(tabId) {
var browser = tabWatcher.browserFromTabId(tabId);
if ( !browser ) {
return;
}
var tab = tabWatcher.tabFromBrowser(browser);
if ( !tab ) {
return;
}
this._remove(tab, getTabBrowser(getOwnerWindow(browser)));
};
vAPI.tabs.remove = (function() {
var remove = function(tabId) {
var browser = tabWatcher.browserFromTabId(tabId);
if ( !browser ) {
return;
}
var tab = tabWatcher.tabFromBrowser(browser);
if ( !tab ) {
return;
}
this._remove(tab, getTabBrowser(getOwnerWindow(browser)));
};
// Do this asynchronously
return function(tabId) {
vAPI.setTimeout(remove.bind(this, tabId), 10);
};
})();
/******************************************************************************/
@ -1170,17 +1177,6 @@ var tabWatcher = (function() {
onClose({ target: target });
};
// https://developer.mozilla.org/en-US/docs/Web/Events/TabOpen
//var onOpen = function({target}) {
// var tabId = tabIdFromTarget(target);
// var browser = browserFromTabId(tabId);
// vAPI.tabs.onNavigation({
// frameId: 0,
// tabId: tabId,
// url: browser.currentURI.asciiSpec,
// });
//};
// https://developer.mozilla.org/en-US/docs/Web/Events/TabShow
var onShow = function({target}) {
tabIdFromTarget(target);
@ -1266,7 +1262,6 @@ var tabWatcher = (function() {
// not set when a tab is opened as a result of session restore -- it is
// set *after* the event is fired in such case.
if ( tabContainer ) {
//tabContainer.addEventListener('TabOpen', onOpen);
tabContainer.addEventListener('TabShow', onShow);
tabContainer.addEventListener('TabClose', onClose);
// when new window is opened TabSelect doesn't run on the selected tab?
@ -1293,7 +1288,6 @@ var tabWatcher = (function() {
tabContainer = tabBrowser.tabContainer;
}
if ( tabContainer ) {
//tabContainer.removeEventListener('TabOpen', onOpen);
tabContainer.removeEventListener('TabShow', onShow);
tabContainer.removeEventListener('TabClose', onClose);
tabContainer.removeEventListener('TabSelect', onSelect);
@ -1847,7 +1841,6 @@ var httpObserver = {
this.frameId = 0;
this.parentFrameId = 0;
this.rawtype = 0;
this.sourceTabId = null;
this.tabId = 0;
this._key = ''; // key is url, from URI.spec
},
@ -1937,9 +1930,10 @@ var httpObserver = {
}
aWindow = loadContext.associatedWindow;
} catch (ex) {
//console.error(ex);
//console.error(ex.toString());
}
}
var gBrowser;
try {
if ( !aWindow && channel.loadGroup && channel.loadGroup.notificationCallbacks ) {
aWindow = channel
@ -1949,21 +1943,23 @@ var httpObserver = {
.associatedWindow;
}
if ( aWindow ) {
return tabWatcher.tabIdFromTarget(
aWindow
gBrowser = aWindow
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.gBrowser
.getBrowserForContentWindow(aWindow)
);
.gBrowser;
}
} catch (ex) {
//console.error(ex);
//console.error(ex.toString());
}
return vAPI.noTabId;
if ( !gBrowser || !gBrowser._getTabForContentWindow ) {
return vAPI.noTabId;
}
// Using `_getTabForContentWindow` ensure older versions of Firefox
// work well.
return tabWatcher.tabIdFromTarget(gBrowser._getTabForContentWindow(aWindow));
},
// https://github.com/gorhill/uBlock/issues/959
@ -1981,24 +1977,6 @@ var httpObserver = {
};
},
handlePopup: function(URI, tabId, sourceTabId) {
if ( !sourceTabId ) {
return false;
}
if ( !URI.schemeIs('http') && !URI.schemeIs('https') ) {
return false;
}
var result = vAPI.tabs.onPopup({
targetTabId: tabId,
openerTabId: sourceTabId,
targetURL: URI.asciiSpec
});
return result === true;
},
handleRequest: function(channel, URI, details) {
var type = this.typeMap[details.rawtype] || 'other';
if ( this.onBeforeRequestTypes && this.onBeforeRequestTypes.has(type) === false ) {
@ -2048,7 +2026,7 @@ var httpObserver = {
},
handleResponseHeaders: function(channel, URI, channelData) {
var type = this.typeMap[channelData[4]] || 'other';
var type = this.typeMap[channelData[3]] || 'other';
if ( this.onHeadersReceivedTypes && this.onHeadersReceivedTypes.has(type) === false ) {
return;
}
@ -2071,8 +2049,8 @@ var httpObserver = {
hostname: hostname,
parentFrameId: channelData[1],
responseHeaders: responseHeaders,
tabId: channelData[3],
type: this.typeMap[channelData[4]] || 'other',
tabId: channelData[2],
type: this.typeMap[channelData[3]] || 'other',
url: URI.asciiSpec
});
@ -2137,6 +2115,19 @@ var httpObserver = {
}
}
// IMPORTANT:
// If this is a main frame, ensure that the proper tab id is being
// used: it can happen that the wrong tab id was looked up at
// `shouldLoadListener` time. Without this, the popup blocker may
// not work properly, and also a tab opened from a link may end up
// being wrongly reported as an embedded element.
if ( pendingRequest !== null && pendingRequest.rawtype === 6 ) {
var tabId = this.tabIdFromChannel(channel);
if ( tabId !== vAPI.noTabId ) {
pendingRequest.tabId = tabId;
}
}
// Behind-the-scene request... Really?
if ( pendingRequest === null ) {
pendingRequest = this.synthesizePendingRequest(channel, rawtype);
@ -2171,7 +2162,6 @@ var httpObserver = {
channel.setProperty(this.REQDATAKEY, [
pendingRequest.frameId,
pendingRequest.parentFrameId,
pendingRequest.sourceTabId,
pendingRequest.tabId,
pendingRequest.rawtype
]);
@ -2196,16 +2186,11 @@ var httpObserver = {
var channelData = oldChannel.getProperty(this.REQDATAKEY);
if ( this.handlePopup(URI, channelData[3], channelData[2]) ) {
result = this.ABORT;
return;
}
var details = {
frameId: channelData[0],
parentFrameId: channelData[1],
tabId: channelData[3],
rawtype: channelData[4]
tabId: channelData[2],
rawtype: channelData[3]
};
if ( this.handleRequest(newChannel, URI, details) ) {
@ -2250,9 +2235,15 @@ vAPI.net.registerListeners = function() {
null;
}
var shouldBlockPopup = function(details) {
var sourceTabId = null;
var uri;
var shouldLoadPopupListenerMessageName = location.host + ':shouldLoadPopup';
var shouldLoadPopupListener = function(e) {
if ( typeof vAPI.tabs.onPopupCreated !== 'function' ) {
return;
}
var openerURL = e.data;
var popupTabId = tabWatcher.tabIdFromTarget(e.target);
var uri, openerTabId;
for ( var browser of tabWatcher.browsers() ) {
uri = browser.currentURI;
@ -2265,25 +2256,23 @@ vAPI.net.registerListeners = function() {
// believe this may have to do with those very temporary
// browser objects created when opening a new tab, i.e. related
// to https://github.com/gorhill/uBlock/issues/212
if ( !uri || uri.spec !== details.openerURL ) {
if ( !uri || uri.spec !== openerURL ) {
continue;
}
sourceTabId = tabWatcher.tabIdFromTarget(browser);
if ( sourceTabId === details.tabId ) {
sourceTabId = null;
continue;
openerTabId = tabWatcher.tabIdFromTarget(browser);
if ( openerTabId !== popupTabId ) {
vAPI.tabs.onPopupCreated(popupTabId, openerTabId);
break;
}
uri = Services.io.newURI(details.url, null, null);
httpObserver.handlePopup(uri, details.tabId, sourceTabId);
break;
}
return sourceTabId;
};
vAPI.messaging.globalMessageManager.addMessageListener(
shouldLoadPopupListenerMessageName,
shouldLoadPopupListener
);
var shouldLoadListenerMessageName = location.host + ':shouldLoad';
var shouldLoadListener = function(e) {
// Non blocking: it is assumed that the http observer is fired after
@ -2291,18 +2280,6 @@ vAPI.net.registerListeners = function() {
// a request would end up being categorized as a behind-the-scene
// requests.
var details = e.data;
var sourceTabId = null;
details.tabId = tabWatcher.tabIdFromTarget(e.target);
// Popup candidate: this code path is taken only for when a new top
// document loads, i.e. only once per document load. TODO: evaluate for
// popup filtering in an asynchrous manner -- it's not really required
// to evaluate on the spot. Still, there is currently no harm given
// this code path is typically taken only once per page load.
if ( details.openerURL ) {
sourceTabId = shouldBlockPopup(details);
}
// We are being called synchronously from the content process, so we
// must return ASAP. The code below merely record the details of the
@ -2311,8 +2288,7 @@ vAPI.net.registerListeners = function() {
pendingReq.frameId = details.frameId;
pendingReq.parentFrameId = details.parentFrameId;
pendingReq.rawtype = details.rawtype;
pendingReq.sourceTabId = sourceTabId;
pendingReq.tabId = details.tabId;
pendingReq.tabId = tabWatcher.tabIdFromTarget(e.target);
};
vAPI.messaging.globalMessageManager.addMessageListener(
@ -2378,6 +2354,11 @@ vAPI.net.registerListeners = function() {
httpObserver.register();
cleanupTasks.push(function() {
vAPI.messaging.globalMessageManager.removeMessageListener(
shouldLoadPopupListenerMessageName,
shouldLoadPopupListener
);
vAPI.messaging.globalMessageManager.removeMessageListener(
shouldLoadListenerMessageName,
shouldLoadListener
@ -2655,7 +2636,7 @@ vAPI.toolbarButton = {
// palette might take a little longer to appear on some platforms,
// give it a small delay and try again.
if ( toolbox.palette === null ) {
if ( !toolbox.palette ) {
vAPI.setTimeout(onReadyStateComplete.bind(null, window, callback, tryCount), 200);
return;
}
@ -2707,7 +2688,7 @@ vAPI.toolbarButton = {
var navbar = document.getElementById('nav-bar');
var toolbarButton = createToolbarButton(window);
if ( palette !== null && palette.querySelector('#' + tbb.id) === null ) {
if ( palette && palette.querySelector('#' + tbb.id) === null ) {
palette.appendChild(toolbarButton);
}

View File

@ -136,6 +136,53 @@ housekeep itself.
var mostRecentRootDocURL = '';
var mostRecentRootDocURLTimestamp = 0;
var popupCandidates = Object.create(null);
var PopupCandidate = function(targetTabId, openerTabId) {
this.targetTabId = targetTabId;
this.openerTabId = openerTabId;
this.selfDestructionTimer = null;
this.launchSelfDestruction();
};
PopupCandidate.prototype.destroy = function() {
if ( this.selfDestructionTimer !== null ) {
clearTimeout(this.selfDestructionTimer);
}
delete popupCandidates[this.targetTabId];
};
PopupCandidate.prototype.launchSelfDestruction = function() {
if ( this.selfDestructionTimer !== null ) {
clearTimeout(this.selfDestructionTimer);
}
this.selfDestructionTimer = vAPI.setTimeout(this.destroy.bind(this), 10000);
};
var popupCandidateTest = function(targetTabId) {
var candidates = popupCandidates, entry;
for ( var tabId in candidates ) {
entry = candidates[tabId];
if ( targetTabId !== tabId && targetTabId !== entry.openerTabId ) {
continue;
}
if ( vAPI.tabs.onPopupUpdated(tabId, entry.openerTabId) === true ) {
entry.destroy();
} else {
entry.launchSelfDestruction();
}
}
};
vAPI.tabs.onPopupCreated = function(targetTabId, openerTabId) {
var popup = popupCandidates[targetTabId];
if ( popup !== undefined ) {
return;
}
popupCandidates[targetTabId] = new PopupCandidate(targetTabId, openerTabId);
popupCandidateTest(targetTabId);
};
var gcPeriod = 10 * 60 * 1000;
// A pushed entry is removed from the stack unless it is committed with
@ -253,34 +300,11 @@ housekeep itself.
}
this.stack.push(new StackEntry(url));
this.update();
popupCandidateTest(this.tabId);
if ( this.commitTimer !== null ) {
clearTimeout(this.commitTimer);
}
this.commitTimer = vAPI.setTimeout(this.onCommit.bind(this), 1000);
};
// 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.
var i = this.stack.length;
if ( i === 1 ) {
return;
}
while ( i-- ) {
if ( this.stack[i].url !== url ) {
continue;
}
this.stack.splice(i, 1);
if ( i === this.stack.length ) {
this.update();
}
return;
}
this.commitTimer = vAPI.setTimeout(this.onCommit.bind(this), 500);
};
// This tells that the url is definitely the one to be associated with the
@ -368,13 +392,6 @@ housekeep itself.
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;
};
@ -407,7 +424,6 @@ housekeep itself.
return {
push: push,
unpush: unpush,
commit: commit,
lookup: lookup,
exists: exists,
@ -425,6 +441,7 @@ vAPI.tabs.onNavigation = function(details) {
if ( details.frameId !== 0 ) {
return;
}
var tabContext = µb.tabContextManager.commit(details.tabId, details.url);
var pageStore = µb.bindTabToPageStats(details.tabId, 'afterNavigate');
@ -453,6 +470,7 @@ vAPI.tabs.onUpdated = function(tabId, changeInfo, tab) {
if ( !changeInfo.url ) {
return;
}
µb.tabContextManager.commit(tabId, changeInfo.url);
µb.bindTabToPageStats(tabId, 'tabUpdated');
};
@ -490,14 +508,14 @@ vAPI.tabs.onClosed = function(tabId) {
// c: close opener
// d: close target
vAPI.tabs.onPopup = (function() {
vAPI.tabs.onPopupUpdated = (function() {
//console.debug('vAPI.tabs.onPopup: details = %o', details);
// The same context object will be reused everytime. This also allows to
// remember whether a popup or popunder was matched.
var context = {};
var popupMatch = function(openerURL, targetURL, clickedURL) {
var popupMatch = function(openerURL, targetURL, clickedURL, popunder) {
var openerHostname = µb.URI.hostnameFromURI(openerURL);
var openerDomain = µb.URI.domainFromHostname(openerHostname);
@ -514,6 +532,7 @@ vAPI.tabs.onPopup = (function() {
if ( openerHostname !== '' ) {
// Check user switch first
if (
popunder !== true &&
targetURL !== clickedURL &&
µb.hnSwitches.evaluateZ('no-popups', openerHostname)
) {
@ -554,14 +573,25 @@ vAPI.tabs.onPopup = (function() {
return '';
};
return function(details) {
var tabContext = µb.tabContextManager.lookup(details.openerTabId);
return function(targetTabId, openerTabId) {
// Opener details.
var tabContext = µb.tabContextManager.lookup(openerTabId);
var openerURL = '';
if ( tabContext.tabId === details.openerTabId ) {
openerURL = tabContext.normalURL;
if ( tabContext.tabId === openerTabId ) {
openerURL = tabContext.rawURL;
if ( openerURL === '' ) {
return;
}
}
if ( openerURL === '' ) {
return;
// Popup details.
tabContext = µb.tabContextManager.lookup(targetTabId);
var targetURL = '';
if ( tabContext.tabId === targetTabId ) {
targetURL = tabContext.rawURL;
if ( targetURL === '' ) {
return;
}
}
// https://github.com/gorhill/uBlock/issues/341
@ -570,8 +600,6 @@ vAPI.tabs.onPopup = (function() {
return;
}
var targetURL = details.targetURL;
// If the page URL is that of our "blocked page" URL, extract the URL of
// the page which was blocked.
if ( targetURL.lastIndexOf(vAPI.getURL('document-blocked.html'), 0) === 0 ) {
@ -582,15 +610,12 @@ vAPI.tabs.onPopup = (function() {
}
// Popup test.
var openerTabId = details.openerTabId;
var targetTabId = details.targetTabId;
var result = popupMatch(openerURL, targetURL, µb.mouseURL);
// Popunder test.
if ( result === '' ) {
openerTabId = details.targetTabId;
targetTabId = details.openerTabId;
result = popupMatch(targetURL, openerURL, µb.mouseURL);
var tmp = openerTabId; openerTabId = targetTabId; targetTabId = tmp;
result = popupMatch(targetURL, openerURL, µb.mouseURL, true);
}
// Log only for when there was a hit against an actual filter (allow or block).