diff --git a/platform/mv3/extension/js/scripting/css-generic.js b/platform/mv3/extension/js/scripting/css-generic.js index 5206e92a7..f242b417b 100644 --- a/platform/mv3/extension/js/scripting/css-generic.js +++ b/platform/mv3/extension/js/scripting/css-generic.js @@ -29,6 +29,9 @@ const genericSelectorMap = self.genericSelectorMap || new Map(); self.genericSelectorMap = undefined; if ( genericSelectorMap.size === 0 ) { return; } +const genericExceptionSieve = self.genericExceptionSieve || new Set(); +self.genericExceptionSieve = undefined; + const genericExceptionMap = self.genericExceptionMap || new Map(); self.genericExceptionMap = undefined; @@ -78,8 +81,11 @@ const uBOL_idFromNode = (node, out) => { const selectorList = genericSelectorMap.get(hash); if ( selectorList === undefined ) { return; } genericSelectorMap.delete(hash); - if ( isExcepted(hash) ) { return; } - out.push(selectorList); + if ( genericExceptionSieve.has(hash) ) { + applyExceptions(selectorList, out); + } else { + out.push(selectorList); + } }; // https://github.com/uBlockOrigin/uBlock-issues/discussions/2076 @@ -99,18 +105,26 @@ const uBOL_classesFromNode = (node, out) => { const selectorList = genericSelectorMap.get(hash); if ( selectorList === undefined ) { continue; } genericSelectorMap.delete(hash); - if ( isExcepted(hash) ) { continue; } - out.push(selectorList); + if ( genericExceptionSieve.has(hash) ) { + applyExceptions(selectorList, out); + } else { + out.push(selectorList); + } } }; -const isExcepted = hash => { - const hostnames = genericExceptionMap.get(hash); - if ( hostnames === undefined ) { return; } - const hasEntities = hostnames.includes('.*'); - return self.isolatedAPI.forEachHostname((hostname) => { - if ( hostnames.includes(` ${hostname} `) ) { return true; } - }, { hasEntities }); +const applyExceptions = (selectorList, out) => { + const selectors = new Set(selectorList.split(',\n')); + self.isolatedAPI.forEachHostname(hostname => { + const exceptions = genericExceptionMap.get(hostname); + if ( exceptions === undefined ) { return; } + for ( const exception of exceptions.split('\n') ) { + selectors.delete(exception); + } + if ( selectors.size === 0 ) { return true; } + }, { hasEntities: true }); + if ( selectors.size === 0 ) { return; } + out.push(Array.from(selectors).join(',\n')); } /******************************************************************************/ diff --git a/platform/mv3/make-rulesets.js b/platform/mv3/make-rulesets.js index f4e380886..eb1ec62e5 100644 --- a/platform/mv3/make-rulesets.js +++ b/platform/mv3/make-rulesets.js @@ -700,25 +700,31 @@ async function processGenericCosmeticFilters( if ( selectors === undefined ) { genericSelectorMap.set(hash, selector) } else { - genericSelectorMap.set(hash, `${selectors},${selector}`) + genericSelectorMap.set(hash, `${selectors},\n${selector}`) } } } // Specific exceptions + const genericExceptionSieve = new Set(); const genericExceptionMap = new Map(); if ( declarativeMap ) { - for ( const details of declarativeMap.values() ) { + for ( const [ exception, details ] of declarativeMap ) { if ( details.rejected ) { continue; } if ( details.key === undefined ) { continue; } if ( details.matches !== undefined ) { continue; } if ( details.excludeMatches === undefined ) { continue; } const type = details.key.charCodeAt(0); const hash = hashFromStr(type, details.key.slice(1)); - const hostnames = genericExceptionMap.get(hash) ?? ''; - genericExceptionMap.set(hash, - `${hostnames} ${details.excludeMatches.join(' ')} ` - ); + genericExceptionSieve.add(hash); + for ( const hn of details.excludeMatches ) { + const exceptions = genericExceptionMap.get(hn); + if ( exceptions === undefined ) { + genericExceptionMap.set(hn, exception); + } else { + genericExceptionMap.set(hn, `${exceptions}\n${exception}`); + } + } } } @@ -735,6 +741,10 @@ async function processGenericCosmeticFilters( /\bself\.\$genericSelectorMap\$/, `${JSON.stringify(genericSelectorMap, scriptletJsonReplacer)}` ); + patchedScriptlet = safeReplace(patchedScriptlet, + /\bself\.\$genericExceptionSieve\$/, + `${JSON.stringify(genericExceptionSieve, scriptletJsonReplacer)}` + ); patchedScriptlet = safeReplace(patchedScriptlet, /\bself\.\$genericExceptionMap\$/, `${JSON.stringify(genericExceptionMap, scriptletJsonReplacer)}` @@ -745,10 +755,10 @@ async function processGenericCosmeticFilters( patchedScriptlet ); + log(`CSS-generic: ${genericExceptionSieve.size} specific CSS exceptions`); log(`CSS-generic: ${genericSelectorMap.size} plain CSS selectors`); - log(`CSS-generic: ${genericExceptionMap.size} specific CSS exceptions`); - return genericSelectorMap.size + genericExceptionMap.size; + return genericSelectorMap.size + genericExceptionSieve.size; } const hashFromStr = (type, s) => { @@ -1491,6 +1501,9 @@ async function main() { urls: [ 'https://ublockorigin.github.io/uBOL-home/tests/test-filters.txt' ], homeURL: 'https://ublockorigin.github.io/uBOL-home/tests/test-filters.html', filters: [ + '###gcf #gcf1 .fail', + '###gcf #gcf2 .fail', + 'ublockorigin.github.io,localhost#@##gcf #gcf2 .fail', ], }); diff --git a/platform/mv3/scriptlets/css-generic.template.js b/platform/mv3/scriptlets/css-generic.template.js index 4b535e981..a353d5854 100644 --- a/platform/mv3/scriptlets/css-generic.template.js +++ b/platform/mv3/scriptlets/css-generic.template.js @@ -27,38 +27,57 @@ /******************************************************************************/ -const selectorsToImport = self.$genericSelectorMap$; -const exceptionsToImport = self.$genericExceptionMap$; +const genericSelectorMap = self.$genericSelectorMap$; +const genericExceptionSieve = self.$genericExceptionSieve$; +const genericExceptionMap = self.$genericExceptionMap$; -if ( selectorsToImport ) { +if ( genericSelectorMap ) { const map = self.genericSelectorMap = self.genericSelectorMap || new Map(); - if ( map.size !== 0 ) { - for ( const entry of selectorsToImport ) { + for ( const entry of genericSelectorMap ) { const before = map.get(entry[0]); - map.set(entry[0], - before === undefined ? entry[1] : `${before},${entry[1]}` - ); + if ( before === undefined ) { + map.set(entry[0], entry[1]); + } else { + map.set(entry[0], `${before},\n${entry[1]}`); + } } } else { - self.genericSelectorMap = new Map(selectorsToImport); + self.genericSelectorMap = new Map(genericSelectorMap); } - selectorsToImport.length = 0; + genericSelectorMap.length = 0; } -if ( exceptionsToImport ) { - const map = self.genericExceptionMap = - self.genericExceptionMap || new Map(); - - if ( map.size !== 0 ) { - for ( const entry of exceptionsToImport ) { - map.set(entry[0], `${map.get(entry[0]) || ''}${entry[1]}`); +if ( genericExceptionSieve ) { + const hashes = self.genericExceptionSieve = + self.genericExceptionSieve || new Set(); + if ( hashes.size !== 0 ) { + for ( const hash of genericExceptionSieve ) { + hashes.add(hash); } } else { - self.genericExceptionMap = new Map(exceptionsToImport); + self.genericExceptionSieve = new Set(genericExceptionSieve); } - exceptionsToImport.length = 0; + genericExceptionSieve.length = 0; +} + +if ( genericExceptionMap ) { + const map = self.genericExceptionMap = + self.genericExceptionMap || new Map(); + if ( map.size !== 0 ) { + for ( const entry of genericExceptionMap ) { + const before = map.get(entry[0]); + if ( before === undefined ) { + map.set(entry[0], entry[1]); + } else { + map.set(entry[0], `${before}\n${entry[1]}`); + } + } + } else { + self.genericExceptionMap = new Map(genericExceptionMap); + } + genericExceptionMap.length = 0; } /******************************************************************************/