From ac86ec33724d4ef0a5d2d6f28bbf39bbcfbd55f8 Mon Sep 17 00:00:00 2001 From: Krzysztof Modras Date: Tue, 19 Nov 2024 14:03:13 +0100 Subject: [PATCH 1/6] POC: on Firefox inject scriplets with browser.contentScripts --- src/background/adblocker.js | 93 ++++++++++++++++++++++++++++++++++--- 1 file changed, 87 insertions(+), 6 deletions(-) diff --git a/src/background/adblocker.js b/src/background/adblocker.js index 6d8c42f8a..8344fc8a1 100644 --- a/src/background/adblocker.js +++ b/src/background/adblocker.js @@ -86,6 +86,9 @@ export async function reloadMainEngine() { await engines.create(engines.MAIN_ENGINE); console.info('[adblocker] Main engine reloaded with no filters'); } + if (__PLATFORM__ === 'firefox') { + contentScripts.unregisterAll(); + } } let updating = false; @@ -163,7 +166,50 @@ export const setup = asyncSetup('adblocker', [ ), ]); -function injectScriptlets(filters, tabId, frameId) { +const contentScripts = (() => { + const map = new Map(); + return { + async register(hostname, code) { + this.unregister(hostname); + try { + const contentScript = await browser.contentScripts.register({ + js: [ + { + code, + }, + ], + allFrames: true, + matches: [`https://*.${hostname}/*`, `http://*.${hostname}/*`], + matchAboutBlank: true, + matchOriginAsFallback: true, + runAt: 'document_start', + }); + map.set(hostname, contentScript); + } catch (e) { + console.warn(e); + this.unregister(hostname); + } + }, + isRegistered(hostname) { + return map.has(hostname); + }, + unregister(hostname) { + const contentScript = map.get(hostname); + if (contentScript) { + contentScript.unregister(); + map.delete(hostname); + } + }, + unregisterAll() { + for (const hostname of map.keys()) { + this.unregister(hostname); + } + }, + }; +})(); + +function injectScriptlets(filters, tabId, frameId, hostname) { + let contentScript = ''; for (const filter of filters) { const parsed = filter.parseScript(); @@ -177,12 +223,19 @@ function injectScriptlets(filters, tabId, frameId) { const scriptletName = `${parsed.name}${parsed.name.endsWith('.js') ? '' : '.js'}`; const scriptlet = scriptlets[scriptletName]; + const func = scriptlet.func; + const args = parsed.args.map((arg) => decodeURIComponent(arg)); if (!scriptlet) { console.warn('[adblocker] unknown scriptlet with name:', scriptletName); continue; } + if (__PLATFORM__ === 'firefox') { + contentScript += `(function () { ${func.toString()} })(...${JSON.stringify(args)})` + continue; + } + chrome.scripting.executeScript( { injectImmediately: true, @@ -193,8 +246,8 @@ function injectScriptlets(filters, tabId, frameId) { tabId, frameIds: [frameId], }, - func: scriptlet.func, - args: parsed.args.map((arg) => decodeURIComponent(arg)), + func, + args, }, () => { if (chrome.runtime.lastError) { @@ -203,6 +256,19 @@ function injectScriptlets(filters, tabId, frameId) { }, ); } + + if (__PLATFORM__ === 'firefox') { + if (filters.length === 0) { + contentScripts.unregister(hostname); + } else if (!contentScripts.isRegistered(hostname)) { + contentScripts.register( + hostname, + contentScript, + ); + } else { + // do nothing if already registered + } + } } function injectStyles(styles, tabId, frameId) { @@ -224,6 +290,9 @@ function injectStyles(styles, tabId, frameId) { } async function injectCosmetics(details, config) { + const isBootstrap = config.bootstrap; + const scriptletsOnly = Boolean(config.scriptletsOnly); + try { setup.pending && (await setup.pending); } catch (e) { @@ -237,6 +306,8 @@ async function injectCosmetics(details, config) { const domain = parsed.domain || ''; const hostname = parsed.hostname || ''; + if (scriptletsOnly && contentScripts.isRegistered(hostname)) return; + if (isPaused(options, hostname)) return; const tabHostname = tabStats.get(tabId)?.hostname; @@ -245,7 +316,6 @@ async function injectCosmetics(details, config) { } const engine = engines.get(engines.MAIN_ENGINE); - const isBootstrap = config.bootstrap; { const { matches } = engine.matchCosmeticFilters({ @@ -282,8 +352,12 @@ async function injectCosmetics(details, config) { } } - if (isBootstrap && scriptFilters.length > 0) { - injectScriptlets(scriptFilters, tabId, frameId); + if (isBootstrap) { + injectScriptlets(scriptFilters, tabId, frameId, hostname); + } + + if (scriptletsOnly) { + return; } const { styles } = engine.injectCosmeticFilters(styleFilters, { @@ -377,6 +451,13 @@ function isTrusted(request, type) { } if (__PLATFORM__ === 'firefox') { + chrome.webNavigation.onBeforeNavigate.addListener( + (details) => { + injectCosmetics(details, { bootstrap: true, scriptletsOnly: true }); + }, + { url: [{ urlPrefix: 'http://' }, { urlPrefix: 'https://' }] }, + ); + function isExtensionRequest(details) { return ( (details.tabId === -1 && details.url.startsWith('moz-extension://')) || From a3ec1b48acafbc4a51970947a1f7702d3061dc5f Mon Sep 17 00:00:00 2001 From: Krzysztof Modras Date: Wed, 11 Dec 2024 18:57:41 +0100 Subject: [PATCH 2/6] fix injection --- src/background/adblocker.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/background/adblocker.js b/src/background/adblocker.js index 8344fc8a1..586c549ef 100644 --- a/src/background/adblocker.js +++ b/src/background/adblocker.js @@ -183,6 +183,7 @@ const contentScripts = (() => { matchAboutBlank: true, matchOriginAsFallback: true, runAt: 'document_start', + world: 'MAIN', }); map.set(hostname, contentScript); } catch (e) { @@ -232,7 +233,7 @@ function injectScriptlets(filters, tabId, frameId, hostname) { } if (__PLATFORM__ === 'firefox') { - contentScript += `(function () { ${func.toString()} })(...${JSON.stringify(args)})` + contentScript += `(${func.toString()})(...${JSON.stringify(args)});\n` continue; } From bcdaf8548c36c96da32c225695e5428e1f9eec8a Mon Sep 17 00:00:00 2001 From: Krzysztof Modras Date: Wed, 11 Dec 2024 19:14:05 +0100 Subject: [PATCH 3/6] Enable with experimental filters --- src/background/adblocker.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/background/adblocker.js b/src/background/adblocker.js index 586c549ef..8561c3fbe 100644 --- a/src/background/adblocker.js +++ b/src/background/adblocker.js @@ -126,6 +126,8 @@ async function updateEngines() { } } +let EXPERIMENTAL_SCRIPTLETS_INJECTION = false; + const HOUR_IN_MS = 60 * 60 * 1000; export const setup = asyncSetup('adblocker', [ OptionsObserver.addListener( @@ -156,6 +158,12 @@ export const setup = asyncSetup('adblocker', [ OptionsObserver.addListener( 'experimentalFilters', async (value, lastValue) => { + if (__PLATFORM__ === 'firefox') { + EXPERIMENTAL_SCRIPTLETS_INJECTION = value; + if (!value) { + contentScripts.unregisterAll(); + } + } engines.setEnv('env_experimental', value); // Experimental filters changed to enabled @@ -232,8 +240,8 @@ function injectScriptlets(filters, tabId, frameId, hostname) { continue; } - if (__PLATFORM__ === 'firefox') { - contentScript += `(${func.toString()})(...${JSON.stringify(args)});\n` + if (__PLATFORM__ === 'firefox' && EXPERIMENTAL_SCRIPTLETS_INJECTION) { + contentScript += `(${func.toString()})(...${JSON.stringify(args)});\n`; continue; } @@ -258,14 +266,11 @@ function injectScriptlets(filters, tabId, frameId, hostname) { ); } - if (__PLATFORM__ === 'firefox') { + if (__PLATFORM__ === 'firefox' && EXPERIMENTAL_SCRIPTLETS_INJECTION) { if (filters.length === 0) { contentScripts.unregister(hostname); } else if (!contentScripts.isRegistered(hostname)) { - contentScripts.register( - hostname, - contentScript, - ); + contentScripts.register(hostname, contentScript); } else { // do nothing if already registered } @@ -454,7 +459,9 @@ function isTrusted(request, type) { if (__PLATFORM__ === 'firefox') { chrome.webNavigation.onBeforeNavigate.addListener( (details) => { - injectCosmetics(details, { bootstrap: true, scriptletsOnly: true }); + if (EXPERIMENTAL_SCRIPTLETS_INJECTION) { + injectCosmetics(details, { bootstrap: true, scriptletsOnly: true }); + } }, { url: [{ urlPrefix: 'http://' }, { urlPrefix: 'https://' }] }, ); From 9ad938ddb11fd86f4b3feff6b9f2d2a60b2afc43 Mon Sep 17 00:00:00 2001 From: Krzysztof Modras Date: Thu, 12 Dec 2024 12:46:02 +0100 Subject: [PATCH 4/6] Addressing code review --- src/background/adblocker.js | 41 ++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/background/adblocker.js b/src/background/adblocker.js index 8561c3fbe..0425fdbaa 100644 --- a/src/background/adblocker.js +++ b/src/background/adblocker.js @@ -126,8 +126,6 @@ async function updateEngines() { } } -let EXPERIMENTAL_SCRIPTLETS_INJECTION = false; - const HOUR_IN_MS = 60 * 60 * 1000; export const setup = asyncSetup('adblocker', [ OptionsObserver.addListener( @@ -158,12 +156,6 @@ export const setup = asyncSetup('adblocker', [ OptionsObserver.addListener( 'experimentalFilters', async (value, lastValue) => { - if (__PLATFORM__ === 'firefox') { - EXPERIMENTAL_SCRIPTLETS_INJECTION = value; - if (!value) { - contentScripts.unregisterAll(); - } - } engines.setEnv('env_experimental', value); // Experimental filters changed to enabled @@ -174,6 +166,16 @@ export const setup = asyncSetup('adblocker', [ ), ]); +let ENABLE_FIREFOX_CONTENT_SCRIPT_SCRIPLET_INJECTION = false; +if (__PLATFORM__ === 'firefox') { + OptionsObserver.addListener('experimentalFilters', (value) => { + ENABLE_FIREFOX_CONTENT_SCRIPT_SCRIPLET_INJECTION = value; + if (!value) { + contentScripts.unregisterAll(); + } + }); +} + const contentScripts = (() => { const map = new Map(); return { @@ -240,7 +242,10 @@ function injectScriptlets(filters, tabId, frameId, hostname) { continue; } - if (__PLATFORM__ === 'firefox' && EXPERIMENTAL_SCRIPTLETS_INJECTION) { + if ( + __PLATFORM__ === 'firefox' && + ENABLE_FIREFOX_CONTENT_SCRIPT_SCRIPLET_INJECTION + ) { contentScript += `(${func.toString()})(...${JSON.stringify(args)});\n`; continue; } @@ -266,7 +271,10 @@ function injectScriptlets(filters, tabId, frameId, hostname) { ); } - if (__PLATFORM__ === 'firefox' && EXPERIMENTAL_SCRIPTLETS_INJECTION) { + if ( + __PLATFORM__ === 'firefox' && + ENABLE_FIREFOX_CONTENT_SCRIPT_SCRIPLET_INJECTION + ) { if (filters.length === 0) { contentScripts.unregister(hostname); } else if (!contentScripts.isRegistered(hostname)) { @@ -296,8 +304,7 @@ function injectStyles(styles, tabId, frameId) { } async function injectCosmetics(details, config) { - const isBootstrap = config.bootstrap; - const scriptletsOnly = Boolean(config.scriptletsOnly); + const { bootstrap: isBootstrap, scriptletsOnly } = config; try { setup.pending && (await setup.pending); @@ -312,7 +319,13 @@ async function injectCosmetics(details, config) { const domain = parsed.domain || ''; const hostname = parsed.hostname || ''; - if (scriptletsOnly && contentScripts.isRegistered(hostname)) return; + if ( + __PLATFORM__ === 'firefox' && + ENABLE_FIREFOX_CONTENT_SCRIPT_SCRIPLET_INJECTION && + scriptletsOnly && + contentScripts.isRegistered(hostname) + ) + return; if (isPaused(options, hostname)) return; @@ -459,7 +472,7 @@ function isTrusted(request, type) { if (__PLATFORM__ === 'firefox') { chrome.webNavigation.onBeforeNavigate.addListener( (details) => { - if (EXPERIMENTAL_SCRIPTLETS_INJECTION) { + if (ENABLE_FIREFOX_CONTENT_SCRIPT_SCRIPLET_INJECTION) { injectCosmetics(details, { bootstrap: true, scriptletsOnly: true }); } }, From 9e251aa8d015c99f001a2e20bf42e82ce171b6a1 Mon Sep 17 00:00:00 2001 From: Krzysztof Modras Date: Thu, 12 Dec 2024 12:53:18 +0100 Subject: [PATCH 5/6] Support pausing --- src/background/adblocker.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/background/adblocker.js b/src/background/adblocker.js index 0425fdbaa..64d694fb5 100644 --- a/src/background/adblocker.js +++ b/src/background/adblocker.js @@ -174,6 +174,13 @@ if (__PLATFORM__ === 'firefox') { contentScripts.unregisterAll(); } }); + + OptionsObserver.addListener('paused', async (paused) => { + if (!ENABLE_FIREFOX_CONTENT_SCRIPT_SCRIPLET_INJECTION) return; + for (const hostname of Object.keys(paused)) { + contentScripts.unregister(hostname); + } + }); } const contentScripts = (() => { From b0198badeb611a7c5621662f48fd5d044fd0ee9d Mon Sep 17 00:00:00 2001 From: Krzysztof Modras Date: Thu, 12 Dec 2024 17:14:52 +0100 Subject: [PATCH 6/6] Cleanup --- src/background/adblocker.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/background/adblocker.js b/src/background/adblocker.js index 64d694fb5..6014fd174 100644 --- a/src/background/adblocker.js +++ b/src/background/adblocker.js @@ -86,7 +86,10 @@ export async function reloadMainEngine() { await engines.create(engines.MAIN_ENGINE); console.info('[adblocker] Main engine reloaded with no filters'); } - if (__PLATFORM__ === 'firefox') { + if ( + __PLATFORM__ === 'firefox' && + ENABLE_FIREFOX_CONTENT_SCRIPT_SCRIPLET_INJECTION + ) { contentScripts.unregisterAll(); } }