mirror of
https://github.com/lexmount/moli.git
synced 2026-09-27 16:01:31 +00:00
fix(location): track ancestor origins per document
This commit is contained in:
@@ -60,6 +60,15 @@ pub(crate) fn flush_blocked_indexed_db_requests(scope: &mut v8::PinScope<'_, '_>
|
||||
flush_drain_blocked_open_requests_task(scope);
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn new_dom_string_list<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
values: &[String],
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
super::exposed_interfaces::ensure_intrinsic_interface_constructor(scope, "DOMStringList")
|
||||
.expect("DOMStringList constructor should materialize before creating an instance");
|
||||
new_idb_dom_string_list(scope, values)
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) use self::core::indexed_db_usage_bytes_for_storage_key;
|
||||
pub(crate) use self::core::set_indexed_db_manager_for_context;
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -14,7 +14,8 @@ mod surface;
|
||||
|
||||
pub(super) use install::{
|
||||
build_location_constructor_template, build_location_runtime_object,
|
||||
install_location_runtime_state,
|
||||
install_location_runtime_state, location_belongs_to_current_local_window,
|
||||
location_owner_has_current_realm,
|
||||
};
|
||||
pub(super) use navigation::{
|
||||
is_same_document_fragment_navigation, resolve_location_navigation_target,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::super::constructors::illegal_constructor_callback;
|
||||
use super::super::navigation_window::{
|
||||
navigation_document_has_opaque_origin, runtime_window_owner,
|
||||
navigation_document_has_opaque_origin, runtime_window_dispatch_scope, runtime_window_owner,
|
||||
};
|
||||
use super::helpers::{
|
||||
location_host_string, navigate_modified_location_url, parsed_location_url,
|
||||
@@ -10,9 +10,16 @@ use super::methods::{
|
||||
location_assign_callback, location_reload_callback, location_replace_callback,
|
||||
location_to_string_callback,
|
||||
};
|
||||
use super::slots::{location_href_slot, sync_location_object_fields};
|
||||
use super::slots::{
|
||||
clear_location_ancestor_origins_slot, location_ancestor_origins_slot,
|
||||
location_empty_ancestor_origins_slot, location_href_slot, location_relevant_document_id_slot,
|
||||
location_relevant_local_window_id_slot, set_location_ancestor_origins_slot,
|
||||
set_location_empty_ancestor_origins_slot, set_location_relevant_document_id_slot,
|
||||
set_location_relevant_local_window_id_slot, sync_location_object_fields,
|
||||
};
|
||||
use super::*;
|
||||
use crate::context_bootstrap::exposed_interfaces::build_intrinsic_interface_instance;
|
||||
use crate::context_bootstrap::indexed_db::new_dom_string_list;
|
||||
use crate::util::{callback_data_index_value, callback_data_item};
|
||||
use crate::web_api_interfaces;
|
||||
use anyhow::{Result, anyhow};
|
||||
@@ -233,9 +240,156 @@ pub(in crate::context_bootstrap) fn install_location_runtime_state<'s>(
|
||||
.initialize(scope, location)
|
||||
.map_err(|error| anyhow!("failed to initialize Location own surface: {error}"))?;
|
||||
}
|
||||
install_location_ancestor_origins_state(scope, location);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install_location_ancestor_origins_state<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
location: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
let Some((local_window_id, document_id)) = current_location_owner_ids(scope, location) else {
|
||||
return;
|
||||
};
|
||||
if location_relevant_local_window_id_slot(scope, location) == Some(local_window_id)
|
||||
&& location_relevant_document_id_slot(scope, location) == Some(document_id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// DOMStringList is otherwise a lazy interface. Record the relevant
|
||||
// Document now, but materialize its list only when script first reads it.
|
||||
clear_location_ancestor_origins_slot(scope, location);
|
||||
set_location_relevant_local_window_id_slot(scope, location, local_window_id);
|
||||
set_location_relevant_document_id_slot(scope, location, document_id);
|
||||
}
|
||||
|
||||
fn new_location_dom_string_list<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
location: v8::Local<'s, v8::Object>,
|
||||
values: &[String],
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let Some(relevant_context) = location.get_creation_context(scope) else {
|
||||
return new_dom_string_list(scope, values);
|
||||
};
|
||||
if relevant_context == scope.get_current_context() {
|
||||
return new_dom_string_list(scope, values);
|
||||
}
|
||||
let list = {
|
||||
let target_scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
let list = new_dom_string_list(target_scope, values);
|
||||
v8::Global::new(target_scope, list)
|
||||
};
|
||||
v8::Local::new(scope, &list)
|
||||
}
|
||||
|
||||
fn current_location_document_state<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
location: v8::Local<'s, v8::Object>,
|
||||
) -> Option<(u64, u64, Vec<String>)> {
|
||||
let owner = runtime_window_owner(scope, location);
|
||||
let dispatch_scope = runtime_window_dispatch_scope(scope, owner)?;
|
||||
let host_ptr = context_host_ptr_from_global_bridge(scope)?;
|
||||
let host = unsafe { &*host_ptr };
|
||||
let (local_window_id, document_id) =
|
||||
location_owner_ids_for_dispatch_scope(host, dispatch_scope)?;
|
||||
let ancestor_origins = match dispatch_scope {
|
||||
crate::native_bridge::OwnerDispatchScope::Child(handle) => {
|
||||
host.child_browsing_context_ancestor_origins(handle)?
|
||||
}
|
||||
crate::native_bridge::OwnerDispatchScope::Top
|
||||
| crate::native_bridge::OwnerDispatchScope::LightweightPopup(_) => Vec::new(),
|
||||
};
|
||||
Some((local_window_id, document_id, ancestor_origins))
|
||||
}
|
||||
|
||||
fn current_location_owner_ids<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
location: v8::Local<'s, v8::Object>,
|
||||
) -> Option<(u64, u64)> {
|
||||
let owner = runtime_window_owner(scope, location);
|
||||
let dispatch_scope = runtime_window_dispatch_scope(scope, owner)?;
|
||||
let host_ptr = context_host_ptr_from_global_bridge(scope)?;
|
||||
location_owner_ids_for_dispatch_scope(unsafe { &*host_ptr }, dispatch_scope)
|
||||
}
|
||||
|
||||
fn location_owner_ids_for_dispatch_scope(
|
||||
host: &crate::native_bridge::JsContextHost,
|
||||
dispatch_scope: crate::native_bridge::OwnerDispatchScope,
|
||||
) -> Option<(u64, u64)> {
|
||||
match dispatch_scope {
|
||||
crate::native_bridge::OwnerDispatchScope::Top => host
|
||||
.current_main_document_task_owner()
|
||||
.map(|owner| (owner.local_window_id.0, owner.document_id.0)),
|
||||
crate::native_bridge::OwnerDispatchScope::Child(handle) => host
|
||||
.current_child_document_task_owner(handle)
|
||||
.map(|owner| (owner.local_window_id.0, owner.document_id.0)),
|
||||
crate::native_bridge::OwnerDispatchScope::LightweightPopup(popup_id) => Some((
|
||||
host.current_lightweight_popup_local_window_id(popup_id)?
|
||||
.as_u64(),
|
||||
host.current_lightweight_popup_document_owner(popup_id)?
|
||||
.document_id()
|
||||
.as_u64(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn location_belongs_to_current_local_window<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
location: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
location_relevant_local_window_id_slot(scope, location).is_some_and(|local_window_id| {
|
||||
current_location_owner_ids(scope, location)
|
||||
.is_some_and(|(current_local_window_id, _)| current_local_window_id == local_window_id)
|
||||
})
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn location_owner_has_current_realm<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
let Some(dispatch_scope) = runtime_window_dispatch_scope(scope, owner) else {
|
||||
return false;
|
||||
};
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
return false;
|
||||
};
|
||||
unsafe { &*host_ptr }
|
||||
.current_runtime_window_execution_context_identity_for_dispatch_scope(scope, dispatch_scope)
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn location_ancestor_origins_for_holder<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
holder: v8::Local<'s, v8::Object>,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let relevant_owner = location_relevant_local_window_id_slot(scope, holder)
|
||||
.zip(location_relevant_document_id_slot(scope, holder));
|
||||
if let Some((local_window_id, document_id, ancestor_origins)) =
|
||||
current_location_document_state(scope, holder)
|
||||
&& relevant_owner == Some((local_window_id, document_id))
|
||||
{
|
||||
if let Some(origins) = location_ancestor_origins_slot(scope, holder) {
|
||||
return origins;
|
||||
}
|
||||
let origins = new_location_dom_string_list(scope, holder, &ancestor_origins);
|
||||
set_location_ancestor_origins_slot(scope, holder, origins);
|
||||
// The Location can outlive its Document and realm. Cache its required
|
||||
// inactive value while that realm can still supply DOMStringList's
|
||||
// prototype, but only after script has requested ancestorOrigins.
|
||||
if location_empty_ancestor_origins_slot(scope, holder).is_none() {
|
||||
let empty = new_location_dom_string_list(scope, holder, &[]);
|
||||
set_location_empty_ancestor_origins_slot(scope, holder, empty);
|
||||
}
|
||||
return origins;
|
||||
}
|
||||
if let Some(empty) = location_empty_ancestor_origins_slot(scope, holder) {
|
||||
return empty;
|
||||
}
|
||||
let empty = new_location_dom_string_list(scope, holder, &[]);
|
||||
set_location_empty_ancestor_origins_slot(scope, holder, empty);
|
||||
empty
|
||||
}
|
||||
|
||||
fn location_own_surface_installed(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
location: v8::Local<'_, v8::Object>,
|
||||
@@ -289,7 +443,9 @@ fn location_attribute_getter<'s>(
|
||||
return;
|
||||
};
|
||||
match attribute {
|
||||
LocationAttribute::AncestorOrigins => rv.set(v8::Array::new(scope, 0).into()),
|
||||
LocationAttribute::AncestorOrigins => {
|
||||
rv.set(location_ancestor_origins_for_holder(scope, holder).into());
|
||||
}
|
||||
LocationAttribute::Href => {
|
||||
set_return_string(scope, rv, ¤t_href);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,106 @@
|
||||
use super::*;
|
||||
use crate::util::{get_private_value, set_private_value};
|
||||
use crate::util::{get_private_object, get_private_value, set_private_value};
|
||||
|
||||
const LOCATION_ANCESTOR_ORIGINS_SLOT: &str = "__moliLocationAncestorOrigins";
|
||||
const LOCATION_EMPTY_ANCESTOR_ORIGINS_SLOT: &str = "__moliLocationEmptyAncestorOrigins";
|
||||
const LOCATION_RELEVANT_DOCUMENT_ID_SLOT: &str = "__moliLocationRelevantDocumentId";
|
||||
const LOCATION_RELEVANT_LOCAL_WINDOW_ID_SLOT: &str = "__moliLocationRelevantLocalWindowId";
|
||||
|
||||
pub(super) fn location_ancestor_origins_slot<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
get_private_object(scope, object, LOCATION_ANCESTOR_ORIGINS_SLOT)
|
||||
}
|
||||
|
||||
pub(super) fn set_location_ancestor_origins_slot<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
value: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
set_private_value(scope, object, LOCATION_ANCESTOR_ORIGINS_SLOT, value.into());
|
||||
}
|
||||
|
||||
pub(super) fn clear_location_ancestor_origins_slot<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
let undefined = v8::undefined(scope);
|
||||
set_private_value(
|
||||
scope,
|
||||
object,
|
||||
LOCATION_ANCESTOR_ORIGINS_SLOT,
|
||||
undefined.into(),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn location_empty_ancestor_origins_slot<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
get_private_object(scope, object, LOCATION_EMPTY_ANCESTOR_ORIGINS_SLOT)
|
||||
}
|
||||
|
||||
pub(super) fn set_location_empty_ancestor_origins_slot<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
value: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
set_private_value(
|
||||
scope,
|
||||
object,
|
||||
LOCATION_EMPTY_ANCESTOR_ORIGINS_SLOT,
|
||||
value.into(),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn location_relevant_document_id_slot<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
) -> Option<u64> {
|
||||
let value = get_private_value(scope, object, LOCATION_RELEVANT_DOCUMENT_ID_SLOT)?;
|
||||
let value = v8::Local::<v8::BigInt>::try_from(value).ok()?;
|
||||
let (document_id, lossless) = value.u64_value();
|
||||
lossless.then_some(document_id)
|
||||
}
|
||||
|
||||
pub(super) fn set_location_relevant_document_id_slot<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
document_id: u64,
|
||||
) {
|
||||
let value = v8::BigInt::new_from_u64(scope, document_id);
|
||||
set_private_value(
|
||||
scope,
|
||||
object,
|
||||
LOCATION_RELEVANT_DOCUMENT_ID_SLOT,
|
||||
value.into(),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn location_relevant_local_window_id_slot<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
) -> Option<u64> {
|
||||
let value = get_private_value(scope, object, LOCATION_RELEVANT_LOCAL_WINDOW_ID_SLOT)?;
|
||||
let value = v8::Local::<v8::BigInt>::try_from(value).ok()?;
|
||||
let (local_window_id, lossless) = value.u64_value();
|
||||
lossless.then_some(local_window_id)
|
||||
}
|
||||
|
||||
pub(super) fn set_location_relevant_local_window_id_slot<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
local_window_id: u64,
|
||||
) {
|
||||
let value = v8::BigInt::new_from_u64(scope, local_window_id);
|
||||
set_private_value(
|
||||
scope,
|
||||
object,
|
||||
LOCATION_RELEVANT_LOCAL_WINDOW_ID_SLOT,
|
||||
value.into(),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn set_location_href_slot<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
|
||||
@@ -2,6 +2,7 @@ use super::history_runtime::state::{history_window_owner, window_has_shared_hist
|
||||
use super::location_history_storage::WINDOW_RUNTIME_OWNER_SLOT;
|
||||
use super::location_runtime::{
|
||||
build_location_runtime_object, install_location_runtime_state,
|
||||
location_belongs_to_current_local_window, location_owner_has_current_realm,
|
||||
sync_window_location_history_navigation_runtime_surface,
|
||||
};
|
||||
use super::navigation_activation::{
|
||||
@@ -83,18 +84,24 @@ pub(crate) fn reset_window_location_history_navigation_runtime_state<'s>(
|
||||
if window_has_shared_history(scope, window) {
|
||||
return Ok(());
|
||||
}
|
||||
let location = match window_runtime_object(scope, window, WINDOW_LOCATION_SLOT) {
|
||||
Some(location) => location,
|
||||
None => {
|
||||
let existing_location = window_runtime_object(scope, window, WINDOW_LOCATION_SLOT);
|
||||
let location = match existing_location {
|
||||
Some(location) if location_belongs_to_current_local_window(scope, location) => {
|
||||
Some(location)
|
||||
}
|
||||
Some(_) | None if location_owner_has_current_realm(scope, window) => {
|
||||
let location = new_location_runtime_object(scope, window, href)?;
|
||||
set_private_value(scope, window, WINDOW_LOCATION_SLOT, location.into());
|
||||
location
|
||||
Some(location)
|
||||
}
|
||||
Some(_) | None => None,
|
||||
};
|
||||
LocationRuntimeObjectDeclaration::new(window, href.to_owned())
|
||||
.initialize(scope, location)
|
||||
.map_err(|error| anyhow::anyhow!("failed to initialize Location object: {error}"))?;
|
||||
install_location_runtime_state(scope, location, href)?;
|
||||
if let Some(location) = location {
|
||||
LocationRuntimeObjectDeclaration::new(window, href.to_owned())
|
||||
.initialize(scope, location)
|
||||
.map_err(|error| anyhow::anyhow!("failed to initialize Location object: {error}"))?;
|
||||
install_location_runtime_state(scope, location, href)?;
|
||||
}
|
||||
|
||||
let initial_seed = initial_navigation_history_seed(scope, window, href);
|
||||
let history = match window_runtime_object(scope, window, WINDOW_HISTORY_SLOT) {
|
||||
|
||||
@@ -317,6 +317,12 @@ impl JsContextHost {
|
||||
expected_current_owner,
|
||||
)?;
|
||||
debug_assert_eq!(owner_transition.retired_owner(), expected_current_owner);
|
||||
let ancestor_origins_refreshed =
|
||||
self.refresh_current_child_document_ancestor_origins(handle);
|
||||
debug_assert!(
|
||||
ancestor_origins_refreshed,
|
||||
"committed child Document must capture its ancestor origins"
|
||||
);
|
||||
|
||||
match owner_transition.local_window_owner_transition() {
|
||||
FrameLocalWindowOwnerTransition::Replaced { .. } => {
|
||||
|
||||
@@ -125,6 +125,12 @@ impl JsContextHost {
|
||||
let current_owner = owner_transition
|
||||
.current_owner()
|
||||
.expect("initial-empty frame initialization must install an owner");
|
||||
let ancestor_origins_initialized =
|
||||
self.refresh_current_child_document_ancestor_origins(handle);
|
||||
debug_assert!(
|
||||
ancestor_origins_initialized,
|
||||
"initial-empty child Document must capture its ancestor origins"
|
||||
);
|
||||
self.register_committed_document_resource_loader(
|
||||
crate::network::context::DocumentFetchContext::new(
|
||||
crate::native_bridge::WindowDocumentOwner::Frame(current_owner),
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::{
|
||||
ChildBrowsingContextBootstrap, ChildBrowsingContextSnapshot, ChildFrameAttachmentSnapshot,
|
||||
JsContextHost, NavigationActivationSeed, NavigationHistoryDocumentId, NavigationHistoryEntryId,
|
||||
NavigationHistoryEntryKey, NavigationHistoryEntrySeed, NavigationHistorySerializedEntry,
|
||||
child_documents::CompletedFrameOwnerResourceTiming,
|
||||
child_documents::CompletedFrameOwnerResourceTiming, window_security_tokens::WindowAccessOrigin,
|
||||
};
|
||||
use crate::{
|
||||
document_runtime::{DocumentPolicyContainer, DocumentSandboxPolicy, DomHandle},
|
||||
@@ -53,6 +53,8 @@ pub(super) struct ChildBrowsingContextEntry {
|
||||
committed_navigation_entry_seed: NavigationHistoryEntrySeed,
|
||||
cached_snapshot: Option<ChildBrowsingContextSnapshot>,
|
||||
document_policy_container: ChildDocumentPolicyContainer,
|
||||
document_internal_ancestor_origins: Vec<WindowAccessOrigin>,
|
||||
ancestor_origins_referrer_policy_snapshot: ChildAncestorOriginsReferrerPolicy,
|
||||
completed_document_network: Option<CompletedChildDocumentNetwork>,
|
||||
completed_frame_owner_resource_timing: Option<CompletedFrameOwnerResourceTiming>,
|
||||
performance_time_origin: ChildPerformanceTimeOrigin,
|
||||
@@ -73,6 +75,14 @@ struct CompletedChildDocumentNetwork {
|
||||
|
||||
pub(super) type ChildDocumentPolicyContainer = DocumentPolicyContainer;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub(in crate::native_bridge::context_host) enum ChildAncestorOriginsReferrerPolicy {
|
||||
#[default]
|
||||
Default,
|
||||
NoReferrer,
|
||||
SameOrigin,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) struct ChildPerformanceTimeOrigin(u64);
|
||||
|
||||
@@ -238,6 +248,34 @@ impl ChildBrowsingContextEntry {
|
||||
self.document_policy_container.clone()
|
||||
}
|
||||
|
||||
pub(super) fn document_internal_ancestor_origins(&self) -> Vec<WindowAccessOrigin> {
|
||||
self.document_internal_ancestor_origins.clone()
|
||||
}
|
||||
|
||||
pub(super) fn set_document_internal_ancestor_origins(
|
||||
&mut self,
|
||||
origins: Vec<WindowAccessOrigin>,
|
||||
) {
|
||||
self.document_internal_ancestor_origins = origins;
|
||||
}
|
||||
|
||||
pub(super) fn clear_document_internal_ancestor_origins(&mut self) {
|
||||
self.document_internal_ancestor_origins.clear();
|
||||
}
|
||||
|
||||
pub(super) fn ancestor_origins_referrer_policy_snapshot(
|
||||
&self,
|
||||
) -> ChildAncestorOriginsReferrerPolicy {
|
||||
self.ancestor_origins_referrer_policy_snapshot
|
||||
}
|
||||
|
||||
pub(super) fn set_ancestor_origins_referrer_policy_snapshot(
|
||||
&mut self,
|
||||
policy: ChildAncestorOriginsReferrerPolicy,
|
||||
) {
|
||||
self.ancestor_origins_referrer_policy_snapshot = policy;
|
||||
}
|
||||
|
||||
pub(super) fn set_document_permissions_policy(
|
||||
&mut self,
|
||||
policy: crate::permissions_policy::DocumentPermissionsPolicy,
|
||||
|
||||
@@ -476,6 +476,8 @@ impl JsContextHost {
|
||||
if !self.child_browsing_contexts.contains_key(&handle) {
|
||||
return None;
|
||||
};
|
||||
let ancestor_origins_referrer_policy =
|
||||
self.child_ancestor_origins_referrer_policy_from_owner_attribute(handle);
|
||||
let permissions_policy =
|
||||
self.child_browsing_context_permissions_policy_for_navigation(handle, &bootstrap);
|
||||
let Some(navigation) = self.replace_child_navigation_load(handle) else {
|
||||
@@ -487,6 +489,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);
|
||||
self.note_child_frame_load_started_for_parent(handle);
|
||||
Some(navigation)
|
||||
|
||||
@@ -27,6 +27,7 @@ impl JsContextHost {
|
||||
) -> Option<crate::frame_owner_model::FrameDocumentOwnerTransition> {
|
||||
if let Some(entry) = self.child_browsing_contexts.get_mut(&handle) {
|
||||
entry.clear_current_document_loader_id();
|
||||
entry.clear_document_internal_ancestor_origins();
|
||||
}
|
||||
let transition = self.frame_owner_store.detach_current_child_document(handle);
|
||||
let retired_owner = transition.and_then(|item| item.retired_owner());
|
||||
@@ -220,6 +221,19 @@ impl JsContextHost {
|
||||
let sandbox_policy_from_owner = super::document_sandbox_policy_from_attribute(
|
||||
self.dom_host().get_attribute(handle, "sandbox").as_deref(),
|
||||
);
|
||||
let ancestor_origins_referrer_policy_snapshot =
|
||||
if is_new || attribute_bootstrap_changed {
|
||||
self.child_ancestor_origins_referrer_policy_from_owner_attribute(handle)
|
||||
} else {
|
||||
existing
|
||||
.as_ref()
|
||||
.map(|entry| entry.ancestor_origins_referrer_policy_snapshot())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
let document_internal_ancestor_origins = existing
|
||||
.as_ref()
|
||||
.map(|entry| entry.document_internal_ancestor_origins())
|
||||
.unwrap_or_default();
|
||||
let document_credentialless = if is_new {
|
||||
self.child_browsing_context_document_credentialless_for_owner(
|
||||
handle,
|
||||
@@ -417,6 +431,8 @@ impl JsContextHost {
|
||||
committed_navigation_entry_seed,
|
||||
cached_snapshot,
|
||||
document_policy_container,
|
||||
document_internal_ancestor_origins,
|
||||
ancestor_origins_referrer_policy_snapshot,
|
||||
completed_document_network: existing.as_ref().and_then(|entry| {
|
||||
entry
|
||||
.completed_document_network_for_refresh(attribute_bootstrap_changed)
|
||||
|
||||
@@ -825,9 +825,20 @@ impl JsContextHost {
|
||||
&self,
|
||||
handle: DomHandle,
|
||||
) -> crate::document_runtime::DocumentSandboxPolicy {
|
||||
document_sandbox_policy_from_attribute(
|
||||
let owner_policy = document_sandbox_policy_from_attribute(
|
||||
self.dom_host().get_attribute(handle, "sandbox").as_deref(),
|
||||
)
|
||||
);
|
||||
// A nested browsing context inherits the active sandboxing flags of
|
||||
// its container Document. Preserve those restrictions when a network
|
||||
// navigation replaces the initial empty Document.
|
||||
self.child_browsing_context_parent_handle(handle)
|
||||
.and_then(|parent| {
|
||||
self.child_browsing_contexts
|
||||
.get(&parent)
|
||||
.map(|entry| entry.document_sandbox_policy())
|
||||
})
|
||||
.unwrap_or(self.document_policy_container().sandbox)
|
||||
.with_response_content_security_policy(owner_policy)
|
||||
}
|
||||
|
||||
pub(crate) fn child_browsing_context_document_credentialless(&self, handle: DomHandle) -> bool {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::{
|
||||
JsContextHost, OwnerDispatchScope, WindowExecutionContextIdentity, WindowExecutionContextOwner,
|
||||
child_frames::{ChildAncestorOriginsReferrerPolicy, ChildBrowsingContextEntry},
|
||||
};
|
||||
use crate::document_runtime::DomHandle;
|
||||
|
||||
@@ -7,6 +8,79 @@ const WINDOW_SECURITY_TOKEN_PREFIX: &str = "moli-window-origin-v1:";
|
||||
const WINDOW_ISOLATED_WORLD_SECURITY_TOKEN_PREFIX: &str = "moli-window-isolated-origin-v1:";
|
||||
|
||||
impl JsContextHost {
|
||||
pub(in crate::native_bridge::context_host) fn child_ancestor_origins_referrer_policy_from_owner_attribute(
|
||||
&self,
|
||||
handle: DomHandle,
|
||||
) -> ChildAncestorOriginsReferrerPolicy {
|
||||
match self
|
||||
.dom_host()
|
||||
.get_attribute(handle, "referrerpolicy")
|
||||
.as_deref()
|
||||
.and_then(crate::referrer_policy::normalize_referrer_policy)
|
||||
.as_deref()
|
||||
{
|
||||
Some("no-referrer") => ChildAncestorOriginsReferrerPolicy::NoReferrer,
|
||||
Some("same-origin") => ChildAncestorOriginsReferrerPolicy::SameOrigin,
|
||||
Some(_) | None => ChildAncestorOriginsReferrerPolicy::Default,
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::context_host) fn refresh_current_child_document_ancestor_origins(
|
||||
&mut self,
|
||||
handle: DomHandle,
|
||||
) -> bool {
|
||||
let Some(child_origin) = self.child_window_access_origin(handle) else {
|
||||
return false;
|
||||
};
|
||||
let Some(parent_scope) = self.owner_dispatch_scope_for_node(handle) else {
|
||||
return false;
|
||||
};
|
||||
let Some(parent_origin) = self.window_access_origin_for_dispatch_scope(parent_scope) else {
|
||||
return false;
|
||||
};
|
||||
let parent_ancestor_origins = match parent_scope {
|
||||
OwnerDispatchScope::Child(parent) => self
|
||||
.child_browsing_contexts
|
||||
.get(&parent)
|
||||
.map(ChildBrowsingContextEntry::document_internal_ancestor_origins)
|
||||
.unwrap_or_default(),
|
||||
OwnerDispatchScope::Top | OwnerDispatchScope::LightweightPopup(_) => Vec::new(),
|
||||
};
|
||||
let Some(policy) = self
|
||||
.child_browsing_contexts
|
||||
.get(&handle)
|
||||
.map(ChildBrowsingContextEntry::ancestor_origins_referrer_policy_snapshot)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
let origins = build_child_document_internal_ancestor_origins(
|
||||
parent_origin,
|
||||
&parent_ancestor_origins,
|
||||
&child_origin,
|
||||
policy,
|
||||
);
|
||||
let Some(entry) = self.child_browsing_contexts.get_mut(&handle) else {
|
||||
return false;
|
||||
};
|
||||
entry.set_document_internal_ancestor_origins(origins);
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn child_browsing_context_ancestor_origins(
|
||||
&self,
|
||||
handle: DomHandle,
|
||||
) -> Option<Vec<String>> {
|
||||
self.current_child_document_task_owner(handle)?;
|
||||
Some(
|
||||
self.child_browsing_contexts
|
||||
.get(&handle)?
|
||||
.document_internal_ancestor_origins()
|
||||
.into_iter()
|
||||
.map(|origin| origin.serialized_origin())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn main_default_world_security_token_key(&self) -> Option<String> {
|
||||
let origin = moli_url::origin_ascii_serialization(self.document_url());
|
||||
if self.document_domain_override.is_some() {
|
||||
@@ -358,6 +432,10 @@ impl WindowAccessOrigin {
|
||||
}
|
||||
}
|
||||
|
||||
fn opaque_without_identity() -> Self {
|
||||
Self::Opaque { identity: None }
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::context_host) fn from_serialized_origin(
|
||||
serialized_origin: String,
|
||||
document_domain: Option<String>,
|
||||
@@ -412,7 +490,7 @@ impl WindowAccessOrigin {
|
||||
}
|
||||
}
|
||||
|
||||
fn has_same_origin(&self, target: &Self) -> bool {
|
||||
pub(in crate::native_bridge::context_host) fn has_same_origin(&self, target: &Self) -> bool {
|
||||
match (self, target) {
|
||||
(
|
||||
Self::Opaque {
|
||||
@@ -446,6 +524,36 @@ impl WindowAccessOrigin {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_child_document_internal_ancestor_origins(
|
||||
parent_origin: WindowAccessOrigin,
|
||||
parent_ancestor_origins: &[WindowAccessOrigin],
|
||||
child_origin: &WindowAccessOrigin,
|
||||
policy: ChildAncestorOriginsReferrerPolicy,
|
||||
) -> Vec<WindowAccessOrigin> {
|
||||
let mut masked = match policy {
|
||||
ChildAncestorOriginsReferrerPolicy::NoReferrer => true,
|
||||
ChildAncestorOriginsReferrerPolicy::SameOrigin => {
|
||||
!parent_origin.has_same_origin(child_origin)
|
||||
}
|
||||
ChildAncestorOriginsReferrerPolicy::Default => false,
|
||||
};
|
||||
let mut origins = Vec::with_capacity(1 + parent_ancestor_origins.len());
|
||||
origins.push(if masked {
|
||||
WindowAccessOrigin::opaque_without_identity()
|
||||
} else {
|
||||
parent_origin.clone()
|
||||
});
|
||||
for ancestor_origin in parent_ancestor_origins {
|
||||
if masked && ancestor_origin.has_same_origin(&parent_origin) {
|
||||
origins.push(WindowAccessOrigin::opaque_without_identity());
|
||||
} else {
|
||||
masked = false;
|
||||
origins.push(ancestor_origin.clone());
|
||||
}
|
||||
}
|
||||
origins
|
||||
}
|
||||
|
||||
pub(crate) fn set_window_security_token(
|
||||
scope: &mut v8::PinScope<'_, '_, ()>,
|
||||
context: v8::Local<'_, v8::Context>,
|
||||
@@ -487,7 +595,9 @@ fn window_isolated_world_security_token_key(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
WindowAccessOrigin, window_isolated_world_security_token_key, window_security_token_key,
|
||||
ChildAncestorOriginsReferrerPolicy, WindowAccessOrigin,
|
||||
build_child_document_internal_ancestor_origins, window_isolated_world_security_token_key,
|
||||
window_security_token_key,
|
||||
};
|
||||
use crate::{frame_owner_model::LocalWindowId, native_bridge::WindowExecutionContextOwner};
|
||||
|
||||
@@ -560,6 +670,62 @@ mod tests {
|
||||
assert!(original_with_domain.can_access(&relaxed_peer));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ancestor_origins_mask_the_same_origin_parent_prefix_only() {
|
||||
let origin_a =
|
||||
WindowAccessOrigin::from_serialized_origin("https://a.example.test".to_owned(), None)
|
||||
.expect("origin A");
|
||||
let origin_b =
|
||||
WindowAccessOrigin::from_serialized_origin("https://b.example.test".to_owned(), None)
|
||||
.expect("origin B");
|
||||
|
||||
let no_referrer = build_child_document_internal_ancestor_origins(
|
||||
origin_b.clone(),
|
||||
&[origin_b.clone(), origin_a.clone()],
|
||||
&origin_a,
|
||||
ChildAncestorOriginsReferrerPolicy::NoReferrer,
|
||||
);
|
||||
assert_eq!(
|
||||
no_referrer
|
||||
.iter()
|
||||
.map(WindowAccessOrigin::serialized_origin)
|
||||
.collect::<Vec<_>>(),
|
||||
vec!["null", "null", "https://a.example.test"]
|
||||
);
|
||||
|
||||
let same_origin = build_child_document_internal_ancestor_origins(
|
||||
origin_b.clone(),
|
||||
std::slice::from_ref(&origin_a),
|
||||
&origin_a,
|
||||
ChildAncestorOriginsReferrerPolicy::SameOrigin,
|
||||
);
|
||||
assert_eq!(
|
||||
same_origin
|
||||
.iter()
|
||||
.map(WindowAccessOrigin::serialized_origin)
|
||||
.collect::<Vec<_>>(),
|
||||
vec!["null", "https://a.example.test"]
|
||||
);
|
||||
|
||||
let default = build_child_document_internal_ancestor_origins(
|
||||
origin_b,
|
||||
&[origin_a],
|
||||
&WindowAccessOrigin::from_serialized_origin(
|
||||
"https://child.example.test".to_owned(),
|
||||
None,
|
||||
)
|
||||
.expect("child origin"),
|
||||
ChildAncestorOriginsReferrerPolicy::Default,
|
||||
);
|
||||
assert_eq!(
|
||||
default
|
||||
.iter()
|
||||
.map(WindowAccessOrigin::serialized_origin)
|
||||
.collect::<Vec<_>>(),
|
||||
vec!["https://b.example.test", "https://a.example.test"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn isolated_world_uses_a_distinct_composite_origin_token() {
|
||||
let origin = "https://example.test".to_owned();
|
||||
|
||||
+124
@@ -1578,3 +1578,127 @@ addEventListener("message", event => {{
|
||||
vec!["/middle.html", "/grandchild.html"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn location_ancestor_origins_is_a_stable_document_list_with_a_detached_empty_list() {
|
||||
let mut vm = new_storage_test_vm("https://ancestor-origins.test/page.html");
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const frame = document.createElement("iframe");
|
||||
(document.body || document.documentElement || document).appendChild(frame);
|
||||
const location = frame.contentWindow.location;
|
||||
const ChildDOMStringList = frame.contentWindow.DOMStringList;
|
||||
const active = location.ancestorOrigins;
|
||||
const activeAgain = location.ancestorOrigins;
|
||||
const activeSnapshot = {
|
||||
values: Array.from(active),
|
||||
sameObject: active === activeAgain,
|
||||
brand: active instanceof ChildDOMStringList,
|
||||
topRealmBrand: active instanceof DOMStringList,
|
||||
isArray: Array.isArray(active),
|
||||
length: active.length,
|
||||
item0: active.item(0),
|
||||
item1IsNull: active.item(1) === null,
|
||||
containsParent: active.contains(window.origin)
|
||||
};
|
||||
frame.remove();
|
||||
const detached = location.ancestorOrigins;
|
||||
|
||||
const unreadFrame = document.createElement("iframe");
|
||||
(document.body || document.documentElement || document).appendChild(unreadFrame);
|
||||
const unreadLocation = unreadFrame.contentWindow.location;
|
||||
unreadFrame.remove();
|
||||
const unreadDetached = unreadLocation.ancestorOrigins;
|
||||
return JSON.stringify({
|
||||
active: activeSnapshot,
|
||||
detachedValues: Array.from(detached),
|
||||
detachedIsDifferent: detached !== active,
|
||||
detachedIsStable: detached === location.ancestorOrigins,
|
||||
detachedBrand: detached instanceof ChildDOMStringList,
|
||||
unreadDetachedValues: Array.from(unreadDetached),
|
||||
unreadDetachedIsArray: Array.isArray(unreadDetached),
|
||||
unreadDetachedConstructor: unreadDetached.constructor.name
|
||||
});
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("Location ancestor origins lifetime probe should evaluate");
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"active":{"values":["https://ancestor-origins.test"],"sameObject":true,"brand":true,"topRealmBrand":false,"isArray":false,"length":1,"item0":"https://ancestor-origins.test","item1IsNull":true,"containsParent":true},"detachedValues":[],"detachedIsDifferent":true,"detachedIsStable":true,"detachedBrand":true,"unreadDetachedValues":[],"unreadDetachedIsArray":false,"unreadDetachedConstructor":"DOMStringList"}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn location_ancestor_origins_snapshots_frame_referrer_policy_per_navigation() {
|
||||
let mut vm = new_storage_test_vm("https://ancestor-policy.test/page.html");
|
||||
|
||||
let initial = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const mount = document.body || document.documentElement || document;
|
||||
|
||||
const snapshotted = document.createElement("iframe");
|
||||
snapshotted.srcdoc = "<!doctype html><p>snapshotted</p>";
|
||||
snapshotted.referrerPolicy = "no-referrer";
|
||||
mount.appendChild(snapshotted);
|
||||
const snapshottedLocation = snapshotted.contentWindow.location;
|
||||
const snapshottedInitialList = snapshottedLocation.ancestorOrigins;
|
||||
snapshotted.referrerPolicy = "";
|
||||
|
||||
const futureNavigation = document.createElement("iframe");
|
||||
futureNavigation.referrerPolicy = "no-referrer";
|
||||
mount.appendChild(futureNavigation);
|
||||
const futureLocation = futureNavigation.contentWindow.location;
|
||||
const futureInitialList = futureLocation.ancestorOrigins;
|
||||
futureNavigation.referrerPolicy = "";
|
||||
futureNavigation.srcdoc = "<!doctype html><p>future navigation</p>";
|
||||
|
||||
Object.assign(globalThis, {
|
||||
__ancestorPolicySnapshottedLocation: snapshottedLocation,
|
||||
__ancestorPolicySnapshottedInitialList: snapshottedInitialList,
|
||||
__ancestorPolicyFutureLocation: futureLocation,
|
||||
__ancestorPolicyFutureInitialList: futureInitialList
|
||||
});
|
||||
return JSON.stringify({
|
||||
snapshotted: Array.from(snapshottedInitialList),
|
||||
future: Array.from(futureInitialList)
|
||||
});
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("initial Location ancestor policy probe should evaluate");
|
||||
assert_eq!(initial, r#"{"snapshotted":["null"],"future":["null"]}"#);
|
||||
|
||||
vm.drain_pending_child_frame_work_for_test();
|
||||
|
||||
let committed = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const snapshotted = __ancestorPolicySnapshottedLocation.ancestorOrigins;
|
||||
const future = __ancestorPolicyFutureLocation.ancestorOrigins;
|
||||
return JSON.stringify({
|
||||
snapshotted: Array.from(snapshotted),
|
||||
snapshottedNewDocumentList:
|
||||
snapshotted !== __ancestorPolicySnapshottedInitialList,
|
||||
snapshottedStable:
|
||||
snapshotted === __ancestorPolicySnapshottedLocation.ancestorOrigins,
|
||||
future: Array.from(future),
|
||||
futureNewDocumentList: future !== __ancestorPolicyFutureInitialList,
|
||||
futureStable: future === __ancestorPolicyFutureLocation.ancestorOrigins
|
||||
});
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("committed Location ancestor policy probe should evaluate");
|
||||
assert_eq!(
|
||||
committed,
|
||||
r#"{"snapshotted":["null"],"snapshottedNewDocumentList":true,"snapshottedStable":true,"future":["https://ancestor-policy.test"],"futureNewDocumentList":true,"futureStable":true}"#
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user