fix(window): share inherited document.domain across related pages

Validation on this branch: cargo fmt --all; cargo clippy --workspace --all-targets --all-features -- -D warnings.
Nextest and WPT reruns omitted at the user's request.
This commit is contained in:
ldm0
2026-10-11 09:01:44 +08:00
committed by Donough Liu
parent 744e050af8
commit 7f0c43968f
15 changed files with 194 additions and 16 deletions
@@ -844,6 +844,34 @@ async fn explicit_browser_and_noopener_blank_documents_keep_their_committed_hist
abort_test_cdp_server(server).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn auxiliary_initial_domain_updates_follow_the_creator() {
let (fixture_addr, _fixture) = spawn_dedicated_fixture_server(
Router::new().fallback(get(|| async {
axum::response::Html("<!doctype html><p>domain</p>")
})),
"auxiliary-domain",
);
let (addr, server) = spawn_test_protocol_server().await;
let (mut browser, _) =
connect_async(format!("ws://{addr}/devtools/browser/{DEFAULT_BROWSER_ID}"))
.await
.unwrap();
let opener_id = create_dynamic_target(&mut browser, 1).await;
let mut opener = connect_dynamic_page(addr, &opener_id).await;
send_cdp_command(&mut opener, 1, "Page.enable", None, json!({})).await;
navigate_dynamic_page_and_wait_for_load(
&mut opener,
2,
&format!("http://{fixture_addr}/parent"),
)
.await;
let (_, _child) = open_auxiliary(addr, &mut opener, "").await;
assert_eq!(evaluate_window_name_probe(&mut opener,3,
"document.domain='127.0.0.1';[p.document.domain,p.document.body.ownerDocument===p.document]").await,json!(["127.0.0.1",true]));
abort_test_cdp_server(server).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn auxiliary_blank_reload_preserves_origin_storage_and_fallback_base() {
let (fixture_addr, _fixture) = spawn_dedicated_fixture_server(
@@ -660,7 +660,11 @@ fn contexts_can_script_access<'s>(
});
};
if accessing_host_ptr != accessed_host_ptr {
return false;
return host.window_execution_context_can_access_other_host(
accessing_identity,
unsafe { &*accessed_host_ptr },
accessed_identity,
);
}
host.window_execution_context_can_access(accessing_identity, accessed_identity)
@@ -19,7 +19,7 @@ use moli_page_types::{
apply_child_browsing_context_navigation_to_entry_seed as apply_child_navigation_to_seed,
replace_child_browsing_context_navigation_in_entry_seed as replace_child_navigation_in_seed,
};
use std::collections::HashSet;
use std::{cell::RefCell, collections::HashSet, rc::Rc};
use url::Url;
mod classic_scripts;
@@ -58,7 +58,7 @@ pub(super) struct ChildBrowsingContextEntry {
performance_time_origin: ChildPerformanceTimeOrigin,
pending_document_load_id: Option<u64>,
classic_script_document_state: classic_scripts::ChildClassicScriptDocumentState,
document_domain_override: Option<String>,
document_domain_override: Rc<RefCell<Option<String>>>,
credentialless: bool,
service_worker_client_id: Option<ServiceWorkerClientId>,
pending_service_worker_client_id: Option<ServiceWorkerClientId>,
@@ -117,15 +117,19 @@ impl ChildBrowsingContextEntry {
}
pub(super) fn document_domain_override(&self) -> Option<String> {
self.document_domain_override.borrow().clone()
}
pub(super) fn shared_document_domain_override(&self) -> Rc<RefCell<Option<String>>> {
self.document_domain_override.clone()
}
pub(super) fn set_document_domain_override(&mut self, domain: String) {
self.document_domain_override = Some(domain);
*self.document_domain_override.borrow_mut() = Some(domain);
}
pub(super) fn clear_document_domain_override(&mut self) {
self.document_domain_override = None;
self.document_domain_override = Rc::new(RefCell::new(None));
}
pub(super) fn window_name(&self) -> &str {
@@ -502,7 +502,8 @@ impl JsContextHost {
// Document keeps its mutable origin until replacement commits.
document_domain_override: existing
.as_ref()
.and_then(|entry| entry.document_domain_override()),
.map(|entry| entry.document_domain_override.clone())
.unwrap_or_default(),
credentialless,
service_worker_client_id: existing
.as_ref()
@@ -47,9 +47,11 @@ impl JsContextHost {
&self,
origin: &str,
inherited_opaque_origin_matches: bool,
inherited_document_domain: Option<&str>,
policy: &crate::document_runtime::DocumentPolicyContainer,
) -> crate::frame_owner_model::FrameDocumentLocalWindowTransition {
let same_origin = self.document_domain_override.is_none()
let same_origin = self.document_domain_override.borrow().as_deref()
== inherited_document_domain
&& (inherited_opaque_origin_matches
|| moli_url::WebOrigin::from_serialized(&self.main_document_security_origin())
.same_origin(&moli_url::WebOrigin::from_serialized(origin)));
@@ -389,7 +391,7 @@ impl JsContextHost {
#[cfg(test)]
force_child_default_context_preflight_failure: false,
child_browsing_context_document_handles: HashMap::new(),
document_domain_override: None,
document_domain_override: Rc::new(RefCell::new(None)),
next_child_browsing_context_id: 1,
next_child_document_load_id: 0,
next_child_classic_script_load_id: 0,
@@ -4,6 +4,22 @@ use std::net::IpAddr;
use url::{Host, Url};
impl JsContextHost {
pub(crate) fn inherit_document_domain_override(&mut self, domain: Rc<RefCell<Option<String>>>) {
self.document_domain_override = domain;
}
pub(crate) fn inherited_document_domain(
&self,
child: Option<DomHandle>,
) -> Rc<RefCell<Option<String>>> {
match child.and_then(|handle| self.child_browsing_context_security_origin_owner(handle)) {
Some(ChildSecurityOriginOwner::Child(owner)) => {
self.child_browsing_contexts[&owner].shared_document_domain_override()
}
Some(ChildSecurityOriginOwner::Main) | None => self.document_domain_override.clone(),
}
}
pub(crate) fn document_domain_value_for_document_handle(
&self,
document_handle: DomHandle,
@@ -11,6 +27,7 @@ impl JsContextHost {
if document_handle == self.document_handle() {
return self
.document_domain_override
.borrow()
.clone()
.unwrap_or_else(|| self.main_document_domain_host().unwrap_or_default());
}
@@ -48,7 +65,7 @@ impl JsContextHost {
if !document_domain_is_allowed_for_host(&current_host, &domain) {
return false;
}
self.document_domain_override = Some(domain);
*self.document_domain_override.borrow_mut() = Some(domain);
return true;
}
let Some(child_handle) =
@@ -68,7 +85,7 @@ impl JsContextHost {
handle: DomHandle,
) -> Option<String> {
match self.child_browsing_context_security_origin_owner(handle)? {
ChildSecurityOriginOwner::Main => self.document_domain_override.clone(),
ChildSecurityOriginOwner::Main => self.document_domain_override.borrow().clone(),
ChildSecurityOriginOwner::Child(owner) => self
.child_browsing_contexts
.get(&owner)
@@ -115,7 +132,9 @@ impl JsContextHost {
return false;
};
match origin_owner {
ChildSecurityOriginOwner::Main => self.document_domain_override = Some(domain),
ChildSecurityOriginOwner::Main => {
*self.document_domain_override.borrow_mut() = Some(domain);
}
ChildSecurityOriginOwner::Child(owner) => {
let Some(entry) = self.child_browsing_contexts.get_mut(&owner) else {
return false;
@@ -867,7 +867,7 @@ pub(crate) struct JsContextHost {
#[cfg(test)]
force_child_default_context_preflight_failure: bool,
child_browsing_context_document_handles: HashMap<DomHandle, DomHandle>,
document_domain_override: Option<String>,
document_domain_override: Rc<RefCell<Option<String>>>,
next_child_browsing_context_id: u64,
next_child_document_load_id: u64,
next_child_classic_script_load_id: u64,
@@ -271,6 +271,12 @@ impl JsContextHost {
};
let (pending, window_proxy) =
allocator.stage_in_scope(scope, &environment, &self.bridge.bindings, init)?;
if !policy.sandbox.forces_opaque_origin {
let target = crate::util::context_host_ptr_from_window_object(scope, window_proxy)
.expect("a related Window retains its native host");
unsafe { &mut *target }.document_domain_override =
self.inherited_document_domain(creator_child_handle);
}
Ok(OpenedRendererWindow {
window_proxy,
window: Some(window),
@@ -105,7 +105,7 @@ impl WindowEnvironmentSettings {
} = &mut origin
{
if self.domain_owner_document == host.document_handle() {
*document_domain = host.document_domain_override.clone();
*document_domain = host.document_domain_override.borrow().clone();
} else if let Some(handle) =
host.child_browsing_context_handle_for_stored_document(self.domain_owner_document)
{
@@ -18,7 +18,7 @@ impl JsContextHost {
return None;
}
let origin = self.main_document_security_origin();
if self.document_domain_override.is_some() {
if self.document_domain_override.borrow().is_some() {
return None;
}
window_security_token_key(origin)
@@ -47,7 +47,7 @@ impl JsContextHost {
}
window_isolated_world_security_token_key(
self.main_document_security_origin(),
self.document_domain_override.is_some(),
self.document_domain_override.borrow().is_some(),
)
}
@@ -107,6 +107,39 @@ impl JsContextHost {
}
}
self.refresh_child_window_access_surfaces_after_origin_mutation(scope);
if let Some(environment) = self.page_script_environment() {
for related in environment.related_document_hosts() {
let related_ptr = related.as_ptr();
if std::ptr::eq(related_ptr, self) {
continue;
}
let related_host = unsafe { &*related_ptr };
if !std::rc::Rc::ptr_eq(
&self.document_domain_override,
&related_host.document_domain_override,
) {
continue;
}
let dispatch_scope = OwnerDispatchScope::Top;
let Some(owner) =
related_host.current_window_execution_context_owner(dispatch_scope)
else {
continue;
};
if let Some((_, context)) =
related_host.window_execution_context(scope, owner, dispatch_scope)
&& set_window_security_token(
scope,
context,
related_host
.main_default_world_security_token_key()
.as_deref(),
)
{
updated += 1;
}
}
}
updated
}
@@ -179,6 +212,39 @@ impl JsContextHost {
)
}
pub(crate) fn window_execution_context_can_access_other_host(
&self,
accessing: WindowExecutionContextIdentity,
target_host: &JsContextHost,
accessed: WindowExecutionContextIdentity,
) -> bool {
if !self.window_execution_context_identity_is_current(accessing)
|| !target_host.window_execution_context_identity_is_current(accessed)
{
return false;
}
if accessing.grants_universal_access() {
return true;
}
let Some(origin) = self.window_access_origin(accessing) else {
return false;
};
let Some(target) =
target_host.window_access_origin_for_dispatch_scope(accessed.dispatch_scope())
else {
return false;
};
// Opaque owner identifiers are local to each host. Shared opaque
// origins are authorized by their V8 security token before this path.
matches!(
(&origin, &target),
(
WindowAccessOrigin::Tuple { .. },
WindowAccessOrigin::Tuple { .. }
)
) && origin.can_access(&target)
}
/// Checks Window access before the target realm is entered or materialized.
///
/// WebIDL operations such as a borrowed `fetch()` must authorize the
@@ -262,7 +328,7 @@ impl JsContextHost {
}
WindowAccessOrigin::from_serialized_origin(
serialized_origin,
self.document_domain_override.clone(),
self.document_domain_override.borrow().clone(),
)
}
+10
View File
@@ -1953,6 +1953,7 @@ impl ScriptVm {
pub(crate) struct ScriptVmInitialDocumentEnvironment {
security_token: Option<v8::Global<v8::Value>>,
document_domain_override: Rc<RefCell<Option<String>>>,
origin: String,
pub(crate) policy_container: crate::document_runtime::DocumentPolicyContainer,
fallback_base_url: Option<url::Url>,
@@ -2052,6 +2053,9 @@ impl ScriptVmPageRealmBootstrap {
let (page_context_cancel_tx, page_context_cancel_rx) =
renderer_page_context_cancel_channel();
let inherited_document_domain = initial_environment
.as_ref()
.map(|environment| environment.document_domain_override.clone());
let inherited_security_token =
initial_environment.and_then(|environment| environment.security_token);
let RendererDocumentIsolateBootstrap {
@@ -2106,6 +2110,12 @@ impl ScriptVmPageRealmBootstrap {
top_level_storage_key,
reserved_service_worker_client_id,
)));
if let Some(domain) = inherited_document_domain {
context_host
.borrow_mut()
.inherit_document_domain_override(domain);
}
context_host
.borrow_mut()
.bind_deferred_context_host_release_queue(
@@ -71,8 +71,23 @@ impl ScriptVmInitialDocumentEnvironment {
.ok_or_else(|| anyhow!("navigation initiator has no live creation context"))?;
let security_token = (!policy_container.sandbox.forces_opaque_origin)
.then(|| v8::Global::new(scope, context.get_security_token(scope)));
let document_domain_override = (!policy_container.sandbox.forces_opaque_origin)
.then(|| crate::util::context_host_ptr_from_context_slot(context))
.flatten()
.map(|host_ptr| {
let host = unsafe { &*host_ptr };
let child = host
.window_execution_context_identity_for_access_check(context)
.and_then(|identity| match identity.dispatch_scope() {
crate::native_bridge::OwnerDispatchScope::Child(handle) => Some(handle),
_ => None,
});
host.inherited_document_domain(child)
})
.unwrap_or_default();
Ok(Self {
security_token,
document_domain_override,
origin,
policy_container,
fallback_base_url: Some(base_url),
@@ -435,6 +450,7 @@ impl ScriptVm {
let token = scope.get_current_context().get_security_token(scope);
Ok(ScriptVmInitialDocumentEnvironment {
security_token: Some(v8::Global::new(scope, token)),
document_domain_override: host.inherited_document_domain(None),
origin,
policy_container,
fallback_base_url,
@@ -495,6 +495,17 @@ impl RendererPageScriptEnvironment {
.find_map(|inner| matches(&inner).map(|()| Self { inner }))
}
pub(crate) fn related_document_hosts(&self) -> Vec<Rc<RefCell<JsContextHost>>> {
self.inner
.renderer_document_isolate
.related_pages
.borrow()
.values()
.filter_map(Weak::upgrade)
.filter_map(|page| page.document_host.borrow().upgrade())
.collect()
}
pub(crate) fn window_identity(
&self,
) -> Option<(
@@ -25,12 +25,17 @@ impl ScriptVm {
} else {
false
};
let inherited_domain =
inherited.map(|environment| environment.document_domain_override.borrow());
Ok(self
._context_host
.borrow()
.main_document_local_window_transition_for_commit(
origin,
inherited_opaque_origin_matches,
inherited_domain
.as_ref()
.and_then(|domain| domain.as_deref()),
policy,
))
}
@@ -111,6 +116,11 @@ impl ScriptVm {
vm.page_runtime_wake_tx = page_task_tx.page_runtime_wake_sender();
{
let mut host = vm._context_host.borrow_mut();
if let Some(environment) = inherited.as_ref() {
host.inherit_document_domain_override(
environment.document_domain_override.clone(),
);
}
host.rebind_initial_main_document_senders(
resource_completion,
page_task_tx.top_level_navigation_handoff_sender(),
@@ -38,6 +38,7 @@ impl ScriptVmDefaultWorldBootstrap {
None,
Some(ScriptVmInitialDocumentEnvironment {
security_token: None,
document_domain_override: Rc::default(),
fallback_base_url: None,
storage_key: None,
origin: inherited_origin.clone(),