Skip to content

Commit

Permalink
Add new procedural cosmetic filter operator: :matches-media()
Browse files Browse the repository at this point in the history
Related issue:
- uBlockOrigin/uBlock-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.
  • Loading branch information
gorhill committed Jul 23, 2022
1 parent deb5fea commit 40c315a
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 6 deletions.
30 changes: 25 additions & 5 deletions src/js/contentscript-extra.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand All @@ -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();
Expand Down Expand Up @@ -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 ],
Expand Down
18 changes: 17 additions & 1 deletion src/js/static-filtering-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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 ],
Expand Down

0 comments on commit 40c315a

Please sign in to comment.