fix(dom): keep <head> content when setting documentElement.innerHTML

Setting innerHTML on the <html> element parses the fragment in its own context
(added in a2fd4d1), which runs html5ever's "before head" insertion mode and
synthesizes both <head> and <body> under the fragment root. The importer's
helper walked into the root <html> and, if it found a <body> child, returned
that body -- so import_children_from imported only the body's children and any
assigned <head> content (<title>, <meta>, <link>) was silently dropped.

A <body> child of the fragment root only appears in this <html>-context case;
every other element context leaves parsed content directly under the root and
already fell through to returning it. Rename find_body_or_root -> fragment_root
and return the root <html> element so both head and body children import.
Behavior for all non-<html> contexts is unchanged.

Fixes #556
This commit is contained in:
Andrey Mnatsakanov committed 2026-08-04 17:41:57 +02:00
1 parent f0541f40a4
commit d0d8617ca6
3 files changed
+45 -9

No files matched your search

+12 -8
View File
@@ -839,18 +839,22 @@ impl DomTree {
self.append_child(parent_id, text_id);
}
pub fn find_body_or_root(&self) -> NodeId {
/// The node whose children are a parsed fragment's top-level nodes: the
/// synthetic root `<html>` element html5ever wraps a fragment in, or the
/// document if there is none. Importing this node's children reproduces the
/// fragment as written.
///
/// This must NOT descend into a synthesized `<body>`. A `<body>` child of the
/// root only appears when the fragment is parsed in the `<html>` context
/// (documentElement.innerHTML), where html5ever's "before head" mode
/// synthesizes both `<head>` and `<body>`; returning the body there dropped
/// the head siblings. Every other element context leaves the parsed content
/// directly under the root, so it is unaffected.
pub fn fragment_root(&self) -> NodeId {
let doc = self.document();
for child in self.children(doc) {
if let Some(n) = self.get_node(child) {
if n.as_element().map(|name| name.local.as_ref() == "html").unwrap_or(false) {
for html_child in self.children(child) {
if let Some(hc) = self.get_node(html_child) {
if hc.as_element().map(|name| name.local.as_ref() == "body").unwrap_or(false) {
return html_child;
}
}
}
return child;
}
}