fix(runtime): admit document lifecycle during awaited evaluation

This commit is contained in:
ldm0
2026-10-11 18:06:40 +08:00
parent 2997a392dc
commit 75fb70710c
2 changed files with 63 additions and 2 deletions
+36
View File
@@ -122,3 +122,39 @@ async fn child_document_close_in_written_script_drains_tail_and_finishes_load()
async fn child_document_close_in_external_writer_drains_tail_and_finishes_load() -> Result<()> {
written_child_script_close_finishes_load(false, false, true).await
}
#[tokio::test(flavor = "multi_thread")]
async fn main_document_close_can_resolve_an_awaited_lifecycle_promise() -> Result<()> {
let server = FixtureServer::spawn().await?;
let browser = Browser::new(AppConfig::default())?;
let mut page = browser
.fetch(&markup_url(&server, "<!doctype html><body>initial"))
.await?;
let result = tokio::time::timeout(
Duration::from_secs(10),
page.evaluate_runtime_expression_with_await_async(
r#"new Promise(resolve => {
document.open();
document.addEventListener('DOMContentLoaded', () => resolve(JSON.stringify({
readyState: document.readyState,
text: document.getElementById('replacement').textContent
})), {once: true});
document.write('<!doctype html><body><p id=replacement>replacement</p>');
document.close();
})"#,
true,
),
)
.await??;
let observed: serde_json::Value = serde_json::from_str(
result["value"]
.as_str()
.expect("replacement lifecycle observation"),
)?;
assert_eq!(
observed,
serde_json::json!({"readyState": "interactive", "text": "replacement"})
);
server.shutdown().await;
Ok(())
}
@@ -623,7 +623,10 @@ pub(in crate::runtime) async fn advance_runtime_expression_await_turn_on_entry_v
) -> (LivePageEntry, Result<PageVmRuntimeExpressionAwaitAdvance>) {
run_entry_on_bound_owner_local_store_local_task(local_executor, entry, move |entry| {
Box::pin(async move {
entry
let replacement_lifecycle_snapshot = entry
.page_vm()
.document_replacement_lifecycle_action_snapshot();
let evaluation = entry
.page_vm_mut()
.advance_runtime_expression_await_turn(
execution_context_id,
@@ -632,7 +635,29 @@ pub(in crate::runtime) async fn advance_runtime_expression_await_turn_on_entry_v
remaining,
result_mode,
)
.await
.await;
// An awaited expression can synchronously open and close a new
// Document, then return a promise resolved by its DCL/load tasks.
// Admit those tasks at this action boundary, before awaiting the
// result, including when evaluation reports an error after mutation.
let reconciliation = {
let (page_vm, pending_document_lifecycle_turn) =
entry.page_vm_and_document_lifecycle_turn_mut();
page_vm
.reconcile_document_replacement_lifecycle_after_owner_action(
replacement_lifecycle_snapshot,
pending_document_lifecycle_turn,
)
.await
};
match (evaluation, reconciliation) {
(Ok(outcome), Ok(_)) => Ok(outcome),
(Err(evaluation_error), Ok(_)) => Err(evaluation_error),
(Ok(_), Err(reconciliation_error)) => Err(reconciliation_error),
(Err(evaluation_error), Err(reconciliation_error)) => Err(anyhow!(
"runtime evaluation failed ({evaluation_error:#}) and its Document replacement lifecycle reconciliation also failed ({reconciliation_error:#})"
)),
}
})
})
.await