mirror of
https://github.com/lexmount/moli.git
synced 2026-10-02 00:00:49 +00:00
fix(scripts): execute dynamic child modules in insertion order
This commit is contained in:
@@ -2174,7 +2174,7 @@ fn start_connected_child_document_script(
|
||||
source_kind,
|
||||
} => {
|
||||
match unsafe { &mut *host_ptr }
|
||||
.queue_child_dynamic_external_classic_script_for_current_document(
|
||||
.queue_child_dynamic_document_script_for_current_document(
|
||||
scope,
|
||||
owner_document_handle,
|
||||
node,
|
||||
|
||||
@@ -10,7 +10,8 @@ use crate::{
|
||||
document_task_lane::DocumentRealmTask,
|
||||
dom::NodeId,
|
||||
frame_owner_model::{
|
||||
DocumentLoadDelayTokenId, FrameDocumentOwner, FrameDocumentTaskOwner, FrameRealmId,
|
||||
ChildDocumentModuleScriptLoadDelay, FrameDocumentOwner, FrameDocumentTaskOwner,
|
||||
FrameRealmId,
|
||||
},
|
||||
module_runtime::{ModuleEntryId, ModuleGraphHandle, ModuleLoadError, ModuleMapKey},
|
||||
parser_module_pending::ParserPendingModuleScriptState,
|
||||
@@ -118,7 +119,7 @@ pub(crate) struct FrameDocumentModuleGraphReadyPayload {
|
||||
script_handle: DomHandle,
|
||||
request_key: ModuleMapKey,
|
||||
tree_id: module_tree::ModuleTreeId,
|
||||
load_delay_token: DocumentLoadDelayTokenId,
|
||||
load_delay_token: ChildDocumentModuleScriptLoadDelay,
|
||||
}
|
||||
|
||||
pub(crate) type FrameDocumentModuleGraphReadyTarget =
|
||||
@@ -200,7 +201,7 @@ impl FrameDocumentModuleGraphReadyTarget {
|
||||
script_handle: DomHandle,
|
||||
request_key: ModuleMapKey,
|
||||
tree_id: module_tree::ModuleTreeId,
|
||||
load_delay_token: DocumentLoadDelayTokenId,
|
||||
load_delay_token: impl Into<ChildDocumentModuleScriptLoadDelay>,
|
||||
) -> Self {
|
||||
assert_eq!(pending_script_id.owner(), owner.document_owner());
|
||||
Self::new(
|
||||
@@ -211,7 +212,7 @@ impl FrameDocumentModuleGraphReadyTarget {
|
||||
script_handle,
|
||||
request_key,
|
||||
tree_id,
|
||||
load_delay_token,
|
||||
load_delay_token: load_delay_token.into(),
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -235,7 +236,7 @@ impl FrameDocumentModuleGraphReadyTarget {
|
||||
self.payload().tree_id
|
||||
}
|
||||
|
||||
pub(crate) fn load_delay_token(&self) -> DocumentLoadDelayTokenId {
|
||||
pub(crate) fn load_delay_token(&self) -> ChildDocumentModuleScriptLoadDelay {
|
||||
self.payload().load_delay_token
|
||||
}
|
||||
}
|
||||
@@ -269,7 +270,7 @@ pub(crate) struct FrameDocumentModuleGraphFailedPayload {
|
||||
script_handle: DomHandle,
|
||||
request_key: ModuleMapKey,
|
||||
tree_id: Option<module_tree::ModuleTreeId>,
|
||||
load_delay_token: DocumentLoadDelayTokenId,
|
||||
load_delay_token: ChildDocumentModuleScriptLoadDelay,
|
||||
}
|
||||
|
||||
pub(crate) type FrameDocumentModuleGraphFailedTarget =
|
||||
@@ -283,7 +284,7 @@ impl FrameDocumentModuleGraphFailedTarget {
|
||||
script_handle: DomHandle,
|
||||
request_key: ModuleMapKey,
|
||||
tree_id: Option<module_tree::ModuleTreeId>,
|
||||
load_delay_token: DocumentLoadDelayTokenId,
|
||||
load_delay_token: impl Into<ChildDocumentModuleScriptLoadDelay>,
|
||||
) -> Self {
|
||||
assert_eq!(pending_script_id.owner(), owner.document_owner());
|
||||
Self::new(
|
||||
@@ -294,7 +295,7 @@ impl FrameDocumentModuleGraphFailedTarget {
|
||||
script_handle,
|
||||
request_key,
|
||||
tree_id,
|
||||
load_delay_token,
|
||||
load_delay_token: load_delay_token.into(),
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -318,7 +319,7 @@ impl FrameDocumentModuleGraphFailedTarget {
|
||||
self.payload().tree_id
|
||||
}
|
||||
|
||||
pub(crate) fn load_delay_token(&self) -> DocumentLoadDelayTokenId {
|
||||
pub(crate) fn load_delay_token(&self) -> ChildDocumentModuleScriptLoadDelay {
|
||||
self.payload().load_delay_token
|
||||
}
|
||||
}
|
||||
@@ -362,7 +363,7 @@ impl ModuleScriptGraphFailedWork<FrameDocumentModuleGraphFailedTarget> {
|
||||
script_handle: DomHandle,
|
||||
request_key: ModuleMapKey,
|
||||
tree_id: Option<module_tree::ModuleTreeId>,
|
||||
load_delay_token: DocumentLoadDelayTokenId,
|
||||
load_delay_token: impl Into<ChildDocumentModuleScriptLoadDelay>,
|
||||
error: ModuleLoadError,
|
||||
) -> Self {
|
||||
Self::with_target(
|
||||
@@ -404,7 +405,7 @@ impl ModuleScriptGraphFailedWork<FrameDocumentModuleGraphFailedTarget> {
|
||||
self.target.tree_id()
|
||||
}
|
||||
|
||||
pub(crate) fn load_delay_token(&self) -> DocumentLoadDelayTokenId {
|
||||
pub(crate) fn load_delay_token(&self) -> ChildDocumentModuleScriptLoadDelay {
|
||||
self.target.load_delay_token()
|
||||
}
|
||||
}
|
||||
@@ -447,7 +448,7 @@ impl ModuleScriptGraphReadyWork<FrameDocumentModuleGraphReadyTarget> {
|
||||
script_handle: DomHandle,
|
||||
request_key: ModuleMapKey,
|
||||
tree_id: module_tree::ModuleTreeId,
|
||||
load_delay_token: DocumentLoadDelayTokenId,
|
||||
load_delay_token: impl Into<ChildDocumentModuleScriptLoadDelay>,
|
||||
graph: ModuleGraphHandle,
|
||||
) -> Self {
|
||||
Self::with_target(
|
||||
@@ -489,7 +490,7 @@ impl ModuleScriptGraphReadyWork<FrameDocumentModuleGraphReadyTarget> {
|
||||
self.target.tree_id()
|
||||
}
|
||||
|
||||
pub(crate) fn load_delay_token(&self) -> DocumentLoadDelayTokenId {
|
||||
pub(crate) fn load_delay_token(&self) -> ChildDocumentModuleScriptLoadDelay {
|
||||
self.target.load_delay_token()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,16 +66,17 @@ pub(crate) use frame_task_sources::{
|
||||
PendingChildExternalClassicDocumentScript, PendingChildJavascriptUrlDocumentScript,
|
||||
};
|
||||
pub(crate) use lifecycle_tasks::{
|
||||
ChildDocumentAsyncClassicScriptLoadDelay, DocumentLinkEventOwner,
|
||||
FrameDocumentCompleteLifecycleAction, FrameDocumentDomContentLoadedLifecycleAction,
|
||||
FrameDocumentImageLoadEventBinding, FrameDocumentInteractiveLifecycleAction,
|
||||
FrameDocumentLifecycleAction, FrameDocumentLifecycleTaskEffect,
|
||||
FrameDocumentMediaLoadDelayBinding, FrameDocumentUnloadLifecycleAction,
|
||||
MainDocumentCompleteLifecycleAction, MainDocumentDomContentLoadedLifecycleAction,
|
||||
MainDocumentImageLoadDelayBinding, MainDocumentInteractiveLifecycleAction,
|
||||
MainDocumentMediaLoadDelayBinding, MainDocumentScriptLoadDelayKind,
|
||||
MainDocumentScriptLoadDelayLease, MainDocumentScriptLoadDelayRelease,
|
||||
MainDocumentStyleLoadEventBinding, StylesheetSubresourceLoadDelayBinding,
|
||||
ChildDocumentAsyncClassicScriptLoadDelay, ChildDocumentModuleScriptLoadDelay,
|
||||
DocumentLinkEventOwner, FrameDocumentCompleteLifecycleAction,
|
||||
FrameDocumentDomContentLoadedLifecycleAction, FrameDocumentImageLoadEventBinding,
|
||||
FrameDocumentInteractiveLifecycleAction, FrameDocumentLifecycleAction,
|
||||
FrameDocumentLifecycleTaskEffect, FrameDocumentMediaLoadDelayBinding,
|
||||
FrameDocumentUnloadLifecycleAction, MainDocumentCompleteLifecycleAction,
|
||||
MainDocumentDomContentLoadedLifecycleAction, MainDocumentImageLoadDelayBinding,
|
||||
MainDocumentInteractiveLifecycleAction, MainDocumentMediaLoadDelayBinding,
|
||||
MainDocumentScriptLoadDelayKind, MainDocumentScriptLoadDelayLease,
|
||||
MainDocumentScriptLoadDelayRelease, MainDocumentStyleLoadEventBinding,
|
||||
StylesheetSubresourceLoadDelayBinding,
|
||||
};
|
||||
pub(crate) use load_delivery_tasks::{
|
||||
FrameDocumentLoadDeliveryAction, FrameDocumentLoadDeliveryAdmission,
|
||||
|
||||
@@ -80,6 +80,7 @@ pub(crate) enum FrameDocumentRealmBoundScriptWork {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum FrameDocumentScriptWorkAdmission {
|
||||
QueuedBehindRealm,
|
||||
QueuedBehindScript,
|
||||
Runnable,
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,28 @@ impl ChildDocumentAsyncClassicScriptLoadDelay {
|
||||
}
|
||||
}
|
||||
|
||||
/// A module inserted after `load` still runs without reopening the load gate.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum ChildDocumentModuleScriptLoadDelay {
|
||||
Pending(DocumentLoadDelayTokenId),
|
||||
AlreadyUnblocked,
|
||||
}
|
||||
|
||||
impl From<DocumentLoadDelayTokenId> for ChildDocumentModuleScriptLoadDelay {
|
||||
fn from(token: DocumentLoadDelayTokenId) -> Self {
|
||||
Self::Pending(token)
|
||||
}
|
||||
}
|
||||
|
||||
impl ChildDocumentModuleScriptLoadDelay {
|
||||
pub(crate) const fn token(self) -> Option<DocumentLoadDelayTokenId> {
|
||||
match self {
|
||||
Self::Pending(token) => Some(token),
|
||||
Self::AlreadyUnblocked => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of consuming one exact main-Document script load-delay lease.
|
||||
///
|
||||
/// This reports a durable load-gate state transition. It does not itself wake
|
||||
|
||||
@@ -10,13 +10,12 @@ use crate::module_runtime::{
|
||||
};
|
||||
use crate::planning::{PreparedScript, ScriptFetchMetadata};
|
||||
|
||||
use super::lifecycle_tasks::DocumentLinkEventOwner;
|
||||
use super::lifecycle_tasks::{ChildDocumentModuleScriptLoadDelay, DocumentLinkEventOwner};
|
||||
use super::module_graph::{
|
||||
FrameDocumentDynamicImportTerminalPreparedAction, FrameDocumentModuleTerminalQueueFollowup,
|
||||
};
|
||||
use super::records::{
|
||||
DocumentLoadDelayTokenId, FrameDocumentOwner, FrameDocumentTaskOwner, FrameRealmId,
|
||||
FrameRequestId, FrameRequestKind,
|
||||
FrameDocumentOwner, FrameDocumentTaskOwner, FrameRealmId, FrameRequestId, FrameRequestKind,
|
||||
};
|
||||
|
||||
/// Exact PageVm-local execution target captured when a child module fetch is
|
||||
@@ -176,7 +175,7 @@ pub(crate) struct FrameDocumentParserRootModuleClient {
|
||||
base_url: url::Url,
|
||||
fetch_metadata: ScriptFetchMetadata,
|
||||
source_is_external: bool,
|
||||
load_delay_token: DocumentLoadDelayTokenId,
|
||||
load_delay_token: ChildDocumentModuleScriptLoadDelay,
|
||||
}
|
||||
|
||||
impl FrameDocumentParserRootModuleClient {
|
||||
@@ -187,7 +186,7 @@ impl FrameDocumentParserRootModuleClient {
|
||||
base_url: url::Url,
|
||||
fetch_metadata: ScriptFetchMetadata,
|
||||
source_is_external: bool,
|
||||
load_delay_token: DocumentLoadDelayTokenId,
|
||||
load_delay_token: impl Into<ChildDocumentModuleScriptLoadDelay>,
|
||||
) -> Self {
|
||||
Self {
|
||||
pending_script_key,
|
||||
@@ -196,7 +195,7 @@ impl FrameDocumentParserRootModuleClient {
|
||||
base_url,
|
||||
fetch_metadata,
|
||||
source_is_external,
|
||||
load_delay_token,
|
||||
load_delay_token: load_delay_token.into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +226,7 @@ impl FrameDocumentParserRootModuleClient {
|
||||
self.source_is_external
|
||||
}
|
||||
|
||||
pub(crate) fn load_delay_token(&self) -> DocumentLoadDelayTokenId {
|
||||
pub(crate) fn load_delay_token(&self) -> ChildDocumentModuleScriptLoadDelay {
|
||||
self.load_delay_token
|
||||
}
|
||||
}
|
||||
@@ -274,7 +273,7 @@ impl FrameDocumentParserModuleRootStartTask {
|
||||
pending_script_id: ParserPendingScriptId<FrameDocumentOwner>,
|
||||
script_handle: DomHandle,
|
||||
script: PreparedScript,
|
||||
load_delay_token: DocumentLoadDelayTokenId,
|
||||
load_delay_token: impl Into<ChildDocumentModuleScriptLoadDelay>,
|
||||
) -> Self {
|
||||
assert_eq!(script.kind, crate::types::ScriptKind::Module);
|
||||
assert_eq!(pending_script_id.owner(), owner.document_owner());
|
||||
|
||||
@@ -70,7 +70,7 @@ impl ChildDocumentModulatorStore {
|
||||
parent_key: ModuleMapKey,
|
||||
requests: Vec<ModuleRequestRecord>,
|
||||
effective_fetch_metadata: ModuleFetchMetadata,
|
||||
load_delay_token: crate::frame_owner_model::DocumentLoadDelayTokenId,
|
||||
load_delay_token: crate::frame_owner_model::ChildDocumentModuleScriptLoadDelay,
|
||||
) -> module_tree::ModuleTreeId {
|
||||
assert_eq!(pending_script_id.owner(), owner.document_owner());
|
||||
let document_owner = owner.document_owner();
|
||||
|
||||
@@ -160,8 +160,8 @@ fn parser_root_script(handle: usize, url: &Url) -> PreparedScript {
|
||||
|
||||
fn parser_root_load_delay_token(
|
||||
handle: usize,
|
||||
) -> crate::frame_owner_model::DocumentLoadDelayTokenId {
|
||||
crate::frame_owner_model::DocumentLoadDelayTokenId(handle as u64 + 1)
|
||||
) -> crate::frame_owner_model::ChildDocumentModuleScriptLoadDelay {
|
||||
crate::frame_owner_model::DocumentLoadDelayTokenId(handle as u64 + 1).into()
|
||||
}
|
||||
|
||||
fn parser_root_client(handle: usize, url: &Url) -> FrameDocumentParserRootModuleClient {
|
||||
|
||||
@@ -352,7 +352,7 @@ pub(crate) fn module_script_graph_failed_work_from_root_client(
|
||||
script: crate::planning::PreparedScript,
|
||||
script_handle: DomHandle,
|
||||
request_key: ModuleMapKey,
|
||||
load_delay_token: crate::frame_owner_model::DocumentLoadDelayTokenId,
|
||||
load_delay_token: crate::frame_owner_model::ChildDocumentModuleScriptLoadDelay,
|
||||
error: ModuleLoadError,
|
||||
) -> DocumentModuleGraphFailedWork {
|
||||
DocumentModuleGraphFailedWork::new(
|
||||
|
||||
@@ -2,15 +2,16 @@ use std::collections::{BTreeMap, HashMap, VecDeque};
|
||||
|
||||
use super::ids::FrameOwnerIdAllocator;
|
||||
use super::lifecycle_tasks::{
|
||||
ChildDocumentAsyncClassicScriptLoadDelay, DocumentLinkEventOwner,
|
||||
FrameDocumentCompleteLifecycleAction, FrameDocumentDomContentLoadedLifecycleAction,
|
||||
FrameDocumentImageLoadEventBinding, FrameDocumentInteractiveLifecycleAction,
|
||||
FrameDocumentLifecycleAction, FrameDocumentMediaLoadDelayBinding,
|
||||
MainDocumentCompleteLifecycleAction, MainDocumentDomContentLoadedLifecycleAction,
|
||||
MainDocumentImageLoadDelayBinding, MainDocumentInteractiveLifecycleAction,
|
||||
MainDocumentMediaLoadDelayBinding, MainDocumentScriptLoadDelayKind,
|
||||
MainDocumentScriptLoadDelayLease, MainDocumentScriptLoadDelayRelease,
|
||||
MainDocumentStyleLoadEventBinding, StylesheetSubresourceLoadDelayBinding,
|
||||
ChildDocumentAsyncClassicScriptLoadDelay, ChildDocumentModuleScriptLoadDelay,
|
||||
DocumentLinkEventOwner, FrameDocumentCompleteLifecycleAction,
|
||||
FrameDocumentDomContentLoadedLifecycleAction, FrameDocumentImageLoadEventBinding,
|
||||
FrameDocumentInteractiveLifecycleAction, FrameDocumentLifecycleAction,
|
||||
FrameDocumentMediaLoadDelayBinding, MainDocumentCompleteLifecycleAction,
|
||||
MainDocumentDomContentLoadedLifecycleAction, MainDocumentImageLoadDelayBinding,
|
||||
MainDocumentInteractiveLifecycleAction, MainDocumentMediaLoadDelayBinding,
|
||||
MainDocumentScriptLoadDelayKind, MainDocumentScriptLoadDelayLease,
|
||||
MainDocumentScriptLoadDelayRelease, MainDocumentStyleLoadEventBinding,
|
||||
StylesheetSubresourceLoadDelayBinding,
|
||||
};
|
||||
use super::load_event_gate::DocumentLoadGateRelease;
|
||||
use super::module_clients::{
|
||||
@@ -2968,12 +2969,20 @@ impl FrameOwnerStore {
|
||||
&mut self,
|
||||
child_handle: DomHandle,
|
||||
owner: FrameDocumentTaskOwner,
|
||||
) -> Option<DocumentLoadDelayTokenId> {
|
||||
self.acquire_current_child_document_load_delay(
|
||||
child_handle,
|
||||
owner,
|
||||
DocumentLoadDelayReason::AsyncModuleScript,
|
||||
)
|
||||
) -> Option<ChildDocumentModuleScriptLoadDelay> {
|
||||
if !self.child_document_task_owner_is_current(child_handle, owner) {
|
||||
return None;
|
||||
}
|
||||
if let Some(token) =
|
||||
self.acquire_document_load_delay(owner, DocumentLoadDelayReason::AsyncModuleScript)
|
||||
{
|
||||
return Some(ChildDocumentModuleScriptLoadDelay::Pending(token));
|
||||
}
|
||||
self.documents
|
||||
.get(&owner.document_id)
|
||||
.filter(|document| document.local_window_id == owner.local_window_id)
|
||||
.is_some_and(|document| document.lifecycle_progress.is_complete())
|
||||
.then_some(ChildDocumentModuleScriptLoadDelay::AlreadyUnblocked)
|
||||
}
|
||||
|
||||
pub(crate) fn accept_current_child_image_load_event(
|
||||
|
||||
@@ -4141,11 +4141,28 @@ fn child_document_script_delays_own_dcl_and_complete_readiness() {
|
||||
.is_none(),
|
||||
"the async-module token must delay complete without blocking DCL"
|
||||
);
|
||||
assert!(store.release_async_module_script_load_delay(owner, async_module));
|
||||
assert!(store.release_async_module_script_load_delay(
|
||||
owner,
|
||||
async_module.token().expect("pending module delay")
|
||||
));
|
||||
let complete = store
|
||||
.prepare_current_child_document_complete_transition(child_handle, owner)
|
||||
.expect("the final async-module terminal should unblock complete");
|
||||
assert!(store.apply_current_child_document_complete_transition(complete));
|
||||
assert_eq!(
|
||||
store.acquire_current_child_async_module_script_load_delay(child_handle, owner),
|
||||
Some(ChildDocumentModuleScriptLoadDelay::AlreadyUnblocked),
|
||||
"dynamic modules remain admissible after load without reopening its gate",
|
||||
);
|
||||
assert_eq!(
|
||||
store.current_child_document_has_load_delay_tokens(child_handle, owner),
|
||||
Some(false)
|
||||
);
|
||||
assert!(
|
||||
store
|
||||
.prepare_current_child_document_complete_transition(child_handle, owner)
|
||||
.is_none()
|
||||
);
|
||||
|
||||
let replacement_transition = store
|
||||
.replace_child_document(
|
||||
@@ -4191,7 +4208,10 @@ fn child_document_script_delays_own_dcl_and_complete_readiness() {
|
||||
"replacement must retire parser-deferred delays owned by the old document"
|
||||
);
|
||||
assert!(
|
||||
!store.release_async_module_script_load_delay(replacement_owner, retired_async_module),
|
||||
!store.release_async_module_script_load_delay(
|
||||
replacement_owner,
|
||||
retired_async_module.token().expect("pending module delay")
|
||||
),
|
||||
"replacement must retire async-module delays owned by the old document"
|
||||
);
|
||||
let final_owner = final_transition
|
||||
@@ -4213,7 +4233,12 @@ fn child_document_script_delays_own_dcl_and_complete_readiness() {
|
||||
"bulk cancellation must consume the parser-deferred token exactly once"
|
||||
);
|
||||
assert!(
|
||||
!store.release_async_module_script_load_delay(final_owner, replacement_async_module),
|
||||
!store.release_async_module_script_load_delay(
|
||||
final_owner,
|
||||
replacement_async_module
|
||||
.token()
|
||||
.expect("pending module delay")
|
||||
),
|
||||
"bulk cancellation must consume the async-module token exactly once"
|
||||
);
|
||||
assert_eq!(
|
||||
|
||||
@@ -36,7 +36,7 @@ impl NativeDocumentModulator {
|
||||
parent_key: ModuleMapKey,
|
||||
requests: Vec<ModuleRequestRecord>,
|
||||
effective_fetch_metadata: ModuleFetchMetadata,
|
||||
load_delay_token: crate::frame_owner_model::DocumentLoadDelayTokenId,
|
||||
load_delay_token: crate::frame_owner_model::ChildDocumentModuleScriptLoadDelay,
|
||||
) -> module_tree::ModuleTreeId {
|
||||
let request_count = requests.len();
|
||||
let parser_tree_job = NativeModuleGraphJob::parser_owned_compiled_entry(
|
||||
|
||||
@@ -6,7 +6,9 @@ use moli_module_script_tree as module_tree;
|
||||
use crate::document_module_graph::{ModuleEntryId, ModuleMapKey};
|
||||
use crate::document_runtime::DomHandle;
|
||||
use crate::document_script_scheduler::{ParserPendingScriptId, ParserPendingScriptKey};
|
||||
use crate::frame_owner_model::{DocumentLoadDelayTokenId, FrameDocumentTaskOwner, FrameRealmId};
|
||||
use crate::frame_owner_model::{
|
||||
ChildDocumentModuleScriptLoadDelay, FrameDocumentTaskOwner, FrameRealmId,
|
||||
};
|
||||
use crate::planning::PreparedScript;
|
||||
|
||||
use super::NativeModuleGraphJob;
|
||||
@@ -22,7 +24,7 @@ pub(crate) struct NativeParserModuleTreeRoot {
|
||||
entry_id: ModuleEntryId,
|
||||
request_count: usize,
|
||||
dependency_count: usize,
|
||||
load_delay_token: DocumentLoadDelayTokenId,
|
||||
load_delay_token: ChildDocumentModuleScriptLoadDelay,
|
||||
}
|
||||
|
||||
impl NativeParserModuleTreeRoot {
|
||||
@@ -36,7 +38,7 @@ impl NativeParserModuleTreeRoot {
|
||||
tree_id: module_tree::ModuleTreeId,
|
||||
entry_id: ModuleEntryId,
|
||||
request_count: usize,
|
||||
load_delay_token: DocumentLoadDelayTokenId,
|
||||
load_delay_token: ChildDocumentModuleScriptLoadDelay,
|
||||
) -> Self {
|
||||
Self {
|
||||
owner,
|
||||
@@ -95,7 +97,7 @@ impl NativeParserModuleTreeRoot {
|
||||
self.dependency_count
|
||||
}
|
||||
|
||||
pub(crate) fn load_delay_token(&self) -> DocumentLoadDelayTokenId {
|
||||
pub(crate) fn load_delay_token(&self) -> ChildDocumentModuleScriptLoadDelay {
|
||||
self.load_delay_token
|
||||
}
|
||||
|
||||
|
||||
@@ -1025,7 +1025,7 @@ impl JsContextHost {
|
||||
if script.kind != ScriptKind::Module {
|
||||
return ScriptDisposition::Continue;
|
||||
}
|
||||
if !self.queue_child_parser_module_root_for_current_document(
|
||||
if !self.queue_child_module_script_for_current_document(
|
||||
child_handle,
|
||||
script_handle,
|
||||
blocking_stylesheet_signatures,
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
|
||||
impl JsContextHost {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn queue_child_dynamic_external_classic_script_for_current_document(
|
||||
pub(crate) fn queue_child_dynamic_document_script_for_current_document(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
owner_document_handle: DomHandle,
|
||||
@@ -18,7 +18,9 @@ impl JsContextHost {
|
||||
mode: ScriptMode,
|
||||
source_kind: ScriptSourceKind,
|
||||
) -> std::result::Result<bool, String> {
|
||||
if kind != ScriptKind::Classic || source_kind != ScriptSourceKind::External {
|
||||
if kind != ScriptKind::Module
|
||||
&& (kind != ScriptKind::Classic || source_kind != ScriptSourceKind::External)
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
let Some(child_handle) =
|
||||
@@ -26,9 +28,17 @@ impl JsContextHost {
|
||||
else {
|
||||
return Ok(false);
|
||||
};
|
||||
// Frame-document scheduling owns ordering and exact Document identities.
|
||||
// This load payload is intentionally unbound to the main scheduler.
|
||||
let script = build_runtime_prepared_script(
|
||||
if !self.child_browsing_context_is_live(child_handle)
|
||||
|| self.child_browsing_context_document_handle(child_handle)
|
||||
!= Some(owner_document_handle)
|
||||
|| self.dom_host().owner_document_handle(script_handle) != Some(owner_document_handle)
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
let Some(owner) = self.current_child_document_task_owner(child_handle) else {
|
||||
return Ok(false);
|
||||
};
|
||||
let mut script = build_runtime_prepared_script(
|
||||
preparation,
|
||||
script_handle,
|
||||
0,
|
||||
@@ -38,14 +48,34 @@ impl JsContextHost {
|
||||
kind,
|
||||
mode,
|
||||
)?;
|
||||
Ok(
|
||||
// Classic and module scripts with async=false share insertion order.
|
||||
// Node allocation order does not determine execution order.
|
||||
script.position = self
|
||||
.child_runtime_script_order
|
||||
.register(owner, script_handle, mode);
|
||||
let accepted = if kind == ScriptKind::Module {
|
||||
self.queue_child_module_script_for_current_document(
|
||||
child_handle,
|
||||
script_handle,
|
||||
Default::default(),
|
||||
script,
|
||||
)
|
||||
} else {
|
||||
self.queue_child_external_classic_document_script_for_current_document(
|
||||
child_handle,
|
||||
owner_document_handle,
|
||||
script_handle,
|
||||
script,
|
||||
),
|
||||
)
|
||||
)
|
||||
};
|
||||
if accepted {
|
||||
let _ = self
|
||||
.dom_host_mut()
|
||||
.set_script_already_started(script_handle, true);
|
||||
} else {
|
||||
self.finish_child_runtime_script(owner, script_handle);
|
||||
}
|
||||
Ok(accepted)
|
||||
}
|
||||
|
||||
pub(crate) fn execute_child_dynamic_inline_classic_script_on_current_stack(
|
||||
|
||||
@@ -129,7 +129,9 @@ impl JsContextHost {
|
||||
self.cancel_child_classic_document_script_work(handle);
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::context_host) fn queue_child_parser_module_root_for_current_document(
|
||||
/// Parser and runtime insertions share the module graph pipeline. Only
|
||||
/// ModuleDefer reserves parser order and delays DOMContentLoaded.
|
||||
pub(in crate::native_bridge::context_host) fn queue_child_module_script_for_current_document(
|
||||
&mut self,
|
||||
handle: DomHandle,
|
||||
script_handle: DomHandle,
|
||||
@@ -168,6 +170,7 @@ impl JsContextHost {
|
||||
let load_delay_token = if parser_ordered {
|
||||
self.frame_owner_store
|
||||
.acquire_current_child_parser_deferred_script_load_delay(handle, owner)
|
||||
.map(Into::into)
|
||||
} else {
|
||||
self.frame_owner_store
|
||||
.acquire_current_child_async_module_script_load_delay(handle, owner)
|
||||
@@ -324,9 +327,12 @@ impl JsContextHost {
|
||||
pub(crate) fn release_child_module_script_load_delay(
|
||||
&mut self,
|
||||
owner: FrameDocumentTaskOwner,
|
||||
load_delay_token: crate::frame_owner_model::DocumentLoadDelayTokenId,
|
||||
load_delay: crate::frame_owner_model::ChildDocumentModuleScriptLoadDelay,
|
||||
parser_ordered: bool,
|
||||
) -> bool {
|
||||
let Some(load_delay_token) = load_delay.token() else {
|
||||
return false;
|
||||
};
|
||||
if parser_ordered {
|
||||
self.frame_owner_store
|
||||
.release_parser_deferred_script_load_delay(owner, load_delay_token)
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
use crate::{
|
||||
document_runtime::DomHandle,
|
||||
frame_owner_model::{FrameDocumentScriptReadyTaskWork, FrameDocumentTaskOwner, FrameRealmId},
|
||||
types::ScriptMode,
|
||||
};
|
||||
|
||||
struct RuntimeScriptEntry {
|
||||
node: DomHandle,
|
||||
position: usize,
|
||||
dispatched: bool,
|
||||
ready: Option<(FrameRealmId, FrameDocumentScriptReadyTaskWork)>,
|
||||
}
|
||||
|
||||
/// The HTML in-order list retains scripts until earlier elements have run.
|
||||
/// Eligible work then enters the stable Page source, which owns task FIFO.
|
||||
#[derive(Default)]
|
||||
pub(super) struct ChildRuntimeScriptOrder {
|
||||
next_position: usize,
|
||||
documents: HashMap<FrameDocumentTaskOwner, VecDeque<RuntimeScriptEntry>>,
|
||||
}
|
||||
|
||||
impl ChildRuntimeScriptOrder {
|
||||
pub(super) fn register(
|
||||
&mut self,
|
||||
owner: FrameDocumentTaskOwner,
|
||||
node: DomHandle,
|
||||
mode: ScriptMode,
|
||||
) -> usize {
|
||||
if let Some(entry) = self
|
||||
.documents
|
||||
.get(&owner)
|
||||
.and_then(|entries| entries.iter().find(|entry| entry.node == node))
|
||||
{
|
||||
return entry.position;
|
||||
}
|
||||
let position = self.next_position;
|
||||
self.next_position = position
|
||||
.checked_add(1)
|
||||
.expect("child runtime script position overflow");
|
||||
if matches!(mode, ScriptMode::InOrder | ScriptMode::ModuleInOrder) {
|
||||
self.documents
|
||||
.entry(owner)
|
||||
.or_default()
|
||||
.push_back(RuntimeScriptEntry {
|
||||
node,
|
||||
position,
|
||||
dispatched: false,
|
||||
ready: None,
|
||||
});
|
||||
}
|
||||
position
|
||||
}
|
||||
|
||||
pub(super) fn waiting_for_predecessor(
|
||||
&self,
|
||||
owner: FrameDocumentTaskOwner,
|
||||
node: DomHandle,
|
||||
) -> bool {
|
||||
self.documents.get(&owner).is_some_and(|entries| {
|
||||
entries
|
||||
.iter()
|
||||
.position(|entry| entry.node == node)
|
||||
.is_some_and(|index| index != 0)
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn admit_or_retain(
|
||||
&mut self,
|
||||
realm_id: FrameRealmId,
|
||||
work: FrameDocumentScriptReadyTaskWork,
|
||||
) -> Option<FrameDocumentScriptReadyTaskWork> {
|
||||
let route = work.route();
|
||||
let Some(entries) = self.documents.get_mut(&route.task_owner()) else {
|
||||
return Some(work);
|
||||
};
|
||||
let Some(index) = entries
|
||||
.iter()
|
||||
.position(|entry| entry.node == route.script_handle())
|
||||
else {
|
||||
return Some(work);
|
||||
};
|
||||
let entry = &mut entries[index];
|
||||
// Evaluation reactions may arrive after initial execution was admitted.
|
||||
if index == 0 || entry.dispatched {
|
||||
entry.dispatched = true;
|
||||
return Some(work);
|
||||
}
|
||||
debug_assert!(
|
||||
entry.ready.is_none(),
|
||||
"an ordered script has one initial ready result"
|
||||
);
|
||||
entry.ready = Some((realm_id, work));
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn finish(
|
||||
&mut self,
|
||||
owner: FrameDocumentTaskOwner,
|
||||
node: DomHandle,
|
||||
) -> Option<(FrameRealmId, FrameDocumentScriptReadyTaskWork)> {
|
||||
let entries = self.documents.get_mut(&owner)?;
|
||||
let index = entries.iter().position(|entry| entry.node == node)?;
|
||||
entries.remove(index);
|
||||
let next = entries.front_mut().and_then(|entry| {
|
||||
let ready = entry.ready.take()?;
|
||||
entry.dispatched = true;
|
||||
Some(ready)
|
||||
});
|
||||
if entries.is_empty() {
|
||||
self.documents.remove(&owner);
|
||||
}
|
||||
next
|
||||
}
|
||||
|
||||
pub(super) fn remove_document(
|
||||
&mut self,
|
||||
owner: FrameDocumentTaskOwner,
|
||||
) -> Vec<FrameDocumentScriptReadyTaskWork> {
|
||||
self.documents
|
||||
.remove(&owner)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|entry| entry.ready.map(|(_, work)| work))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::frame_owner_model::{
|
||||
DocumentId, FrameDocumentRealmBoundScriptWork, FrameSchedulerLaneId, LocalWindowId,
|
||||
PendingChildDynamicDocumentScript,
|
||||
};
|
||||
|
||||
fn owner(document: u64) -> FrameDocumentTaskOwner {
|
||||
FrameDocumentTaskOwner::new(
|
||||
FrameSchedulerLaneId(1),
|
||||
LocalWindowId(2),
|
||||
DocumentId(document),
|
||||
)
|
||||
}
|
||||
|
||||
fn work(owner: FrameDocumentTaskOwner, node: DomHandle) -> FrameDocumentScriptReadyTaskWork {
|
||||
FrameDocumentRealmBoundScriptWork::DynamicClassic(PendingChildDynamicDocumentScript {
|
||||
child_handle: DomHandle::new(1),
|
||||
owner,
|
||||
realm_id: Some(FrameRealmId(2)),
|
||||
script_handle: node,
|
||||
source: String::new(),
|
||||
script_nonce: None,
|
||||
script_integrity: None,
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_order_uses_admission_not_node_order_and_does_not_hold_async() {
|
||||
let mut order = ChildRuntimeScriptOrder::default();
|
||||
let owner = owner(3);
|
||||
let (first, second, third, independent) = (
|
||||
DomHandle::new(40),
|
||||
DomHandle::new(20),
|
||||
DomHandle::new(10),
|
||||
DomHandle::new(5),
|
||||
);
|
||||
assert!(
|
||||
order.register(owner, first, ScriptMode::InOrder)
|
||||
< order.register(owner, second, ScriptMode::ModuleInOrder)
|
||||
);
|
||||
order.register(owner, third, ScriptMode::InOrder);
|
||||
order.register(owner, independent, ScriptMode::Async);
|
||||
let realm = FrameRealmId(2);
|
||||
assert!(order.admit_or_retain(realm, work(owner, third)).is_none());
|
||||
assert!(order.admit_or_retain(realm, work(owner, second)).is_none());
|
||||
assert!(
|
||||
order
|
||||
.admit_or_retain(realm, work(owner, independent))
|
||||
.is_some()
|
||||
);
|
||||
assert!(order.admit_or_retain(realm, work(owner, first)).is_some());
|
||||
assert_eq!(
|
||||
order
|
||||
.finish(owner, first)
|
||||
.unwrap()
|
||||
.1
|
||||
.route()
|
||||
.script_handle(),
|
||||
second
|
||||
);
|
||||
// A TLA reaction for the admitted module is independent of later elements.
|
||||
assert!(order.admit_or_retain(realm, work(owner, second)).is_some());
|
||||
assert_eq!(
|
||||
order
|
||||
.finish(owner, second)
|
||||
.unwrap()
|
||||
.1
|
||||
.route()
|
||||
.script_handle(),
|
||||
third
|
||||
);
|
||||
assert!(order.finish(owner, third).is_none());
|
||||
assert!(order.documents.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_order_rollback_and_document_retirement_leave_other_owners_runnable() {
|
||||
let mut order = ChildRuntimeScriptOrder::default();
|
||||
let (first, rejected, last) = (DomHandle::new(3), DomHandle::new(4), DomHandle::new(5));
|
||||
let (old, current) = (owner(10), owner(11));
|
||||
for node in [first, rejected, last] {
|
||||
order.register(old, node, ScriptMode::ModuleInOrder);
|
||||
}
|
||||
let realm = FrameRealmId(2);
|
||||
assert!(order.admit_or_retain(realm, work(old, last)).is_none());
|
||||
assert!(order.finish(old, rejected).is_none());
|
||||
order.register(current, first, ScriptMode::ModuleInOrder);
|
||||
assert!(order.admit_or_retain(realm, work(current, first)).is_some());
|
||||
let retired = order.remove_document(old);
|
||||
assert_eq!(retired.len(), 1);
|
||||
assert_eq!(retired[0].route().script_handle(), last);
|
||||
assert!(order.finish(old, first).is_none());
|
||||
assert!(order.finish(current, first).is_none());
|
||||
assert!(order.documents.is_empty());
|
||||
}
|
||||
}
|
||||
@@ -349,6 +349,8 @@ impl JsContextHost {
|
||||
super::text_track_default_modes::TextTrackDefaultModeState::default(),
|
||||
child_document_script_ready_tasks:
|
||||
super::document_script_ready_inputs::ChildDocumentScriptReadyTaskLedger::default(),
|
||||
child_runtime_script_order:
|
||||
super::child_runtime_script_order::ChildRuntimeScriptOrder::default(),
|
||||
pending_child_external_classic_document_scripts: HashMap::new(),
|
||||
pending_child_modulepreload_work_awaiting_realm: VecDeque::new(),
|
||||
active_child_browsing_context_host_loads: Vec::new(),
|
||||
|
||||
@@ -186,6 +186,12 @@ impl JsContextHost {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some(work) = self
|
||||
.child_runtime_script_order
|
||||
.admit_or_retain(realm_id, work)
|
||||
else {
|
||||
return true;
|
||||
};
|
||||
let task_id = self.child_document_script_ready_tasks.allocate_task_id();
|
||||
let target = RendererPageChildDocumentScriptReadyTarget::new(
|
||||
route.child_handle(),
|
||||
@@ -202,9 +208,9 @@ impl JsContextHost {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(work) = self.child_document_script_ready_tasks.remove_exact(target) {
|
||||
self.settle_child_document_script_ready_task_without_execution(work);
|
||||
}
|
||||
// A closed Page route cannot accept successors. Retire the whole
|
||||
// Document queue without recursively promoting its ordered scripts.
|
||||
self.retire_child_document_script_ready_tasks_for_owner(owner);
|
||||
tracing::debug!(
|
||||
?target,
|
||||
"retired child DocumentScriptReady payload after stable route closure"
|
||||
@@ -226,10 +232,10 @@ impl JsContextHost {
|
||||
self.queue_child_document_script_ready_task_for_realm(work, realm_id)
|
||||
}
|
||||
|
||||
/// Move every currently-runnable scheduler action into the single stable
|
||||
/// child-frame source. Current owners without a materialized realm retain
|
||||
/// their work in the scheduler store; stale owners are consumed rather
|
||||
/// than blocking later Documents.
|
||||
/// Admit scheduler actions through the runtime in-order list into the
|
||||
/// single stable child-frame source. Current owners without a materialized
|
||||
/// realm retain their work in the scheduler store; stale owners are consumed
|
||||
/// rather than blocking later Documents.
|
||||
pub(crate) fn admit_runnable_child_document_script_tasks(&mut self) -> usize {
|
||||
let mut admitted = 0;
|
||||
loop {
|
||||
@@ -309,6 +315,17 @@ impl JsContextHost {
|
||||
self.child_document_script_ready_tasks.remove_exact(target)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_child_runtime_script(
|
||||
&mut self,
|
||||
owner: FrameDocumentTaskOwner,
|
||||
script_handle: crate::document_runtime::DomHandle,
|
||||
) {
|
||||
if let Some((realm_id, work)) = self.child_runtime_script_order.finish(owner, script_handle)
|
||||
{
|
||||
let _ = self.queue_child_document_script_ready_task_for_realm(work, realm_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn discard_pending_child_document_script_ready_task(
|
||||
&mut self,
|
||||
task_id: RendererPageChildDocumentScriptReadyTaskId,
|
||||
@@ -324,7 +341,10 @@ impl JsContextHost {
|
||||
&mut self,
|
||||
owner: FrameDocumentTaskOwner,
|
||||
) -> usize {
|
||||
let retired = self.child_document_script_ready_tasks.remove_owner(owner);
|
||||
// Also drain work waiting for an earlier runtime script. This path is
|
||||
// used for both Document retirement and failed realm materialization.
|
||||
let mut retired = self.child_runtime_script_order.remove_document(owner);
|
||||
retired.extend(self.child_document_script_ready_tasks.remove_owner(owner));
|
||||
let retired_count = retired.len();
|
||||
for work in retired {
|
||||
self.settle_child_document_script_ready_task_without_execution(work);
|
||||
@@ -418,8 +438,16 @@ impl JsContextHost {
|
||||
..
|
||||
} => FrameDocumentScriptWorkAdmission::QueuedBehindRealm,
|
||||
};
|
||||
let work = work.bind_to_realm(realm_id);
|
||||
self.queue_child_document_script_ready_task_for_realm(work.into(), realm_id)
|
||||
let work = FrameDocumentScriptReadyTaskWork::from(work.bind_to_realm(realm_id));
|
||||
let admission = if self
|
||||
.child_runtime_script_order
|
||||
.waiting_for_predecessor(owner, work.route().script_handle())
|
||||
{
|
||||
FrameDocumentScriptWorkAdmission::QueuedBehindScript
|
||||
} else {
|
||||
admission
|
||||
};
|
||||
self.queue_child_document_script_ready_task_for_realm(work, realm_id)
|
||||
.then_some(admission)
|
||||
}
|
||||
|
||||
@@ -445,6 +473,8 @@ impl JsContextHost {
|
||||
&mut self,
|
||||
work: FrameDocumentScriptReadyTaskWork,
|
||||
) {
|
||||
let route = work.route();
|
||||
self.finish_child_runtime_script(route.task_owner(), route.script_handle());
|
||||
let FrameDocumentScriptReadyTaskWork::DocumentScriptExecution(work) = work else {
|
||||
// Scheduler-owned parser/module state is retired together with its
|
||||
// exact Document. It must not be reinserted into another queue.
|
||||
|
||||
@@ -80,6 +80,7 @@ mod child_dynamic_scripts;
|
||||
mod child_events;
|
||||
mod child_frame_navigation;
|
||||
mod child_frame_runtime;
|
||||
mod child_runtime_script_order;
|
||||
pub(crate) use child_frame_runtime::install_child_window_proxy_access_check_handlers;
|
||||
mod child_frame_snapshots;
|
||||
mod child_frames;
|
||||
@@ -969,6 +970,7 @@ pub(crate) struct JsContextHost {
|
||||
text_track_default_modes: text_track_default_modes::TextTrackDefaultModeState,
|
||||
child_document_script_ready_tasks:
|
||||
document_script_ready_inputs::ChildDocumentScriptReadyTaskLedger,
|
||||
child_runtime_script_order: child_runtime_script_order::ChildRuntimeScriptOrder,
|
||||
pending_child_external_classic_document_scripts:
|
||||
HashMap<u64, child_frames::PendingChildExternalClassicDocumentScriptLoad>,
|
||||
pending_child_modulepreload_work_awaiting_realm:
|
||||
|
||||
@@ -1,5 +1,100 @@
|
||||
use super::*;
|
||||
|
||||
async fn drain_child_runtime_module_tasks(page_vm: &mut PageVm) -> anyhow::Result<()> {
|
||||
let loader = page_vm.request_client.clone();
|
||||
for _ in 0..32 {
|
||||
if page_vm
|
||||
.run_next_child_frame_task_source_for_semantic_test()
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if page_vm
|
||||
.run_exact_selected_page_task_for_test(
|
||||
PageSelectedTaskTestSelector::ChildModuleScriptTerminal,
|
||||
&loader,
|
||||
)
|
||||
.await?
|
||||
|| page_vm
|
||||
.run_exact_selected_page_task_for_test(
|
||||
PageSelectedTaskTestSelector::ModuleReaction,
|
||||
&loader,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
anyhow::bail!("child runtime module tasks did not settle in 32 turns")
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn child_runtime_inline_modules_preserve_insertion_order_after_load_and_release_tla() {
|
||||
run_page_vm_async_test(async move {
|
||||
let loader = crate::network::ResourceRequestClient::new(&FetchConfig::default())?;
|
||||
let (mut page_vm, _resource_source, _owner_wake_rx) =
|
||||
page_vm_with_bound_task_sources_and_owner_wake(
|
||||
&loader,
|
||||
Url::parse("https://example.com/runtime-module")?,
|
||||
);
|
||||
page_vm.vm_mut().eval(
|
||||
r#"
|
||||
globalThis.events = [];
|
||||
const frame = document.createElement('iframe');
|
||||
frame.id = 'runtime-module';
|
||||
frame.srcdoc = '<!doctype html><body>child';
|
||||
frame.onload = () => events.push('frame-load');
|
||||
document.body.append(frame);
|
||||
"queued"
|
||||
"#,
|
||||
)?;
|
||||
drain_child_runtime_module_tasks(&mut page_vm).await?;
|
||||
assert_eq!(page_vm.vm_mut().eval("events.join('|')")?, "frame-load");
|
||||
page_vm.vm_mut().eval(
|
||||
r#"
|
||||
(() => {
|
||||
const d = document.getElementById('runtime-module').contentDocument;
|
||||
const second = d.createElement('script'), first = d.createElement('script');
|
||||
first.type = second.type = 'module';
|
||||
first.async = second.async = false;
|
||||
first.textContent = `parent.events.push('first-start');
|
||||
await new Promise(resolve => parent.releaseModule = resolve);
|
||||
parent.events.push('first-end');`;
|
||||
second.textContent = `parent.events.push('second');`;
|
||||
first.onload = () => events.push('first-load');
|
||||
second.onload = () => events.push('second-load');
|
||||
d.body.append(first);
|
||||
d.body.append(second);
|
||||
return 'queued';
|
||||
})()
|
||||
"#,
|
||||
)?;
|
||||
drain_child_runtime_module_tasks(&mut page_vm).await?;
|
||||
assert_eq!(
|
||||
page_vm.vm_mut().eval("events.join('|')")?,
|
||||
"frame-load|first-start|second",
|
||||
"a pending TLA must not hold the next ordered module or repeat document load"
|
||||
);
|
||||
page_vm.vm_mut().eval("releaseModule(); 'resolved'")?;
|
||||
drain_child_runtime_module_tasks(&mut page_vm).await?;
|
||||
assert_eq!(
|
||||
page_vm.vm_mut().eval("events.join('|')")?,
|
||||
"frame-load|first-start|second|first-end"
|
||||
);
|
||||
assert_eq!(
|
||||
page_vm
|
||||
.vm_mut()
|
||||
.eval("document.getElementById('runtime-module').contentDocument.readyState")?,
|
||||
"complete"
|
||||
);
|
||||
Ok::<_, anyhow::Error>(())
|
||||
})
|
||||
.await
|
||||
.expect("child runtime inline modules should execute after load");
|
||||
}
|
||||
|
||||
pub(super) async fn queue_child_module_document_script_ready(
|
||||
page_vm: &mut PageVm,
|
||||
base_url: &str,
|
||||
|
||||
@@ -570,7 +570,7 @@ impl ScriptVm {
|
||||
parent_key: ModuleMapKey,
|
||||
requests: Vec<ModuleRequestRecord>,
|
||||
effective_fetch_metadata: ModuleFetchMetadata,
|
||||
load_delay_token: crate::frame_owner_model::DocumentLoadDelayTokenId,
|
||||
load_delay_token: crate::frame_owner_model::ChildDocumentModuleScriptLoadDelay,
|
||||
) -> ModuleTreeId {
|
||||
self.child_document_modulator_store
|
||||
.record_compiled_parser_root(
|
||||
|
||||
@@ -285,7 +285,7 @@ impl ChildModuleScriptExecutionOwner<'_> {
|
||||
script_handle: DomHandle,
|
||||
mode: ScriptMode,
|
||||
pending_script_key: crate::document_script_scheduler::ParserPendingScriptKey,
|
||||
load_delay_token: crate::frame_owner_model::DocumentLoadDelayTokenId,
|
||||
load_delay_token: crate::frame_owner_model::ChildDocumentModuleScriptLoadDelay,
|
||||
) -> std::result::Result<(), DocumentScriptExecutionOutcome> {
|
||||
let current = {
|
||||
let host = self.vm._context_host.borrow();
|
||||
@@ -398,7 +398,7 @@ impl ChildModuleScriptExecutionOwner<'_> {
|
||||
realm_id: FrameRealmId,
|
||||
mode: ScriptMode,
|
||||
pending_script_key: crate::document_script_scheduler::ParserPendingScriptKey,
|
||||
load_delay_token: crate::frame_owner_model::DocumentLoadDelayTokenId,
|
||||
load_delay_token: crate::frame_owner_model::ChildDocumentModuleScriptLoadDelay,
|
||||
) -> std::result::Result<(), DocumentScriptExecutionOutcome> {
|
||||
let current = self.check_current_frame_parser_module_route(owner, realm_id);
|
||||
if current.is_err() && mode == ScriptMode::ModuleDefer {
|
||||
@@ -436,7 +436,7 @@ impl ChildModuleScriptExecutionOwner<'_> {
|
||||
&mut self,
|
||||
owner: FrameDocumentTaskOwner,
|
||||
mode: ScriptMode,
|
||||
load_delay_token: crate::frame_owner_model::DocumentLoadDelayTokenId,
|
||||
load_delay_token: crate::frame_owner_model::ChildDocumentModuleScriptLoadDelay,
|
||||
) -> bool {
|
||||
let released = self
|
||||
.vm
|
||||
|
||||
@@ -46,7 +46,8 @@ impl<'vm> ChildReadyDocumentScriptOwner<'vm> {
|
||||
work: FrameDocumentScriptReadyTaskWork,
|
||||
) -> ChildReadyDocumentScriptOwnerOutput<'_> {
|
||||
Box::pin(async move {
|
||||
match work {
|
||||
let route = work.route();
|
||||
let outcome = match work {
|
||||
FrameDocumentScriptReadyTaskWork::Scheduler(work) => {
|
||||
self.run_ready_work(work).await
|
||||
}
|
||||
@@ -63,7 +64,14 @@ impl<'vm> ChildReadyDocumentScriptOwner<'vm> {
|
||||
.run_ready_work(work)
|
||||
.await
|
||||
}
|
||||
}
|
||||
};
|
||||
// Execution of a module begins in this task; a top-level await
|
||||
// continuation must not hold later in-order script elements.
|
||||
self.vm
|
||||
._context_host
|
||||
.borrow_mut()
|
||||
.finish_child_runtime_script(route.task_owner(), route.script_handle());
|
||||
outcome
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user