fix(html): retain meta refresh inserted by load callbacks

Keep the pre-load registration that preserves refreshes across meta adoption,
and also scan after owner load callbacks when no refresh is armed. This retains
support for callbacks that insert a new refresh without canceling the original
document's refresh when its element was moved or removed.

Add deterministic Window-load and iframe-load insertion tests. Both fail before
this fix, and the full Release WPT probe also exposed dynamic-append.html timing
out at 7e79f9c154a; that incomplete validation was stopped before Cases refresh.

Fixes: 7e79f9c154 (fix(html): arm child meta refresh before load callbacks)

Validation:
- cargo fmt --all
- cargo clippy --workspace --all-targets --all-features -- -D warnings
- cargo nextest run --no-fail-fast
This commit is contained in:
ldm0
2026-10-18 00:43:58 +08:00
parent 7e79f9c154
commit bec88e11cf
2 changed files with 60 additions and 0 deletions
@@ -453,6 +453,11 @@ impl JsContextHost {
self.trace_stale_child_load_delivery(action, "owner_element_load");
return ChildFrameLoadDeliveryPhaseResult::after_callback(None);
}
// Load callbacks may insert a new refresh, but must not cancel an
// existing document-owned refresh by moving its meta element away.
if !self.child_meta_refresh_navigations.contains_key(&handle) {
self.queue_child_meta_refresh_navigation_if_needed(scope, handle);
}
ChildFrameLoadDeliveryPhaseResult::after_callback(progress)
}
@@ -1358,6 +1358,61 @@ async fn detached_child_navigation_error_exposes_committed_entry_during_dispatch
"committed:true|error:true:true:AbortError|finished-rejected:AbortError||true"
);
}
async fn assert_child_meta_refresh_can_be_inserted_during_load(in_window_load: bool) {
let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).expect("loader");
let mut vm = new_storage_test_vm_with_loader("https://child-meta-refresh.test/", &loader);
vm.eval(&format!(
"globalThis.insertInWindowLoad = {in_window_load};"
))
.unwrap();
vm.eval(
r#"
const root = document.documentElement || document.appendChild(document.createElement('html'));
const frame = document.createElement('iframe');
frame.onload = () => {
if (!insertInWindowLoad) {
frame.contentDocument.body.innerHTML = '<meta http-equiv="refresh" content="0;url=about:srcdoc#dynamic">';
}
};
frame.srcdoc = `<script>
onload = () => {
if (parent.insertInWindowLoad) {
document.body.innerHTML = '<meta http-equiv="refresh" content="0;url=about:srcdoc#dynamic">';
}
};
<\/script><p>source</p>`;
root.appendChild(frame);
'queued'
"#,
)
.expect("dynamic child refresh setup should evaluate");
vm.drain_pending_child_frame_work_for_test();
assert_eq!(
vm.eval("String(frame.contentDocument.querySelectorAll('meta').length)")
.unwrap(),
"1",
"the load callback should insert the refresh element",
);
vm.advance_timers_until_deadline_for_test(&loader)
.await
.expect("dynamically inserted refresh should run");
assert_eq!(
vm.eval("frame.contentWindow.location.href").unwrap(),
"about:srcdoc#dynamic",
"a refresh inserted by a load callback must be scheduled",
);
}
#[tokio::test]
async fn child_meta_refresh_can_be_inserted_in_window_load_callback() {
assert_child_meta_refresh_can_be_inserted_during_load(true).await;
}
#[tokio::test]
async fn child_meta_refresh_can_be_inserted_in_owner_load_callback() {
assert_child_meta_refresh_can_be_inserted_during_load(false).await;
}
async fn assert_child_meta_refresh_survives_adoption_during_load(source_loads_first: bool) {
let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).expect("loader");
let mut vm = new_storage_test_vm_with_loader("https://child-meta-refresh.test/", &loader);