From 5acd07f9d47330c37308fbca6b08ca35b6bc3e18 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Fri, 17 Jul 2026 08:28:12 +0800 Subject: [PATCH] fix(html): preserve document scripting mode --- moli-dom/src/native/document.rs | 18 +++++ moli-dom/src/native/host/document.rs | 41 ++++++++++- .../src/native/html_serialization/engine.rs | 20 +++++- moli-dom/src/native/html_serialization/mod.rs | 23 ++++-- moli-dom/src/native/mod.rs | 17 ++++- moli-dom/src/native/queries.rs | 11 +++ moli-parser/src/html.rs | 47 +++++++++++-- moli-parser/src/live_target.rs | 70 ++++++++++++++++--- .../src/document_runtime/dom_facade.rs | 9 ++- .../child_frames/request_scope.rs | 4 ++ .../detached_objects/builders/documents.rs | 21 ++++-- .../src/native_bridge/document/lifecycle.rs | 16 +++-- .../script_vm/tests/dom_elements/detached.rs | 52 ++++++++++++++ 13 files changed, 312 insertions(+), 37 deletions(-) diff --git a/moli-dom/src/native/document.rs b/moli-dom/src/native/document.rs index 1b24603fcb..fc62d5212b 100644 --- a/moli-dom/src/native/document.rs +++ b/moli-dom/src/native/document.rs @@ -51,6 +51,10 @@ pub struct Document { ready_state: DocumentReadyState, quirks_mode: QuirksMode, kind: DocumentKind, + // The document's HTML scripting flag. Browsing-context policy can still + // override execution, but detached/template documents must retain `false` + // for fragment parsing and serialization even without a window. + scripting_enabled: bool, css_target: Option, default_language: Option>, source_last_modified_ms: Option, @@ -63,6 +67,10 @@ impl Document { } pub fn new_html(url: Url) -> Self { + Self::new_html_with_scripting(url, true) + } + + pub fn new_html_with_scripting(url: Url, scripting_enabled: bool) -> Self { Self { base_url_state: DocumentBaseUrlState::new(&url), url, @@ -70,6 +78,7 @@ impl Document { ready_state: DocumentReadyState::Complete, quirks_mode: QuirksMode::NoQuirks, kind: DocumentKind::Html, + scripting_enabled, css_target: None, default_language: None, source_last_modified_ms: None, @@ -84,6 +93,7 @@ impl Document { ready_state: DocumentReadyState::Complete, quirks_mode: QuirksMode::NoQuirks, kind: DocumentKind::Xml, + scripting_enabled: true, css_target: None, default_language: None, source_last_modified_ms: None, @@ -126,6 +136,10 @@ impl Document { self.kind == DocumentKind::Html } + pub fn scripting_enabled(&self) -> bool { + self.scripting_enabled + } + pub fn fallback_base_url(&self) -> &Url { self.base_url_state.fallback_base_url() } @@ -159,6 +173,10 @@ impl Document { self.content_type = content_type.into().into_boxed_str(); } + pub fn set_scripting_enabled(&mut self, scripting_enabled: bool) { + self.scripting_enabled = scripting_enabled; + } + pub fn set_css_target(&mut self, target: Option) -> bool { if self.css_target == target { return false; diff --git a/moli-dom/src/native/host/document.rs b/moli-dom/src/native/host/document.rs index a0adc6f9c9..921c88303f 100644 --- a/moli-dom/src/native/host/document.rs +++ b/moli-dom/src/native/host/document.rs @@ -22,8 +22,19 @@ impl DomHost { } pub fn create_detached_html_document_with_url(&mut self, url: Url) -> DomHandle { + self.create_detached_html_document_with_url_and_scripting(url, true) + } + + pub fn create_detached_html_document_with_url_and_scripting( + &mut self, + url: Url, + scripting_enabled: bool, + ) -> DomHandle { self.dom.create_node( - NodeData::Document(Box::new(Document::new_html(url))), + NodeData::Document(Box::new(Document::new_html_with_scripting( + url, + scripting_enabled, + ))), None, false, false, @@ -973,6 +984,15 @@ impl DomHost { .map(Document::quirks_mode) } + pub fn document_scripting_enabled_for_handle( + &self, + document_handle: DomHandle, + ) -> Option { + self.node(document_handle) + .and_then(Node::as_document) + .map(Document::scripting_enabled) + } + pub fn document_base_url(&self) -> Option { self.document_base_url_for_handle(self.document_handle()) } @@ -1134,6 +1154,25 @@ impl DomHost { true } + pub fn set_document_scripting_enabled_for_handle( + &mut self, + document_handle: DomHandle, + scripting_enabled: bool, + ) -> bool { + let Some(document) = self + .node_mut(document_handle) + .and_then(|node| node.data_mut().as_document_mut()) + else { + return false; + }; + if document.scripting_enabled() == scripting_enabled { + return false; + } + document.set_scripting_enabled(scripting_enabled); + self.record_mutation(MutationScope::LocalState); + true + } + pub fn set_document_quirks_mode_for_handle( &mut self, document_handle: DomHandle, diff --git a/moli-dom/src/native/html_serialization/engine.rs b/moli-dom/src/native/html_serialization/engine.rs index d4a93d0d87..1a30b9b3e6 100644 --- a/moli-dom/src/native/html_serialization/engine.rs +++ b/moli-dom/src/native/html_serialization/engine.rs @@ -458,6 +458,23 @@ pub(super) fn serialize_html( .then_some(html) } +pub(super) fn serialize_html_with_stored_scripting_state( + dom: &NativeDom, + node_id: NativeNodeId, + target: HtmlSerializationTarget, +) -> Option { + let scripting_enabled_for_node = + |node| dom.node_document_scripting_enabled(node).unwrap_or(false); + let mut html = String::new(); + serialize_html_into_sink( + dom, + node_id, + HtmlSerializationOptions::new(target, &scripting_enabled_for_node), + &mut html, + ) + .then_some(html) +} + pub(in crate::native) fn serialize_html_with_shadow_root_provider( dom: &NativeDom, node_id: NativeNodeId, @@ -483,7 +500,8 @@ pub(super) fn serialize_html_with_limit( node_id: NativeNodeId, max_bytes: usize, ) -> Result, HtmlSerializationLimitExceeded> { - let scripting_enabled_for_node = |_: NativeNodeId| true; + let scripting_enabled_for_node = + |node| dom.node_document_scripting_enabled(node).unwrap_or(false); let mut out = BoundedHtmlSerialization::new(max_bytes); if !serialize_html_into_sink( dom, diff --git a/moli-dom/src/native/html_serialization/mod.rs b/moli-dom/src/native/html_serialization/mod.rs index fe7a953a99..d3af904375 100644 --- a/moli-dom/src/native/html_serialization/mod.rs +++ b/moli-dom/src/native/html_serialization/mod.rs @@ -8,7 +8,9 @@ pub(super) use engine::{ HtmlSerializationTarget, HtmlSerializedShadowRoot, escape_html_attribute_into_string, serialize_html_with_shadow_root_provider, }; -use engine::{serialize_html, serialize_html_with_limit}; +use engine::{ + serialize_html, serialize_html_with_limit, serialize_html_with_stored_scripting_state, +}; /// A bounded serialization stopped before appending bytes past its limit. #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -30,7 +32,12 @@ impl std::error::Error for HtmlSerializationLimitExceeded {} impl NativeDom { pub fn serialize_document(&self) -> String { - self.serialize_document_with_scripting_enabled(true) + serialize_html_with_stored_scripting_state( + self, + self.document_node_id, + HtmlSerializationTarget::ChildrenOnly, + ) + .unwrap_or_default() } pub fn serialize_document_with_scripting_enabled(&self, scripting_enabled: bool) -> String { @@ -44,7 +51,11 @@ impl NativeDom { } pub fn outer_html(&self, node_id: NativeNodeId) -> Option { - self.outer_html_with_scripting_enabled(node_id, true) + serialize_html_with_stored_scripting_state( + self, + node_id, + HtmlSerializationTarget::IncludeNode, + ) } pub fn outer_html_with_scripting_enabled( @@ -74,7 +85,11 @@ impl NativeDom { } pub fn inner_html(&self, node_id: NativeNodeId) -> Option { - self.inner_html_with_scripting_enabled(node_id, true) + serialize_html_with_stored_scripting_state( + self, + node_id, + HtmlSerializationTarget::ChildrenOnly, + ) } pub fn inner_html_with_scripting_enabled( diff --git a/moli-dom/src/native/mod.rs b/moli-dom/src/native/mod.rs index 33ccd4dd71..c8b8ce5f41 100644 --- a/moli-dom/src/native/mod.rs +++ b/moli-dom/src/native/mod.rs @@ -159,13 +159,20 @@ impl NativeDom { } pub fn new_html(final_url: url::Url) -> Self { + Self::new_html_with_scripting(final_url, true) + } + + pub fn new_html_with_scripting(final_url: url::Url, scripting_enabled: bool) -> Self { let document_node_id = NativeNodeId::new(0); let document_node = Node::new( document_node_id, None, None, NodeFlags::new(true), - NodeData::Document(Box::new(Document::new_html(final_url))), + NodeData::Document(Box::new(Document::new_html_with_scripting( + final_url, + scripting_enabled, + ))), ); Self { nodes: NativeNodeStorage::from_node(document_node), @@ -507,7 +514,7 @@ impl NativeDom { .map(|document| document.url().clone()) .unwrap_or_else(|| url::Url::parse("about:blank").expect("about:blank is valid")); let owner_document = self.create_node( - NodeData::Document(Box::new(Document::new_html(url))), + NodeData::Document(Box::new(Document::new_html_with_scripting(url, false))), None, false, false, @@ -2584,6 +2591,12 @@ mod tests { .and_then(Node::owner_document) .expect("template content owner document"); assert_ne!(content_owner, dom.document_node_id()); + assert_eq!( + dom.node(content_owner) + .and_then(Node::as_document) + .map(Document::scripting_enabled), + Some(false) + ); let child = dom.create_element("span"); assert_eq!( diff --git a/moli-dom/src/native/queries.rs b/moli-dom/src/native/queries.rs index 2dba8afb40..a29c1eb303 100644 --- a/moli-dom/src/native/queries.rs +++ b/moli-dom/src/native/queries.rs @@ -285,6 +285,17 @@ impl NativeDom { .map(|document| document.is_html_document()) } + pub fn node_document_scripting_enabled(&self, node_id: NativeNodeId) -> Option { + let node = self.node(node_id)?; + if let Some(document) = node.as_document() { + return Some(document.scripting_enabled()); + } + let owner_document = node.owner_document()?; + self.node(owner_document) + .and_then(Node::as_document) + .map(|document| document.scripting_enabled()) + } + fn collect_matching_elements( &self, root: NativeNodeId, diff --git a/moli-parser/src/html.rs b/moli-parser/src/html.rs index fd9f9c9ba1..13184b7c7d 100644 --- a/moli-parser/src/html.rs +++ b/moli-parser/src/html.rs @@ -417,7 +417,14 @@ impl HtmlParser { } pub fn parse_dom_host(&self, final_url: Url, html: String) -> DomHost { - let stream = self.start_document(final_url); + let target = + ParserStreamHtmlTreeSinkTarget::new_with_declarative_shadow_roots_and_scripting( + final_url, + true, + self.scripting_enabled, + ); + let stream = + HtmlTreeSinkStream::from_target_with_scripting(target, self.scripting_enabled); for chunk in html_chunks(&html) { stream.feed(chunk); } @@ -430,8 +437,13 @@ impl HtmlParser { html: String, ) -> NativeDom { let target = - ParserStreamHtmlTreeSinkTarget::new_with_declarative_shadow_roots(final_url, false); - let stream = HtmlTreeSinkStream::from_target_with_scripting(target, self.scripting_enabled); + ParserStreamHtmlTreeSinkTarget::new_with_declarative_shadow_roots_and_scripting( + final_url, + false, + self.scripting_enabled, + ); + let stream = + HtmlTreeSinkStream::from_target_with_scripting(target, self.scripting_enabled); for chunk in html_chunks(&html) { stream.feed(chunk); } @@ -476,10 +488,12 @@ impl HtmlParser { allow_declarative_shadow_roots: bool, scripting_enabled: bool, ) -> NativeDom { - let target = ParserStreamHtmlTreeSinkTarget::new_with_declarative_shadow_roots( - final_url, - allow_declarative_shadow_roots, - ); + let target = + ParserStreamHtmlTreeSinkTarget::new_with_declarative_shadow_roots_and_scripting( + final_url, + allow_declarative_shadow_roots, + scripting_enabled, + ); let context = QualName::new( None, Namespace::from(context_namespace), @@ -2087,6 +2101,25 @@ mod tests { ); } + #[test] + fn standalone_html_parser_preserves_the_requested_scripting_mode() { + let document = HtmlParser::SCRIPTING_DISABLED.parse_without_declarative_shadow_roots( + Url::parse("https://example.test/").expect("test url"), + "".to_owned(), + ); + assert_eq!( + document + .document() + .map(|document| document.scripting_enabled()), + Some(false) + ); + let noscript = first_element_by_ns(&document, HTML_NS, "noscript"); + assert_eq!( + document.inner_html(noscript).as_deref(), + Some("& <>") + ); + } + #[test] fn mathml_annotation_xml_text_html_is_html_integration_point() { let document = parse_test_document(concat!( diff --git a/moli-parser/src/live_target.rs b/moli-parser/src/live_target.rs index 6c31f172d3..50f4330388 100644 --- a/moli-parser/src/live_target.rs +++ b/moli-parser/src/live_target.rs @@ -2032,15 +2032,15 @@ impl ParserStreamHtmlTreeSinkTarget { Some(move || sink.finish()) } - fn new(final_url: Url) -> Self { - Self::new_with_declarative_shadow_roots(final_url, true) - } - - pub(super) fn new_with_declarative_shadow_roots( + pub(super) fn new_with_declarative_shadow_roots_and_scripting( final_url: Url, allow_declarative_shadow_roots: bool, + scripting_enabled: bool, ) -> Self { - let dom_host = DomHost::from_dom(NativeDom::new(final_url.clone())); + let dom_host = DomHost::from_dom(NativeDom::new_html_with_scripting( + final_url.clone(), + scripting_enabled, + )); let document_handle = dom_host.document_handle(); Self { owned_dom_host: Some(dom_host), @@ -3557,8 +3557,13 @@ impl ParserStreamHtmlTreeSinkTarget { pub(super) fn new_parser_stream_html_tree_sink_target( final_url: Url, + scripting_enabled: bool, ) -> ParserStreamHtmlTreeSinkTarget { - ParserStreamHtmlTreeSinkTarget::new(final_url) + ParserStreamHtmlTreeSinkTarget::new_with_declarative_shadow_roots_and_scripting( + final_url, + true, + scripting_enabled, + ) } pub(super) fn new_parser_stream_html_tree_sink_stream( @@ -3566,7 +3571,7 @@ pub(super) fn new_parser_stream_html_tree_sink_stream( scripting_enabled: bool, ) -> HtmlTreeSinkStream { HtmlTreeSinkStream::from_target_with_scripting( - new_parser_stream_html_tree_sink_target(final_url), + new_parser_stream_html_tree_sink_target(final_url, scripting_enabled), scripting_enabled, ) } @@ -4314,6 +4319,55 @@ fn parser_stream_live_fragment_root_writes_fragment() { ); } +#[test] +fn live_fragment_parser_preserves_noscript_when_scripting_is_disabled() { + let url = Url::parse("https://example.test/").expect("test url"); + let mut dom_host = DomHost::from_dom(NativeDom::new_html_with_scripting(url.clone(), false)); + let document = dom_host.document_handle(); + let html = dom_host.create_element("html"); + let body = dom_host.create_element("body"); + assert!(dom_host.append_child(document, html)); + assert!(dom_host.append_child(html, body)); + let existing_noscript = dom_host.create_element("noscript"); + let existing_text = dom_host.create_text_node("existing&"); + assert!(dom_host.append_child(existing_noscript, existing_text)); + assert!(dom_host.append_child(body, existing_noscript)); + let fragment = dom_host.create_document_fragment_for_document(document); + let ptr = &mut dom_host as *mut DomHost; + let mut effects = DomMutationEffects::default(); + + { + let mut collector = TestMutationEffectCollector { + host: ptr, + effects: &mut effects, + panic_on_mutation: false, + }; + crate::HtmlParser::with_scripting_enabled(false).parse_fragment_into_live_dom( + url, + fragment, + document, + body, + "http://www.w3.org/1999/xhtml", + "body", + "tail", + &mut collector, + false, + ); + } + + assert_eq!( + dom_host + .elements_by_tag_name(fragment, "noscript", false) + .len(), + 1, + "a scripting-disabled live fragment must preserve its noscript root" + ); + assert_eq!( + dom_host.elements_by_tag_name(fragment, "span", false).len(), + 1 + ); +} + #[test] fn live_fragment_parser_uses_template_contents_owner_document() { let url = Url::parse("https://example.test/").expect("test url"); diff --git a/moli-renderer-v8/src/document_runtime/dom_facade.rs b/moli-renderer-v8/src/document_runtime/dom_facade.rs index 86ec4dad8f..92663cb23d 100644 --- a/moli-renderer-v8/src/document_runtime/dom_facade.rs +++ b/moli-renderer-v8/src/document_runtime/dom_facade.rs @@ -585,8 +585,13 @@ impl DocumentRuntime { self.dom_host.create_detached_xml_document() } - pub(crate) fn create_detached_html_document_with_url(&mut self, url: url::Url) -> DomHandle { - self.dom_host.create_detached_html_document_with_url(url) + pub(crate) fn create_detached_html_document_with_url_and_scripting( + &mut self, + url: url::Url, + scripting_enabled: bool, + ) -> DomHandle { + self.dom_host + .create_detached_html_document_with_url_and_scripting(url, scripting_enabled) } pub(crate) fn create_detached_xml_document_with_url(&mut self, url: url::Url) -> DomHandle { diff --git a/moli-renderer-v8/src/native_bridge/context_host/child_frames/request_scope.rs b/moli-renderer-v8/src/native_bridge/context_host/child_frames/request_scope.rs index a8629a4137..e21412a3d4 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/child_frames/request_scope.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/child_frames/request_scope.rs @@ -772,6 +772,10 @@ impl JsContextHost { } self.child_browsing_context_host_for_document_handle(document_handle) .map(|handle| self.child_browsing_context_scripting_enabled(handle)) + .or_else(|| { + self.dom_host() + .document_scripting_enabled_for_handle(document_handle) + }) // DOMParser and document.implementation documents have no browsing // context, so scripting is disabled for their fragment parsers too. .unwrap_or(false) diff --git a/moli-renderer-v8/src/native_bridge/document/detached_objects/builders/documents.rs b/moli-renderer-v8/src/native_bridge/document/detached_objects/builders/documents.rs index 5758c4ac53..9effdea0f3 100644 --- a/moli-renderer-v8/src/native_bridge/document/detached_objects/builders/documents.rs +++ b/moli-renderer-v8/src/native_bridge/document/detached_objects/builders/documents.rs @@ -60,14 +60,19 @@ fn create_native_detached_document_handle_with_url( scope: &mut v8::PinScope<'_, '_>, kind: &str, url: Url, + scripting_enabled: bool, ) -> Option { let runtime_ptr = context_host_ptr_from_global_bridge(scope)?; let runtime = unsafe { &mut *runtime_ptr }; - Some(if kind == "html" { - runtime.create_detached_html_document_with_url(url) + let handle = if kind == "html" { + runtime.create_detached_html_document_with_url_and_scripting(url, scripting_enabled) } else { runtime.create_detached_xml_document_with_url(url) - }) + }; + let _ = runtime + .dom_host_mut() + .set_document_scripting_enabled_for_handle(handle, scripting_enabled); + Some(handle) } fn detached_document_url(parsed: &DomHost) -> Url { @@ -94,6 +99,7 @@ fn new_detached_document_shell<'s>( scope: &mut v8::PinScope<'s, '_>, kind: &str, url: Url, + scripting_enabled: bool, ) -> Option> { let to_string_tag = if kind == "html" { Some("HTMLDocument") @@ -116,7 +122,9 @@ fn new_detached_document_shell<'s>( ); set_detached_document_url_state(scope, state, &url)?; define_detached_state(scope, document, state); - if let Some(handle) = create_native_detached_document_handle_with_url(scope, kind, url) { + if let Some(handle) = + create_native_detached_document_handle_with_url(scope, kind, url, scripting_enabled) + { define_detached_native_handle(scope, document, handle); } install_detached_document_instance_properties(scope, document, kind); @@ -201,12 +209,13 @@ pub(crate) fn build_detached_document_object_from_dom_host_with_content_type<'s> ) -> Option> { let url = detached_document_url(&parsed); let quirks_mode = parsed.dom().document()?.quirks_mode(); + let scripting_enabled = parsed.dom().document()?.scripting_enabled(); let compat_mode = if quirks_mode == selectors::matching::QuirksMode::Quirks { "BackCompat" } else { "CSS1Compat" }; - let document = new_detached_document_shell(scope, kind, url)?; + let document = new_detached_document_shell(scope, kind, url, scripting_enabled)?; if let Some(state) = detached_state_object(scope, document) { let _ = state.set( scope, @@ -328,6 +337,7 @@ pub(in crate::native_bridge::document) fn build_detached_html_document_object<'s scope, "html", Url::parse("about:blank").expect("static about:blank parses"), + false, )?; populate_native_html_document_shell(scope, document, title)?; Some(document) @@ -347,6 +357,7 @@ pub(in crate::native_bridge::document) fn build_detached_document_object<'s>( scope, kind, Url::parse("about:blank").expect("static about:blank parses"), + false, )?; if kind == "xml" && let Some(namespace_uri) = namespace_uri.as_deref() diff --git a/moli-renderer-v8/src/native_bridge/document/lifecycle.rs b/moli-renderer-v8/src/native_bridge/document/lifecycle.rs index 79408d500e..b1da4590d3 100644 --- a/moli-renderer-v8/src/native_bridge/document/lifecycle.rs +++ b/moli-renderer-v8/src/native_bridge/document/lifecycle.rs @@ -805,13 +805,15 @@ fn exec_command_insert_html_target(runtime: &JsContextHost) -> Option } fn input_text_from_html_fragment(runtime: &JsContextHost, value: &str) -> String { - let parsed = HtmlParser::with_scripting_enabled(true) - .parse_fragment_without_declarative_shadow_roots( - runtime.host_document().url().clone(), - "http://www.w3.org/1999/xhtml", - "body", - value.to_owned(), - ); + let document_handle = runtime.dom_host().document_handle(); + let parsed = + HtmlParser::with_scripting_enabled(runtime.document_scripting_enabled(document_handle)) + .parse_fragment_without_declarative_shadow_roots( + runtime.host_document().url().clone(), + "http://www.w3.org/1999/xhtml", + "body", + value.to_owned(), + ); let root = parsed .body_node_id() .unwrap_or_else(|| parsed.document_node_id()); diff --git a/moli-renderer-v8/src/script_vm/tests/dom_elements/detached.rs b/moli-renderer-v8/src/script_vm/tests/dom_elements/detached.rs index f0481b5cfc..cb12da36b8 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_elements/detached.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_elements/detached.rs @@ -298,6 +298,58 @@ fn domparser_html_preserves_quirks_mode_and_parses_with_scripting_disabled() { ); } +#[test] +fn offline_html_documents_parse_and_serialize_noscript_with_scripting_disabled() { + let mut vm = new_storage_test_vm("https://offline-noscript.test/"); + + let result = vm + .eval( + r#" +(() => { + const encoded = '& <>'; + const decoded = '&\u00a0<>'; + const markup = value => ``; + const matches = (node, serialization) => + node.textContent === decoded && node.innerHTML === serialization; + + const live = document.createElement('div'); + live.innerHTML = markup(decoded); + + const parsed = new DOMParser().parseFromString( + `${markup(encoded)}`, + 'text/html' + ); + + const template = document.createElement('template'); + template.innerHTML = markup(encoded); + + const created = document.implementation.createHTMLDocument(''); + created.body.innerHTML = `
${markup(encoded)}
`; + + const contextualDocument = document.implementation.createHTMLDocument(''); + const range = contextualDocument.createRange(); + range.selectNode(contextualDocument.body); + const contextual = range.createContextualFragment(markup(encoded)); + + const written = document.implementation.createHTMLDocument(''); + written.write(`
${markup(decoded)}
`); + + return [ + matches(live.firstChild, decoded), + matches(parsed.body.firstChild, encoded), + matches(template.content.firstChild, encoded), + matches(created.body.firstChild.firstChild, encoded), + matches(contextual.firstChild, encoded), + matches(written.body.firstChild.firstChild, encoded) + ].join('|'); +})() +"#, + ) + .expect("offline noscript parsing and serialization probe should evaluate"); + + assert_eq!(result, "true|true|true|true|true|true"); +} + #[test] fn domparser_xml_preserves_requested_content_type_for_success_and_error_documents() { let mut vm = new_storage_test_vm("https://domparser-xml-content-type.test/");