Skip to content

Out of order streaming using <template for> - #73326

Draft
hjanuschka wants to merge 1 commit into
WebKit:mainfrom
hjanuschka:template-for
Draft

hjanuschka wants to merge 1 commit into
WebKit:mainfrom
hjanuschka:template-for

Conversation

@hjanuschka

@hjanuschka hjanuschka commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

6262d4c

Out of order streaming using <template for>
https://bugs.webkit.org/show_bug.cgi?id=324175

Reviewed by NOBODY (OOPS!).

Parse processing instructions in HTML and expose their pseudo-attribute
accessors behind the HTMLProcessingInstructionEnabled setting, which is
enabled by default and acts as a kill switch for parser fallout. Add
HTMLTemplateElement.htmlFor behind the separate HTMLTemplateForEnabled
setting and use <?marker>, <?start>, and <?end> processing instructions as
parser insertion points for <template for>.

Redirect parser output into the selected insertion point, remove replaced
ranges and marker nodes, and preserve parser behavior across fragments,
shadow roots, nested patches, scripts, and DOM mutations.

Tests: fast/parser/html-processing-instruction-enabled.html
       imported/w3c/web-platform-tests/dom/nodes/MutationObserver-characterData.html
       imported/w3c/web-platform-tests/dom/nodes/processing-instruction-attributes.html
       imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/reflect-html-for.html
       imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/

