fix(parser): preserve fragment context form submission state

This commit is contained in:
ldm0
2026-10-11 18:06:47 +08:00
parent 41f777c81e
commit 130c968f87
2 changed files with 53 additions and 1 deletions
+39
View File
@@ -3970,6 +3970,45 @@ fn parser_stream_marks_only_eof_unclosed_form_controls_as_submission_blocking()
}
}
#[test]
fn live_fragment_parser_preserves_the_contexts_submission_blocking_state() {
for (tag_name, html) in [("select", "<option>value</option>"), ("textarea", "value")] {
for blocked in [false, true] {
let url = Url::parse("https://fragment.test/").expect("test url");
let mut dom_host = DomHost::from_dom(NativeDom::new_html(url.clone()));
let document = dom_host.document_handle();
let context = dom_host.create_element(tag_name);
dom_host.set_blocks_form_submission(context, blocked);
let fragment = dom_host.create_document_fragment();
let mut effects = DomMutationEffects::default();
let mut collector = TestMutationEffectCollector {
host: &mut dom_host,
effects: &mut effects,
panic_on_mutation: false,
};
crate::HtmlParser::with_scripting_enabled(true).parse_fragment_into_live_dom(
url,
fragment,
document,
context,
"http://www.w3.org/1999/xhtml",
tag_name,
html,
&mut collector,
false,
);
assert_eq!(
dom_host
.node(context)
.and_then(Node::as_element)
.is_some_and(|element| element.blocks_form_submission()),
blocked,
"fragment parsing must preserve {tag_name}'s pre-existing EOF state"
);
}
}
}
#[test]
fn parser_stream_async_prefetch_uses_shared_script_type_classification() {
let html = concat!(
+14 -1
View File
@@ -47,6 +47,7 @@ enum TokenizerPause {
struct EmbedderPausingTreeBuilder {
inner: TreeBuilder<ParseHandle, DocumentSink>,
fragment_context: Option<NativeNodeId>,
// Retain a callback's Break until html5ever accepts a pause result. This is
// an undelivered notification, not a copy of the owner's current run state;
// it must survive both later Continue callbacks and input chunk boundaries.
@@ -56,6 +57,7 @@ struct EmbedderPausingTreeBuilder {
#[derive(Default)]
struct OpenFormControlTracer {
handles: RefCell<Vec<NativeNodeId>>,
fragment_context: Option<NativeNodeId>,
}
impl Tracer for OpenFormControlTracer {
@@ -72,6 +74,11 @@ impl Tracer for OpenFormControlTracer {
let Some(node_id) = node.dom_node_id() else {
return;
};
// html5ever also traces the fragment context for GC. It is an existing
// element, not an unclosed control created by this parser.
if Some(node_id) == self.fragment_context {
return;
}
let mut handles = self.handles.borrow_mut();
if !handles.contains(&node_id) {
handles.push(node_id);
@@ -83,6 +90,7 @@ impl EmbedderPausingTreeBuilder {
fn new(sink: DocumentSink, opts: TreeBuilderOpts) -> Self {
Self {
inner: TreeBuilder::new(sink, opts),
fragment_context: None,
deferred_owner_interruption: Cell::new(false),
}
}
@@ -92,8 +100,10 @@ impl EmbedderPausingTreeBuilder {
context_handle: ParseHandle,
opts: TreeBuilderOpts,
) -> Self {
let fragment_context = context_handle.dom_node_id();
Self {
inner: TreeBuilder::new_for_fragment(sink, context_handle, None, opts),
fragment_context,
deferred_owner_interruption: Cell::new(false),
}
}
@@ -103,7 +113,10 @@ impl EmbedderPausingTreeBuilder {
}
fn begin_tree_builder_finish(&self) {
let tracer = OpenFormControlTracer::default();
let tracer = OpenFormControlTracer {
fragment_context: self.fragment_context,
..OpenFormControlTracer::default()
};
self.inner.trace_handles(&tracer);
self.inner
.sink