From ee9edd418e0cbe846075e6c0d148742f6b6b2ca8 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Fri, 17 Jul 2026 06:09:50 +0800 Subject: [PATCH] fix(domparser): preserve HTML parse modes --- .../wpt-cross-current/failed-cases.txt | 1 - .../wpt-cross-current/passed-cases.txt | 1 + moli-dom/src/native/host/document.rs | 19 ++++++++++ moli-parser/src/stream.rs | 24 ++++++------ .../detached_objects/builders/documents.rs | 18 +++++++++ .../detached_surface/document_state.rs | 8 +++- .../script_vm/tests/dom_elements/detached.rs | 38 +++++++++++++++++++ 7 files changed, 94 insertions(+), 15 deletions(-) diff --git a/moli-benchmark/wpt-cross-current/failed-cases.txt b/moli-benchmark/wpt-cross-current/failed-cases.txt index 3c87454ab7..16486c3d4d 100644 --- a/moli-benchmark/wpt-cross-current/failed-cases.txt +++ b/moli-benchmark/wpt-cross-current/failed-cases.txt @@ -2730,7 +2730,6 @@ dom/ranges/tentative/OpaqueRange-range-updates.html dom/ranges/tentative/OpaqueRange-supported-elements.html dom/ranges/tentative/OpaqueRange-unsupported-elements.html dom/ranges/tentative/OpaqueRange-validation.html -domparsing/DOMParser-parseFromString-html.html domparsing/DOMParser-parseFromString-url-base-pushstate.html domparsing/DOMParser-parseFromString-url-base.html domparsing/DOMParser-parseFromString-url-moretests.html diff --git a/moli-benchmark/wpt-cross-current/passed-cases.txt b/moli-benchmark/wpt-cross-current/passed-cases.txt index b3c90fd45f..aae69a761d 100644 --- a/moli-benchmark/wpt-cross-current/passed-cases.txt +++ b/moli-benchmark/wpt-cross-current/passed-cases.txt @@ -3608,6 +3608,7 @@ dom/traversal/TreeWalker.html dom/window-extends-event-target.html dom/xpath-result-single-node-value-nullable.html domparsing/DOMParser-parseFromString-encoding.html +domparsing/DOMParser-parseFromString-html.html domparsing/DOMParser-parseFromString-stylesheets.html domparsing/createContextualFragment.html domparsing/domparser-spurious-attributes.html diff --git a/moli-dom/src/native/host/document.rs b/moli-dom/src/native/host/document.rs index 6976752602..3caf54b7db 100644 --- a/moli-dom/src/native/host/document.rs +++ b/moli-dom/src/native/host/document.rs @@ -1033,6 +1033,25 @@ impl DomHost { true } + pub fn set_document_quirks_mode_for_handle( + &mut self, + document_handle: DomHandle, + quirks_mode: selectors::matching::QuirksMode, + ) -> bool { + let Some(document) = self + .node_mut(document_handle) + .and_then(|node| node.data_mut().as_document_mut()) + else { + return false; + }; + if document.quirks_mode() == quirks_mode { + return false; + } + document.set_quirks_mode(quirks_mode); + self.record_mutation(MutationScope::QueryState); + true + } + pub fn set_document_default_language_for_handle( &mut self, document_handle: DomHandle, diff --git a/moli-parser/src/stream.rs b/moli-parser/src/stream.rs index 6eb4516fa0..4ff4021899 100644 --- a/moli-parser/src/stream.rs +++ b/moli-parser/src/stream.rs @@ -27,7 +27,9 @@ use super::{ ParserYield, }, live_target::{ParserRuntimeDomSinks, ParserStreamHtmlTreeSinkTarget}, - session::{HtmlParserSession, HtmlParserSessionResult, new_html_tree_sink_session}, + session::{ + HtmlParserSession, HtmlParserSessionResult, HtmlTreeSinkSession, new_html_tree_sink_session, + }, }; pub(super) struct HtmlTreeSinkStream { @@ -424,11 +426,7 @@ pub(crate) fn prepare_parser_script_handoff_for_static_document( } impl HtmlTreeSinkStream { - pub(super) fn from_target_with_scripting( - target: ParserStreamHtmlTreeSinkTarget, - scripting_enabled: bool, - ) -> Self { - let session = new_html_tree_sink_session(target, scripting_enabled); + fn from_session(session: HtmlTreeSinkSession) -> Self { Self { parser: session.parser, script_input: session.script_input, @@ -437,6 +435,13 @@ impl HtmlTreeSinkStream { } } + pub(super) fn from_target_with_scripting( + target: ParserStreamHtmlTreeSinkTarget, + scripting_enabled: bool, + ) -> Self { + Self::from_session(new_html_tree_sink_session(target, scripting_enabled)) + } + #[cfg(any(test, feature = "test-support"))] pub(super) fn from_fragment_target( target: ParserStreamHtmlTreeSinkTarget, @@ -452,12 +457,7 @@ impl HtmlTreeSinkStream { context_local_name, scripting_enabled, ); - Self { - parser: session.parser, - script_input: session.script_input, - parser_script_positions: HashMap::new(), - next_parser_script_position: 0, - } + Self::from_session(session) } fn parser_script_position(&mut self, node_id: NativeNodeId) -> usize { 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 6672200358..5f4902ef4a 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 @@ -195,8 +195,19 @@ pub(crate) fn build_detached_document_object_from_dom_host_with_content_type<'s> character_set: Option<&str>, ) -> Option> { let url = detached_document_url(&parsed); + let quirks_mode = parsed.dom().document()?.quirks_mode(); + let compat_mode = if quirks_mode == selectors::matching::QuirksMode::Quirks { + "BackCompat" + } else { + "CSS1Compat" + }; let document = new_detached_document_shell(scope, kind, url)?; if let Some(state) = detached_state_object(scope, document) { + let _ = state.set( + scope, + v8str(scope, "compatMode").into(), + v8_string(scope, compat_mode)?.into(), + ); if let Some(content_type) = content_type { let _ = state.set( scope, @@ -212,6 +223,13 @@ pub(crate) fn build_detached_document_object_from_dom_host_with_content_type<'s> ); } } + if let Some(document_handle) = detached_native_handle(scope, document) + && let Some(runtime_ptr) = context_host_ptr_from_global_bridge(scope) + { + let _ = unsafe { &mut *runtime_ptr } + .dom_host_mut() + .set_document_quirks_mode_for_handle(document_handle, quirks_mode); + } import_detached_document_children_from_host(scope, document, &parsed)?; Some(document) } diff --git a/moli-renderer-v8/src/native_bridge/document/detached_surface/document_state.rs b/moli-renderer-v8/src/native_bridge/document/detached_surface/document_state.rs index a265cb3cff..20996a2049 100644 --- a/moli-renderer-v8/src/native_bridge/document/detached_surface/document_state.rs +++ b/moli-renderer-v8/src/native_bridge/document/detached_surface/document_state.rs @@ -268,10 +268,14 @@ pub(in crate::native_bridge) fn bridge_detached_document_character_set_callback< pub(in crate::native_bridge) fn bridge_detached_document_compat_mode_callback<'a>( scope: &mut v8::PinScope<'a, '_>, - _args: v8::FunctionCallbackArguments<'a>, + args: v8::FunctionCallbackArguments<'a>, mut rv: v8::ReturnValue<'_, v8::Value>, ) { - set_string_return_value(scope, &mut rv, "CSS1Compat"); + let compat_mode = v8::Local::::try_from(args.get(0)) + .ok() + .map(|document| detached_document_state_string(scope, document, "compatMode", "CSS1Compat")) + .unwrap_or_else(|| "CSS1Compat".to_owned()); + set_string_return_value(scope, &mut rv, &compat_mode); } pub(in crate::native_bridge) fn bridge_detached_document_referrer_callback<'a>( 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 73b176b29a..e38fceb1ff 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 @@ -108,6 +108,44 @@ fn adopted_xml_cdata_uses_html_fragment_serialization() { ); } +#[test] +fn domparser_html_preserves_quirks_mode_and_parses_with_scripting_disabled() { + let mut vm = new_storage_test_vm("https://domparser-html-mode.test/"); + + let result = vm + .eval( + r#" +(() => { + const parser = new DOMParser(); + const quirks = parser.parseFromString( + "", + "text/html" + ); + const standards = parser.parseFromString( + "", + "text/html" + ); + const noscript = parser.parseFromString( + "", + "text/html" + ); + return JSON.stringify({ + quirks: quirks.compatMode, + standards: standards.compatMode, + noscriptChildren: Array.from(noscript.querySelector("noscript").children) + .map(element => element.id) + }); +})() +"#, + ) + .expect("DOMParser HTML parse mode probe should evaluate"); + + assert_eq!( + result, + r#"{"quirks":"BackCompat","standards":"CSS1Compat","noscriptChildren":["first","second"]}"# + ); +} + #[test] fn detached_query_brand_checks_accept_standard_prototype_methods() { let mut vm = new_storage_test_vm("https://detached-query-brand-check.test/");