fix(domparser): preserve HTML parse modes

This commit is contained in:
ldm0
2026-09-23 08:52:45 +08:00
parent 7e5ec902e9
commit dfd3fe7569
7 changed files with 99 additions and 22 deletions
@@ -2549,7 +2549,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
@@ -4103,6 +4103,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-doctype.html
domparsing/DOMParser-parseFromString-xml-parsererror.html
+19
View File
@@ -1134,6 +1134,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,
+12 -14
View File
@@ -29,8 +29,8 @@ use super::{
},
live_target::{ParserRuntimeDomSinks, ParserStreamHtmlTreeSinkTarget},
session::{
HtmlParserSession, HtmlParserSessionResult, new_fragment_html_tree_sink_session,
new_html_tree_sink_session,
HtmlParserSession, HtmlParserSessionResult, HtmlTreeSinkSession,
new_fragment_html_tree_sink_session, new_html_tree_sink_session,
},
};
@@ -429,11 +429,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,
@@ -442,6 +438,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))
}
pub(super) fn from_fragment_target(
target: ParserStreamHtmlTreeSinkTarget,
context_handle: NativeNodeId,
@@ -456,12 +459,7 @@ impl HtmlTreeSinkStream {
context_local_name,
scripting_enabled,
);
Self {
parser: session.parser,
script_input: session.script_input,
parser_script_positions: RefCell::default(),
next_parser_script_position: Cell::new(0),
}
Self::from_session(session)
}
fn parser_script_position(&self, node_id: NativeNodeId) -> usize {
@@ -1721,7 +1719,7 @@ mod tests {
#[test]
fn parser_script_handoff_only_exposes_nonceable_nonces() {
fn handoff_nonce(markup: &str) -> Option<String> {
let mut stream = DocumentStream::new_parser_stream_for_testing(
let mut stream = DocumentStream::new_scripting_enabled_parser_stream_for_testing(
Url::parse("https://example.test/page.html").expect("test url"),
);
let outcome = stream.pump_parser_step(markup);
@@ -201,16 +201,34 @@ pub(crate) fn build_detached_document_object_from_dom_host_with_content_type<'s>
character_set: Option<&str>,
) -> Option<v8::Local<'s, v8::Object>> {
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)
@@ -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::<v8::Object>::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>(
@@ -272,6 +272,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(
"<html><head></head><body></body></html>",
"text/html"
);
const standards = parser.parseFromString(
"<!doctype html><html><head></head><body></body></html>",
"text/html"
);
const noscript = parser.parseFromString(
"<body><noscript><p id='first'></p><p id='second'></p></noscript></body>",
"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/");