mirror of
https://github.com/gorhill/uBlock.git
synced 2024-09-04 01:59:38 +02:00
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:
parent
dd5fc444bb
commit
91caed32d3
@ -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() {
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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, {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@
|
||||
return 0;
|
||||
}
|
||||
return document.querySelectorAll(
|
||||
details.declarative.map(entry => entry[0]).join(',')
|
||||
details.declarative.join(',\n')
|
||||
).length;
|
||||
})();
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user