Skip to content

fix: do not let a foreign end tag close an HTML element#1790

Open
spokodev wants to merge 1 commit into
inikulin:masterfrom
spokodev:fix/foreign-end-tag-namespace
Open

fix: do not let a foreign end tag close an HTML element#1790
spokodev wants to merge 1 commit into
inikulin:masterfrom
spokodev:fix/foreign-end-tag-namespace

Conversation

@spokodev

Copy link
Copy Markdown

The "any other end tag" rule for the in-body insertion mode (WHATWG 13.2.6.4.7, step 3) only matches an open node that is an HTML element; anything else falls through to step 4 (special element check, then ignore).

genericEndTagInBody matched an open element by tag id alone, without checking its namespace. A foreign integration-point element (desc, title, mi, mo, mn, ms, mtext, annotation-xml) whose lowercase name collides with an HTML tag id was therefore wrongly matched, closing an open HTML formatting element that should stay open.

Repro

<svg><desc><b>x</desc>y

The foreign <svg desc> end tag </desc> must not close the open <b>, so <b> stays open and collects the trailing text. Browser-confirmed correct output:

| <html>
|   <head>
|   <body>
|     <svg svg>
|       <svg desc>
|         <b>
|           "xy"

Before this fix parse5 produced b.text = "x" (the <b> was wrongly closed and "y" leaked out as a sibling); the correct result is b.text = "xy".

Fix

Add p.treeAdapter.getNamespaceURI(element) === NS.HTML to the match condition (NS is already imported) so a foreign element falls through to the namespace-aware _isSpecialElement check and the end tag is ignored, as the spec requires.

Tests

Added a focused regression test in packages/parse5/lib/parser/index.test.ts asserting b.text === "xy". It fails on the pristine source (expected "x" to be "xy") and passes with the fix. The full conformance suite stays green at 19,325 cases (unchanged).

The "any other end tag" rule for the in-body insertion mode (WHATWG
13.2.6.4.7, step 3) only matches an open node that is an HTML element.
genericEndTagInBody matched an open element by tag id alone, so a
foreign integration-point element (desc, title, mi, mo, mn, ms, mtext,
annotation-xml) whose lowercase name collides with an HTML tag id was
wrongly matched, closing an open HTML formatting element that should
stay open.

Add a namespace check to the match condition so a foreign element falls
through to the namespace-aware special-element check and the end tag is
ignored, as required by step 4.

Browser-confirmed: `<svg><desc><b>x</desc>y` keeps <b> open so its text
is "xy", not "x".
expect(onParseError).not.toHaveBeenCalled();
});

it('Regression - foreign end tag must not close an HTML element with a colliding tag id', () => {

@43081j 43081j Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
it('Regression - foreign end tag must not close an HTML element with a colliding tag id', () => {
it('end tag in body must not match a foreign element with the same tag ID', () => {

this isn't really a regression since it was never covered, even by the html5lib-tests it seems 😬

also its not that the end tag is foreign, its the reverse. the start tag is foreign and the end tag shouldn't close it

on a side note: this really belongs in html5lib-tests, but its fine here for now i think. maybe lets add a TODO

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.

2 participants