From bb41d9594fda7d06499a215992905e4e650ed30b Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Fri, 11 Aug 2023 13:22:25 -0400 Subject: [PATCH] [mv3] Use workaround to inject scriptlets in Firefox Additionally: Use `export UBO_VERSION=local` at the console to build MV3 extension using current version of uBO code base. By default, the version is taken from `./platform/mv3/ubo-version' and usually set to last stable release. --- .../mv3/extension/js/scripting-manager.js | 11 ++-- .../extension/js/scripting/css-procedural.js | 9 +++- platform/mv3/make-rulesets.js | 10 ++-- platform/mv3/make-scriptlets.js | 2 +- platform/mv3/scriptlets/scriptlet.template.js | 54 ++++++++++++++++++- src/js/contentscript-extra.js | 9 +++- tools/make-mv3.sh | 15 +++--- 7 files changed, 89 insertions(+), 21 deletions(-) diff --git a/platform/mv3/extension/js/scripting-manager.js b/platform/mv3/extension/js/scripting-manager.js index f1f949016..9a99c2929 100644 --- a/platform/mv3/extension/js/scripting-manager.js +++ b/platform/mv3/extension/js/scripting-manager.js @@ -417,10 +417,6 @@ function registerSpecific(context) { /******************************************************************************/ function registerScriptlet(context, scriptletDetails) { - // https://bugzilla.mozilla.org/show_bug.cgi?id=1736575 - // `MAIN` world not yet supported in Firefox - if ( isGecko ) { return; } - const { before, filteringModeDetails, rulesetsDetails } = context; const hasBroadHostPermission = @@ -476,9 +472,14 @@ function registerScriptlet(context, scriptletDetails) { matches, excludeMatches, runAt: 'document_start', - world: 'MAIN', }; + // https://bugzilla.mozilla.org/show_bug.cgi?id=1736575 + // `MAIN` world not yet supported in Firefox + if ( isGecko === false ) { + directive.world = 'MAIN'; + } + // register if ( registered === undefined ) { context.toAdd.push(directive); diff --git a/platform/mv3/extension/js/scripting/css-procedural.js b/platform/mv3/extension/js/scripting/css-procedural.js index 388f1c822..5c477f1d1 100644 --- a/platform/mv3/extension/js/scripting/css-procedural.js +++ b/platform/mv3/extension/js/scripting/css-procedural.js @@ -485,8 +485,13 @@ class PSelector { prime(input) { const root = input || document; if ( this.selector === '' ) { return [ root ]; } - if ( input !== document && /^ [>+~]/.test(this.selector) ) { - return Array.from(PSelectorSpathTask.qsa(input, this.selector)); + if ( input !== document ) { + const c0 = this.selector.charCodeAt(0); + if ( c0 === 0x2B /* + */ || c0 === 0x7E /* ~ */ ) { + return Array.from(PSelectorSpathTask.qsa(input, this.selector)); + } else if ( c0 === 0x3E /* > */ ) { + return Array.from(input.querySelectorAll(`:scope ${this.selector}`)); + } } return Array.from(root.querySelectorAll(this.selector)); } diff --git a/platform/mv3/make-rulesets.js b/platform/mv3/make-rulesets.js index 5f95b6c51..0972c62e7 100644 --- a/platform/mv3/make-rulesets.js +++ b/platform/mv3/make-rulesets.js @@ -53,19 +53,23 @@ const commandLineArgs = (( ) => { return args; })(); +const platform = commandLineArgs.get('platform') || 'chromium'; const outputDir = commandLineArgs.get('output') || '.'; const cacheDir = `${outputDir}/../mv3-data`; const rulesetDir = `${outputDir}/rulesets`; const scriptletDir = `${rulesetDir}/scripting`; const env = [ - 'chromium', + platform, 'mv3', - 'native_css_has', 'ublock', 'ubol', 'user_stylesheet', ]; +if ( platform !== 'firefox' ) { + env.push('native_css_has'); +} + /******************************************************************************/ const jsonSetMapReplacer = (k, v) => { @@ -1222,7 +1226,7 @@ async function main() { resources: Array.from(requiredRedirectResources).map(path => `/${path}`), matches: [ '' ], }; - if ( commandLineArgs.get('platform') === 'chromium' ) { + if ( platform === 'chromium' ) { web_accessible_resources.use_dynamic_url = true; } manifest.web_accessible_resources = [ web_accessible_resources ]; diff --git a/platform/mv3/make-scriptlets.js b/platform/mv3/make-scriptlets.js index 63cf9f27f..2bc920d07 100644 --- a/platform/mv3/make-scriptlets.js +++ b/platform/mv3/make-scriptlets.js @@ -167,7 +167,7 @@ export async function commit(rulesetId, path, writeFn) { content = safeReplace(content, /\$scriptletName\$/, details.name, 0); content = safeReplace(content, 'self.$argsList$', - JSON.stringify(Array.from(details.args.keys())) + JSON.stringify(Array.from(details.args.keys()).map(a => JSON.parse(a))) ); content = safeReplace(content, 'self.$hostnamesMap$', diff --git a/platform/mv3/scriptlets/scriptlet.template.js b/platform/mv3/scriptlets/scriptlet.template.js index b6859f5af..f6ade44f2 100644 --- a/platform/mv3/scriptlets/scriptlet.template.js +++ b/platform/mv3/scriptlets/scriptlet.template.js @@ -21,6 +21,7 @@ */ /* jshint esversion:11 */ +/* global cloneInto */ 'use strict'; @@ -31,10 +32,14 @@ // Important! // Isolate from global scope -(function uBOL_$scriptletName$() { +// Start of local scope +(( ) => { /******************************************************************************/ +// Start of injected code +const uBOL_$scriptletName$ = function() { + const scriptletGlobals = new Map(); // jshint ignore: line const argsList = self.$argsList$; @@ -109,13 +114,58 @@ if ( entitiesMap.size !== 0 ) { // Apply scriplets for ( const i of todoIndices ) { - try { $scriptletName$(...JSON.parse(argsList[i])); } + try { $scriptletName$(...argsList[i]); } catch(ex) {} } argsList.length = 0; /******************************************************************************/ +}; +// End of injected code + +/******************************************************************************/ + +// Inject code + +// https://bugzilla.mozilla.org/show_bug.cgi?id=1736575 +// `MAIN` world not yet supported in Firefox, so we inject the code into +// 'MAIN' ourself when enviroment in Firefox. + +// Not Firefox +if ( typeof wrappedJSObject !== 'object' ) { + return uBOL_$scriptletName$(); +} + +// Firefox +{ + const page = self.wrappedJSObject; + let script, url; + try { + page.uBOL_$scriptletName$ = cloneInto([ + [ '(', uBOL_$scriptletName$.toString(), ')();' ], + { type: 'text/javascript; charset=utf-8' }, + ], self); + const blob = new page.Blob(...page.uBOL_$scriptletName$); + url = page.URL.createObjectURL(blob); + const doc = page.document; + script = doc.createElement('script'); + script.async = false; + script.src = url; + (doc.head || doc.documentElement || doc).append(script); + } catch (ex) { + console.error(ex); + } + if ( url ) { + if ( script ) { script.remove(); } + page.URL.revokeObjectURL(url); + } + delete page.uBOL_$scriptletName$; +} + +/******************************************************************************/ + +// End of local scope })(); /******************************************************************************/ diff --git a/src/js/contentscript-extra.js b/src/js/contentscript-extra.js index 29a736d21..88d9f1349 100644 --- a/src/js/contentscript-extra.js +++ b/src/js/contentscript-extra.js @@ -378,8 +378,13 @@ class PSelector { prime(input) { const root = input || document; if ( this.selector === '' ) { return [ root ]; } - if ( input !== document && /^ ?[>+~]/.test(this.selector) ) { - return Array.from(PSelectorSpathTask.qsa(input, this.selector)); + if ( input !== document ) { + const c0 = this.selector.charCodeAt(0); + if ( c0 === 0x2B /* + */ || c0 === 0x7E /* ~ */ ) { + return Array.from(PSelectorSpathTask.qsa(input, this.selector)); + } else if ( c0 === 0x3E /* > */ ) { + return Array.from(input.querySelectorAll(`:scope ${this.selector}`)); + } } return Array.from(root.querySelectorAll(this.selector)); } diff --git a/tools/make-mv3.sh b/tools/make-mv3.sh index a4e46e29e..353ff8d0e 100755 --- a/tools/make-mv3.sh +++ b/tools/make-mv3.sh @@ -35,7 +35,6 @@ if [ "$QUICK" != "yes" ]; then rm -rf $DES fi - mkdir -p $DES cd $DES DES=$(pwd) @@ -45,11 +44,15 @@ mkdir -p $DES/css/fonts mkdir -p $DES/js mkdir -p $DES/img -UBO_DIR=$(mktemp -d) -UBO_REPO="https://github.com/gorhill/uBlock.git" -UBO_VERSION=$(cat platform/mv3/ubo-version) -echo "*** uBOLite.mv3: Fetching uBO $UBO_VERSION from $UBO_REPO into $UBO_DIR" -git clone -q --depth 1 --branch "$UBO_VERSION" "$UBO_REPO" "$UBO_DIR" +if [ "$UBO_VERSION" != "local" ]; then + UBO_VERSION=$(cat platform/mv3/ubo-version) + UBO_REPO="https://github.com/gorhill/uBlock.git" + UBO_DIR=$(mktemp -d) + echo "*** uBOLite.mv3: Fetching uBO $UBO_VERSION from $UBO_REPO into $UBO_DIR" + git clone -q --depth 1 --branch "$UBO_VERSION" "$UBO_REPO" "$UBO_DIR" +else + UBO_DIR=. +fi echo "*** uBOLite.mv3: Copying common files" cp -R $UBO_DIR/src/css/fonts/* $DES/css/fonts/