mirror of https://github.com/gorhill/uBlock.git
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.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() {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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, {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue