From f204d24bf4a7a5f70419bd8b0c7a87595f0a4181 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 23 Sep 2019 08:25:23 -0400 Subject: [PATCH] Match static popup filter against local context Related feedback: - https://www.reddit.com/r/uBlockOrigin/comments/d6zbqv/ For static filter `popup` filter purpose, the URL of the embedded frame from which the popup was launched will be used in the matching algorithm. --- platform/chromium/vapi-background.js | 9 +-- platform/chromium/webext.js | 16 +++- src/js/tab.js | 109 +++++++++++++++++---------- 3 files changed, 87 insertions(+), 47 deletions(-) diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index 0d0dc96110d6f..c23720265cac3 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -260,10 +260,7 @@ vAPI.Tabs = class { details.url = this.sanitizeURL(details.url); this.onNavigation(details); } - this.onCreated( - details.tabId, - details.sourceTabId - ); + this.onCreated(details); }); browser.webNavigation.onCommitted.addListener(details => { @@ -592,7 +589,7 @@ vAPI.Tabs = class { onClosed(/* tabId, details */) { } - onCreated(/* openedTabId, openerTabId */) { + onCreated(/* details */) { } onNavigation(/* details */) { @@ -655,7 +652,7 @@ if ( browser.windows instanceof Object ) { // Ensure ImageData for toolbar icon is valid before use. vAPI.setIcon = (( ) => { - const browserAction = browser.browserAction; + const browserAction = webext.browserAction; const titleTemplate = browser.runtime.getManifest().browser_action.default_title + ' ({badge})'; diff --git a/platform/chromium/webext.js b/platform/chromium/webext.js index 5f61dcf1d0ac1..86708eb10fbae 100644 --- a/platform/chromium/webext.js +++ b/platform/chromium/webext.js @@ -32,7 +32,9 @@ const promisifyNoFail = function(thisArg, fnName, outFn = r => r) { return function() { return new Promise(resolve => { fn.call(thisArg, ...arguments, function() { - void chrome.runtime.lastError; + if ( chrome.runtime.lastError instanceof Object ) { + void chrome.runtime.lastError.message; + } resolve(outFn(...arguments)); }); }); @@ -55,6 +57,14 @@ const promisify = function(thisArg, fnName) { }; const webext = { + // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/browserAction + browserAction: { + onClicked: chrome.browserAction.onClicked, + setBadgeBackgroundColor: promisifyNoFail(chrome.browserAction, 'setBadgeBackgroundColor'), + setBadgeText: promisifyNoFail(chrome.browserAction, 'setBadgeText'), + setIcon: promisifyNoFail(chrome.browserAction, 'setIcon'), + setTitle: promisifyNoFail(chrome.browserAction, 'setTitle'), + }, // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/menus menus: { create: function() { @@ -89,6 +99,10 @@ const webext = { remove: promisifyNoFail(chrome.tabs, 'remove'), update: promisifyNoFail(chrome.tabs, 'update', tab => tab instanceof Object ? tab : null), }, + // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webNavigation + webNavigation: { + getFrame: promisify(chrome.webNavigation, 'getFrame'), + }, // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/windows windows: { get: promisifyNoFail(chrome.windows, 'get', win => win instanceof Object ? win : null), diff --git a/src/js/tab.js b/src/js/tab.js index a8616563c3255..1e30bdfbd0de2 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -103,12 +103,13 @@ const popupMatch = function( fctxt, - openerURL, + rootOpenerURL, + localOpenerURL, targetURL, - popupType + popupType = 'popup' ) { - fctxt.setTabOriginFromURL(openerURL) - .setDocOriginFromURL(openerURL) + fctxt.setTabOriginFromURL(rootOpenerURL) + .setDocOriginFromURL(localOpenerURL || rootOpenerURL) .setURL(targetURL) .setType('popup'); let result; @@ -231,10 +232,17 @@ const popunderMatch = function( fctxt, - openerURL, + rootOpenerURL, + localOpenerURL, targetURL ) { - let result = popupMatch(fctxt, targetURL, openerURL, 'popunder'); + let result = popupMatch( + fctxt, + targetURL, + undefined, + rootOpenerURL, + 'popunder' + ); if ( result === 1 ) { return result; } // https://github.com/gorhill/uBlock/issues/1010#issuecomment-186824878 @@ -243,7 +251,7 @@ // a broad one, we will consider the opener tab to be a popunder tab. // For now, a "broad" filter is one which does not touch any part of // the hostname part of the opener URL. - let popunderURL = openerURL, + let popunderURL = rootOpenerURL, popunderHostname = µb.URI.hostnameFromURI(popunderURL); if ( popunderHostname === '' ) { return 0; } @@ -251,7 +259,7 @@ fctxt, popunderURL, popunderHostname, - popupMatch(fctxt, targetURL, popunderURL, 'popup') + popupMatch(fctxt, targetURL, undefined, popunderURL) ); if ( result !== 0 ) { return result; } @@ -264,7 +272,7 @@ fctxt, popunderURL, popunderHostname, - popupMatch(fctxt, targetURL, popunderURL, 'popup') + popupMatch(fctxt, targetURL, undefined, popunderURL) ); }; @@ -273,8 +281,11 @@ const openerTabId = openerDetails.tabId; let tabContext = µb.tabContextManager.lookup(openerTabId); if ( tabContext === null ) { return; } - const openerURL = tabContext.rawURL; - if ( openerURL === '' ) { return; } + const rootOpenerURL = tabContext.rawURL; + if ( rootOpenerURL === '' ) { return; } + const localOpenerURL = openerDetails.frameId !== 0 + ? openerDetails.frameURL + : undefined; // Popup details. tabContext = µb.tabContextManager.lookup(targetTabId); @@ -283,21 +294,20 @@ if ( targetURL === '' ) { return; } // https://github.com/gorhill/uBlock/issues/341 - // Allow popups if uBlock is turned off in opener's context. - if ( µb.getNetFilteringSwitch(openerURL) === false ) { return; } + // Allow popups if uBlock is turned off in opener's context. + if ( µb.getNetFilteringSwitch(rootOpenerURL) === false ) { return; } // https://github.com/gorhill/uBlock/issues/1538 if ( - µb.getNetFilteringSwitch(µb.normalizePageURL( - openerTabId, - openerURL) + µb.getNetFilteringSwitch( + µb.normalizePageURL(openerTabId, rootOpenerURL) ) === false ) { return; } - // If the page URL is that of our "blocked page" URL, extract the URL of - // the page which was blocked. + // If the page URL is that of our "blocked page" URL, extract the URL + // of the page which was blocked. if ( targetURL.startsWith(vAPI.getURL('document-blocked.html')) ) { const matches = /details=([^&]+)/.exec(targetURL); if ( matches !== null ) { @@ -314,12 +324,12 @@ // https://github.com/gorhill/uBlock/issues/2919 // - If the target tab matches a clicked link, assume it's legit. if ( areDifferentURLs(targetURL, openerDetails.trustedURL) ) { - result = popupMatch(fctxt, openerURL, targetURL, 'popup'); + result = popupMatch(fctxt, rootOpenerURL, localOpenerURL, targetURL); } // Popunder test. if ( result === 0 && openerDetails.popunder ) { - result = popunderMatch(fctxt, openerURL, targetURL); + result = popunderMatch(fctxt, rootOpenerURL, localOpenerURL, targetURL); if ( result === 1 ) { popupType = 'popunder'; } @@ -332,10 +342,10 @@ if ( popupType === 'popup' ) { fctxt.setURL(targetURL) .setTabId(openerTabId) - .setTabOriginFromURL(openerURL) - .setDocOriginFromURL(openerURL); + .setTabOriginFromURL(rootOpenerURL) + .setDocOriginFromURL(localOpenerURL); } else { - fctxt.setURL(openerURL) + fctxt.setURL(rootOpenerURL) .setTabId(targetTabId) .setTabOriginFromURL(targetURL) .setDocOriginFromURL(targetURL); @@ -446,12 +456,15 @@ housekeep itself. const popupCandidates = new Map(); const PopupCandidate = class { - constructor(targetTabId, openerTabId) { - this.targetTabId = targetTabId; + constructor(createDetails, openerDetails) { + this.targetTabId = createDetails.tabId; this.opener = { - tabId: openerTabId, + tabId: createDetails.sourceTabId, + tabURL: openerDetails[0].url, + frameId: createDetails.sourceFrameId, + frameURL: openerDetails[1].url, popunder: false, - trustedURL: openerTabId === µb.maybeGoodPopup.tabId + trustedURL: createDetails.tabId === µb.maybeGoodPopup.tabId ? µb.maybeGoodPopup.url : '' }; @@ -477,10 +490,8 @@ housekeep itself. } }; - const popupCandidateTest = function(targetTabId) { - for ( const entry of popupCandidates ) { - const tabId = entry[0]; - const candidate = entry[1]; + const popupCandidateTest = async function(targetTabId) { + for ( const [ tabId, candidate ] of popupCandidates ) { if ( targetTabId !== tabId && targetTabId !== candidate.opener.tabId @@ -493,7 +504,8 @@ housekeep itself. if ( targetTabId === candidate.opener.tabId ) { candidate.opener.popunder = true; } - if ( µb.onPopupUpdated(tabId, candidate.opener) === true ) { + const result = await µb.onPopupUpdated(tabId, candidate.opener); + if ( result === true ) { candidate.destroy(); } else { candidate.launchSelfDestruction(); @@ -501,15 +513,32 @@ housekeep itself. } }; - const onTabCreated = function(targetTabId, openerTabId) { - const popup = popupCandidates.get(targetTabId); + const onTabCreated = async function(createDetails) { + const { sourceTabId, sourceFrameId, tabId } = createDetails; + const popup = popupCandidates.get(tabId); if ( popup === undefined ) { + let openerDetails; + try { + openerDetails = await Promise.all([ + webext.webNavigation.getFrame({ + tabId: createDetails.sourceTabId, + frameId: 0, + }), + webext.webNavigation.getFrame({ + tabId: sourceTabId, + frameId: sourceFrameId, + }), + ]); + } + catch (reason) { + return; + } popupCandidates.set( - targetTabId, - new PopupCandidate(targetTabId, openerTabId) + tabId, + new PopupCandidate(createDetails, openerDetails) ); } - popupCandidateTest(targetTabId); + popupCandidateTest(tabId); }; const gcPeriod = 10 * 60 * 1000; @@ -818,9 +847,9 @@ vAPI.Tabs = class extends vAPI.Tabs { µBlock.contextMenu.update(); } - onCreated(targetTabId, openerTabId) { - super.onCreated(targetTabId, openerTabId); - µBlock.tabContextManager.onTabCreated(targetTabId, openerTabId); + onCreated(details) { + super.onCreated(details); + µBlock.tabContextManager.onTabCreated(details); } // When the DOM content of root frame is loaded, this means the tab