From e447211fe2d7d7d9f90c26fae38de57cb12fc6ff Mon Sep 17 00:00:00 2001 From: ldm0 Date: Wed, 2 Sep 2026 03:53:23 +0800 Subject: [PATCH] fix(dom): serialize XML innerHTML by document mode --- .../src/native_bridge/element/content.rs | 14 +++-- .../src/script_vm/tests/dom_xhr/shadow_dom.rs | 53 +++++++++++++++++++ moli-renderer-v8/src/xml_serializer.rs | 18 +++++++ 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/moli-renderer-v8/src/native_bridge/element/content.rs b/moli-renderer-v8/src/native_bridge/element/content.rs index f7f418b98e..a23e137a8e 100644 --- a/moli-renderer-v8/src/native_bridge/element/content.rs +++ b/moli-renderer-v8/src/native_bridge/element/content.rs @@ -833,10 +833,16 @@ fn node_inner_text( } fn node_inner_html(runtime: &JsContextHost, handle: DomHandle) -> Option { - let scripting_enabled_for_node = |node| runtime.node_document_scripting_enabled(node); - runtime - .dom_host() - .get_html(handle, &scripting_enabled_for_node, false, &[]) + let dom_host = runtime.dom_host(); + if dom_host + .node_document_is_html_document(handle) + .unwrap_or(true) + { + let scripting_enabled_for_node = |node| runtime.node_document_scripting_enabled(node); + dom_host.get_html(handle, &scripting_enabled_for_node, false, &[]) + } else { + crate::xml_serializer::serialize_native_inner_html(dom_host, handle) + } } fn is_element_or_shadow_root_receiver(runtime: &JsContextHost, handle: DomHandle) -> bool { diff --git a/moli-renderer-v8/src/script_vm/tests/dom_xhr/shadow_dom.rs b/moli-renderer-v8/src/script_vm/tests/dom_xhr/shadow_dom.rs index 0a1e5a7919..b9dae89465 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_xhr/shadow_dom.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_xhr/shadow_dom.rs @@ -3340,6 +3340,59 @@ fn set_html_unsafe_preserves_declarative_shadow_roots() { "function|1|true|true|2|input|span|1|em|true|2|true|false|true|true|true|slot|true" ); } + +#[test] +fn set_html_unsafe_in_xml_documents_uses_xml_fragment_serialization() { + let mut vm = new_storage_test_vm("https://set-html-unsafe-xml.test/"); + + let result = vm + .eval( + r#" +(() => { + const markup = '

test'; + const xml = document.implementation.createDocument(null, 'root', null); + xml.documentElement.setHTMLUnsafe(markup); + + const svg = document.implementation.createDocument( + 'http://www.w3.org/2000/svg', + 'root', + null + ); + svg.documentElement.setHTMLUnsafe(markup); + + const liveSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + liveSvg.setHTMLUnsafe(''); + + return [ + xml.contentType, + xml.documentElement.innerHTML, + xml.documentElement.firstChild.namespaceURI, + svg.contentType, + svg.documentElement.innerHTML, + svg.documentElement.firstChild.namespaceURI, + liveSvg.innerHTML, + liveSvg.firstChild.namespaceURI + ].join('|'); +})() +"#, + ) + .expect("setHTMLUnsafe should serialize XML document fragments as XML"); + + assert_eq!( + result, + concat!( + "application/xml|", + "

test

|", + "http://www.w3.org/1999/xhtml|", + "image/svg+xml|", + "

test

|", + "http://www.w3.org/1999/xhtml|", + "|", + "http://www.w3.org/2000/svg" + ) + ); +} + #[test] fn get_html_serializes_shadow_roots_when_requested() { let mut vm = new_storage_test_vm("https://shadow-get-html.test/"); diff --git a/moli-renderer-v8/src/xml_serializer.rs b/moli-renderer-v8/src/xml_serializer.rs index 79b9878b40..15453ce68c 100644 --- a/moli-renderer-v8/src/xml_serializer.rs +++ b/moli-renderer-v8/src/xml_serializer.rs @@ -109,6 +109,24 @@ pub(crate) fn serialize_native_handle(dom_host: &DomHost, handle: NativeNodeId) ) } +pub(crate) fn serialize_native_inner_html( + dom_host: &DomHost, + handle: NativeNodeId, +) -> Option { + let child_container = dom_host + .node(handle)? + .as_element() + .and_then(Element::template_contents) + .unwrap_or(handle); + let mut next_generated_prefix = 1; + Some(serialize_native_children( + dom_host, + child_container, + &NamespaceContext::default(), + &mut next_generated_prefix, + )) +} + fn serialize_native_node( dom_host: &DomHost, handle: NativeNodeId,