Merge pull request #559 from mnaza/fix/documentelement-innerhtml-head-556
fix(dom): keep <head> content when setting documentElement.innerHTML
This commit is contained in:
3 files changed
+45
-9
No files matched your search
@@ -839,18 +839,22 @@ impl DomTree {
|
|||||||
self.append_child(parent_id, text_id);
|
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();
|
let doc = self.document();
|
||||||
for child in self.children(doc) {
|
for child in self.children(doc) {
|
||||||
if let Some(n) = self.get_node(child) {
|
if let Some(n) = self.get_node(child) {
|
||||||
if n.as_element().map(|name| name.local.as_ref() == "html").unwrap_or(false) {
|
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;
|
return child;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -431,7 +431,7 @@ fn op_dom_inner(state: &OpState, cmd: String, arg1: String, arg2: String) -> Str
|
|||||||
Some(name) => obscura_dom::parse_fragment_with_context(&arg2, name),
|
Some(name) => obscura_dom::parse_fragment_with_context(&arg2, name),
|
||||||
None => obscura_dom::parse_fragment(&arg2),
|
None => obscura_dom::parse_fragment(&arg2),
|
||||||
};
|
};
|
||||||
let import_root = fragment.find_body_or_root();
|
let import_root = fragment.fragment_root();
|
||||||
dom.import_children_from(target, &fragment, import_root);
|
dom.import_children_from(target, &fragment, import_root);
|
||||||
}
|
}
|
||||||
"true".into()
|
"true".into()
|
||||||
|
|||||||
@@ -2142,6 +2142,38 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Setting innerHTML on the <html> element parses in the "before head"
|
||||||
|
/// insertion mode, which synthesizes head and body. The importer must keep
|
||||||
|
/// both; it previously returned the synthesized body and dropped the head
|
||||||
|
/// (so a <title>/<meta> assigned this way vanished).
|
||||||
|
#[test]
|
||||||
|
fn documentelement_inner_html_keeps_head_and_body() {
|
||||||
|
let mut rt = setup_runtime("<html><head></head><body></body></html>");
|
||||||
|
let v = rt
|
||||||
|
.evaluate(
|
||||||
|
"(function(){ document.documentElement.innerHTML = '<head><title>T</title></head><body><p>hi</p></body>'; \
|
||||||
|
var t = document.querySelector('title'); var p = document.querySelector('p'); \
|
||||||
|
return (t ? t.textContent : 'no-title') + '|' + (p ? p.textContent : 'no-p'); })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(v, serde_json::json!("T|hi"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Regression guard: innerHTML on an ordinary element still imports the
|
||||||
|
/// parsed nodes directly (no head/body is synthesized for a div context),
|
||||||
|
/// so the fix above must not change the common case.
|
||||||
|
#[test]
|
||||||
|
fn ordinary_element_inner_html_imports_content_directly() {
|
||||||
|
let mut rt = setup_runtime("<html><body><div id=\"d\"></div></body></html>");
|
||||||
|
let v = rt
|
||||||
|
.evaluate(
|
||||||
|
"(function(){ var d=document.getElementById('d'); d.innerHTML='<span>a</span><span>b</span>'; \
|
||||||
|
return d.children.length + '|' + d.textContent; })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(v, serde_json::json!("2|ab"));
|
||||||
|
}
|
||||||
|
|
||||||
/// Issue #463: the same must hold for a template that arrives via innerHTML
|
/// Issue #463: the same must hold for a template that arrives via innerHTML
|
||||||
/// rather than the initial document parse — that is how most frameworks
|
/// rather than the initial document parse — that is how most frameworks
|
||||||
/// inject templates.
|
/// inject templates.
|
||||||
|
|||||||
Reference in new issue
Block a user