diff --git a/moli-renderer-v8/src/context_bootstrap.rs b/moli-renderer-v8/src/context_bootstrap.rs index 0130f7ffaf..b6ec24e18b 100644 --- a/moli-renderer-v8/src/context_bootstrap.rs +++ b/moli-renderer-v8/src/context_bootstrap.rs @@ -134,7 +134,7 @@ pub(crate) use css_fontface_runtime::{load_font_faces_for_family, rebuild_font_f pub(crate) use location_navigation::{ LocationNavigationKind, dispatch_top_level_form_navigation_event, dispatch_top_level_navigation_event_with_source_element, meta_refresh_navigation_kind, - 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, }; diff --git a/moli-renderer-v8/src/context_bootstrap/location_navigation.rs b/moli-renderer-v8/src/context_bootstrap/location_navigation.rs index 095222afeb..b33157c730 100644 --- a/moli-renderer-v8/src/context_bootstrap/location_navigation.rs +++ b/moli-renderer-v8/src/context_bootstrap/location_navigation.rs @@ -67,6 +67,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>, @@ -74,7 +81,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(), ); } @@ -118,8 +130,10 @@ pub(crate) fn navigate_top_level_same_document_from_browser( LocationNavigationKind::Assign, Some(target), None, - false, - true, + LocationNavigationOptions { + force_exact_same_document_navigation: true, + ..LocationNavigationOptions::default() + }, ); true } @@ -173,8 +187,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()) @@ -195,8 +211,7 @@ pub(crate) fn navigate_location_object_with_source_element<'s>( kind, raw_target, source_element, - false, - false, + LocationNavigationOptions::default(), ); } @@ -207,7 +222,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() + }, ); } @@ -217,9 +261,13 @@ 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 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 @@ -628,7 +676,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::Assign) && !is_javascript_url { host.mark_child_browsing_context_top_level_history_increment(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 1d0ddfdef4..5cb097d01e 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.rs b/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface.rs index ded97a8fb7..e099cf1c1c 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface.rs @@ -11332,6 +11332,79 @@ async fn child_location_function_uses_executing_document_as_navigation_referrer( assert_eq!(document_referrer, child_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()) + ); +} + #[tokio::test(flavor = "current_thread")] async fn parent_location_assignment_uses_parent_document_as_navigation_referrer() { const HOST: &str = "parent-location-referrer.test"; diff --git a/moli-renderer-v8/src/script_vm/tests/http_fixture.rs b/moli-renderer-v8/src/script_vm/tests/http_fixture.rs index 95b3aa369b..5a6052b092 100644 --- a/moli-renderer-v8/src/script_vm/tests/http_fixture.rs +++ b/moli-renderer-v8/src/script_vm/tests/http_fixture.rs @@ -36,15 +36,21 @@ pub(super) struct StaticHttpServer { impl StaticHttpServer { pub(super) async fn spawn(expected_requests: usize) -> Self { + const BODY: &str = "child fixture"; + Self::spawn_with_bodies(vec![BODY.to_owned(); expected_requests]).await + } + + pub(super) async fn spawn_with_bodies(response_bodies: Vec) -> Self { let listener = tokio::net::TcpListener::bind("127.0.0.1:0") .await .expect("bind static HTTP test server"); let address = listener .local_addr() .expect("read static HTTP test server address"); + let expected_requests = response_bodies.len(); let task = tokio::spawn(async move { let mut requests = Vec::with_capacity(expected_requests); - for _ in 0..expected_requests { + for body in response_bodies { let (mut socket, _) = listener .accept() .await @@ -119,12 +125,11 @@ impl StaticHttpServer { headers, }); - const BODY: &str = "child fixture"; socket .write_all( format!( - "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{BODY}", - BODY.len() + "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", + body.len() ) .as_bytes(), )