* LayoutTests/fast/parser/html-processing-instruction-enabled-expected.txt: Added.
* LayoutTests/fast/parser/html-processing-instruction-enabled.html: Added.
* LayoutTests/fast/xpath/xpath-functional-test-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/dom/nodes/MutationObserver-characterData.html:
* LayoutTests/imported/w3c/web-platform-tests/dom/nodes/processing-instruction-attributes-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/reflect-html-for-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-append-element-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-append-text-and-element-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-append-to-head-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-case-sensitive-dom-inserted-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-case-sensitive-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-empty-hash-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-foster-parenting-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-gradual-append-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-html-setters-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-innerHTML-ambiguous-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-innerHTML-within-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-invokes-pagehide-with-end-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-invokes-pagehide-without-end-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-move-cross-document-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-move-end-while-appending-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-move-marker-while-appending-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-move-start-while-appending-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-move-while-appending-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-multiple-cases-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-multiple-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-mutation-records-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-nested-patch-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-prepend-element-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-prepend-text-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-replace-children-with-element-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-replace-children-with-text-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-replace-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-sanitization-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-script-children-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-script-eval-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-script-replace-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-shadow-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-shadow-nested-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-shadow-root-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-style-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-style-in-head-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-supersedes-invalid-shadowrootmode-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-title-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-unclosed-content-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/syntax/parsing/html5lib_url_file=html5test-com-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/syntax/parsing/html5lib_url_file=processing-instructions-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/syntax/parsing/html5lib_url_file=tests1-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/syntax/parsing/html5lib_write_file=html5test-com-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/syntax/parsing/html5lib_write_file=processing-instructions-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/syntax/parsing/html5lib_write_file=tests1-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/syntax/parsing/html5lib_write_single_file=html5test-com-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/syntax/parsing/html5lib_write_single_file=processing-instructions-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/syntax/parsing/html5lib_write_single_file=tests1-expected.txt:
* Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml:
* Source/WebCore/dom/ProcessingInstruction.cpp:
(WebCore::ProcessingInstruction::updateAttributesIfNeeded):
(WebCore::ProcessingInstruction::pseudoAttributeValue):
(WebCore::ProcessingInstruction::hasAttributes):
(WebCore::ProcessingInstruction::getAttributeNames):
(WebCore::ProcessingInstruction::getAttribute):
(WebCore::ProcessingInstruction::setAttribute):
(WebCore::ProcessingInstruction::removeAttribute):
(WebCore::ProcessingInstruction::toggleAttribute):
(WebCore::ProcessingInstruction::hasAttribute):
(WebCore::ProcessingInstruction::updateDataFromAttributes):
(WebCore::ProcessingInstruction::checkStyleSheet):
* Source/WebCore/dom/ProcessingInstruction.h:
* Source/WebCore/dom/ProcessingInstruction.idl:
* Source/WebCore/html/HTMLTemplateElement.cpp:
(WebCore::HTMLTemplateElement::insertionTarget const):
(WebCore::HTMLTemplateElement::insertionNextChild const):
(WebCore::HTMLTemplateElement::prepareContentPatching):
(WebCore::HTMLTemplateElement::finishParsingChildren):
* Source/WebCore/html/HTMLTemplateElement.h:
* Source/WebCore/html/HTMLTemplateElement.idl:
* Source/WebCore/html/parser/AtomHTMLToken.h:
(WebCore::AtomHTMLToken::processingInstructionTarget const):
(WebCore::AtomHTMLToken::processingInstructionData const):
(WebCore::AtomHTMLToken::AtomHTMLToken):
* Source/WebCore/html/parser/HTMLConstructionSite.cpp:
(WebCore::insert):
(WebCore::HTMLConstructionSite::insertProcessingInstruction):
(WebCore::HTMLConstructionSite::insertProcessingInstructionOnDocument):
(WebCore::HTMLConstructionSite::insertProcessingInstructionOnHTMLHtmlElement):
(WebCore::HTMLConstructionSite::insertHTMLTemplateElement):
(WebCore::HTMLConstructionSite::insertTextNode):
(WebCore::HTMLConstructionSite::treeScopeForCurrentNode):
(WebCore::HTMLConstructionSite::containerForCurrentNode):
(WebCore::HTMLConstructionSite::ownerDocumentForCurrentNode):
(WebCore::registryForCurrentNode):
* Source/WebCore/html/parser/HTMLConstructionSite.h:
* Source/WebCore/html/parser/HTMLParserOptions.cpp:
(WebCore::HTMLParserOptions::HTMLParserOptions):
* Source/WebCore/html/parser/HTMLParserOptions.h:
* Source/WebCore/html/parser/HTMLToken.h:
(WebCore::HTMLToken::clear):
(WebCore::HTMLToken::processingInstructionTarget const):
(WebCore::HTMLToken::processingInstructionData const):
(WebCore::HTMLToken::beginProcessingInstruction):
(WebCore::HTMLToken::appendToProcessingInstructionTarget):
(WebCore::HTMLToken::appendToProcessingInstructionData):
* Source/WebCore/html/parser/HTMLTokenizer.cpp:
(WebCore::HTMLTokenizer::emitProcessingInstruction):
(WebCore::HTMLTokenizer::processToken):
* Source/WebCore/html/parser/HTMLTokenizer.h:
* Source/WebCore/html/parser/HTMLTreeBuilder.cpp:
(WebCore::HTMLTreeBuilder::processToken):
(WebCore::HTMLTreeBuilder::processProcessingInstruction):
(WebCore::HTMLTreeBuilder::processTokenInForeignContent):
* Source/WebCore/html/parser/HTMLTreeBuilder.h:

Co-authored-by: Noam Rosenthal <nrosenthal@chromium.org>

6262d4c

