mirror of
https://github.com/lexmount/moli.git
synced 2026-10-07 00:01:00 +00:00
fix(refresh): use child document as navigation referrer
This commit is contained in:
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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<url::Url>,
|
||||
}
|
||||
|
||||
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<String>,
|
||||
) {
|
||||
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<String>,
|
||||
) {
|
||||
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<String>,
|
||||
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<String>,
|
||||
source_element: Option<v8::Local<'s, v8::Object>>,
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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#"<!doctype html>
|
||||
<script>location.hash = '#section'</script>
|
||||
<meta http-equiv="refresh" content="0; url=/refresh/target.html">"#
|
||||
.to_owned(),
|
||||
"<!doctype html><body>refresh target</body>".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";
|
||||
|
||||
@@ -36,15 +36,21 @@ pub(super) struct StaticHttpServer {
|
||||
|
||||
impl StaticHttpServer {
|
||||
pub(super) async fn spawn(expected_requests: usize) -> Self {
|
||||
const BODY: &str = "<!doctype html><body>child fixture</body>";
|
||||
Self::spawn_with_bodies(vec![BODY.to_owned(); expected_requests]).await
|
||||
}
|
||||
|
||||
pub(super) async fn spawn_with_bodies(response_bodies: Vec<String>) -> 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 = "<!doctype html><body>child fixture</body>";
|
||||
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(),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user