From db3de692ddcea2be6da643f10b4c1f91297f730c 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 ++++++++++ .../detached_objects/builders/documents.rs | 28 +++++++++++--- .../detached_surface/document_state.rs | 8 +++- .../detached_document_and_element_surfaces.rs | 38 +++++++++++++++++++ 6 files changed, 87 insertions(+), 8 deletions(-) diff --git a/moli-benchmark/wpt-cross-current/failed-cases.txt b/moli-benchmark/wpt-cross-current/failed-cases.txt index 00aece7268..5261270de0 100644 --- a/moli-benchmark/wpt-cross-current/failed-cases.txt +++ b/moli-benchmark/wpt-cross-current/failed-cases.txt @@ -2553,7 +2553,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 3976a2be71..272805a90e 100644 --- a/moli-benchmark/wpt-cross-current/passed-cases.txt +++ b/moli-benchmark/wpt-cross-current/passed-cases.txt @@ -4097,6 +4097,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/DOMParser-parseFromString-xml-parsererror.html domparsing/createContextualFragment-in-detached-xml-document-crash.html diff --git a/moli-dom/src/native/host/document.rs b/moli-dom/src/native/host/document.rs index 1d9301c617..394377f882 100644 --- a/moli-dom/src/native/host/document.rs +++ b/moli-dom/src/native/host/document.rs @@ -1120,6 +1120,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-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 918c7053d2..1930be84ea 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 @@ -200,16 +200,34 @@ 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 content_type = content_type.unwrap_or(parsed.dom().document()?.content_type()); let document = new_detached_document_shell(scope, kind, content_type, url)?; - if let Some(state) = detached_state_object(scope, document) - && let Some(character_set) = character_set - { + if let Some(state) = detached_state_object(scope, document) { let _ = state.set( scope, - v8str(scope, "characterSet").into(), - v8_string(scope, character_set)?.into(), + v8str(scope, "compatMode").into(), + v8_string(scope, compat_mode)?.into(), ); + if let Some(character_set) = character_set { + let _ = state.set( + scope, + v8str(scope, "characterSet").into(), + v8_string(scope, character_set)?.into(), + ); + } + } + 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 7b02b5d1b9..b1df185e36 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 @@ -267,10 +267,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/detached_document_and_element_surfaces.rs b/moli-renderer-v8/src/script_vm/tests/dom_elements/detached/detached_document_and_element_surfaces.rs index 83bfc3a510..460c4c944d 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_elements/detached/detached_document_and_element_surfaces.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_elements/detached/detached_document_and_element_surfaces.rs @@ -271,6 +271,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/");