mirror of https://github.com/gorhill/uBlock.git
Refactor how cosmetic filters with pseudo-elements are parsed
Related issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/1247#issuecomment-953284365 Distinguish between selectors which can be querySelector-ed and/or used ni a stylesheet.
This commit is contained in:
parent
97a33c9572
commit
ef07171f5a
|
@ -387,7 +387,7 @@ FilterContainer.prototype.compileGenericHideSelector = function(
|
||||||
parser,
|
parser,
|
||||||
writer
|
writer
|
||||||
) {
|
) {
|
||||||
const { raw, compiled, pseudoclass } = parser.result;
|
const { raw, compiled } = parser.result;
|
||||||
if ( compiled === undefined ) {
|
if ( compiled === undefined ) {
|
||||||
const who = writer.properties.get('name') || '?';
|
const who = writer.properties.get('name') || '?';
|
||||||
logger.writeOne({
|
logger.writeOne({
|
||||||
|
@ -432,7 +432,7 @@ FilterContainer.prototype.compileGenericHideSelector = function(
|
||||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/131
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/131
|
||||||
// Support generic procedural filters as per advanced settings.
|
// Support generic procedural filters as per advanced settings.
|
||||||
// TODO: prevent double compilation.
|
// TODO: prevent double compilation.
|
||||||
if ( compiled !== raw && pseudoclass === false ) {
|
if ( compiled !== raw ) {
|
||||||
if ( µb.hiddenSettings.allowGenericProceduralFilters === true ) {
|
if ( µb.hiddenSettings.allowGenericProceduralFilters === true ) {
|
||||||
return this.compileSpecificSelector(parser, '', false, writer);
|
return this.compileSpecificSelector(parser, '', false, writer);
|
||||||
}
|
}
|
||||||
|
@ -1031,11 +1031,6 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( pfilter.pseudo !== undefined ) {
|
|
||||||
injectedHideFilters.push(pfilter.selector);
|
|
||||||
proceduralSet.delete(json);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if ( proceduralSet.size !== 0 ) {
|
if ( proceduralSet.size !== 0 ) {
|
||||||
out.proceduralFilters = Array.from(proceduralSet);
|
out.proceduralFilters = Array.from(proceduralSet);
|
||||||
|
|
|
@ -129,7 +129,6 @@ const Parser = class {
|
||||||
exception: false,
|
exception: false,
|
||||||
raw: '',
|
raw: '',
|
||||||
compiled: '',
|
compiled: '',
|
||||||
pseudoclass: false,
|
|
||||||
};
|
};
|
||||||
this.reset();
|
this.reset();
|
||||||
}
|
}
|
||||||
|
@ -289,7 +288,6 @@ const Parser = class {
|
||||||
analyzeExtPattern() {
|
analyzeExtPattern() {
|
||||||
this.result.exception = this.isException();
|
this.result.exception = this.isException();
|
||||||
this.result.compiled = undefined;
|
this.result.compiled = undefined;
|
||||||
this.result.pseudoclass = false;
|
|
||||||
|
|
||||||
let selector = this.strFromSpan(this.patternSpan);
|
let selector = this.strFromSpan(this.patternSpan);
|
||||||
if ( selector === '' ) {
|
if ( selector === '' ) {
|
||||||
|
@ -1323,7 +1321,11 @@ Parser.prototype.SelectorCompiler = class {
|
||||||
style.remove();
|
style.remove();
|
||||||
return stylesheet;
|
return stylesheet;
|
||||||
})();
|
})();
|
||||||
this.rePseudoElement = /:(?::?after|:?before|:-?[a-z][a-z-]*[a-z])$/;
|
this.div = (( ) => {
|
||||||
|
if ( typeof document !== 'object' ) { return null; }
|
||||||
|
if ( document instanceof Object === false ) { return null; }
|
||||||
|
return document.createElement('div');
|
||||||
|
})();
|
||||||
this.reProceduralOperator = new RegExp([
|
this.reProceduralOperator = new RegExp([
|
||||||
'^(?:',
|
'^(?:',
|
||||||
Array.from(parser.proceduralOperatorTokens.keys()).join('|'),
|
Array.from(parser.proceduralOperatorTokens.keys()).join('|'),
|
||||||
|
@ -1366,11 +1368,11 @@ Parser.prototype.SelectorCompiler = class {
|
||||||
}
|
}
|
||||||
|
|
||||||
let extendedSyntax = false;
|
let extendedSyntax = false;
|
||||||
const selectorType = this.cssSelectorType(raw);
|
|
||||||
if ( selectorType !== 0 ) {
|
// Can be used in a declarative CSS rule?
|
||||||
|
if ( this.sheetSelectable(raw) ) {
|
||||||
extendedSyntax = this.reExtendedSyntax.test(raw);
|
extendedSyntax = this.reExtendedSyntax.test(raw);
|
||||||
if ( (extendedSyntax || isProcedural) === false ) {
|
if ( (extendedSyntax || isProcedural) === false ) {
|
||||||
out.pseudoclass = selectorType === 3;
|
|
||||||
out.compiled = raw;
|
out.compiled = raw;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1402,9 +1404,6 @@ Parser.prototype.SelectorCompiler = class {
|
||||||
const compiled = this.compileProceduralSelector(raw);
|
const compiled = this.compileProceduralSelector(raw);
|
||||||
if ( compiled === undefined ) { return false; }
|
if ( compiled === undefined ) { return false; }
|
||||||
|
|
||||||
if ( compiled.pseudo !== undefined ) {
|
|
||||||
out.pseudoclass = compiled.pseudo;
|
|
||||||
}
|
|
||||||
out.compiled = compiled.selector !== compiled.raw
|
out.compiled = compiled.selector !== compiled.raw
|
||||||
? JSON.stringify(compiled)
|
? JSON.stringify(compiled)
|
||||||
: compiled.selector;
|
: compiled.selector;
|
||||||
|
@ -1429,11 +1428,6 @@ Parser.prototype.SelectorCompiler = class {
|
||||||
: `${selector}:style(${style})`;
|
: `${selector}:style(${style})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return value:
|
|
||||||
// 0b00 (0) = not a valid CSS selector
|
|
||||||
// 0b01 (1) = valid CSS selector, without pseudo-element
|
|
||||||
// 0b11 (3) = valid CSS selector, with pseudo element
|
|
||||||
//
|
|
||||||
// Quick regex-based validation -- most cosmetic filters are of the
|
// Quick regex-based validation -- most cosmetic filters are of the
|
||||||
// simple form and in such case a regex is much faster.
|
// simple form and in such case a regex is much faster.
|
||||||
// Keep in mind:
|
// Keep in mind:
|
||||||
|
@ -1445,27 +1439,28 @@ Parser.prototype.SelectorCompiler = class {
|
||||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/1751
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/1751
|
||||||
// Do not rely on matches() or querySelector() to test whether a
|
// Do not rely on matches() or querySelector() to test whether a
|
||||||
// selector is declarative or not.
|
// selector is declarative or not.
|
||||||
cssSelectorType(s) {
|
sheetSelectable(s) {
|
||||||
if ( this.reSimpleSelector.test(s) ) { return 1; }
|
if ( this.reSimpleSelector.test(s) ) { return true; }
|
||||||
const pos = this.cssPseudoElement(s);
|
if ( this.stylesheet === null ) { return true; }
|
||||||
if ( pos !== -1 ) {
|
|
||||||
return this.cssSelectorType(s.slice(0, pos)) === 1 ? 3 : 0;
|
|
||||||
}
|
|
||||||
if ( this.stylesheet === null ) { return 1; }
|
|
||||||
try {
|
try {
|
||||||
this.stylesheet.insertRule(`${s}{color:red}`);
|
this.stylesheet.insertRule(`${s}{color:red}`);
|
||||||
if ( this.stylesheet.cssRules.length === 0 ) { return 0; }
|
if ( this.stylesheet.cssRules.length === 0 ) { return false; }
|
||||||
this.stylesheet.deleteRule(0);
|
this.stylesheet.deleteRule(0);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
return 1;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
cssPseudoElement(s) {
|
querySelectable(s) {
|
||||||
if ( s.lastIndexOf(':') === -1 ) { return -1; }
|
if ( this.reSimpleSelector.test(s) ) { return true; }
|
||||||
const match = this.rePseudoElement.exec(s);
|
if ( this.div === null ) { return true; }
|
||||||
return match !== null ? match.index : -1;
|
try {
|
||||||
|
this.div.querySelector(`${s},${s}:not(#foo)`);
|
||||||
|
} catch (ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
compileProceduralSelector(raw) {
|
compileProceduralSelector(raw) {
|
||||||
|
@ -1534,10 +1529,10 @@ Parser.prototype.SelectorCompiler = class {
|
||||||
|
|
||||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/341#issuecomment-447603588
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/341#issuecomment-447603588
|
||||||
// Reject instances of :not() filters for which the argument is
|
// Reject instances of :not() filters for which the argument is
|
||||||
// a valid CSS selector, otherwise we would be adversely
|
// a valid CSS selector, otherwise we would be adversely changing the
|
||||||
// changing the behavior of CSS4's :not().
|
// behavior of CSS4's :not().
|
||||||
compileNotSelector(s) {
|
compileNotSelector(s) {
|
||||||
if ( this.cssSelectorType(s) === 0 ) {
|
if ( this.querySelectable(s) === false ) {
|
||||||
return this.compileProcedural(s);
|
return this.compileProcedural(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1545,7 +1540,7 @@ Parser.prototype.SelectorCompiler = class {
|
||||||
compileUpwardArgument(s) {
|
compileUpwardArgument(s) {
|
||||||
const i = this.compileInteger(s, 1, 256);
|
const i = this.compileInteger(s, 1, 256);
|
||||||
if ( i !== undefined ) { return i; }
|
if ( i !== undefined ) { return i; }
|
||||||
if ( this.cssSelectorType(s) === 1 ) { return s; }
|
if ( this.querySelectable(s) ) { return s; }
|
||||||
}
|
}
|
||||||
|
|
||||||
compileRemoveSelector(s) {
|
compileRemoveSelector(s) {
|
||||||
|
@ -1555,7 +1550,7 @@ Parser.prototype.SelectorCompiler = class {
|
||||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/382#issuecomment-703725346
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/382#issuecomment-703725346
|
||||||
// Prepend `:scope` only when it can be deemed implicit.
|
// Prepend `:scope` only when it can be deemed implicit.
|
||||||
compileSpathExpression(s) {
|
compileSpathExpression(s) {
|
||||||
if ( this.cssSelectorType(/^\s*[+:>~]/.test(s) ? `:scope${s}` : s) === 1 ) {
|
if ( this.querySelectable(/^\s*[+:>~]/.test(s) ? `:scope${s}` : s) ) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1714,9 +1709,7 @@ Parser.prototype.SelectorCompiler = class {
|
||||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/341#issuecomment-447603588
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/341#issuecomment-447603588
|
||||||
// Maybe that one operator is a valid CSS selector and if so,
|
// Maybe that one operator is a valid CSS selector and if so,
|
||||||
// then consider it to be part of the prefix.
|
// then consider it to be part of the prefix.
|
||||||
if ( this.cssSelectorType(raw.slice(opNameBeg, i)) === 1 ) {
|
if ( this.querySelectable(raw.slice(opNameBeg, i)) ) { continue; }
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Extract and remember operator details.
|
// Extract and remember operator details.
|
||||||
let operator = raw.slice(opNameBeg, opNameEnd);
|
let operator = raw.slice(opNameBeg, opNameEnd);
|
||||||
operator = this.normalizedOperators.get(operator) || operator;
|
operator = this.normalizedOperators.get(operator) || operator;
|
||||||
|
@ -1752,8 +1745,18 @@ Parser.prototype.SelectorCompiler = class {
|
||||||
|
|
||||||
// No task found: then we have a CSS selector.
|
// No task found: then we have a CSS selector.
|
||||||
// At least one task found: nothing should be left to parse.
|
// At least one task found: nothing should be left to parse.
|
||||||
if ( tasks.length === 0 && action === undefined ) {
|
if ( tasks.length === 0 ) {
|
||||||
prefix = raw;
|
if ( action === undefined ) {
|
||||||
|
prefix = raw;
|
||||||
|
}
|
||||||
|
if ( root && this.sheetSelectable(prefix) ) {
|
||||||
|
if ( action === undefined ) {
|
||||||
|
return { selector: prefix };
|
||||||
|
} else if ( action[0] === ':style' ) {
|
||||||
|
return { selector: prefix, action };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else if ( opPrefixBeg < n ) {
|
} else if ( opPrefixBeg < n ) {
|
||||||
if ( action !== undefined ) { return; }
|
if ( action !== undefined ) { return; }
|
||||||
const spath = this.compileSpathExpression(raw.slice(opPrefixBeg));
|
const spath = this.compileSpathExpression(raw.slice(opPrefixBeg));
|
||||||
|
@ -1773,7 +1776,7 @@ Parser.prototype.SelectorCompiler = class {
|
||||||
prefix += ' *';
|
prefix += ' *';
|
||||||
}
|
}
|
||||||
prefix = prefix.replace(this.reDropScope, '');
|
prefix = prefix.replace(this.reDropScope, '');
|
||||||
if ( this.cssSelectorType(prefix) === 0 ) {
|
if ( this.querySelectable(prefix) === false ) {
|
||||||
if (
|
if (
|
||||||
root ||
|
root ||
|
||||||
this.reIsCombinator.test(prefix) === false ||
|
this.reIsCombinator.test(prefix) === false ||
|
||||||
|
@ -1797,17 +1800,6 @@ Parser.prototype.SelectorCompiler = class {
|
||||||
out.action = action;
|
out.action = action;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pseudo elements are valid only when used in a root task list AND
|
|
||||||
// only when there are no procedural operators: pseudo elements can't
|
|
||||||
// be querySelectorAll-ed.
|
|
||||||
if ( prefix !== '' ) {
|
|
||||||
const pos = this.cssPseudoElement(prefix);
|
|
||||||
if ( pos !== -1 ) {
|
|
||||||
if ( root === false || tasks.length !== 0 ) { return; }
|
|
||||||
out.pseudo = pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue