Make var()/env() declarations engine-proof at the import boundary#21
Merged
Conversation
CSS engines disagree about what their CSSOM exposes for declarations whose value contains a substitution function: Chromium enumerates the shorthand's longhands with empty values (authored text survives only in cssText), while happy-dom destroys the declaration at parse time — each longhand reports the bare var() text and cssText serialises the same mangle, so the authored declaration is unrecoverable. The importer's Chromium-specific cssText recovery papered over half of this; tests exercised a different (lossy) path than production, and inline style="" harvesting had no recovery at all in either engine. New @core/css-substitution module: before the engine parse, every declaration whose value uses var()/env() is encoded as a marker custom property (all engines preserve custom properties verbatim — validated in Chromium and happy-dom) and decoded back after. Output is byte-faithful and identical across engines by construction: - cssToStyleRules encodes the stylesheet before replaceSync and decodes in parseDeclarations; recoverSubstitutionShorthands is deleted. - harvestInlineStyles re-encodes the authored style attribute, fixing shorthand+var inline styles that were silently lost in BOTH engines. - @Keyframes / @font-face blocks pass through unencoded (keyframes are captured as raw CSS; a marker would leak into published output). cssToStyleRules shrank below its grandfathered cap; ratcheted down. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up from the import-fidelity work (#15/#19): the test harness (happy-dom) and production (Chromium) exposed DIFFERENT lossy views of CSS declarations whose value uses
var()/env():cssText(covered by an engine-specific recovery hack).var()text (1px solidgone), andcssTextserialises the same mangle, so the authored declaration is unrecoverable. Tests therefore exercised a lossy path production never sees — exactly the bug class behind "all borders dropped".style=""harvesting had no recovery at all:style="border: 1px solid var(--x)"was silently lost in both engines.Fix — don't let engines see substitution declarations
New
@core/css-substitutionleaf module: before any engine parse, every declaration whose value containsvar(/env(is encoded as a marker custom property (all engines preserve custom properties verbatim — validated empirically in Chromium and happy-dom), and decoded back afterwards. Output is byte-faithful and identical across engines by construction:cssToStyleRulesencodes beforereplaceSync, decodes inparseDeclarations; the Chromium-specificrecoverSubstitutionShorthandsis deleted.harvestInlineStylesre-encodes the authoredstyleattribute — fixing inline shorthand+var declarations lost in both engines.@keyframes/@font-faceblocks pass through unencoded (keyframes are captured as raw CSS; a marker would leak into published output) — covered by tests incl. vendor-prefixed keyframes.cssToStyleRules.tsshrank below its grandfathered size cap; ratcheted down per the gate.Test plan
🤖 Generated with Claude Code