diff --git a/platform/mv3/extension/js/popup.js b/platform/mv3/extension/js/popup.js index b9f051db6..72cf4ba5b 100644 --- a/platform/mv3/extension/js/popup.js +++ b/platform/mv3/extension/js/popup.js @@ -307,9 +307,14 @@ async function init() { ruleCount += rules.removeparam + rules.redirect + rules.csp; } let specificCount = 0; - if ( css.specific instanceof Object ) { - specificCount += css.specific.domainBased; - specificCount += css.specific.entityBased; + if ( typeof css.specific === 'number' ) { + specificCount += css.specific; + } + if ( typeof css.declarative === 'number' ) { + specificCount += css.declarative; + } + if ( typeof css.procedural === 'number' ) { + specificCount += css.procedural; } dom.text( qs$(div, 'p'), diff --git a/platform/mv3/extension/js/ruleset-manager.js b/platform/mv3/extension/js/ruleset-manager.js index 56899e6c3..e1d9cdca4 100644 --- a/platform/mv3/extension/js/ruleset-manager.js +++ b/platform/mv3/extension/js/ruleset-manager.js @@ -394,10 +394,10 @@ async function updateCspRules() { if ( addRules.length === 0 && removeRuleIds.length === 0 ) { return; } if ( removeRuleIds.length !== 0 ) { - console.info(`Remove ${removeRuleIds.length} DNR redirect rules`); + console.info(`Remove ${removeRuleIds.length} DNR csp rules`); } if ( addRules.length !== 0 ) { - console.info(`Add ${addRules.length} DNR redirect rules`); + console.info(`Add ${addRules.length} DNR csp rules`); } return dnr.updateDynamicRules({ addRules, removeRuleIds }); diff --git a/platform/mv3/extension/js/scripting-manager.js b/platform/mv3/extension/js/scripting-manager.js index 94818d403..f70758531 100644 --- a/platform/mv3/extension/js/scripting-manager.js +++ b/platform/mv3/extension/js/scripting-manager.js @@ -38,40 +38,6 @@ const isGecko = browser.runtime.getURL('').startsWith('moz-extension://'); const resourceDetailPromises = new Map(); -function getSpecificDetails() { - let promise = resourceDetailPromises.get('specific'); - if ( promise !== undefined ) { return promise; } - promise = fetchJSON('/rulesets/specific-details').then(entries => { - const out = new Map(); - for ( const entry of entries ) { - out.set(entry[0], new Map(entry[1])); - } - return out; - }); - resourceDetailPromises.set('specific', promise); - return promise; -} - -function getDeclarativeDetails() { - let promise = resourceDetailPromises.get('declarative'); - if ( promise !== undefined ) { return promise; } - promise = fetchJSON('/rulesets/declarative-details').then( - entries => new Map(entries) - ); - resourceDetailPromises.set('declarative', promise); - return promise; -} - -function getProceduralDetails() { - let promise = resourceDetailPromises.get('procedural'); - if ( promise !== undefined ) { return promise; } - promise = fetchJSON('/rulesets/procedural-details').then( - entries => new Map(entries) - ); - resourceDetailPromises.set('procedural', promise); - return promise; -} - function getScriptletDetails() { let promise = resourceDetailPromises.get('scriptlet'); if ( promise !== undefined ) { return promise; } @@ -135,8 +101,8 @@ function registerGeneric(context, genericDetails) { if ( hostnames !== undefined ) { excludeHostnames.push(...hostnames); } - if ( details.css.generic instanceof Object === false ) { continue; } - if ( details.css.generic.count === 0 ) { continue; } + const count = details.css?.generic || 0; + if ( count === 0 ) { continue; } js.push(`/rulesets/scripting/generic/${details.id}.js`); } @@ -144,19 +110,20 @@ function registerGeneric(context, genericDetails) { js.push('/js/scripting/css-generic.js'); + const { none, network, extendedSpecific, extendedGeneric } = filteringModeDetails; const matches = []; const excludeMatches = []; - if ( filteringModeDetails.extendedGeneric.has('all-urls') ) { - excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.none)); - excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.network)); - excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.extendedSpecific)); + if ( extendedGeneric.has('all-urls') ) { + excludeMatches.push(...ut.matchesFromHostnames(none)); + excludeMatches.push(...ut.matchesFromHostnames(network)); + excludeMatches.push(...ut.matchesFromHostnames(extendedSpecific)); excludeMatches.push(...ut.matchesFromHostnames(excludeHostnames)); matches.push(''); } else { matches.push( ...ut.matchesFromHostnames( ut.subtractHostnameIters( - Array.from(filteringModeDetails.extendedGeneric), + Array.from(extendedGeneric), excludeHostnames ) ) @@ -198,50 +165,33 @@ function registerGeneric(context, genericDetails) { /******************************************************************************/ -function registerProcedural(context, proceduralDetails) { +function registerProcedural(context) { const { before, filteringModeDetails, rulesetsDetails } = context; const js = []; - const hostnameMatches = new Set(); - for ( const details of rulesetsDetails ) { - if ( details.css.procedural === 0 ) { continue; } - js.push(`/rulesets/scripting/procedural/${details.id}.js`); - if ( proceduralDetails.has(details.id) ) { - for ( const hn of proceduralDetails.get(details.id) ) { - hostnameMatches.add(hn); - } - } + for ( const rulesetDetails of rulesetsDetails ) { + const count = rulesetDetails.css?.procedural || 0; + if ( count === 0 ) { continue; } + js.push(`/rulesets/scripting/procedural/${rulesetDetails.id}.js`); } - if ( js.length === 0 ) { return; } + const { none, network, extendedSpecific, extendedGeneric } = filteringModeDetails; + const matches = [ + ...ut.matchesFromHostnames(extendedSpecific), + ...ut.matchesFromHostnames(extendedGeneric), + ]; + if ( matches.length === 0 ) { return; } + js.push('/js/scripting/css-procedural.js'); - const { - none, - network, - extendedSpecific, - extendedGeneric, - } = filteringModeDetails; - - const matches = []; const excludeMatches = []; - if ( extendedSpecific.has('all-urls') || extendedGeneric.has('all-urls') ) { + if ( none.has('all-urls') === false ) { excludeMatches.push(...ut.matchesFromHostnames(none)); - excludeMatches.push(...ut.matchesFromHostnames(network)); - matches.push(...ut.matchesFromHostnames(hostnameMatches)); - } else if ( extendedSpecific.size !== 0 || extendedGeneric.size !== 0 ) { - matches.push( - ...ut.matchesFromHostnames( - ut.intersectHostnameIters( - [ ...extendedSpecific, ...extendedGeneric ], - hostnameMatches - ) - ) - ); } - - if ( matches.length === 0 ) { return; } + if ( network.has('all-urls') === false ) { + excludeMatches.push(...ut.matchesFromHostnames(network)); + } const registered = before.get('css-procedural'); before.delete('css-procedural'); // Important! @@ -277,48 +227,33 @@ function registerProcedural(context, proceduralDetails) { /******************************************************************************/ -function registerDeclarative(context, declarativeDetails) { +function registerDeclarative(context) { const { before, filteringModeDetails, rulesetsDetails } = context; const js = []; - const hostnameMatches = []; - for ( const details of rulesetsDetails ) { - if ( details.css.declarative === 0 ) { continue; } - js.push(`/rulesets/scripting/declarative/${details.id}.js`); - if ( declarativeDetails.has(details.id) ) { - hostnameMatches.push(...declarativeDetails.get(details.id)); - } + for ( const rulesetDetails of rulesetsDetails ) { + const count = rulesetDetails.css?.declarative || 0; + if ( count === 0 ) { continue; } + js.push(`/rulesets/scripting/declarative/${rulesetDetails.id}.js`); } - if ( js.length === 0 ) { return; } + const { none, network, extendedSpecific, extendedGeneric } = filteringModeDetails; + const matches = [ + ...ut.matchesFromHostnames(extendedSpecific), + ...ut.matchesFromHostnames(extendedGeneric), + ]; + if ( matches.length === 0 ) { return; } + js.push('/js/scripting/css-declarative.js'); - const { - none, - network, - extendedSpecific, - extendedGeneric, - } = filteringModeDetails; - - const matches = []; const excludeMatches = []; - if ( extendedSpecific.has('all-urls') || extendedGeneric.has('all-urls') ) { + if ( none.has('all-urls') === false ) { excludeMatches.push(...ut.matchesFromHostnames(none)); - excludeMatches.push(...ut.matchesFromHostnames(network)); - matches.push(...ut.matchesFromHostnames(hostnameMatches)); - } else if ( extendedSpecific.size !== 0 || extendedGeneric.size !== 0 ) { - matches.push( - ...ut.matchesFromHostnames( - ut.intersectHostnameIters( - [ ...extendedSpecific, ...extendedGeneric ], - hostnameMatches - ) - ) - ); } - - if ( matches.length === 0 ) { return; } + if ( network.has('all-urls') === false ) { + excludeMatches.push(...ut.matchesFromHostnames(network)); + } const registered = before.get('css-declarative'); before.delete('css-declarative'); // Important! @@ -354,6 +289,68 @@ function registerDeclarative(context, declarativeDetails) { /******************************************************************************/ +function registerSpecific(context) { + const { before, filteringModeDetails, rulesetsDetails } = context; + + const js = []; + for ( const rulesetDetails of rulesetsDetails ) { + const count = rulesetDetails.css?.specific || 0; + if ( count === 0 ) { continue; } + js.push(`/rulesets/scripting/specific/${rulesetDetails.id}.js`); + } + if ( js.length === 0 ) { return; } + + const { none, network, extendedSpecific, extendedGeneric } = filteringModeDetails; + const matches = [ + ...ut.matchesFromHostnames(extendedSpecific), + ...ut.matchesFromHostnames(extendedGeneric), + ]; + if ( matches.length === 0 ) { return; } + + js.push('/js/scripting/css-specific.js'); + + const excludeMatches = []; + if ( none.has('all-urls') === false ) { + excludeMatches.push(...ut.matchesFromHostnames(none)); + } + if ( network.has('all-urls') === false ) { + excludeMatches.push(...ut.matchesFromHostnames(network)); + } + + const registered = before.get('css-specific'); + before.delete('css-specific'); // Important! + + // register + if ( registered === undefined ) { + context.toAdd.push({ + id: 'css-specific', + js, + allFrames: true, + matches, + excludeMatches, + runAt: 'document_start', + }); + return; + } + + // update + const directive = { id: 'css-specific' }; + if ( arrayEq(registered.js, js, false) === false ) { + directive.js = js; + } + if ( arrayEq(registered.matches, matches) === false ) { + directive.matches = matches; + } + if ( arrayEq(registered.excludeMatches, excludeMatches) === false ) { + directive.excludeMatches = excludeMatches; + } + if ( directive.js || directive.matches || directive.excludeMatches ) { + context.toUpdate.push(directive); + } +} + +/******************************************************************************/ + function registerScriptlet(context, scriptletDetails) { // https://bugzilla.mozilla.org/show_bug.cgi?id=1736575 // `MAIN` world not yet supported in Firefox @@ -432,210 +429,6 @@ function registerScriptlet(context, scriptletDetails) { /******************************************************************************/ -function registerSpecific(context, specificDetails) { - const { filteringModeDetails } = context; - - let toRegisterMap; - if ( - filteringModeDetails.extendedSpecific.has('all-urls') || - filteringModeDetails.extendedGeneric.has('all-urls') - ) { - toRegisterMap = registerSpecificAll(context, specificDetails); - } else { - toRegisterMap = registerSpecificSome(context, specificDetails); - } - - for ( const [ fname, hostnames ] of toRegisterMap ) { - toRegisterableScript(context, fname, hostnames); - } -} - -function registerSpecificSome(context, specificDetails) { - const { filteringModeDetails, rulesetsDetails } = context; - const toRegisterMap = new Map(); - - const targetHostnames = [ - ...filteringModeDetails.extendedSpecific, - ...filteringModeDetails.extendedGeneric, - ]; - - const checkMatches = (hostnamesToFidsMap, hn) => { - let fids = hostnamesToFidsMap.get(hn); - if ( fids === undefined ) { return; } - if ( typeof fids === 'number' ) { fids = [ fids ]; } - for ( const fid of fids ) { - const fname = ut.fnameFromFileId(fid); - let existing = toRegisterMap.get(fname); - if ( existing ) { - if ( existing[0] === '*' ) { continue; } - existing.push(hn); - } else { - toRegisterMap.set(fname, existing = [ hn ]); - } - if ( hn !== '*' ) { continue; } - existing.length = 0; - existing.push('*'); - break; - } - }; - - for ( const rulesetDetails of rulesetsDetails ) { - const hostnamesToFidsMap = specificDetails.get(rulesetDetails.id); - if ( hostnamesToFidsMap === undefined ) { continue; } - for ( let hn of targetHostnames ) { - while ( hn ) { - checkMatches(hostnamesToFidsMap, hn); - hn = ut.toBroaderHostname(hn); - } - } - } - - return toRegisterMap; -} - -function registerSpecificAll(context, specificDetails) { - const { filteringModeDetails, rulesetsDetails } = context; - const toRegisterMap = new Map(); - - const excludeSet = new Set([ - ...filteringModeDetails.network, - ...filteringModeDetails.none, - ]); - - for ( const rulesetDetails of rulesetsDetails ) { - const hostnamesToFidsMap = specificDetails.get(rulesetDetails.id); - if ( hostnamesToFidsMap === undefined ) { continue; } - for ( let [ hn, fids ] of hostnamesToFidsMap ) { - if ( excludeSet.has(hn) ) { continue; } - if ( ut.isDescendantHostnameOfIter(hn, excludeSet) ) { continue; } - if ( typeof fids === 'number' ) { fids = [ fids ]; } - for ( const fid of fids ) { - const fname = ut.fnameFromFileId(fid); - let existing = toRegisterMap.get(fname); - if ( existing ) { - if ( existing[0] === '*' ) { continue; } - existing.push(hn); - } else { - toRegisterMap.set(fname, existing = [ hn ]); - } - if ( hn !== '*' ) { continue; } - existing.length = 0; - existing.push('*'); - break; - } - } - } - - return toRegisterMap; -} - -const toRegisterableScript = (context, fname, hostnames) => { - if ( context.before.has(fname) ) { - return toUpdatableScript(context, fname, hostnames); - } - const matches = hostnames - ? ut.matchesFromHostnames(hostnames) - : [ '' ]; - const excludeMatches = matches.length === 1 && matches[0] === '' - ? ut.matchesFromHostnames(context.filteringModeDetails.none) - : []; - const directive = { - id: fname, - allFrames: true, - matches, - excludeMatches, - js: [ `/rulesets/scripting/specific/${fname.slice(-1)}/${fname.slice(0,-1)}.js` ], - runAt: 'document_start', - }; - context.toAdd.push(directive); -}; - -const toUpdatableScript = (context, fname, hostnames) => { - const registered = context.before.get(fname); - context.before.delete(fname); // Important! - const directive = { id: fname }; - const matches = hostnames - ? ut.matchesFromHostnames(hostnames) - : [ '' ]; - if ( arrayEq(registered.matches, matches) === false ) { - directive.matches = matches; - } - const excludeMatches = matches.length === 1 && matches[0] === '' - ? ut.matchesFromHostnames(context.filteringModeDetails.none) - : []; - if ( arrayEq(registered.excludeMatches, excludeMatches) === false ) { - directive.excludeMatches = excludeMatches; - } - if ( directive.matches || directive.excludeMatches ) { - context.toUpdate.push(directive); - } -}; - -/******************************************************************************/ - -function registerSpecificEntity(context) { - const { before, filteringModeDetails, rulesetsDetails } = context; - - const js = []; - for ( const details of rulesetsDetails ) { - if ( details.css.specific instanceof Object === false ) { continue; } - if ( details.css.specific.entityBased === 0 ) { continue; } - js.push(`/rulesets/scripting/specific-entity/${details.id}.js`); - } - - if ( js.length === 0 ) { return; } - - const matches = []; - const excludeMatches = []; - if ( filteringModeDetails.extendedGeneric.has('all-urls') ) { - excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.none)); - excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.network)); - excludeMatches.push(...ut.matchesFromHostnames(filteringModeDetails.extendedSpecific)); - matches.push(''); - } else { - matches.push( - ...ut.matchesFromHostnames(filteringModeDetails.extendedGeneric) - ); - } - - if ( matches.length === 0 ) { return; } - - js.push('/js/scripting/css-specific.entity.js'); - - const registered = before.get('css-specific.entity'); - before.delete('css-specific.entity'); // Important! - - // register - if ( registered === undefined ) { - context.toAdd.push({ - id: 'css-specific.entity', - js, - allFrames: true, - matches, - excludeMatches, - runAt: 'document_start', - }); - return; - } - - // update - const directive = { id: 'css-specific.entity' }; - if ( arrayEq(registered.js, js, false) === false ) { - directive.js = js; - } - if ( arrayEq(registered.matches, matches) === false ) { - directive.matches = matches; - } - if ( arrayEq(registered.excludeMatches, excludeMatches) === false ) { - directive.excludeMatches = excludeMatches; - } - if ( directive.js || directive.matches || directive.excludeMatches ) { - context.toUpdate.push(directive); - } -} - -/******************************************************************************/ - async function registerInjectables(origins) { void origins; @@ -644,19 +437,13 @@ async function registerInjectables(origins) { const [ filteringModeDetails, rulesetsDetails, - declarativeDetails, - proceduralDetails, scriptletDetails, - specificDetails, genericDetails, registered, ] = await Promise.all([ getFilteringModeDetails(), getEnabledRulesetsDetails(), - getDeclarativeDetails(), - getProceduralDetails(), getScriptletDetails(), - getSpecificDetails(), getGenericDetails(), browser.scripting.getRegisteredContentScripts(), ]); @@ -676,11 +463,10 @@ async function registerInjectables(origins) { toRemove, }; - registerDeclarative(context, declarativeDetails); - registerProcedural(context, proceduralDetails); + registerDeclarative(context); + registerProcedural(context); registerScriptlet(context, scriptletDetails); - registerSpecific(context, specificDetails); - registerSpecificEntity(context); + registerSpecific(context); registerGeneric(context, genericDetails); toRemove.push(...Array.from(before.keys())); diff --git a/platform/mv3/extension/js/scripting/css-declarative.js b/platform/mv3/extension/js/scripting/css-declarative.js index b0d70f642..71eca9b97 100644 --- a/platform/mv3/extension/js/scripting/css-declarative.js +++ b/platform/mv3/extension/js/scripting/css-declarative.js @@ -32,41 +32,77 @@ /******************************************************************************/ const declarativeImports = self.declarativeImports || []; +self.declarativeImports = undefined; delete self.declarativeImports; -const lookupSelectors = (hn, out) => { - for ( const { argsList, hostnamesMap } of declarativeImports ) { - let argsIndices = hostnamesMap.get(hn); - if ( argsIndices === undefined ) { continue; } - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - out.push(...details.a.map(json => JSON.parse(json))); - } - } -}; - -let hn; -try { hn = document.location.hostname; } catch(ex) { } -const selectors = []; -while ( hn ) { - lookupSelectors(hn, selectors); - if ( hn === '*' ) { break; } - const pos = hn.indexOf('.'); - if ( pos !== -1 ) { - hn = hn.slice(pos + 1); - } else { - hn = '*'; - } -} - -declarativeImports.length = 0; - /******************************************************************************/ +const hnParts = []; +try { hnParts.push(...document.location.hostname.split('.')); } +catch(ex) { } +const hnpartslen = hnParts.length; +if ( hnpartslen === 0 ) { return; } + +const selectors = []; + +for ( const { argsList, exceptionsMap, hostnamesMap, entitiesMap } of declarativeImports ) { + 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 ]; } + for ( const argsIndex of argsIndices ) { + if ( tonotdoIndices.includes(argsIndex) ) { continue; } + todoIndices.add(argsIndex); + } + }; + for ( let i = 0; i < hnpartslen; i++ ) { + const hn = hnParts.slice(i).join('.'); + collectArgIndices(hn); + } + 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); + } + } + } + entitiesMap.clear(); + } + for ( const i of todoIndices ) { + selectors.push(...argsList[i].map(json => JSON.parse(json))); + } + argsList.length = 0; +} +declarativeImports.length = 0; + if ( selectors.length === 0 ) { return; } +/******************************************************************************/ + const cssRuleFromProcedural = details => { const { tasks, action } = details; let mq; @@ -114,3 +150,5 @@ try { })(); /******************************************************************************/ + +void 0; diff --git a/platform/mv3/extension/js/scripting/css-procedural.js b/platform/mv3/extension/js/scripting/css-procedural.js index 4068023ba..b52cc209b 100644 --- a/platform/mv3/extension/js/scripting/css-procedural.js +++ b/platform/mv3/extension/js/scripting/css-procedural.js @@ -31,7 +31,75 @@ /******************************************************************************/ -let proceduralFilterer; +const proceduralImports = self.proceduralImports || []; +self.proceduralImports = undefined; +delete self.proceduralImports; + +/******************************************************************************/ + +const hnParts = []; +try { hnParts.push(...document.location.hostname.split('.')); } +catch(ex) { } +const hnpartslen = hnParts.length; +if ( hnpartslen === 0 ) { return; } + +const selectors = []; + +for ( const { argsList, exceptionsMap, hostnamesMap, entitiesMap } of proceduralImports ) { + 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 ]; } + for ( const argsIndex of argsIndices ) { + if ( tonotdoIndices.includes(argsIndex) ) { continue; } + todoIndices.add(argsIndex); + } + }; + for ( let i = 0; i < hnpartslen; i++ ) { + const hn = hnParts.slice(i).join('.'); + collectArgIndices(hn); + } + 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); + } + } + } + entitiesMap.clear(); + } + for ( const i of todoIndices ) { + selectors.push(...argsList[i].map(json => JSON.parse(json))); + } + argsList.length = 0; +} +proceduralImports.length = 0; + +if ( selectors.length === 0 ) { return; } /******************************************************************************/ @@ -658,43 +726,7 @@ class ProceduralFilterer { /******************************************************************************/ -const proceduralImports = self.proceduralImports || []; -delete self.proceduralImports; - -const lookupSelectors = (hn, out) => { - for ( const { argsList, hostnamesMap } of proceduralImports ) { - let argsIndices = hostnamesMap.get(hn); - if ( argsIndices === undefined ) { continue; } - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - out.push(...details.a.map(json => JSON.parse(json))); - } - } -}; - -let hn; -try { hn = document.location.hostname; } catch(ex) { } -const selectors = []; -while ( hn ) { - lookupSelectors(hn, selectors); - if ( hn === '*' ) { break; } - const pos = hn.indexOf('.'); - if ( pos !== -1 ) { - hn = hn.slice(pos + 1); - } else { - hn = '*'; - } -} - -proceduralImports.length = 0; - -/******************************************************************************/ - -if ( selectors.length === 0 ) { return; } - -proceduralFilterer = new ProceduralFilterer(selectors); +const proceduralFilterer = new ProceduralFilterer(selectors); const observer = new MutationObserver(mutations => { let domChanged = false; @@ -727,3 +759,5 @@ observer.observe(document, { })(); /******************************************************************************/ + +void 0; diff --git a/platform/mv3/extension/js/scripting/css-specific.entity.js b/platform/mv3/extension/js/scripting/css-specific.entity.js deleted file mode 100644 index 9b38d31e6..000000000 --- a/platform/mv3/extension/js/scripting/css-specific.entity.js +++ /dev/null @@ -1,85 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2019-present Raymond Hill - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see {http://www.gnu.org/licenses/}. - - Home: https://github.com/gorhill/uBlock -*/ - -/* jshint esversion:11 */ - -'use strict'; - -/******************************************************************************/ - -// Important! -// Isolate from global scope -(function uBOL_cssSpecificEntity() { - -/******************************************************************************/ - -// $rulesetId$ - -const specificEntityImports = self.specificEntityImports || []; -delete self.specificEntityImports; - -/******************************************************************************/ - -const lookupSelectors = (hn, entity, out) => { - for ( const { argsList, entitiesMap } of specificEntityImports ) { - let argsIndices = entitiesMap.get(entity); - if ( argsIndices === undefined ) { continue; } - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - out.push(details.a); - } - } -}; - -let hn = ''; -try { hn = document.location.hostname; } catch(ex) { } -const selectors = []; -const hnparts = hn.split('.'); -const hnpartslen = hnparts.length - 1; -for ( let i = 0; i < hnpartslen; i++ ) { - for ( let j = hnpartslen; j > i; j-- ) { - lookupSelectors( - hnparts.slice(i).join('.'), - hnparts.slice(i,j).join('.'), - selectors - ); - } -} - -if ( selectors.length === 0 ) { return; } - -try { - const sheet = new CSSStyleSheet(); - sheet.replace(`@layer{${selectors.join(',')}{display:none!important;}}`); - document.adoptedStyleSheets = [ - ...document.adoptedStyleSheets, - sheet - ]; -} catch(ex) { -} - -/******************************************************************************/ - -})(); - -/******************************************************************************/ diff --git a/platform/mv3/extension/js/scripting/css-specific.js b/platform/mv3/extension/js/scripting/css-specific.js new file mode 100644 index 000000000..0997385fb --- /dev/null +++ b/platform/mv3/extension/js/scripting/css-specific.js @@ -0,0 +1,122 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +/* jshint esversion:11 */ + +'use strict'; + +/******************************************************************************/ + +// Important! +// Isolate from global scope +(function uBOL_cssSpecific() { + +/******************************************************************************/ + +const specificImports = self.specificImports || []; +self.specificImports = undefined; +delete self.specificImports; + +/******************************************************************************/ + +const hnParts = []; +try { hnParts.push(...document.location.hostname.split('.')); } +catch(ex) { } +const hnpartslen = hnParts.length; +if ( hnpartslen === 0 ) { return; } + +const selectors = []; + +for ( const { argsList, exceptionsMap, hostnamesMap, entitiesMap } of specificImports ) { + 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 ]; } + for ( const argsIndex of argsIndices ) { + if ( tonotdoIndices.includes(argsIndex) ) { continue; } + todoIndices.add(argsIndex); + } + }; + for ( let i = 0; i < hnpartslen; i++ ) { + const hn = hnParts.slice(i).join('.'); + collectArgIndices(hn); + } + 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); + } + } + } + entitiesMap.clear(); + } + for ( const i of todoIndices ) { + selectors.push(argsList[i]); + } + argsList.length = 0; +} +specificImports.length = 0; + +if ( selectors.length === 0 ) { return; } + +/******************************************************************************/ + +try { + const sheet = new CSSStyleSheet(); + sheet.replace(`@layer{${selectors.join(',')}{display:none!important;}}`); + document.adoptedStyleSheets = [ + ...document.adoptedStyleSheets, + sheet + ]; +} catch(ex) { +} + +/******************************************************************************/ + +})(); + +/******************************************************************************/ + +void 0; diff --git a/platform/mv3/extension/js/settings.js b/platform/mv3/extension/js/settings.js index 7772bf972..5008253d3 100644 --- a/platform/mv3/extension/js/settings.js +++ b/platform/mv3/extension/js/settings.js @@ -163,7 +163,7 @@ function renderFilterLists(soft = false) { // DOM list entries. dom.cl.add('#lists .listEntries .listEntry[data-listkey]', 'discard'); - // Visually split the filter lists in three groups + // Visually split the filter lists in groups const ulLists = qs$('#lists'); const groups = new Map([ [ @@ -172,10 +172,18 @@ function renderFilterLists(soft = false) { ruleset.id === 'default' ), ], + [ + 'annoyances', + rulesetDetails.filter(ruleset => + ruleset.group === 'annoyances' + ), + ], [ 'misc', rulesetDetails.filter(ruleset => - ruleset.id !== 'default' && typeof ruleset.lang !== 'string' + ruleset.id !== 'default' && + ruleset.group === undefined && + typeof ruleset.lang !== 'string' ), ], [ diff --git a/platform/mv3/extension/js/utils.js b/platform/mv3/extension/js/utils.js index 7d012ad69..bf3fdcf87 100644 --- a/platform/mv3/extension/js/utils.js +++ b/platform/mv3/extension/js/utils.js @@ -119,14 +119,6 @@ const hostnamesFromMatches = origins => { /******************************************************************************/ -const fnameFromFileId = fid => - fid.toString(32).padStart(7, '0'); - -const fidFromFileName = fname => - parseInt(fname, 32); - -/******************************************************************************/ - export { parsedURLromOrigin, toBroaderHostname, @@ -136,6 +128,4 @@ export { subtractHostnameIters, matchesFromHostnames, hostnamesFromMatches, - fnameFromFileId, - fidFromFileName, }; diff --git a/platform/mv3/make-rulesets.js b/platform/mv3/make-rulesets.js index 7e615eb24..83c0fe294 100644 --- a/platform/mv3/make-rulesets.js +++ b/platform/mv3/make-rulesets.js @@ -30,7 +30,6 @@ import process from 'process'; import { createHash } from 'crypto'; import redirectResourcesMap from './js/redirect-resources.js'; import { dnrRulesetFromRawLists } from './js/static-dnr-filtering.js'; -import { fnameFromFileId } from './js/utils.js'; import * as sfp from './js/static-filtering-parser.js'; import * as makeScriptlet from './make-scriptlets.js'; @@ -158,10 +157,7 @@ const writeOps = []; const ruleResources = []; const rulesetDetails = []; -const declarativeDetails = new Map(); -const proceduralDetails = new Map(); const scriptletStats = new Map(); -const specificDetails = new Map(); const genericDetails = new Map(); const requiredRedirectResources = new Set(); @@ -403,45 +399,12 @@ function loadAllSourceScriptlets() { /******************************************************************************/ -const globalPatchedScriptletsSet = new Set(); - -function addScriptingAPIResources(id, hostnames, fid) { - if ( hostnames === undefined ) { return; } - for ( const hn of hostnames ) { - let hostnamesToFidMap = specificDetails.get(id); - if ( hostnamesToFidMap === undefined ) { - hostnamesToFidMap = new Map(); - specificDetails.set(id, hostnamesToFidMap); - } - let fids = hostnamesToFidMap.get(hn); - if ( fids === undefined ) { - hostnamesToFidMap.set(hn, fid); - } else if ( fids instanceof Set ) { - fids.add(fid); - } else if ( fid !== fids ) { - fids = new Set([ fids, fid ]); - hostnamesToFidMap.set(hn, fids); - } - } -} - -const toCSSSpecific = s => (uidint32(s) & ~0b11) | 0b00; - -const pathFromFileName = fname => `${fname.slice(-1)}/${fname.slice(0,-1)}.js`; - -/******************************************************************************/ - async function processGenericCosmeticFilters(assetDetails, bucketsMap, exclusions) { - const out = { - count: 0, - exclusionCount: 0, - }; - if ( bucketsMap === undefined ) { return out; } - if ( bucketsMap.size === 0 ) { return out; } + if ( bucketsMap === undefined ) { return 0; } + if ( bucketsMap.size === 0 ) { return 0; } const bucketsList = Array.from(bucketsMap); const count = bucketsList.reduce((a, v) => a += v[1].length, 0); - if ( count === 0 ) { return out; } - out.count = count; + if ( count === 0 ) { return 0; } const selectorLists = bucketsList.map(v => [ v[0], v[1].join(',') ]); const originalScriptletMap = await loadAllSourceScriptlets(); @@ -464,13 +427,11 @@ async function processGenericCosmeticFilters(assetDetails, bucketsMap, exclusion log(`CSS-generic: ${count} plain CSS selectors`); - return out; + return count; } /******************************************************************************/ -const MAX_COSMETIC_FILTERS_PER_FILE = 256; - // This merges selectors which are used by the same hostnames function groupSelectorsByHostnames(mapin) { @@ -588,52 +549,13 @@ function argsMap2List(argsMap, hostnamesMap) { /******************************************************************************/ -function splitDomainAndEntity(mapin) { - const domainBased = new Map(); - const entityBased = new Map(); - for ( const [ selector, domainDetails ] of mapin ) { - domainBased.set(selector, domainDetails); - if ( domainDetails.rejected ) { continue; } - if ( Array.isArray(domainDetails.matches) === false ) { continue; } - const domainMatches = []; - const entityMatches = []; - for ( const hn of domainDetails.matches ) { - if ( hn.endsWith('.*') ) { - entityMatches.push(hn.slice(0, -2)); - } else { - domainMatches.push(hn); - } - } - if ( entityMatches.length === 0 ) { continue; } - if ( domainMatches.length !== 0 ) { - domainDetails.matches = domainMatches; - } else { - domainBased.delete(selector); - } - const entityDetails = { - matches: entityMatches, - }; - if ( Array.isArray(domainDetails.excludeMatches) ) { - entityDetails.excludeMatches = domainDetails.excludeMatches.slice(); - } - entityBased.set(selector, entityDetails); - } - return { domainBased, entityBased }; -} - -/******************************************************************************/ - async function processCosmeticFilters(assetDetails, mapin) { - if ( mapin === undefined ) { return; } + if ( mapin === undefined ) { return 0; } + if ( mapin.size === 0 ) { return 0; } - const { domainBased, entityBased } = splitDomainAndEntity(mapin); - const entityBasedEntries = groupHostnamesBySelectors( - groupSelectorsByHostnames(entityBased) - ); const domainBasedEntries = groupHostnamesBySelectors( - groupSelectorsByHostnames(domainBased) + groupSelectorsByHostnames(mapin) ); - // We do not want more than n CSS files per subscription, so we will // group multiple unrelated selectors in the same file, and distinct // css declarations will be injected programmatically according to the @@ -645,89 +567,68 @@ async function processCosmeticFilters(assetDetails, mapin) { const originalScriptletMap = await loadAllSourceScriptlets(); const generatedFiles = []; - for ( let i = 0; i < domainBasedEntries.length; i += MAX_COSMETIC_FILTERS_PER_FILE ) { - const slice = domainBasedEntries.slice(i, i + MAX_COSMETIC_FILTERS_PER_FILE); - const argsMap = slice.map(entry => [ - entry[0], - { - a: entry[1].a ? entry[1].a.join(',\n') : undefined, - n: entry[1].n - } - ]); - const hostnamesMap = new Map(); - for ( const [ id, details ] of slice ) { - if ( details.y === undefined ) { continue; } - scriptletHostnameToIdMap(details.y, id, hostnamesMap); - } - const argsList = argsMap2List(argsMap, hostnamesMap); - const patchedScriptlet = originalScriptletMap.get('css-specific') - .replace( - '$rulesetId$', - assetDetails.id - ).replace( - /\bself\.\$argsList\$/m, - `${JSON.stringify(argsList, scriptletJsonReplacer)}` - ).replace( - /\bself\.\$hostnamesMap\$/m, - `${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}` - ); - const fid = toCSSSpecific(patchedScriptlet); - if ( globalPatchedScriptletsSet.has(fid) === false ) { - globalPatchedScriptletsSet.add(fid); - const fname = fnameFromFileId(fid); - writeFile(`${scriptletDir}/specific/${pathFromFileName(fname)}`, patchedScriptlet); - generatedFiles.push(fname); - } - for ( const entry of slice ) { - addScriptingAPIResources(assetDetails.id, entry[1].y, fid); + const argsMap = domainBasedEntries.map(entry => [ + entry[0], + { + a: entry[1].a ? entry[1].a.join(',\n') : undefined, + n: entry[1].n } + ]); + const hostnamesMap = new Map(); + for ( const [ id, details ] of domainBasedEntries ) { + if ( details.y === undefined ) { continue; } + scriptletHostnameToIdMap(details.y, id, hostnamesMap); + } + const argsList = argsMap2List(argsMap, hostnamesMap); + const entitiesMap = new Map(); + for ( const [ hn, details ] of hostnamesMap ) { + if ( hn.endsWith('.*') === false ) { continue; } + hostnamesMap.delete(hn); + entitiesMap.set(hn.slice(0, -2), details); } - // For entity-based entries, we generate a single scriptlet which will be - // injected only in Complete mode. - if ( entityBasedEntries.length !== 0 ) { - const argsMap = entityBasedEntries.map(entry => [ - entry[0], - { - a: entry[1].a ? entry[1].a.join(',') : undefined, - n: entry[1].n, + // Extract exceptions from argsList, simplify argsList entries + const exceptionsMap = new Map(); + for ( let i = 0; i < argsList.length; i++ ) { + const details = argsList[i]; + if ( details.n ) { + for ( const hn of details.n ) { + if ( exceptionsMap.has(hn) === false ) { + exceptionsMap.set(hn, []); + } + exceptionsMap.get(hn).push(i); } - ]); - const entitiesMap = new Map(); - for ( const [ id, details ] of entityBasedEntries ) { - if ( details.y === undefined ) { continue; } - scriptletHostnameToIdMap(details.y, id, entitiesMap); } - const argsList = argsMap2List(argsMap, entitiesMap); - const patchedScriptlet = originalScriptletMap.get('css-specific.entity') - .replace( - '$rulesetId$', - assetDetails.id - ).replace( - /\bself\.\$argsList\$/m, - `${JSON.stringify(argsList, scriptletJsonReplacer)}` - ).replace( - /\bself\.\$entitiesMap\$/m, - `${JSON.stringify(entitiesMap, scriptletJsonReplacer)}` - ); - const fname = `${assetDetails.id}`; - writeFile(`${scriptletDir}/specific-entity/${fname}.js`, patchedScriptlet); - generatedFiles.push(fname); + argsList[i] = details.a; } + const patchedScriptlet = originalScriptletMap.get('css-specific') + .replace( + '$rulesetId$', + assetDetails.id + ).replace( + /\bself\.\$argsList\$/m, + `${JSON.stringify(argsList, scriptletJsonReplacer)}` + ).replace( + /\bself\.\$hostnamesMap\$/m, + `${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}` + ).replace( + /\bself\.\$entitiesMap\$/m, + `${JSON.stringify(entitiesMap, scriptletJsonReplacer)}` + ).replace( + /\bself\.\$exceptionsMap\$/m, + `${JSON.stringify(exceptionsMap, scriptletJsonReplacer)}` + ); + writeFile(`${scriptletDir}/specific/${assetDetails.id}.js`, patchedScriptlet); + generatedFiles.push(`${assetDetails.id}`); + if ( generatedFiles.length !== 0 ) { - log(`CSS-specific domain-based: ${domainBased.size} distinct filters`); - log(`\tCombined into ${domainBasedEntries.length} distinct entries`); - log(`CSS-specific entity-based: ${entityBased.size} distinct filters`); - log(`\tCombined into ${entityBasedEntries.length} distinct entries`); - log(`CSS-specific injectable files: ${generatedFiles.length}`); - log(`\t${generatedFiles.join(', ')}`); + log(`CSS-specific: ${mapin.size} distinct filters`); + log(`\tCombined into ${hostnamesMap.size} distinct hostnames`); + log(`\tCombined into ${entitiesMap.size} distinct entities`); } - return { - domainBased: domainBasedEntries.length, - entityBased: entityBasedEntries.length, - }; + return hostnamesMap.size + entitiesMap.size; } /******************************************************************************/ @@ -761,8 +662,29 @@ async function processDeclarativeCosmeticFilters(assetDetails, mapin) { if ( details.y === undefined ) { continue; } scriptletHostnameToIdMap(details.y, id, hostnamesMap); } - const argsList = argsMap2List(argsMap, hostnamesMap); + const entitiesMap = new Map(); + for ( const [ hn, details ] of hostnamesMap ) { + if ( hn.endsWith('.*') === false ) { continue; } + hostnamesMap.delete(hn); + entitiesMap.set(hn.slice(0, -2), details); + } + + // Extract exceptions from argsList, simplify argsList entries + const exceptionsMap = new Map(); + for ( let i = 0; i < argsList.length; i++ ) { + const details = argsList[i]; + if ( details.n ) { + for ( const hn of details.n ) { + if ( exceptionsMap.has(hn) === false ) { + exceptionsMap.set(hn, []); + } + exceptionsMap.get(hn).push(i); + } + } + argsList[i] = details.a; + } + const originalScriptletMap = await loadAllSourceScriptlets(); const patchedScriptlet = originalScriptletMap.get('css-declarative') .replace( @@ -774,29 +696,22 @@ async function processDeclarativeCosmeticFilters(assetDetails, mapin) { ).replace( /\bself\.\$hostnamesMap\$/m, `${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}` + ).replace( + /\bself\.\$entitiesMap\$/m, + `${JSON.stringify(entitiesMap, scriptletJsonReplacer)}` + ).replace( + /\bself\.\$exceptionsMap\$/m, + `${JSON.stringify(exceptionsMap, scriptletJsonReplacer)}` ); writeFile(`${scriptletDir}/declarative/${assetDetails.id}.js`, patchedScriptlet); - { - const hostnames = new Set(); - for ( const entry of contentArray ) { - if ( Array.isArray(entry[1].y) === false ) { continue; } - for ( const hn of entry[1].y ) { - hostnames.add(hn); - } - } - if ( hostnames.has('*') ) { - hostnames.clear(); - hostnames.add('*'); - } - declarativeDetails.set(assetDetails.id, Array.from(hostnames).sort()); - } - if ( contentArray.length !== 0 ) { - log(`Declarative-related distinct filters: ${contentArray.length} distinct combined selectors`); + log(`CSS-declarative: ${declaratives.size} distinct filters`); + log(`\tCombined into ${hostnamesMap.size} distinct hostnames`); + log(`\tCombined into ${entitiesMap.size} distinct entities`); } - return contentArray.length; + return hostnamesMap.size + entitiesMap.size; } /******************************************************************************/ @@ -830,8 +745,29 @@ async function processProceduralCosmeticFilters(assetDetails, mapin) { if ( details.y === undefined ) { continue; } scriptletHostnameToIdMap(details.y, id, hostnamesMap); } - const argsList = argsMap2List(argsMap, hostnamesMap); + const entitiesMap = new Map(); + for ( const [ hn, details ] of hostnamesMap ) { + if ( hn.endsWith('.*') === false ) { continue; } + hostnamesMap.delete(hn); + entitiesMap.set(hn.slice(0, -2), details); + } + + // Extract exceptions from argsList, simplify argsList entries + const exceptionsMap = new Map(); + for ( let i = 0; i < argsList.length; i++ ) { + const details = argsList[i]; + if ( details.n ) { + for ( const hn of details.n ) { + if ( exceptionsMap.has(hn) === false ) { + exceptionsMap.set(hn, []); + } + exceptionsMap.get(hn).push(i); + } + } + argsList[i] = details.a; + } + const originalScriptletMap = await loadAllSourceScriptlets(); const patchedScriptlet = originalScriptletMap.get('css-procedural') .replace( @@ -843,35 +779,29 @@ async function processProceduralCosmeticFilters(assetDetails, mapin) { ).replace( /\bself\.\$hostnamesMap\$/m, `${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}` + ).replace( + /\bself\.\$entitiesMap\$/m, + `${JSON.stringify(entitiesMap, scriptletJsonReplacer)}` + ).replace( + /\bself\.\$exceptionsMap\$/m, + `${JSON.stringify(exceptionsMap, scriptletJsonReplacer)}` ); writeFile(`${scriptletDir}/procedural/${assetDetails.id}.js`, patchedScriptlet); - { - const hostnames = new Set(); - for ( const entry of contentArray ) { - if ( Array.isArray(entry[1].y) === false ) { continue; } - for ( const hn of entry[1].y ) { - hostnames.add(hn); - } - } - if ( hostnames.has('*') ) { - hostnames.clear(); - hostnames.add('*'); - } - proceduralDetails.set(assetDetails.id, Array.from(hostnames).sort()); - } - if ( contentArray.length !== 0 ) { - log(`Procedural-related distinct filters: ${contentArray.length} distinct combined selectors`); + log(`Procedural-related distinct filters: ${procedurals.size} distinct combined selectors`); + log(`\tCombined into ${hostnamesMap.size} distinct hostnames`); + log(`\tCombined into ${entitiesMap.size} distinct entities`); } - return contentArray.length; + return hostnamesMap.size + entitiesMap.size; } /******************************************************************************/ async function processScriptletFilters(assetDetails, mapin) { - if ( mapin === undefined ) { return; } + if ( mapin === undefined ) { return 0; } + if ( mapin.size === 0 ) { return 0; } makeScriptlet.init(); @@ -887,7 +817,7 @@ async function processScriptletFilters(assetDetails, mapin) { scriptletStats.set(assetDetails.id, stats); } makeScriptlet.reset(); - return { domainBasedTokens: stats.length }; + return stats.length; } /******************************************************************************/ @@ -942,13 +872,6 @@ async function rulesetFromURLs(assetDetails) { continue; } const parsed = JSON.parse(selector); - const matches = - details.matches.filter(hn => hn.endsWith('.*') === false); - if ( matches.length === 0 ) { - rejectedCosmetic.push(`Entity-based filter not supported: ${parsed.raw}`); - continue; - } - details.matches = matches; parsed.raw = undefined; proceduralCosmetic.set(JSON.stringify(parsed), details); } @@ -983,6 +906,7 @@ async function rulesetFromURLs(assetDetails) { rulesetDetails.push({ id: assetDetails.id, name: assetDetails.name, + group: assetDetails.group, enabled: assetDetails.enabled, lang: assetDetails.lang, homeURL: assetDetails.homeURL, @@ -1110,7 +1034,11 @@ async function main() { } // Handpicked rulesets from assets.json - const handpicked = [ 'block-lan', 'dpollock-0', 'adguard-spyware-url' ]; + const handpicked = [ + 'block-lan', + 'dpollock-0', + 'adguard-spyware-url', + ]; for ( const id of handpicked ) { const asset = assets[id]; if ( asset.content !== 'filters' ) { continue; } @@ -1127,6 +1055,33 @@ async function main() { }); } + // Handpicked annoyance rulesets from assets.json + await rulesetFromURLs({ + id: 'easylist-cookies', + name: 'EasyList – Cookies Notices' , + group: 'annoyances', + enabled: false, + urls: [ + 'https://ublockorigin.github.io/uAssets/thirdparties/easylist-cookies.txt', + ], + homeURL: 'https://github.com/uBlockOrigin/uAssets', + }); + await rulesetFromURLs({ + id: 'easylist-annoyances', + name: 'EasyList – Annoyances' , + group: 'annoyances', + enabled: false, + urls: [ + 'https://ublockorigin.github.io/uAssets/thirdparties/easylist-annoyances.txt', + 'https://ublockorigin.github.io/uAssets/thirdparties/easylist-chat.txt', + 'https://ublockorigin.github.io/uAssets/thirdparties/easylist-newsletters.txt', + 'https://ublockorigin.github.io/uAssets/thirdparties/easylist-notifications.txt', + 'https://ublockorigin.github.io/uAssets/thirdparties/easylist-social.txt', + 'https://ublockorigin.github.io/uAssets/filters/annoyances.txt', + ], + homeURL: 'https://github.com/uBlockOrigin/uAssets', + }); + // Handpicked rulesets from abroad await rulesetFromURLs({ id: 'stevenblack-hosts', @@ -1141,29 +1096,6 @@ async function main() { `${JSON.stringify(rulesetDetails, null, 1)}\n` ); - // We sort the hostnames for convenience/performance in the extension's - // script manager -- the scripting API does a sort() internally. - for ( const [ rulesetId, hostnamesToFidsMap ] of specificDetails ) { - specificDetails.set( - rulesetId, - Array.from(hostnamesToFidsMap).sort() - ); - } - writeFile( - `${rulesetDir}/specific-details.json`, - `${JSON.stringify(specificDetails, jsonSetMapReplacer)}\n` - ); - - writeFile( - `${rulesetDir}/declarative-details.json`, - `${JSON.stringify(declarativeDetails, jsonSetMapReplacer, 1)}\n` - ); - - writeFile( - `${rulesetDir}/procedural-details.json`, - `${JSON.stringify(proceduralDetails, jsonSetMapReplacer, 1)}\n` - ); - writeFile( `${rulesetDir}/scriptlet-details.json`, `${JSON.stringify(scriptletStats, jsonSetMapReplacer, 1)}\n` diff --git a/platform/mv3/scriptlet.template.js b/platform/mv3/scriptlet.template.js index aa3a6e1e0..b6859f5af 100644 --- a/platform/mv3/scriptlet.template.js +++ b/platform/mv3/scriptlet.template.js @@ -99,7 +99,7 @@ if ( entitiesMap.size !== 0 ) { if ( argsIndices === undefined ) { continue; } if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } for ( const argsIndex of argsIndices ) { - if ( tonotdoIndices(argsIndex) ) { continue; } + if ( tonotdoIndices.includes(argsIndex) ) { continue; } todoIndices.add(argsIndex); } } diff --git a/platform/mv3/scriptlets/css-declarative.js b/platform/mv3/scriptlets/css-declarative.js index e5a342b5d..38eaad495 100644 --- a/platform/mv3/scriptlets/css-declarative.js +++ b/platform/mv3/scriptlets/css-declarative.js @@ -23,6 +23,8 @@ 'use strict'; +// ruleset: $rulesetId$ + /******************************************************************************/ /// name css-declarative @@ -35,14 +37,16 @@ /******************************************************************************/ -// $rulesetId$ - const argsList = self.$argsList$; const hostnamesMap = new Map(self.$hostnamesMap$); +const entitiesMap = new Map(self.$entitiesMap$); + +const exceptionsMap = new Map(self.$exceptionsMap$); + self.declarativeImports = self.declarativeImports || []; -self.declarativeImports.push({ argsList, hostnamesMap }); +self.declarativeImports.push({ argsList, hostnamesMap, entitiesMap, exceptionsMap }); /******************************************************************************/ diff --git a/platform/mv3/scriptlets/css-procedural.js b/platform/mv3/scriptlets/css-procedural.js index 252b335a2..81df35c2c 100644 --- a/platform/mv3/scriptlets/css-procedural.js +++ b/platform/mv3/scriptlets/css-procedural.js @@ -23,6 +23,8 @@ 'use strict'; +// ruleset: $rulesetId$ + /******************************************************************************/ /// name css-procedural @@ -35,14 +37,16 @@ /******************************************************************************/ -// $rulesetId$ - const argsList = self.$argsList$; const hostnamesMap = new Map(self.$hostnamesMap$); +const entitiesMap = new Map(self.$entitiesMap$); + +const exceptionsMap = new Map(self.$exceptionsMap$); + self.proceduralImports = self.proceduralImports || []; -self.proceduralImports.push({ argsList, hostnamesMap }); +self.proceduralImports.push({ argsList, hostnamesMap, entitiesMap, exceptionsMap }); /******************************************************************************/ diff --git a/platform/mv3/scriptlets/css-specific.js b/platform/mv3/scriptlets/css-specific.js index 87341ccc9..ffc871b77 100644 --- a/platform/mv3/scriptlets/css-specific.js +++ b/platform/mv3/scriptlets/css-specific.js @@ -23,6 +23,8 @@ 'use strict'; +// ruleset: $rulesetId$ + /******************************************************************************/ /// name css-specific @@ -31,54 +33,20 @@ // Important! // Isolate from global scope -(function uBOL_cssSpecific() { +(function uBOL_cssSpecificImports() { /******************************************************************************/ -// $rulesetId$ - const argsList = self.$argsList$; const hostnamesMap = new Map(self.$hostnamesMap$); -/******************************************************************************/ +const entitiesMap = new Map(self.$entitiesMap$); -let hn; -try { hn = document.location.hostname; } catch(ex) { } -const styles = []; -while ( hn ) { - if ( hostnamesMap.has(hn) ) { - let argsIndices = hostnamesMap.get(hn); - if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; } - for ( const argsIndex of argsIndices ) { - const details = argsList[argsIndex]; - if ( details.n && details.n.includes(hn) ) { continue; } - styles.push(details.a); - } - } - if ( hn === '*' ) { break; } - const pos = hn.indexOf('.'); - if ( pos !== -1 ) { - hn = hn.slice(pos + 1); - } else { - hn = '*'; - } -} +const exceptionsMap = new Map(self.$exceptionsMap$); -argsList.length = 0; -hostnamesMap.clear(); - -if ( styles.length === 0 ) { return; } - -try { - const sheet = new CSSStyleSheet(); - sheet.replace(`@layer{${styles.join(',')}{display:none!important;}}`); - document.adoptedStyleSheets = [ - ...document.adoptedStyleSheets, - sheet - ]; -} catch(ex) { -} +self.specificImports = self.specificImports || []; +self.specificImports.push({ argsList, hostnamesMap, entitiesMap, exceptionsMap }); /******************************************************************************/