Repository navigation
Conversation
|
Status: reproduced on bun 1.4.3 ( Draft. Upstream has not reviewed cloudflare/lol-html#337 (opened 2026-09-08, pinged once on 2026-09-15). Fork state: the fork's After #43266 merges, this PR needs a rebase, and its pin must move to the v3.0.1 port. To request that, comment on this PR. To ship before upstream answers, a maintainer merges oven-sh/lol-html#4 after #43266 lands, and this PR pins the result. That makes Bun's output differ from Cloudflare Workers for void-to-non-void renames until upstream merges. |
|
Updated 5:45 AM PT - Sep 15th, 2026
✅ @robobun, your commit fd65a8b040c5daa8a2e6c1f763f3447c64058b5b passed in 🧪 To try this PR locally: bunx bun-pr 41968That installs a local version of the PR into your bun-41968 --bun |
… name Pin oven-sh/lol-html at 5816ca4 (oven-sh/lol-html#4, the bun-branch port of cloudflare/lol-html#337). `el.tagName = "picture"` on `<img>` rewrote only the start tag, so the following siblings re-parsed as children of the unclosed `<picture>`. lol-html now writes `</picture>` right after the start tag unless the new name is void too. The pin also moves past ea6125b (descendant-selector matching linear in depth), which #41479 pins on its own.
8bed0d3 to
fd65a8b
Compare
Draft until upstream reviews cloudflare/lol-html#337 (fork port: oven-sh/lol-html#4). After #43266 (lol-html v3.0.1) merges, the pin moves to that port.
Problem
el.tagName = "picture"on a void element (img,br) rewrites only the start tag. Nothing closes the new name:<img><p>next</p>becomes<picture><p>next</p>, and the<p>re-parses as a child of<picture>.Element::set_tag_namein lol-html (vendor/lolhtml/src/rewritable_units/element.rs:181). It renames the end tag only whencan_have_contentis true, and a void element has no end tag.Fix
oven-sh/lol-htmlat5816ca4, the port for the v2.7.2 lineage that main pins. For an HTML void element renamed to a non-void name, lol-html now writes</name>right after the start tag, before anyafter()content./>elements in foreign content,remove(),replace().canHaveContentstaysfalse.test/js/workerd/html-rewriter.test.js(new test, fails on bun 1.4.3). Alsobundler_html.Background
HTMLRewriterbinds lol-html, built from theoven-sh/lol-htmlfork at the commit inscripts/build/deps/lolhtml.ts.canHaveContent). Void elements cannot.after()content of a void element lives on its start tag. The end tag is part of that tag's serialization, so it stays ahead of everyafter()call. A userlandel.after("</picture>", { html: true })cannot.Downsides
</p>(an empty paragraph). A GitHub code search found none.Notes
Repro on bun 1.4.3:
Fuzz numbers: 8 of 12 void-to-non-void renames diverged (the 4 that agreed had no following siblings), 157/157 other renames and 51/51 invalid-name throws agreed with the DOM.
Fork lineages. Main pins v2.7.2 plus handler suspension. The fork's
bunbranch moved to v3.0.1 on 2026-09-17 for #43266.5816ca4(fork branchrobobun/void-rename-end-tag-v2.7.2) is the port for the lineage main pins. It also includesea6125b(descendant matching linear in depth), because that lineage is linear; #43266 brings the same change through v3.0.1. oven-sh/lol-html#4 (55edbfd) is the port on the v3.0.1bunbranch.Why
void -> voidemits no end tag:</br>parses as a second<br>. Why foreign/>elements are unchanged:/>closes them whatever the name.remove()/replace()drop the start tag, so no end tag is written.Other bun suites run:
htmlrewriter-additional-bugs,html-rewriter-end-error,test/js/web/html/,bundler_html.lol-html checks on the pinned commit (v2.7.2 lineage):
cargo test --features integration_test174 + 3 + 32 + 2 pass (nine upstream expectations that used an<img>rename as a match marker now include the end tag),cargo clippy --all-targetsno new warnings. The v3.0.1 port:cargo test --features _integration_test210 + 3 + 38 + 2 pass, including a new test that renames a void element before and during a handler suspension. The same patch on upstreammain: 184 lib tests pass.Doubled end tag check (parse5):
<picture></picture></picture>tail,<span></span></span>tailand<amp-img></amp-img></amp-img>tailall parse to one empty element plus the text.<p></p></p>tailparses to two<p>elements.no test proof · iteration 2 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/workerd/html-rewriter.test.js