mirror of
https://github.com/lexmount/moli.git
synced 2026-10-30 16:07:24 +00:00
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:
+28
@@ -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(¤t_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),
|
||||
|
||||
+1
-1
@@ -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(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user