Opened 7 weeks ago
Last modified 4 weeks ago
#65967 new defect (bug)
HTML API: Fuzz-test and fix detections in 7.2
| Reported by: | jonsurrell | Owned by: | |
|---|---|---|---|
| Priority: | normal | Milestone: | 7.2 |
| Component: | HTML API | Version: | |
| Severity: | normal | Keywords: | has-patch has-unit-tests |
| Cc: | Focuses: |
Description
The purpose of this ticket is to track work to fuzz-test the HTML API against inputs that could raise issues or bugs and then fix them in 7.2.
Previously:
Change History (6)
This ticket was mentioned in PR #13208 on WordPress/wordpress-develop by @jonsurrell.
7 weeks ago
#1
- Keywords has-patch has-unit-tests added
This ticket was mentioned in PR #12518 on WordPress/wordpress-develop by @jonsurrell.
7 weeks ago
#2
Removing an attribute could accidentally change tag semantics. For example, removing attr from <svg><g /attr>ok produced <svg><g />ok, producing a self-closing flag on the g tag that was not present before.
Trac ticket: https://core.trac.wordpress.org/ticket/65967
Alternative to #12368.
## Use of AI Tools
AI assistance: Yes
Tool(s): Codex
Model(s): GPT-5
Used for: Diagnosis, tests, implementation, adversarial review, and verification. All generated changes were reviewed before submission.
This ticket was mentioned in PR #9657 on WordPress/wordpress-develop by @jonsurrell.
7 weeks ago
#3
</br> gets special treatment in the HTML standard. It creates a BR element with no attributes (regardless of whether it _appears_ to have attributes in the HTML like </br attr-name>).
- Stop on
</br>in::next_tag()like other openers. - Return
array()from::get_attribute_names_with_prefix()on</br>instead ofnulllike true close tags. - Add tests.
To do:
- [ ] Ensure
</br>works with functions like::set_attribute()and::add_class(). These will require rewriting the tag to<br attrs …>or the attributes will be discarded.
Trac ticket: https://core.trac.wordpress.org/ticket/65967
This ticket was mentioned in PR #7312 on WordPress/wordpress-develop by @jonsurrell.
6 weeks ago
#4
Improve support for content appearing after BODY, HTML nodes where
possible. Some HTML may be representable by the HTML API without
introducing ordering problems.
When HTML moves to the "after-{body,html,…}" insertion modes, BODY and
HTML remain on the stack of open elements. Certain content is inserted
in place (comments), some content is inserted in BODY (white space
text), and most other content will switch back to the "in body"
insertion mode.
The difficulty with the HTML API is that the result in the tree may be
out-of-order compared to the positioning of the corresponding tokens in
the HTML input text. For example:
</body>FOO
Produces this tree where the ordering is the same even though a BODY
closer is reached:
[DOCUMENT]
└HTML
├HEAD
├BODY
│ └#text: FOO
└#comment: comment
Compare with this case:
</body>FOO
The tree is identical, the FOO text is _out of order_ in the tree
compared to the HTML string. This case continues to be invalid because
when the FOO text needs to be reached, the parser would have already
reached a BODY tag closer and content outside of the body:
[DOCUMENT]
└HTML
├HEAD
├BODY
│ └#text: FOO
└#comment: comment
This approach deviates from the explicit steps in the spec and will pop
the BODY and HTML tags from the stack of open elements when content
should appear outside of them. Flags are set for each "after-" mode to
prevent returning to BODY and then continuing to produce content
outside, which would produce the unsupported out-of-order behavior.
See https://html.spec.whatwg.org/#parsing-main-afterbody.
Trac ticket: https://core.trac.wordpress.org/ticket/65967
This ticket was mentioned in PR #12539 on WordPress/wordpress-develop by @jonsurrell.
6 weeks ago
#5
Ensure CDATA is parsed correctly at integration points.
For example <svg><foreignobject> is an element in the svg namespace and an integration point. This means it processes tokens based on the current insertion mode (not following the foreign content rules). But since it's not an HTML element, it supports CDATA sections.
Whether CDATA sections are recognized or not by the tokenizer depends solely on the adjusted current node's namespace. If the node is not in the HTML namespace, then CDATA sections are recognized.
Integration points are used to determine whether to use the current insertion mode or to follow rules for parsing tokens in foreign content. Integration points have no impact on CDATA sections.
See:
- https://github.com/whatwg/html/issues/4016
- https://github.com/whatwg/html/issues/8558
- https://github.com/web-platform-tests/wpt/pull/61253
- https://github.com/web-platform-tests/wpt/pull/61303
- https://github.com/WebKit/WebKit/pull/69464
Trac ticket: https://core.trac.wordpress.org/ticket/65967
## Use of AI Tools
AI assistance: Yes
Tool(s): Codex, Claude Code
Model(s): GPT-5.6 sol, Claude Fable 5.1
Used for: Implementation, tests, review
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
## Problem
Inside a tag, a
/is ignored unless it's immediately followed by>, in which case it's the self-closing flag. When an attribute is directly preceded by a/, removing just the attribute's own span can leave that/adjacent to the>, introducing a self-closing flag that wasn't in the input:Other affected shapes:
<g/attr>→<g/>,<g a/b>(removeb) →<g a/>,<g a="x"/b>(removeb) →<g a="x"/>, and duplicated attributes like<g /a /a>→<g / />.## Fix
Attribute removals now also consume any
/characters immediately preceding the attribute. This is safe because a/can only appear directly before an attribute as a separator: tag names and attribute names terminate at/, and inside an unquoted value/is part of the value rather than preceding an attribute.Two consequences of consuming the slashes are handled:
<g/a="x"b>. Removing/a="x"outright would merge the tag name with the following attribute (<gb>);<g c/a="x"b>would become<g cb>. When the consumed slashes were the only separator and an attribute immediately follows, a single space takes their place:<g b>,<g c b>.<g/a>), the removal now starts at the same offset whereset_attribute()inserts new attributes. Lexical updates were ordered by start then by text, which placed the removal (empty text) before the insertion and corrupted the output (<g> b="x"/a>). Zero-length insertions now sort before consuming replacements at the same offset.The same logic applies when removing duplicated attributes.
Whitespace between a
/and the attribute is intentionally left alone (<g / attr>→<g / >), since/ >is not a self-closing flag.## Testing
test_remove_attribute_preserves_tag_semantics: data provider of slash/quote/equals shapes; asserts exact output, then re-parses and checks the tag name, self-closing flag, remaining attribute set, and following text are unchanged.test_set_and_remove_attribute_at_tag_name_end: combinations ofset_attribute()/add_class()andremove_attribute()where the removal reaches the tag-name end.Beyond the unit tests, a combinatorial fuzzer over malformed tag shapes (separators
' ',/,//,/,''…; attribute shapesa,a=b,a="b",a=/,a=b/,=,=a,a ="b"…; closers>,/>,/ >,//>…; up to three attributes; duplicates) appliedremove_attribute,set_attribute,add_class,remove_classand combinations, then re-parsed the output and diffed the token stream against the expected change. Before this PR: ~83k of 1.6M checks diverged. After: 0 of 2.3M, excluding one unrelated pre-existing class noted below.## Out of scope
The fuzzer also surfaced a pre-existing
set_attribute( $name, true )issue: a bare boolean name emitted directly before whitespace followed by=merges with it. E.g.<x =>+set_attribute( 'b', true )→<x b =>, which parses asb=""and drops the=attribute. That's independent of removal and not addressed here.## Trac ticket:
https://core.trac.wordpress.org/ticket/65967
---
🤖 Generated with Claude Code