diff --git a/src/js/contentscript-extra.js b/src/js/contentscript-extra.js index 5c43d721d..ff8c03730 100644 --- a/src/js/contentscript-extra.js +++ b/src/js/contentscript-extra.js @@ -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 ], diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js index f7296e757..33bf8e1e5 100644 --- a/src/js/static-filtering-parser.js +++ b/src/js/static-filtering-parser.js @@ -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 ],