From 4e4a43adada77fb779f7804b26cf63ededa13802 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Thu, 3 Sep 2026 00:12:16 +0800 Subject: [PATCH] fix(forms): honor submission entry-list guards --- .../src/native_bridge/context_host/core.rs | 4 + .../native_bridge/element/forms/submission.rs | 13 +++ .../src/script_vm/tests/dom_xhr/forms.rs | 83 +++++++++++++++++++ ...n-child-browsing-context-target-basic.html | 1 + 4 files changed, 101 insertions(+) diff --git a/moli-renderer-v8/src/native_bridge/context_host/core.rs b/moli-renderer-v8/src/native_bridge/context_host/core.rs index fb781b807..a61e1d039 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/core.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/core.rs @@ -1671,6 +1671,10 @@ impl JsContextHost { true } + pub(crate) fn is_constructing_form_data_for(&self, form_handle: DomHandle) -> bool { + self.constructing_form_data_forms.contains(&form_handle) + } + pub(crate) fn end_form_data_construction(&mut self, form_handle: DomHandle) { if let Some(index) = self .constructing_form_data_forms diff --git a/moli-renderer-v8/src/native_bridge/element/forms/submission.rs b/moli-renderer-v8/src/native_bridge/element/forms/submission.rs index 355c51a44..5155229db 100644 --- a/moli-renderer-v8/src/native_bridge/element/forms/submission.rs +++ b/moli-renderer-v8/src/native_bridge/element/forms/submission.rs @@ -205,6 +205,7 @@ pub(crate) fn submit_form_with_submit_event( let form_can_dispatch_submit_event = { let runtime = unsafe { &*runtime_ptr }; form_is_connected_or_in_detached_document(runtime, form_handle) + && !runtime.is_constructing_form_data_for(form_handle) }; if !form_can_dispatch_submit_event { return false; @@ -576,6 +577,15 @@ pub(in crate::native_bridge) fn submit_form_default_action( submitter: Option, user_initiated: bool, ) -> bool { + let form_can_submit = { + let runtime = unsafe { &*runtime_ptr }; + form_is_connected_or_in_detached_document(runtime, form_handle) + && !runtime.is_constructing_form_data_for(form_handle) + }; + if !form_can_submit { + return false; + } + let method = { let runtime = unsafe { &*runtime_ptr }; resolve_form_submission_method(runtime, form_handle, submitter) @@ -604,6 +614,9 @@ pub(in crate::native_bridge) fn submit_form_default_action( else { return false; }; + if !form_is_connected_or_in_detached_document(unsafe { &*runtime_ptr }, form_handle) { + return false; + } let special_target = target_name .as_deref() diff --git a/moli-renderer-v8/src/script_vm/tests/dom_xhr/forms.rs b/moli-renderer-v8/src/script_vm/tests/dom_xhr/forms.rs index fd0366d3a..a1deaf755 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_xhr/forms.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_xhr/forms.rs @@ -2562,6 +2562,89 @@ fn url_like_href_resolution_does_not_legacy_encode_fragment_question_mark() { "https://href-fragment-gbk.test/search#frag?q=%E5%AE%B6%E5%B1%85" ); } + +#[test] +fn form_submission_returns_while_constructing_entry_list() { + let mut vm = new_storage_test_vm("https://form-entry-list-reentrant.test/page.html"); + + let result = vm + .eval( + r#" +(() => { + const parent = document.body || document.documentElement || document; + const form = document.createElement('form'); + form.action = '/submitted'; + form.innerHTML = ''; + parent.appendChild(form); + + const outcomes = []; + let submitEvents = 0; + form.addEventListener('submit', event => { + submitEvents++; + event.preventDefault(); + }); + form.addEventListener('formdata', () => { + for (const submit of [ + () => form.submit(), + () => form.requestSubmit() + ]) { + try { + submit(); + outcomes.push('returned'); + } catch (error) { + outcomes.push(`threw:${error.name}`); + } + } + }); + + form.submit(); + return JSON.stringify({ outcomes, submitEvents }); +})() +"#, + ) + .expect("form submission entry-list reentrancy probe should evaluate"); + + assert_eq!( + result, + r#"{"outcomes":["returned","returned"],"submitEvents":0}"# + ); + assert_eq!( + vm.take_pending_location_navigation_with_seed() + .expect("outer form submission should still queue navigation") + .url + .as_str(), + "https://form-entry-list-reentrant.test/submitted?n=v" + ); +} + +#[test] +fn form_submission_aborts_when_formdata_listener_disconnects_form() { + let mut vm = new_storage_test_vm("https://form-disconnected-after-entry-list.test/page.html"); + + let result = vm + .eval( + r#" +(() => { + const parent = document.body || document.documentElement || document; + const form = document.createElement('form'); + form.action = '/submitted'; + form.innerHTML = ''; + parent.appendChild(form); + form.addEventListener('formdata', () => form.remove()); + form.submit(); + return String(form.isConnected); +})() +"#, + ) + .expect("formdata disconnect submission probe should evaluate"); + + assert_eq!(result, "false"); + assert!( + vm.take_pending_location_navigation_with_seed().is_none(), + "a form disconnected while constructing its entry list must not navigate" + ); +} + #[tokio::test(flavor = "current_thread")] async fn formdata_event_appended_entries_are_submitted_to_named_iframe() { let server = StaticHttpServer::spawn(1).await; diff --git a/moli-wpt-compat/fixtures/wpt/ported/navigation/navigation-child-browsing-context-target-basic.html b/moli-wpt-compat/fixtures/wpt/ported/navigation/navigation-child-browsing-context-target-basic.html index 387236c3b..644f8d6c5 100644 --- a/moli-wpt-compat/fixtures/wpt/ported/navigation/navigation-child-browsing-context-target-basic.html +++ b/moli-wpt-compat/fixtures/wpt/ported/navigation/navigation-child-browsing-context-target-basic.html @@ -31,6 +31,7 @@ const form = document.createElement("form"); form.target = "frame2"; form.action = "resources/a-page.html"; + document.body.appendChild(form); form.submit(); document.body.setAttribute(