diff --git a/src/js/contentscript-extra.js b/src/js/contentscript-extra.js index ff8c03730..658fd5c5a 100644 --- a/src/js/contentscript-extra.js +++ b/src/js/contentscript-extra.js @@ -415,6 +415,13 @@ class PSelectorRoot extends PSelector { this.lastAllowanceTime = 0; this.styleToken = styleToken; } + prime(input) { + try { + return super.prime(input); + } catch (ex) { + } + return []; + } } PSelectorRoot.prototype.hit = false; @@ -534,7 +541,7 @@ class ProceduralFilterer { // TODO: Current assumption is one style per hit element. Could be an // issue if an element has multiple styling and one styling is - // brough back. Possibly too rare to care about this for now. + // brought back. Possibly too rare to care about this for now. unstyleNodes(nodes) { for ( const node of nodes ) { if ( this.styledNodes.has(node) ) { continue; } @@ -543,7 +550,7 @@ class ProceduralFilterer { } createProceduralFilter(o) { - return new PSelectorRoot(o); + return new PSelectorRoot(typeof o === 'string' ? JSON.parse(o) : o); } onDOMCreated() { diff --git a/src/js/contentscript.js b/src/js/contentscript.js index c2d5661b0..cb75f8cc3 100644 --- a/src/js/contentscript.js +++ b/src/js/contentscript.js @@ -501,6 +501,7 @@ vAPI.DOMFilterer = class { this.stylesheets = []; this.exceptedCSSRules = []; this.exceptions = []; + this.convertedProceduralFilters = []; this.proceduralFilterer = null; // https://github.com/uBlockOrigin/uBlock-issues/issues/167 // By the time the DOMContentLoaded is fired, the content script might @@ -518,11 +519,11 @@ vAPI.DOMFilterer = class { explodeCSS(css) { const out = []; - const reBlock = /^\{(.*)\}$/m; + const cssHide = `{${vAPI.hideStyle}}`; const blocks = css.trim().split(/\n\n+/); for ( const block of blocks ) { - const match = reBlock.exec(block); - out.push([ block.slice(0, match.index).trim(), match[1] ]); + if ( block.endsWith(cssHide) === false ) { continue; } + out.push(block.slice(0, -cssHide.length).trim()); } return out; } @@ -621,9 +622,6 @@ vAPI.DOMFilterer = class { } addProceduralSelectors(selectors) { - if ( Array.isArray(selectors) === false || selectors.length === 0 ) { - return; - } const procedurals = []; for ( const raw of selectors ) { procedurals.push(JSON.parse(raw)); @@ -652,23 +650,30 @@ vAPI.DOMFilterer = class { ? `[${this.proceduralFilterer.masterToken}]` : undefined; for ( const css of this.stylesheets ) { - const blocks = this.explodeCSS(css); - for ( const block of blocks ) { + for ( const block of this.explodeCSS(css) ) { if ( includePrivateSelectors === false && masterToken !== undefined && - block[0].startsWith(masterToken) + block.startsWith(masterToken) ) { continue; } - out.declarative.push([ block[0], block[1] ]); + out.declarative.push(block); } } const excludeProcedurals = (bits & 0b10) !== 0; - if ( excludeProcedurals !== true ) { - out.procedural = hasProcedural - ? Array.from(this.proceduralFilterer.selectors.values()) - : []; + if ( excludeProcedurals === false ) { + out.procedural = []; + if ( hasProcedural ) { + out.procedural.push( + ...this.proceduralFilterer.selectors.values() + ); + } + for ( const json of this.convertedProceduralFilters ) { + out.procedural.push( + this.proceduralFiltererInstance().createProceduralFilter(json) + ); + } } return out; } @@ -1319,6 +1324,7 @@ vAPI.DOMFilterer = class { domFilterer.addCSS(cfeDetails.injectedCSS); domFilterer.addProceduralSelectors(cfeDetails.proceduralFilters); domFilterer.exceptCSSRules(cfeDetails.exceptedFilters); + domFilterer.convertedProceduralFilters = cfeDetails.convertedProceduralFilters; } vAPI.userStylesheet.apply(); diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js index f3bbde054..1aaac401b 100644 --- a/src/js/cosmetic-filtering.js +++ b/src/js/cosmetic-filtering.js @@ -823,6 +823,32 @@ FilterContainer.prototype.getSession = function() { /******************************************************************************/ +FilterContainer.prototype.cssRuleFromProcedural = function(json) { + const pfilter = JSON.parse(json); + if ( pfilter.cssable !== true ) { return; } + const { tasks, action } = pfilter; + let mq; + if ( tasks !== undefined && tasks.length === 1 ) { + if ( tasks[0][0] !== ':matches-media' ) { return; } + mq = tasks[0][1]; + } + let style; + if ( Array.isArray(action) ) { + if ( action[0] !== ':style' ) { return; } + style = action[1]; + } + if ( mq === undefined && style === undefined ) { return; } + if ( mq === undefined ) { + return `${pfilter.selector}\n{${style}}`; + } + if ( style === undefined ) { + return `@media ${mq} {\n${pfilter.selector}\n{display:none!important;}\n}`; + } + return `@media ${mq} {\n${pfilter.selector}\n{${style}}\n}`; +}; + +/******************************************************************************/ + FilterContainer.prototype.retrieveGenericSelectors = function(request) { if ( this.acceptedCount === 0 ) { return; } if ( !request.ids && !request.classes ) { return; } @@ -944,6 +970,8 @@ FilterContainer.prototype.retrieveSpecificSelectors = function( domain: request.domain, exceptionFilters: [], exceptedFilters: [], + proceduralFilters: [], + convertedProceduralFilters: [], noDOMSurveying: this.needDOMSurveyor === false, }; const injectedCSS = []; @@ -1019,19 +1047,13 @@ FilterContainer.prototype.retrieveSpecificSelectors = function( // we extract and inject them immediately. if ( proceduralSet.size !== 0 ) { for ( const json of proceduralSet ) { - const pfilter = JSON.parse(json); - if ( pfilter.tasks === undefined ) { - const { action } = pfilter; - if ( action !== undefined && action[0] === ':style' ) { - injectedCSS.push(`${pfilter.selector}\n{${action[1]}}`); - proceduralSet.delete(json); - continue; - } - } - } - if ( proceduralSet.size !== 0 ) { - out.proceduralFilters = Array.from(proceduralSet); + const cssRule = this.cssRuleFromProcedural(json); + if ( cssRule === undefined ) { continue; } + injectedCSS.push(cssRule); + proceduralSet.delete(json); + out.convertedProceduralFilters.push(json); } + out.proceduralFilters.push(...proceduralSet); } // Highly generic cosmetic filters: sent once along with specific ones. diff --git a/src/js/messaging.js b/src/js/messaging.js index ce210c9f3..7c5335f5f 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -631,15 +631,17 @@ const retrieveContentScriptParameters = async function(sender, request) { request.domain = domainFromHostname(request.hostname); request.entity = entityFromDomain(request.domain); - response.specificCosmeticFilters = + const scf = response.specificCosmeticFilters = cosmeticFilteringEngine.retrieveSpecificSelectors(request, response); // The procedural filterer's code is loaded only when needed and must be // present before returning response to caller. if ( - Array.isArray(response.specificCosmeticFilters.proceduralFilters) || ( - logger.enabled && - response.specificCosmeticFilters.exceptedFilters.length !== 0 + scf.proceduralFilters.length !== 0 || ( + logger.enabled && ( + scf.convertedProceduralFilters.length !== 0 || + scf.exceptedFilters.length !== 0 + ) ) ) { await vAPI.tabs.executeScript(tabId, { diff --git a/src/js/scriptlets/cosmetic-logger.js b/src/js/scriptlets/cosmetic-logger.js index 53a626eed..1e0fc5a7f 100644 --- a/src/js/scriptlets/cosmetic-logger.js +++ b/src/js/scriptlets/cosmetic-logger.js @@ -40,8 +40,6 @@ const simpleDeclarativeSet = new Set(); let simpleDeclarativeStr; const complexDeclarativeSet = new Set(); let complexDeclarativeStr; -const declarativeStyleDict = new Map(); -let declarativeStyleStr; const proceduralDict = new Map(); const exceptionDict = new Map(); let exceptionStr; @@ -124,30 +122,12 @@ const processDeclarativeComplex = function(out) { /******************************************************************************/ -const processDeclarativeStyle = function(out) { - if ( declarativeStyleDict.size === 0 ) { return; } - if ( declarativeStyleStr === undefined ) { - declarativeStyleStr = safeGroupSelectors(declarativeStyleDict.keys()); - } - if ( document.querySelector(declarativeStyleStr) === null ) { return; } - for ( const selector of declarativeStyleDict.keys() ) { - if ( safeQuerySelector(selector) === null ) { continue; } - for ( const style of declarativeStyleDict.get(selector) ) { - const raw = `##${selector}:style(${style})`; - out.push(raw); - loggedSelectors.add(raw); - } - declarativeStyleDict.delete(selector); - declarativeStyleStr = undefined; - } -}; - -/******************************************************************************/ - const processProcedural = function(out) { if ( proceduralDict.size === 0 ) { return; } for ( const [ raw, pselector ] of proceduralDict ) { - if ( pselector.hit === false ) { continue; } + if ( pselector.hit === false && pselector.exec().length === 0 ) { + continue; + } out.push(`##${raw}`); proceduralDict.delete(raw); } @@ -201,7 +181,6 @@ const processTimer = new vAPI.SafeAnimationFrame(( ) => { } processDeclarativeComplex(toLog); - processDeclarativeStyle(toLog); processProcedural(toLog); processExceptions(toLog); processProceduralExceptions(toLog); @@ -240,18 +219,8 @@ const attributeObserver = new MutationObserver(mutations => { const handlers = { onFiltersetChanged: function(changes) { //console.time('dom logger/filterset changed'); - for ( const entry of (changes.declarative || []) ) { - for ( let selector of entry[0].split(',\n') ) { - if ( entry[1] !== 'display:none!important;' ) { - declarativeStyleStr = undefined; - const styles = declarativeStyleDict.get(selector); - if ( styles === undefined ) { - declarativeStyleDict.set(selector, [ entry[1] ]); - continue; - } - styles.push(entry[1]); - continue; - } + for ( const block of (changes.declarative || []) ) { + for ( const selector of block.split(',\n') ) { if ( loggedSelectors.has(selector) ) { continue; } if ( reHasCSSCombinators.test(selector) ) { complexDeclarativeSet.add(selector); diff --git a/src/js/scriptlets/dom-inspector.js b/src/js/scriptlets/dom-inspector.js index c1edb90f4..d8ce5c330 100644 --- a/src/js/scriptlets/dom-inspector.js +++ b/src/js/scriptlets/dom-inspector.js @@ -492,43 +492,28 @@ try { /******************************************************************************/ const cosmeticFilterMapper = (function() { - // https://github.com/gorhill/uBlock/issues/546 - var matchesFnName; - if ( typeof document.body.matches === 'function' ) { - matchesFnName = 'matches'; - } else if ( typeof document.body.mozMatchesSelector === 'function' ) { - matchesFnName = 'mozMatchesSelector'; - } else if ( typeof document.body.webkitMatchesSelector === 'function' ) { - matchesFnName = 'webkitMatchesSelector'; - } - const nodesFromStyleTag = function(rootNode) { const filterMap = roRedNodes; const details = vAPI.domFilterer.getAllSelectors(); // Declarative selectors. - for ( const entry of (details.declarative || []) ) { - for ( const selector of entry[0].split(',\n') ) { - let canonical = selector; + for ( const block of (details.declarative || []) ) { + for ( const selector of block.split(',\n') ) { let nodes; - if ( entry[1] !== vAPI.hideStyle ) { - canonical += ':style(' + entry[1] + ')'; - } if ( reHasCSSCombinators.test(selector) ) { nodes = document.querySelectorAll(selector); } else { if ( filterMap.has(rootNode) === false && - rootNode[matchesFnName](selector) + rootNode.matches(selector) ) { - filterMap.set(rootNode, canonical); + filterMap.set(rootNode, selector); } nodes = rootNode.querySelectorAll(selector); } for ( const node of nodes ) { - if ( filterMap.has(node) === false ) { - filterMap.set(node, canonical); - } + if ( filterMap.has(node) ) { continue; } + filterMap.set(node, selector); } } } diff --git a/src/js/scriptlets/dom-survey-elements.js b/src/js/scriptlets/dom-survey-elements.js index 0386f3894..9d0f10a2b 100644 --- a/src/js/scriptlets/dom-survey-elements.js +++ b/src/js/scriptlets/dom-survey-elements.js @@ -59,7 +59,7 @@ return 0; } return document.querySelectorAll( - details.declarative.map(entry => entry[0]).join(',') + details.declarative.join(',\n') ).length; })(); } diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js index 33bf8e1e5..919169559 100644 --- a/src/js/static-filtering-parser.js +++ b/src/js/static-filtering-parser.js @@ -1824,9 +1824,9 @@ Parser.prototype.SelectorCompiler = class { } if ( root && this.sheetSelectable(prefix) ) { if ( action === undefined ) { - return { selector: prefix }; + return { selector: prefix, cssable: true }; } else if ( action[0] === ':style' ) { - return { selector: prefix, action }; + return { selector: prefix, cssable: true, action }; } } @@ -1862,6 +1862,9 @@ Parser.prototype.SelectorCompiler = class { } const out = { selector: prefix }; + if ( root && this.sheetSelectable(prefix) ) { + out.cssable = true; + } if ( tasks.length !== 0 ) { out.tasks = tasks;