From d006fd06e7bb82c1a0d2c60e33163c8cc73036af Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Fri, 7 Mar 2025 17:04:02 -0500 Subject: [PATCH] [mv3] Add support for ancestor context syntax in scriptlets Related commit: https://github.com/gorhill/uBlock/commit/a483f7955fc8c9c820684c3f55c9f982a7abb2d3 --- platform/mv3/make-rulesets.js | 4 +- platform/mv3/make-scriptlets.js | 37 +++-- platform/mv3/scriptlets/scriptlet.template.js | 150 +++++++----------- 3 files changed, 81 insertions(+), 110 deletions(-) diff --git a/platform/mv3/make-rulesets.js b/platform/mv3/make-rulesets.js index 7fb24351b..d6efcd77e 100644 --- a/platform/mv3/make-rulesets.js +++ b/platform/mv3/make-rulesets.js @@ -1526,8 +1526,8 @@ async function main() { await rulesetFromURLs({ id: 'est-0', - group: 'regions', - lang: 'et', + group: 'regions', + lang: 'et', name: '🇪🇪ee: Eesti saitidele kohandatud filter', enabled: false, urls: [ 'https://ubol-et.adblock.ee/list.txt' ], diff --git a/platform/mv3/make-scriptlets.js b/platform/mv3/make-scriptlets.js index c2fc6d69f..cf356c5ea 100644 --- a/platform/mv3/make-scriptlets.js +++ b/platform/mv3/make-scriptlets.js @@ -97,8 +97,9 @@ export function compile(assetDetails, details) { world: resourceEntry.world, args: new Map(), hostnames: new Map(), - entities: new Map(), exceptions: new Map(), + hasEntities: false, + hasAncestors: false, matches: new Set(), }); } @@ -109,23 +110,21 @@ export function compile(assetDetails, details) { const iArgs = scriptletDetails.args.get(argsToken); if ( details.matches ) { for ( const hn of details.matches ) { - if ( hn.endsWith('.*') ) { + const isEntity = hn.endsWith('.*') || hn.endsWith('.*>>'); + scriptletDetails.hasEntities ||= isEntity; + const isAncestor = hn.endsWith('>>') + scriptletDetails.hasAncestors ||= isAncestor; + if ( isEntity || isAncestor ) { scriptletDetails.matches.clear(); scriptletDetails.matches.add('*'); - const entity = hn.slice(0, -2); - if ( scriptletDetails.entities.has(entity) === false ) { - scriptletDetails.entities.set(entity, new Set()); - } - scriptletDetails.entities.get(entity).add(iArgs); - } else { - if ( scriptletDetails.matches.has('*') === false ) { - scriptletDetails.matches.add(hn); - } - if ( scriptletDetails.hostnames.has(hn) === false ) { - scriptletDetails.hostnames.set(hn, new Set()); - } - scriptletDetails.hostnames.get(hn).add(iArgs); } + if ( scriptletDetails.matches.has('*') === false ) { + scriptletDetails.matches.add(hn); + } + if ( scriptletDetails.hostnames.has(hn) === false ) { + scriptletDetails.hostnames.set(hn, new Set()); + } + scriptletDetails.hostnames.get(hn).add(iArgs); } } else { scriptletDetails.matches.add('*'); @@ -172,8 +171,12 @@ export async function commit(rulesetId, path, writeFn) { JSON.stringify(patchHnMap(details.hostnames)) ); content = safeReplace(content, - 'self.$entitiesMap$', - JSON.stringify(patchHnMap(details.entities)) + 'self.$hasEntities$', + JSON.stringify(details.hasEntities) + ); + content = safeReplace(content, + 'self.$hasAncestors$', + JSON.stringify(details.hasAncestors) ); content = safeReplace(content, 'self.$exceptionsMap$', diff --git a/platform/mv3/scriptlets/scriptlet.template.js b/platform/mv3/scriptlets/scriptlet.template.js index f43fb6ad2..fb5ace818 100644 --- a/platform/mv3/scriptlets/scriptlet.template.js +++ b/platform/mv3/scriptlets/scriptlet.template.js @@ -20,30 +20,13 @@ */ -/* eslint-disable indent */ - // ruleset: $rulesetId$ // Important! // Isolate from global scope // Start of local scope -(( ) => { - -/******************************************************************************/ - -// Start of code to inject -const uBOL_$scriptletName$ = function() { - -const scriptletGlobals = {}; // eslint-disable-line - -const argsList = self.$argsList$; - -const hostnamesMap = new Map(self.$hostnamesMap$); - -const entitiesMap = new Map(self.$entitiesMap$); - -const exceptionsMap = new Map(self.$exceptionsMap$); +(function uBOL_$scriptletName$() { /******************************************************************************/ @@ -51,98 +34,83 @@ function $scriptletName$(){} /******************************************************************************/ -const hnParts = []; -try { - let origin = document.location.origin; - if ( origin === 'null' ) { - const origins = document.location.ancestorOrigins || []; - for ( let i = 0; i < origins.length; i++ ) { - origin = origins[i]; - if ( origin !== 'null' ) { break; } - } - } - const beg = origin.lastIndexOf('://'); - if ( beg === -1 ) { return; } - let hn = origin.slice(beg+3) - const end = hn.indexOf(':'); - if ( end !== -1 ) { hn = hn.slice(0, end); } - hnParts.push(...hn.split('.')); -} catch { -} -const hnpartslen = hnParts.length; -if ( hnpartslen === 0 ) { return; } +const scriptletGlobals = {}; // eslint-disable-line +const argsList = self.$argsList$; +const hostnamesMap = new Map(self.$hostnamesMap$); +const exceptionsMap = new Map(self.$exceptionsMap$); +const hasEntities = self.$hasEntities$; +const hasAncestors = self.$hasAncestors$; -const todoIndices = new Set(); -const tonotdoIndices = []; - -// Exceptions -if ( exceptionsMap.size !== 0 ) { - for ( let i = 0; i < hnpartslen; i++ ) { - const hn = hnParts.slice(i).join('.'); - const excepted = exceptionsMap.get(hn); - if ( excepted ) { tonotdoIndices.push(...excepted); } - } - exceptionsMap.clear(); -} - -// Hostname-based -if ( hostnamesMap.size !== 0 ) { - const collectArgIndices = hn => { - let argsIndices = hostnamesMap.get(hn); - if ( argsIndices === undefined ) { return; } - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } +const collectArgIndices = (hn, map, out) => { + let argsIndices = map.get(hn); + if ( argsIndices === undefined ) { return; } + if ( typeof argsIndices !== 'number' ) { for ( const argsIndex of argsIndices ) { - if ( tonotdoIndices.includes(argsIndex) ) { continue; } - todoIndices.add(argsIndex); + out.add(argsIndex); } - }; - for ( let i = 0; i < hnpartslen; i++ ) { - const hn = hnParts.slice(i).join('.'); - collectArgIndices(hn); + } else { + out.add(argsIndices); } - collectArgIndices('*'); - hostnamesMap.clear(); -} +}; -// Entity-based -if ( entitiesMap.size !== 0 ) { - const n = hnpartslen - 1; - for ( let i = 0; i < n; i++ ) { - for ( let j = n; j > i; j-- ) { - const en = hnParts.slice(i,j).join('.'); - let argsIndices = entitiesMap.get(en); - if ( argsIndices === undefined ) { continue; } - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - if ( tonotdoIndices.includes(argsIndex) ) { continue; } - todoIndices.add(argsIndex); +const indicesFromHostname = (hostname, suffix = '') => { + const hnParts = hostname.split('.'); + const hnpartslen = hnParts.length; + if ( hnpartslen === 0 ) { return; } + for ( let i = 0; i < hnpartslen; i++ ) { + const hn = `${hnParts.slice(i).join('.')}${suffix}`; + collectArgIndices(hn, hostnamesMap, todoIndices); + collectArgIndices(hn, exceptionsMap, tonotdoIndices); + } + if ( hasEntities ) { + const n = hnpartslen - 1; + for ( let i = 0; i < n; i++ ) { + for ( let j = n; j > i; j-- ) { + const en = `${hnParts.slice(i,j).join('.')}.*${suffix}`; + collectArgIndices(en, hostnamesMap, todoIndices); + collectArgIndices(en, exceptionsMap, tonotdoIndices); } } } - entitiesMap.clear(); +}; + +const entries = (( ) => { + const docloc = document.location; + const origins = [ docloc.origin ]; + if ( docloc.ancestorOrigins ) { + origins.push(...docloc.ancestorOrigins); + } + return origins.map((origin, i) => { + const beg = origin.lastIndexOf('://'); + if ( beg === -1 ) { return; } + const hn = origin.slice(beg+3) + const end = hn.indexOf(':'); + return { hn: end === -1 ? hn : hn.slice(0, end), i }; + }).filter(a => a !== undefined); +})(); +if ( entries.length === 0 ) { return; } + +const todoIndices = new Set(); +const tonotdoIndices = new Set(); + +indicesFromHostname(entries[0].hn); +if ( hasAncestors ) { + for ( const entry of entries ) { + if ( entry.i === 0 ) { continue; } + indicesFromHostname(entry.hn, '>>'); + } } // Apply scriplets for ( const i of todoIndices ) { + if ( tonotdoIndices.has(i) ) { continue; } try { $scriptletName$(...argsList[i]); } catch { } } -argsList.length = 0; - -/******************************************************************************/ - -}; -// End of code to inject - -/******************************************************************************/ - -uBOL_$scriptletName$(); /******************************************************************************/ // End of local scope })(); -/******************************************************************************/ - void 0;