From 66a1c5347b3aec42e9c2dadb2085294cb987c6dc Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 6 Dec 2025 10:10:45 -0500 Subject: [PATCH] draft --- platform/mv3/extension/js/background.js | 41 +++++------ .../mv3/extension/js/scripting-manager.js | 12 ++-- .../mv3/extension/js/scripting/css-api.js | 7 +- .../mv3/extension/js/scripting/css-generic.js | 2 +- .../js/scripting/css-procedural-api.js | 8 ++- .../extension/js/scripting/css-procedural.js | 69 ++++--------------- .../extension/js/scripting/css-specific.js | 67 ++++-------------- .../js/scripting/css-user-terminate.js | 4 +- .../extension/js/scripting/isolated-api.js | 54 +++++++++------ platform/mv3/extension/js/scripting/picker.js | 4 +- .../extension/js/scripting/tool-overlay.js | 4 +- platform/mv3/make-rulesets.js | 17 +---- .../mv3/scriptlets/css-procedural.template.js | 5 +- .../mv3/scriptlets/css-specific.template.js | 5 +- 14 files changed, 105 insertions(+), 194 deletions(-) diff --git a/platform/mv3/extension/js/background.js b/platform/mv3/extension/js/background.js index 840cc4030..126e8fe7c 100644 --- a/platform/mv3/extension/js/background.js +++ b/platform/mv3/extension/js/background.js @@ -224,29 +224,28 @@ function onMessage(request, sender, callback) { switch ( request.what ) { - case 'insertCSS': { + case 'updateCSS': { if ( frameId === false ) { return false; } // https://bugs.webkit.org/show_bug.cgi?id=262491 if ( frameId !== 0 && webextFlavor === 'safari' ) { return false; } - browser.scripting.insertCSS({ - css: request.css, - origin: 'USER', - target: { tabId, frameIds: [ frameId ] }, - }).catch(reason => { - ubolErr(`insertCSS/${reason}`); - }); - return false; - } - - case 'removeCSS': { - if ( frameId === false ) { return false; } - browser.scripting.removeCSS({ - css: request.css, - origin: 'USER', - target: { tabId, frameIds: [ frameId ] }, - }).catch(reason => { - ubolErr(`removeCSS/${reason}`); - }); + if ( request.insert ) { + browser.scripting.insertCSS({ + css: request.insert, + origin: 'USER', + target: { tabId, frameIds: [ frameId ] }, + }).catch(reason => { + ubolErr(`insertCSS/${reason}`); + }); + } + if ( request.remove ) { + browser.scripting.removeCSS({ + css: request.remove, + origin: 'USER', + target: { tabId, frameIds: [ frameId ] }, + }).catch(reason => { + ubolErr(`removeCSS/${reason}`); + }); + } return false; } @@ -668,6 +667,8 @@ async function startSession() { // launch time whether content css/scripts are properly registered. registerInjectables(); + // Cosmetic filtering-related content scripts cache fitlering data in + // session storage. sessionAccessLevel({ accessLevel: 'TRUSTED_AND_UNTRUSTED_CONTEXTS' }); // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/declarativeNetRequest diff --git a/platform/mv3/extension/js/scripting-manager.js b/platform/mv3/extension/js/scripting-manager.js index 8ae4fef97..c52deffa6 100644 --- a/platform/mv3/extension/js/scripting-manager.js +++ b/platform/mv3/extension/js/scripting-manager.js @@ -296,7 +296,7 @@ async function registerProcedural(context) { { const keys = await localKeys(); for ( const key of keys ) { - if ( key.startsWith('css.procedural.data.') === false ) { continue; } + if ( key.startsWith('css.procedural.') === false ) { continue; } sessionRemove(key); localRemove(key); } @@ -321,8 +321,8 @@ async function registerProcedural(context) { const promises = []; for ( const id of rulesetIds ) { promises.push( - fetchJSON(`/rulesets/scripting/procedural/data/${id}`).then(data => { - return localWrite(`css.procedural.data.${id}`, data); + fetchJSON(`/rulesets/scripting/procedural/${id}`).then(data => { + return localWrite(`css.procedural.json.${id}`, data); }) ); } @@ -385,7 +385,7 @@ async function registerSpecific(context) { { const keys = await localKeys(); for ( const key of keys ) { - if ( key.startsWith('css.specific.data.') === false ) { continue; } + if ( key.startsWith('css.specific.') === false ) { continue; } sessionRemove(key); localRemove(key); } @@ -410,8 +410,8 @@ async function registerSpecific(context) { const promises = []; for ( const id of rulesetIds ) { promises.push( - fetchJSON(`/rulesets/scripting/specific/data/${id}`).then(data => { - return localWrite(`css.specific.data.${id}`, data); + fetchJSON(`/rulesets/scripting/specific/${id}`).then(data => { + return localWrite(`css.specific.json.${id}`, data); }) ); } diff --git a/platform/mv3/extension/js/scripting/css-api.js b/platform/mv3/extension/js/scripting/css-api.js index 13f322d11..0be325d48 100644 --- a/platform/mv3/extension/js/scripting/css-api.js +++ b/platform/mv3/extension/js/scripting/css-api.js @@ -22,10 +22,11 @@ (api => { if ( typeof api === 'object' ) { return; } self.cssAPI = { - insert(css) { + update(insert, remove) { chrome.runtime.sendMessage({ - what: 'insertCSS', - css, + what: 'updateCSS', + insert, + remove }).catch(( ) => { }); }, diff --git a/platform/mv3/extension/js/scripting/css-generic.js b/platform/mv3/extension/js/scripting/css-generic.js index 52c3781a2..b009ffab2 100644 --- a/platform/mv3/extension/js/scripting/css-generic.js +++ b/platform/mv3/extension/js/scripting/css-generic.js @@ -188,7 +188,7 @@ const uBOL_processNodes = ( ) => { if ( styleSheetTimer !== undefined ) { return; } styleSheetTimer = self.requestAnimationFrame(( ) => { styleSheetTimer = undefined; - self.cssAPI.insert(`${styleSheetSelectors.join(',')}{display:none!important;}`); + self.cssAPI.update(`${styleSheetSelectors.join(',')}{display:none!important;}`); styleSheetSelectors.length = 0; }); }; diff --git a/platform/mv3/extension/js/scripting/css-procedural-api.js b/platform/mv3/extension/js/scripting/css-procedural-api.js index 256e42720..83763c5dc 100644 --- a/platform/mv3/extension/js/scripting/css-procedural-api.js +++ b/platform/mv3/extension/js/scripting/css-procedural-api.js @@ -608,9 +608,11 @@ class ProceduralFilterer { for ( const elem of this.styledNodes ) { elem.removeAttribute(token); } - const css = `[${token}]\n{${style}}\n`; promises.push( - chrome.runtime.sendMessage({ what: 'removeCSS', css }).catch(( ) => { }) + chrome.runtime.sendMessage({ + what: 'updateCSS', + remove: `[${token}]\n{${style}}\n`, + }).catch(( ) => { }) ); } this.styleTokenMap.clear(); @@ -680,7 +682,7 @@ class ProceduralFilterer { if ( styleToken !== undefined ) { return styleToken; } styleToken = randomToken(); this.styleTokenMap.set(style, styleToken); - self.cssAPI.insert(`[${styleToken}]\n{${style}}\n`); + self.cssAPI.update(`[${styleToken}]\n{${style}}\n`); return styleToken; } diff --git a/platform/mv3/extension/js/scripting/css-procedural.js b/platform/mv3/extension/js/scripting/css-procedural.js index eca031c79..d2dae1f06 100644 --- a/platform/mv3/extension/js/scripting/css-procedural.js +++ b/platform/mv3/extension/js/scripting/css-procedural.js @@ -32,66 +32,21 @@ self.proceduralImports = undefined; const isolatedAPI = self.isolatedAPI; -const lookupHostname = (hostname, details) => { - const listref = isolatedAPI.binarySearch(details.hostnames, hostname); - if ( listref === -1 ) { return; } - details.listrefs ||= []; - details.listrefs.push(listref); -}; +const selectors = []; -const lookupAll = hostname => { - for ( const details of proceduralImports ) { - lookupHostname(hostname, details); - } -}; - -isolatedAPI.forEachHostname(lookupAll, { - hasEntities: proceduralImports.some(a => a.hasEntities) -}); - -const toLookup = proceduralImports.filter(a => Array.isArray(a.listrefs)); -if ( toLookup.length === 0 ) { return; } - -const selectors = new Set(); -const exceptions = new Set(); - -const lookupSelectors = async details => { - const { rulesetId } = details; - const key = `css.procedural.data.${rulesetId}`; - const data = await isolatedAPI.storageGet(key); - if ( Boolean(data) === false ) { return; } - if ( data.signature !== details.signature ) { return; } - for ( const listref of details.listrefs ) { - const ilist = data.selectorListRefs[listref]; - const list = JSON.parse(`[${data.selectorLists[ilist]}]`); - for ( const iselector of list ) { - if ( iselector >= 0 ) { - selectors.add(data.selectors[iselector]); - } else { - exceptions.add(data.selectors[~iselector]); - } - } - } -}; - -const promises = []; -for ( const details of toLookup ) { - promises.push(lookupSelectors(details)); +const cachedCSS = await isolatedAPI.sessionGet('css.procedural.cache') || {}; +if ( cachedCSS[isolatedAPI.docHostname] ) { + selectors.push(...cachedCSS[isolatedAPI.docHostname]); +} else { + selectors.push(...await isolatedAPI.getSelectors('procedural', proceduralImports)); + cachedCSS[isolatedAPI.docHostname] = selectors; + isolatedAPI.sessionSet('css.procedural.cache', cachedCSS); } - -await Promise.all(promises); +if ( selectors.length === 0 ) { return; } proceduralImports.length = 0; -for ( const selector of exceptions ) { - selectors.delete(selector); -} - -if ( selectors.size === 0 ) { return; } - -const exceptedSelectors = Array.from(selectors).map(a => JSON.parse(a)); - -const declaratives = exceptedSelectors.filter(a => a.cssable); +const declaratives = selectors.filter(a => a.cssable); if ( declaratives.length !== 0 ) { const cssRuleFromProcedural = details => { const { tasks, action } = details; @@ -127,11 +82,11 @@ if ( declaratives.length !== 0 ) { sheetText.push(ruleText); } if ( sheetText.length !== 0 ) { - self.cssAPI.insert(sheetText.join('\n')); + self.cssAPI.update(sheetText.join('\n')); } } -const procedurals = exceptedSelectors.filter(a => a.cssable === undefined); +const procedurals = selectors.filter(a => a.cssable === undefined); if ( procedurals.length !== 0 ) { const addSelectors = selectors => { if ( self.listsProceduralFiltererAPI instanceof Object === false ) { return; } diff --git a/platform/mv3/extension/js/scripting/css-specific.js b/platform/mv3/extension/js/scripting/css-specific.js index e8248ab03..0076bf56d 100644 --- a/platform/mv3/extension/js/scripting/css-specific.js +++ b/platform/mv3/extension/js/scripting/css-specific.js @@ -32,63 +32,22 @@ self.specificImports = undefined; const isolatedAPI = self.isolatedAPI; -const lookupHostname = (hostname, details) => { - const listref = isolatedAPI.binarySearch(details.hostnames, hostname); - if ( listref === -1 ) { return; } - details.listrefs ||= []; - details.listrefs.push(listref); -}; +const selectors = []; -const lookupAll = hostname => { - for ( const details of specificImports ) { - lookupHostname(hostname, details); - } -}; +self.cssAPI.update('*{visibility:hidden!important;}'); -isolatedAPI.forEachHostname(lookupAll, { - hasEntities: specificImports.some(a => a.hasEntities) -}); - -const toLookup = specificImports.filter(a => Array.isArray(a.listrefs)); -if ( toLookup.length === 0 ) { return; } - -const selectors = new Set(); -const exceptions = new Set(); - -const lookupSelectors = async details => { - const { rulesetId } = details; - const key = `css.specific.data.${rulesetId}`; - const data = await isolatedAPI.storageGet(key); - if ( Boolean(data) === false ) { return; } - if ( data.signature !== details.signature ) { return; } - for ( const listref of details.listrefs ) { - const ilist = data.selectorListRefs[listref]; - const list = JSON.parse(`[${data.selectorLists[ilist]}]`); - for ( const iselector of list ) { - if ( iselector >= 0 ) { - selectors.add(data.selectors[iselector]); - } else { - exceptions.add(data.selectors[~iselector]); - } - } - } -}; - -const promises = []; -for ( const details of toLookup ) { - promises.push(lookupSelectors(details)); +const cachedCSS = await isolatedAPI.sessionGet('css.specific.cache') || {}; +if ( cachedCSS[isolatedAPI.docHostname] ) { + selectors.push(...cachedCSS[isolatedAPI.docHostname]); +} else { + selectors.push(...await isolatedAPI.getSelectors('specific', specificImports)); + cachedCSS[isolatedAPI.docHostname] = selectors; + isolatedAPI.sessionSet('css.specific.cache', cachedCSS); } - -await Promise.all(promises); - -for ( const selector of exceptions ) { - selectors.delete(selector); -} - -if ( selectors.size === 0 ) { return; } - -const css = `${Array.from(selectors).join(',\n')}{display:none!important;}`; -self.cssAPI.insert(css); +const insert = selectors.length !== 0 + ? `${Array.from(selectors).join(',\n')}{display:none!important;}` + : undefined; +self.cssAPI.update(insert, '*{visibility:hidden!important;}'); /******************************************************************************/ diff --git a/platform/mv3/extension/js/scripting/css-user-terminate.js b/platform/mv3/extension/js/scripting/css-user-terminate.js index 6e63eb934..412dd7494 100644 --- a/platform/mv3/extension/js/scripting/css-user-terminate.js +++ b/platform/mv3/extension/js/scripting/css-user-terminate.js @@ -26,8 +26,8 @@ const plainSelectors = self.customFilters?.plainSelectors; if ( plainSelectors ) { chrome.runtime.sendMessage({ - what: 'removeCSS', - css: `${plainSelectors.join(',\n')}{display:none!important;}`, + what: 'updateCSS', + remove: `${plainSelectors.join(',\n')}{display:none!important;}`, }).catch(( ) => { }); } diff --git a/platform/mv3/extension/js/scripting/isolated-api.js b/platform/mv3/extension/js/scripting/isolated-api.js index 252a5169e..8c7fa7859 100644 --- a/platform/mv3/extension/js/scripting/isolated-api.js +++ b/platform/mv3/extension/js/scripting/isolated-api.js @@ -29,6 +29,7 @@ const hostnameStack = (( ) => { const docloc = document.location; + isolatedAPI.docHostname = docloc.hostname; const origins = [ docloc.origin ]; if ( docloc.ancestorOrigins ) { origins.push(...docloc.ancestorOrigins); @@ -95,45 +96,54 @@ return -1; }; - const sessionGet = async function(key) { - let data; + isolatedAPI.sessionGet = async function(key) { try { const bin = await chrome.storage.session.get(key); - data = bin?.[key] ?? undefined; - } catch (error) { - console.trace(error); + return bin?.[key] ?? undefined; + } catch { } - return data; }; - const sessionSet = function(key, data) { + isolatedAPI.sessionSet = function(key, data) { try { chrome.storage.session.set({ [key]: data }); - } catch (error) { - console.trace(error); + } catch { } }; - const localGet = async function(key) { - let data; + isolatedAPI.localGet = async function(key) { try { const bin = await chrome.storage.local.get(key); - data = bin?.[key] ?? undefined; - } catch (error) { - console.trace(error); + return bin?.[key] ?? undefined; + } catch { } - return data; }; - isolatedAPI.storageGet = async function(key) { - let data = await sessionGet(key); - if ( data === undefined ) { - data = await localGet(key); - if ( data !== undefined ) { - sessionSet(key, data); + isolatedAPI.getSelectors = async function(realm, details) { + const selectors = new Set(); + const exceptions = new Set(); + const lookupHostname = (hostname, data) => { + const listref = isolatedAPI.binarySearch(data.hostnames, hostname); + if ( listref === -1 ) { return; } + const ilist = data.selectorListRefs[listref]; + const list = JSON.parse(`[${data.selectorLists[ilist]}]`); + for ( const iselector of list ) { + if ( iselector >= 0 ) { + selectors.add(data.selectors[iselector]); + } else { + exceptions.add(data.selectors[~iselector]); + } } + }; + const selectorsFromRuleset = async rulesetId => { + const data = await isolatedAPI.localGet(`css.${realm}.json.${rulesetId}`); + isolatedAPI.forEachHostname(lookupHostname, data); + }; + await Promise.all(details.map(a => selectorsFromRuleset(a.rulesetId))); + for ( const selector of exceptions ) { + selectors.delete(selector); } - return data; + return Array.from(selectors); }; })(self.isolatedAPI); diff --git a/platform/mv3/extension/js/scripting/picker.js b/platform/mv3/extension/js/scripting/picker.js index 60df48b0e..85917e035 100644 --- a/platform/mv3/extension/js/scripting/picker.js +++ b/platform/mv3/extension/js/scripting/picker.js @@ -246,7 +246,7 @@ async function previewSelector(selector) { } } if ( previewedCSS !== '' ) { - await ubolOverlay.sendMessage({ what: 'removeCSS', css: previewedCSS }); + await ubolOverlay.sendMessage({ what: 'updateCSS', remove: previewedCSS }); previewedCSS = ''; } } @@ -261,7 +261,7 @@ async function previewSelector(selector) { return; } previewedCSS = `${selector}{display:none!important;}`; - await ubolOverlay.sendMessage({ what: 'insertCSS', css: previewedCSS }); + await ubolOverlay.sendMessage({ what: 'updateCSS', insert: previewedCSS }); } let previewedSelector = ''; diff --git a/platform/mv3/extension/js/scripting/tool-overlay.js b/platform/mv3/extension/js/scripting/tool-overlay.js index 39d14cc63..f5f94480e 100644 --- a/platform/mv3/extension/js/scripting/tool-overlay.js +++ b/platform/mv3/extension/js/scripting/tool-overlay.js @@ -78,7 +78,7 @@ self.ubolOverlay = { `:root > [${this.secretAttr}-loaded] { visibility: visible !important; }`, `:root > [${this.secretAttr}-click] { pointer-events: none !important; }`, ].join('\n'); - this.sendMessage({ what: 'insertCSS', css: this.pickerCSS }); + this.sendMessage({ what: 'updateCSS', insert: this.pickerCSS }); self.addEventListener('scroll', this.onViewportChanged, { passive: true }); self.addEventListener('resize', this.onViewportChanged, { passive: true }); self.addEventListener('keydown', this.onKeyPressed, true); @@ -86,7 +86,7 @@ self.ubolOverlay = { stop() { if ( this.pickerCSS ) { - this.sendMessage({ what: 'removeCSS', css: this.pickerCSS }); + this.sendMessage({ what: 'updateCSS', remove: this.pickerCSS }); this.pickerCSS = undefined; } self.removeEventListener('scroll', this.onViewportChanged, { passive: true }); diff --git a/platform/mv3/make-rulesets.js b/platform/mv3/make-rulesets.js index d8cb80137..65a1dfc41 100644 --- a/platform/mv3/make-rulesets.js +++ b/platform/mv3/make-rulesets.js @@ -870,12 +870,13 @@ async function processCosmeticFilters(assetDetails, realm, mapin) { }); const data = JSON.stringify({ - signature: secret, selectors: Array.from(allSelectors.keys()), selectorLists: Array.from(allSelectorLists.keys()), selectorListRefs: sortedHostnames.map(a => allHostnames.get(a)), + hostnames: sortedHostnames, + hasEntities, }); - writeFile(`${scriptletDir}/${realm}/data/${assetDetails.id}.json`, data); + writeFile(`${scriptletDir}/${realm}/${assetDetails.id}.json`, data); // The cosmetic filters will be injected programmatically as content // script and the decisions to activate the cosmetic filters will be @@ -885,18 +886,6 @@ async function processCosmeticFilters(assetDetails, realm, mapin) { 'self.$rulesetId$', JSON.stringify(assetDetails.id) ); - patchedScriptlet = safeReplace(patchedScriptlet, - 'self.$signature$', - JSON.stringify(secret) - ); - patchedScriptlet = safeReplace(patchedScriptlet, - /\bself\.\$hostnames\$/, - `/* ${sortedHostnames.length} */ ${JSON.stringify(sortedHostnames)}` - ); - patchedScriptlet = safeReplace(patchedScriptlet, - 'self.$hasEntities$', - JSON.stringify(hasEntities) - ); writeFile(`${scriptletDir}/${realm}/${assetDetails.id}.js`, patchedScriptlet); log(`CSS-${realm}: ${allSelectors.size} distinct filters for ${allHostnames.size} distinct hostnames`); diff --git a/platform/mv3/scriptlets/css-procedural.template.js b/platform/mv3/scriptlets/css-procedural.template.js index 58229fa86..b704f630b 100644 --- a/platform/mv3/scriptlets/css-procedural.template.js +++ b/platform/mv3/scriptlets/css-procedural.template.js @@ -28,12 +28,9 @@ if ( Boolean(chrome?.storage?.local) === false ) { return; } /******************************************************************************/ const rulesetId = self.$rulesetId$; -const signature = self.$signature$; -const hostnames = self.$hostnames$; -const hasEntities = self.$hasEntities$; self.proceduralImports = self.proceduralImports || []; -self.proceduralImports.push({ rulesetId, signature, hostnames, hasEntities }); +self.proceduralImports.push({ rulesetId }); /******************************************************************************/ diff --git a/platform/mv3/scriptlets/css-specific.template.js b/platform/mv3/scriptlets/css-specific.template.js index e7e4778ae..cec9190a3 100644 --- a/platform/mv3/scriptlets/css-specific.template.js +++ b/platform/mv3/scriptlets/css-specific.template.js @@ -28,12 +28,9 @@ if ( Boolean(chrome?.storage?.local) === false ) { return; } /******************************************************************************/ const rulesetId = self.$rulesetId$; -const signature = self.$signature$; -const hostnames = self.$hostnames$; -const hasEntities = self.$hasEntities$; self.specificImports = self.specificImports || []; -self.specificImports.push({ rulesetId, signature, hostnames, hasEntities }); +self.specificImports.push({ rulesetId }); /******************************************************************************/