Improve conversion of procedural cosmetic filters into CSS rules

Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/2185#issuecomment-1193164728
This commit is contained in:
Raymond Hill 2022-07-24 11:41:08 -04:00
parent dd5fc444bb
commit 91caed32d3
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
8 changed files with 86 additions and 92 deletions

View File

@ -415,6 +415,13 @@ class PSelectorRoot extends PSelector {
this.lastAllowanceTime = 0; this.lastAllowanceTime = 0;
this.styleToken = styleToken; this.styleToken = styleToken;
} }
prime(input) {
try {
return super.prime(input);
} catch (ex) {
}
return [];
}
} }
PSelectorRoot.prototype.hit = false; PSelectorRoot.prototype.hit = false;
@ -534,7 +541,7 @@ class ProceduralFilterer {
// TODO: Current assumption is one style per hit element. Could be an // TODO: Current assumption is one style per hit element. Could be an
// issue if an element has multiple styling and one styling is // 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) { unstyleNodes(nodes) {
for ( const node of nodes ) { for ( const node of nodes ) {
if ( this.styledNodes.has(node) ) { continue; } if ( this.styledNodes.has(node) ) { continue; }
@ -543,7 +550,7 @@ class ProceduralFilterer {
} }
createProceduralFilter(o) { createProceduralFilter(o) {
return new PSelectorRoot(o); return new PSelectorRoot(typeof o === 'string' ? JSON.parse(o) : o);
} }
onDOMCreated() { onDOMCreated() {

View File

@ -501,6 +501,7 @@ vAPI.DOMFilterer = class {
this.stylesheets = []; this.stylesheets = [];
this.exceptedCSSRules = []; this.exceptedCSSRules = [];
this.exceptions = []; this.exceptions = [];
this.convertedProceduralFilters = [];
this.proceduralFilterer = null; this.proceduralFilterer = null;
// https://github.com/uBlockOrigin/uBlock-issues/issues/167 // https://github.com/uBlockOrigin/uBlock-issues/issues/167
// By the time the DOMContentLoaded is fired, the content script might // By the time the DOMContentLoaded is fired, the content script might
@ -518,11 +519,11 @@ vAPI.DOMFilterer = class {
explodeCSS(css) { explodeCSS(css) {
const out = []; const out = [];
const reBlock = /^\{(.*)\}$/m; const cssHide = `{${vAPI.hideStyle}}`;
const blocks = css.trim().split(/\n\n+/); const blocks = css.trim().split(/\n\n+/);
for ( const block of blocks ) { for ( const block of blocks ) {
const match = reBlock.exec(block); if ( block.endsWith(cssHide) === false ) { continue; }
out.push([ block.slice(0, match.index).trim(), match[1] ]); out.push(block.slice(0, -cssHide.length).trim());
} }
return out; return out;
} }
@ -621,9 +622,6 @@ vAPI.DOMFilterer = class {
} }
addProceduralSelectors(selectors) { addProceduralSelectors(selectors) {
if ( Array.isArray(selectors) === false || selectors.length === 0 ) {
return;
}
const procedurals = []; const procedurals = [];
for ( const raw of selectors ) { for ( const raw of selectors ) {
procedurals.push(JSON.parse(raw)); procedurals.push(JSON.parse(raw));
@ -652,23 +650,30 @@ vAPI.DOMFilterer = class {
? `[${this.proceduralFilterer.masterToken}]` ? `[${this.proceduralFilterer.masterToken}]`
: undefined; : undefined;
for ( const css of this.stylesheets ) { for ( const css of this.stylesheets ) {
const blocks = this.explodeCSS(css); for ( const block of this.explodeCSS(css) ) {
for ( const block of blocks ) {
if ( if (
includePrivateSelectors === false && includePrivateSelectors === false &&
masterToken !== undefined && masterToken !== undefined &&
block[0].startsWith(masterToken) block.startsWith(masterToken)
) { ) {
continue; continue;
} }
out.declarative.push([ block[0], block[1] ]); out.declarative.push(block);
} }
} }
const excludeProcedurals = (bits & 0b10) !== 0; const excludeProcedurals = (bits & 0b10) !== 0;
if ( excludeProcedurals !== true ) { if ( excludeProcedurals === false ) {
out.procedural = hasProcedural out.procedural = [];
? Array.from(this.proceduralFilterer.selectors.values()) if ( hasProcedural ) {
: []; out.procedural.push(
...this.proceduralFilterer.selectors.values()
);
}
for ( const json of this.convertedProceduralFilters ) {
out.procedural.push(
this.proceduralFiltererInstance().createProceduralFilter(json)
);
}
} }
return out; return out;
} }
@ -1319,6 +1324,7 @@ vAPI.DOMFilterer = class {
domFilterer.addCSS(cfeDetails.injectedCSS); domFilterer.addCSS(cfeDetails.injectedCSS);
domFilterer.addProceduralSelectors(cfeDetails.proceduralFilters); domFilterer.addProceduralSelectors(cfeDetails.proceduralFilters);
domFilterer.exceptCSSRules(cfeDetails.exceptedFilters); domFilterer.exceptCSSRules(cfeDetails.exceptedFilters);
domFilterer.convertedProceduralFilters = cfeDetails.convertedProceduralFilters;
} }
vAPI.userStylesheet.apply(); vAPI.userStylesheet.apply();

View File

@ -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) { FilterContainer.prototype.retrieveGenericSelectors = function(request) {
if ( this.acceptedCount === 0 ) { return; } if ( this.acceptedCount === 0 ) { return; }
if ( !request.ids && !request.classes ) { return; } if ( !request.ids && !request.classes ) { return; }
@ -944,6 +970,8 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
domain: request.domain, domain: request.domain,
exceptionFilters: [], exceptionFilters: [],
exceptedFilters: [], exceptedFilters: [],
proceduralFilters: [],
convertedProceduralFilters: [],
noDOMSurveying: this.needDOMSurveyor === false, noDOMSurveying: this.needDOMSurveyor === false,
}; };
const injectedCSS = []; const injectedCSS = [];
@ -1019,19 +1047,13 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
// we extract and inject them immediately. // we extract and inject them immediately.
if ( proceduralSet.size !== 0 ) { if ( proceduralSet.size !== 0 ) {
for ( const json of proceduralSet ) { for ( const json of proceduralSet ) {
const pfilter = JSON.parse(json); const cssRule = this.cssRuleFromProcedural(json);
if ( pfilter.tasks === undefined ) { if ( cssRule === undefined ) { continue; }
const { action } = pfilter; injectedCSS.push(cssRule);
if ( action !== undefined && action[0] === ':style' ) { proceduralSet.delete(json);
injectedCSS.push(`${pfilter.selector}\n{${action[1]}}`); out.convertedProceduralFilters.push(json);
proceduralSet.delete(json);
continue;
}
}
}
if ( proceduralSet.size !== 0 ) {
out.proceduralFilters = Array.from(proceduralSet);
} }
out.proceduralFilters.push(...proceduralSet);
} }
// Highly generic cosmetic filters: sent once along with specific ones. // Highly generic cosmetic filters: sent once along with specific ones.

View File

@ -631,15 +631,17 @@ const retrieveContentScriptParameters = async function(sender, request) {
request.domain = domainFromHostname(request.hostname); request.domain = domainFromHostname(request.hostname);
request.entity = entityFromDomain(request.domain); request.entity = entityFromDomain(request.domain);
response.specificCosmeticFilters = const scf = response.specificCosmeticFilters =
cosmeticFilteringEngine.retrieveSpecificSelectors(request, response); cosmeticFilteringEngine.retrieveSpecificSelectors(request, response);
// The procedural filterer's code is loaded only when needed and must be // The procedural filterer's code is loaded only when needed and must be
// present before returning response to caller. // present before returning response to caller.
if ( if (
Array.isArray(response.specificCosmeticFilters.proceduralFilters) || ( scf.proceduralFilters.length !== 0 || (
logger.enabled && logger.enabled && (
response.specificCosmeticFilters.exceptedFilters.length !== 0 scf.convertedProceduralFilters.length !== 0 ||
scf.exceptedFilters.length !== 0
)
) )
) { ) {
await vAPI.tabs.executeScript(tabId, { await vAPI.tabs.executeScript(tabId, {

View File

@ -40,8 +40,6 @@ const simpleDeclarativeSet = new Set();
let simpleDeclarativeStr; let simpleDeclarativeStr;
const complexDeclarativeSet = new Set(); const complexDeclarativeSet = new Set();
let complexDeclarativeStr; let complexDeclarativeStr;
const declarativeStyleDict = new Map();
let declarativeStyleStr;
const proceduralDict = new Map(); const proceduralDict = new Map();
const exceptionDict = new Map(); const exceptionDict = new Map();
let exceptionStr; 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) { const processProcedural = function(out) {
if ( proceduralDict.size === 0 ) { return; } if ( proceduralDict.size === 0 ) { return; }
for ( const [ raw, pselector ] of proceduralDict ) { for ( const [ raw, pselector ] of proceduralDict ) {
if ( pselector.hit === false ) { continue; } if ( pselector.hit === false && pselector.exec().length === 0 ) {
continue;
}
out.push(`##${raw}`); out.push(`##${raw}`);
proceduralDict.delete(raw); proceduralDict.delete(raw);
} }
@ -201,7 +181,6 @@ const processTimer = new vAPI.SafeAnimationFrame(( ) => {
} }
processDeclarativeComplex(toLog); processDeclarativeComplex(toLog);
processDeclarativeStyle(toLog);
processProcedural(toLog); processProcedural(toLog);
processExceptions(toLog); processExceptions(toLog);
processProceduralExceptions(toLog); processProceduralExceptions(toLog);
@ -240,18 +219,8 @@ const attributeObserver = new MutationObserver(mutations => {
const handlers = { const handlers = {
onFiltersetChanged: function(changes) { onFiltersetChanged: function(changes) {
//console.time('dom logger/filterset changed'); //console.time('dom logger/filterset changed');
for ( const entry of (changes.declarative || []) ) { for ( const block of (changes.declarative || []) ) {
for ( let selector of entry[0].split(',\n') ) { for ( const selector of block.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;
}
if ( loggedSelectors.has(selector) ) { continue; } if ( loggedSelectors.has(selector) ) { continue; }
if ( reHasCSSCombinators.test(selector) ) { if ( reHasCSSCombinators.test(selector) ) {
complexDeclarativeSet.add(selector); complexDeclarativeSet.add(selector);

View File

@ -492,43 +492,28 @@ try {
/******************************************************************************/ /******************************************************************************/
const cosmeticFilterMapper = (function() { 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 nodesFromStyleTag = function(rootNode) {
const filterMap = roRedNodes; const filterMap = roRedNodes;
const details = vAPI.domFilterer.getAllSelectors(); const details = vAPI.domFilterer.getAllSelectors();
// Declarative selectors. // Declarative selectors.
for ( const entry of (details.declarative || []) ) { for ( const block of (details.declarative || []) ) {
for ( const selector of entry[0].split(',\n') ) { for ( const selector of block.split(',\n') ) {
let canonical = selector;
let nodes; let nodes;
if ( entry[1] !== vAPI.hideStyle ) {
canonical += ':style(' + entry[1] + ')';
}
if ( reHasCSSCombinators.test(selector) ) { if ( reHasCSSCombinators.test(selector) ) {
nodes = document.querySelectorAll(selector); nodes = document.querySelectorAll(selector);
} else { } else {
if ( if (
filterMap.has(rootNode) === false && filterMap.has(rootNode) === false &&
rootNode[matchesFnName](selector) rootNode.matches(selector)
) { ) {
filterMap.set(rootNode, canonical); filterMap.set(rootNode, selector);
} }
nodes = rootNode.querySelectorAll(selector); nodes = rootNode.querySelectorAll(selector);
} }
for ( const node of nodes ) { for ( const node of nodes ) {
if ( filterMap.has(node) === false ) { if ( filterMap.has(node) ) { continue; }
filterMap.set(node, canonical); filterMap.set(node, selector);
}
} }
} }
} }

View File

@ -59,7 +59,7 @@
return 0; return 0;
} }
return document.querySelectorAll( return document.querySelectorAll(
details.declarative.map(entry => entry[0]).join(',') details.declarative.join(',\n')
).length; ).length;
})(); })();
} }

View File

@ -1824,9 +1824,9 @@ Parser.prototype.SelectorCompiler = class {
} }
if ( root && this.sheetSelectable(prefix) ) { if ( root && this.sheetSelectable(prefix) ) {
if ( action === undefined ) { if ( action === undefined ) {
return { selector: prefix }; return { selector: prefix, cssable: true };
} else if ( action[0] === ':style' ) { } 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 }; const out = { selector: prefix };
if ( root && this.sheetSelectable(prefix) ) {
out.cssable = true;
}
if ( tasks.length !== 0 ) { if ( tasks.length !== 0 ) {
out.tasks = tasks; out.tasks = tasks;