Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Declarative shadow root adoption #10933

Open
dead-claudia opened this issue Jan 20, 2025 · 3 comments
Open

Declarative shadow root adoption #10933

dead-claudia opened this issue Jan 20, 2025 · 3 comments
Labels
addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest

Comments

@dead-claudia
Copy link

dead-claudia commented Jan 20, 2025

What problem are you trying to solve?

There's no way for declarative shadow roots to declaratively adopt styles, forcing JS to be used to add them. This partially defeats the point of using declarative shadow DOM at all, since it means some styles' application are unavoidably blocked on scripting.

What solutions exist today?

None, really.

How would you solve it?

  1. Add a shadowrootadopt="...", a token list of adopted <style> and <link rel="stylesheet"> adopt IDs.
  2. Add an adoptid attribute to those stylesheet elements. When present, the inner stylesheet is not displayed by the DOM.

You could shim the load behavior with this as the first script:

<script type="module">
function wrap({prototype: p}, key, hackSource) {
    const {get} = Object.getOwnPropertyDescriptor(p, key)
    Object.defineProperty(p, {
        get() { return hackSource.get(this) || get.call(this) },
    })
}

const hackSheet = new WeakMap()
const hackOwner = new WeakMap()
wrap(HTMLLinkElement, "sheet", hackSheet)
wrap(HTMLStyleElement, "sheet", hackSheet)
wrap(SVGStyleElement, "sheet", hackSheet)
wrap(StyleElement, "ownerNode", hackOwner)

const sheets = new Map()

for (const source of document.querySelectorAll("style[adoptid], link[rel~=stylesheet][adoptid]")) {
    if (hackReplacement.has(source)) continue
    const sheet = source.sheet
    if (!sheet) continue
        
    // Detaches the stylesheet, so it no longer applies to the document
    const replacement = source.cloneNode()
    source.replaceWith(replacement)
    // Hide the evidence (mostly)
    hackSheet.set(replacement, sheet)
    hackOwner.set(sheet, replacement)

    // Link the ID
    sheets.set(
        source.getAttribute("adoptid")
            .trimStart()
            .replace(/\s.*$/, ""),
        sheet,
    )
}

for (const t of document.querySelectorAll("template[shadowrootadopt]")) {
    const adopt = t.getAttribute("shadowrootadopt")
    t.removeAttribute("shadowrootadopt")
    for (let id of adopt.trim().split(/ +/g)) {
        const sheet = sheets.get(id)
        if (sheet) {
            t.adoptedStyleSheets.push(elem.sheet)
        }
    }
}
</script>

Anything else?

No response

@dead-claudia dead-claudia added addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest labels Jan 20, 2025
@KurtCattiSchmidt
Copy link

Declarative CSS Modules are another solution to this issue #10673

@sheet is another potential solution: https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/AtSheet/explainer.md

@dead-claudia
Copy link
Author

I have perf concerns with that @sheet proposal that mirror my concerns in w3c/csswg-drafts#11547 about <link> elements. In fact, that's part of why I went with an attribute.

I'll file an issue there on it.

@dead-claudia
Copy link
Author

Updated to use an attribute to make it stop displaying after load.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest
Development

No branches or pull requests

2 participants