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"]))
This commit is contained in:
Raymond Hill 2024-03-13 14:28:53 -04:00
parent 6f54317bdf
commit 52b46eb98b
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
3 changed files with 71 additions and 2 deletions

View File

@ -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 // https://github.com/AdguardTeam/ExtendedCss/issues/31#issuecomment-302391277
// Prepend `:scope ` if needed. // Prepend `:scope ` if needed.
class PSelectorSpathTask extends PSelectorTask { class PSelectorSpathTask extends PSelectorTask {
@ -471,7 +503,6 @@ class PSelectorXpathTask extends PSelectorTask {
class PSelector { class PSelector {
constructor(o) { constructor(o) {
this.raw = o.raw;
this.selector = o.selector; this.selector = o.selector;
this.tasks = []; this.tasks = [];
const tasks = []; const tasks = [];
@ -542,6 +573,7 @@ PSelector.prototype.operatorToTaskMap = new Map([
[ 'min-text-length', PSelectorMinTextLengthTask ], [ 'min-text-length', PSelectorMinTextLengthTask ],
[ 'not', PSelectorIfNotTask ], [ 'not', PSelectorIfNotTask ],
[ 'others', PSelectorOthersTask ], [ 'others', PSelectorOthersTask ],
[ 'shadow', PSelectorShadowTask ],
[ 'spath', PSelectorSpathTask ], [ 'spath', PSelectorSpathTask ],
[ 'upward', PSelectorUpwardTask ], [ 'upward', PSelectorUpwardTask ],
[ 'watch-attr', PSelectorWatchAttrs ], [ 'watch-attr', PSelectorWatchAttrs ],

View File

@ -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 // https://github.com/AdguardTeam/ExtendedCss/issues/31#issuecomment-302391277
// Prepend `:scope ` if needed. // Prepend `:scope ` if needed.
class PSelectorSpathTask extends PSelectorTask { class PSelectorSpathTask extends PSelectorTask {
@ -366,7 +396,6 @@ class PSelectorXpathTask extends PSelectorTask {
class PSelector { class PSelector {
constructor(o) { constructor(o) {
this.raw = o.raw;
this.selector = o.selector; this.selector = o.selector;
this.tasks = []; this.tasks = [];
const tasks = []; const tasks = [];
@ -437,6 +466,7 @@ PSelector.prototype.operatorToTaskMap = new Map([
[ 'min-text-length', PSelectorMinTextLengthTask ], [ 'min-text-length', PSelectorMinTextLengthTask ],
[ 'not', PSelectorIfNotTask ], [ 'not', PSelectorIfNotTask ],
[ 'others', PSelectorOthersTask ], [ 'others', PSelectorOthersTask ],
[ 'shadow', PSelectorShadowTask ],
[ 'spath', PSelectorSpathTask ], [ 'spath', PSelectorSpathTask ],
[ 'upward', PSelectorUpwardTask ], [ 'upward', PSelectorUpwardTask ],
[ 'watch-attr', PSelectorWatchAttrs ], [ 'watch-attr', PSelectorWatchAttrs ],

View File

@ -3208,6 +3208,7 @@ class ExtSelectorCompiler {
'matches-path', 'matches-path',
'min-text-length', 'min-text-length',
'others', 'others',
'shadow',
'upward', 'upward',
'watch-attr', 'watch-attr',
'xpath', 'xpath',
@ -3862,6 +3863,8 @@ class ExtSelectorCompiler {
return this.compileText(arg); return this.compileText(arg);
case 'remove-class': case 'remove-class':
return this.compileText(arg); return this.compileText(arg);
case 'shadow':
return this.compileSelector(arg);
case 'style': case 'style':
return this.compileStyleProperties(arg); return this.compileStyleProperties(arg);
case 'upward': case 'upward':
@ -3999,6 +4002,10 @@ class ExtSelectorCompiler {
compileUpwardArgument(s) { compileUpwardArgument(s) {
const i = this.compileInteger(s, 1, 256); const i = this.compileInteger(s, 1, 256);
if ( i !== undefined ) { return i; } if ( i !== undefined ) { return i; }
return this.compilePlainSelector(s);
}
compilePlainSelector(s) {
const parts = this.astFromRaw(s, 'selectorList' ); const parts = this.astFromRaw(s, 'selectorList' );
if ( this.astIsValidSelectorList(parts) !== true ) { return; } if ( this.astIsValidSelectorList(parts) !== true ) { return; }
if ( this.astHasType(parts, 'ProceduralSelector') ) { return; } if ( this.astHasType(parts, 'ProceduralSelector') ) { return; }