mirror of
https://github.com/lexmount/moli.git
synced 2026-10-03 00:00:44 +00:00
fix(scripts): skip execution after cross-document adoption
Check each prepared script's node Document before executing or reporting a source failure, while settling its original ordering and load-delay ownership. Apply the check to main-document classic/module and child-document script work. Keep terminal events for scripts adopted during execution, and allow existing top-level-await evaluations to continue. Cover removal, adoption, moving back, source failures, child lifecycle completion, and adoption after evaluation starts.
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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<DynamicScriptPageTaskClaim>,
|
||||
) -> 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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<MainParserModuleExecution> {
|
||||
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)),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user