Skip to content

fix: keep effective style guards out of host markup - #497

Open
Mohamed Mansour (mohamedmansour) wants to merge 2 commits into
mainfrom
mohamedmansour-fix-transient-style-guard
Open

fix: keep effective style guards out of host markup#497
Mohamed Mansour (mohamedmansour) wants to merge 2 commits into
mainfrom
mohamedmansour-fix-transient-style-guard

Conversation

@mohamedmansour

@mohamedmansour Mohamed Mansour (mohamedmansour) commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • use the parsed anonymous shadow guard as the sole no-FOUC guard when CSSOM confirms it is active
  • reserve inline host mutation for the unavailable-shadow-guard fallback and restore authored values, priorities, and style-attribute presence
  • cover host serialization during linked stylesheet mounting, authored important styles, CSP fallback, release transitions, cancellation, and cleanup

Root cause

The 0.0.26 mount guard always wrote temporary transition-property and visibility declarations to the custom-element host even when its stronger first-layer shadow rule had parsed successfully. Author code serializing live DOM during the pending mount could persist those framework-owned declarations as input.

Design rationale

CSS cascading rules give important declarations from the inner shadow context precedence over outer author declarations, including inline important styles. Important declarations also reverse cascade-layer order, so the prepended anonymous first layer wins over later component layers. Chromium, Firefox, and WebKit all confirmed the guard computes transition-property: none and visibility: hidden without changing host markup.

A CSSOM parse check is preferable to a computed-style probe: the guard CSS and selector are framework constants, while getComputedStyle() would force style resolution on every mount and cannot distinguish the guard from authored hidden styles. If CSP or parsing prevents the sheet from becoming available, the existing inline fallback remains and content stays detached until linked styles settle.

Regression evidence

Before the production fix, the new browser test failed because outerHTML changed from the authored style to include transition-property: none !important; visibility: hidden !important while the external stylesheets were pending.

Performance

Chromium microbenchmarks alternated the pre-fix and fixed implementations and included a pending-style flush plus guard release:

Effective guard workload Before After Median change
2,000 mounts, no authored inline style 7.9 ms 5.4 ms -31.6%
2,000 mounts, authored important values 9.3 ms 6.5 ms -30.1%

P95 improved from 12.1 ms to 6.4 ms and from 11.2 ms to 7.7 ms respectively. The common path removes four inline-style reads, two writes, restoration reads/writes, mutation-observer visibility, and host capture from the pending release closure.

The rare unavailable-shadow-guard fallback adds exact style-attribute restoration. An isolated 5,000-fallback microbenchmark added 0.5 ms with no authored style and 1.2 ms with authored values, about 0.10-0.24 microseconds per fallback. A 3,000-pending-mount CDP sample reduced median V8 heap by about 33 bytes per mount and kept the DOM node count identical; native/embedder heap readings were noisy and are not used as a claim.

The minified production framework bundle changes from 78,447 to 78,588 bytes, or 24,533 to 24,573 bytes gzip: +141 raw / +40 gzip bytes.

Validation

  • framework TypeScript and E2E typechecks
  • targeted guard lifecycle tests in Chromium, Firefox, and WebKit: 4/4 per engine
  • full CSS-link suite: 18/18
  • full framework suite: 275 passed, 2 skipped
  • cargo xtask check
  • all GitHub PR checks

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: dd179fee-1085-4b47-b973-c065a7e089a6
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