Extend matches-css() to support any pseudo-element

This commit deprecates matches-css-before() and matches-css-after(): these
should no longer be used once 1.45.0 is published and widespread. The
deprecated syntax will eventually be removed in some future.

The syntax of procedural operator matches-css() has been extended to also
be able to target pesudo elements. Examples:

Same as before:

    example.com##p:matches-css(opacity: 0.5)

This is the new way to target an `::after` pseudo-element:

    example.com##p:matches-css(after, content: Ads)

This is the new way to target a `::before` pseudo-element:

    example.com##p:matches-css(before, content: Ads)

The new syntax also means any valid pseudo-element can now be used as
a target:

    example.com##p:matches-css(first-letter, opacity: 0.5)

If the first argument does not match the pattern "property name: value",
then it will be deemed a pseudo-element to target, and the second argument
will be the "property name: value".

Related issue:
- https://github.com/AdguardTeam/ExtendedCss/issues/150
This commit is contained in:
Raymond Hill 2022-08-18 11:28:44 -04:00
parent bdc68f3a81
commit 7bc0b5d2bd
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
2 changed files with 24 additions and 8 deletions

View File

@ -81,6 +81,7 @@ class PSelectorMatchesCSSTask extends PSelectorTask {
constructor(task) {
super();
this.name = task[1].name;
this.pseudo = task[1].pseudo ? `::${task[1].pseudo}` : null;
let arg0 = task[1].value, arg1;
if ( Array.isArray(arg0) ) {
arg1 = arg0[1]; arg0 = arg0[0];
@ -94,15 +95,19 @@ class PSelectorMatchesCSSTask extends PSelectorTask {
}
}
}
PSelectorMatchesCSSTask.prototype.pseudo = null;
class PSelectorMatchesCSSAfterTask extends PSelectorMatchesCSSTask {
constructor(task) {
super(task);
this.pseudo = 'after';
}
}
PSelectorMatchesCSSAfterTask.prototype.pseudo = ':after';
class PSelectorMatchesCSSBeforeTask extends PSelectorMatchesCSSTask {
constructor(task) {
super(task);
this.pseudo = 'before';
}
}
PSelectorMatchesCSSBeforeTask.prototype.pseudo = ':before';
class PSelectorMatchesMediaTask extends PSelectorTask {
constructor(task) {

View File

@ -1560,6 +1560,13 @@ Parser.prototype.SelectorCompiler = class {
}
compileCSSDeclaration(s) {
let pseudo; {
const match = /^[a-z-]+,/.exec(s);
if ( match !== null ) {
pseudo = match[0].slice(0, -1);
s = s.slice(match[0].length).trim();
}
}
const pos = s.indexOf(':');
if ( pos === -1 ) { return; }
const name = s.slice(0, pos).trim();
@ -1576,7 +1583,7 @@ Parser.prototype.SelectorCompiler = class {
regexDetails = '^' + value.replace(this.reEscapeRegex, '\\$&') + '$';
this.regexToRawValue.set(regexDetails, value);
}
return { name: name, value: regexDetails };
return { name, pseudo, value: regexDetails };
}
compileInteger(s, min = 0, max = 0x7FFFFFFF) {
@ -1711,7 +1718,11 @@ Parser.prototype.SelectorCompiler = class {
value = `/${task[1].value}/`;
}
}
raw.push(`${task[0]}(${task[1].name}: ${value})`);
if ( task[1].pseudo ) {
raw.push(`:matches-css(${task[1].pseudo}, ${task[1].name}: ${value})`);
} else {
raw.push(`:matches-css(${task[1].name}: ${value})`);
}
break;
case ':not':
case ':if-not':
@ -1907,9 +1918,9 @@ Parser.prototype.SelectorCompiler = class {
case ':matches-css':
return this.compileCSSDeclaration(args);
case ':matches-css-after':
return this.compileCSSDeclaration(args);
return this.compileCSSDeclaration(`after, ${args}`);
case ':matches-css-before':
return this.compileCSSDeclaration(args);
return this.compileCSSDeclaration(`before, ${args}`);
case ':matches-media':
return this.compileMediaQuery(args);
case ':matches-path':