Skip to content

Parse and serialize template content in HTML output - #686

Open
olavoasantos wants to merge 1 commit into
preserve-html-parser-datafrom
serialize-template-content
Open

olavoasantos wants to merge 1 commit into
preserve-html-parser-datafrom
serialize-template-content

Conversation

@olavoasantos

@olavoasantos olavoasantos commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Problem

HTMLTemplateElement stores its implemented contents in template.content, not in the template element's ordinary child list. Serialization walked only ordinary children, so outerHTML omitted populated template content. In addition, parsing a <template> nested in ordinary HTML placed its descendants in that ordinary child list instead of the template content fragment.

Impact

Minor. Template markup assigned through the exposed innerHTML path could vanish from outerHTML, while parsed nested templates could have the wrong tree shape. Consumers that pass serialized templates through Remote DOM therefore lose or misrepresent implemented template content.

Reproduction

const template = document.createElement('template') as HTMLTemplateElement;
template.innerHTML = '<p class="message">Hello</p>';

template.outerHTML;

Before this change, the final expression returned <template></template> because serialization did not visit template.content. It now returns <template><p class="message">Hello</p></template>.

Change

  • Direct parser descendants between HTML <template> start and end tags into the template's content fragment, restoring the outer insertion target when the template closes.
  • Serialize a template's content fragment between its opening and closing tags.
  • Replace recursive serialization with an explicit work stack, so deeply nested template content can serialize without overflowing the call stack.
  • Limit void-element closing-tag omission to the HTML namespace, preserving non-HTML serialization such as SVG <br></br>.

Tests

Adds packages/polyfill/source/tests/serialization-template.test.ts, covering empty, populated, nested, and reentrantly parsed templates; restoration of surrounding insertion targets; escaping, comments, and void elements inside template content; the HTML-versus-SVG void distinction; and constructed and parsed 6,000-level template serialization with explicit 20-second budgets.

Stack

The restacked layer accepts nullable NamespaceURI values and gives both 6,000-deep serialization tests explicit timeout budgets.

Validation

Fresh GitHub CI on restacked head 27f275e passes:

  • lint;
  • type-check and build;
  • unit tests with coverage;
  • bundle-size checks;
  • Playwright end-to-end tests;
  • the classified Web Platform Test suite.

@olavoasantos
olavoasantos force-pushed the serialize-template-content branch from ea10745 to 83a8503 Compare September 3, 2026 15:29
@olavoasantos
olavoasantos force-pushed the serialize-template-content branch from 83a8503 to 27f275e Compare September 4, 2026 14:31
@olavoasantos
olavoasantos marked this pull request as ready for review September 14, 2026 18:41
@henrytao-me

Copy link
Copy Markdown
Member

The template-content routing and iterative serializer look good. One narrow completion before landing: lazy empty template content still falls back to the element's ordinary children.

At serialization.ts:149-151, [CONTENT] ?? el means the output depends on whether .content has been read:

const template = document.createElement('template');
template.appendChild(document.createElement('span')).textContent = 'ordinary';

template.outerHTML;
// Head: '<template><span>ordinary</span></template>'

void template.content;

template.outerHTML;
// Head: '<template></template>'

Native returns <template></template> both times. template.innerHTML is also empty throughout, and neither child list changes when the content getter materializes an empty fragment. HTML serialization uses a template's content fragment, never its ordinary children.

The incorrect initial output is inherited from base; the new implementation makes it switch after a getter read. This is an incomplete branch of the template fix, not a newly introduced loss of valid content.

Please select actual HTML templates by namespace and exact localName, then treat absent content as empty without allocating it. Keep ordinary-child serialization for ordinary elements, foreign/null-namespace elements named template, and HTML elements whose established localName is Template.

Focused regression coverage:

  • Direct template outerHTML and containing-element innerHTML, before and after reading .content.
  • Lazy, initialized-empty, populated and cleared content, including prefixed HTML templates.
  • Ordinary child identity/parent links and lazy content allocation stay unchanged during serialization; non-template controls still serialize their children.

A build-only candidate for this selection changes exactly four lazy-template results to native behavior in a 167-case matrix; the other 163 results stay unchanged. No production source fix or full candidate suite performed.

Separately, this PR already implements the foreign-namespace portion of the #685 correction. Reuse that guard when correcting/restacking #685 rather than adding another implementation. The remaining established-name/case and entity issues stay with #685; closing-tag/SVG parsing stays with #687.

Validation at 27f275e against actual #685 base 1b7881e: 243 polyfill and 59 focused core tests pass; 8/10 added tests fail base. Locked polyfill TypeScript and formatting checks pass, and reported CI is green. All 48 ordinary parse/round-trip cases and 20 generated trees match native. Serialization-read hook/observer/link/laziness probes pass. These are focused source/test/native results, not full-repository or live Shell/admin/transport sign-off.

@olavoasantos
olavoasantos force-pushed the serialize-template-content branch from 27f275e to ceebf52 Compare September 16, 2026 13:57
@olavoasantos
olavoasantos force-pushed the serialize-template-content branch from ceebf52 to 2f9bb05 Compare September 16, 2026 14:08
@olavoasantos
olavoasantos force-pushed the serialize-template-content branch from 2f9bb05 to ecd3b1e Compare September 16, 2026 20:13
@olavoasantos
olavoasantos removed this pull request from stack #701 September 16, 2026 20:15
@olavoasantos
olavoasantos added this pull request to stack #718 September 16, 2026 20:15
@olavoasantos
olavoasantos force-pushed the serialize-template-content branch from ecd3b1e to 0a39389 Compare September 17, 2026 19:40
@olavoasantos

Copy link
Copy Markdown
Contributor Author

Agreed. [CONTENT] ?? el made serialization depend on whether .content had been read. Serialization now identifies actual HTML templates by namespace plus exact localName and treats absent internal content as empty without invoking the getter or allocating a fragment.

The coverage includes lazy, initialized-empty, populated, cleared, and prefixed HTML templates; foreign/null-namespace and established-case Template controls still serialize ordinary children. It also pins ordinary child identity/links and lazy allocation. The #685 namespace guard stays owned by #685; this layer only reuses it. The new head is 0a39389, fresh CI is green, and this needs another look after the force-update.

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.

4 participants