Skip to content

Improve inject-css-in-shadow-dom - add ability to inject styleSheet to object's root #581

Description

@AdamWr

Related to - AdguardTeam/AdguardFilters#242087 (comment)

As far as I understand, current scriptlet does not work for some elements because website uses Declarative Shadow DOM https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM#declaratively_with_html and then add elements to it.

Steps to reproduce:

  1. Add to user rules:
fiddle.jshell.net###displayAdCard
fiddle.jshell.net#%#//scriptlet('inject-css-in-shadow-dom', '#displayAdCard, #adBanner { display: none !important; }')
  1. Navigate to - https://jsfiddle.net/ar7vct9L/
  2. Click run
Code, in case if test page will be removed
<style>
  #displayAdCard { height: 250px; background: #fde68a; }
</style>

<div class="content">Outside: page content</div>
<div id="displayAdCard">Outside: page ad (a regular ## rule's job)</div>

<div id="host">
  <template shadowrootmode="open">
    <style>
      #displayAdCard { height: 250px; background: #fde68a; }
    </style>
    <span>I'm in the shadow DOM</span>
    <div class="content">Inside: shadow DOM content</div>
    <div id="displayAdCard">Inside: ad card in the declarative shadow root</div>
  </template>
</div>

<script>
  // Created by the browser's HTML parser: attachShadow is never called for it.
  const declarativeRoot = document.getElementById('host').shadowRoot;
  const adCard = declarativeRoot.getElementById('displayAdCard');

  // adCard is already connected, so adCard.getRootNode() === declarativeRoot
  // when attachShadow runs. The <slot> keeps adCard's own text rendered.
  adCard.attachShadow({ mode: 'open' }).innerHTML = `
    <slot></slot>
    <div id="adBanner">Nested: ad banner in the attached shadow root</div>
  `;

  const nestedRoot = adCard.shadowRoot;
  const isRendered = (element) => element.getClientRects().length > 0;

  console.table({
    'outside content': isRendered(document.querySelector('.content')),
    'outside #displayAdCard': isRendered(document.getElementById('displayAdCard')),
    'inside content': isRendered(declarativeRoot.querySelector('.content')),
    'inside #displayAdCard': isRendered(adCard),
    'nested #adBanner': isRendered(nestedRoot.getElementById('adBanner'))
  });
</script>

All yellow highlighted elements should be hidden.
#displayAdCard inside shadow root is not hidden.

Screenshot Image

I guess in hijackAttachShadow:

export const hijackAttachShadow = (
context: typeof globalThis,
hostSelector: string,
callback: AttachShadowCallback,
): void => {
const handlerWrapper = (target: AttachShadow, thisArg: Element, args: unknown[]): ShadowRoot => {
const shadowRoot: ShadowRoot = Reflect.apply(target, thisArg, args);
if (thisArg && thisArg.matches(hostSelector || '*')) {
callback(shadowRoot);
}
return shadowRoot;
};
const attachShadowHandler: ProxyHandler<AttachShadow> = {
apply: handlerWrapper,
};
context.Element.prototype.attachShadow = new Proxy<AttachShadow>(
context.Element.prototype.attachShadow,
attachShadowHandler,
);
};

we probably could do something like:

const hostRoot = thisArg.getRootNode();
if (hostRoot instanceof ShadowRoot) {
 callback(hostRoot);
}

To inject styleSheet to this element.
Perhaps this could be controlled by an additional parameter.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions