Skip to content

fix(page-tree): map NULL to U+FFFD in escapeCssIdentifier - #309

Open
shuvamk wants to merge 1 commit into
CoreBunch:mainfrom
shuvamk:fix/css-identifier-null-replacement
Open

fix(page-tree): map NULL to U+FFFD in escapeCssIdentifier#309
shuvamk wants to merge 1 commit into
CoreBunch:mainfrom
shuvamk:fix/css-identifier-null-replacement

Conversation

@shuvamk

@shuvamk shuvamk commented Jul 29, 2026

Copy link
Copy Markdown

Summary

escapeCssIdentifier is a vendored implementation of the CSS.escape algorithm (CSSOM §serialize-an-identifier), used to build class-kind selectors (styleRule.ts) and class-attribute tokens. It implements every step of the spec except step 1: "If the character is NULL (U+0000), then append U+FFFD REPLACEMENT CHARACTER to result."

The control-character branch deliberately starts at U+0001:

if (
  (codeUnit >= 0x0001 && codeUnit <= 0x001f) ||   // NULL (0x0000) excluded
  codeUnit === 0x007f ||
  ...

so a U+0000 matches no branch and falls through to the generic escaped += '\\' + char, emitting a backslash followed by a literal NULL byte. The browser's native CSS.escape maps U+0000 to U+FFFD instead.

Reproduction

Input (as code points) Before Native CSS.escape / spec
U+0000 \ + literal U+0000 U+FFFD
a, U+0000, b a\ + U+0000 + b a + U+FFFD + b

Every other step (control chars, DEL, leading digit, -+digit, lone -, allowed set, escape-as-char) already matches the spec — this is a single missing branch.

Fix

Add the U+0000U+FFFD branch as the first step of the per-code-unit loop, exactly where the spec places it.

The function had no direct test coverage, so this adds one alongside the fix — NULL, control characters, leading digit, digit-after-dash, lone dash, and passthrough — all matching native CSS.escape. The test is co-located under src/core/page-tree/__tests__/ and imports the helper relatively, so it neither expands the module barrel nor trips the deep-import gate.

Verification

  • bun run build
  • bun test (6337 pass, 0 fail; the NULL case fails on main and passes with the fix)
  • bun run lint

Checklist

  • Tests cover behavior changes.
  • Docs were updated when behavior, config, deployment, or public surfaces changed — n/a (the docstring already claims CSS.escape parity; this makes the code match it).
  • No compatibility shim was added for old pre-release behavior.
  • No secrets, local databases, uploads, or generated artifacts are included.

escapeCssIdentifier vendors the CSS.escape algorithm but omitted its
first step — a NULL (U+0000) must serialize to the U+FFFD replacement
character. The control-character branch starts at U+0001, so a NULL fell
through to the generic case and was emitted as a backslash followed by a
literal NULL byte, diverging from the browser's native CSS.escape.

Add the missing branch and cover escapeCssIdentifier directly (it had no
tests): NULL, control chars, leading digit, leading dash, and passthrough.

Co-Authored-By: Claude Opus 4.8 <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