Skip to content

fix(parser): recognize CDATA directly inside SVG and MathML integration points - #2540

Open
spokodev wants to merge 3 commits into
fb55:masterfrom
spokodev:fix/cdata-in-integration-points
Open

spokodev wants to merge 3 commits into
fb55:masterfrom
spokodev:fix/cdata-in-integration-points

Conversation

@spokodev

@spokodev spokodev commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #2458. Greptile flagged this case on that PR before it was merged (comment), and it is right.

That PR uses isInForeignContext() to decide whether <![CDATA[ starts a CDATA section. Directly inside an HTML integration point such as foreignObject, title or mi, that is false, because opening one pushes ForeignContext.None. So <svg><foreignObject><![CDATA[x><b>]]> now gives a [CDATA[x comment followed by a live <b>. Before #2458 it gave a single [CDATA[x><b>]] comment.

The markup declaration open state switches to the CDATA section state when the adjusted current node is not an element in the HTML namespace. foreignObject is an SVG element, so this is a CDATA section and its content is text. whatwg/html#4016 was closed on this in July ("Gecko, WebKit, and tests match the standard"), and web-platform-tests/wpt#61253 added the tests. Chromium and parse5 still produce a comment here, so parse5 is not a usable oracle for these inputs.

This adds Parser#isCurrentNodeForeign(), which treats an integration point as being in the namespace it was opened in, and uses it for the CDATA decision in the tokenizer and in oncdata. A tokenizer whose callbacks only implement isInForeignContext gets the same events as before, except for unclosed CDATA at the end of input (see below). The open PR #2492 adds a similar check for the element being opened, for the self-closing flag; this one is about the current node, and the two branches merge without conflicts.

A second commit handles the end of input: unclosed CDATA now goes to oncdata when the current node is SVG or MathML, so <svg><![CDATA[x><b> and <svg><foreignObject><![CDATA[x><b> end with the text x><b> instead of a [CDATA[ comment, as in Firefox and WebKit. The first of those already gave a comment before #2458; Codex flagged the second one below. For a tokenizer with only isInForeignContext, unclosed CDATA now goes to oncdata when that returns true, as closed CDATA already did.

input Firefox 155, WebKit 26.6 Chromium 153, parse5 8.0.1 master this PR
<svg><foreignObject><![CDATA[x><b>]]></foreignObject></svg> text x><b> comment, live <b> comment, live <b> text x><b>
<svg><title><![CDATA[x><b>]]></title></svg> text x><b> comment, live <b> comment, live <b> text x><b>
<math><mi><![CDATA[x><b>]]></mi></math> text x><b> comment, live <b> comment, live <b> text x><b>
<math><annotation-xml><![CDATA[x><b>]]></annotation-xml></math> text x><b> text x><b> comment, live <b> text x><b>
<svg><foreignObject><div><![CDATA[x><b>]]></div></foreignObject></svg> comment, live <b> comment, live <b> comment, live <b> comment, live <b>

The six cases from wpt#61253, run through parseDocument and compared with the trees in that file: master matches 3, this PR matches 6.

Tests: five event tests next to the existing foreign CDATA ones. Four fail on master: foreignObject, mtext and the two unclosed-at-EOF cases. The fifth puts a <p> inside foreignObject and passes on both, as a check that HTML content there still gets a bogus comment. npm run test:vi: 205 passed, and npm run lint passes, after merging the current master.

View guided diff

Summary by CodeRabbit

  • Bug Fixes
    • Corrected CDATA handling in SVG and MathML integration points: CDATA in foreign content is treated as text, while CDATA in the associated HTML context remains an HTML comment.
    • Improved handling of CDATA in HTML paragraphs nested within SVG integration points.
    • Corrected handling of unclosed CDATA at the end of input in SVG and SVG integration-point content.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-11T00:46:44.485950Z 4370e53 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: e33e3ad5-27cb-451b-b01b-c0f878b542a0


📥 Commits

Reviewing files that changed from the base of the PR and between 91388d5 and 4370e53.



⛔ Files ignored due to path filters (1)
  • src/__snapshots__/Parser.events.spec.ts.snap is excluded by !**/*.snap


📒 Files selected for processing (3)
  • src/Parser.events.spec.ts
  • src/Parser.ts
  • src/Tokenizer.ts


Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.




📝 Walkthrough
📝 Walkthrough

Walkthrough

The parser and tokenizer now distinguish foreign integration-point elements from their HTML content when handling CDATA. Unclosed CDATA is emitted as CDATA in XML mode or when the current node is foreign. Event snapshots cover these cases.

Changes

CDATA integration-point handling

Layer / File(s) Summary
Foreign-node CDATA handling
src/Tokenizer.ts, src/Parser.ts, src/Parser.events.spec.ts
The parser reports whether the current node is foreign. The tokenizer uses that status to recognize CDATA, with a fallback to isInForeignContext() when the callback is absent. Unclosed CDATA is emitted as CDATA in XML mode or when the current node is foreign. Event snapshots cover SVG foreignObject, MathML mtext, nested HTML content, and unclosed CDATA at EOF.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~12 minutes

Change: Bug fix

Suggested reviewers: fb55



Merge Risk: ⚪ Minimal · up to 4370e

No confirmed issue prevents merging after normal checks.

Pre-merge checks | Passed 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 3…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the primary parser change: recognizing CDATA directly inside SVG and MathML integration points.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR



  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

I’m a rabbit; I hop by the stream,
Where SVG and MathML meet.
CDATA finds its proper name,
Even when it ends incomplete.
I thump my paws and nibble greens,
Then bound away through parsing dreams.

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no actionable new issues were found.

Summary

This PR treats CDATA directly inside SVG and MathML integration points as text.

  • CDATA inside SVG and MathML integration points stays text.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Tokenizer reads CDATA opener"] --> B{"CDATA enabled or current node foreign?"}
    B -->|Yes| C["Read content as CDATA"]
    B -->|No| D["Read an HTML comment"]
    C --> E["Parser emits text"]
    C --> F["At EOF, emit remaining foreign content as text"]
Loading

Reviews (2) · Last reviewed commit: "Merge branch 'master' into fix/cdata-in-..." · Reviewed by Greptile

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/Parser.ts">

<violation number="1" location="src/Parser.ts:357">
P3: For an integration point, this branch returns the context entry before it (`foreignContext[length - 2]`), i.e. the nearest svg/math ancestor rather than the integration point's actual parent element. Because `emitOpenTag` pushes onto `foreignContext` only for `svg`, `math`, and integration-point elements, any HTML element opened inside foreign content (e.g. `<div>` inside `<svg>`) leaves no entry, so the lookup skips over it. Concretely, for `<svg><div><mi><![CDATA[x]]></mi></div></svg>` the stack is `[svg, div, mi]` with `foreignContext = [None, Svg, None]`; the check reads `foreignContext[1] === Svg` and treats the CDATA as foreign text. Per the HTML spec, `div` inside SVG is an HTML element, so the nested `mi` content is HTML and `<![CDATA[` should be a bogus comment. (The same HTML-in-foreign approximation already existed for `<svg><div><![CDATA[` before this PR; this method extends it to nested integration points.) Worth documenting as an approximation or handling the immediate parent's namespace, since it affects whether CDATA text vs comment events are emitted.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread src/Parser.ts Outdated
htmlIntegrationElements.has(this.stack[this.stack.length - 1])
) {
return (
this.foreignContext[this.foreignContext.length - 2] !==

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: For an integration point, this branch returns the context entry before it (foreignContext[length - 2]), i.e. the nearest svg/math ancestor rather than the integration point's actual parent element. Because emitOpenTag pushes onto foreignContext only for svg, math, and integration-point elements, any HTML element opened inside foreign content (e.g. <div> inside <svg>) leaves no entry, so the lookup skips over it. Concretely, for <svg><div><mi><![CDATA[x]]></mi></div></svg> the stack is [svg, div, mi] with foreignContext = [None, Svg, None]; the check reads foreignContext[1] === Svg and treats the CDATA as foreign text. Per the HTML spec, div inside SVG is an HTML element, so the nested mi content is HTML and <![CDATA[ should be a bogus comment. (The same HTML-in-foreign approximation already existed for <svg><div><![CDATA[ before this PR; this method extends it to nested integration points.) Worth documenting as an approximation or handling the immediate parent's namespace, since it affects whether CDATA text vs comment events are emitted.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/Parser.ts, line 357:

<comment>For an integration point, this branch returns the context entry before it (`foreignContext[length - 2]`), i.e. the nearest svg/math ancestor rather than the integration point's actual parent element. Because `emitOpenTag` pushes onto `foreignContext` only for `svg`, `math`, and integration-point elements, any HTML element opened inside foreign content (e.g. `<div>` inside `<svg>`) leaves no entry, so the lookup skips over it. Concretely, for `<svg><div><mi><![CDATA[x]]></mi></div></svg>` the stack is `[svg, div, mi]` with `foreignContext = [None, Svg, None]`; the check reads `foreignContext[1] === Svg` and treats the CDATA as foreign text. Per the HTML spec, `div` inside SVG is an HTML element, so the nested `mi` content is HTML and `<![CDATA[` should be a bogus comment. (The same HTML-in-foreign approximation already existed for `<svg><div><![CDATA[` before this PR; this method extends it to nested integration points.) Worth documenting as an approximation or handling the immediate parent's namespace, since it affects whether CDATA text vs comment events are emitted.</comment>

<file context>
@@ -341,6 +341,26 @@ export class Parser implements Callbacks {
+            htmlIntegrationElements.has(this.stack[this.stack.length - 1])
+        ) {
+            return (
+                this.foreignContext[this.foreignContext.length - 2] !==
+                ForeignContext.None
+            );
</file context>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked in Chromium, Firefox and WebKit: <div> closes the <svg>, so <svg><div><mi><![CDATA[x]]></mi></div></svg> parses as an empty svg followed by div > mi with a [CDATA[x]] comment. htmlparser2 keeps HTML elements such as div, p and b inside the svg, so <svg><div><![CDATA[x]]> already gives text instead of that comment on master, and with this PR the nested mi follows the same model. Matching the browsers there would mean closing foreign content on those start tags, which I've left out of this PR.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 91388d58e2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/Tokenizer.ts Outdated

This branch has not been deployed

No deployments
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