diff --git a/moli-renderer-v8/src/native_bridge/context_host/host_loads.rs b/moli-renderer-v8/src/native_bridge/context_host/host_loads.rs index 283a349531..6cc269a2bf 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/host_loads.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/host_loads.rs @@ -408,6 +408,9 @@ impl JsContextHost { self.abort_and_requeue_child_load_delivery(action); return ChildFrameLoadDeliveryPhaseResult::without_callback(None); }; + // Capture the original document's refresh before load callbacks can + // move or remove its meta element. + self.queue_child_meta_refresh_navigation_if_needed(scope, handle); self.enter_child_browsing_context_host_load_dispatch(handle); self.dispatch_child_window_event(scope, handle, "load", event); self.leave_child_browsing_context_host_load_dispatch(handle); @@ -450,7 +453,6 @@ impl JsContextHost { self.trace_stale_child_load_delivery(action, "owner_element_load"); return ChildFrameLoadDeliveryPhaseResult::after_callback(None); } - self.queue_child_meta_refresh_navigation_if_needed(scope, handle); ChildFrameLoadDeliveryPhaseResult::after_callback(progress) } diff --git a/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface/extracted/frame_navigation_and_history.rs b/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface/extracted/frame_navigation_and_history.rs index dd68ece10c..e8fd18bee9 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface/extracted/frame_navigation_and_history.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface/extracted/frame_navigation_and_history.rs @@ -1358,6 +1358,68 @@ 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_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); + vm.eval( + r#" +globalThis.sourceLoads = 0; +globalThis.destinationLoads = 0; +const root = document.documentElement || document.appendChild(document.createElement('html')); +const source = document.createElement('iframe'); +const destination = document.createElement('iframe'); +destination.setAttribute('sandbox', 'allow-same-origin'); +function adoptMetaWhenBothFramesHaveLoaded() { + if (sourceLoads === 1 && destinationLoads === 1) { + destination.contentDocument.body.appendChild(source.contentDocument.querySelector('meta')); + } +} +source.onload = () => { ++sourceLoads; adoptMetaWhenBothFramesHaveLoaded(); }; +destination.onload = () => { ++destinationLoads; adoptMetaWhenBothFramesHaveLoaded(); }; +source.srcdoc = '

source

'; +destination.srcdoc = '

destination

'; +'prepared' +"#, + ) + .expect("meta adoption setup should evaluate"); + let (first, second) = if source_loads_first { + ("source", "destination") + } else { + ("destination", "source") + }; + vm.eval(&format!("root.appendChild({first}); 'queued'")) + .expect("first frame should attach"); + vm.drain_pending_child_frame_work_for_test(); + vm.eval(&format!("root.appendChild({second}); 'queued'")) + .expect("second frame should attach"); + vm.drain_pending_child_frame_work_for_test(); + assert_eq!( + vm.eval("String(destination.contentDocument.querySelectorAll('meta').length)") + .unwrap(), + "1", + "the load callback should adopt the refresh element into the sandboxed document", + ); + vm.advance_timers_until_deadline_for_test(&loader) + .await + .expect("the original document's scheduled refresh should run"); + assert_eq!( + vm.eval("JSON.stringify([source.contentWindow.location.href, destination.contentWindow.location.href, sourceLoads, destinationLoads])") + .unwrap(), + r#"["about:srcdoc#refreshed","about:srcdoc",1,1]"#, + "adoption must preserve the original refresh and must not refresh the sandboxed destination", + ); +} + +#[tokio::test] +async fn child_meta_refresh_survives_adoption_when_source_loads_last() { + assert_child_meta_refresh_survives_adoption_during_load(false).await; +} + +#[tokio::test] +async fn child_meta_refresh_survives_adoption_when_destination_loads_last() { + assert_child_meta_refresh_survives_adoption_during_load(true).await; +} + #[tokio::test] async fn child_meta_refresh_timer_is_canceled_when_frame_reloads() { let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).expect("loader");