diff --git a/moli-renderer-v8/src/native_bridge/context_host/child_frames/classic_scripts.rs b/moli-renderer-v8/src/native_bridge/context_host/child_frames/classic_scripts.rs index 019eb73d34..87f00c74a6 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/child_frames/classic_scripts.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/child_frames/classic_scripts.rs @@ -425,6 +425,12 @@ impl JsContextHost { if current_realm_id != Some(realm_id) || work.realm_id.is_some_and(|id| id != realm_id) { return None; } + let document = self + .frame_owner_current_child_snapshot(work.child_handle)? + .document_handle; + if self.dom_host().owner_document_handle(work.script_handle) != Some(document) { + return None; + } let execution = match &work.source_result { Ok(source) => FrameDocumentExternalClassicScriptExecution::script_job( self.frame_owner_store @@ -479,11 +485,8 @@ impl JsContextHost { { return false; } - if self.dom_host().owner_document_handle(event.script_handle) - != Some(snapshot.document_handle) - { - return false; - } + // Adoption is checked before execution. A script can move itself while + // running and must still receive its terminal event on the same element. let _ = self.child_browsing_context_document_wrapper(scope, event.child_handle); let host_ptr = self as *mut JsContextHost; let Some(target) = @@ -1910,7 +1913,8 @@ impl JsContextHost { &mut self, failed: FrameDocumentClassicSourceFailureWork, ) -> FrameDocumentClassicSourceFailureReportApplication { - let (target, _failure, script_element_event) = failed.into_parts(); + let script_handle = failed.script_handle(); + let (target, _failure, mut script_element_event) = failed.into_parts(); let child_handle = target.child_handle(); let owner = target.task_owner().document_owner(); let runner_owner_current = self.frame_parser_classic_scripts.has_runner(owner); @@ -1949,6 +1953,15 @@ impl JsContextHost { FrameDocumentClassicSourceFailureReportSkipReason::StaleRealm, ); } + if self + .frame_owner_current_child_snapshot(child_handle) + .is_none_or(|snapshot| { + self.dom_host().owner_document_handle(script_handle) + != Some(snapshot.document_handle) + }) + { + script_element_event = None; + } FrameDocumentClassicSourceFailureReportApplication::completed( FrameDocumentClassicScriptCompletionAction::new( FrameDocumentClassicScriptCompletionTarget::new( diff --git a/moli-renderer-v8/src/runtime/page_vm/page_owned_document_script_hooks.rs b/moli-renderer-v8/src/runtime/page_vm/page_owned_document_script_hooks.rs index 8577ad813d..357782f91b 100644 --- a/moli-renderer-v8/src/runtime/page_vm/page_owned_document_script_hooks.rs +++ b/moli-renderer-v8/src/runtime/page_vm/page_owned_document_script_hooks.rs @@ -21,6 +21,7 @@ use super::{ PageOwnedScriptFailureClassification, PageVm, complete_page_owned_prepared_script_execution_failure_body, complete_prepared_script_execution_failure_report_with_activity, + complete_prepared_script_execution_success_with_activity, execute_prepared_script_on_script_execution_lane, }; @@ -112,6 +113,18 @@ impl PageOwnedDocumentScriptHooks for MainPageOwnedDocumentScriptHooks<'_, '_> { failure: PageOwnedDocumentScriptSourceFailure, runtime_script_claim: Option, ) -> PageOwnedDocumentScriptBodyExecution { + if self.page_vm.vm().prepared_script_changed_documents(&script) { + if let Some(claim) = runtime_script_claim { + self.page_vm + .vm_mut() + .cancel_claimed_runtime_owned_script_load_delay_body(claim, &script); + } + return complete_prepared_script_execution_success_with_activity( + script, + crate::script_vm::PreparedScriptBodyActivity::NotEntered, + ) + .into_body_execution(); + } let (error, module_failure_policy, error_value) = failure.into_parts(); if let Some(claim) = runtime_script_claim { let terminal_activity = self diff --git a/moli-renderer-v8/src/runtime/page_vm/parser_deferred_classic.rs b/moli-renderer-v8/src/runtime/page_vm/parser_deferred_classic.rs index 0ee6333eb6..3abde43c6f 100644 --- a/moli-renderer-v8/src/runtime/page_vm/parser_deferred_classic.rs +++ b/moli-renderer-v8/src/runtime/page_vm/parser_deferred_classic.rs @@ -372,11 +372,19 @@ impl ParserClassicDocumentScriptExecutionHooks .document_runtime .mark_script_already_started_by_node_id(failure.script.node_id); let script_handle = DomHandle::new(failure.script.node_id.index()); - let event = owner + let event = if owner .page_vm .vm() - .document_runtime - .plan_parser_owned_script_event_task(ScriptEventKind::Error, script_handle); + .prepared_script_changed_documents(&failure.script) + { + None + } else { + owner + .page_vm + .vm() + .document_runtime + .plan_parser_owned_script_event_task(ScriptEventKind::Error, script_handle) + }; tracing::debug!( expected_owner = ?failure.owner, script_node_id = ?failure.script.node_id, diff --git a/moli-renderer-v8/src/runtime/page_vm/parser_owned_module_completion.rs b/moli-renderer-v8/src/runtime/page_vm/parser_owned_module_completion.rs index a5fc28233d..12bce93d2e 100644 --- a/moli-renderer-v8/src/runtime/page_vm/parser_owned_module_completion.rs +++ b/moli-renderer-v8/src/runtime/page_vm/parser_owned_module_completion.rs @@ -18,7 +18,7 @@ use crate::script_vm::{ ParserModuleEvaluationSettlement, ParserModuleTerminalDisposition, ParserOwnedModuleSuccessTerminal, PreparedModuleSuccessSettlement, PreparedScriptBodyActivity, }; -use crate::types::ScriptRun; +use crate::types::{ScriptRun, ScriptSkipReason}; use super::parser_owned_document_script::MainParserModuleExecution; use super::parser_task_completion::{ @@ -62,6 +62,48 @@ impl ModuleScriptCompletionOutcome { } } impl PageVm { + fn complete_moved_module_script( + &mut self, + mut continuation: ModuleScriptContinuation, + terminal_disposition: ParserModuleTerminalDisposition, + ) -> ModuleScriptCompletionOutcome { + let load_delay = continuation.take_main_document_load_delay_binding(); + let dynamic_owner = continuation.dynamic_script_owner_id(); + let script = continuation.script; + if let Some(owner_id) = dynamic_owner { + self.vm_mut() + .cancel_runtime_owned_script_load_delay_body(&script, owner_id); + } + if let Some(binding) = load_delay { + let _ = self + .vm_mut() + .enqueue_main_document_script_load_delay_settlement_best_effort(&script, binding); + } + let run = ScriptRun::skipped( + script.node_id, + script.kind, + script.mode, + script.source_kind, + script.url, + ScriptSkipReason::NotInMainDocument, + ); + if terminal_disposition == ParserModuleTerminalDisposition::ReturnToSelectedParserTask { + ModuleScriptCompletionOutcome::TerminalForSelectedTask { + run, + terminal: ParserOwnedModuleSuccessTerminal::new( + ParserModuleEvaluationSettlement::Completed, + None, + PreparedScriptBodyActivity::NotEntered, + ), + } + } else { + ModuleScriptCompletionOutcome::Completed { + run, + task_effect: MainParserContinuationTaskEffect::NotApplied, + } + } + } + pub(super) fn complete_module_script_failure_for_runner( &mut self, script_continuation: ModuleScriptContinuation, @@ -88,6 +130,13 @@ impl PageVm { prepared_activity: PreparedScriptBodyActivity, terminal_disposition: ParserModuleTerminalDisposition, ) -> ModuleScriptCompletionOutcome { + if matches!(prepared_activity, PreparedScriptBodyActivity::NotEntered) + && self + .vm() + .prepared_script_changed_documents(&script_continuation.script) + { + return self.complete_moved_module_script(script_continuation, terminal_disposition); + } let parser_owner = script_continuation .parser_document_owner() .map(MainParserDocumentOwner::task_owner); @@ -151,6 +200,16 @@ impl PageVm { mut script_continuation: ModuleScriptContinuation, terminal_disposition: ParserModuleTerminalDisposition, ) -> Result { + if self + .vm() + .prepared_script_changed_documents(&script_continuation.script) + { + let (run, execution) = self + .complete_moved_module_script(script_continuation, terminal_disposition) + .into_parser_execution(); + self.report.runs.push(run); + return Ok(execution); + } let Some(graph) = script_continuation.completed_graph.take() else { self.handle_module_script_continuation_graph_advance( ModuleScriptContinuationGraphAdvance::Ready(Box::new(script_continuation)), diff --git a/moli-renderer-v8/src/runtime/page_vm/tests/child_module_script_terminal.rs b/moli-renderer-v8/src/runtime/page_vm/tests/child_module_script_terminal.rs index 506d413ab0..339ea5baa5 100644 --- a/moli-renderer-v8/src/runtime/page_vm/tests/child_module_script_terminal.rs +++ b/moli-renderer-v8/src/runtime/page_vm/tests/child_module_script_terminal.rs @@ -51,6 +51,69 @@ pub(super) async fn queue_real_child_module_terminal( .context("real terminal producer should retain an exact child realm") } +#[tokio::test(flavor = "current_thread")] +async fn child_parser_modules_check_preparation_document_before_execution() { + run_page_vm_async_test(async { + for fetch_fails in [false, true] { + for movement in ["before", "return", "during"] { + let source = if movement == "during" { + "parent.__adoptionEvents.push('execute'); parent.document.body.append(parent.__movedModule);" + } else { + "parent.__adoptionEvents.push('execute');" + }; + let (base_url, server) = spawn_path_response_http_server(vec![( + "/adopted.mjs", + if fetch_fails { "HTTP/1.1 404 Not Found" } else { "HTTP/1.1 200 OK" }, + source.to_owned(), Duration::ZERO, + )]).await; + let loader = crate::network::ResourceRequestClient::new(&FetchConfig::default())?; + let (mut page_vm, mut resource_source, mut owner_wake_rx) = + page_vm_with_bound_task_sources_and_owner_wake( + &loader, Url::parse(&format!("{base_url}/page"))?, + ); + queue_real_child_module_terminal( + &mut page_vm, &mut resource_source, &mut owner_wake_rx, + "adopted-child", &format!("{base_url}/adopted.mjs"), + ).await?; + page_vm.vm_mut().eval(r#" + globalThis.__adoptionEvents = []; + globalThis.__childDocument = document.getElementById('adopted-child').contentDocument; + globalThis.__movedModule = __childDocument.querySelector('script'); + __movedModule.onload = () => __adoptionEvents.push('load'); + __movedModule.onerror = () => __adoptionEvents.push('error'); + "#)?; + if movement != "during" { + page_vm.vm_mut().eval("document.body.append(__movedModule)")?; + if movement == "return" { + page_vm.vm_mut().eval("__childDocument.body.append(__movedModule)")?; + } + } + run_expected_child_module_script_terminal_turn( + &mut page_vm, "adopted child module source terminal", + ).await; + assert!(page_vm.run_exact_selected_page_task_for_test( + PageSelectedTaskTestSelector::ChildDocumentScriptReady, &loader, + ).await?); + let expected = if movement == "before" { "" } else if fetch_fails { "error" } else { "execute|load" }; + assert_eq!(page_vm.vm_mut().eval("__adoptionEvents.join('|')")?, expected, + "fetch_fails={fetch_fails}, movement={movement}"); + for source in [ + ChildFrameSemanticTurnKind::DocumentLifecycle, + ChildFrameSemanticTurnKind::DocumentLifecycle, + ChildFrameSemanticTurnKind::HostLoad, + ] { + run_expected_child_frame_task_source_after_realm_prerequisite_for_wait( + &mut page_vm, source, "lifecycle after adopted module", + ).await; + } + assert_eq!(page_vm.vm_mut().eval("__childDocument.readyState")?, "complete"); + server.await?; + } + } + Ok::<_, anyhow::Error>(()) + }).await.unwrap(); +} + #[tokio::test(flavor = "current_thread")] async fn child_parser_module_roots_compile_binary_wasm_responses() { run_page_vm_async_test(async { diff --git a/moli-renderer-v8/src/runtime/page_vm/tests/module_error_reporting.rs b/moli-renderer-v8/src/runtime/page_vm/tests/module_error_reporting.rs index 97d0861fca..24e310dda6 100644 --- a/moli-renderer-v8/src/runtime/page_vm/tests/module_error_reporting.rs +++ b/moli-renderer-v8/src/runtime/page_vm/tests/module_error_reporting.rs @@ -17,6 +17,182 @@ fn bound_parser_module(page_vm: &mut PageVm, position: u32, url: Url) -> Prepare script } +#[tokio::test(flavor = "current_thread")] +async fn prepared_main_classic_scripts_check_their_node_document() { + run_page_vm_async_test(async { + for (mutation, executes) in [ + ("__script.remove()", true), + ("__other.body.append(__script)", false), + ( + "__other.body.append(__script); document.body.append(__script)", + true, + ), + ] { + let loader = + crate::network::ResourceRequestClient::new(&FetchConfig::default()).unwrap(); + let mut page_vm = test_page_vm_with_loader_and_document_url( + &loader, + Vec::new(), + Url::parse("https://example.com/adopted-classic.html").unwrap(), + ); + let mut script = bound_parser_module( + &mut page_vm, + 9301, + Url::parse("https://example.com/adopted.js").unwrap(), + ); + script.kind = ScriptKind::Classic; + script.mode = ScriptMode::Normal; + script.source = + crate::planning::ScriptSource::Loaded("++globalThis.__runs;".to_owned()); + page_vm + .vm_mut() + .eval(&format!( + r#" + globalThis.__runs = 0; + globalThis.__script = document.querySelector('script'); + globalThis.__other = document.implementation.createHTMLDocument(''); + {mutation}; + "# + )) + .unwrap(); + let outcome = page_vm + .vm_mut() + .run_prepared_script(&loader, &script, None) + .await + .unwrap(); + assert_eq!( + matches!( + outcome, + crate::script_vm::PreparedScriptExecutionOutcome::Completed(_) + ), + executes, + "{mutation}", + ); + assert_eq!( + page_vm.vm_mut().eval("__runs").unwrap(), + if executes { "1" } else { "0" } + ); + } + }) + .await; +} + +#[tokio::test(flavor = "current_thread")] +async fn adopted_main_module_graphs_settle_without_execution_or_events() { + run_page_vm_async_test(async { + for fetch_fails in [false, true] { + for move_back in [false, true] { + let loader = + crate::network::ResourceRequestClient::new(&FetchConfig::default()).unwrap(); + let mut page_vm = test_page_vm_with_loader_and_document_url( + &loader, + Vec::new(), + Url::parse("https://example.com/adopted-module.html").unwrap(), + ); + let url = Url::parse("https://example.com/adopted.mjs").unwrap(); + let script = bound_parser_module(&mut page_vm, 9302, url.clone()); + page_vm + .vm_mut() + .eval( + r#" + globalThis.__counts = [0, 0, 0, 0]; + globalThis.__script = document.querySelector('script'); + __script.onload = () => ++__counts[1]; + __script.onerror = () => ++__counts[2]; + addEventListener('error', event => { ++__counts[3]; event.preventDefault(); }); + "#, + ) + .unwrap(); + let work = install_parser_module_defer_work(&mut page_vm, script); + page_vm + .execute_post_parse_page_owned_task_on_named_owner_lane(&loader, work) + .await + .unwrap(); + page_vm + .vm_mut() + .eval("document.implementation.createHTMLDocument('').body.append(__script)") + .unwrap(); + if move_back { + page_vm + .vm_mut() + .eval("document.body.append(__script)") + .unwrap(); + } + if fetch_fails { + enqueue_parser_owned_module_script_fetch_error_for_test( + &mut page_vm, + 0, + &url, + "HTTP 404", + ); + } else { + enqueue_parser_owned_module_script_fetch_completion_for_test( + &mut page_vm, + 0, + &url, + "++globalThis.__counts[0];", + ); + } + assert!( + run_next_main_module_fetch_terminal_for_test(&mut page_vm) + .unwrap() + .is_some() + ); + run_and_finish_ready_parser_deferred_task_for_test( + &mut page_vm, + &loader, + "adopted module terminal", + ) + .await; + run_parser_module_completion_turns_for_test( + &mut page_vm, + &loader, + 0, + "adopted module terminal", + ) + .await; + let expected = if !move_back { + "[0,0,0,0]" + } else if fetch_fails { + "[0,0,1,0]" + } else { + "[1,1,0,0]" + }; + assert_eq!( + page_vm.vm_mut().eval("JSON.stringify(__counts)").unwrap(), + expected + ); + } + } + }) + .await; +} + +#[tokio::test(flavor = "current_thread")] +async fn module_evaluation_continues_if_element_is_adopted_after_it_starts() { + run_page_vm_async_test(async { + let loader = crate::network::ResourceRequestClient::new(&FetchConfig::default()).unwrap(); + let mut page_vm = test_page_vm_with_loader_and_document_url( + &loader, Vec::new(), Url::parse("https://example.com/adopted-tla.html").unwrap(), + ); + let url = Url::parse("https://example.com/adopted-tla.mjs").unwrap(); + let script = bound_parser_module(&mut page_vm, 9303, url.clone()); + page_vm.vm_mut().eval("globalThis.__runs = 0; globalThis.__script = document.querySelector('script');").unwrap(); + let work = install_parser_module_defer_work(&mut page_vm, script); + page_vm.execute_post_parse_page_owned_task_on_named_owner_lane(&loader, work).await.unwrap(); + enqueue_parser_owned_module_script_fetch_completion_for_test( + &mut page_vm, 0, &url, + "await new Promise(resolve => { globalThis.__resumeModule = resolve; }); ++globalThis.__runs;", + ); + assert!(run_next_main_module_fetch_terminal_for_test(&mut page_vm).unwrap().is_some()); + run_ready_parser_deferred_body_for_test(&mut page_vm, &loader, "module before adoption").await; + assert_eq!(page_vm.vm_mut().eval("__runs").unwrap(), "0"); + page_vm.vm_mut().eval("document.implementation.createHTMLDocument('').body.append(__script); __resumeModule();").unwrap(); + run_parser_module_completion_turns_for_test(&mut page_vm, &loader, 1, "module adopted after evaluation start").await; + assert_eq!(page_vm.vm_mut().eval("__runs").unwrap(), "1"); + }).await; +} + #[tokio::test(flavor = "current_thread")] async fn parser_module_error_reporting_preserves_inline_source_origin() { run_page_vm_async_test(async { diff --git a/moli-renderer-v8/src/runtime/phase_one/mod.rs b/moli-renderer-v8/src/runtime/phase_one/mod.rs index 2dd81e1e5b..9e79514d9c 100644 --- a/moli-renderer-v8/src/runtime/phase_one/mod.rs +++ b/moli-renderer-v8/src/runtime/phase_one/mod.rs @@ -5311,6 +5311,19 @@ queueMicrotask(() => window.__mainParserClassicCheckpointEvents.push('script-mic #[test] fn main_parser_blocking_source_failure_uses_the_shared_completion_event_flow() { + for (movement, expected) in [ + ("", "error:true|error-microtask"), + ("foreign.body.append(script);", ""), + ( + "foreign.body.append(script); document.body.append(script);", + "error:true|error-microtask", + ), + ] { + assert_main_parser_blocking_source_failure_events(movement, expected); + } + } + + fn assert_main_parser_blocking_source_failure_events(movement: &str, expected: &str) { let runtime = tokio::runtime::Builder::new_current_thread() .enable_all() .build() @@ -5350,6 +5363,13 @@ queueMicrotask(() => window.__mainParserClassicCheckpointEvents.push('script-mic .vm_mut() .eval("window.__mainParserClassicFailureEvents = []") .expect("source failure event state should initialize"); + page_vm.vm_mut().eval(&format!(r#" + (() => {{ + const script = document.querySelector('script'); + const foreign = document.implementation.createHTMLDocument(''); + {movement} + }})() + "#)).expect("source failure script movement should complete"); let task_owner = page_vm .vm() @@ -5405,8 +5425,8 @@ queueMicrotask(() => window.__mainParserClassicCheckpointEvents.push('script-mic .vm_mut() .eval("__mainParserClassicFailureEvents.join('|')") .expect("source failure events should evaluate"), - "error:true|error-microtask", - "source failure must dispatch error with currentScript cleared and settle its reactions before parser continuation" + expected, + "source failure must check the preparation Document before dispatching error and settling reactions" ); })); } diff --git a/moli-renderer-v8/src/runtime/phase_one/parser_blocking_document_script.rs b/moli-renderer-v8/src/runtime/phase_one/parser_blocking_document_script.rs index 3cf914084d..6d27b6c9db 100644 --- a/moli-renderer-v8/src/runtime/phase_one/parser_blocking_document_script.rs +++ b/moli-renderer-v8/src/runtime/phase_one/parser_blocking_document_script.rs @@ -241,14 +241,17 @@ impl ParserClassicDocumentScriptExecutionHooks let script_handle = failure.script_handle(); let script_url = failure.script_url().clone(); let error = failure.error().to_owned(); - let event = owner - .page_vm - .vm() - .document_runtime - .plan_parser_owned_script_event_task( + let runtime = &owner.page_vm.vm().document_runtime; + let event = if runtime.dom_host().owner_document_handle(script_handle) + == Some(runtime.document_handle()) + { + runtime.plan_parser_owned_script_event_task( crate::host::ScriptEventKind::Error, script_handle, - ); + ) + } else { + None + }; if let Some((script, _, source_network_result)) = failure.into_execution_failure_parts() && let Some(network_result) = source_network_result.as_deref() { diff --git a/moli-renderer-v8/src/runtime/phase_one/parser_turn.rs b/moli-renderer-v8/src/runtime/phase_one/parser_turn.rs index b243816ed5..a1b51bfc26 100644 --- a/moli-renderer-v8/src/runtime/phase_one/parser_turn.rs +++ b/moli-renderer-v8/src/runtime/phase_one/parser_turn.rs @@ -1295,7 +1295,12 @@ impl<'loader, 'state> ParserDriver<'loader, 'state> { .set_script_already_started(handle, true); let _ = self.scheduler.grant_parse_visible_reevaluation_credit(); } else if failure.is_external_source_failure() { - page_vm.vm_mut().queue_script_preparation_error(handle)?; + let runtime = &page_vm.vm().document_runtime; + if runtime.dom_host().owner_document_handle(handle) + == Some(runtime.document_handle()) + { + page_vm.vm_mut().queue_script_preparation_error(handle)?; + } } else { page_vm .vm_mut() diff --git a/moli-renderer-v8/src/script_vm.rs b/moli-renderer-v8/src/script_vm.rs index 4a9f19f06d..a7e6c8ce28 100644 --- a/moli-renderer-v8/src/script_vm.rs +++ b/moli-renderer-v8/src/script_vm.rs @@ -6542,7 +6542,24 @@ impl ScriptVm { handle } + // Main-document work retains its preparation owner until it is ready. Check + // adoption when entering execution, without cancelling its fetch or order slot. + pub(crate) fn prepared_script_changed_documents(&self, script: &PreparedScript) -> bool { + let node = script + .host_script_handle + .as_deref() + .and_then(|handle| self.document_runtime.resolve_host_script_handle(handle)) + .unwrap_or(script.node_id); + self.document_runtime + .dom_host() + .owner_document_handle(node) + .is_some_and(|document| document != self.document_runtime.document_handle()) + } + fn prepared_script_is_live_for_execution(&mut self, script: &PreparedScript) -> bool { + if self.prepared_script_changed_documents(script) { + return false; + } let Some(handle) = script.host_script_handle.as_deref() else { let allow_missing_handle = script.kind == ScriptKind::Classic && script.source_kind == ScriptSourceKind::Inline diff --git a/moli-renderer-v8/src/script_vm/native_module/child_parser_module/execution.rs b/moli-renderer-v8/src/script_vm/native_module/child_parser_module/execution.rs index 653575bbde..826a508632 100644 --- a/moli-renderer-v8/src/script_vm/native_module/child_parser_module/execution.rs +++ b/moli-renderer-v8/src/script_vm/native_module/child_parser_module/execution.rs @@ -44,6 +44,14 @@ impl FrameModuleScriptDocumentScriptHooks for ChildModuleScriptExecutionOwner<'_ work.script().mode, work.pending_script_id().key(), work.load_delay_token(), + )?; + self.check_script_preparation_document( + work.owner(), + work.realm_id(), + work.script_handle(), + work.script().mode, + work.pending_script_id().key(), + work.load_delay_token(), ) } @@ -57,6 +65,14 @@ impl FrameModuleScriptDocumentScriptHooks for ChildModuleScriptExecutionOwner<'_ work.script().mode, work.pending_script_id().key(), work.load_delay_token(), + )?; + self.check_script_preparation_document( + work.owner(), + work.realm_id(), + work.script_handle(), + work.script().mode, + work.pending_script_id().key(), + work.load_delay_token(), ) } @@ -262,6 +278,37 @@ impl FrameModuleScriptDocumentScriptHooks for ChildModuleScriptExecutionOwner<'_ } impl ChildModuleScriptExecutionOwner<'_> { + fn check_script_preparation_document( + &mut self, + owner: FrameDocumentTaskOwner, + realm_id: FrameRealmId, + script_handle: DomHandle, + mode: ScriptMode, + pending_script_key: crate::document_script_scheduler::ParserPendingScriptKey, + load_delay_token: crate::frame_owner_model::DocumentLoadDelayTokenId, + ) -> std::result::Result<(), DocumentScriptExecutionOutcome> { + let current = { + let host = self.vm._context_host.borrow(); + host.frame_owner_current_child_snapshot_for_realm(realm_id) + .is_some_and(|snapshot| { + host.dom_host().owner_document_handle(script_handle) + == Some(snapshot.document_handle) + }) + }; + if current { + return Ok(()); + } + let order_released = + self.complete_parser_deferred_module_script(owner, realm_id, mode, pending_script_key); + let released = self.release_module_script_load_delay(owner, mode, load_delay_token); + if order_released || released { + self.queue_lifecycle_followups_for_module_work(owner, realm_id); + Err(DocumentScriptExecutionOutcome::Progressed) + } else { + Err(DocumentScriptExecutionOutcome::NoProgress) + } + } + fn complete_parser_deferred_module_script( &mut self, owner: FrameDocumentTaskOwner,