diff --git a/moli-renderer-v8/src/context_bootstrap.rs b/moli-renderer-v8/src/context_bootstrap.rs index d256005acd..b6c9495837 100644 --- a/moli-renderer-v8/src/context_bootstrap.rs +++ b/moli-renderer-v8/src/context_bootstrap.rs @@ -157,6 +157,7 @@ pub(crate) use location_navigation::{ dispatch_top_level_navigation_event_with_source_element, is_native_location, meta_refresh_navigation_kind, navigate_borrowed_location, navigate_location_object_with_child_navigate_event, + navigate_location_object_with_child_navigate_event_and_initiator_url, navigate_location_object_with_source_element, navigate_top_level_meta_refresh, navigate_top_level_same_document_from_browser, resolve_cross_window_location_target, sandbox_blocks_ancestor_or_top_navigation_from_source, diff --git a/moli-renderer-v8/src/context_bootstrap/location_navigation.rs b/moli-renderer-v8/src/context_bootstrap/location_navigation.rs index d9c22ea544..1d6cfc8424 100644 --- a/moli-renderer-v8/src/context_bootstrap/location_navigation.rs +++ b/moli-renderer-v8/src/context_bootstrap/location_navigation.rs @@ -77,6 +77,13 @@ pub(super) enum NavigationNavigateHistoryKind { Replace, } +#[derive(Default)] +struct LocationNavigationOptions { + dispatch_child_navigate_event_for_all_kinds: bool, + force_exact_same_document_navigation: bool, + explicit_initiator_url: Option, +} + pub(crate) fn navigate_location_object<'s>( scope: &mut v8::PinScope<'s, '_>, location: v8::Local<'s, v8::Object>, @@ -84,7 +91,12 @@ pub(crate) fn navigate_location_object<'s>( raw_target: Option, ) { navigate_location_object_with_source_element_and_child_navigate_event( - scope, location, kind, raw_target, None, false, false, + scope, + location, + kind, + raw_target, + None, + LocationNavigationOptions::default(), ); } @@ -131,8 +143,10 @@ pub(crate) fn navigate_top_level_same_document_from_browser( kind, Some(target), None, - false, - true, + LocationNavigationOptions { + force_exact_same_document_navigation: true, + ..LocationNavigationOptions::default() + }, ); true } @@ -186,8 +200,10 @@ pub(crate) fn navigate_top_level_meta_refresh( kind, Some(target.to_string()), None, - false, - same_document_fragment, + LocationNavigationOptions { + force_exact_same_document_navigation: same_document_fragment, + ..LocationNavigationOptions::default() + }, ); context_host_ptr_from_global_bridge(scope) .is_some_and(|host_ptr| unsafe { &*host_ptr }.has_pending_location_navigation()) @@ -208,8 +224,7 @@ pub(crate) fn navigate_location_object_with_source_element<'s>( kind, raw_target, source_element, - false, - false, + LocationNavigationOptions::default(), ); } @@ -220,7 +235,36 @@ pub(crate) fn navigate_location_object_with_child_navigate_event<'s>( raw_target: Option, ) { navigate_location_object_with_source_element_and_child_navigate_event( - scope, location, kind, raw_target, None, true, false, + scope, + location, + kind, + raw_target, + None, + LocationNavigationOptions { + dispatch_child_navigate_event_for_all_kinds: true, + ..LocationNavigationOptions::default() + }, + ); +} + +pub(crate) fn navigate_location_object_with_child_navigate_event_and_initiator_url<'s>( + scope: &mut v8::PinScope<'s, '_>, + location: v8::Local<'s, v8::Object>, + kind: LocationNavigationKind, + raw_target: Option, + initiator_url: url::Url, +) { + navigate_location_object_with_source_element_and_child_navigate_event( + scope, + location, + kind, + raw_target, + None, + LocationNavigationOptions { + dispatch_child_navigate_event_for_all_kinds: true, + explicit_initiator_url: Some(initiator_url), + ..LocationNavigationOptions::default() + }, ); } @@ -230,8 +274,7 @@ fn navigate_location_object_with_source_element_and_child_navigate_event<'s>( kind: LocationNavigationKind, raw_target: Option, source_element: Option>, - dispatch_child_navigate_event_for_all_kinds: bool, - force_exact_same_document_navigation: bool, + options: LocationNavigationOptions, ) { let owner = runtime_window_owner(scope, location); if let Some(context) = location.get_creation_context(scope) @@ -246,6 +289,11 @@ fn navigate_location_object_with_source_element_and_child_navigate_event<'s>( } } let location = window_location_for_holder(scope, owner).unwrap_or(location); + let LocationNavigationOptions { + dispatch_child_navigate_event_for_all_kinds, + force_exact_same_document_navigation, + explicit_initiator_url, + } = options; let current_href = location_href_slot(scope, location).unwrap_or_default(); let current_url = url::Url::parse(¤t_href).ok(); let raw_target_is_fragment_only = raw_target @@ -619,7 +667,8 @@ fn navigate_location_object_with_source_element_and_child_navigate_event<'s>( } if let Some(host_ptr) = host_ptr { let host = unsafe { &mut *host_ptr }; - let initiator_url = location_navigation_initiator_url(scope, host); + let initiator_url = + explicit_initiator_url.or_else(|| location_navigation_initiator_url(scope, host)); if matches!(kind, LocationNavigationKind::Reload) { host.queue_child_browsing_context_reload_from_existing_seed( handle, 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 33747f9235..45961e93d5 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 @@ -2,7 +2,8 @@ use super::{FrameDocumentLoadDeliveryTask, JsContextHost}; use crate::{ context_bootstrap::{ LocationNavigationKind, construct_original_event, construct_original_page_transition_event, - meta_refresh_navigation_kind, navigate_location_object_with_child_navigate_event, + meta_refresh_navigation_kind, + navigate_location_object_with_child_navigate_event_and_initiator_url, record_performance_load_event_end_for_window, record_performance_load_event_start_for_window, }, @@ -794,6 +795,9 @@ fn child_meta_refresh_callback<'s>( if host.current_child_document_task_owner(handle) != Some(task.owner) { return; } + let Some(initiator_url) = host.child_browsing_context_current_url(handle) else { + return; + }; let Some(window) = host.child_browsing_context_window_wrapper(scope, handle) else { return; }; @@ -803,11 +807,12 @@ fn child_meta_refresh_callback<'s>( else { return; }; - navigate_location_object_with_child_navigate_event( + navigate_location_object_with_child_navigate_event_and_initiator_url( scope, location, task.navigation_kind, Some(task.target_url.to_string()), + initiator_url, ); } 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 b169b4d2b3..fef1ac1922 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 @@ -3697,3 +3697,76 @@ async fn parent_location_assignment_uses_parent_document_as_navigation_referrer( assert_eq!(requests[1].header_value("referer"), Some(top_url.as_str())); assert_eq!(document_referrer, top_url.as_str()); } + +#[tokio::test(flavor = "current_thread")] +async fn child_meta_refresh_after_same_document_navigation_uses_child_referrer() { + const HOST: &str = "child-meta-refresh-referrer.test"; + + let server = StaticHttpServer::spawn_with_bodies(vec![ + r#" + +"# + .to_owned(), + "refresh target".to_owned(), + ]) + .await; + let top_url = server.url_for_host(HOST, "/refresh/parent.html"); + let child_url = server.url_for_host(HOST, "/refresh/source.html"); + let target_url = server.url_for_host(HOST, "/refresh/target.html"); + let loader = static_http_loader([server.resolve_entry(HOST)]); + let mut vm = new_storage_page_task_executor_test_vm_with_loader(top_url.as_str(), &loader); + + vm.eval(&format!( + r#" +(() => {{ + globalThis.__childMetaRefreshReferrerLoadCount = 0; + const frame = document.createElement('iframe'); + frame.src = {}; + frame.onload = () => {{ + globalThis.__childMetaRefreshReferrerLoadCount++; + }}; + (document.body || document.documentElement || document).appendChild(frame); +}})() +"#, + serde_json::to_string(child_url.as_str()).expect("serialize meta refresh child URL") + )) + .expect("child meta refresh referrer setup should evaluate"); + advance_page_task_executor_until_eval_equals( + &mut vm, + &loader, + "String(__childMetaRefreshReferrerLoadCount)", + "1", + "initial meta refresh child document should load", + ) + .await; + + advance_page_task_executor_until_eval_equals( + &mut vm, + &loader, + "String(__childMetaRefreshReferrerLoadCount)", + "2", + "child meta refresh navigation should load", + ) + .await; + + let state = vm + .eval( + r#" +(() => { + const frame = document.querySelector('iframe'); + return [frame.contentWindow.location.href, frame.contentDocument.referrer].join('|'); +})() +"#, + ) + .expect("child meta refresh referrer state should evaluate"); + assert_eq!(state, format!("{target_url}|{child_url}")); + + let requests = server.finish().await; + assert_eq!(requests[0].target, "/refresh/source.html"); + assert_eq!(requests[0].header_value("referer"), Some(top_url.as_str())); + assert_eq!(requests[1].target, "/refresh/target.html"); + assert_eq!( + requests[1].header_value("referer"), + Some(child_url.as_str()) + ); +}