Skip to content

Commit 40c315a

Browse files
committed
Add new procedural cosmetic filter operator: :matches-media()
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.
1 parent deb5fea commit 40c315a

File tree

2 files changed

+42
-6
lines changed

2 files changed

+42
-6
lines changed

src/js/contentscript-extra.js

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,22 @@ class PSelectorMatchesCSSBeforeTask extends PSelectorMatchesCSSTask {
104104
}
105105
PSelectorMatchesCSSBeforeTask.prototype.pseudo = ':before';
106106

107-
class PSelectorMinTextLengthTask extends PSelectorTask {
107+
class PSelectorMatchesMediaTask extends PSelectorTask {
108108
constructor(task) {
109109
super();
110-
this.min = task[1];
110+
this.mql = window.matchMedia(task[1]);
111+
if ( this.mql.media === 'not all' ) { return; }
112+
this.mql.addEventListener('change', ( ) => {
113+
if ( typeof vAPI !== 'object' ) { return; }
114+
if ( vAPI === null ) { return; }
115+
const filterer = vAPI.domFilterer && vAPI.domFilterer.proceduralFilterer;
116+
if ( filterer instanceof Object === false ) { return; }
117+
filterer.onDOMChanged([ null ]);
118+
});
111119
}
112120
transpose(node, output) {
113-
if ( node.textContent.length >= this.min ) {
114-
output.push(node);
115-
}
121+
if ( this.mql.matches === false ) { return; }
122+
output.push(node);
116123
}
117124
}
118125

@@ -132,6 +139,18 @@ class PSelectorMatchesPathTask extends PSelectorTask {
132139
}
133140
}
134141

142+
class PSelectorMinTextLengthTask extends PSelectorTask {
143+
constructor(task) {
144+
super();
145+
this.min = task[1];
146+
}
147+
transpose(node, output) {
148+
if ( node.textContent.length >= this.min ) {
149+
output.push(node);
150+
}
151+
}
152+
}
153+
135154
class PSelectorOthersTask extends PSelectorTask {
136155
constructor() {
137156
super();
@@ -322,6 +341,7 @@ class PSelector {
322341
[ ':matches-css', PSelectorMatchesCSSTask ],
323342
[ ':matches-css-after', PSelectorMatchesCSSAfterTask ],
324343
[ ':matches-css-before', PSelectorMatchesCSSBeforeTask ],
344+
[ ':matches-media', PSelectorMatchesMediaTask ],
325345
[ ':matches-path', PSelectorMatchesPathTask ],
326346
[ ':min-text-length', PSelectorMinTextLengthTask ],
327347
[ ':not', PSelectorIfNotTask ],

src/js/static-filtering-parser.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1581,6 +1581,18 @@ Parser.prototype.SelectorCompiler = class {
15811581
return n;
15821582
}
15831583

1584+
compileMediaQuery(s) {
1585+
if ( typeof self !== 'object' ) { return; }
1586+
if ( self === null ) { return; }
1587+
if ( typeof self.matchMedia !== 'function' ) { return; }
1588+
try {
1589+
const mql = self.matchMedia(s);
1590+
if ( mql instanceof self.MediaQueryList === false ) { return; }
1591+
if ( mql.media !== 'not all' ) { return s; }
1592+
} catch(ex) {
1593+
}
1594+
}
1595+
15841596
// https://github.com/uBlockOrigin/uBlock-issues/issues/341#issuecomment-447603588
15851597
// Reject instances of :not() filters for which the argument is
15861598
// a valid CSS selector, otherwise we would be adversely changing the
@@ -1702,6 +1714,7 @@ Parser.prototype.SelectorCompiler = class {
17021714
case ':spath':
17031715
raw.push(task[1]);
17041716
break;
1717+
case ':matches-media':
17051718
case ':min-text-length':
17061719
case ':others':
17071720
case ':upward':
@@ -1878,6 +1891,8 @@ Parser.prototype.SelectorCompiler = class {
18781891
return this.compileCSSDeclaration(args);
18791892
case ':matches-css-before':
18801893
return this.compileCSSDeclaration(args);
1894+
case ':matches-media':
1895+
return this.compileMediaQuery(args);
18811896
case ':matches-path':
18821897
return this.compileText(args);
18831898
case ':min-text-length':
@@ -1918,7 +1933,8 @@ Parser.prototype.proceduralOperatorTokens = new Map([
19181933
[ 'matches-css', 0b11 ],
19191934
[ 'matches-css-after', 0b11 ],
19201935
[ 'matches-css-before', 0b11 ],
1921-
[ 'matches-path', 0b01 ],
1936+
[ 'matches-media', 0b11 ],
1937+
[ 'matches-path', 0b11 ],
19221938
[ 'min-text-length', 0b01 ],
19231939
[ 'not', 0b01 ],
19241940
[ 'nth-ancestor', 0b00 ],

0 commit comments

Comments
 (0)