mirror of
https://github.com/lexmount/moli.git
synced 2026-10-30 16:07:24 +00:00
fix(location): use executing document as navigation referrer
This commit is contained in:
@@ -177,6 +177,7 @@ pub(in crate::context_bootstrap) fn dispatch_history_entry_post_commit_events<'s
|
||||
unsafe { &mut *host_ptr }.queue_child_browsing_context_navigation_without_seed_update(
|
||||
child_handle,
|
||||
&applied.url,
|
||||
None,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -596,15 +596,18 @@ 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);
|
||||
if matches!(kind, LocationNavigationKind::Reload) {
|
||||
host.queue_child_browsing_context_reload_from_existing_seed(
|
||||
handle,
|
||||
resolved.as_str(),
|
||||
initiator_url,
|
||||
);
|
||||
} else {
|
||||
host.queue_child_browsing_context_navigation_without_seed_update(
|
||||
handle,
|
||||
resolved.as_str(),
|
||||
initiator_url,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -877,6 +880,33 @@ fn context_host_ptr_for_navigation_owner(
|
||||
})
|
||||
}
|
||||
|
||||
fn location_navigation_initiator_url(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
host: &JsContextHost,
|
||||
) -> Option<url::Url> {
|
||||
if let Some(popup_id) = crate::native_bridge::active_lightweight_popup_id(scope) {
|
||||
return host.lightweight_popup_document_url(popup_id);
|
||||
}
|
||||
let dispatch_scope = scope
|
||||
.get_incumbent_context()
|
||||
.and_then(|context| host.window_execution_context_identity_for_access_check(context))
|
||||
.map(|identity| identity.dispatch_scope())
|
||||
.or_else(|| {
|
||||
crate::context_bootstrap::current_child_browsing_context_handle_for_runtime_scope(scope)
|
||||
.map(crate::native_bridge::OwnerDispatchScope::Child)
|
||||
})
|
||||
.unwrap_or(crate::native_bridge::OwnerDispatchScope::Top);
|
||||
match dispatch_scope {
|
||||
crate::native_bridge::OwnerDispatchScope::Top => Some(host.document_url().clone()),
|
||||
crate::native_bridge::OwnerDispatchScope::Child(handle) => {
|
||||
host.child_browsing_context_current_url(handle)
|
||||
}
|
||||
crate::native_bridge::OwnerDispatchScope::LightweightPopup(popup_id) => {
|
||||
host.lightweight_popup_document_url(popup_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod meta_refresh_tests {
|
||||
use super::*;
|
||||
|
||||
@@ -45,6 +45,7 @@ pub(super) struct PendingChildDocumentNavigation {
|
||||
pub(super) admitted_history_entry: Option<moli_session_history::SessionHistoryEntry>,
|
||||
pub(super) target: ChildDocumentNavigationFetchTarget,
|
||||
pub(super) target_url: Url,
|
||||
pub(super) referrer_source_url: Url,
|
||||
pub(super) resource_loader: crate::network::navigation::NavigationResourceLoader,
|
||||
pub(super) reserved_service_worker_client_id:
|
||||
Option<crate::service_worker_runtime::ServiceWorkerClientId>,
|
||||
|
||||
@@ -14,6 +14,7 @@ impl JsContextHost {
|
||||
bootstrap: ChildBrowsingContextBootstrap,
|
||||
navigation_load: FrameDocumentNavigationLoadBinding,
|
||||
initiator: ChildDocumentNavigationInitiator,
|
||||
initiator_url: Option<Url>,
|
||||
) -> Option<ChildDocumentCommitResult> {
|
||||
if !self.child_browsing_contexts.contains_key(&handle) {
|
||||
return None;
|
||||
@@ -71,7 +72,13 @@ impl JsContextHost {
|
||||
|
||||
if requires_async_load {
|
||||
if self
|
||||
.start_child_document_load(handle, bootstrap.clone(), navigation_load, initiator)
|
||||
.start_child_document_load(
|
||||
handle,
|
||||
bootstrap.clone(),
|
||||
navigation_load,
|
||||
initiator,
|
||||
initiator_url,
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
return Some(ChildDocumentCommitResult::pending());
|
||||
|
||||
@@ -116,6 +116,7 @@ impl JsContextHost {
|
||||
bootstrap: ChildBrowsingContextBootstrap,
|
||||
navigation_load: crate::frame_owner_model::FrameDocumentNavigationLoadBinding,
|
||||
initiator: ChildDocumentNavigationInitiator,
|
||||
initiator_url: Option<url::Url>,
|
||||
) -> Option<u64> {
|
||||
let target_url = Self::child_browsing_context_bootstrap_url(&bootstrap)?;
|
||||
let initiating_loader = self.document_resource_loader_for_owner(navigation_load.owner())?;
|
||||
@@ -179,7 +180,8 @@ impl JsContextHost {
|
||||
.and_then(|entry| entry.pending_service_worker_client_id());
|
||||
let frame_owner_resource_timing =
|
||||
self.pending_frame_owner_resource_timing(handle, &target_url, initiator);
|
||||
let initiator_url = self.document_url_for_child_context(handle);
|
||||
let initiator_url =
|
||||
initiator_url.unwrap_or_else(|| self.document_url_for_child_context(handle));
|
||||
let browser_context = self.host_document().cookie_browser_context();
|
||||
self.pending_child_document_navigations.insert(
|
||||
load_id,
|
||||
@@ -190,6 +192,7 @@ impl JsContextHost {
|
||||
.and_then(|entry| entry.navigation_entry_seed().session_history.admitted_entry),
|
||||
target,
|
||||
target_url: target_url.clone(),
|
||||
referrer_source_url: initiator_url.clone(),
|
||||
resource_loader: resource_loader.clone(),
|
||||
reserved_service_worker_client_id: service_worker_client_id,
|
||||
document_credentialless,
|
||||
@@ -581,6 +584,13 @@ impl JsContextHost {
|
||||
loaded.final_url.clone(),
|
||||
&pending.target_url,
|
||||
);
|
||||
let document_referrer = moli_fetch::referrer_header_value(
|
||||
&pending.referrer_source_url,
|
||||
&final_url,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
self.clear_child_browsing_context_pending_navigation(handle);
|
||||
let Some(entry) = self.child_browsing_contexts.get_mut(&handle) else {
|
||||
self.clear_pending_service_worker_child_client_if_matches(
|
||||
@@ -602,6 +612,7 @@ impl JsContextHost {
|
||||
};
|
||||
entry.commit_pending_child_document_load(
|
||||
&final_url,
|
||||
&document_referrer,
|
||||
&loaded.policy_container,
|
||||
sandbox,
|
||||
document_credentialless,
|
||||
|
||||
@@ -31,7 +31,7 @@ impl JsContextHost {
|
||||
}
|
||||
}
|
||||
self.sync_existing_child_browsing_context_runtime_surface_from_seed(scope, handle);
|
||||
self.queue_child_browsing_context_navigation_to_url(handle, &url)
|
||||
self.queue_child_browsing_context_navigation_to_url(handle, &url, None)
|
||||
}
|
||||
|
||||
pub(crate) fn navigate_child_browsing_context_with_request(
|
||||
@@ -74,13 +74,14 @@ impl JsContextHost {
|
||||
if let Some(entry) = self.child_browsing_contexts.get_mut(&handle) {
|
||||
entry.apply_queued_navigation_to_entry_seed(&url, replace_current);
|
||||
}
|
||||
self.queue_child_browsing_context_navigation_to_url(handle, &url)
|
||||
self.queue_child_browsing_context_navigation_to_url(handle, &url, None)
|
||||
}
|
||||
|
||||
pub(crate) fn queue_child_browsing_context_navigation_without_seed_update(
|
||||
&mut self,
|
||||
handle: DomHandle,
|
||||
resolved_url: &str,
|
||||
initiator_url: Option<Url>,
|
||||
) -> bool {
|
||||
if !self.child_browsing_contexts.contains_key(&handle) {
|
||||
return false;
|
||||
@@ -88,7 +89,7 @@ impl JsContextHost {
|
||||
let Some(url) = Url::parse(resolved_url).ok() else {
|
||||
return false;
|
||||
};
|
||||
self.queue_child_browsing_context_navigation_to_url(handle, &url)
|
||||
self.queue_child_browsing_context_navigation_to_url(handle, &url, initiator_url)
|
||||
}
|
||||
|
||||
pub(crate) fn queue_deferred_child_browsing_context_navigation_from_entry_seed(
|
||||
@@ -110,6 +111,7 @@ impl JsContextHost {
|
||||
.set_child_browsing_context_pending_navigation(
|
||||
handle,
|
||||
ChildBrowsingContextBootstrap::Url(url),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.is_none()
|
||||
@@ -156,6 +158,7 @@ impl JsContextHost {
|
||||
.set_child_browsing_context_pending_navigation(
|
||||
handle,
|
||||
ChildBrowsingContextBootstrap::Url(url),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.is_none()
|
||||
@@ -184,6 +187,7 @@ impl JsContextHost {
|
||||
.set_child_browsing_context_pending_navigation(
|
||||
handle,
|
||||
ChildBrowsingContextBootstrap::Request(request),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.is_none()
|
||||
@@ -197,10 +201,11 @@ impl JsContextHost {
|
||||
&mut self,
|
||||
handle: DomHandle,
|
||||
resolved_url: &str,
|
||||
initiator_url: Option<Url>,
|
||||
) -> bool {
|
||||
let Some(url) = Url::parse(resolved_url).ok() else {
|
||||
return false;
|
||||
};
|
||||
self.queue_child_browsing_context_navigation_to_url(handle, &url)
|
||||
self.queue_child_browsing_context_navigation_to_url(handle, &url, initiator_url)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,6 +239,7 @@ impl JsContextHost {
|
||||
&mut self,
|
||||
handle: DomHandle,
|
||||
url: &Url,
|
||||
initiator_url: Option<Url>,
|
||||
) -> bool {
|
||||
if !self.child_browsing_contexts.contains_key(&handle) {
|
||||
return false;
|
||||
@@ -251,6 +252,7 @@ impl JsContextHost {
|
||||
.set_child_browsing_context_pending_navigation(
|
||||
handle,
|
||||
ChildBrowsingContextBootstrap::Url(url.clone()),
|
||||
initiator_url,
|
||||
true,
|
||||
)
|
||||
.is_none()
|
||||
@@ -278,6 +280,7 @@ impl JsContextHost {
|
||||
.set_child_browsing_context_pending_navigation(
|
||||
handle,
|
||||
ChildBrowsingContextBootstrap::Request(request),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.is_none()
|
||||
@@ -354,7 +357,11 @@ impl JsContextHost {
|
||||
let dispatch_load_on_no_string_completion =
|
||||
entry_snapshot.navigation_seed_is_initial_about_blank_commit();
|
||||
let entry = self.child_browsing_contexts.get_mut(&handle)?;
|
||||
entry.set_pending_navigation(ChildBrowsingContextBootstrap::Url(url.clone()), true);
|
||||
entry.set_pending_navigation(
|
||||
ChildBrowsingContextBootstrap::Url(url.clone()),
|
||||
None,
|
||||
true,
|
||||
);
|
||||
self.queue_child_browsing_context_javascript_url_execution(
|
||||
handle,
|
||||
url.clone(),
|
||||
@@ -381,6 +388,7 @@ impl JsContextHost {
|
||||
attribute_bootstrap,
|
||||
navigation_load,
|
||||
ChildDocumentNavigationInitiator::FrameOwnerElement,
|
||||
None,
|
||||
);
|
||||
self.sync_existing_child_browsing_context_window_state(scope, handle);
|
||||
commit_result
|
||||
@@ -394,6 +402,7 @@ impl JsContextHost {
|
||||
) -> Option<ChildDocumentCommitResult> {
|
||||
let entry_snapshot = self.child_browsing_contexts.get(&handle).cloned()?;
|
||||
let pending_bootstrap = entry_snapshot.pending_live_navigation()?;
|
||||
let initiator_url = entry_snapshot.pending_live_navigation_initiator_url();
|
||||
if self.current_child_navigation_load(handle) != Some(navigation_load) {
|
||||
tracing::debug!(
|
||||
?handle,
|
||||
@@ -423,6 +432,7 @@ impl JsContextHost {
|
||||
pending_bootstrap,
|
||||
navigation_load,
|
||||
ChildDocumentNavigationInitiator::BrowsingContext,
|
||||
initiator_url,
|
||||
);
|
||||
self.sync_existing_child_browsing_context_window_state(scope, handle);
|
||||
commit_result
|
||||
|
||||
@@ -47,6 +47,7 @@ pub(super) struct ChildBrowsingContextEntry {
|
||||
attribute_bootstrap: ChildBrowsingContextBootstrap,
|
||||
pending_attribute_bootstrap_commit: bool,
|
||||
pending_live_navigation: Option<ChildBrowsingContextBootstrap>,
|
||||
pending_live_navigation_initiator_url: Option<Url>,
|
||||
pending_live_navigation_reflects_window_state: bool,
|
||||
live_bootstrap: ChildBrowsingContextBootstrap,
|
||||
navigation_entry_seed: NavigationHistoryEntrySeed,
|
||||
@@ -441,6 +442,7 @@ impl ChildBrowsingContextEntry {
|
||||
pub(super) fn commit_pending_child_document_load(
|
||||
&mut self,
|
||||
final_url: &Url,
|
||||
document_referrer: &str,
|
||||
policy_container: &ChildDocumentPolicyContainer,
|
||||
sandbox: DocumentSandboxPolicy,
|
||||
credentialless: bool,
|
||||
@@ -450,6 +452,7 @@ impl ChildBrowsingContextEntry {
|
||||
self.reset_performance_time_origin();
|
||||
self.clear_document_runtime_state();
|
||||
self.rewrite_current_navigation_url_after_load(final_url);
|
||||
self.document_policy_container.document_referrer = document_referrer.to_owned();
|
||||
self.apply_loaded_referrer_policy_to_current_document(
|
||||
policy_container.referrer_policy.clone(),
|
||||
);
|
||||
@@ -967,6 +970,7 @@ impl ChildBrowsingContextEntry {
|
||||
|
||||
pub(super) fn clear_pending_form_submission_navigation(&mut self) {
|
||||
self.pending_live_navigation = None;
|
||||
self.pending_live_navigation_initiator_url = None;
|
||||
}
|
||||
|
||||
pub(super) fn has_pending_live_navigation(&self) -> bool {
|
||||
@@ -1031,6 +1035,19 @@ impl ChildBrowsingContextEntry {
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub(super) fn pending_live_navigation_initiator_url(&self) -> Option<Url> {
|
||||
self.pending_live_navigation_initiator_url.clone()
|
||||
}
|
||||
|
||||
pub(super) fn pending_live_navigation_initiator_url_for_refresh(
|
||||
&self,
|
||||
attribute_bootstrap_changed: bool,
|
||||
) -> Option<Url> {
|
||||
(!attribute_bootstrap_changed)
|
||||
.then_some(self.pending_live_navigation_initiator_url())
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub(super) fn pending_live_navigation_reflects_window_state(&self) -> bool {
|
||||
self.pending_live_navigation_reflects_window_state
|
||||
}
|
||||
@@ -1051,16 +1068,19 @@ impl ChildBrowsingContextEntry {
|
||||
pub(super) fn set_pending_navigation(
|
||||
&mut self,
|
||||
bootstrap: ChildBrowsingContextBootstrap,
|
||||
initiator_url: Option<Url>,
|
||||
reflects_window_state: bool,
|
||||
) {
|
||||
self.pending_attribute_bootstrap_commit = false;
|
||||
self.pending_live_navigation = Some(bootstrap);
|
||||
self.pending_live_navigation_initiator_url = initiator_url;
|
||||
self.pending_live_navigation_reflects_window_state = reflects_window_state;
|
||||
}
|
||||
|
||||
pub(super) fn clear_pending_navigation(&mut self) {
|
||||
self.pending_attribute_bootstrap_commit = false;
|
||||
self.pending_live_navigation = None;
|
||||
self.pending_live_navigation_initiator_url = None;
|
||||
self.pending_live_navigation_reflects_window_state = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -471,6 +471,7 @@ impl JsContextHost {
|
||||
&mut self,
|
||||
handle: DomHandle,
|
||||
bootstrap: ChildBrowsingContextBootstrap,
|
||||
initiator_url: Option<Url>,
|
||||
reflects_window_state: bool,
|
||||
) -> Option<crate::frame_owner_model::FrameDocumentNavigationLoadBinding> {
|
||||
if !self.child_browsing_contexts.contains_key(&handle) {
|
||||
@@ -490,7 +491,7 @@ impl JsContextHost {
|
||||
let entry = self.child_browsing_contexts.get_mut(&handle)?;
|
||||
entry.set_document_permissions_policy(permissions_policy);
|
||||
entry.set_ancestor_origins_referrer_policy_snapshot(ancestor_origins_referrer_policy);
|
||||
entry.set_pending_navigation(bootstrap, reflects_window_state);
|
||||
entry.set_pending_navigation(bootstrap, initiator_url, reflects_window_state);
|
||||
self.note_child_frame_load_started_for_parent(handle);
|
||||
Some(navigation)
|
||||
}
|
||||
|
||||
@@ -424,6 +424,13 @@ impl JsContextHost {
|
||||
pending_live_navigation: existing.as_ref().and_then(|entry| {
|
||||
entry.pending_live_navigation_for_refresh(attribute_bootstrap_changed)
|
||||
}),
|
||||
pending_live_navigation_initiator_url: existing.as_ref().and_then(
|
||||
|entry| {
|
||||
entry.pending_live_navigation_initiator_url_for_refresh(
|
||||
attribute_bootstrap_changed,
|
||||
)
|
||||
},
|
||||
),
|
||||
pending_live_navigation_reflects_window_state: existing
|
||||
.as_ref()
|
||||
.is_some_and(|entry| {
|
||||
|
||||
+122
@@ -3439,3 +3439,125 @@ fn initial_empty_child_history_mutation_preserves_pending_srcdoc_navigation() {
|
||||
"about:srcdoc|true"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn child_location_function_uses_executing_document_as_navigation_referrer() {
|
||||
const HOST: &str = "child-location-referrer.test";
|
||||
|
||||
let server = StaticHttpServer::spawn(2).await;
|
||||
let top_url = server.url_for_host(HOST, "/source/navigate-child-function.html");
|
||||
let child_url = server.url_for_host(HOST, "/source/support/location-set.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.__childLocationReferrerLoadCount = 0;
|
||||
const frame = document.createElement('iframe');
|
||||
frame.src = {};
|
||||
frame.onload = () => {{
|
||||
globalThis.__childLocationReferrerLoadCount++;
|
||||
}};
|
||||
(document.body || document.documentElement || document).appendChild(frame);
|
||||
}})()
|
||||
"#,
|
||||
serde_json::to_string(child_url.as_str()).expect("serialize child URL")
|
||||
))
|
||||
.expect("child Location referrer setup should evaluate");
|
||||
advance_page_task_executor_until_eval_equals(
|
||||
&mut vm,
|
||||
&loader,
|
||||
"String(__childLocationReferrerLoadCount)",
|
||||
"1",
|
||||
"initial child document should load",
|
||||
)
|
||||
.await;
|
||||
|
||||
let child_context_id = vm
|
||||
.live_child_default_runtime_realm_inventory()
|
||||
.into_iter()
|
||||
.map(|realm| realm.context_id)
|
||||
.next()
|
||||
.expect("loaded child realm should exist");
|
||||
vm.eval_in_child_default_context(
|
||||
child_context_id,
|
||||
"globalThis.go = function() { location.href = 'support/dummy.html'; }",
|
||||
)
|
||||
.expect("child navigation function should install");
|
||||
vm.eval("document.querySelector('iframe').contentWindow.go()")
|
||||
.expect("parent should call the child navigation function");
|
||||
advance_page_task_executor_until_eval_equals(
|
||||
&mut vm,
|
||||
&loader,
|
||||
"String(__childLocationReferrerLoadCount)",
|
||||
"2",
|
||||
"child Location navigation should load",
|
||||
)
|
||||
.await;
|
||||
|
||||
let document_referrer = vm
|
||||
.eval("document.querySelector('iframe').contentDocument.referrer")
|
||||
.expect("child navigation referrer should evaluate");
|
||||
let requests = server.finish().await;
|
||||
assert_eq!(requests[0].target, "/source/support/location-set.html");
|
||||
assert_eq!(
|
||||
requests[1].header_value("referer"),
|
||||
Some(child_url.as_str())
|
||||
);
|
||||
assert_eq!(document_referrer, 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";
|
||||
|
||||
let server = StaticHttpServer::spawn(2).await;
|
||||
let top_url = server.url_for_host(HOST, "/source/navigate-child-function-parent.html");
|
||||
let child_url = server.url_for_host(HOST, "/source/initial.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.__parentLocationReferrerLoadCount = 0;
|
||||
const frame = document.createElement('iframe');
|
||||
frame.src = {};
|
||||
frame.onload = () => {{
|
||||
globalThis.__parentLocationReferrerLoadCount++;
|
||||
}};
|
||||
(document.body || document.documentElement || document).appendChild(frame);
|
||||
}})()
|
||||
"#,
|
||||
serde_json::to_string(child_url.as_str()).expect("serialize child URL")
|
||||
))
|
||||
.expect("parent Location referrer setup should evaluate");
|
||||
advance_page_task_executor_until_eval_equals(
|
||||
&mut vm,
|
||||
&loader,
|
||||
"String(__parentLocationReferrerLoadCount)",
|
||||
"1",
|
||||
"initial child document should load",
|
||||
)
|
||||
.await;
|
||||
|
||||
vm.eval("document.querySelector('iframe').contentWindow.location = 'support/dummy.html'")
|
||||
.expect("parent should navigate the child Location");
|
||||
advance_page_task_executor_until_eval_equals(
|
||||
&mut vm,
|
||||
&loader,
|
||||
"String(__parentLocationReferrerLoadCount)",
|
||||
"2",
|
||||
"parent-initiated child Location navigation should load",
|
||||
)
|
||||
.await;
|
||||
|
||||
let document_referrer = vm
|
||||
.eval("document.querySelector('iframe').contentDocument.referrer")
|
||||
.expect("parent-initiated child navigation referrer should evaluate");
|
||||
let requests = server.finish().await;
|
||||
assert_eq!(requests[0].target, "/source/initial.html");
|
||||
assert_eq!(requests[1].header_value("referer"), Some(top_url.as_str()));
|
||||
assert_eq!(document_referrer, top_url.as_str());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user