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.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() {

View File

@ -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();

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) {
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.

View File

@ -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, {

View File

@ -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);

View File

@ -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);
}
}
}

View File

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

View File

@ -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;