Misc iOS, visionOS, tvOS & watchOS macOS Linux Windows
✅ 🧪 style ✅ 🛠 ios ✅ 🛠 mac ✅ 🛠 wpe ⏳ 🛠 win
✅ 🧪 bindings ✅ 🛠 ios-sim ✅ 🛠 mac-AS-debug ✅ 🧪 wpe-wk2 ⏳ 🧪 win-tests
✅ 🧪 webkitperl ✅ 🧪 ios-wk2 ✅ 🧪 api-mac ✅ 🧪 api-wpe
✅ 🧪 ios-wk2-wpt ✅ 🧪 api-mac-debug ✅ 🧪 jsc-wpe
✅ 🧪 jsc-x86-64 ✅ 🧪 api-ios ✅ 🧪 mac-wk2 ✅ 🛠 gtk3-gcc
✅ 🛠 🧪 jsc-debug-arm64 ✅ 🛠 ios-safer-cpp ✅ 🧪 mac-AS-debug-wk2 ✅ 🛠 gtk
✅ 🛠 vision ✅ 🧪 mac-wk2-stress ✅ 🧪 gtk-wk2
✅ 🛠 vision-sim ✅ 🧪 mac-intel-wk2 ✅ 🧪 api-gtk
✅ 🧪 vision-wk2 ✅ 🛠 mac-safer-cpp   🛠 playstation
✅ 🛠 tv ✅ 🧪 mac-site-isolation
✅ 🛠 tv-sim
✅ 🛠 watch
✅ 🛠 watch-sim

@Ahmad-S792

Copy link
Copy Markdown
Contributor

@hjanuschka - Can we sync WPT in separate PR and just rebase them in this? I know it is draft but with separate, it might be bit easier to review.

@hjanuschka

Copy link
Copy Markdown
Contributor Author

Done, thanks. I split the WPT sync into #73348 and stacked this PR on top of it. Once #73348 lands, I will rebase this PR onto updated main so only the implementation and resulting baseline updates remain.

@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Sep 7, 2026
@hjanuschka
hjanuschka marked this pull request as ready for review September 8, 2026 16:02
@hjanuschka
hjanuschka requested a review from a team as a code owner September 14, 2026 20:09

@annevk annevk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It seems the parser changes are unconditional. I think we probably want to have a flag for processing instruction support specifically in case there is fallout. That might also be a piece we can land independently from streaming.

@rniwa @cdumez what do you think?

Comment thread Source/WebCore/html/parser/HTMLConstructionSite.cpp Outdated
https://bugs.webkit.org/show_bug.cgi?id=324175

Reviewed by NOBODY (OOPS!).

Parse processing instructions in HTML and expose their pseudo-attribute
accessors behind the HTMLProcessingInstructionEnabled setting, which is
enabled by default and acts as a kill switch for parser fallout. Add
HTMLTemplateElement.htmlFor behind the separate HTMLTemplateForEnabled
setting and use <?marker>, <?start>, and <?end> processing instructions as
parser insertion points for <template for>.

Redirect parser output into the selected insertion point, remove replaced
ranges and marker nodes, and preserve parser behavior across fragments,
shadow roots, nested patches, scripts, and DOM mutations.

Tests: fast/parser/html-processing-instruction-enabled.html
       imported/w3c/web-platform-tests/dom/nodes/MutationObserver-characterData.html
       imported/w3c/web-platform-tests/dom/nodes/processing-instruction-attributes.html
       imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/reflect-html-for.html
       imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/

