Skip to content

Use constructable stylesheets for client-created sheets when available - #18

Draft
Pearce-Ropion wants to merge 1 commit into
mainfrom
pearce/sheet-js-review
Draft

Pearce-Ropion wants to merge 1 commit into
mainfrom
pearce/sheet-js-review

Conversation

@Pearce-Ropion

Copy link
Copy Markdown

What is the purpose of this PR?

Stylesheets created on the client with a <style> element require CSP's style-src-elem directive (with 'unsafe-inline' or a nonce). This change prefers Constructable Stylesheets (new CSSStyleSheet() + adoptedStyleSheets) instead, which bypass CSP entirely since no DOM element is inserted. It falls back to the original <style>-element + nonce approach when the browser doesn't support constructable stylesheets.

What should reviewers focus on?

supportsConstructedStylesheets() detects whether adoptedStyleSheets exist, CSSStyleSheet can be called, and replaceSync is available. Confirm this detection guards both the adoption path and the fallback correctly.

details

The feature-detection check at packages/core/src/sheet.js#L27-L35 protects against calling new CSSStyleSheet() or accessing adoptedStyleSheets on an unsupported target (e.g. an older browser or a Node/test environment without polyfills). It uses a try-catch because feature-detection across different runtime shapes is fragile.

The creation path at packages/core/src/sheet.js#L150-L160 only branches into adopted stylesheet construction if the feature-detection passes; otherwise it falls through to the original <style>-element code.

The hydration scan at packages/core/src/sheet.js#L83-L84 now includes both root.styleSheets (from <style> and <link> elements) and root.adoptedStyleSheets (from prior constructable stylesheet adoption). Without this, calling reset() a second time on an adopted-stylesheet-based sheet would fail to rehydrate and duplicate marker rules on every reset. Confirm this merges the arrays in the right order and handles missing properties gracefully.

details

When a sheet is first created with adopted stylesheets, the hydration scan saves the internal rule groups by finding the marker rules. On reset(), the scan runs again to rehydrate — but if reset() had only scanned root.styleSheets (the old behavior), it would see zero stylesheets, fail to find the markers, and create new duplicates.

The fix adds root.adoptedStyleSheets to the scanned list so the rehydration finds the same groups again. The order matters: scanned first are the regular stylesheets (which come earlier logically), then adopted stylesheets. The fallback || [] on each property guards against runtimes where either property doesn't exist.

How is this PR tested?

A new test suite in packages/core/tests/sheet-adopted-stylesheets.js covers adopted stylesheet adoption, rule serialization, and rehydration across reset() calls, plus the fallback to <style> elements when constructable stylesheets are unsupported.

details

The test suite uses a minimal fake CSSOM/DOM in packages/core/tests/internal/fakeDom.js (jsdom doesn't implement constructable stylesheets, and a real browser wasn't available). installFakeGlobals() mocks CSSStyleSheet, document, and window as ambient globals, then returns a restore function that clears them. The test runner executes every test file sequentially in one shared Node process, so leaking these globals would break unrelated packages — the restore call at the end of this file prevents that.

Key coverage: createSheet() adopts a stylesheet when supported (verified by checking adoptedStyleSheets length and membership). A second reset() call must not adopt a second stylesheet or duplicate marker/media-group pairs (the count of marker rules must match names.length). Rules applied after reset() must append to the rehydrated group, not a duplicate one. The fallback path must create a <style> element when replaceSync is absent from the prototype.

🏗️ Generated for Sigma by Claude Code

Avoids CSP style-src conflicts from inserting a <style> element, since
constructable stylesheets adopted via adoptedStyleSheets never touch the
DOM. Falls back to the existing <style>-element approach (with nonce
support) when the browser doesn't support them. Also fixes the hydration
scan to include root.adoptedStyleSheets, so reset() rehydrates existing
adopted-sheet rule groups instead of duplicating them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant