From 52b46eb98bedfa746206e89ac016734cbbed092a Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 13 Mar 2024 14:28:53 -0400 Subject: [PATCH] Add procedural operator `:shadow()` -- status is experimental For internal use by filter list maintainers, do not open issues about this. Left undocumented on purpose. This new procedural operator allows to target elements in the shadow root of an element. subject:shadow(arg) - Description: Look-up matching elements inside the shadow root (if present) of _subject_. - Chainable: Yes - _subject_: Can be a plain or procedural selector. - _arg_: A plain or a procedural selector for the elements to target inside the shadowroot. Example: ..##body > div:not([class]):shadow(div[style]):has(:shadow([data-i18n^="#ad"])) --- .../extension/js/scripting/css-procedural.js | 34 ++++++++++++++++++- src/js/contentscript-extra.js | 32 ++++++++++++++++- src/js/static-filtering-parser.js | 7 ++++ 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/platform/mv3/extension/js/scripting/css-procedural.js b/platform/mv3/extension/js/scripting/css-procedural.js index 8293278be..7f50f8098 100644 --- a/platform/mv3/extension/js/scripting/css-procedural.js +++ b/platform/mv3/extension/js/scripting/css-procedural.js @@ -341,6 +341,38 @@ class PSelectorOthersTask extends PSelectorTask { /******************************************************************************/ +class PSelectorShadowTask extends PSelectorTask { + constructor(task) { + super(); + this.selector = task[1]; + } + transpose(node, output) { + const root = this.openOrClosedShadowRoot(node); + if ( root === null ) { return; } + const nodes = root.querySelectorAll(this.selector); + output.push(...nodes); + } + get openOrClosedShadowRoot() { + if ( PSelectorShadowTask.openOrClosedShadowRoot !== undefined ) { + return PSelectorShadowTask.openOrClosedShadowRoot; + } + if ( typeof chrome === 'object' && chrome !== null ) { + if ( chrome.dom instanceof Object ) { + if ( typeof chrome.dom.openOrClosedShadowRoot === 'function' ) { + PSelectorShadowTask.openOrClosedShadowRoot = + chrome.dom.openOrClosedShadowRoot; + return PSelectorShadowTask.openOrClosedShadowRoot; + } + } + } + PSelectorShadowTask.openOrClosedShadowRoot = node => + node.openOrClosedShadowRoot || null; + return PSelectorShadowTask.openOrClosedShadowRoot; + } +} + +/******************************************************************************/ + // https://github.com/AdguardTeam/ExtendedCss/issues/31#issuecomment-302391277 // Prepend `:scope ` if needed. class PSelectorSpathTask extends PSelectorTask { @@ -471,7 +503,6 @@ class PSelectorXpathTask extends PSelectorTask { class PSelector { constructor(o) { - this.raw = o.raw; this.selector = o.selector; this.tasks = []; const tasks = []; @@ -542,6 +573,7 @@ PSelector.prototype.operatorToTaskMap = new Map([ [ 'min-text-length', PSelectorMinTextLengthTask ], [ 'not', PSelectorIfNotTask ], [ 'others', PSelectorOthersTask ], + [ 'shadow', PSelectorShadowTask ], [ 'spath', PSelectorSpathTask ], [ 'upward', PSelectorUpwardTask ], [ 'watch-attr', PSelectorWatchAttrs ], diff --git a/src/js/contentscript-extra.js b/src/js/contentscript-extra.js index 9dee352d3..34b0ef0bf 100644 --- a/src/js/contentscript-extra.js +++ b/src/js/contentscript-extra.js @@ -242,6 +242,36 @@ class PSelectorOthersTask extends PSelectorTask { } } +class PSelectorShadowTask extends PSelectorTask { + constructor(task) { + super(); + this.selector = task[1]; + } + transpose(node, output) { + const root = this.openOrClosedShadowRoot(node); + if ( root === null ) { return; } + const nodes = root.querySelectorAll(this.selector); + output.push(...nodes); + } + get openOrClosedShadowRoot() { + if ( PSelectorShadowTask.openOrClosedShadowRoot !== undefined ) { + return PSelectorShadowTask.openOrClosedShadowRoot; + } + if ( typeof chrome === 'object' && chrome !== null ) { + if ( chrome.dom instanceof Object ) { + if ( typeof chrome.dom.openOrClosedShadowRoot === 'function' ) { + PSelectorShadowTask.openOrClosedShadowRoot = + chrome.dom.openOrClosedShadowRoot; + return PSelectorShadowTask.openOrClosedShadowRoot; + } + } + } + PSelectorShadowTask.openOrClosedShadowRoot = node => + node.openOrClosedShadowRoot || null; + return PSelectorShadowTask.openOrClosedShadowRoot; + } +} + // https://github.com/AdguardTeam/ExtendedCss/issues/31#issuecomment-302391277 // Prepend `:scope ` if needed. class PSelectorSpathTask extends PSelectorTask { @@ -366,7 +396,6 @@ class PSelectorXpathTask extends PSelectorTask { class PSelector { constructor(o) { - this.raw = o.raw; this.selector = o.selector; this.tasks = []; const tasks = []; @@ -437,6 +466,7 @@ PSelector.prototype.operatorToTaskMap = new Map([ [ 'min-text-length', PSelectorMinTextLengthTask ], [ 'not', PSelectorIfNotTask ], [ 'others', PSelectorOthersTask ], + [ 'shadow', PSelectorShadowTask ], [ 'spath', PSelectorSpathTask ], [ 'upward', PSelectorUpwardTask ], [ 'watch-attr', PSelectorWatchAttrs ], diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js index 491cf0368..48c5f62e7 100644 --- a/src/js/static-filtering-parser.js +++ b/src/js/static-filtering-parser.js @@ -3208,6 +3208,7 @@ class ExtSelectorCompiler { 'matches-path', 'min-text-length', 'others', + 'shadow', 'upward', 'watch-attr', 'xpath', @@ -3862,6 +3863,8 @@ class ExtSelectorCompiler { return this.compileText(arg); case 'remove-class': return this.compileText(arg); + case 'shadow': + return this.compileSelector(arg); case 'style': return this.compileStyleProperties(arg); case 'upward': @@ -3999,6 +4002,10 @@ class ExtSelectorCompiler { compileUpwardArgument(s) { const i = this.compileInteger(s, 1, 256); if ( i !== undefined ) { return i; } + return this.compilePlainSelector(s); + } + + compilePlainSelector(s) { const parts = this.astFromRaw(s, 'selectorList' ); if ( this.astIsValidSelectorList(parts) !== true ) { return; } if ( this.astHasType(parts, 'ProceduralSelector') ) { return; }