fix(runtime): admit document lifecycle during awaited evaluation

This commit is contained in:
ldm0
2026-09-27 06:33:27 +08:00
parent c2c5ce0da0
commit 2cbc7db2ef
2 changed files with 64 additions and 2 deletions
+37
View File
@@ -6711,3 +6711,40 @@ async fn parse_time_lifecycle_queue_can_stop_cleanly_at_load_stage_after_load_mi
#[path = "scripts/document_write_errors.rs"]
mod document_write_errors;
#[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 url = url::Url::parse(&server.url("/compat/child-dynamic-markup-document"))?;
url.query_pairs_mut()
.append_pair("markup", "<!doctype html><body>initial");
let mut page = browser.fetch(url.as_str()).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(())
}
@@ -592,7 +592,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,
@@ -601,7 +604,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