fix(document): initialize child navigation readiness

This commit is contained in:
ldm0
2026-09-27 06:43:21 +08:00
parent 6fcfd2c32f
commit cf7e6e8b3e
5 changed files with 108 additions and 5 deletions
+3
View File
@@ -22,6 +22,9 @@ mod preparation_errors;
#[path = "scripts/child_error_insertion.rs"]
mod child_error_insertion;
#[path = "scripts/child_readiness.rs"]
mod child_readiness;
fn diagnostic_global<'a>(
page: &'a moli_core::page::Page,
name: &str,
@@ -28,7 +28,7 @@ async fn child_script_error_write_probe(
parent.childErrorObserved({text: document.body.textContent,
currentScriptIsNull: document.currentScript === null,
tailMissing: document.getElementById('tail') === null,
eventType: event.type,
readyState: document.readyState, eventType: event.type,
nestedRan: globalThis.nestedRan === true});
}`;
frame.srcdoc = '<!doctype html><head><script>' + handler + '<' + '/script></head>' +
@@ -73,3 +73,10 @@ async fn child_parser_fetch_error_write_executes_nested_inline_scripts() -> Resu
assert_eq!(result["nestedRan"], true, "{result}");
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn child_parser_fetch_error_handler_observes_loading_document() -> Result<()> {
let result = child_script_error_write_probe(false, false).await?;
assert_eq!(result["during"]["readyState"], "loading", "{result}");
Ok(())
}
@@ -0,0 +1,86 @@
use super::*;
#[tokio::test(flavor = "multi_thread")]
async fn child_document_readiness_starts_loading_before_parser_scripts() -> Result<()> {
let server = FixtureServer::spawn().await?;
let browser = Browser::new(AppConfig::default())?;
let mut page = browser.fetch(&server.url("/static")).await?;
let result = page
.evaluate_runtime_expression_with_await_async(
r#"(async () => {
const srcdoc = await new Promise(resolve => {
const frame = document.createElement('iframe');
frame.onload = () => {
resolve({states: frame.contentWindow.readyStates,
readyState: frame.contentDocument.readyState});
frame.remove();
};
frame.srcdoc = '<!doctype html><head><script>' +
'window.readyStates = [document.readyState];' +
'document.addEventListener("readystatechange", () => readyStates.push(document.readyState));' +
'<' + '/script></head><body>ready';
document.body.append(frame);
});
const network = await new Promise(resolve => {
const frame = document.createElement('iframe');
frame.onload = () => {
resolve({duringScriptLoad: frame.contentWindow.parserConnectedLoadWriteReadyState,
readyState: frame.contentDocument.readyState});
frame.remove();
};
frame.src = '/compat/parser-connected-external-classic-load-document-write-insertion-point';
document.body.append(frame);
});
return JSON.stringify({srcdoc, network, parent: document.readyState});
})()"#,
true,
)
.await?;
server.shutdown().await;
let value: serde_json::Value =
serde_json::from_str(result["value"].as_str().expect("child readiness probe"))?;
assert_eq!(
value,
serde_json::json!({
"srcdoc": {"states": ["loading", "interactive", "complete"], "readyState": "complete"},
"network": {"duringScriptLoad": "loading", "readyState": "complete"},
"parent": "complete"
})
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn child_document_readiness_keeps_initial_empty_and_detached_documents_complete() -> Result<()>
{
let server = FixtureServer::spawn().await?;
let browser = Browser::new(AppConfig::default())?;
let mut page = browser.fetch(&server.url("/static")).await?;
let result = page
.evaluate_runtime_expression_with_await_async(
r#"(() => {
const frame = document.createElement('iframe');
document.body.append(frame);
const initialEmpty = frame.contentDocument.readyState;
frame.remove();
return JSON.stringify({initialEmpty,
detachedHtml: new DOMParser().parseFromString('<p>detached', 'text/html').readyState,
detachedXml: new DOMParser().parseFromString('<detached/>', 'application/xml').readyState,
createdHtml: document.implementation.createHTMLDocument('').readyState,
constructed: new Document().readyState});
})()"#,
true,
)
.await?;
server.shutdown().await;
let value: serde_json::Value =
serde_json::from_str(result["value"].as_str().expect("detached readiness probe"))?;
assert_eq!(
value,
serde_json::json!({
"initialEmpty": "complete", "detachedHtml": "complete", "detachedXml": "complete",
"createdHtml": "complete", "constructed": "complete"
})
);
Ok(())
}
@@ -5,7 +5,10 @@ use crate::frame_owner_model::{
DocumentCreationKind, FrameDocumentInteractiveLifecycleAction,
FrameDocumentLocalWindowTransition, FrameLocalWindowOwnerTransition,
};
use crate::{document_runtime::DomHandle, frame_owner_model::FrameDocumentOwnerTransition};
use crate::{
document_runtime::DomHandle, dom::native::DocumentReadyState,
frame_owner_model::FrameDocumentOwnerTransition,
};
use moli_web_mime::is_dom_parser_xml_mime;
use url::Url;
@@ -200,6 +203,10 @@ impl JsContextHost {
);
document_handle
};
// Navigation-created Documents start loading before parser scripts can
// observe them. Keep the generic detached/initial-empty default complete.
let _ = self
.set_dom_document_ready_state_for_handle(document_handle, DocumentReadyState::Loading);
let document_url = self.document_url_for_handle(document_handle);
let document_base_url = self.document_base_url_for_handle(document_handle);
let parser_document_url = document_url.clone();
@@ -3674,8 +3674,8 @@ async fn page_vm_child_parser_blocking_classic_waits_for_preceding_stylesheet()
page_vm
.vm_mut()
.eval("__childParserStylesheetEvents.join('|')")?,
"script:stylesheet-ready:complete",
"stylesheet source must be installed before the parser-blocking script executes"
"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(
@@ -3752,7 +3752,7 @@ async fn page_vm_child_parser_blocking_classic_waits_for_preceding_stylesheet()
ChildFrameSemanticTurnKind::HostLoad,
]
);
assert_eq!(final_events, "script:stylesheet-ready:complete|load");
assert_eq!(final_events, "script:stylesheet-ready:loading|load");
server.await.expect("child parser stylesheet server should finish");
})
.await;