* LayoutTests/fast/parser/html-processing-instruction-enabled-expected.txt: Added.
* LayoutTests/fast/parser/html-processing-instruction-enabled.html: Added.
* LayoutTests/fast/xpath/xpath-functional-test-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/dom/nodes/MutationObserver-characterData.html:
* LayoutTests/imported/w3c/web-platform-tests/dom/nodes/processing-instruction-attributes-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/reflect-html-for-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-append-element-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-append-text-and-element-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-append-to-head-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-case-sensitive-dom-inserted-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-case-sensitive-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-empty-hash-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-foster-parenting-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-gradual-append-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-html-setters-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-innerHTML-ambiguous-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-innerHTML-within-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-invokes-pagehide-with-end-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-invokes-pagehide-without-end-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-move-cross-document-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-move-end-while-appending-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-move-marker-while-appending-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-move-start-while-appending-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-move-while-appending-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-multiple-cases-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-multiple-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-mutation-records-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-nested-patch-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-prepend-element-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-prepend-text-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-replace-children-with-element-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-replace-children-with-text-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-replace-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-sanitization-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-script-children-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-script-eval-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-script-replace-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-shadow-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-shadow-nested-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-shadow-root-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-style-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-style-in-head-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-supersedes-invalid-shadowrootmode-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-title-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/scripting-1/the-template-element/template-for/template-for-unclosed-content-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/syntax/parsing/html5lib_url_file=html5test-com-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/syntax/parsing/html5lib_url_file=processing-instructions-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/syntax/parsing/html5lib_url_file=tests1-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/syntax/parsing/html5lib_write_file=html5test-com-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/syntax/parsing/html5lib_write_file=processing-instructions-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/syntax/parsing/html5lib_write_file=tests1-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/syntax/parsing/html5lib_write_single_file=html5test-com-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/syntax/parsing/html5lib_write_single_file=processing-instructions-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/syntax/parsing/html5lib_write_single_file=tests1-expected.txt:
* Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml:
* Source/WebCore/dom/ProcessingInstruction.cpp:
(WebCore::ProcessingInstruction::updateAttributesIfNeeded):
(WebCore::ProcessingInstruction::pseudoAttributeValue):
(WebCore::ProcessingInstruction::hasAttributes):
(WebCore::ProcessingInstruction::getAttributeNames):
(WebCore::ProcessingInstruction::getAttribute):
(WebCore::ProcessingInstruction::setAttribute):
(WebCore::ProcessingInstruction::removeAttribute):
(WebCore::ProcessingInstruction::toggleAttribute):
(WebCore::ProcessingInstruction::hasAttribute):
(WebCore::ProcessingInstruction::updateDataFromAttributes):
(WebCore::ProcessingInstruction::checkStyleSheet):
* Source/WebCore/dom/ProcessingInstruction.h:
* Source/WebCore/dom/ProcessingInstruction.idl:
* Source/WebCore/html/HTMLTemplateElement.cpp:
(WebCore::HTMLTemplateElement::insertionTarget const):
(WebCore::HTMLTemplateElement::insertionNextChild const):
(WebCore::HTMLTemplateElement::prepareContentPatching):
(WebCore::HTMLTemplateElement::finishParsingChildren):
* Source/WebCore/html/HTMLTemplateElement.h:
* Source/WebCore/html/HTMLTemplateElement.idl:
* Source/WebCore/html/parser/AtomHTMLToken.h:
(WebCore::AtomHTMLToken::processingInstructionTarget const):
(WebCore::AtomHTMLToken::processingInstructionData const):
(WebCore::AtomHTMLToken::AtomHTMLToken):
* Source/WebCore/html/parser/HTMLConstructionSite.cpp:
(WebCore::insert):
(WebCore::HTMLConstructionSite::insertProcessingInstruction):
(WebCore::HTMLConstructionSite::insertProcessingInstructionOnDocument):
(WebCore::HTMLConstructionSite::insertProcessingInstructionOnHTMLHtmlElement):
(WebCore::HTMLConstructionSite::insertHTMLTemplateElement):
(WebCore::HTMLConstructionSite::insertTextNode):
(WebCore::HTMLConstructionSite::treeScopeForCurrentNode):
(WebCore::HTMLConstructionSite::containerForCurrentNode):
(WebCore::HTMLConstructionSite::ownerDocumentForCurrentNode):
(WebCore::registryForCurrentNode):
* Source/WebCore/html/parser/HTMLConstructionSite.h:
* Source/WebCore/html/parser/HTMLParserOptions.cpp:
(WebCore::HTMLParserOptions::HTMLParserOptions):
* Source/WebCore/html/parser/HTMLParserOptions.h:
* Source/WebCore/html/parser/HTMLToken.h:
(WebCore::HTMLToken::clear):
(WebCore::HTMLToken::processingInstructionTarget const):
(WebCore::HTMLToken::processingInstructionData const):
(WebCore::HTMLToken::beginProcessingInstruction):
(WebCore::HTMLToken::appendToProcessingInstructionTarget):
(WebCore::HTMLToken::appendToProcessingInstructionData):
* Source/WebCore/html/parser/HTMLTokenizer.cpp:
(WebCore::HTMLTokenizer::emitProcessingInstruction):
(WebCore::HTMLTokenizer::processToken):
* Source/WebCore/html/parser/HTMLTokenizer.h:
* Source/WebCore/html/parser/HTMLTreeBuilder.cpp:
(WebCore::HTMLTreeBuilder::processToken):
(WebCore::HTMLTreeBuilder::processProcessingInstruction):
(WebCore::HTMLTreeBuilder::processTokenInForeignContent):
* Source/WebCore/html/parser/HTMLTreeBuilder.h:

