mirror of
https://github.com/lexmount/moli.git
synced 2026-10-03 16:00:45 +00:00
fix(html): queue document lifecycle transitions on the DOM source
Admit DOMContentLoaded and main Window load after their parser/defer and load prerequisites so earlier DOM tasks keep their FIFO positions. Keep interactive readiness at parser stop, preserve child realm prerequisites, and return a load boundary to its driver if an earlier DOM callback adds a new load delay. Queue parser-owned external import-map errors during preparation, including child and document.write parsers, so later dynamic errors cannot overtake them. Cover ordering, replacement and load-delay behavior in main and child documents. Validation: - cargo fmt --all - cargo clippy --workspace --all-targets --all-features -- -D warnings - cargo nextest run --no-fail-fast: 17,871 passed, 13 skipped - 270 WPT cases: 254 pass, 10 fail, 5 timeout, 1 error; one new pass and no case-status or passing-subtest-count regressions
This commit is contained in:
@@ -37,6 +37,9 @@ mod parser_cleanup;
|
||||
#[path = "scripts/parser_preparation.rs"]
|
||||
mod parser_preparation;
|
||||
|
||||
#[path = "scripts/lifecycle_task_order.rs"]
|
||||
mod lifecycle_task_order;
|
||||
|
||||
fn diagnostic_global<'a>(
|
||||
page: &'a moli_core::page::Page,
|
||||
name: &str,
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
use super::*;
|
||||
|
||||
async fn parser_rejection_notification_precedes_lifecycle_events(child: bool) -> Result<()> {
|
||||
let markup = r#"<!doctype html><body><script>
|
||||
window.events = [];
|
||||
document.addEventListener('readystatechange', () => events.push(document.readyState));
|
||||
document.addEventListener('DOMContentLoaded', () => events.push('DOMContentLoaded'));
|
||||
addEventListener('load', () => events.push('load'));
|
||||
addEventListener('unhandledrejection', event => {
|
||||
events.push(event.reason);
|
||||
event.preventDefault();
|
||||
});
|
||||
Promise.reject('rejection');
|
||||
</script><p id=tail>parser tail</p>"#;
|
||||
let result = observe_lifecycle_markup(child, markup).await?;
|
||||
assert_eq!(
|
||||
result["value"].as_str(),
|
||||
Some(r#"["interactive","rejection","DOMContentLoaded","complete","load"]"#),
|
||||
"child={child}"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn observe_lifecycle_markup(child: bool, markup: &str) -> Result<serde_json::Value> {
|
||||
let server = FixtureServer::spawn().await?;
|
||||
let browser = Browser::new(AppConfig::default())?;
|
||||
let mut target = url::Url::parse(&server.url("/compat/child-dynamic-markup-document"))?;
|
||||
target.query_pairs_mut().append_pair("markup", markup);
|
||||
if child {
|
||||
let parent = format!(
|
||||
"<!doctype html><iframe id=target src=\"{}\"></iframe>",
|
||||
target.as_str().replace('&', "&")
|
||||
);
|
||||
target.set_query(None);
|
||||
target.query_pairs_mut().append_pair("markup", &parent);
|
||||
}
|
||||
let mut page = browser.fetch(target.as_str()).await?;
|
||||
let result = page
|
||||
.evaluate_runtime_expression_with_await_async(
|
||||
"JSON.stringify((document.getElementById('target')?.contentWindow || window).events)",
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
server.shutdown().await;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn main_parser_rejection_notification_precedes_lifecycle_events() -> Result<()> {
|
||||
parser_rejection_notification_precedes_lifecycle_events(false).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn child_parser_rejection_notification_precedes_lifecycle_events() -> Result<()> {
|
||||
parser_rejection_notification_precedes_lifecycle_events(true).await
|
||||
}
|
||||
|
||||
async fn deferred_rejection_keeps_dom_fifo_position(child: bool, module: bool) -> Result<()> {
|
||||
let attribute = if module { "type=module" } else { "defer" };
|
||||
let markup = format!(
|
||||
r#"<!doctype html><body><script>
|
||||
window.events = [];
|
||||
document.addEventListener('readystatechange', () => events.push(document.readyState));
|
||||
document.addEventListener('DOMContentLoaded', () => events.push('DOMContentLoaded'));
|
||||
addEventListener('load', () => events.push('load'));
|
||||
addEventListener('unhandledrejection', event => {{
|
||||
event.preventDefault();
|
||||
events.push(event.reason);
|
||||
const later = document.createElement('script');
|
||||
later.src = '';
|
||||
later.onerror = () => events.push('later DOM task');
|
||||
document.body.append(later);
|
||||
}});
|
||||
</script><script {attribute} src="data:text/javascript,Promise.reject('rejection')"></script>
|
||||
<p id=tail>parser tail</p>"#
|
||||
);
|
||||
let result = observe_lifecycle_markup(child, &markup).await?;
|
||||
let events: Vec<String> = serde_json::from_str(result["value"].as_str().unwrap())?;
|
||||
let at = |name: &str| events.iter().position(|event| event == name).unwrap();
|
||||
assert!(at("interactive") < at("rejection"), "{events:?}");
|
||||
assert!(at("rejection") < at("DOMContentLoaded"), "{events:?}");
|
||||
assert!(at("DOMContentLoaded") < at("later DOM task"), "{events:?}");
|
||||
assert!(at("rejection") < at("complete"), "{events:?}");
|
||||
assert!(at("complete") < at("load"), "{events:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn main_defer_dom_tasks_keep_their_positions_around_domcontentloaded() -> Result<()> {
|
||||
deferred_rejection_keeps_dom_fifo_position(false, false).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn child_defer_dom_tasks_keep_their_positions_around_domcontentloaded() -> Result<()> {
|
||||
deferred_rejection_keeps_dom_fifo_position(true, false).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn main_module_dom_tasks_keep_their_positions_around_domcontentloaded() -> Result<()> {
|
||||
deferred_rejection_keeps_dom_fifo_position(false, true).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn child_module_dom_tasks_keep_their_positions_around_domcontentloaded() -> Result<()> {
|
||||
deferred_rejection_keeps_dom_fifo_position(true, true).await
|
||||
}
|
||||
|
||||
async fn earlier_dom_task_replaces_document_before_queued_dcl(child: bool) -> Result<()> {
|
||||
let markup = r#"<!doctype html><body><script>
|
||||
window.events = [];
|
||||
document.addEventListener('DOMContentLoaded', () => events.push('old DCL'));
|
||||
addEventListener('unhandledrejection', event => {
|
||||
event.preventDefault();
|
||||
document.open();
|
||||
document.write("<!doctype html><script>document.addEventListener('DOMContentLoaded', () => events.push('new DCL'))<\/script><p>replacement</p>");
|
||||
document.close();
|
||||
}, {once: true});
|
||||
Promise.reject('replace');
|
||||
</script><p>old parser tail</p>"#;
|
||||
let result = observe_lifecycle_markup(child, markup).await?;
|
||||
assert_eq!(result["value"].as_str(), Some(r#"["new DCL"]"#));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn main_earlier_dom_task_retires_queued_domcontentloaded() -> Result<()> {
|
||||
earlier_dom_task_replaces_document_before_queued_dcl(false).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn child_earlier_dom_task_retires_queued_domcontentloaded() -> Result<()> {
|
||||
earlier_dom_task_replaces_document_before_queued_dcl(true).await
|
||||
}
|
||||
|
||||
async fn dom_callback_before_load_does_not_lose_load(child: bool) -> Result<()> {
|
||||
let markup = r#"<!doctype html><body><script>
|
||||
window.events = [];
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
events.push('DOMContentLoaded');
|
||||
Promise.reject('before load');
|
||||
});
|
||||
addEventListener('unhandledrejection', event => {
|
||||
event.preventDefault();
|
||||
events.push('rejection');
|
||||
const script = document.createElement('script');
|
||||
script.async = true;
|
||||
script.src = 'data:text/javascript,' + encodeURIComponent("events.push('async')");
|
||||
document.body.append(script);
|
||||
});
|
||||
addEventListener('load', () => events.push('load'));
|
||||
</script><p>parser tail</p>"#;
|
||||
let result = observe_lifecycle_markup(child, markup).await?;
|
||||
let events: Vec<String> = serde_json::from_str(result["value"].as_str().unwrap())?;
|
||||
for expected in ["DOMContentLoaded", "rejection", "async", "load"] {
|
||||
assert_eq!(
|
||||
events
|
||||
.iter()
|
||||
.filter(|event| event.as_str() == expected)
|
||||
.count(),
|
||||
1,
|
||||
"child={child}, {events:?}"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn main_earlier_dom_callback_does_not_lose_load_after_starting_async_script() -> Result<()> {
|
||||
dom_callback_before_load_does_not_lose_load(false).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn child_earlier_dom_callback_does_not_lose_load_after_starting_async_script() -> Result<()> {
|
||||
dom_callback_before_load_does_not_lose_load(true).await
|
||||
}
|
||||
|
||||
async fn parser_import_map_error_keeps_dom_fifo_position(
|
||||
child: bool,
|
||||
document_write: bool,
|
||||
) -> Result<()> {
|
||||
let import_map = r#"<script type=importmap src="data:application/json,%7B%7D"
|
||||
onerror="events.push('parser error')"></script>"#;
|
||||
let parser_markup = if document_write {
|
||||
format!(
|
||||
"<script>document.write({})</script>",
|
||||
serde_json::to_string(import_map)?.replace("</script>", "<\\/script>")
|
||||
)
|
||||
} else {
|
||||
import_map.to_owned()
|
||||
};
|
||||
let markup = format!(
|
||||
r#"<!doctype html><head><script>
|
||||
window.events = [];
|
||||
document.addEventListener('DOMContentLoaded', () => events.push('DOMContentLoaded'));
|
||||
</script>{parser_markup}<script>
|
||||
const later = document.createElement('script');
|
||||
later.type = 'importmap';
|
||||
later.src = 'data:application/json,%7B%7D';
|
||||
later.onerror = () => events.push('dynamic error');
|
||||
document.head.append(later);
|
||||
</script></head><body>parser tail"#
|
||||
);
|
||||
let result = observe_lifecycle_markup(child, &markup).await?;
|
||||
assert_eq!(
|
||||
result["value"].as_str(),
|
||||
Some(r#"["parser error","dynamic error","DOMContentLoaded"]"#),
|
||||
"child={child}, document_write={document_write}"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn main_parser_import_map_error_precedes_later_dom_tasks() -> Result<()> {
|
||||
parser_import_map_error_keeps_dom_fifo_position(false, false).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn child_parser_import_map_error_precedes_later_dom_tasks() -> Result<()> {
|
||||
parser_import_map_error_keeps_dom_fifo_position(true, false).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn main_written_import_map_error_precedes_later_dom_tasks() -> Result<()> {
|
||||
parser_import_map_error_keeps_dom_fifo_position(false, true).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn child_written_import_map_error_precedes_later_dom_tasks() -> Result<()> {
|
||||
parser_import_map_error_keeps_dom_fifo_position(true, true).await
|
||||
}
|
||||
@@ -2129,10 +2129,7 @@ impl DocumentRuntime {
|
||||
}
|
||||
}
|
||||
PreparedImportMapSource::ExternalUnsupported => {
|
||||
let _ = self.enqueue_script_event_lifecycle_work(
|
||||
ScriptEventKind::Error,
|
||||
&host_script_handle,
|
||||
);
|
||||
unsafe { &mut *host_ptr }.queue_script_preparation_error(scope, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,9 @@ impl DocumentRuntime {
|
||||
.unwrap_or(work.len());
|
||||
// This is an owner-queue boundary marker, not yet the HTML event-loop
|
||||
// queue point for DOMContentLoaded. When it reaches the front after all
|
||||
// defer-like and parser-owned pre-DCL work has drained, the driver first
|
||||
// runs ready timer tasks recorded during classic defer execution.
|
||||
// defer-like and parser-owned pre-DCL work has drained, the driver
|
||||
// admits the event to the shared DOM task source before yielding to
|
||||
// ready timer tasks recorded during classic defer execution.
|
||||
work.insert(
|
||||
domcontentloaded_index,
|
||||
PostParsePageOwnedWork::main_document_domcontentloaded(owner),
|
||||
|
||||
@@ -151,11 +151,10 @@ pub(crate) fn accept_parser_owned_import_map_handoff(
|
||||
start_line: u64,
|
||||
start_column: u64,
|
||||
import_map: PreparedImportMap,
|
||||
) {
|
||||
) -> anyhow::Result<()> {
|
||||
vm.document_runtime
|
||||
.note_parser_script_start_position(node_id, start_line, start_column);
|
||||
let host_script_handle = vm
|
||||
.document_runtime
|
||||
vm.document_runtime
|
||||
.bind_parser_owned_script_handle_for_node(import_map.node_id);
|
||||
let _ = vm
|
||||
.document_runtime
|
||||
@@ -169,7 +168,7 @@ pub(crate) fn accept_parser_owned_import_map_handoff(
|
||||
let Some(source) =
|
||||
vm.inline_script_element_source_for_execution(node_id, &source, request)
|
||||
else {
|
||||
return;
|
||||
return Ok(());
|
||||
};
|
||||
if let Err(error) =
|
||||
register_parser_owned_import_map_source(vm, &source, &import_map.base_url)
|
||||
@@ -181,12 +180,10 @@ pub(crate) fn accept_parser_owned_import_map_handoff(
|
||||
}
|
||||
}
|
||||
PreparedImportMapSource::ExternalUnsupported => {
|
||||
let _ = vm.document_runtime.enqueue_script_event_lifecycle_work(
|
||||
crate::host::ScriptEventKind::Error,
|
||||
&host_script_handle,
|
||||
);
|
||||
vm.queue_script_preparation_error(node_id)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_module_specifier(
|
||||
|
||||
@@ -63,8 +63,21 @@ impl JsContextHost {
|
||||
action: FrameDocumentLifecycleAction,
|
||||
realm_id: FrameRealmId,
|
||||
) -> bool {
|
||||
if !matches!(action, FrameDocumentLifecycleAction::Interactive(_)) {
|
||||
return self
|
||||
.page_task_capabilities
|
||||
.get()
|
||||
.expect("a child Document must retain its Page task capabilities")
|
||||
.dom_manipulation()
|
||||
.send_child_document_lifecycle(
|
||||
crate::page_task_queue::RendererPageChildDocumentLifecycleTarget::new(
|
||||
action, realm_id,
|
||||
),
|
||||
)
|
||||
.is_ok();
|
||||
}
|
||||
self.page_child_frame_task_sender()
|
||||
.send_document_lifecycle(
|
||||
.send_interactive_lifecycle(
|
||||
crate::page_task_queue::RendererPageChildDocumentLifecycleTarget::new(
|
||||
action, realm_id,
|
||||
),
|
||||
@@ -196,6 +209,35 @@ impl JsContextHost {
|
||||
self.route_child_document_lifecycle_action(action.into(), realm_request.realm_id())
|
||||
}
|
||||
|
||||
/// Parser stop changes readiness synchronously. Only an unmaterialized
|
||||
/// child realm needs an owner continuation before it can dispatch events.
|
||||
pub(crate) fn finish_child_document_parser_stop(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
action: FrameDocumentInteractiveLifecycleAction,
|
||||
) -> FrameDocumentLifecycleTaskEffect {
|
||||
if !self.child_document_lifecycle_action_is_current(action.into()) {
|
||||
return FrameDocumentLifecycleTaskEffect::NotApplied;
|
||||
}
|
||||
let dispatch_scope = super::super::OwnerDispatchScope::Child(action.child_handle());
|
||||
let execution_owner = crate::native_bridge::WindowExecutionContextOwner::Frame(
|
||||
action.owner().local_window_id,
|
||||
);
|
||||
let Some((_, context)) =
|
||||
self.window_execution_context(scope, execution_owner, dispatch_scope)
|
||||
else {
|
||||
return if self.queue_child_document_interactive_lifecycle_action(action) {
|
||||
FrameDocumentLifecycleTaskEffect::ConsumedWithoutEvent
|
||||
} else {
|
||||
FrameDocumentLifecycleTaskEffect::NotApplied
|
||||
};
|
||||
};
|
||||
let context = v8::Global::new(scope, context);
|
||||
let context = v8::Local::new(scope, &context);
|
||||
let child_scope = &mut v8::ContextScope::new(scope, context);
|
||||
self.run_child_document_lifecycle_action(child_scope, action.into())
|
||||
}
|
||||
|
||||
pub(crate) fn queue_child_document_domcontentloaded_if_ready(
|
||||
&mut self,
|
||||
child_handle: DomHandle,
|
||||
|
||||
@@ -937,12 +937,11 @@ impl JsContextHost {
|
||||
}
|
||||
}
|
||||
PreparedImportMapSource::ExternalUnsupported => {
|
||||
tracing::debug!(
|
||||
child_handle = ?child_handle,
|
||||
document_handle = ?document_handle,
|
||||
script_handle = ?node_id,
|
||||
"child parser external import map is unsupported"
|
||||
);
|
||||
if !self.queue_script_preparation_error(scope, node_id) {
|
||||
return ScriptDisposition::AdmissionFailed {
|
||||
script_handle: node_id,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
ScriptDisposition::Continue
|
||||
@@ -1143,7 +1142,7 @@ impl JsContextHost {
|
||||
document_handle,
|
||||
parser,
|
||||
) {
|
||||
self.queue_child_document_interactive_lifecycle_action(action);
|
||||
self.finish_child_document_parser_stop(scope, action);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -749,9 +749,10 @@ impl JsContextHost {
|
||||
};
|
||||
};
|
||||
let initial_classic_ready_work = install.initial_classic_ready_work;
|
||||
let parser_stop_queued = install
|
||||
.parser_stop_action
|
||||
.is_some_and(|action| self.queue_child_document_interactive_lifecycle_action(action));
|
||||
let parser_stop_queued = install.parser_stop_action.is_some_and(|action| {
|
||||
self.finish_child_document_parser_stop(scope, action)
|
||||
!= crate::frame_owner_model::FrameDocumentLifecycleTaskEffect::NotApplied
|
||||
});
|
||||
self.promote_pending_service_worker_child_client(handle);
|
||||
self.register_or_update_service_worker_child_client(handle);
|
||||
self.complete_pending_service_worker_child_client_navigation(handle);
|
||||
|
||||
@@ -781,6 +781,16 @@ impl JsContextHost {
|
||||
.promise_rejection()
|
||||
}
|
||||
|
||||
pub(crate) fn page_main_document_lifecycle_sender(
|
||||
&self,
|
||||
) -> crate::page_task_queue::RendererPageMainDocumentLifecycleSender {
|
||||
self.page_task_capabilities
|
||||
.get()
|
||||
.expect("a live Page Window must install its lifecycle task capability")
|
||||
.dom_manipulation()
|
||||
.main_document_lifecycle()
|
||||
}
|
||||
|
||||
pub(crate) fn page_file_reading_sender(
|
||||
&self,
|
||||
) -> crate::page_task_queue::RendererPageFileReadingSender {
|
||||
|
||||
@@ -17,6 +17,7 @@ mod history_traversal;
|
||||
mod image_load_event;
|
||||
mod indexed_db_task;
|
||||
mod internal_loading;
|
||||
mod main_document_lifecycle;
|
||||
mod main_document_post_parse;
|
||||
mod main_document_runtime;
|
||||
mod main_document_task_owner;
|
||||
@@ -218,12 +219,10 @@ pub(crate) use self::dedicated_worker_client_event::{
|
||||
RendererPageDedicatedWorkerClientEventProducer, RendererPageDedicatedWorkerClientEventSender,
|
||||
RendererPageDedicatedWorkerClientEventTask,
|
||||
};
|
||||
#[cfg(test)]
|
||||
pub(crate) use self::dom_manipulation::RendererPageDomManipulationOwner;
|
||||
pub(crate) use self::dom_manipulation::{
|
||||
PageDomManipulationTurnAction, PageDomManipulationTurnOutcome,
|
||||
RendererPageDomManipulationRoute, RendererPageDomManipulationSender,
|
||||
RendererPageDomManipulationTask,
|
||||
RendererPageDomManipulationOwner, RendererPageDomManipulationRoute,
|
||||
RendererPageDomManipulationSender, RendererPageDomManipulationTask,
|
||||
};
|
||||
pub(crate) use self::dynamic_import_owner_action::{
|
||||
PageDynamicImportOwnerActionDocumentEffect, PageDynamicImportOwnerActionTurnAction,
|
||||
@@ -278,6 +277,11 @@ pub(crate) use self::internal_loading::{
|
||||
RendererPageInternalLoadingOwner, RendererPageInternalLoadingSender,
|
||||
RendererPageInternalLoadingTask,
|
||||
};
|
||||
pub(crate) use self::main_document_lifecycle::{
|
||||
PageMainDocumentLifecycleTurnAction, PageMainDocumentLifecycleTurnOutcome,
|
||||
RendererPageMainDocumentLifecycleCompletion, RendererPageMainDocumentLifecycleOwner,
|
||||
RendererPageMainDocumentLifecycleSender, RendererPageMainDocumentLifecycleTask,
|
||||
};
|
||||
pub(crate) use self::main_document_post_parse::{
|
||||
MainDocumentCompletionRecheckEffect, MainDocumentPostParseCallbackExecution,
|
||||
MainDocumentPostParseCallbackSettlement, MainDocumentPostParseExecution,
|
||||
|
||||
@@ -390,7 +390,7 @@ pub(crate) type PageChildClassicScriptSourceLoadTurnOutcome =
|
||||
PageOwnerTurnOutcome<PageChildClassicScriptSourceLoadTurnAction>;
|
||||
|
||||
impl RendererPageChildFrameTask {
|
||||
fn new(owner: RendererPageChildFrameTaskOwner) -> Self {
|
||||
pub(super) fn new(owner: RendererPageChildFrameTaskOwner) -> Self {
|
||||
debug_assert!(!matches!(
|
||||
owner.target(),
|
||||
RendererPageChildFrameTaskTarget::ParserModuleRootStart(_)
|
||||
@@ -566,10 +566,14 @@ impl RendererPageChildFrameTaskSender {
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn send_document_lifecycle(
|
||||
pub(crate) fn send_interactive_lifecycle(
|
||||
&self,
|
||||
target: RendererPageChildDocumentLifecycleTarget,
|
||||
) -> Result<(), RendererPageChildFrameTaskRouteClosed> {
|
||||
debug_assert!(matches!(
|
||||
target.action(),
|
||||
crate::frame_owner_model::FrameDocumentLifecycleAction::Interactive(_)
|
||||
));
|
||||
self.send(RendererPageChildFrameTaskTarget::DocumentLifecycle(target))
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,8 @@ pub(crate) enum RendererPageDomManipulationOwner {
|
||||
ScriptPreparationError(RendererPageScriptPreparationErrorOwner),
|
||||
PromiseRejection(RendererPagePromiseRejectionOwner),
|
||||
ImageLoadEvent(RendererPageImageLoadEventOwner),
|
||||
MainDocumentLifecycle(super::RendererPageMainDocumentLifecycleOwner),
|
||||
ChildDocumentLifecycle(super::RendererPageChildFrameTaskOwner),
|
||||
PopupLoadEvent(RendererPagePopupLoadEventOwner),
|
||||
PopupClose(RendererPagePopupCloseOwner),
|
||||
ConnectedStyleEvent(RendererPageStylesheetTaskOwner),
|
||||
@@ -97,6 +99,8 @@ pub(crate) enum RendererPageDomManipulationTask {
|
||||
ScriptPreparationError(RendererPageScriptPreparationErrorTask),
|
||||
PromiseRejection(RendererPagePromiseRejectionTask),
|
||||
ImageLoadEvent(RendererPageImageLoadEventTask),
|
||||
MainDocumentLifecycle(super::RendererPageMainDocumentLifecycleTask),
|
||||
ChildDocumentLifecycle(super::RendererPageChildFrameTask),
|
||||
PopupLoadEvent(RendererPagePopupLoadEventTask),
|
||||
PopupClose(RendererPagePopupCloseTask),
|
||||
ConnectedStyleEvent(RendererPageConnectedStyleEventTask),
|
||||
@@ -130,6 +134,12 @@ impl RendererPageDomManipulationTask {
|
||||
Self::ImageLoadEvent(task) => {
|
||||
RendererPageDomManipulationOwner::ImageLoadEvent(task.owner())
|
||||
}
|
||||
Self::MainDocumentLifecycle(task) => {
|
||||
RendererPageDomManipulationOwner::MainDocumentLifecycle(task.owner)
|
||||
}
|
||||
Self::ChildDocumentLifecycle(task) => {
|
||||
RendererPageDomManipulationOwner::ChildDocumentLifecycle(task.owner())
|
||||
}
|
||||
Self::PopupLoadEvent(task) => {
|
||||
RendererPageDomManipulationOwner::PopupLoadEvent(task.owner())
|
||||
}
|
||||
@@ -169,6 +179,8 @@ pub(crate) enum PageDomManipulationTurnAction {
|
||||
ScriptPreparationError(super::PageScriptPreparationErrorTurnAction),
|
||||
PromiseRejection(super::PagePromiseRejectionTurnAction),
|
||||
ImageLoadEvent(super::PageImageLoadEventTurnAction),
|
||||
MainDocumentLifecycle(super::PageMainDocumentLifecycleTurnAction),
|
||||
ChildDocumentLifecycle(super::PageChildDocumentLifecycleTurnAction),
|
||||
PopupLoadEvent(super::PagePopupLoadEventTurnAction),
|
||||
PopupClose(super::PagePopupCloseTurnAction),
|
||||
ConnectedStyleEvent(PageConnectedStyleEventTurnAction),
|
||||
@@ -234,6 +246,30 @@ impl RendererPageDomManipulationSender {
|
||||
RendererPageImageLoadEventSender::new(self.route.clone(), self.root_document)
|
||||
}
|
||||
|
||||
pub(crate) fn main_document_lifecycle(&self) -> super::RendererPageMainDocumentLifecycleSender {
|
||||
super::RendererPageMainDocumentLifecycleSender::new(self.route.clone(), self.root_document)
|
||||
}
|
||||
|
||||
pub(crate) fn send_child_document_lifecycle(
|
||||
&self,
|
||||
target: super::RendererPageChildDocumentLifecycleTarget,
|
||||
) -> Result<(), super::child_frame_task::RendererPageChildFrameTaskRouteClosed> {
|
||||
debug_assert!(!matches!(
|
||||
target.action(),
|
||||
crate::frame_owner_model::FrameDocumentLifecycleAction::Interactive(_)
|
||||
));
|
||||
self.route
|
||||
.send(RendererPageDomManipulationTask::ChildDocumentLifecycle(
|
||||
super::RendererPageChildFrameTask::new(
|
||||
super::RendererPageChildFrameTaskOwner::new(
|
||||
self.root_document,
|
||||
super::RendererPageChildFrameTaskTarget::DocumentLifecycle(target),
|
||||
),
|
||||
),
|
||||
))
|
||||
.map_err(|_| super::child_frame_task::RendererPageChildFrameTaskRouteClosed)
|
||||
}
|
||||
|
||||
pub(crate) fn popup_load_event(&self) -> RendererPagePopupLoadEventSender {
|
||||
RendererPagePopupLoadEventSender::new(self.route.clone(), self.root_document)
|
||||
}
|
||||
@@ -292,6 +328,21 @@ pub(crate) struct RendererPageDomManipulationSource {
|
||||
}
|
||||
|
||||
impl RendererPageDomManipulationSource {
|
||||
pub(crate) fn has_main_document_lifecycle_task(
|
||||
&mut self,
|
||||
root_document: RendererDocumentToken,
|
||||
document: crate::frame_owner_model::FrameDocumentTaskOwner,
|
||||
) -> bool {
|
||||
self.source.has_matching_task(|ready| {
|
||||
matches!(
|
||||
ready.value(),
|
||||
RendererPageDomManipulationTask::MainDocumentLifecycle(task)
|
||||
if task.owner.root_document == root_document
|
||||
&& task.owner.body.owner() == document
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Remove cancelled task closures before they become scheduler-visible.
|
||||
///
|
||||
/// Element toggle coalescing cancels and reposts at the tail, matching the
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use crate::{
|
||||
frame_owner_model::FrameDocumentTaskOwner,
|
||||
runtime::{PageOwnerTurnOutcome, RendererDocumentToken},
|
||||
script_vm::{MainDocumentLifecycleBody, MainDocumentLifecycleTargetEffect},
|
||||
};
|
||||
|
||||
use super::dom_manipulation::{RendererPageDomManipulationRoute, RendererPageDomManipulationTask};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) struct RendererPageMainDocumentLifecycleOwner {
|
||||
pub(crate) root_document: RendererDocumentToken,
|
||||
pub(crate) body: MainDocumentLifecycleBody,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum RendererPageMainDocumentLifecycleCompletion {
|
||||
Executed,
|
||||
/// An earlier DOM task added a load delay after this task was admitted.
|
||||
/// Return the exact load boundary to its driver without completing it.
|
||||
LoadBlocked {
|
||||
owner: FrameDocumentTaskOwner,
|
||||
},
|
||||
}
|
||||
|
||||
/// An admitted HTML lifecycle task, after parser/defer and load prerequisites.
|
||||
/// Its receipt releases the driver's completion token only after execution.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct RendererPageMainDocumentLifecycleTask {
|
||||
pub(crate) owner: RendererPageMainDocumentLifecycleOwner,
|
||||
pub(crate) completion: Option<oneshot::Sender<RendererPageMainDocumentLifecycleCompletion>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct RendererPageMainDocumentLifecycleSender {
|
||||
route: RendererPageDomManipulationRoute,
|
||||
root_document: RendererDocumentToken,
|
||||
}
|
||||
|
||||
impl RendererPageMainDocumentLifecycleSender {
|
||||
pub(super) fn new(
|
||||
route: RendererPageDomManipulationRoute,
|
||||
root_document: RendererDocumentToken,
|
||||
) -> Self {
|
||||
Self {
|
||||
route,
|
||||
root_document,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn send(
|
||||
&self,
|
||||
body: MainDocumentLifecycleBody,
|
||||
completion: Option<oneshot::Sender<RendererPageMainDocumentLifecycleCompletion>>,
|
||||
) -> anyhow::Result<()> {
|
||||
anyhow::ensure!(
|
||||
!matches!(body, MainDocumentLifecycleBody::Interactive(_)),
|
||||
"interactive readiness belongs to parser completion"
|
||||
);
|
||||
self.route
|
||||
.send(RendererPageDomManipulationTask::MainDocumentLifecycle(
|
||||
RendererPageMainDocumentLifecycleTask {
|
||||
owner: RendererPageMainDocumentLifecycleOwner {
|
||||
root_document: self.root_document,
|
||||
body,
|
||||
},
|
||||
completion,
|
||||
},
|
||||
))
|
||||
.map_err(|_| anyhow::anyhow!("main document lifecycle DOM task route closed"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) struct PageMainDocumentLifecycleTurnAction {
|
||||
pub(crate) owner: RendererPageMainDocumentLifecycleOwner,
|
||||
pub(crate) target: MainDocumentLifecycleTargetEffect,
|
||||
}
|
||||
|
||||
pub(crate) type PageMainDocumentLifecycleTurnOutcome =
|
||||
PageOwnerTurnOutcome<PageMainDocumentLifecycleTurnAction>;
|
||||
@@ -627,6 +627,15 @@ pub(crate) enum RendererPageSchedulerTask {
|
||||
}
|
||||
|
||||
impl RendererPageOwnedTaskSources {
|
||||
pub(crate) fn has_queued_main_document_lifecycle_dom_task(
|
||||
&mut self,
|
||||
root_document: crate::runtime::RendererDocumentToken,
|
||||
document: crate::frame_owner_model::FrameDocumentTaskOwner,
|
||||
) -> bool {
|
||||
self.dom_manipulation
|
||||
.has_main_document_lifecycle_task(root_document, document)
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
runtime_wake: PageRuntimeWakeSignal,
|
||||
owner_wake: RendererOwnerWakeSender,
|
||||
@@ -1958,6 +1967,43 @@ impl RendererPageOwnedTaskSourcesTestHarness {
|
||||
})
|
||||
}
|
||||
|
||||
/// Compare the two child semantic source heads without changing either FIFO.
|
||||
pub(crate) fn next_child_semantic_task_target(
|
||||
&self,
|
||||
) -> Option<super::RendererPageChildFrameTaskTarget> {
|
||||
self.sources
|
||||
.borrow_mut()
|
||||
.ready_descriptors()
|
||||
.into_iter()
|
||||
.filter(|descriptor| {
|
||||
matches!(
|
||||
descriptor,
|
||||
RendererPageReadyDescriptor::ChildFrameTask { .. }
|
||||
| RendererPageReadyDescriptor::DomManipulation {
|
||||
owner: super::RendererPageDomManipulationOwner::ChildDocumentLifecycle(
|
||||
_
|
||||
),
|
||||
..
|
||||
}
|
||||
)
|
||||
})
|
||||
.min_by_key(|descriptor| {
|
||||
(
|
||||
descriptor.runnable_since(),
|
||||
descriptor.enqueue_order().unwrap_or(0),
|
||||
descriptor.source_kind(),
|
||||
)
|
||||
})
|
||||
.map(|descriptor| match descriptor {
|
||||
RendererPageReadyDescriptor::ChildFrameTask { owner, .. }
|
||||
| RendererPageReadyDescriptor::DomManipulation {
|
||||
owner: super::RendererPageDomManipulationOwner::ChildDocumentLifecycle(owner),
|
||||
..
|
||||
} => owner.target(),
|
||||
_ => unreachable!("child semantic selector must retain its exact target"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Dequeue one production scheduler task selected by a narrow semantic
|
||||
/// fixture. The selector sees the same descriptor as the owner scheduler;
|
||||
/// this helper adds no lane-specific consumer or alternate queue model.
|
||||
|
||||
@@ -141,12 +141,13 @@ pub(super) struct PendingDocumentLifecycleTurn {
|
||||
/// can immediately take another owner turn without ordinary Page work.
|
||||
///
|
||||
/// This is stable residence state, not a wake hint. It keeps a runnable
|
||||
/// parser-finish chain (notably `interactive` -> DOMContentLoaded) from
|
||||
/// being displaced by an older resource terminal that became ready while
|
||||
/// the parser task was still executing.
|
||||
/// parser preparation and lifecycle task admission contiguous. Once an
|
||||
/// event is admitted to the DOM source, the resident waits for its receipt
|
||||
/// and ordinary task arbitration owns event delivery.
|
||||
pub(super) owner_turn_is_runnable: bool,
|
||||
pub(super) driver: PostParseLifecycleDriver,
|
||||
pub(super) completed_task: Option<PostParsePageOwnedTask>,
|
||||
pub(super) awaiting_dom_task: Option<PendingDocumentLifecycleDomTask>,
|
||||
pub(super) completion_action: Option<PostParseLifecycleCompletionAction>,
|
||||
/// Whether this exact resident owns a sealed main-parser defer/module
|
||||
/// continuation.
|
||||
@@ -158,3 +159,10 @@ pub(super) struct PendingDocumentLifecycleTurn {
|
||||
pub(super) has_sealed_main_parser_script_queue: bool,
|
||||
pub(super) started: Instant,
|
||||
}
|
||||
|
||||
pub(super) struct PendingDocumentLifecycleDomTask {
|
||||
pub(super) task: PostParsePageOwnedTask,
|
||||
pub(super) completion: tokio::sync::oneshot::Receiver<
|
||||
crate::page_task_queue::RendererPageMainDocumentLifecycleCompletion,
|
||||
>,
|
||||
}
|
||||
|
||||
@@ -763,11 +763,30 @@ pub(super) fn select_page_scheduler_turn(
|
||||
!lifecycle_is_deferred && entry.document_lifecycle_owner_turn_is_runnable();
|
||||
let has_ready_main_parser_script_continuation =
|
||||
!lifecycle_is_deferred && entry.has_ready_main_parser_script_continuation();
|
||||
let document_lifecycle = DocumentLifecycleClassReadiness::from_resident_state(
|
||||
let mut document_lifecycle = DocumentLifecycleClassReadiness::from_resident_state(
|
||||
has_pending_document_lifecycle_turn,
|
||||
document_lifecycle_owner_turn_is_runnable,
|
||||
has_ready_main_parser_script_continuation,
|
||||
);
|
||||
let has_lifecycle_dom_task = entry
|
||||
.page_vm()
|
||||
.vm()
|
||||
.current_main_document_task_owner()
|
||||
.is_some_and(|owner| {
|
||||
task_sources.has_queued_main_document_lifecycle_dom_task(
|
||||
entry.page_vm().document_lifecycle.identity().document,
|
||||
owner,
|
||||
)
|
||||
});
|
||||
if has_lifecycle_dom_task
|
||||
&& !matches!(
|
||||
document_lifecycle,
|
||||
DocumentLifecycleClassReadiness::RunnableContinuation
|
||||
| DocumentLifecycleClassReadiness::ReadyMainParserScriptContinuation
|
||||
)
|
||||
{
|
||||
document_lifecycle = DocumentLifecycleClassReadiness::QueuedDomTask;
|
||||
}
|
||||
let gate_policy = lifecycle_gate
|
||||
.as_mut()
|
||||
.map(|gate| gate.turn_policy(entry, !snapshot.eligible.is_empty()))
|
||||
@@ -794,8 +813,26 @@ pub(super) fn select_page_scheduler_turn(
|
||||
),
|
||||
},
|
||||
Some(PageTurnClass::Ordinary) => {
|
||||
// Preserve parser-finish priority across task sources while the
|
||||
// lifecycle event occupies its real DOM FIFO position. Earlier DOM
|
||||
// tasks run first; no descriptor can jump to the lifecycle payload.
|
||||
let dom_head_is_eligible = snapshot.eligible.iter().any(|descriptor| {
|
||||
matches!(
|
||||
descriptor,
|
||||
crate::page_task_queue::RendererPageReadyDescriptor::DomManipulation { .. }
|
||||
)
|
||||
});
|
||||
// A child lifecycle head can be awaiting its exact realm. Allow
|
||||
// that prerequisite source to run before restoring DOM priority.
|
||||
let eligible = if has_lifecycle_dom_task && dom_head_is_eligible {
|
||||
snapshot.eligible.into_iter().filter(|descriptor| {
|
||||
matches!(descriptor, crate::page_task_queue::RendererPageReadyDescriptor::DomManipulation { .. })
|
||||
}).collect()
|
||||
} else {
|
||||
snapshot.eligible
|
||||
};
|
||||
let selected = scheduler
|
||||
.select_ready_descriptor(snapshot.eligible)
|
||||
.select_ready_descriptor(eligible)
|
||||
.expect("selected ordinary Page-turn class must retain an eligible descriptor");
|
||||
let task = task_sources.take_task(selected);
|
||||
RendererPageScheduledTurn::Ordinary(Box::new(task))
|
||||
@@ -815,6 +852,12 @@ pub(in crate::runtime) async fn advance_page_owner_one_turn_via_local_task(
|
||||
) -> (LivePageEntry, Result<()>) {
|
||||
run_entry_on_bound_owner_local_store_local_task(local_executor, entry, move |entry| {
|
||||
Box::pin(async move {
|
||||
let main_lifecycle_owner = match &task {
|
||||
RendererPageSchedulerTask::DomManipulation(
|
||||
crate::page_task_queue::RendererPageDomManipulationTask::MainDocumentLifecycle(task),
|
||||
) => Some(task.owner),
|
||||
_ => None,
|
||||
};
|
||||
let replacement_lifecycle_snapshot = entry
|
||||
.page_vm()
|
||||
.document_replacement_lifecycle_action_snapshot();
|
||||
@@ -847,6 +890,21 @@ pub(in crate::runtime) async fn advance_page_owner_one_turn_via_local_task(
|
||||
.await
|
||||
};
|
||||
|
||||
if application.is_ok()
|
||||
&& main_lifecycle_owner.is_some_and(|owner| {
|
||||
owner.root_document == entry.page_vm().document_lifecycle.identity().document
|
||||
&& entry.page_vm().vm().current_main_document_task_owner() == Some(owner.body.owner())
|
||||
})
|
||||
{
|
||||
let (_, pending) = entry.page_vm_and_document_lifecycle_turn_mut();
|
||||
if let Some(pending) = pending {
|
||||
// Resume the exact driver to consume its receipt. An
|
||||
// executed event publishes the reached stage before other
|
||||
// sources run; a blocked load returns to its prerequisites.
|
||||
pending.owner_turn_is_runnable = true;
|
||||
}
|
||||
}
|
||||
|
||||
match (application, reconciliation) {
|
||||
(Ok(()), Ok(_)) => Ok(()),
|
||||
(Err(action_error), Ok(_)) => Err(action_error),
|
||||
|
||||
@@ -81,6 +81,8 @@ pub(super) enum PageTurnClass {
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(super) enum DocumentLifecycleClassReadiness {
|
||||
Absent,
|
||||
/// The DOM source owns the admitted event. Only its FIFO head may run.
|
||||
QueuedDomTask,
|
||||
Available,
|
||||
RunnableContinuation,
|
||||
ReadyMainParserScriptContinuation,
|
||||
@@ -110,7 +112,7 @@ impl DocumentLifecycleClassReadiness {
|
||||
}
|
||||
|
||||
const fn is_available(self) -> bool {
|
||||
!matches!(self, Self::Absent)
|
||||
!matches!(self, Self::Absent | Self::QueuedDomTask)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -249,6 +249,7 @@ impl PageVm {
|
||||
owner_turn_is_runnable: true,
|
||||
driver,
|
||||
completed_task: None,
|
||||
awaiting_dom_task: None,
|
||||
completion_action: None,
|
||||
has_sealed_main_parser_script_queue,
|
||||
started,
|
||||
@@ -372,6 +373,34 @@ impl PageVm {
|
||||
return Ok(settled);
|
||||
}
|
||||
|
||||
let pending = pending_document_lifecycle_turn
|
||||
.as_mut()
|
||||
.expect("post-parse lifecycle state should remain installed");
|
||||
if let Some(waiting) = pending.awaiting_dom_task.as_mut() {
|
||||
match waiting.completion.try_recv() {
|
||||
Ok(crate::page_task_queue::RendererPageMainDocumentLifecycleCompletion::Executed) => {
|
||||
pending.completed_task = Some(pending.awaiting_dom_task.take().unwrap().task);
|
||||
}
|
||||
Ok(crate::page_task_queue::RendererPageMainDocumentLifecycleCompletion::LoadBlocked { owner }) => {
|
||||
// The attempt did not execute the load boundary. Its token
|
||||
// must never tell the driver that this stage was reached.
|
||||
pending.awaiting_dom_task = None;
|
||||
self.page_task_queue.enqueue_front_post_parse_work_preserving_order(vec![
|
||||
PostParsePageOwnedWork::main_document_window_load(owner),
|
||||
]);
|
||||
}
|
||||
Err(tokio::sync::oneshot::error::TryRecvError::Empty) => {
|
||||
return Ok(DocumentLifecycleTurnOutcome::blocked(
|
||||
DocumentLifecycleTurnAction::None,
|
||||
document,
|
||||
));
|
||||
}
|
||||
Err(tokio::sync::oneshot::error::TryRecvError::Closed) => {
|
||||
anyhow::bail!("current lifecycle DOM task disappeared before completion");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Feed the previous exact task result into the driver before other
|
||||
// page work is admitted. `AwaitProgress` parks immediately; a producer
|
||||
// wake returns to owner arbitration instead of waiting in this method.
|
||||
@@ -412,17 +441,6 @@ impl PageVm {
|
||||
)
|
||||
.await
|
||||
}
|
||||
PostParseLifecycleAdvance::TimerQueuedByClassicDeferBeforeDomContentLoaded => {
|
||||
self.run_classic_defer_timer_before_domcontentloaded(&request_client)
|
||||
.await?;
|
||||
self.outcome_after_exact_post_parse_action(
|
||||
pending_document_lifecycle_turn,
|
||||
document,
|
||||
stage,
|
||||
DocumentLifecycleTurnAction::Progressed,
|
||||
false,
|
||||
)
|
||||
}
|
||||
PostParseLifecycleAdvance::NeedsContinuation => self
|
||||
.outcome_after_exact_post_parse_action(
|
||||
pending_document_lifecycle_turn,
|
||||
@@ -570,6 +588,7 @@ impl PageVm {
|
||||
owner_turn_is_runnable: true,
|
||||
driver,
|
||||
completed_task: None,
|
||||
awaiting_dom_task: None,
|
||||
completion_action: None,
|
||||
has_sealed_main_parser_script_queue: false,
|
||||
started: Instant::now(),
|
||||
@@ -598,6 +617,7 @@ impl PageVm {
|
||||
owner_turn_is_runnable: _,
|
||||
driver,
|
||||
completed_task,
|
||||
awaiting_dom_task: _,
|
||||
completion_action,
|
||||
has_sealed_main_parser_script_queue: _,
|
||||
started: _,
|
||||
@@ -620,6 +640,7 @@ impl PageVm {
|
||||
owner_turn_is_runnable: true,
|
||||
driver,
|
||||
completed_task,
|
||||
awaiting_dom_task: None,
|
||||
completion_action,
|
||||
has_sealed_main_parser_script_queue,
|
||||
started: Instant::now(),
|
||||
@@ -647,6 +668,7 @@ impl PageVm {
|
||||
owner_turn_is_runnable: true,
|
||||
driver,
|
||||
completed_task: None,
|
||||
awaiting_dom_task: None,
|
||||
completion_action: None,
|
||||
has_sealed_main_parser_script_queue: self
|
||||
.vm()
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
//! The exact lifecycle resident remains the sole ordinary DCL/load authority.
|
||||
//! This component only reconciles an already-claimed body: typed checkpoint
|
||||
//! continuations, milestone journal visibility, body settlement, and lifecycle
|
||||
//! priming stay in one auditable order. Parser completion may call this same
|
||||
//! component after claiming the exact DCL direct successor, but the parser
|
||||
//! continuation's own task-end checkpoint has already completed first. This
|
||||
//! preserves the observable task boundary without opening a scheduler round in
|
||||
//! which an ordinary Page task could overtake DCL.
|
||||
//! priming stay in one auditable order. DCL and load reach this coordinator
|
||||
//! through the shared DOM task source after parser completion has admitted
|
||||
//! them; interactive readiness remains part of parser completion.
|
||||
|
||||
use anyhow::Result;
|
||||
use std::time::Instant;
|
||||
@@ -42,17 +40,6 @@ pub(super) async fn execute_main_document_lifecycle_on_owner_local_task(
|
||||
execute_main_document_lifecycle_body_on_owner_local_task(page_vm, body).await
|
||||
}
|
||||
|
||||
pub(super) async fn execute_parser_exact_domcontentloaded_on_owner_local_task(
|
||||
page_vm: &mut PageVm,
|
||||
owner: crate::frame_owner_model::FrameDocumentTaskOwner,
|
||||
) -> Result<MainDocumentLifecycleTaskRun> {
|
||||
execute_main_document_lifecycle_body_on_owner_local_task(
|
||||
page_vm,
|
||||
MainDocumentLifecycleBody::DomContentLoaded { owner },
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn execute_main_document_lifecycle_body_on_owner_local_task(
|
||||
page_vm: &mut PageVm,
|
||||
body: MainDocumentLifecycleBody,
|
||||
|
||||
@@ -105,6 +105,7 @@ mod page_image_load_event;
|
||||
mod page_indexed_db_task;
|
||||
mod page_internal_loading;
|
||||
mod page_internal_loading_task_completion;
|
||||
mod page_main_document_lifecycle;
|
||||
mod page_main_document_post_parse;
|
||||
mod page_main_document_runtime;
|
||||
mod page_main_native_module_task;
|
||||
@@ -2119,9 +2120,23 @@ impl PageVm {
|
||||
) -> bool {
|
||||
use crate::{
|
||||
frame_owner_model::ChildFrameSemanticTurnKind,
|
||||
page_task_queue::RendererPageChildFrameTaskTarget,
|
||||
page_task_queue::{
|
||||
RendererPageChildFrameTaskTarget, RendererPageDomManipulationOwner,
|
||||
RendererPageReadyDescriptor,
|
||||
},
|
||||
};
|
||||
|
||||
if expected == ChildFrameSemanticTurnKind::DocumentLifecycle {
|
||||
return self.page_task_executor_sources_for_test().has_scheduler_task_for_executor_test(|descriptor| {
|
||||
matches!(descriptor,
|
||||
RendererPageReadyDescriptor::DomManipulation { owner: RendererPageDomManipulationOwner::ChildDocumentLifecycle(_), .. }
|
||||
) || matches!(descriptor,
|
||||
RendererPageReadyDescriptor::ChildFrameTask { owner, .. }
|
||||
if matches!(owner.target(), RendererPageChildFrameTaskTarget::DocumentLifecycle(_))
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
let Some(target) = self
|
||||
.page_task_executor_sources_for_test()
|
||||
.next_child_frame_task_target()
|
||||
@@ -2961,7 +2976,11 @@ impl PageVm {
|
||||
{
|
||||
return Some(ChildFrameSemanticTurnKind::NavigationCommit);
|
||||
}
|
||||
if self
|
||||
if matches!(
|
||||
self.page_task_executor_sources_for_test()
|
||||
.next_child_semantic_task_target(),
|
||||
Some(crate::page_task_queue::RendererPageChildFrameTaskTarget::DocumentLifecycle(_))
|
||||
) && self
|
||||
.run_exact_selected_page_task_for_test(
|
||||
PageSelectedTaskTestSelector::ChildDocumentLifecycle,
|
||||
&loader,
|
||||
@@ -3170,11 +3189,61 @@ impl PageVm {
|
||||
match advance {
|
||||
PostParseLifecycleAdvance::PageOwnedTask(mut task) => {
|
||||
let request_client = self.request_client.clone();
|
||||
self.execute_ordinary_post_parse_page_owned_task_on_named_owner_lane(
|
||||
&request_client,
|
||||
task.take_work_for_execution(),
|
||||
)
|
||||
.await?;
|
||||
let work = task.take_work_for_execution();
|
||||
if let PostParsePageOwnedWork::Lifecycle(lifecycle) = &work
|
||||
&& matches!(
|
||||
**lifecycle,
|
||||
PostParseLifecycleWork::DispatchDomContentLoaded { .. }
|
||||
| PostParseLifecycleWork::DispatchWindowLoad { .. }
|
||||
)
|
||||
{
|
||||
let body = crate::script_vm::MainDocumentLifecycleBody::from_post_parse_work(
|
||||
lifecycle,
|
||||
)
|
||||
.unwrap();
|
||||
let (sender, mut receiver) = tokio::sync::oneshot::channel();
|
||||
self.vm()
|
||||
.queue_main_document_lifecycle_dom_task(body, Some(sender))?;
|
||||
if matches!(
|
||||
body,
|
||||
crate::script_vm::MainDocumentLifecycleBody::DomContentLoaded { .. }
|
||||
) {
|
||||
self.run_ready_classic_defer_timers_before_domcontentloaded(
|
||||
&request_client,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
loop {
|
||||
match receiver.try_recv() {
|
||||
Ok(crate::page_task_queue::RendererPageMainDocumentLifecycleCompletion::Executed) => break,
|
||||
Ok(crate::page_task_queue::RendererPageMainDocumentLifecycleCompletion::LoadBlocked { owner }) => {
|
||||
self.page_task_queue.enqueue_front_post_parse_work_preserving_order(vec![PostParsePageOwnedWork::main_document_window_load(owner)]);
|
||||
return Ok(PostParseLifecycleLoopAdvance::Continue(Box::new(None)));
|
||||
}
|
||||
Err(tokio::sync::oneshot::error::TryRecvError::Closed) => {
|
||||
anyhow::bail!(
|
||||
"lifecycle DOM task disappeared before its fixture completion"
|
||||
);
|
||||
}
|
||||
Err(tokio::sync::oneshot::error::TryRecvError::Empty) => {
|
||||
anyhow::ensure!(
|
||||
self.run_exact_selected_page_task_for_test(
|
||||
PageSelectedTaskTestSelector::AnyDomManipulation,
|
||||
&request_client,
|
||||
)
|
||||
.await?,
|
||||
"queued lifecycle task must remain runnable"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.execute_ordinary_post_parse_page_owned_task_on_named_owner_lane(
|
||||
&request_client,
|
||||
work,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
if self.vm().has_pending_location_navigation() {
|
||||
return Ok(PostParseLifecycleLoopAdvance::Complete(
|
||||
PostParseLifecycleCompletionAction::TriggeredNavigation,
|
||||
@@ -3184,12 +3253,6 @@ impl PageVm {
|
||||
*task,
|
||||
))))
|
||||
}
|
||||
PostParseLifecycleAdvance::TimerQueuedByClassicDeferBeforeDomContentLoaded => {
|
||||
let request_client = self.request_client.clone();
|
||||
self.run_classic_defer_timer_before_domcontentloaded(&request_client)
|
||||
.await?;
|
||||
Ok(PostParseLifecycleLoopAdvance::Continue(Box::new(None)))
|
||||
}
|
||||
PostParseLifecycleAdvance::NeedsContinuation => {
|
||||
Ok(PostParseLifecycleLoopAdvance::Continue(Box::new(None)))
|
||||
}
|
||||
|
||||
+11
-3
@@ -22,14 +22,22 @@ impl PageVm {
|
||||
matches!(
|
||||
descriptor,
|
||||
RendererPageReadyDescriptor::ChildFrameTask { owner, .. }
|
||||
if matches!(
|
||||
| RendererPageReadyDescriptor::DomManipulation {
|
||||
owner: crate::page_task_queue::RendererPageDomManipulationOwner::ChildDocumentLifecycle(owner), ..
|
||||
} if matches!(
|
||||
owner.target(),
|
||||
RendererPageChildFrameTaskTarget::DocumentLifecycle(_)
|
||||
)
|
||||
)
|
||||
})?;
|
||||
let RendererPageSchedulerTask::ChildFrameTask(task) = task else {
|
||||
unreachable!("DocumentLifecycle descriptor must dequeue a child-frame task")
|
||||
let task = match task {
|
||||
RendererPageSchedulerTask::ChildFrameTask(task)
|
||||
| RendererPageSchedulerTask::DomManipulation(
|
||||
crate::page_task_queue::RendererPageDomManipulationTask::ChildDocumentLifecycle(
|
||||
task,
|
||||
),
|
||||
) => task,
|
||||
_ => unreachable!("DocumentLifecycle descriptor must dequeue its admitted task"),
|
||||
};
|
||||
Some(self.apply_selected_page_child_document_lifecycle_turn(task))
|
||||
}
|
||||
|
||||
+4
-11
@@ -1,15 +1,8 @@
|
||||
//! Task-end boundary for an exact child `Document` lifecycle action.
|
||||
//! Task-end boundary for an exact child Document lifecycle action.
|
||||
//!
|
||||
//! Interactive, `DOMContentLoaded`, and complete transitions are ordinary
|
||||
//! selected child-frame tasks. Their bodies synchronously dispatch the
|
||||
//! corresponding document event when a wrapper still exists, but deliberately
|
||||
//! leave Promise reactions pending. This module maps that execution-produced
|
||||
//! fact to the sole Page task-completion authority.
|
||||
//!
|
||||
//! This does not govern the parser-owned direct-successor boundary where the
|
||||
//! final deferred script completes parsing and synchronously dispatches DCL in
|
||||
//! the same task. That separately typed parser completion path must remain
|
||||
//! contiguous and must not be turned into another scheduler task here.
|
||||
//! DCL and complete use the shared DOM source. Interactive readiness belongs
|
||||
//! to parser stop; an unmaterialized realm may need a child owner continuation.
|
||||
//! The selected dispatcher completes the event's checkpoint and follow-up.
|
||||
|
||||
use crate::page_task_queue::{
|
||||
PageChildDocumentLifecycleTargetEffect, PageChildDocumentLifecycleTurnAction,
|
||||
|
||||
@@ -5,11 +5,20 @@ use crate::page_task_queue::{
|
||||
use super::PageVm;
|
||||
|
||||
impl PageVm {
|
||||
pub(in crate::runtime) fn apply_selected_page_dom_manipulation_turn(
|
||||
pub(in crate::runtime) async fn apply_selected_page_dom_manipulation_turn(
|
||||
&mut self,
|
||||
task: RendererPageDomManipulationTask,
|
||||
) -> anyhow::Result<PageDomManipulationTurnOutcome> {
|
||||
match task {
|
||||
RendererPageDomManipulationTask::ChildDocumentLifecycle(task) => Ok(self
|
||||
.apply_selected_page_child_document_lifecycle_turn(task)
|
||||
.map_action(PageDomManipulationTurnAction::ChildDocumentLifecycle)),
|
||||
RendererPageDomManipulationTask::MainDocumentLifecycle(task) => self
|
||||
.apply_selected_page_main_document_lifecycle_turn(task)
|
||||
.await
|
||||
.map(|outcome| {
|
||||
outcome.map_action(PageDomManipulationTurnAction::MainDocumentLifecycle)
|
||||
}),
|
||||
RendererPageDomManipulationTask::BroadcastChannel(task) => self
|
||||
.apply_selected_page_broadcast_channel_delivery_turn(task)
|
||||
.map(|outcome| outcome.map_action(PageDomManipulationTurnAction::BroadcastChannel)),
|
||||
|
||||
@@ -18,6 +18,12 @@ impl PageVm {
|
||||
loader: &crate::network::ResourceRequestClient,
|
||||
) -> Result<()> {
|
||||
let completion = match action {
|
||||
PageDomManipulationTurnAction::ChildDocumentLifecycle(action) => {
|
||||
action.into_page_task_completion()
|
||||
}
|
||||
PageDomManipulationTurnAction::MainDocumentLifecycle(action) => {
|
||||
action.into_page_task_completion()
|
||||
}
|
||||
PageDomManipulationTurnAction::BroadcastChannel(action) => {
|
||||
action.into_page_task_completion()
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ pub(crate) enum PageDomManipulationTestFamily {
|
||||
FileEntryFileCallback,
|
||||
ScriptPreparationError,
|
||||
PromiseRejection,
|
||||
MainDocumentLifecycle,
|
||||
ImageLoadEvent,
|
||||
PopupLoadEvent,
|
||||
ConnectedStyleEvent,
|
||||
@@ -34,6 +35,9 @@ impl PageDomManipulationTestFamily {
|
||||
matches!(
|
||||
(self, owner),
|
||||
(
|
||||
Self::MainDocumentLifecycle,
|
||||
RendererPageDomManipulationOwner::MainDocumentLifecycle(_)
|
||||
) | (
|
||||
Self::BroadcastChannel,
|
||||
RendererPageDomManipulationOwner::BroadcastChannel(_)
|
||||
) | (
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
use crate::{
|
||||
page_task_queue::{
|
||||
PageMainDocumentLifecycleTurnAction, PageMainDocumentLifecycleTurnOutcome,
|
||||
RendererPageMainDocumentLifecycleCompletion, RendererPageMainDocumentLifecycleTask,
|
||||
},
|
||||
runtime::PendingDocumentLifecycleTurn,
|
||||
script_vm::{
|
||||
MainDocumentLifecycleBody, MainDocumentLifecycleTargetEffect,
|
||||
MainDocumentLifecycleTargetRejection, PostParsePageOwnedTask,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{IntoPageTaskCompletion, PageTaskCompletion, PageVm};
|
||||
|
||||
/// Event bodies finish their own checkpoints through the lifecycle coordinator.
|
||||
/// A blocked or stale task does not enter page code.
|
||||
impl IntoPageTaskCompletion for PageMainDocumentLifecycleTurnAction {
|
||||
fn into_page_task_completion(self) -> PageTaskCompletion {
|
||||
PageTaskCompletion::NoCompletion
|
||||
}
|
||||
}
|
||||
|
||||
impl PageVm {
|
||||
pub(super) async fn run_ready_classic_defer_timers_before_domcontentloaded(
|
||||
&mut self,
|
||||
loader: &crate::network::ResourceRequestClient,
|
||||
) -> anyhow::Result<()> {
|
||||
// Preserve the existing cross-source timer preference, after DCL has
|
||||
// acquired its FIFO position in the DOM task source.
|
||||
while self
|
||||
.vm()
|
||||
.document_runtime
|
||||
.has_ready_timeout_queued_by_classic_defer_script()
|
||||
{
|
||||
self.run_classic_defer_timer_before_domcontentloaded(loader)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn queue_post_parse_lifecycle_dom_task(
|
||||
&self,
|
||||
pending: &mut PendingDocumentLifecycleTurn,
|
||||
body: MainDocumentLifecycleBody,
|
||||
task: PostParsePageOwnedTask,
|
||||
) -> anyhow::Result<()> {
|
||||
anyhow::ensure!(
|
||||
pending.awaiting_dom_task.is_none() && pending.completed_task.is_none(),
|
||||
"a lifecycle resident may await only one exact task"
|
||||
);
|
||||
let (sender, receiver) = tokio::sync::oneshot::channel();
|
||||
self.vm()
|
||||
.queue_main_document_lifecycle_dom_task(body, Some(sender))?;
|
||||
pending.awaiting_dom_task = Some(
|
||||
super::super::document_lifecycle_turn::PendingDocumentLifecycleDomTask {
|
||||
task,
|
||||
completion: receiver,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) async fn apply_selected_page_main_document_lifecycle_turn(
|
||||
&mut self,
|
||||
task: RendererPageMainDocumentLifecycleTask,
|
||||
) -> anyhow::Result<PageMainDocumentLifecycleTurnOutcome> {
|
||||
let RendererPageMainDocumentLifecycleTask { owner, completion } = task;
|
||||
if owner.root_document == self.document_lifecycle.identity().document
|
||||
&& let MainDocumentLifecycleBody::WindowLoad {
|
||||
owner: document_owner,
|
||||
} = owner.body
|
||||
&& self
|
||||
.vm_mut()
|
||||
.main_document_window_load_task_is_ready(document_owner)
|
||||
== Some(false)
|
||||
{
|
||||
// The DOM queue may have run a producer since admission. Do not
|
||||
// open the milestone journal or consume its boundary token while
|
||||
// the exact Document has a new load delay.
|
||||
let completion =
|
||||
completion.expect("queued Window load must retain its lifecycle driver");
|
||||
let _ = completion.send(RendererPageMainDocumentLifecycleCompletion::LoadBlocked {
|
||||
owner: document_owner,
|
||||
});
|
||||
return Ok(PageMainDocumentLifecycleTurnOutcome::new(
|
||||
PageMainDocumentLifecycleTurnAction {
|
||||
owner,
|
||||
target: MainDocumentLifecycleTargetEffect::NotApplied {
|
||||
reason: MainDocumentLifecycleTargetRejection::TransitionRejected,
|
||||
current_owner: self.vm().current_main_document_task_owner(),
|
||||
},
|
||||
},
|
||||
));
|
||||
}
|
||||
let target = if owner.root_document == self.document_lifecycle.identity().document {
|
||||
let run = super::main_document_lifecycle_completion::execute_main_document_lifecycle_on_owner_local_task(
|
||||
self, owner.body,
|
||||
).await?;
|
||||
let target = run.completion.target();
|
||||
if let crate::script_vm::MainDocumentLifecycleFollowup::ScheduleInternalLoading {
|
||||
task,
|
||||
ready_at,
|
||||
} = run.completion.into_followup()
|
||||
{
|
||||
self.vm()
|
||||
.schedule_page_internal_loading_task(task, ready_at)?;
|
||||
}
|
||||
// The common selected Page owner reconciles document.open() after
|
||||
// the body and its checkpoints, just as for other DOM tasks.
|
||||
target
|
||||
} else {
|
||||
MainDocumentLifecycleTargetEffect::NotApplied {
|
||||
reason: MainDocumentLifecycleTargetRejection::TransitionRejected,
|
||||
current_owner: self.vm().current_main_document_task_owner(),
|
||||
}
|
||||
};
|
||||
if let Some(completion) = completion {
|
||||
// Replacement may have retired the receiver during this task.
|
||||
let _ = completion.send(RendererPageMainDocumentLifecycleCompletion::Executed);
|
||||
}
|
||||
Ok(PageMainDocumentLifecycleTurnOutcome::new(
|
||||
PageMainDocumentLifecycleTurnAction { owner, target },
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,7 @@ pub(crate) enum PageSelectedTaskTestSelector {
|
||||
ChildNavigationCommit,
|
||||
ChildModulepreloadEventAction,
|
||||
DomManipulation(PageDomManipulationTestFamily),
|
||||
AnyDomManipulation,
|
||||
DedicatedWorkerClientEvent,
|
||||
DynamicImportOwnerAction,
|
||||
FileReading,
|
||||
@@ -89,6 +90,9 @@ impl PageSelectedTaskTestSelector {
|
||||
Self::ChildDocumentLifecycle => matches!(
|
||||
descriptor,
|
||||
RendererPageReadyDescriptor::ChildFrameTask { owner, .. }
|
||||
| RendererPageReadyDescriptor::DomManipulation {
|
||||
owner: crate::page_task_queue::RendererPageDomManipulationOwner::ChildDocumentLifecycle(owner), ..
|
||||
}
|
||||
if matches!(
|
||||
owner.target(),
|
||||
crate::page_task_queue::RendererPageChildFrameTaskTarget::DocumentLifecycle(_)
|
||||
@@ -153,6 +157,10 @@ impl PageSelectedTaskTestSelector {
|
||||
descriptor,
|
||||
RendererPageReadyDescriptor::ChildModulepreloadEventAction { .. }
|
||||
),
|
||||
Self::AnyDomManipulation => matches!(
|
||||
descriptor,
|
||||
RendererPageReadyDescriptor::DomManipulation { .. }
|
||||
),
|
||||
Self::DomManipulation(family) => matches!(
|
||||
descriptor,
|
||||
RendererPageReadyDescriptor::DomManipulation { owner, .. }
|
||||
@@ -308,7 +316,13 @@ impl PageSelectedTaskTestSelector {
|
||||
|
||||
fn matches_task(self, task: &RendererPageSchedulerTask) -> bool {
|
||||
match (self, task) {
|
||||
(Self::ChildDocumentLifecycle, RendererPageSchedulerTask::ChildFrameTask(task)) => {
|
||||
(Self::ChildDocumentLifecycle, RendererPageSchedulerTask::ChildFrameTask(task))
|
||||
| (
|
||||
Self::ChildDocumentLifecycle,
|
||||
RendererPageSchedulerTask::DomManipulation(
|
||||
RendererPageDomManipulationTask::ChildDocumentLifecycle(task),
|
||||
),
|
||||
) => {
|
||||
matches!(
|
||||
task.owner().target(),
|
||||
crate::page_task_queue::RendererPageChildFrameTaskTarget::DocumentLifecycle(_)
|
||||
@@ -369,6 +383,7 @@ impl PageSelectedTaskTestSelector {
|
||||
Self::ChildModulepreloadEventAction,
|
||||
RendererPageSchedulerTask::ChildModulepreloadEventAction(_),
|
||||
) => true,
|
||||
(Self::AnyDomManipulation, RendererPageSchedulerTask::DomManipulation(_)) => true,
|
||||
(Self::DomManipulation(family), RendererPageSchedulerTask::DomManipulation(task)) => {
|
||||
family.matches_owner(task.owner())
|
||||
}
|
||||
|
||||
@@ -74,6 +74,27 @@ impl PageVm {
|
||||
descriptor: RendererPageReadyDescriptor,
|
||||
) -> bool {
|
||||
match descriptor {
|
||||
RendererPageReadyDescriptor::DomManipulation {
|
||||
owner:
|
||||
crate::page_task_queue::RendererPageDomManipulationOwner::ChildDocumentLifecycle(
|
||||
owner,
|
||||
),
|
||||
..
|
||||
} => {
|
||||
if owner.root_document() != self.document_lifecycle.identity().document {
|
||||
return true;
|
||||
}
|
||||
let crate::page_task_queue::RendererPageChildFrameTaskTarget::DocumentLifecycle(
|
||||
target,
|
||||
) = owner.target()
|
||||
else {
|
||||
unreachable!("child lifecycle DOM carrier must retain its exact target");
|
||||
};
|
||||
// A current task retains its DOM FIFO position while its
|
||||
// separately queued realm prerequisite runs. It is not stale
|
||||
// merely because that realm has not materialized yet.
|
||||
!self.vm().child_document_lifecycle_waits_for_realm(target)
|
||||
}
|
||||
RendererPageReadyDescriptor::ActionWindow { .. }
|
||||
| RendererPageReadyDescriptor::DomManipulation { .. }
|
||||
| RendererPageReadyDescriptor::UserInteraction { .. }
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
//! owns the bounded handoff from that work to parser completion: it consumes a
|
||||
//! one-shot drained-queue permit, asks the lifecycle authority to claim the
|
||||
//! exact DOMContentLoaded successor, closes the selected parser task's
|
||||
//! checkpoint boundary, and only then hands that already-claimed successor to
|
||||
//! the lifecycle coordinator. The coordinator applies it to the surviving
|
||||
//! checkpoint boundary, and admits the claimed successor to the shared DOM
|
||||
//! task source. Its selected-task coordinator applies it to the surviving
|
||||
//! exact Document or stale-rejects it after replacement.
|
||||
//! It is not a scheduler lane and stores no durable lifecycle state.
|
||||
|
||||
@@ -16,9 +16,7 @@ use crate::frame_owner_model::FrameDocumentTaskOwner;
|
||||
use crate::network::ResourceRequestClient;
|
||||
use crate::page_task_queue::{PostParseLifecycleWork, PostParsePageOwnedWork};
|
||||
use crate::runtime::{PendingDocumentLifecycleTurn, RendererDocumentLifecycleIdentity};
|
||||
use crate::script_vm::{
|
||||
MainDocumentLifecycleBodyKind, ParserFinishDomContentLoadedTask, PostParsePageOwnedTask,
|
||||
};
|
||||
use crate::script_vm::{MainDocumentLifecycleBody, PostParsePageOwnedTask};
|
||||
|
||||
use super::super::document_lifecycle_turn::{
|
||||
DocumentLifecycleTurnAction, DocumentLifecycleTurnOutcome,
|
||||
@@ -164,31 +162,20 @@ impl ParserCompletion {
|
||||
};
|
||||
}
|
||||
};
|
||||
// The parser continuation and DOMContentLoaded are distinct HTML task
|
||||
// boundaries even though this Chromium-compatible direct successor
|
||||
// deliberately does not reopen ordinary scheduler arbitration. Drain
|
||||
// terminal reactions first; the lifecycle coordinator then owns DCL's
|
||||
// separate task-end checkpoint.
|
||||
// Finish parser reactions before admitting the DCL global task. An
|
||||
// earlier DOM task keeps its FIFO position; later timer callbacks may
|
||||
// enqueue DOM work only behind this already admitted lifecycle task.
|
||||
Self::finish_task(page_vm, task_effect)?;
|
||||
page_vm.vm().queue_main_document_lifecycle_dom_task(
|
||||
MainDocumentLifecycleBody::DomContentLoaded {
|
||||
owner: successor_owner,
|
||||
},
|
||||
None,
|
||||
)?;
|
||||
let request_client = page_vm.request_client.clone();
|
||||
while page_vm
|
||||
.vm()
|
||||
.document_runtime
|
||||
.has_ready_timeout_queued_by_classic_defer_script()
|
||||
{
|
||||
page_vm
|
||||
.run_classic_defer_timer_before_domcontentloaded(&request_client)
|
||||
.await?;
|
||||
}
|
||||
let run = super::main_document_lifecycle_completion::execute_parser_exact_domcontentloaded_on_owner_local_task(
|
||||
page_vm,
|
||||
successor_owner,
|
||||
)
|
||||
.await?;
|
||||
anyhow::ensure!(
|
||||
run.completion.kind() == MainDocumentLifecycleBodyKind::DomContentLoaded,
|
||||
"parse-time DOMContentLoaded successor lost its typed lifecycle execution"
|
||||
);
|
||||
page_vm
|
||||
.run_ready_classic_defer_timers_before_domcontentloaded(&request_client)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -228,32 +215,6 @@ impl PageVm {
|
||||
ParserCompletion::finish_parse_time(self, completion).await
|
||||
}
|
||||
|
||||
async fn execute_domcontentloaded_after_main_parser_finish_on_named_owner_lane(
|
||||
&mut self,
|
||||
mut task: ParserFinishDomContentLoadedTask,
|
||||
) -> Result<PostParsePageOwnedTask> {
|
||||
let owner = task.owner();
|
||||
let work = task.take_work_for_execution();
|
||||
let PostParsePageOwnedWork::Lifecycle(work) = work else {
|
||||
anyhow::bail!("parser-finish successor is not lifecycle work");
|
||||
};
|
||||
let successor_owner = match ParserCompletion::exact_domcontentloaded_owner(*work, owner) {
|
||||
Ok(owner) => owner,
|
||||
Err(message) => anyhow::bail!(message),
|
||||
};
|
||||
|
||||
let run = super::main_document_lifecycle_completion::execute_parser_exact_domcontentloaded_on_owner_local_task(
|
||||
self,
|
||||
successor_owner,
|
||||
)
|
||||
.await?;
|
||||
anyhow::ensure!(
|
||||
run.completion.kind() == MainDocumentLifecycleBodyKind::DomContentLoaded,
|
||||
"DOMContentLoaded direct successor lost its typed lifecycle execution"
|
||||
);
|
||||
Ok(task.into_completed_task())
|
||||
}
|
||||
|
||||
pub(super) async fn execute_and_complete_selected_post_parse_page_owned_task(
|
||||
&mut self,
|
||||
loader: &ResourceRequestClient,
|
||||
@@ -262,12 +223,38 @@ impl PageVm {
|
||||
stage: PageVmInitStage,
|
||||
mut task: Box<PostParsePageOwnedTask>,
|
||||
) -> Result<DocumentLifecycleTurnOutcome> {
|
||||
let work = task.take_work_for_execution();
|
||||
if let PostParsePageOwnedWork::Lifecycle(lifecycle) = &work
|
||||
&& matches!(
|
||||
**lifecycle,
|
||||
PostParseLifecycleWork::DispatchDomContentLoaded { .. }
|
||||
| PostParseLifecycleWork::DispatchWindowLoad { .. }
|
||||
)
|
||||
{
|
||||
let body = MainDocumentLifecycleBody::from_post_parse_work(lifecycle)
|
||||
.expect("selected DCL/load work must have a typed lifecycle body");
|
||||
self.queue_post_parse_lifecycle_dom_task(
|
||||
pending_document_lifecycle_turn
|
||||
.as_mut()
|
||||
.expect("lifecycle resident must remain installed"),
|
||||
body,
|
||||
*task,
|
||||
)?;
|
||||
if matches!(body, MainDocumentLifecycleBody::DomContentLoaded { .. }) {
|
||||
self.run_ready_classic_defer_timers_before_domcontentloaded(loader)
|
||||
.await?;
|
||||
}
|
||||
return self.outcome_after_exact_post_parse_action(
|
||||
pending_document_lifecycle_turn,
|
||||
document,
|
||||
stage,
|
||||
DocumentLifecycleTurnAction::Progressed,
|
||||
true,
|
||||
);
|
||||
}
|
||||
let replacement_lifecycle_snapshot = self.document_replacement_lifecycle_action_snapshot();
|
||||
let execution = self
|
||||
.execute_post_parse_page_owned_task_on_named_owner_lane(
|
||||
loader,
|
||||
task.take_work_for_execution(),
|
||||
)
|
||||
.execute_post_parse_page_owned_task_on_named_owner_lane(loader, work)
|
||||
.await;
|
||||
// Generic post-parse callbacks must finish their old-realm task before
|
||||
// a synchronous `document.open()` replacement is admitted. MainParser
|
||||
@@ -416,50 +403,32 @@ impl PageVm {
|
||||
};
|
||||
}
|
||||
};
|
||||
if let Some(dcl_task) = claimed_dcl {
|
||||
if let Some(mut dcl_task) = claimed_dcl {
|
||||
ParserCompletion::finish_task_with_replacement_admission(self, task_effect)?;
|
||||
while self
|
||||
.vm()
|
||||
.document_runtime
|
||||
.has_ready_timeout_queued_by_classic_defer_script()
|
||||
{
|
||||
self.run_classic_defer_timer_before_domcontentloaded(loader)
|
||||
.await?;
|
||||
}
|
||||
let replacement_lifecycle_snapshot =
|
||||
self.document_replacement_lifecycle_action_snapshot();
|
||||
let execution = self
|
||||
.execute_domcontentloaded_after_main_parser_finish_on_named_owner_lane(
|
||||
dcl_task,
|
||||
)
|
||||
.await;
|
||||
let admission = self
|
||||
.take_document_replacement_lifecycle_admission_after_action(
|
||||
replacement_lifecycle_snapshot,
|
||||
);
|
||||
let completed_dcl_task = match (execution, admission) {
|
||||
(Ok(task), Ok(_)) => task,
|
||||
(Err(execution_error), Ok(_)) => return Err(execution_error),
|
||||
(Ok(_), Err(admission_error)) => return Err(admission_error),
|
||||
(Err(execution_error), Err(admission_error)) => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"parser-finish DOMContentLoaded action failed ({execution_error:#}) and its Document replacement admission also failed ({admission_error:#})"
|
||||
));
|
||||
}
|
||||
let owner = dcl_task.owner();
|
||||
let PostParsePageOwnedWork::Lifecycle(work) =
|
||||
dcl_task.take_work_for_execution()
|
||||
else {
|
||||
anyhow::bail!("parser-finish successor is not lifecycle work");
|
||||
};
|
||||
pending_document_lifecycle_turn
|
||||
.as_mut()
|
||||
.expect("post-parse lifecycle state should remain installed")
|
||||
.completed_task = Some(completed_dcl_task);
|
||||
if let Some(outcome) = self
|
||||
.transition_lifecycle_for_pending_top_level_navigation(
|
||||
pending_document_lifecycle_turn,
|
||||
document,
|
||||
stage,
|
||||
)
|
||||
{
|
||||
return Ok(outcome);
|
||||
}
|
||||
let owner = ParserCompletion::exact_domcontentloaded_owner(*work, owner)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
self.queue_post_parse_lifecycle_dom_task(
|
||||
pending_document_lifecycle_turn
|
||||
.as_mut()
|
||||
.expect("lifecycle resident must remain installed"),
|
||||
MainDocumentLifecycleBody::DomContentLoaded { owner },
|
||||
dcl_task.into_pending_task(),
|
||||
)?;
|
||||
self.run_ready_classic_defer_timers_before_domcontentloaded(loader)
|
||||
.await?;
|
||||
return self.outcome_after_exact_post_parse_action(
|
||||
pending_document_lifecycle_turn,
|
||||
document,
|
||||
stage,
|
||||
DocumentLifecycleTurnAction::Progressed,
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
ParserCompletion::finish_task_with_replacement_admission(self, task_effect)?;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ impl PageVm {
|
||||
self.apply_selected_page_action_window_turn(deadline)
|
||||
}
|
||||
RendererPageSchedulerTask::DomManipulation(task) => {
|
||||
let outcome = self.apply_selected_page_dom_manipulation_turn(task)?;
|
||||
let outcome = self.apply_selected_page_dom_manipulation_turn(task).await?;
|
||||
self.finish_selected_page_dom_manipulation_task(outcome.action, loader)
|
||||
.await?;
|
||||
Ok(())
|
||||
|
||||
@@ -206,6 +206,26 @@ async fn advance_unblocked_exact_lifecycle_to_stage(
|
||||
},
|
||||
..
|
||||
} => assert_eq!(*runnable_document, document),
|
||||
DocumentLifecycleTurnOutcome {
|
||||
readiness: DocumentLifecycleTurnReadiness::Blocked { document: waiting },
|
||||
..
|
||||
} if pending
|
||||
.as_ref()
|
||||
.is_some_and(|pending| pending.awaiting_dom_task.is_some()) =>
|
||||
{
|
||||
assert_eq!(*waiting, document);
|
||||
let loader = page_vm.request_client.clone();
|
||||
assert!(
|
||||
page_vm
|
||||
.run_exact_selected_page_task_for_test(
|
||||
super::super::PageSelectedTaskTestSelector::DomManipulation(
|
||||
super::super::PageDomManipulationTestFamily::MainDocumentLifecycle
|
||||
),
|
||||
&loader
|
||||
)
|
||||
.await?
|
||||
);
|
||||
}
|
||||
_ => panic!(
|
||||
"unblocked exact lifecycle should remain runnable before {stage:?}: {outcome:?}"
|
||||
),
|
||||
@@ -267,6 +287,47 @@ async fn prepare_parse_time_exact_domcontentloaded(
|
||||
Ok(owner)
|
||||
}
|
||||
|
||||
impl PageVm {
|
||||
async fn advance_lifecycle_and_selected_dom_task_for_test(
|
||||
&mut self,
|
||||
pending: &mut Option<PendingDocumentLifecycleTurn>,
|
||||
document: RendererDocumentLifecycleIdentity,
|
||||
) -> anyhow::Result<DocumentLifecycleTurnOutcome> {
|
||||
for _ in 0..64 {
|
||||
let outcome = self
|
||||
.advance_post_parse_lifecycle_one_owner_turn(pending, document)
|
||||
.await?;
|
||||
if !matches!(
|
||||
outcome.readiness,
|
||||
DocumentLifecycleTurnReadiness::Blocked { .. }
|
||||
) || pending
|
||||
.as_ref()
|
||||
.is_none_or(|pending| pending.awaiting_dom_task.is_none())
|
||||
{
|
||||
return Ok(outcome);
|
||||
}
|
||||
let snapshot = self.document_replacement_lifecycle_action_snapshot();
|
||||
let loader = self.request_client.clone();
|
||||
assert!(
|
||||
self.run_exact_selected_page_task_for_test(
|
||||
super::super::PageSelectedTaskTestSelector::DomManipulation(
|
||||
super::super::PageDomManipulationTestFamily::MainDocumentLifecycle,
|
||||
),
|
||||
&loader,
|
||||
)
|
||||
.await?
|
||||
);
|
||||
if let Some(outcome) = self
|
||||
.reconcile_document_replacement_lifecycle_after_owner_action(snapshot, pending)
|
||||
.await?
|
||||
{
|
||||
return Ok(outcome);
|
||||
}
|
||||
}
|
||||
anyhow::bail!("lifecycle fixture exceeded its bounded DOM task turns")
|
||||
}
|
||||
}
|
||||
|
||||
async fn exact_lifecycle_turn_reaches_handler_navigation(
|
||||
event_target: &str,
|
||||
event_name: &str,
|
||||
@@ -303,7 +364,7 @@ async fn exact_lifecycle_turn_reaches_handler_navigation(
|
||||
|
||||
for _ in 0..128 {
|
||||
match page_vm
|
||||
.advance_post_parse_lifecycle_one_owner_turn(&mut pending, document)
|
||||
.advance_lifecycle_and_selected_dom_task_for_test(&mut pending, document)
|
||||
.await?
|
||||
{
|
||||
DocumentLifecycleTurnOutcome {
|
||||
@@ -369,7 +430,7 @@ async fn exact_lifecycle_turn_publishes_document_open_replacement(
|
||||
|
||||
for _ in 0..96 {
|
||||
match page_vm
|
||||
.advance_post_parse_lifecycle_one_owner_turn(
|
||||
.advance_lifecycle_and_selected_dom_task_for_test(
|
||||
&mut pending_document_lifecycle_turn,
|
||||
document,
|
||||
)
|
||||
@@ -401,7 +462,17 @@ async fn exact_lifecycle_turn_publishes_document_open_replacement(
|
||||
},
|
||||
..
|
||||
} => {
|
||||
assert_eq!(next_document, document);
|
||||
if next_document != document {
|
||||
assert_ne!(next_document, initial_document);
|
||||
assert_eq!(page_vm.document_lifecycle.identity(), next_document);
|
||||
assert_eq!(
|
||||
pending_document_lifecycle_turn
|
||||
.as_ref()
|
||||
.map(|pending| pending.document),
|
||||
Some(next_document)
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
document = next_document;
|
||||
}
|
||||
DocumentLifecycleTurnOutcome {
|
||||
@@ -576,7 +647,7 @@ fn load_stage_non_replacing_javascript_navigation_resumes_exact_lifecycle() {
|
||||
|
||||
for _ in 0..128 {
|
||||
let outcome = page_vm
|
||||
.advance_post_parse_lifecycle_one_owner_turn(&mut pending, document)
|
||||
.advance_lifecycle_and_selected_dom_task_for_test(&mut pending, document)
|
||||
.await?;
|
||||
if !matches!(
|
||||
outcome.action,
|
||||
@@ -622,7 +693,7 @@ fn load_stage_non_replacing_javascript_navigation_resumes_exact_lifecycle() {
|
||||
|
||||
for _ in 0..128 {
|
||||
let resumed = page_vm
|
||||
.advance_post_parse_lifecycle_one_owner_turn(
|
||||
.advance_lifecycle_and_selected_dom_task_for_test(
|
||||
&mut pending,
|
||||
document,
|
||||
)
|
||||
@@ -1729,6 +1800,15 @@ Promise.resolve().then(() => __parseTimeExactDclOrder.push("module-terminal:micr
|
||||
outcome,
|
||||
ParseTimeMainParserBoundaryOutcome::CurrentDocumentRetained
|
||||
);
|
||||
assert_eq!(
|
||||
page_vm.vm_mut().eval_without_microtask_checkpoint_for_test(
|
||||
"__parseTimeExactDclOrder.join('|')"
|
||||
)?,
|
||||
"module-terminal:microtask",
|
||||
"parser completion must only enqueue DCL"
|
||||
);
|
||||
let loader = page_vm.request_client.clone();
|
||||
assert!(page_vm.run_exact_selected_page_task_for_test(super::super::PageSelectedTaskTestSelector::DomManipulation(super::super::PageDomManipulationTestFamily::MainDocumentLifecycle), &loader).await?);
|
||||
assert_eq!(
|
||||
page_vm.vm_mut().eval_without_microtask_checkpoint_for_test(
|
||||
"__parseTimeExactDclOrder.join('|')"
|
||||
@@ -1789,6 +1869,8 @@ Promise.resolve().then(() => {
|
||||
ParseTimeMainParserBoundaryOutcome::DocumentReplaced,
|
||||
"the parser task-end checkpoint must report replacement before attempting the old exact DCL"
|
||||
);
|
||||
let loader = page_vm.request_client.clone();
|
||||
assert!(page_vm.run_exact_selected_page_task_for_test(super::super::PageSelectedTaskTestSelector::DomManipulation(super::super::PageDomManipulationTestFamily::MainDocumentLifecycle), &loader).await?);
|
||||
assert_ne!(
|
||||
page_vm.vm().current_main_document_task_owner(),
|
||||
Some(owner)
|
||||
@@ -1918,13 +2000,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
.await?;
|
||||
assert_eq!(
|
||||
outcome,
|
||||
ParseTimeMainParserBoundaryOutcome::DocumentReplaced,
|
||||
"phase one must stop the retired Document after an exact-DCL reaction replaces it"
|
||||
ParseTimeMainParserBoundaryOutcome::CurrentDocumentRetained,
|
||||
"the parser returns before the queued DCL listener can replace its Document"
|
||||
);
|
||||
assert_ne!(
|
||||
page_vm.vm().current_main_document_task_owner(),
|
||||
Some(owner)
|
||||
let loader = page_vm.request_client.clone();
|
||||
assert!(
|
||||
page_vm
|
||||
.run_exact_selected_page_task_for_test(super::super::PageSelectedTaskTestSelector::DomManipulation(super::super::PageDomManipulationTestFamily::MainDocumentLifecycle), &loader)
|
||||
.await?
|
||||
);
|
||||
assert_ne!(page_vm.vm().current_main_document_task_owner(), Some(owner));
|
||||
assert_eq!(
|
||||
page_vm.vm_mut().eval(
|
||||
"String(document.getElementById('parse-time-replacement') !== null)"
|
||||
@@ -2377,7 +2462,7 @@ fn domcontentloaded_turn_transitions_same_exact_document_to_load_residence() {
|
||||
|
||||
let dcl_outcome = loop {
|
||||
let outcome = page_vm
|
||||
.advance_post_parse_lifecycle_one_owner_turn(
|
||||
.advance_lifecycle_and_selected_dom_task_for_test(
|
||||
&mut pending_document_lifecycle_turn,
|
||||
document,
|
||||
)
|
||||
@@ -2437,7 +2522,7 @@ fn domcontentloaded_turn_transitions_same_exact_document_to_load_residence() {
|
||||
|
||||
loop {
|
||||
match page_vm
|
||||
.advance_post_parse_lifecycle_one_owner_turn(
|
||||
.advance_lifecycle_and_selected_dom_task_for_test(
|
||||
&mut pending_document_lifecycle_turn,
|
||||
document,
|
||||
)
|
||||
@@ -3605,7 +3690,7 @@ fn top_level_http_location_navigation_reserves_service_worker_client_until_commi
|
||||
} => {
|
||||
loop {
|
||||
match page_vm
|
||||
.advance_post_parse_lifecycle_one_owner_turn(
|
||||
.advance_lifecycle_and_selected_dom_task_for_test(
|
||||
&mut pending_document_lifecycle_turn,
|
||||
lifecycle_document,
|
||||
)
|
||||
|
||||
@@ -2053,12 +2053,20 @@ async fn run_child_interactive_domcontentloaded_then_host_load_for_wait(
|
||||
page_vm: &mut PageVm,
|
||||
label: &str,
|
||||
) -> ChildFrameSemanticTurnKind {
|
||||
run_expected_child_frame_task_source_after_realm_prerequisite_for_wait(
|
||||
page_vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
&format!("{label} interactive transition"),
|
||||
)
|
||||
.await;
|
||||
if page_vm.has_ready_child_frame_semantic_turn_for_test(
|
||||
ChildFrameSemanticTurnKind::RealmMaterialization,
|
||||
) {
|
||||
run_expected_child_realm_materialization_for_wait(page_vm, label).await;
|
||||
}
|
||||
if matches!(page_vm.page_task_executor_sources_for_test().next_child_frame_task_target(), Some(crate::page_task_queue::RendererPageChildFrameTaskTarget::DocumentLifecycle(target)) if matches!(target.action(), crate::frame_owner_model::FrameDocumentLifecycleAction::Interactive(_)))
|
||||
{
|
||||
run_expected_child_frame_task_source_after_realm_prerequisite_for_wait(
|
||||
page_vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
&format!("{label} queued interactive transition"),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
run_child_domcontentloaded_then_host_load_for_wait(page_vm, label).await
|
||||
}
|
||||
|
||||
@@ -3439,14 +3447,7 @@ globalThis.__childClassicWaitValue = 91;
|
||||
"external:true|current:external-classic|inline:91",
|
||||
"second DocumentScriptReady should run parser continuation without iframe load"
|
||||
);
|
||||
followup_sources.push(
|
||||
run_expected_child_frame_task_source_after_realm_prerequisite_for_wait(
|
||||
&mut page_vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
"child classic parser EOF interactive transition",
|
||||
)
|
||||
.await,
|
||||
);
|
||||
assert_eq!(page_vm.vm_mut().eval("document.querySelector('iframe').contentDocument.readyState")?, "interactive", "parser EOF must apply interactive synchronously");
|
||||
followup_sources.push(
|
||||
run_expected_child_frame_task_source_after_realm_prerequisite_for_wait(
|
||||
&mut page_vm,
|
||||
@@ -3535,10 +3536,9 @@ globalThis.__childClassicWaitValue = 91;
|
||||
ChildFrameSemanticTurnKind::DocumentScriptReady,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
ChildFrameSemanticTurnKind::HostLoad
|
||||
],
|
||||
"child classic completion should progress through script, parser continuation, interactive, DOMContentLoaded, complete, and HostLoad turns"
|
||||
"child classic completion should progress through script, parser continuation, DOMContentLoaded, complete, and HostLoad turns"
|
||||
);
|
||||
assert_eq!(
|
||||
final_events,
|
||||
@@ -3679,14 +3679,7 @@ async fn page_vm_child_parser_blocking_classic_waits_for_preceding_stylesheet()
|
||||
"script:stylesheet-ready:loading",
|
||||
"stylesheet source must be installed before the parser-blocking script executes while the document is still loading"
|
||||
);
|
||||
followup_sources.push(
|
||||
run_expected_child_frame_task_source_after_realm_prerequisite_for_wait(
|
||||
&mut page_vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
"stylesheet-gated child interactive transition",
|
||||
)
|
||||
.await,
|
||||
);
|
||||
assert_eq!(page_vm.vm_mut().eval("document.querySelector('iframe').contentDocument.readyState")?, "interactive", "parser EOF must apply interactive synchronously");
|
||||
followup_sources.push(
|
||||
run_expected_child_frame_task_source_after_realm_prerequisite_for_wait(
|
||||
&mut page_vm,
|
||||
@@ -3750,7 +3743,6 @@ async fn page_vm_child_parser_blocking_classic_waits_for_preceding_stylesheet()
|
||||
ChildFrameSemanticTurnKind::DocumentScriptReady,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
ChildFrameSemanticTurnKind::HostLoad,
|
||||
]
|
||||
);
|
||||
@@ -4516,9 +4508,8 @@ globalThis.__childClassicDeferWaitValue = 73;
|
||||
ChildFrameSemanticTurnKind::RealmMaterialization,
|
||||
ChildFrameSemanticTurnKind::DocumentScriptReady,
|
||||
ChildFrameSemanticTurnKind::DocumentScriptReady,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle
|
||||
],
|
||||
"child defer classic bootstrap should end with the document-owned interactive turn"
|
||||
"child defer classic bootstrap should apply interactive at parser EOF"
|
||||
);
|
||||
assert_eq!(
|
||||
followup_sources,
|
||||
@@ -5100,7 +5091,6 @@ globalThis.__childClassicAsyncWaitValue = 41;
|
||||
ChildFrameSemanticTurnKind::DocumentScriptReady,
|
||||
ChildFrameSemanticTurnKind::DocumentScriptReady,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle
|
||||
],
|
||||
"child async bootstrap should dispatch interactive and DCL before the later async completion"
|
||||
);
|
||||
@@ -5347,7 +5337,6 @@ parent.__childClassicDeferOrderEvents.push("current:" + document.currentScript.i
|
||||
ChildFrameSemanticTurnKind::RealmMaterialization,
|
||||
ChildFrameSemanticTurnKind::DocumentScriptReady,
|
||||
ChildFrameSemanticTurnKind::DocumentScriptReady,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle
|
||||
],
|
||||
"child defer ordering bootstrap should reach interactive before either source completion"
|
||||
);
|
||||
@@ -5782,8 +5771,8 @@ async fn page_vm_child_parser_defer_preserves_cross_kind_document_order() {
|
||||
"parser EOF must not execute any mixed parser-deferred script"
|
||||
);
|
||||
assert!(
|
||||
bootstrap_sources.contains(&ChildFrameSemanticTurnKind::DocumentLifecycle),
|
||||
"mixed parser-deferred document should reach interactive"
|
||||
page_vm.vm_mut().eval("document.querySelector('iframe').contentDocument.readyState")? == "interactive",
|
||||
"mixed parser-deferred document should become interactive at parser EOF"
|
||||
);
|
||||
assert_eq!(
|
||||
bootstrap_sources
|
||||
@@ -6493,7 +6482,6 @@ globalThis.__childParserModuleWaitValue = 188;
|
||||
ChildFrameSemanticTurnKind::DocumentScriptReady,
|
||||
ChildFrameSemanticTurnKind::ParserModuleRootStart,
|
||||
ChildFrameSemanticTurnKind::DocumentScriptReady,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle
|
||||
],
|
||||
"the typed root fetch-start must preserve parser discovery FIFO, then the parser should run the following inline script and reach interactive"
|
||||
);
|
||||
@@ -8865,8 +8853,8 @@ async fn page_vm_child_modulepreload_terminal_event_does_not_delay_complete() {
|
||||
.iter()
|
||||
.filter(|source| **source == ChildFrameSemanticTurnKind::DocumentLifecycle)
|
||||
.count()
|
||||
>= 3,
|
||||
"interactive, DOMContentLoaded, and complete must advance while the terminal link event remains queued: {lifecycle_turns:?}"
|
||||
== 2,
|
||||
"DOMContentLoaded and complete must each take a queued turn while the terminal link event remains queued: {lifecycle_turns:?}"
|
||||
);
|
||||
assert!(
|
||||
lifecycle_turns.contains(&ChildFrameSemanticTurnKind::HostLoad),
|
||||
@@ -9082,8 +9070,8 @@ async fn page_vm_child_modulepreload_fetch_does_not_delay_iframe_load() {
|
||||
.iter()
|
||||
.filter(|source| **source == ChildFrameSemanticTurnKind::DocumentLifecycle)
|
||||
.count()
|
||||
>= 3,
|
||||
"interactive, DOMContentLoaded and complete should run while modulepreload fetch is pending: {startup_sources:?}"
|
||||
== 2,
|
||||
"DOMContentLoaded and complete should each take a queued turn while modulepreload fetch is pending: {startup_sources:?}"
|
||||
);
|
||||
assert!(startup_sources.contains(&ChildFrameSemanticTurnKind::HostLoad));
|
||||
assert_eq!(
|
||||
@@ -9565,7 +9553,7 @@ globalThis.__childDocumentLoadWaitValue = 42;
|
||||
first_followup_source,
|
||||
events_after_first_followup,
|
||||
lifecycle_ready_after_first_followup,
|
||||
interactive_source,
|
||||
interactive_state,
|
||||
host_load_source,
|
||||
final_events,
|
||||
) = local_executor
|
||||
@@ -9644,12 +9632,7 @@ globalThis.__childDocumentLoadWaitValue = 42;
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
);
|
||||
|
||||
let interactive_source = run_expected_child_frame_task_source_after_realm_prerequisite_for_wait(
|
||||
&mut page_vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
"child document parser EOF interactive transition",
|
||||
)
|
||||
.await;
|
||||
let interactive_state = page_vm.vm_mut().eval("document.querySelector('iframe').contentDocument.readyState")?;
|
||||
let host_load_source = run_child_domcontentloaded_then_host_load_for_wait(
|
||||
&mut page_vm,
|
||||
"child document iframe load",
|
||||
@@ -9672,7 +9655,7 @@ globalThis.__childDocumentLoadWaitValue = 42;
|
||||
first_followup_source,
|
||||
events_after_first_followup,
|
||||
lifecycle_ready_after_first_followup,
|
||||
interactive_source,
|
||||
interactive_state,
|
||||
host_load_source,
|
||||
final_events,
|
||||
))
|
||||
@@ -9710,8 +9693,8 @@ globalThis.__childDocumentLoadWaitValue = 42;
|
||||
"document-script ready should make the later lifecycle turn runnable"
|
||||
);
|
||||
assert_eq!(
|
||||
interactive_source,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
interactive_state,
|
||||
"interactive",
|
||||
"parser EOF should become interactive before HostLoad"
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -9885,8 +9868,6 @@ parent.__multiChildDocumentEvents.push("child-b-script:" + (globalThis === self)
|
||||
page_vm.run_next_child_frame_task_source_for_semantic_test().await,
|
||||
page_vm.run_next_child_frame_task_source_for_semantic_test().await,
|
||||
page_vm.run_next_child_frame_task_source_for_semantic_test().await,
|
||||
page_vm.run_next_child_frame_task_source_for_semantic_test().await,
|
||||
page_vm.run_next_child_frame_task_source_for_semantic_test().await,
|
||||
];
|
||||
let first_host_load_source = page_vm.run_next_child_frame_task_source_for_semantic_test().await;
|
||||
let events_after_first_host_load = page_vm
|
||||
@@ -9966,8 +9947,8 @@ parent.__multiChildDocumentEvents.push("child-b-script:" + (globalThis === self)
|
||||
);
|
||||
assert_eq!(
|
||||
lifecycle_sources,
|
||||
vec![Some(ChildFrameSemanticTurnKind::DocumentLifecycle); 6],
|
||||
"interactive, DOMContentLoaded and complete must each consume one lifecycle turn per child"
|
||||
vec![Some(ChildFrameSemanticTurnKind::DocumentLifecycle); 4],
|
||||
"DOMContentLoaded and complete must each consume one lifecycle turn per child"
|
||||
);
|
||||
assert_eq!(
|
||||
first_host_load_source,
|
||||
@@ -10034,7 +10015,7 @@ parent.__childReadyHostLoadEvents.push("child-script:" + (globalThis === self));
|
||||
script_ready_source,
|
||||
events_after_script_ready,
|
||||
lifecycle_ready_after_script,
|
||||
interactive_source,
|
||||
interactive_state,
|
||||
host_load_source,
|
||||
events_after_host_load,
|
||||
) = local_executor
|
||||
@@ -10092,7 +10073,7 @@ parent.__childReadyHostLoadEvents.push("child-script:" + (globalThis === self));
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
);
|
||||
|
||||
let interactive_source = page_vm.run_next_child_frame_task_source_for_semantic_test().await;
|
||||
let interactive_state = page_vm.vm_mut().eval("document.querySelector('iframe').contentDocument.readyState")?;
|
||||
let host_load_source = Some(
|
||||
run_child_domcontentloaded_then_host_load_for_wait(
|
||||
&mut page_vm,
|
||||
@@ -10111,7 +10092,7 @@ parent.__childReadyHostLoadEvents.push("child-script:" + (globalThis === self));
|
||||
script_ready_source,
|
||||
events_after_script_ready,
|
||||
lifecycle_ready_after_script,
|
||||
interactive_source,
|
||||
interactive_state,
|
||||
host_load_source,
|
||||
events_after_host_load,
|
||||
))
|
||||
@@ -10146,8 +10127,8 @@ parent.__childReadyHostLoadEvents.push("child-script:" + (globalThis === self));
|
||||
"DocumentScriptReady should make the later lifecycle turn runnable"
|
||||
);
|
||||
assert_eq!(
|
||||
interactive_source,
|
||||
Some(ChildFrameSemanticTurnKind::DocumentLifecycle),
|
||||
interactive_state,
|
||||
"interactive",
|
||||
"parser EOF should dispatch interactive before HostLoad"
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -10771,7 +10752,7 @@ async fn page_vm_realm_materialization_created_ready_work_enters_document_script
|
||||
.run_next_child_frame_task_source_for_semantic_test()
|
||||
.await,
|
||||
Some(ChildFrameSemanticTurnKind::DocumentLifecycle),
|
||||
"outer parser EOF should become interactive before nested document work"
|
||||
"outer DOMContentLoaded must precede nested parser work admitted after it"
|
||||
);
|
||||
assert_eq!(
|
||||
page_vm
|
||||
@@ -10792,7 +10773,7 @@ async fn page_vm_realm_materialization_created_ready_work_enters_document_script
|
||||
.run_next_child_frame_task_source_for_semantic_test()
|
||||
.await,
|
||||
Some(ChildFrameSemanticTurnKind::DocumentLifecycle),
|
||||
"outer DOMContentLoaded should remain a later FIFO turn after nested work already admitted by realm materialization"
|
||||
"nested DOMContentLoaded must remain a later FIFO turn after its parser work"
|
||||
);
|
||||
Ok::<_, anyhow::Error>(())
|
||||
})
|
||||
@@ -11344,12 +11325,12 @@ onload = () => {
|
||||
host_load_pending_after_completion,
|
||||
script_ready_source,
|
||||
events_after_script_ready,
|
||||
interactive_source,
|
||||
interactive_state,
|
||||
host_load_source,
|
||||
events_after_host_load,
|
||||
nested_script_ready_source,
|
||||
events_after_nested_script_ready,
|
||||
nested_interactive_source,
|
||||
nested_interactive_state,
|
||||
nested_host_load_source,
|
||||
events_after_nested_host_load,
|
||||
) = local_executor
|
||||
@@ -11393,7 +11374,7 @@ onload = () => {
|
||||
let events_after_script_ready = page_vm
|
||||
.vm_mut()
|
||||
.eval("__hostLoadNestedEvents.join('|')")?;
|
||||
let interactive_source = page_vm.run_next_child_frame_task_source_for_semantic_test().await;
|
||||
let interactive_state = page_vm.vm_mut().eval("document.querySelector('iframe').contentDocument.readyState")?;
|
||||
let host_load_source = Some(
|
||||
run_child_domcontentloaded_then_host_load_for_wait(
|
||||
&mut page_vm,
|
||||
@@ -11419,8 +11400,7 @@ onload = () => {
|
||||
let events_after_nested_script_ready = page_vm
|
||||
.vm_mut()
|
||||
.eval("__hostLoadNestedEvents.join('|')")?;
|
||||
let nested_interactive_source =
|
||||
page_vm.run_next_child_frame_task_source_for_semantic_test().await;
|
||||
let nested_interactive_state = page_vm.vm_mut().eval("document.querySelector('iframe').contentDocument.querySelector('iframe').contentDocument.readyState")?;
|
||||
let nested_host_load_source = Some(
|
||||
run_child_domcontentloaded_then_host_load_for_wait(
|
||||
&mut page_vm,
|
||||
@@ -11438,12 +11418,12 @@ onload = () => {
|
||||
host_load_pending_after_completion,
|
||||
script_ready_source,
|
||||
events_after_script_ready,
|
||||
interactive_source,
|
||||
interactive_state,
|
||||
host_load_source,
|
||||
events_after_host_load,
|
||||
nested_script_ready_source,
|
||||
events_after_nested_script_ready,
|
||||
nested_interactive_source,
|
||||
nested_interactive_state,
|
||||
nested_host_load_source,
|
||||
events_after_nested_host_load,
|
||||
))
|
||||
@@ -11471,8 +11451,8 @@ onload = () => {
|
||||
);
|
||||
assert_eq!(events_after_script_ready, "child-script");
|
||||
assert_eq!(
|
||||
interactive_source,
|
||||
Some(ChildFrameSemanticTurnKind::DocumentLifecycle),
|
||||
interactive_state,
|
||||
"interactive",
|
||||
"child parser EOF should become interactive before window load"
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -11494,8 +11474,8 @@ onload = () => {
|
||||
"nested script should run on the later DocumentScriptReady turn"
|
||||
);
|
||||
assert_eq!(
|
||||
nested_interactive_source,
|
||||
Some(ChildFrameSemanticTurnKind::DocumentLifecycle),
|
||||
nested_interactive_state,
|
||||
"interactive",
|
||||
"nested parser EOF should become interactive before nested HostLoad"
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -11586,7 +11566,7 @@ addEventListener("load", () => parent.__childLoadNavigationEvents.push("load-lis
|
||||
completion_source,
|
||||
script_ready_source,
|
||||
events_after_script_ready,
|
||||
interactive_source,
|
||||
interactive_state,
|
||||
host_load_source,
|
||||
events_after_host_load,
|
||||
pending_after_host_load,
|
||||
@@ -11638,7 +11618,7 @@ addEventListener("load", () => parent.__childLoadNavigationEvents.push("load-lis
|
||||
.vm_mut()
|
||||
.eval("__childLoadNavigationEvents.join('|')")?;
|
||||
|
||||
let interactive_source = page_vm.run_next_child_frame_task_source_for_semantic_test().await;
|
||||
let interactive_state = page_vm.vm_mut().eval("document.querySelector('iframe').contentDocument.readyState")?;
|
||||
let host_load_source = Some(
|
||||
run_child_domcontentloaded_then_host_load_for_wait(
|
||||
&mut page_vm,
|
||||
@@ -11688,7 +11668,7 @@ addEventListener("load", () => parent.__childLoadNavigationEvents.push("load-lis
|
||||
completion_source,
|
||||
script_ready_source,
|
||||
events_after_script_ready,
|
||||
interactive_source,
|
||||
interactive_state,
|
||||
host_load_source,
|
||||
events_after_host_load,
|
||||
pending_after_host_load,
|
||||
@@ -11723,8 +11703,8 @@ addEventListener("load", () => parent.__childLoadNavigationEvents.push("load-lis
|
||||
"DocumentScriptReady should not dispatch child window load inline"
|
||||
);
|
||||
assert_eq!(
|
||||
interactive_source,
|
||||
Some(ChildFrameSemanticTurnKind::DocumentLifecycle),
|
||||
interactive_state,
|
||||
"interactive",
|
||||
"child document should become interactive before its window load"
|
||||
);
|
||||
assert_eq!(
|
||||
|
||||
@@ -213,7 +213,7 @@ JSON.stringify({
|
||||
PageDomManipulationTestFamily::ViewTransitionUpdate,
|
||||
)
|
||||
.expect("view-transition update callback should queue one platform task");
|
||||
let body = page_vm.apply_selected_page_dom_manipulation_turn(task)?;
|
||||
let body = page_vm.apply_selected_page_dom_manipulation_turn(task).await?;
|
||||
let PageDomManipulationTurnAction::ViewTransitionUpdate(action) = body.action else {
|
||||
unreachable!("the selected view-transition task must retain its typed action")
|
||||
};
|
||||
|
||||
@@ -1246,7 +1246,7 @@ impl<'loader, 'state> ParserDriver<'loader, 'state> {
|
||||
start_line,
|
||||
start_column,
|
||||
import_map,
|
||||
);
|
||||
)?;
|
||||
Ok(ScriptHandoffOutcome::NoNavigation)
|
||||
}
|
||||
ParserScriptHandoff::NoExecution {
|
||||
|
||||
@@ -753,6 +753,16 @@ fn streaming_stylesheet_and_json_ld_reach_tail_and_post_parse_boundary() {
|
||||
DocumentLifecycleTurnReadiness::Runnable { .. },
|
||||
..
|
||||
} => {}
|
||||
DocumentLifecycleTurnOutcome {
|
||||
readiness: DocumentLifecycleTurnReadiness::Blocked { .. },
|
||||
..
|
||||
} if pending.as_ref().is_some_and(|pending| pending.awaiting_dom_task.is_some()) => {
|
||||
let loader = page_vm.request_client.clone();
|
||||
assert!(page_vm.run_exact_selected_page_task_for_test(
|
||||
crate::runtime::page_vm::PageSelectedTaskTestSelector::AnyDomManipulation,
|
||||
&loader,
|
||||
).await?, "admitted DCL must advance through the real DOM source head");
|
||||
}
|
||||
outcome => panic!(
|
||||
"streaming fixture should reach DCL without parking: {outcome:?}"
|
||||
),
|
||||
|
||||
@@ -5338,9 +5338,23 @@ impl ScriptVm {
|
||||
) -> bool {
|
||||
use crate::{
|
||||
frame_owner_model::ChildFrameSemanticTurnKind,
|
||||
page_task_queue::RendererPageChildFrameTaskTarget,
|
||||
page_task_queue::{
|
||||
RendererPageChildFrameTaskTarget, RendererPageDomManipulationOwner,
|
||||
RendererPageReadyDescriptor,
|
||||
},
|
||||
};
|
||||
|
||||
if expected == ChildFrameSemanticTurnKind::DocumentLifecycle {
|
||||
return self._page_task_residence_for_executor_test.as_ref().expect("semantic fixture must retain its sources").task_sources().has_scheduler_task_for_executor_test(|descriptor| {
|
||||
matches!(descriptor,
|
||||
RendererPageReadyDescriptor::DomManipulation { owner: RendererPageDomManipulationOwner::ChildDocumentLifecycle(_), .. }
|
||||
) || matches!(descriptor,
|
||||
RendererPageReadyDescriptor::ChildFrameTask { owner, .. }
|
||||
if matches!(owner.target(), RendererPageChildFrameTaskTarget::DocumentLifecycle(_))
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
let Some(target) = self
|
||||
._page_task_residence_for_executor_test
|
||||
.as_ref()
|
||||
@@ -5393,7 +5407,14 @@ impl ScriptVm {
|
||||
{
|
||||
return Some(ChildFrameSemanticTurnKind::NavigationCommit);
|
||||
}
|
||||
if self
|
||||
if matches!(
|
||||
self._page_task_residence_for_executor_test
|
||||
.as_ref()
|
||||
.expect("child fixture must retain its sources")
|
||||
.task_sources()
|
||||
.next_child_semantic_task_target(),
|
||||
Some(crate::page_task_queue::RendererPageChildFrameTaskTarget::DocumentLifecycle(_))
|
||||
) && self
|
||||
.run_child_document_lifecycle_body_for_test()
|
||||
.expect("typed child lifecycle executor turn should succeed")
|
||||
.is_some()
|
||||
|
||||
@@ -27,15 +27,29 @@ impl<'vm> ChildDocumentLifecycleOwner<'vm> {
|
||||
pub(super) fn notify_parser_stop_action(
|
||||
&mut self,
|
||||
action: FrameDocumentInteractiveLifecycleAction,
|
||||
) -> bool {
|
||||
self.vm
|
||||
._context_host
|
||||
.borrow_mut()
|
||||
.queue_child_document_interactive_lifecycle_action(action)
|
||||
) -> Result<FrameDocumentLifecycleTaskEffect> {
|
||||
let context_host = self.vm._context_host.clone();
|
||||
self.vm.with_default_context_scope(move |scope, _| {
|
||||
Ok(context_host
|
||||
.borrow_mut()
|
||||
.finish_child_document_parser_stop(scope, action))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ScriptVm {
|
||||
pub(crate) fn child_document_lifecycle_waits_for_realm(
|
||||
&self,
|
||||
target: RendererPageChildDocumentLifecycleTarget,
|
||||
) -> bool {
|
||||
let host = self._context_host.borrow();
|
||||
host.child_document_lifecycle_action_is_current(target.action())
|
||||
&& host.has_child_frame_realm_materialization_request(
|
||||
target.child_handle(),
|
||||
target.document_owner(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn current_child_document_lifecycle_target(
|
||||
&self,
|
||||
expected: RendererPageChildDocumentLifecycleTarget,
|
||||
@@ -102,9 +116,9 @@ impl ScriptVm {
|
||||
let Some(task) = source.take_scheduler_task_for_executor_test(|descriptor| {
|
||||
matches!(
|
||||
descriptor,
|
||||
crate::page_task_queue::RendererPageReadyDescriptor::ChildFrameTask {
|
||||
owner,
|
||||
..
|
||||
crate::page_task_queue::RendererPageReadyDescriptor::ChildFrameTask { owner, .. }
|
||||
| crate::page_task_queue::RendererPageReadyDescriptor::DomManipulation {
|
||||
owner: crate::page_task_queue::RendererPageDomManipulationOwner::ChildDocumentLifecycle(owner), ..
|
||||
} if matches!(
|
||||
owner.target(),
|
||||
RendererPageChildFrameTaskTarget::DocumentLifecycle(_)
|
||||
@@ -113,8 +127,14 @@ impl ScriptVm {
|
||||
}) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let crate::page_task_queue::RendererPageSchedulerTask::ChildFrameTask(task) = task else {
|
||||
unreachable!("child-frame descriptor must dequeue its own family source")
|
||||
let task = match task {
|
||||
crate::page_task_queue::RendererPageSchedulerTask::ChildFrameTask(task)
|
||||
| crate::page_task_queue::RendererPageSchedulerTask::DomManipulation(
|
||||
crate::page_task_queue::RendererPageDomManipulationTask::ChildDocumentLifecycle(
|
||||
task,
|
||||
),
|
||||
) => task,
|
||||
_ => unreachable!("child lifecycle selector must dequeue its admitted task"),
|
||||
};
|
||||
let RendererPageChildFrameTaskTarget::DocumentLifecycle(target) = task.owner().target()
|
||||
else {
|
||||
|
||||
@@ -60,7 +60,7 @@ impl ScriptVm {
|
||||
self.apply_child_document_owner_transition(transition);
|
||||
}
|
||||
if let Some(action) = parser_stop_action {
|
||||
ChildDocumentLifecycleOwner::new(self).notify_parser_stop_action(action);
|
||||
ChildDocumentLifecycleOwner::new(self).notify_parser_stop_action(action)?;
|
||||
}
|
||||
let mut ready_owner = ChildDocumentScriptSchedulerOwner::new(self);
|
||||
for work in ready_work {
|
||||
|
||||
@@ -5317,7 +5317,7 @@ mod tests {
|
||||
vm: &mut ScriptVm,
|
||||
label: &str,
|
||||
) {
|
||||
for transition in ["interactive", "DOMContentLoaded", "complete"] {
|
||||
for transition in ["DOMContentLoaded", "complete"] {
|
||||
assert!(
|
||||
vm.run_child_frame_task_source_once_for_test(
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
@@ -5355,7 +5355,6 @@ mod tests {
|
||||
label: &str,
|
||||
) {
|
||||
for expected in [
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
ChildFrameSemanticTurnKind::HostLoad,
|
||||
|
||||
@@ -110,8 +110,8 @@ impl ScriptVm {
|
||||
/// The main-document lifecycle coordinator owns the ordinary task-end
|
||||
/// checkpoint. Keeping this primitive body-only prevents DCL and
|
||||
/// `readystatechange` helpers from silently becoming a second completion
|
||||
/// authority. Parser-finish DCL temporarily uses the explicit compatibility
|
||||
/// wrapper below until P5-A3 moves that direct successor as one unit.
|
||||
/// authority. Parser completion admits DCL to the DOM task source after
|
||||
/// finishing its own task-end checkpoint.
|
||||
pub(super) fn dispatch_document_lifecycle_event_body(
|
||||
&mut self,
|
||||
event_type: &str,
|
||||
|
||||
@@ -908,6 +908,32 @@ impl ScriptVm {
|
||||
Some(ParserFinishDomContentLoadedWork::new(owner, *work))
|
||||
}
|
||||
|
||||
pub(crate) fn main_document_window_load_task_is_ready(
|
||||
&mut self,
|
||||
owner: FrameDocumentTaskOwner,
|
||||
) -> Option<bool> {
|
||||
let has_runtime_delay = self.has_post_domcontentloaded_load_delaying_runtime_work();
|
||||
self._context_host
|
||||
.borrow()
|
||||
.current_main_document_complete_transition_is_ready(owner)
|
||||
.map(|ready| ready && !has_runtime_delay)
|
||||
}
|
||||
|
||||
pub(crate) fn queue_main_document_lifecycle_dom_task(
|
||||
&self,
|
||||
body: super::MainDocumentLifecycleBody,
|
||||
completion: Option<
|
||||
tokio::sync::oneshot::Sender<
|
||||
crate::page_task_queue::RendererPageMainDocumentLifecycleCompletion,
|
||||
>,
|
||||
>,
|
||||
) -> anyhow::Result<()> {
|
||||
self._context_host
|
||||
.borrow()
|
||||
.page_main_document_lifecycle_sender()
|
||||
.send(body, completion)
|
||||
}
|
||||
|
||||
/// Consume the completed parser task and claim only its exact, already
|
||||
/// queued DOMContentLoaded successor.
|
||||
///
|
||||
@@ -1840,21 +1866,6 @@ impl ScriptVm {
|
||||
reached_boundary,
|
||||
invalidation_policy,
|
||||
} = PostParseProcessingAction::from_document_processing_action(action);
|
||||
if work.is_domcontentloaded_task()
|
||||
&& self
|
||||
.document_runtime
|
||||
.has_ready_timeout_queued_by_classic_defer_script()
|
||||
{
|
||||
// The lifecycle plan pre-seeds this boundary marker so owner work
|
||||
// stays ordered. It has now reached the front after defer-like and
|
||||
// parser-owned pre-DCL work, which is the point at which HTML queues
|
||||
// the actual DOMContentLoaded global task. Give only ready timers
|
||||
// scheduled by classic defer scripts their intervening task turn.
|
||||
page_task_queue.enqueue_front_post_parse_work_preserving_order(vec![work]);
|
||||
return PostParseDriverStep::Ready(Box::new(
|
||||
ReadyPostParseAction::TimerQueuedByClassicDeferBeforeDomContentLoaded,
|
||||
));
|
||||
}
|
||||
if !(work.is_domcontentloaded_task() || work.is_window_load_task()) {
|
||||
return PostParseDriverStep::Ready(Box::new(ReadyPostParseAction::Processing(
|
||||
Box::new(PostParseProcessingAction {
|
||||
@@ -1977,11 +1988,6 @@ impl ScriptVm {
|
||||
ReadyPostParseAction::Processing(action) => {
|
||||
return Ok(PostParseProcessingStep::Action(action));
|
||||
}
|
||||
ReadyPostParseAction::TimerQueuedByClassicDeferBeforeDomContentLoaded => {
|
||||
return Ok(
|
||||
PostParseProcessingStep::TimerQueuedByClassicDeferBeforeDomContentLoaded,
|
||||
);
|
||||
}
|
||||
},
|
||||
PostParseDriverStep::NeedsContinuation => {
|
||||
return Ok(PostParseProcessingStep::NeedsContinuation);
|
||||
@@ -2020,11 +2026,6 @@ impl ScriptVm {
|
||||
execution.into_page_owned_task(),
|
||||
)));
|
||||
}
|
||||
PostParseProcessingStep::TimerQueuedByClassicDeferBeforeDomContentLoaded => {
|
||||
return Ok(
|
||||
PostParseLifecycleAdvance::TimerQueuedByClassicDeferBeforeDomContentLoaded,
|
||||
);
|
||||
}
|
||||
PostParseProcessingStep::NeedsContinuation => {
|
||||
return Ok(PostParseLifecycleAdvance::NeedsContinuation);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ pub(crate) enum PostParseDrainResult {
|
||||
|
||||
pub(crate) enum PostParseLifecycleAdvance {
|
||||
PageOwnedTask(Box<PostParsePageOwnedTask>),
|
||||
TimerQueuedByClassicDeferBeforeDomContentLoaded,
|
||||
NeedsContinuation,
|
||||
AwaitProgress,
|
||||
Complete(PostParseLifecycleCompletionAction),
|
||||
@@ -193,20 +192,20 @@ pub(crate) struct PostParsePageOwnedTask {
|
||||
pub(super) completion: PostParseTaskCompletion,
|
||||
}
|
||||
|
||||
/// The exact DOMContentLoaded action claimed as the direct successor of a
|
||||
/// The exact DOMContentLoaded action claimed for DOM task admission after a
|
||||
/// drained main-parser queue.
|
||||
///
|
||||
/// This value is deliberately short-lived: the existing post-parse queue and
|
||||
/// lifecycle driver remain the only durable authority. The wrapper merely
|
||||
/// prevents an already-claimed DCL action from falling back to a generic
|
||||
/// page-owned task before the parser continuation commits it.
|
||||
/// page-owned task before the parser continuation admits it to the DOM source.
|
||||
pub(crate) struct ParserFinishDomContentLoadedTask {
|
||||
owner: FrameDocumentTaskOwner,
|
||||
task: PostParsePageOwnedTask,
|
||||
}
|
||||
|
||||
/// The exact parse-time DOMContentLoaded work claimed as the direct successor
|
||||
/// of a drained main-parser queue.
|
||||
/// The exact parse-time DOMContentLoaded work claimed for DOM task admission
|
||||
/// after a drained main-parser queue.
|
||||
///
|
||||
/// Unlike [`ParserFinishDomContentLoadedTask`], phase one has no installed
|
||||
/// post-parse driver task token to complete. The lifecycle authority still
|
||||
@@ -244,7 +243,7 @@ impl ParserFinishDomContentLoadedTask {
|
||||
self.task.take_work_for_execution()
|
||||
}
|
||||
|
||||
pub(crate) fn into_completed_task(self) -> PostParsePageOwnedTask {
|
||||
pub(crate) fn into_pending_task(self) -> PostParsePageOwnedTask {
|
||||
self.task
|
||||
}
|
||||
}
|
||||
@@ -323,7 +322,6 @@ impl PostParseProcessingAction {
|
||||
|
||||
pub(crate) enum ReadyPostParseAction {
|
||||
Processing(Box<PostParseProcessingAction>),
|
||||
TimerQueuedByClassicDeferBeforeDomContentLoaded,
|
||||
}
|
||||
|
||||
pub(crate) enum PostParseRuntimeDriverStep {
|
||||
@@ -340,7 +338,6 @@ pub(crate) enum PostParseDriverStep {
|
||||
|
||||
pub(crate) enum PostParseProcessingStep {
|
||||
Action(Box<PostParseProcessingAction>),
|
||||
TimerQueuedByClassicDeferBeforeDomContentLoaded,
|
||||
NeedsContinuation,
|
||||
AwaitProgress,
|
||||
Idle,
|
||||
|
||||
@@ -6068,7 +6068,7 @@ impl ScriptVm {
|
||||
}
|
||||
};
|
||||
self.apply_pending_child_document_owner_retirements();
|
||||
let (application, body_activity) = application;
|
||||
let (application, mut body_activity) = application;
|
||||
let Some(application) = application else {
|
||||
return Ok(CurrentChildDocumentLoadApplication::Applied { body_activity });
|
||||
};
|
||||
@@ -6076,9 +6076,13 @@ impl ScriptVm {
|
||||
if let Some(transition) = owner_transition {
|
||||
self.apply_child_document_owner_transition(transition);
|
||||
}
|
||||
if let Some(action) = parser_stop_action {
|
||||
super::child_document_lifecycle::ChildDocumentLifecycleOwner::new(self)
|
||||
.notify_parser_stop_action(action);
|
||||
if let Some(action) = parser_stop_action
|
||||
&& super::child_document_lifecycle::ChildDocumentLifecycleOwner::new(self)
|
||||
.notify_parser_stop_action(action)?
|
||||
== crate::frame_owner_model::FrameDocumentLifecycleTaskEffect::EventDispatched
|
||||
{
|
||||
body_activity =
|
||||
crate::native_bridge::ChildDocumentLoadBodyActivity::PageCodeOrEventDispatch;
|
||||
}
|
||||
if let Some(work) = work {
|
||||
super::child_document_script_scheduler::ChildDocumentScriptSchedulerOwner::new(self)
|
||||
|
||||
@@ -11654,7 +11654,7 @@ async fn iframe_javascript_url_string_completion_replaces_child_document() {
|
||||
"javascript URL should execute on DocumentScriptReady",
|
||||
)
|
||||
.await;
|
||||
for transition in ["interactive", "DOMContentLoaded", "complete"] {
|
||||
for transition in ["DOMContentLoaded", "complete"] {
|
||||
expect_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
@@ -13644,7 +13644,7 @@ async fn child_window_load_replacement_stops_old_delivery_before_owner_output()
|
||||
"old Window load listener must retain a current callback relevant realm"
|
||||
);
|
||||
}
|
||||
for context in ["old interactive", "old DOMContentLoaded", "old complete"] {
|
||||
for context in ["old DOMContentLoaded", "old complete"] {
|
||||
expect_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
@@ -13696,11 +13696,7 @@ async fn child_window_load_replacement_stops_old_delivery_before_owner_output()
|
||||
"replacement child load handlers should install",
|
||||
)
|
||||
.await;
|
||||
for context in [
|
||||
"replacement interactive",
|
||||
"replacement DOMContentLoaded",
|
||||
"replacement complete",
|
||||
] {
|
||||
for context in ["replacement DOMContentLoaded", "replacement complete"] {
|
||||
expect_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
@@ -13855,7 +13851,7 @@ async fn child_pageshow_replacement_stops_old_frame_finish_and_protocol_output()
|
||||
"old Window lifecycle listeners must retain current callback relevant realms"
|
||||
);
|
||||
}
|
||||
for context in ["old interactive", "old DOMContentLoaded", "old complete"] {
|
||||
for context in ["old DOMContentLoaded", "old complete"] {
|
||||
expect_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
@@ -13906,11 +13902,7 @@ async fn child_pageshow_replacement_stops_old_frame_finish_and_protocol_output()
|
||||
"replacement pageshow handlers should install",
|
||||
)
|
||||
.await;
|
||||
for context in [
|
||||
"replacement interactive",
|
||||
"replacement DOMContentLoaded",
|
||||
"replacement complete",
|
||||
] {
|
||||
for context in ["replacement DOMContentLoaded", "replacement complete"] {
|
||||
expect_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
@@ -14088,13 +14080,12 @@ async fn child_static_media_delays_complete_and_iframe_load_until_loadeddata() {
|
||||
"child media parser script should run before lifecycle",
|
||||
)
|
||||
.await;
|
||||
expect_page_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
&loader,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
"child media document should enter interactive and accept its media token",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
vm.eval("document.querySelector('iframe').contentDocument.readyState")
|
||||
.expect("child readiness"),
|
||||
"interactive",
|
||||
"parser EOF must apply interactive synchronously"
|
||||
);
|
||||
expect_page_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
&loader,
|
||||
@@ -14212,13 +14203,12 @@ async fn child_media_network_failure_releases_lifecycle_before_later_host_load()
|
||||
"child media parser script should install listeners",
|
||||
)
|
||||
.await;
|
||||
expect_page_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
&loader,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
"child failed media document should become interactive",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
vm.eval("document.querySelector('iframe').contentDocument.readyState")
|
||||
.expect("child readiness"),
|
||||
"interactive",
|
||||
"parser EOF must apply interactive synchronously"
|
||||
);
|
||||
let request = request_rx.await.expect("child media request should arrive");
|
||||
assert!(
|
||||
request
|
||||
@@ -14339,13 +14329,12 @@ async fn child_image_network_failure_releases_lifecycle_before_later_host_load()
|
||||
"child image parser script should install listeners",
|
||||
)
|
||||
.await;
|
||||
expect_page_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
&loader,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
"child image document should become interactive",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
vm.eval("document.querySelector('iframe').contentDocument.readyState")
|
||||
.expect("child readiness"),
|
||||
"interactive",
|
||||
"parser EOF must apply interactive synchronously"
|
||||
);
|
||||
let request = request_rx.await.expect("child image request should arrive");
|
||||
assert!(
|
||||
request
|
||||
@@ -14518,13 +14507,12 @@ async fn child_dynamic_media_accepted_during_dcl_delays_later_load_turns() {
|
||||
"dynamic media parser script should install its DCL producer",
|
||||
)
|
||||
.await;
|
||||
expect_page_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
&loader,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
"dynamic media document should enter interactive",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
vm.eval("document.querySelector('iframe').contentDocument.readyState")
|
||||
.expect("child readiness"),
|
||||
"interactive",
|
||||
"parser EOF must apply interactive synchronously"
|
||||
);
|
||||
expect_page_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
&loader,
|
||||
@@ -14617,13 +14605,12 @@ async fn child_static_text_track_starts_at_interactive_without_own_load_token()
|
||||
"child track parser script should install its listener",
|
||||
)
|
||||
.await;
|
||||
expect_page_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
&loader,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
"interactive should start the static child track",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
vm.eval("document.querySelector('iframe').contentDocument.readyState")
|
||||
.expect("child readiness"),
|
||||
"interactive",
|
||||
"parser EOF must apply interactive synchronously"
|
||||
);
|
||||
assert!(
|
||||
vm.run_one_dom_manipulation_task_executor_turn(
|
||||
PageDomManipulationTestFamily::TextTrackDefaultMode,
|
||||
@@ -14746,12 +14733,12 @@ async fn child_document_replacement_retires_media_sequence_and_delay() {
|
||||
"first media document parser script should run",
|
||||
)
|
||||
.await;
|
||||
expect_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
"first media document should enter interactive",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
vm.eval("document.querySelector('iframe').contentDocument.readyState")
|
||||
.expect("child readiness"),
|
||||
"interactive",
|
||||
"parser EOF must apply interactive synchronously"
|
||||
);
|
||||
expect_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
@@ -14796,7 +14783,6 @@ async fn child_document_replacement_retires_media_sequence_and_delay() {
|
||||
)
|
||||
.await;
|
||||
for (source, transition) in [
|
||||
(ChildFrameSemanticTurnKind::DocumentLifecycle, "interactive"),
|
||||
(
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
"DOMContentLoaded",
|
||||
@@ -14868,13 +14854,12 @@ async fn moving_pending_child_media_restarts_under_the_new_document_owner() {
|
||||
"moving media parser script should run",
|
||||
)
|
||||
.await;
|
||||
expect_page_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
&loader,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
"moving media document should enter interactive",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
vm.eval("document.querySelector('iframe').contentDocument.readyState")
|
||||
.expect("child readiness"),
|
||||
"interactive",
|
||||
"parser EOF must apply interactive synchronously"
|
||||
);
|
||||
expect_page_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
&loader,
|
||||
@@ -15014,12 +14999,12 @@ async fn child_image_event_delay_blocks_complete_and_host_load_until_terminal()
|
||||
"child image parser script should run before lifecycle",
|
||||
)
|
||||
.await;
|
||||
expect_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
"child image document should enter interactive",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
vm.eval("document.querySelector('iframe').contentDocument.readyState")
|
||||
.expect("child readiness"),
|
||||
"interactive",
|
||||
"parser EOF must apply interactive synchronously"
|
||||
);
|
||||
expect_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
@@ -15117,12 +15102,12 @@ async fn child_document_replacement_cancels_stale_image_event_and_delay() {
|
||||
"first image document parser script should run",
|
||||
)
|
||||
.await;
|
||||
expect_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
"first image document should enter interactive",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
vm.eval("document.querySelector('iframe').contentDocument.readyState")
|
||||
.expect("child readiness"),
|
||||
"interactive",
|
||||
"parser EOF must apply interactive synchronously"
|
||||
);
|
||||
expect_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
@@ -15166,8 +15151,19 @@ async fn child_document_replacement_cancels_stale_image_event_and_delay() {
|
||||
"replacement parser script should run",
|
||||
)
|
||||
.await;
|
||||
assert!(
|
||||
vm.apply_next_image_load_event_body_for_test()
|
||||
.expect("stale image DOM task"),
|
||||
"the earlier image task must retire at the shared DOM FIFO head"
|
||||
);
|
||||
assert_eq!(
|
||||
vm.eval("__childImageReplacementEvents.join('|')")
|
||||
.expect("retired image trace"),
|
||||
"first-dcl",
|
||||
"the stale image task must not dispatch into the replacement"
|
||||
);
|
||||
|
||||
for (source, transition) in [
|
||||
(ChildFrameSemanticTurnKind::DocumentLifecycle, "interactive"),
|
||||
(
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
"DOMContentLoaded",
|
||||
@@ -15233,12 +15229,12 @@ async fn moving_pending_child_image_rebinds_event_without_consuming_new_request(
|
||||
"moving image parser script should run",
|
||||
)
|
||||
.await;
|
||||
expect_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
"moving image document should enter interactive",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
vm.eval("document.querySelector('iframe').contentDocument.readyState")
|
||||
.expect("child readiness"),
|
||||
"interactive",
|
||||
"parser EOF must apply interactive synchronously"
|
||||
);
|
||||
expect_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
@@ -15266,6 +15262,17 @@ async fn moving_pending_child_image_rebinds_event_without_consuming_new_request(
|
||||
"#,
|
||||
)
|
||||
.expect("pending child image should move to the parent document");
|
||||
assert!(
|
||||
vm.apply_next_image_load_event_body_for_test()
|
||||
.expect("old image DOM task"),
|
||||
"the old image task must retire before the later complete task at the shared FIFO head"
|
||||
);
|
||||
assert_eq!(
|
||||
vm.eval("__movedChildImageEvents.join('|')")
|
||||
.expect("old image trace"),
|
||||
"child-dcl",
|
||||
"retiring the old image task must leave the rebound request pending"
|
||||
);
|
||||
expect_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
@@ -16035,12 +16042,12 @@ async fn child_document_close_without_defer_queues_replacement_domcontentloaded(
|
||||
"child parser script should install the original DCL handler",
|
||||
)
|
||||
.await;
|
||||
expect_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
"original child document should become interactive",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
vm.eval("document.querySelector('iframe').contentDocument.readyState")
|
||||
.expect("child readiness"),
|
||||
"interactive",
|
||||
"parser EOF must apply interactive synchronously"
|
||||
);
|
||||
expect_child_frame_task_source_after_realm_prerequisite(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
@@ -16150,10 +16157,16 @@ async fn child_script_document_open_after_location_navigation_is_noop() {
|
||||
.await,
|
||||
"the blob URL assigned by the child script must commit on a later navigation turn"
|
||||
);
|
||||
expect_one_child_frame_task_source(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::RealmMaterialization,
|
||||
"blob replacement must materialize its exact realm",
|
||||
)
|
||||
.await;
|
||||
expect_one_child_frame_task_source(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
"the queued srcdoc lifecycle task must stale-discard before blob lifecycle work",
|
||||
"the older srcdoc DCL must stale-discard at the shared DOM head",
|
||||
)
|
||||
.await;
|
||||
for transition in ["interactive", "DOMContentLoaded", "complete"] {
|
||||
|
||||
@@ -4752,11 +4752,16 @@ async fn run_child_document_lifecycle_and_host_load_for_test(vm: &mut ScriptVm,
|
||||
// Only consecutive materialization tasks at the stable family head
|
||||
// belong here. Never jump over an earlier DocumentScriptReady task.
|
||||
}
|
||||
assert!(
|
||||
vm.run_child_frame_task_source_once_for_test(ChildFrameSemanticTurnKind::DocumentLifecycle)
|
||||
if matches!(vm._page_task_residence_for_executor_test.as_ref().expect("child fixture sources").task_sources().next_child_frame_task_target(), Some(crate::page_task_queue::RendererPageChildFrameTaskTarget::DocumentLifecycle(target)) if matches!(target.action(), crate::frame_owner_model::FrameDocumentLifecycleAction::Interactive(_)))
|
||||
{
|
||||
assert!(
|
||||
vm.run_child_frame_task_source_once_for_test(
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle
|
||||
)
|
||||
.await,
|
||||
"{message}: DocumentLifecycle should make the installed document interactive"
|
||||
);
|
||||
"{message}: a child without a parser realm must apply its queued interactive transition"
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
vm.run_child_frame_task_source_once_for_test(ChildFrameSemanticTurnKind::DocumentLifecycle)
|
||||
.await,
|
||||
@@ -7147,7 +7152,7 @@ async fn child_srcdoc_inline_classic_script_runs_as_frame_script_job() {
|
||||
"child:true|current:inline-classic",
|
||||
"DocumentScriptReady should execute the inline classic script without firing iframe load"
|
||||
);
|
||||
for transition in ["interactive", "DOMContentLoaded", "complete"] {
|
||||
for transition in ["DOMContentLoaded", "complete"] {
|
||||
assert!(
|
||||
vm.run_child_frame_task_source_once_for_test(
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle
|
||||
@@ -7264,7 +7269,7 @@ async fn child_inline_classic_script_moved_from_original_document_is_skipped() {
|
||||
"after:after-stale-inline",
|
||||
"parser continuation should run the next inline classic script without firing iframe load"
|
||||
);
|
||||
for transition in ["interactive", "DOMContentLoaded", "complete"] {
|
||||
for transition in ["DOMContentLoaded", "complete"] {
|
||||
run_realm_prerequisite_then_expected_child_frame_semantic_turn_for_test(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
@@ -7593,7 +7598,7 @@ async fn child_external_classic_script_load_executes_as_frame_script_job() {
|
||||
"external:true|external-current:external-classic|external-write:true|script-load|inline-current:after-external-classic|inline:73",
|
||||
"ignored document.open() must leave the parser owner alive for the following inline script"
|
||||
);
|
||||
for transition in ["interactive", "DOMContentLoaded", "complete"] {
|
||||
for transition in ["DOMContentLoaded", "complete"] {
|
||||
run_page_realm_prerequisite_then_expected_child_frame_semantic_turn(
|
||||
&mut vm,
|
||||
&loader,
|
||||
@@ -8935,10 +8940,11 @@ async fn child_inline_parser_module_executes_from_registered_pending_script() {
|
||||
"parser should continue past the deferred inline module after graph start",
|
||||
)
|
||||
.await;
|
||||
assert!(
|
||||
vm.run_child_frame_task_source_once_for_test(ChildFrameSemanticTurnKind::DocumentLifecycle)
|
||||
.await,
|
||||
"parser EOF should make the child document interactive before defer execution"
|
||||
assert_eq!(
|
||||
vm.eval("document.querySelector('iframe').contentDocument.readyState")
|
||||
.expect("child readiness"),
|
||||
"interactive",
|
||||
"parser EOF must apply interactive synchronously"
|
||||
);
|
||||
assert!(
|
||||
vm.run_child_frame_task_source_once_for_test(
|
||||
@@ -9048,13 +9054,12 @@ async fn child_external_parser_module_executes_from_document_ready_lane() {
|
||||
"parser should continue past module-defer script from DocumentScriptReady after fetch start",
|
||||
)
|
||||
.await;
|
||||
run_page_realm_prerequisite_then_expected_child_frame_semantic_turn(
|
||||
&mut vm,
|
||||
&loader,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
"child parser module should enter interactive before module-defer execution",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
vm.eval("document.querySelector('iframe').contentDocument.readyState")
|
||||
.expect("child readiness"),
|
||||
"interactive",
|
||||
"parser EOF must apply interactive synchronously"
|
||||
);
|
||||
assert!(
|
||||
!vm.run_one_child_frame_task_executor_turn(ChildFrameSemanticTurnKind::HostLoad, &loader)
|
||||
.await
|
||||
@@ -9194,7 +9199,7 @@ async fn child_dynamic_import_root_fetch_uses_child_import_map_and_initiator_url
|
||||
.expect("child dynamic import owner ready flag should evaluate"),
|
||||
"true"
|
||||
);
|
||||
for transition in ["interactive", "DOMContentLoaded", "complete"] {
|
||||
for transition in ["DOMContentLoaded", "complete"] {
|
||||
run_realm_prerequisite_then_expected_child_frame_semantic_turn_for_test(
|
||||
&mut vm,
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
@@ -9451,14 +9456,11 @@ async fn child_external_classic_source_error_dispatches_before_later_inline() {
|
||||
"script-error|after-inline",
|
||||
"parser continuation should execute the following inline script without firing iframe load"
|
||||
);
|
||||
assert!(
|
||||
vm.run_one_child_frame_task_executor_turn(
|
||||
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
||||
&loader,
|
||||
)
|
||||
.await
|
||||
.expect("child interactive task should use the selected-task dispatcher"),
|
||||
"parser EOF should dispatch interactive before HostLoad"
|
||||
assert_eq!(
|
||||
vm.eval("document.querySelector('iframe').contentDocument.readyState")
|
||||
.expect("child readiness"),
|
||||
"interactive",
|
||||
"parser EOF must apply interactive synchronously"
|
||||
);
|
||||
assert!(
|
||||
vm.run_one_child_frame_task_executor_turn(
|
||||
@@ -9596,10 +9598,11 @@ async fn child_inline_classic_throw_reports_to_child_window_and_continues() {
|
||||
"child-error:Uncaught Error: child-boom:child-boom:true|after-inline:true",
|
||||
"parser continuation should run the following inline script without firing iframe load"
|
||||
);
|
||||
assert!(
|
||||
vm.run_child_frame_task_source_once_for_test(ChildFrameSemanticTurnKind::DocumentLifecycle)
|
||||
.await,
|
||||
"parser EOF should dispatch interactive before HostLoad"
|
||||
assert_eq!(
|
||||
vm.eval("document.querySelector('iframe').contentDocument.readyState")
|
||||
.expect("child readiness"),
|
||||
"interactive",
|
||||
"parser EOF must apply interactive synchronously"
|
||||
);
|
||||
assert!(
|
||||
vm.run_child_frame_task_source_once_for_test(ChildFrameSemanticTurnKind::DocumentLifecycle)
|
||||
|
||||
@@ -128,7 +128,8 @@ addEventListener("error", () => {
|
||||
base_url: document_url.clone(),
|
||||
initiator_url: document_url,
|
||||
},
|
||||
);
|
||||
)
|
||||
.expect("parser import-map handoff should be admitted");
|
||||
|
||||
assert_eq!(
|
||||
vm.eval_without_microtask_checkpoint_for_test("__importMapFailureOrder.join('|')")
|
||||
@@ -182,7 +183,8 @@ fn parser_import_map_registration_obeys_inline_script_csp() {
|
||||
base_url: document_url.clone(),
|
||||
initiator_url: document_url,
|
||||
},
|
||||
);
|
||||
)
|
||||
.expect("parser import-map handoff should be admitted");
|
||||
}
|
||||
|
||||
let base_url = Url::parse("https://script-terminal.test/import-map.html").unwrap();
|
||||
|
||||
Reference in New Issue
Block a user