Skip to content

Commit

Permalink
Add procedural operator :shadow() -- status is experimental
Browse files Browse the repository at this point in the history
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"]))
  • Loading branch information
gorhill committed Mar 13, 2024
1 parent 6f54317 commit 52b46eb
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 2 deletions.
34 changes: 33 additions & 1 deletion platform/mv3/extension/js/scripting/css-procedural.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -471,7 +503,6 @@ class PSelectorXpathTask extends PSelectorTask {

class PSelector {
constructor(o) {
this.raw = o.raw;
this.selector = o.selector;
this.tasks = [];
const tasks = [];
Expand Down Expand Up @@ -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 ],
Expand Down
32 changes: 31 additions & 1 deletion src/js/contentscript-extra.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -366,7 +396,6 @@ class PSelectorXpathTask extends PSelectorTask {

class PSelector {
constructor(o) {
this.raw = o.raw;
this.selector = o.selector;
this.tasks = [];
const tasks = [];
Expand Down Expand Up @@ -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 ],
Expand Down
7 changes: 7 additions & 0 deletions src/js/static-filtering-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -3208,6 +3208,7 @@ class ExtSelectorCompiler {
'matches-path',
'min-text-length',
'others',
'shadow',
'upward',
'watch-attr',
'xpath',
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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; }
Expand Down

0 comments on commit 52b46eb

Please sign in to comment.