Co-authored-by: Noam Rosenthal <nrosenthal@chromium.org>
@hjanuschka

Copy link
Copy Markdown
Contributor Author

Added HTMLProcessingInstructionEnabled setting (default on) as a kill switch for the parser change.

@hjanuschka
hjanuschka requested a review from annevk September 15, 2026 23:23
@annevk

annevk commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

As mentioned on Slack I'd prefer we split this up into smaller patches so we can get it right. I've also filed some issues against DOM and HTML based on looking at this again. Below you'll find an AI review. I can't vouch for everything, but a lot of it seems correct.

AI review

There are three likely bugs, some DOM API conformance issues, and the patch still bundles default-on processing instruction (PI) parsing with <template for>. Line numbers are at 6262d4c. The libxml2 table was checked with a standalone harness that mirrors parseAttributes(); everything else comes from reading the code, so please confirm with tests.

Likely bugs

1. Removing the markers disconnects every iframe in the target. finishParsingChildren() removes <?start> and <?end> with parserRemoveChild() (Source/WebCore/html/HTMLTemplateElement.cpp:197, :200). For parser removals, removeNodeWithScriptAssertion() disconnects all subframes under the parent, not just under the removed child (Source/WebCore/dom/ContainerNode.cpp:243). Any iframe in the patched content, or already in the target, loses its browsing context at </template>. With a <?marker> directly in <body>, that's every iframe in the body. Untested repro:

<div id=t><?start name="e">Loading…<?end></div>
<template for=e><iframe id=f srcdoc=hi></iframe></template>
<script>test(() => assert_not_equals(f.contentWindow, null))</script>

Removing the markers with remove() matches the spec's "remove", and a PI has no subframes, so nothing else gets disconnected.

2. Script runs inside HTMLTreeBuilder::constructTree(). The remove() calls in prepareContentPatching() (HTMLTemplateElement.cpp:147) fire pagehide in removed iframes synchronously; the pagehide WPTs depend on that. Mutation events fire too, where they're enabled.

  • If the <template for> itself came from document.write(), a handler calling document.write() re-enters constructTree(). That should trip ASSERT(!m_destructionProhibited) (HTMLTreeBuilder.cpp:349).
  • In normal streaming, the same handler goes through document.open() instead and aborts the parser mid-token.
  • WebKit runs custom element constructors outside the tree builder, under ThrowOnDynamicMarkupInsertionCountIncrementer (HTMLDocumentParser.cpp:244), to prevent exactly this. I found no memory-safety problem, because refcounting keeps everything alive.

Untested repro for the re-entrant case:

<div id=t><?start name="p"><iframe></iframe><?end></div>
<script>
  document.querySelector("iframe").contentWindow.onpagehide = () => document.write("<p>");
  document.write("<template for=p>x</template>");
</script>

Wrap the removal in that incrementer, or better, run it from executeQueuedTasks(). The spec has the same gap.

3. Patched content leaves sibling and positional styles stale. ChildChangeInvalidation::invalidateAfterChange() returns early for parser insertions (Source/WebCore/style/ChildChangeInvalidation.cpp:381). It assumes the parser only appends to parents that haven't finished parsing. Patches insert into finished parents, before existing siblings, so :last-child, :nth-*, + and ~ can stay wrong. That shows most when the page shell has already rendered, which is the point of streaming. Untested repro:

