Add new procedural cosmetic filter operator: :matches-media()

Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/2185

The argument must be a valid media query as documented on MDN, i.e.
what appears between the `@media` at-rule and the first opening
curly bracket (including the parentheses when required):
- https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries

Best practice:

Use `:matches-media()` after plain CSS selectors, if any.

Good:
    example.com###target-1 > .target-2:matches-media((min-width: 800px))

Bad (though this will still work):
    example.com##:matches-media((min-width: 800px)) #target-1 > .target-2

The reason for this is to keep the door open for a future optimisation
where uBO could convert `:matches-media()`-based filters into CSS media
rules injected declaratively in a user stylesheet.
This commit is contained in:
Raymond Hill 2022-07-23 09:30:31 -04:00
parent deb5fea0ba
commit 40c315a107
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
2 changed files with 42 additions and 6 deletions

View file

@ -104,15 +104,22 @@ class PSelectorMatchesCSSBeforeTask extends PSelectorMatchesCSSTask {
}
PSelectorMatchesCSSBeforeTask.prototype.pseudo = ':before';
class PSelectorMinTextLengthTask extends PSelectorTask {
class PSelectorMatchesMediaTask extends PSelectorTask {
constructor(task) {
super();
this.min = task[1];
this.mql = window.matchMedia(task[1]);
if ( this.mql.media === 'not all' ) { return; }
this.mql.addEventListener('change', ( ) => {
if ( typeof vAPI !== 'object' ) { return; }
if ( vAPI === null ) { return; }
const filterer = vAPI.domFilterer && vAPI.domFilterer.proceduralFilterer;
if ( filterer instanceof Object === false ) { return; }
filterer.onDOMChanged([ null ]);
});
}
transpose(node, output) {
if ( node.textContent.length >= this.min ) {
output.push(node);
}
if ( this.mql.matches === false ) { return; }
output.push(node);
}
}
@ -132,6 +139,18 @@ class PSelectorMatchesPathTask extends PSelectorTask {
}
}
class PSelectorMinTextLengthTask extends PSelectorTask {
constructor(task) {
super();
this.min = task[1];
}
transpose(node, output) {
if ( node.textContent.length >= this.min ) {
output.push(node);
}
}
}
class PSelectorOthersTask extends PSelectorTask {
constructor() {
super();
@ -322,6 +341,7 @@ class PSelector {
[ ':matches-css', PSelectorMatchesCSSTask ],
[ ':matches-css-after', PSelectorMatchesCSSAfterTask ],
[ ':matches-css-before', PSelectorMatchesCSSBeforeTask ],
[ ':matches-media', PSelectorMatchesMediaTask ],
[ ':matches-path', PSelectorMatchesPathTask ],
[ ':min-text-length', PSelectorMinTextLengthTask ],
[ ':not', PSelectorIfNotTask ],

View file

@ -1581,6 +1581,18 @@ Parser.prototype.SelectorCompiler = class {
return n;
}
compileMediaQuery(s) {
if ( typeof self !== 'object' ) { return; }
if ( self === null ) { return; }
if ( typeof self.matchMedia !== 'function' ) { return; }
try {
const mql = self.matchMedia(s);
if ( mql instanceof self.MediaQueryList === false ) { return; }
if ( mql.media !== 'not all' ) { return s; }
} catch(ex) {
}
}
// https://github.com/uBlockOrigin/uBlock-issues/issues/341#issuecomment-447603588
// Reject instances of :not() filters for which the argument is
// a valid CSS selector, otherwise we would be adversely changing the
@ -1702,6 +1714,7 @@ Parser.prototype.SelectorCompiler = class {
case ':spath':
raw.push(task[1]);
break;
case ':matches-media':
case ':min-text-length':
case ':others':
case ':upward':
@ -1878,6 +1891,8 @@ Parser.prototype.SelectorCompiler = class {
return this.compileCSSDeclaration(args);
case ':matches-css-before':
return this.compileCSSDeclaration(args);
case ':matches-media':
return this.compileMediaQuery(args);
case ':matches-path':
return this.compileText(args);
case ':min-text-length':
@ -1918,7 +1933,8 @@ Parser.prototype.proceduralOperatorTokens = new Map([
[ 'matches-css', 0b11 ],
[ 'matches-css-after', 0b11 ],
[ 'matches-css-before', 0b11 ],
[ 'matches-path', 0b01 ],
[ 'matches-media', 0b11 ],
[ 'matches-path', 0b11 ],
[ 'min-text-length', 0b01 ],
[ 'not', 0b01 ],
[ 'nth-ancestor', 0b00 ],