<style>li:last-child { color: green }</style>
<ul><li id=a>A</li><?marker name="m"></ul>
<script>getComputedStyle(a).color;</script>
<template for=m><li>B</li></template>
<script>test(() => assert_not_equals(getComputedStyle(a).color, "rgb(0, 128, 0)"))</script>

The target needs sibling and positional invalidation when the patch finishes, for the forward-direction rules too, not just what invalidateAfterFinishedParsingChildren() covers.

ProcessingInstruction attribute API (on by default)

4. Inserting a PI throws away its attribute map. The dirty flag is set in checkStyleSheet() (Source/WebCore/dom/ProcessingInstruction.cpp:263). postConnectionSteps() also calls that, so insertion re-parses the data. After pi.setAttribute("$", "v"); document.body.append(pi), getAttribute("$") returns null instead of "v", and $ is one of the names the WPT treats as valid. Set the flag from the data-change path (CharacterData::setDataAndUpdate()) instead.

5. libxml2 doesn't implement the pseudo-attribute grammar the DOM Standard references. Results for parseAttributes() plus the re-scan:

data spec patch
a="x<TAB>y" x<TAB>y x y (newlines too)
xmlns="urn:x" a="1" xmlns, a nothing
a="1" xmlns:p="urn:x" a, xmlns:p a
a="1"/><attrs b="2" error a

It also makes libxml2 reachable with arbitrary strings from any page, through getAttribute() and through marker scanning. A small parser for the PseudoAtts grammar would fix both.

6. The new ProcessingInstruction(target, data) constructor is missing. idlharness doesn't catch this because the imported interfaces/dom.idl is stale.

7. ProcessingInstruction::hasAttributes() hides Node::hasAttributes() const. The base version returns false for PIs. Rename it and use [ImplementedAs].

Parser

  • Tree construction looks right. The html5lib processing instruction tests pass 123/123.
  • Tokenizer: the target is stored twice, in m_temporaryBuffer and in a new 256-character HTMLToken buffer that's cleared on every token. One small Latin-1 buffer would do. Also drop the static_cast<char> (HTMLTokenizer.cpp:1032) and the String allocated just to compare against "xml" (:1036).
  • Rebase needed: it conflicts in HTMLToken::clear() with 321805@main, which switched to shrink(0).
  • Small things:
    • HTMLParserOptions::templateForEnabled is set but never read; the code checks document().settings() instead (HTMLConstructionSite.cpp:644).
    • The hasValidShadowRootMode loop parses shadowrootmode again, though the declarative shadow DOM block below already does (:600).
    • If script appends a marker PI directly to a <template> element, insertion hits ASSERT(!hasTagName(templateTag)) (ContainerNode.cpp:795, :1129). That's a debug-only assert, but web-reachable.

Tests

  • MutationObserver-characterData.html was edited in place. The next import will revert it, and upstream still expects "?processing data?". Keep the test unmodified with a FAIL baseline and fix it upstream to expect "data". The subtest is named "ProcessingInstruction", so a PI was always intended.
  • Three FAIL baselines are upstream test bugs, so the patch's behavior is right:
    • "Unclosed element… with only start" expects <span>New content</span>, but the template only contains "New".
    • template-for-move-cross-document.html expects "A" right before the span, but the <script> element sits between them.
    • template-for-only-html-001.html loads resources/template-for.xhtml, which doesn't exist upstream.
  • The Sanitizer and stream/append API failures are expected, because WebKit doesn't implement those APIs.
  • WPTs worth adding:

Landing

  • Performance follow-ups:
    • finding markers walks the whole <html> subtree for every <template for>;
    • the preload scanner treats <template for> contents as inert, so nothing in a patch is preloaded.

@hjanuschka
hjanuschka marked this pull request as draft October 5, 2026 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merging-blocked Applied to prevent a change from being merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants