mirror of
https://github.com/lexmount/moli.git
synced 2026-10-31 00:06:19 +00:00
refactor(window): dispatch cross-origin access through native tables
Replace WeakMap-backed Window surfaces with native access-check dispatch. Keep per-Window Location identity and resolve dynamic children from the current frame tree. Reuse the ordinary Window accessor value helpers. Apply cross-origin descriptor and fallback rules, preserve borrowed receiver behavior, and cover navigation, named collisions, removal, and realm teardown.
This commit is contained in:
@@ -5113,7 +5113,10 @@ async fn cross_origin_window_proxy_exposes_standard_noop_shape() -> Result<()> {
|
||||
'replace',\
|
||||
'__moliChildBrowsingContextHandle',\
|
||||
'unknownCrossOriginProbe'\
|
||||
].map((name) => `${name}:${name in win.location}:${Object.prototype.hasOwnProperty.call(win.location, name)}`);\
|
||||
].map((name) => {\
|
||||
try { return `${name}:${name in win.location}:${Object.prototype.hasOwnProperty.call(win.location, name)}`; }\
|
||||
catch (error) { return `${name}:${error.name}:${error instanceof DOMException}`; }\
|
||||
});\
|
||||
const calls = ['blur', 'focus', 'close'].map((name) => {\
|
||||
try {\
|
||||
return `${name}:${String(win[name]())}`;\
|
||||
@@ -5150,14 +5153,18 @@ async fn cross_origin_window_proxy_exposes_standard_noop_shape() -> Result<()> {
|
||||
const lengthDescriptor = Object.getOwnPropertyDescriptor(win, 'length');\
|
||||
const locationDescriptor = Object.getOwnPropertyDescriptor(win, 'location');\
|
||||
const locationHrefDescriptor = Object.getOwnPropertyDescriptor(win.location, 'href');\
|
||||
const locationHashDescriptor = Object.getOwnPropertyDescriptor(win.location, 'hash');\
|
||||
const deniedDescriptor = (object, name) => {\
|
||||
try { Object.getOwnPropertyDescriptor(object, name); return { error: 'accepted' }; }\
|
||||
catch (error) { return { error: `${error.name}:${error instanceof DOMException}` }; }\
|
||||
};\
|
||||
const locationHashDescriptor = deniedDescriptor(win.location, 'hash');\
|
||||
const postMessageDescriptor = Object.getOwnPropertyDescriptor(win, 'postMessage');\
|
||||
const noopDescriptors = ['blur', 'focus', 'close'].map((name) => {\
|
||||
const descriptor = Object.getOwnPropertyDescriptor(win, name);\
|
||||
return `${name}:${descriptor?.enumerable}:${descriptor?.configurable}:${descriptor?.writable}:${typeof descriptor?.value}:${descriptor?.value?.name}:${descriptor?.value?.length}`;\
|
||||
});\
|
||||
const locationReplaceDescriptor = Object.getOwnPropertyDescriptor(win.location, 'replace');\
|
||||
const setTimeoutDescriptor = Object.getOwnPropertyDescriptor(win, 'setTimeout');\
|
||||
const setTimeoutDescriptor = deniedDescriptor(win, 'setTimeout');\
|
||||
let locationReplaceInvalidReceiver;\
|
||||
try {\
|
||||
win.location.replace.call({}, '/compat/window-child-browsing-context-target-name-a?via=bad-replace-receiver');\
|
||||
@@ -5233,8 +5240,9 @@ async fn cross_origin_window_proxy_exposes_standard_noop_shape() -> Result<()> {
|
||||
selfDescriptor: {\
|
||||
enumerable: selfDescriptor?.enumerable,\
|
||||
configurable: selfDescriptor?.configurable,\
|
||||
writable: selfDescriptor?.writable,\
|
||||
valueIsSelf: selfDescriptor?.value === win\
|
||||
getterType: typeof selfDescriptor?.get,\
|
||||
setterType: typeof selfDescriptor?.set,\
|
||||
getterIsSelf: selfDescriptor?.get.call(win) === win\
|
||||
},\
|
||||
lengthDescriptor: {\
|
||||
enumerable: lengthDescriptor?.enumerable,\
|
||||
@@ -5254,12 +5262,7 @@ async fn cross_origin_window_proxy_exposes_standard_noop_shape() -> Result<()> {
|
||||
getterType: typeof locationHrefDescriptor?.get,\
|
||||
setterType: typeof locationHrefDescriptor?.set\
|
||||
},\
|
||||
locationHashDescriptor: {\
|
||||
enumerable: locationHashDescriptor?.enumerable,\
|
||||
configurable: locationHashDescriptor?.configurable,\
|
||||
getterType: typeof locationHashDescriptor?.get,\
|
||||
setterType: typeof locationHashDescriptor?.set\
|
||||
},\
|
||||
locationHashDescriptor,\
|
||||
postMessageDescriptor: {\
|
||||
enumerable: postMessageDescriptor?.enumerable,\
|
||||
configurable: postMessageDescriptor?.configurable,\
|
||||
@@ -5281,12 +5284,7 @@ async fn cross_origin_window_proxy_exposes_standard_noop_shape() -> Result<()> {
|
||||
locationReplaceForgedReceiver,\
|
||||
locationHrefSetterInvalidReceiver,\
|
||||
locationGetterInvalidReceiver,\
|
||||
setTimeoutDescriptor: {\
|
||||
enumerable: setTimeoutDescriptor?.enumerable,\
|
||||
configurable: setTimeoutDescriptor?.configurable,\
|
||||
getterType: typeof setTimeoutDescriptor?.get,\
|
||||
setterType: typeof setTimeoutDescriptor?.set\
|
||||
},\
|
||||
setTimeoutDescriptor,\
|
||||
documentDescriptor: documentDescriptorShape,\
|
||||
windowLocationAssignResult,\
|
||||
locationStableAfterWindowAssign: win.location === locationBeforeWindowAssign,\
|
||||
@@ -5304,7 +5302,7 @@ async fn cross_origin_window_proxy_exposes_standard_noop_shape() -> Result<()> {
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(
|
||||
r#"{"self":true,"window":true,"frames":true,"parent":true,"top":true,"opener":true,"thenType":"undefined","length":3,"closed":false,"blurType":"function","focusType":"function","closeType":"function","postMessageType":"function","restrictedMutationProbe":["deleteDocument:SecurityError","deleteSetTimeout:SecurityError","defineDocument:SecurityError","definePostMessage:SecurityError","deleteLocationHref:SecurityError","defineLocationHref:SecurityError"],"hasProbe":["document:true:true","setTimeout:true:true","postMessage:true:true","location:true:true","self:true:true","window:true:true","frames:true:true","parent:true:true","top:true:true","closed:true:true","opener:true:true","then:true:true","__moliChildBrowsingContextHandle:SecurityError:true","__moliCrossOriginWindowLocation:SecurityError:true","unknownCrossOriginProbe:SecurityError:true"],"locationHasProbe":["href:true:true","hash:true:true","replace:true:true","__moliChildBrowsingContextHandle:false:false","unknownCrossOriginProbe:false:false"],"calls":["blur:undefined","focus:undefined","close:undefined"],"invalidNoopReceivers":["blur:TypeError:true","focus:TypeError:true","close:TypeError:true"],"postMessageWindowReceiver":"ok","postMessageInvalidReceiver":"TypeError:true","ownNamesLeakInternal":false,"ownKeysLeakInternal":false,"locationOwnNamesLeakInternal":false,"selfDescriptor":{"enumerable":false,"configurable":false,"writable":false,"valueIsSelf":true},"lengthDescriptor":{"enumerable":false,"configurable":false,"getterType":"function","setterType":"function"},"locationDescriptor":{"enumerable":false,"configurable":false,"getterType":"function","setterType":"function"},"locationHrefDescriptor":{"enumerable":false,"configurable":false,"getterType":"undefined","setterType":"function"},"locationHashDescriptor":{"enumerable":false,"configurable":false,"getterType":"function","setterType":"function"},"postMessageDescriptor":{"enumerable":false,"configurable":false,"writable":false,"valueType":"function","valueName":"postMessage","valueLength":1},"noopDescriptors":["blur:false:false:false:function:blur:0","focus:false:false:false:function:focus:0","close:false:false:false:function:close:0"],"locationReplaceDescriptor":{"enumerable":false,"configurable":false,"writable":false,"valueType":"function","valueName":"replace","valueLength":1},"locationReplaceInvalidReceiver":"TypeError:true","locationReplaceForgedReceiver":"TypeError:true","locationHrefSetterInvalidReceiver":"TypeError:true","locationGetterInvalidReceiver":"TypeError:true","setTimeoutDescriptor":{"enumerable":false,"configurable":false,"getterType":"function","setterType":"function"},"documentDescriptor":{"enumerable":false,"configurable":false,"getterType":"function","setterType":"function"},"windowLocationAssignResult":"ok","locationStableAfterWindowAssign":true,"deniedWindowProbe":["document:SecurityError:true","frameElement:SecurityError:true","history:SecurityError:true","navigation:SecurityError:true","localStorage:SecurityError:true","sessionStorage:SecurityError:true","indexedDB:SecurityError:true","customElements:SecurityError:true","navigator:SecurityError:true","performance:SecurityError:true","console:SecurityError:true","screen:SecurityError:true","visualViewport:SecurityError:true","crypto:SecurityError:true","caches:SecurityError:true","clientInformation:SecurityError:true","cookieStore:SecurityError:true","credentialless:SecurityError:true","crossOriginIsolated:SecurityError:true","documentPictureInPicture:SecurityError:true","fetch:SecurityError:true","isSecureContext:SecurityError:true","origin:SecurityError:true","originAgentCluster:SecurityError:true","scheduler:SecurityError:true","speechSynthesis:SecurityError:true","structuredClone:SecurityError:true","trustedTypes:SecurityError:true","setTimeout:SecurityError:true","clearImmediate:SecurityError:true","addEventListener:SecurityError:true","dispatchEvent:SecurityError:true","queueMicrotask:SecurityError:true","requestAnimationFrame:SecurityError:true","getComputedStyle:SecurityError:true","getSelection:SecurityError:true","matchMedia:SecurityError:true","event:SecurityError:true","onerror:SecurityError:true","innerWidth:SecurityError:true","innerHeight:SecurityError:true","devicePixelRatio:SecurityError:true","scrollX:SecurityError:true","pageYOffset:SecurityError:true","scrollTo:SecurityError:true","open:SecurityError:true","stop:SecurityError:true","print:SecurityError:true","find:SecurityError:true","alert:SecurityError:true","confirm:SecurityError:true","prompt:SecurityError:true","reportError:SecurityError:true","btoa:SecurityError:true","atob:SecurityError:true"],"documentAccess":"SecurityError:true"}"#.to_owned(),
|
||||
r#"{"self":true,"window":true,"frames":true,"parent":true,"top":true,"opener":true,"thenType":"undefined","length":3,"closed":false,"blurType":"function","focusType":"function","closeType":"function","postMessageType":"function","restrictedMutationProbe":["deleteDocument:SecurityError","deleteSetTimeout:SecurityError","defineDocument:SecurityError","definePostMessage:SecurityError","deleteLocationHref:SecurityError","defineLocationHref:SecurityError"],"hasProbe":["document:true:true","setTimeout:SecurityError:true","postMessage:true:true","location:true:true","self:true:true","window:true:true","frames:true:true","parent:true:true","top:true:true","closed:true:true","opener:true:true","then:true:true","__moliChildBrowsingContextHandle:SecurityError:true","__moliCrossOriginWindowLocation:SecurityError:true","unknownCrossOriginProbe:SecurityError:true"],"locationHasProbe":["href:true:true","hash:SecurityError:true","replace:true:true","__moliChildBrowsingContextHandle:SecurityError:true","unknownCrossOriginProbe:SecurityError:true"],"calls":["blur:undefined","focus:undefined","close:undefined"],"invalidNoopReceivers":["blur:TypeError:true","focus:TypeError:true","close:TypeError:true"],"postMessageWindowReceiver":"ok","postMessageInvalidReceiver":"TypeError:true","ownNamesLeakInternal":false,"ownKeysLeakInternal":false,"locationOwnNamesLeakInternal":false,"selfDescriptor":{"enumerable":false,"configurable":true,"getterType":"function","setterType":"undefined","getterIsSelf":true},"lengthDescriptor":{"enumerable":false,"configurable":true,"getterType":"function","setterType":"undefined"},"locationDescriptor":{"enumerable":false,"configurable":true,"getterType":"function","setterType":"function"},"locationHrefDescriptor":{"enumerable":false,"configurable":true,"getterType":"undefined","setterType":"function"},"locationHashDescriptor":{"error":"SecurityError:true"},"postMessageDescriptor":{"enumerable":false,"configurable":true,"writable":false,"valueType":"function","valueName":"postMessage","valueLength":1},"noopDescriptors":["blur:false:true:false:function:blur:0","focus:false:true:false:function:focus:0","close:false:true:false:function:close:0"],"locationReplaceDescriptor":{"enumerable":false,"configurable":true,"writable":false,"valueType":"function","valueName":"replace","valueLength":1},"locationReplaceInvalidReceiver":"TypeError:true","locationReplaceForgedReceiver":"TypeError:true","locationHrefSetterInvalidReceiver":"TypeError:true","locationGetterInvalidReceiver":"TypeError:true","setTimeoutDescriptor":{"error":"SecurityError:true"},"documentDescriptor":{"enumerable":false,"configurable":true,"getterType":"undefined","setterType":"undefined"},"windowLocationAssignResult":"ok","locationStableAfterWindowAssign":true,"deniedWindowProbe":["document:read","frameElement:SecurityError:true","history:SecurityError:true","navigation:SecurityError:true","localStorage:SecurityError:true","sessionStorage:SecurityError:true","indexedDB:SecurityError:true","customElements:SecurityError:true","navigator:SecurityError:true","performance:SecurityError:true","console:SecurityError:true","screen:SecurityError:true","visualViewport:SecurityError:true","crypto:SecurityError:true","caches:SecurityError:true","clientInformation:SecurityError:true","cookieStore:SecurityError:true","credentialless:SecurityError:true","crossOriginIsolated:SecurityError:true","documentPictureInPicture:SecurityError:true","fetch:SecurityError:true","isSecureContext:SecurityError:true","origin:SecurityError:true","originAgentCluster:SecurityError:true","scheduler:SecurityError:true","speechSynthesis:SecurityError:true","structuredClone:SecurityError:true","trustedTypes:SecurityError:true","setTimeout:SecurityError:true","clearImmediate:SecurityError:true","addEventListener:SecurityError:true","dispatchEvent:SecurityError:true","queueMicrotask:SecurityError:true","requestAnimationFrame:SecurityError:true","getComputedStyle:SecurityError:true","getSelection:SecurityError:true","matchMedia:SecurityError:true","event:SecurityError:true","onerror:SecurityError:true","innerWidth:SecurityError:true","innerHeight:SecurityError:true","devicePixelRatio:SecurityError:true","scrollX:SecurityError:true","pageYOffset:SecurityError:true","scrollTo:SecurityError:true","open:SecurityError:true","stop:SecurityError:true","print:SecurityError:true","find:SecurityError:true","alert:SecurityError:true","confirm:SecurityError:true","prompt:SecurityError:true","reportError:SecurityError:true","btoa:SecurityError:true","atob:SecurityError:true"],"documentAccess":"read"}"#.to_owned(),
|
||||
),
|
||||
"{}",
|
||||
page.serialize_html_async().await.unwrap()
|
||||
@@ -5454,7 +5452,7 @@ async fn cross_origin_window_proxy_exposes_named_child_frames() -> Result<()> {
|
||||
assert_eq!(
|
||||
before,
|
||||
Some(
|
||||
r#"{"hasNamed":true,"ownNamed":true,"hasDocumentCollision":true,"hasFocusCollision":true,"sameAsIndexed":true,"documentDescriptor":{"enumerable":false,"configurable":false,"getterType":"function","setterType":"function","valueType":"undefined"},"focusDescriptor":{"enumerable":false,"configurable":false,"writable":false,"valueType":"function"},"documentAccess":"SecurityError:true","focusType":"function","namedSelf":true,"namedWindow":true,"namedFrames":true,"namedParent":true,"namedTop":true,"namedLength":0,"descriptor":{"enumerable":false,"configurable":true,"writable":false,"valueType":"object"}}"#
|
||||
r#"{"hasNamed":true,"ownNamed":true,"hasDocumentCollision":true,"hasFocusCollision":true,"sameAsIndexed":true,"documentDescriptor":{"enumerable":false,"configurable":true,"getterType":"undefined","setterType":"undefined","valueType":"object"},"focusDescriptor":{"enumerable":false,"configurable":true,"writable":false,"valueType":"function"},"documentAccess":"read","focusType":"function","namedSelf":true,"namedWindow":true,"namedFrames":true,"namedParent":true,"namedTop":true,"namedLength":0,"descriptor":{"enumerable":false,"configurable":true,"writable":false,"valueType":"object"}}"#
|
||||
.to_owned(),
|
||||
),
|
||||
"{}",
|
||||
@@ -5497,7 +5495,7 @@ async fn cross_origin_window_proxy_exposes_named_child_frames() -> Result<()> {
|
||||
afterOwnNamed: probe(() => Object.prototype.hasOwnProperty.call(refreshed, 'nestedNamed')),\
|
||||
afterNamedType: probe(() => typeof refreshed.nestedNamed),\
|
||||
afterLength: refreshed.length,\
|
||||
afterDocumentDescriptorGetter: typeof Object.getOwnPropertyDescriptor(refreshed, 'document')?.get,\
|
||||
afterDocumentDescriptorGetter: probe(() => typeof Object.getOwnPropertyDescriptor(refreshed, 'document')?.get),\
|
||||
afterFocusType: typeof refreshed.focus\
|
||||
});\
|
||||
})()",
|
||||
@@ -5508,7 +5506,7 @@ async fn cross_origin_window_proxy_exposes_named_child_frames() -> Result<()> {
|
||||
assert_eq!(
|
||||
after,
|
||||
Some(
|
||||
r#"{"afterHasNamed":"SecurityError:true","afterOwnNamed":"SecurityError:true","afterNamedType":"SecurityError:true","afterLength":0,"afterDocumentDescriptorGetter":"function","afterFocusType":"function"}"#
|
||||
r#"{"afterHasNamed":"SecurityError:true","afterOwnNamed":"SecurityError:true","afterNamedType":"SecurityError:true","afterLength":0,"afterDocumentDescriptorGetter":"SecurityError:true","afterFocusType":"function"}"#
|
||||
.to_owned(),
|
||||
),
|
||||
"{}",
|
||||
|
||||
@@ -208,8 +208,8 @@ mod webrtc;
|
||||
mod websocket;
|
||||
mod window_accessors;
|
||||
pub(crate) use window_accessors::{
|
||||
cached_cross_origin_window_surface, window_closed_getter, window_length_getter,
|
||||
window_opener_getter, window_parent_getter, window_top_getter,
|
||||
window_closed_value, window_frames_value, window_length_value, window_opener_value,
|
||||
window_parent_value, window_self_value, window_top_value,
|
||||
};
|
||||
mod window_events;
|
||||
mod window_lazy_surface;
|
||||
|
||||
@@ -1724,7 +1724,6 @@ pub(crate) fn finish_context_bootstrap(
|
||||
document_runtime: &mut JsContextHost,
|
||||
secure_context_url: &url::Url,
|
||||
) -> Result<()> {
|
||||
super::window_accessors::initialize_cross_origin_window_cache(scope)?;
|
||||
super::exposed_interfaces::initialize_realm_interface_registry(
|
||||
scope,
|
||||
super::exposed_interfaces::RealmKind::Window,
|
||||
|
||||
@@ -8,11 +8,11 @@ use crate::{
|
||||
};
|
||||
|
||||
mod child_context;
|
||||
mod cross_origin_cache;
|
||||
mod document;
|
||||
mod helpers;
|
||||
mod interceptors;
|
||||
mod surface;
|
||||
mod values;
|
||||
|
||||
use super::CHILD_BROWSING_CONTEXT_HANDLE_SLOT as WINDOW_CHILD_CONTEXT_HANDLE_SLOT;
|
||||
|
||||
@@ -22,8 +22,6 @@ pub(crate) use child_context::window_length_getter;
|
||||
pub(super) use child_context::{
|
||||
window_credentialless_getter, window_cross_origin_isolated_getter, window_frame_element_getter,
|
||||
};
|
||||
pub(crate) use cross_origin_cache::cached_cross_origin_window_surface;
|
||||
pub(super) use cross_origin_cache::initialize_cross_origin_window_cache;
|
||||
pub(super) use document::{WINDOW_DOCUMENT_SLOT, window_document_getter_template};
|
||||
pub(crate) use document::{
|
||||
bind_current_child_window_document, retain_window_document_in_retired_realm,
|
||||
@@ -46,3 +44,7 @@ pub(super) use surface::{
|
||||
window_screen_getter, window_scroll_x_getter, window_scroll_y_getter, window_self_getter,
|
||||
window_speech_synthesis_getter, window_visual_viewport_getter, window_window_getter,
|
||||
};
|
||||
pub(crate) use values::{
|
||||
window_closed_value, window_frames_value, window_length_value, window_opener_value,
|
||||
window_parent_value, window_self_value, window_top_value,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use super::helpers::{
|
||||
window_child_context_handle, window_current_dispatch_scope, window_hidden_value,
|
||||
window_host_ptr, window_is_closed, window_receiver,
|
||||
window_child_context_handle, window_hidden_value, window_host_ptr, window_receiver,
|
||||
};
|
||||
use super::*;
|
||||
|
||||
@@ -12,25 +11,9 @@ pub(crate) fn window_length_getter<'s>(
|
||||
let Some(receiver) = window_receiver(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
if window_is_closed(scope, receiver) {
|
||||
rv.set_uint32(0);
|
||||
return;
|
||||
if let Some(value) = super::values::window_length_value(scope, receiver) {
|
||||
rv.set(value);
|
||||
}
|
||||
let Some(host_ptr) = window_host_ptr(scope, receiver) else {
|
||||
rv.set(v8::Number::new(scope, 0.0).into());
|
||||
return;
|
||||
};
|
||||
let runtime = unsafe { &mut *host_ptr };
|
||||
let Some(owner) = window_current_dispatch_scope(scope, receiver, runtime) else {
|
||||
rv.set(v8::Number::new(scope, 0.0).into());
|
||||
return;
|
||||
};
|
||||
let count = if let Some(handle) = owner.child_window() {
|
||||
runtime.child_browsing_context_child_frame_count(handle)
|
||||
} else {
|
||||
runtime.child_browsing_context_count()
|
||||
};
|
||||
rv.set(v8::Number::new(scope, count as f64).into());
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn window_frame_element_getter<'s>(
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
use anyhow::{Result, anyhow};
|
||||
|
||||
use crate::util::{get_private_value, set_private_value, v8str};
|
||||
|
||||
const CACHE: &str = "__moliCrossOriginWindowSurfaceCache";
|
||||
|
||||
/// Capture the builtins before author code runs. The array is owned by V8's
|
||||
/// realm graph, so neither the cache nor its functions root a retired realm.
|
||||
/// Slot zero starts as the constructor and becomes a WeakMap on first use.
|
||||
pub(in crate::context_bootstrap) fn initialize_cross_origin_window_cache(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
) -> Result<()> {
|
||||
let context = scope.get_current_context();
|
||||
let anchor = context.get_extras_binding_object(scope);
|
||||
if get_private_value(scope, anchor, CACHE).is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
let constructor = context
|
||||
.global(scope)
|
||||
.get(scope, v8str(scope, "WeakMap").into())
|
||||
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
||||
.ok_or_else(|| anyhow!("missing intrinsic WeakMap constructor"))?;
|
||||
let prototype = constructor
|
||||
.get(scope, v8str(scope, "prototype").into())
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
.ok_or_else(|| anyhow!("missing intrinsic WeakMap prototype"))?;
|
||||
let [get, set] = ["get", "set"].map(|name| {
|
||||
prototype
|
||||
.get(scope, v8str(scope, name).into())
|
||||
.filter(|value| value.is_function())
|
||||
.ok_or_else(|| anyhow!("missing intrinsic WeakMap.{name}"))
|
||||
});
|
||||
let entries = [constructor.into(), get?, set?];
|
||||
let cache = v8::Array::new_with_elements(scope, &entries);
|
||||
set_private_value(scope, anchor, CACHE, cache.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Each target owns an ephemeron cache keyed by the accessing realm. This
|
||||
/// preserves per-observer function identity without retaining dead observers.
|
||||
pub(crate) fn cached_cross_origin_window_surface<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
target: v8::Local<'s, v8::Context>,
|
||||
accessing: v8::Local<'s, v8::Context>,
|
||||
build: impl FnOnce(&mut v8::PinScope<'s, '_>) -> v8::Local<'s, v8::Object>,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let (map, get, set) = {
|
||||
let scope = &mut v8::ContextScope::new(scope, target);
|
||||
let anchor = target.get_extras_binding_object(scope);
|
||||
let cache = get_private_value(scope, anchor, CACHE)
|
||||
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
|
||||
.expect("Window cross-origin cache initialized during bootstrap");
|
||||
let value = cache.get_index(scope, 0).expect("Window cache slot");
|
||||
let map = if let Ok(constructor) = v8::Local::<v8::Function>::try_from(value) {
|
||||
let map = constructor
|
||||
.new_instance(scope, &[])
|
||||
.expect("intrinsic WeakMap construction");
|
||||
assert_eq!(cache.set_index(scope, 0, map.into()), Some(true));
|
||||
map
|
||||
} else {
|
||||
v8::Local::<v8::Object>::try_from(value).expect("Window surface WeakMap")
|
||||
};
|
||||
let method = |scope: &mut v8::PinScope<'s, '_>, index| {
|
||||
cache
|
||||
.get_index(scope, index)
|
||||
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
||||
.expect("captured intrinsic WeakMap method")
|
||||
};
|
||||
(map, method(scope, 1), method(scope, 2))
|
||||
};
|
||||
let scope = &mut v8::ContextScope::new(scope, accessing);
|
||||
let key = accessing.get_extras_binding_object(scope);
|
||||
let existing = get
|
||||
.call(scope, map.into(), &[key.into()])
|
||||
.expect("intrinsic WeakMap.get");
|
||||
if let Ok(surface) = v8::Local::<v8::Object>::try_from(existing) {
|
||||
return surface;
|
||||
}
|
||||
let surface = build(scope);
|
||||
set.call(scope, map.into(), &[key.into(), surface.into()])
|
||||
.expect("intrinsic WeakMap.set");
|
||||
surface
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
use super::helpers::{
|
||||
window_child_context_handle, window_hidden_value, window_host_ptr, window_is_closed,
|
||||
window_receiver,
|
||||
window_child_context_handle, window_hidden_value, window_host_ptr, window_receiver,
|
||||
};
|
||||
use super::*;
|
||||
|
||||
@@ -94,19 +93,9 @@ fn set_receiver_window_alias<'s>(
|
||||
let Some(receiver) = window_receiver(scope, args) else {
|
||||
return;
|
||||
};
|
||||
if matches!(slot, WINDOW_PARENT_SLOT | WINDOW_TOP_SLOT) && window_is_closed(scope, receiver) {
|
||||
rv.set_null();
|
||||
return;
|
||||
if let Some(value) = super::values::window_alias_value(scope, receiver, slot) {
|
||||
rv.set(value);
|
||||
}
|
||||
// Cross-origin descriptors run in the accessing realm. Only these public
|
||||
// Window aliases resolve their internal slots in the receiver's realm.
|
||||
let context = receiver
|
||||
.get_creation_context(scope)
|
||||
.unwrap_or_else(|| scope.get_current_context());
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
rv.set(
|
||||
window_hidden_value(scope, receiver, slot).unwrap_or_else(|| context.global(scope).into()),
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn window_opener_getter<'s>(
|
||||
@@ -117,32 +106,9 @@ pub(crate) fn window_opener_getter<'s>(
|
||||
let Some(receiver) = window_receiver(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
rv.set_null();
|
||||
if window_is_closed(scope, receiver) || window_child_context_handle(scope, receiver).is_some() {
|
||||
return;
|
||||
if let Some(value) = super::values::window_opener_value(scope, receiver) {
|
||||
rv.set(value);
|
||||
}
|
||||
let Some(host) = receiver
|
||||
.get_creation_context(scope)
|
||||
.and_then(context_host_ptr_from_context_slot)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(environment) = unsafe { &*host }.page_script_environment() else {
|
||||
return;
|
||||
};
|
||||
let Some(opener) = environment.opener_in_scope(scope) else {
|
||||
return;
|
||||
};
|
||||
let Some(opener_host) = opener
|
||||
.get_creation_context(scope)
|
||||
.and_then(context_host_ptr_from_context_slot)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if unsafe { &*opener_host }.browsing_context_is_closed() {
|
||||
return;
|
||||
}
|
||||
rv.set(opener.into());
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn window_opener_setter<'s>(
|
||||
@@ -363,5 +329,7 @@ pub(crate) fn window_closed_getter<'s>(
|
||||
let Some(receiver) = window_receiver(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
rv.set_bool(window_is_closed(scope, receiver));
|
||||
if let Some(value) = super::values::window_closed_value(scope, receiver) {
|
||||
rv.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
use super::helpers::{
|
||||
window_current_dispatch_scope, window_hidden_value, window_host_ptr, window_is_closed,
|
||||
};
|
||||
use super::*;
|
||||
|
||||
pub(super) fn window_alias_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
slot: &'static str,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
if matches!(slot, WINDOW_PARENT_SLOT | WINDOW_TOP_SLOT) && window_is_closed(scope, receiver) {
|
||||
return Some(v8::null(scope).into());
|
||||
}
|
||||
let context = receiver
|
||||
.get_creation_context(scope)
|
||||
.unwrap_or_else(|| scope.get_current_context());
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
Some(window_hidden_value(scope, receiver, slot).unwrap_or_else(|| context.global(scope).into()))
|
||||
}
|
||||
|
||||
macro_rules! alias {
|
||||
($name:ident, $slot:ident) => {
|
||||
pub(crate) fn $name<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
window_alias_value(scope, receiver, $slot)
|
||||
}
|
||||
};
|
||||
}
|
||||
alias!(window_self_value, WINDOW_SELF_SLOT);
|
||||
alias!(window_frames_value, WINDOW_FRAMES_SLOT);
|
||||
alias!(window_parent_value, WINDOW_PARENT_SLOT);
|
||||
alias!(window_top_value, WINDOW_TOP_SLOT);
|
||||
|
||||
pub(crate) fn window_closed_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
let closed = window_is_closed(scope, receiver);
|
||||
Some(v8::Boolean::new(scope, closed).into())
|
||||
}
|
||||
|
||||
pub(crate) fn window_length_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
let count = if !window_is_closed(scope, receiver)
|
||||
&& let Some(host) = window_host_ptr(scope, receiver)
|
||||
&& let Some(owner) = window_current_dispatch_scope(scope, receiver, unsafe { &*host })
|
||||
{
|
||||
let host = unsafe { &mut *host };
|
||||
match owner.child_window() {
|
||||
Some(handle) => host.child_browsing_context_child_frame_count(handle),
|
||||
None => host.child_browsing_context_count(),
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
Some(v8::Number::new(scope, count as f64).into())
|
||||
}
|
||||
|
||||
pub(crate) fn window_opener_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
let null = v8::null(scope).into();
|
||||
if window_is_closed(scope, receiver) || window_child_context_handle(scope, receiver).is_some() {
|
||||
return Some(null);
|
||||
}
|
||||
let opener = (|| {
|
||||
let host = receiver
|
||||
.get_creation_context(scope)
|
||||
.and_then(context_host_ptr_from_context_slot)?;
|
||||
let environment = unsafe { &*host }.page_script_environment()?;
|
||||
let opener = environment.opener_in_scope(scope)?;
|
||||
let host = opener
|
||||
.get_creation_context(scope)
|
||||
.and_then(context_host_ptr_from_context_slot)?;
|
||||
(!unsafe { &*host }.browsing_context_is_closed()).then_some(opener)
|
||||
})();
|
||||
Some(opener.map(Into::into).unwrap_or(null))
|
||||
}
|
||||
@@ -257,13 +257,6 @@ impl JsContextHost {
|
||||
global,
|
||||
projection.document,
|
||||
);
|
||||
self.install_child_window_proxy_cross_origin_access_surface(
|
||||
scope,
|
||||
init.handle,
|
||||
global,
|
||||
projection.parent,
|
||||
projection.top,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+1
-5
@@ -67,11 +67,7 @@ pub(in crate::native_bridge::context_host::child_frame_runtime) fn initialize_ch
|
||||
validate_child_window_realm_snapshot(host, &snapshot)?;
|
||||
bind_document(host, scope, &snapshot, document)?;
|
||||
|
||||
Ok(ChildWindowRealmProjection {
|
||||
parent,
|
||||
top,
|
||||
document,
|
||||
})
|
||||
Ok(ChildWindowRealmProjection { top, document })
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::context_host::child_frame_runtime) fn rebind_child_window_realm_document_state<
|
||||
|
||||
-2
@@ -55,8 +55,6 @@ pub(in crate::native_bridge::context_host::child_frame_runtime) struct ChildWind
|
||||
pub(in crate::native_bridge::context_host::child_frame_runtime) struct ChildWindowRealmProjection<
|
||||
's,
|
||||
> {
|
||||
pub(in crate::native_bridge::context_host::child_frame_runtime) parent:
|
||||
v8::Local<'s, v8::Object>,
|
||||
pub(in crate::native_bridge::context_host::child_frame_runtime) top: v8::Local<'s, v8::Object>,
|
||||
pub(in crate::native_bridge::context_host::child_frame_runtime) document:
|
||||
v8::Local<'s, v8::Object>,
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
CHILD_BROWSING_CONTEXT_HANDLE_SLOT,
|
||||
dispatch_cross_document_navigation_navigate_event_for_window,
|
||||
},
|
||||
definitions::{define_function_accessor_property, define_get_set_property},
|
||||
definitions::define_function_accessor_property,
|
||||
document_runtime::DomHandle,
|
||||
document_script_scheduler::FrameDocumentClassicScriptSchedulerWork,
|
||||
native_bridge::{
|
||||
@@ -19,13 +19,15 @@ use crate::{
|
||||
},
|
||||
webidl, window_host,
|
||||
};
|
||||
use moli_webapi_declare::WebApiObject;
|
||||
use moli_webapi_declare::{WebApiCrossOriginDeclaration, WebApiObject};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
mod main_page;
|
||||
mod cross_origin_bindings;
|
||||
mod location;
|
||||
use cross_origin_bindings::*;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct ChildWindowProxyFacadeContextHandle(DomHandle);
|
||||
@@ -51,7 +53,6 @@ struct ChildWindowProxyRecord {
|
||||
realm_top_window_wrapper: Option<v8::Global<v8::Object>>,
|
||||
live_window_exposed_to_top: bool,
|
||||
cross_origin_window_proxy: Option<v8::Global<v8::Object>>,
|
||||
cross_origin_access_surface: Option<v8::Global<v8::Object>>,
|
||||
default_execution_context_id: Option<i64>,
|
||||
}
|
||||
|
||||
@@ -114,11 +115,10 @@ impl ChildWindowProxyRecords {
|
||||
self.records.get_mut(&handle)?.facade_context.take()
|
||||
}
|
||||
|
||||
fn clear_retired_realm_surfaces(&mut self, handle: DomHandle) {
|
||||
fn clear_retired_realm_projections(&mut self, handle: DomHandle) {
|
||||
let Some(record) = self.records.get_mut(&handle) else {
|
||||
return;
|
||||
};
|
||||
record.cross_origin_access_surface = None;
|
||||
record.cross_origin_endpoint_projections.clear();
|
||||
record.realm_top_window_wrapper = None;
|
||||
}
|
||||
@@ -131,7 +131,6 @@ impl ChildWindowProxyRecords {
|
||||
) {
|
||||
let record = self.record_mut(handle);
|
||||
record.cross_origin_window_proxy = None;
|
||||
record.cross_origin_access_surface = None;
|
||||
record.live_window_wrapper = Some(v8::Global::new(scope, window));
|
||||
}
|
||||
|
||||
@@ -276,30 +275,6 @@ impl ChildWindowProxyRecords {
|
||||
self.record_mut(handle).cross_origin_window_proxy = Some(v8::Global::new(scope, proxy));
|
||||
}
|
||||
|
||||
fn set_cross_origin_access_surface(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
handle: DomHandle,
|
||||
access_surface: v8::Local<'_, v8::Object>,
|
||||
) {
|
||||
self.record_mut(handle).cross_origin_access_surface =
|
||||
Some(v8::Global::new(scope, access_surface));
|
||||
}
|
||||
|
||||
fn cross_origin_handler_data<'s>(
|
||||
&self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
handle: DomHandle,
|
||||
) -> Option<(v8::Local<'s, v8::Object>, v8::Local<'s, v8::Object>)> {
|
||||
let record = self.records.get(&handle)?;
|
||||
let access_surface = record.cross_origin_access_surface.as_ref()?;
|
||||
let window_proxy = record.live_window_wrapper.as_ref()?;
|
||||
Some((
|
||||
v8::Local::new(scope, access_surface),
|
||||
v8::Local::new(scope, window_proxy),
|
||||
))
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::context_host) fn set_default_execution_context_id(
|
||||
&mut self,
|
||||
handle: DomHandle,
|
||||
@@ -599,21 +574,6 @@ const CROSS_ORIGIN_DENIED_WINDOW_PROPERTIES: &[&str] = &[
|
||||
"atob",
|
||||
];
|
||||
|
||||
const CROSS_ORIGIN_LOCATION_DENIED_PROPERTIES: &[&str] = &[
|
||||
"ancestorOrigins",
|
||||
"assign",
|
||||
"hash",
|
||||
"host",
|
||||
"hostname",
|
||||
"origin",
|
||||
"pathname",
|
||||
"port",
|
||||
"protocol",
|
||||
"reload",
|
||||
"search",
|
||||
"toString",
|
||||
];
|
||||
|
||||
const CROSS_ORIGIN_WINDOW_NOOP_METHODS: &[&str] = &["blur", "close", "focus"];
|
||||
|
||||
const CROSS_ORIGIN_WINDOW_LOCATION_SLOT: &str = "__moliCrossOriginWindowLocation";
|
||||
@@ -789,19 +749,6 @@ struct CrossOriginPropertyDescriptorDeclaration<'scope> {
|
||||
configurable: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, WebApiObject)]
|
||||
#[webapi(plain)]
|
||||
struct CrossOriginLocationMethodsDeclaration {
|
||||
#[webapi(
|
||||
method,
|
||||
length = 1,
|
||||
callback = cross_origin_location_replace_callback,
|
||||
readonly,
|
||||
dont_delete
|
||||
)]
|
||||
replace: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(plain)]
|
||||
struct CrossOriginLocationProxyHandlerDeclaration {
|
||||
@@ -811,6 +758,10 @@ struct CrossOriginLocationProxyHandlerDeclaration {
|
||||
set: (),
|
||||
#[webapi(method, length = 2, callback = cross_origin_location_proxy_descriptor_callback)]
|
||||
get_own_property_descriptor: (),
|
||||
#[webapi(method, length = 1, callback = cross_origin_location_proxy_own_keys_callback)]
|
||||
own_keys: (),
|
||||
#[webapi(method, length = 2, callback = cross_origin_location_proxy_has_callback)]
|
||||
has: (),
|
||||
#[webapi(method, length = 2, callback = cross_origin_window_denied_callback)]
|
||||
delete_property: (),
|
||||
#[webapi(method, length = 3, callback = cross_origin_window_denied_callback)]
|
||||
@@ -1047,11 +998,10 @@ impl JsContextHost {
|
||||
// old inner global. Cross-origin callers can observe only the stable
|
||||
// WindowProxy whitelist, so park that proxy on a restricted facade.
|
||||
if self.top_window_can_access_child(handle) {
|
||||
// These helpers are objects in the outgoing inner realm. Keep
|
||||
// the stable detached proxy, but do not root its retired Context
|
||||
// through the access surface while awaiting a replacement realm.
|
||||
// Drop caller-realm endpoint projections while the stable
|
||||
// WindowProxy waits for a replacement LocalWindow.
|
||||
self.child_window_proxy_records
|
||||
.clear_retired_realm_surfaces(handle);
|
||||
.clear_retired_realm_projections(handle);
|
||||
return true;
|
||||
}
|
||||
let Some(window_proxy) = self.child_window_proxy_records.live_window(scope, handle) else {
|
||||
@@ -1098,19 +1048,6 @@ impl JsContextHost {
|
||||
child_frame_count,
|
||||
&named_indices,
|
||||
);
|
||||
let access_surface = new_null_prototype_object(facade_scope);
|
||||
install_live_cross_origin_child_window_surface(
|
||||
facade_scope,
|
||||
access_surface,
|
||||
handle,
|
||||
parent,
|
||||
top,
|
||||
window_proxy,
|
||||
child_frame_count,
|
||||
&named_indices,
|
||||
);
|
||||
self.child_window_proxy_records
|
||||
.set_cross_origin_access_surface(facade_scope, handle, access_surface);
|
||||
self.child_window_proxy_records
|
||||
.set_facade_context(handle, facade_context);
|
||||
true
|
||||
@@ -1121,42 +1058,6 @@ impl JsContextHost {
|
||||
.live_window_exposed_to_top(handle)
|
||||
}
|
||||
|
||||
pub(crate) fn install_child_window_proxy_cross_origin_access_surface<'s>(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
handle: DomHandle,
|
||||
window_proxy: v8::Local<'s, v8::Object>,
|
||||
realm_parent: v8::Local<'s, v8::Object>,
|
||||
realm_top: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
// parent/top on a WindowProxy are browsing-context identities. Do not
|
||||
// replace them with the caller-specific projections installed on a
|
||||
// replacement inner global.
|
||||
let parent = self
|
||||
.child_window_proxy_records
|
||||
.browsing_context_parent(scope, handle)
|
||||
.unwrap_or(realm_parent);
|
||||
let top = self
|
||||
.child_window_proxy_records
|
||||
.browsing_context_top(scope, handle)
|
||||
.unwrap_or(realm_top);
|
||||
let surface = new_null_prototype_object(scope);
|
||||
let child_frame_count = self.child_browsing_context_child_frame_count(handle);
|
||||
let named_indices = self.child_browsing_context_child_frame_named_indices(handle);
|
||||
install_live_cross_origin_child_window_surface(
|
||||
scope,
|
||||
surface,
|
||||
handle,
|
||||
parent,
|
||||
top,
|
||||
window_proxy,
|
||||
child_frame_count,
|
||||
&named_indices,
|
||||
);
|
||||
self.child_window_proxy_records
|
||||
.set_cross_origin_access_surface(scope, handle, surface);
|
||||
}
|
||||
|
||||
pub(crate) fn child_browsing_context_window_proxy_for_top<'s>(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
@@ -2037,22 +1938,7 @@ pub(super) fn build_cross_origin_location_proxy<'s>(
|
||||
CHILD_BROWSING_CONTEXT_HANDLE_SLOT,
|
||||
v8::Number::new(scope, handle.index() as f64).into(),
|
||||
);
|
||||
install_cross_origin_symbol_slots(scope, target, "Location");
|
||||
for name in CROSS_ORIGIN_LOCATION_DENIED_PROPERTIES {
|
||||
let _ = define_function_accessor_property(
|
||||
scope,
|
||||
target,
|
||||
name,
|
||||
cross_origin_window_denied_callback,
|
||||
None,
|
||||
cross_origin_window_denied_callback,
|
||||
None,
|
||||
cross_origin_property_attributes(),
|
||||
);
|
||||
}
|
||||
install_cross_origin_location_href(scope, target);
|
||||
install_cross_origin_location_methods(scope, target);
|
||||
set_cross_origin_object_slot(scope, target, "then", v8::undefined(scope).into());
|
||||
let _ = install_cross_origin_location_keys(scope, target);
|
||||
let location = wrap_cross_origin_location_proxy(scope, target).unwrap_or(target);
|
||||
set_private_value(
|
||||
scope,
|
||||
@@ -2073,22 +1959,7 @@ fn build_detached_cross_origin_location_proxy<'s>(
|
||||
CROSS_ORIGIN_LOCATION_PROXY_SLOT,
|
||||
v8::Boolean::new(scope, true).into(),
|
||||
);
|
||||
install_cross_origin_symbol_slots(scope, target, "Location");
|
||||
for name in CROSS_ORIGIN_LOCATION_DENIED_PROPERTIES {
|
||||
let _ = define_function_accessor_property(
|
||||
scope,
|
||||
target,
|
||||
name,
|
||||
cross_origin_window_denied_callback,
|
||||
None,
|
||||
cross_origin_window_denied_callback,
|
||||
None,
|
||||
cross_origin_property_attributes(),
|
||||
);
|
||||
}
|
||||
install_cross_origin_location_href(scope, target);
|
||||
install_cross_origin_location_methods(scope, target);
|
||||
set_cross_origin_object_slot(scope, target, "then", v8::undefined(scope).into());
|
||||
let _ = install_cross_origin_location_keys(scope, target);
|
||||
let location = wrap_cross_origin_location_proxy(scope, target).unwrap_or(target);
|
||||
set_private_value(
|
||||
scope,
|
||||
@@ -2113,6 +1984,8 @@ fn wrap_cross_origin_location_proxy<'s>(
|
||||
get: (),
|
||||
set: (),
|
||||
get_own_property_descriptor: (),
|
||||
own_keys: (),
|
||||
has: (),
|
||||
delete_property: (),
|
||||
define_property: (),
|
||||
}
|
||||
@@ -2123,37 +1996,39 @@ fn wrap_cross_origin_location_proxy<'s>(
|
||||
v8::Local::<v8::Object>::try_from(proxy).ok()
|
||||
}
|
||||
|
||||
fn install_cross_origin_location_href<'s>(
|
||||
fn install_cross_origin_location_keys<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
location: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
let Some(setter) = v8::Function::builder(cross_origin_location_navigate_setter_callback)
|
||||
.length(1)
|
||||
.build(scope)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if let Some(setter_name) = v8_string(scope, "set href") {
|
||||
setter.set_name(setter_name);
|
||||
target: v8::Local<'s, v8::Object>,
|
||||
) -> Option<()> {
|
||||
for key in cross_origin_location_keys(scope) {
|
||||
target
|
||||
.define_own_property(
|
||||
scope,
|
||||
key.try_into().ok()?,
|
||||
v8::undefined(scope).into(),
|
||||
v8::PropertyAttribute::DONT_ENUM,
|
||||
)
|
||||
.filter(|defined| *defined)?;
|
||||
}
|
||||
let _ = define_get_set_property(
|
||||
scope,
|
||||
location,
|
||||
v8str(scope, "href").into(),
|
||||
v8::undefined(scope).into(),
|
||||
setter.into(),
|
||||
cross_origin_property_attributes(),
|
||||
"href",
|
||||
);
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn install_cross_origin_location_methods<'s>(
|
||||
fn cross_origin_location_keys<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
location: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
CrossOriginLocationMethodsDeclaration::default()
|
||||
.initialize(scope, location)
|
||||
.expect("cross-origin Location methods declaration should initialize");
|
||||
) -> Vec<v8::Local<'s, v8::Value>> {
|
||||
let mut keys: Vec<v8::Local<v8::Value>> = LocationMembers::cross_origin_properties()
|
||||
.iter()
|
||||
.map(|member| v8str(scope, member.name()).into())
|
||||
.collect();
|
||||
keys.push(v8str(scope, "then").into());
|
||||
for symbol in [
|
||||
v8::Symbol::get_to_string_tag(scope),
|
||||
v8::Symbol::get_has_instance(scope),
|
||||
v8::Symbol::get_is_concat_spreadable(scope),
|
||||
] {
|
||||
keys.push(symbol.into());
|
||||
}
|
||||
keys
|
||||
}
|
||||
|
||||
fn install_cross_origin_symbol_slots(
|
||||
@@ -2182,302 +2057,6 @@ fn install_cross_origin_symbol_slots(
|
||||
}
|
||||
}
|
||||
|
||||
fn child_window_cross_origin_access_surface<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
holder: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
child_window_cross_origin_handler_data(scope, holder).map(|(surface, _)| surface)
|
||||
}
|
||||
|
||||
fn child_window_cross_origin_proxy_self<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
holder: v8::Local<'s, v8::Object>,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
child_window_cross_origin_handler_data(scope, holder)
|
||||
.map(|(_, window_proxy)| window_proxy)
|
||||
.unwrap_or(holder)
|
||||
}
|
||||
|
||||
fn child_window_cross_origin_handler_data<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
holder: v8::Local<'s, v8::Object>,
|
||||
) -> Option<(v8::Local<'s, v8::Object>, v8::Local<'s, v8::Object>)> {
|
||||
let holder_context = holder.get_creation_context(scope)?;
|
||||
let host_ptr = crate::util::context_host_ptr_from_context_slot(holder_context)?;
|
||||
let host = unsafe { &*host_ptr };
|
||||
if holder_context
|
||||
.get_slot::<crate::util::MainDefaultWindowContext>()
|
||||
.is_some()
|
||||
&& host.browsing_context_is_closed()
|
||||
{
|
||||
return Some((
|
||||
main_page::access_surface(scope, holder_context),
|
||||
holder_context.global(scope),
|
||||
));
|
||||
}
|
||||
if let Some(identity) = host.window_execution_context_identity_for_access_check(holder_context)
|
||||
&& host.window_execution_context_identity_is_current(identity)
|
||||
{
|
||||
if !host.window_execution_context_identity_is_default_world(identity) {
|
||||
return None;
|
||||
}
|
||||
if identity.dispatch_scope() == super::super::OwnerDispatchScope::Top {
|
||||
let window = holder_context.global(scope);
|
||||
return Some((main_page::access_surface(scope, holder_context), window));
|
||||
}
|
||||
let super::super::OwnerDispatchScope::Child(handle) = identity.dispatch_scope() else {
|
||||
return None;
|
||||
};
|
||||
return host
|
||||
.child_window_proxy_records
|
||||
.cross_origin_handler_data(scope, handle);
|
||||
}
|
||||
|
||||
// A WindowProxy belongs to the browsing context, not to one LocalWindow
|
||||
// generation. A parked facade context keeps the stable proxy attached
|
||||
// while the previous realm is retired and the replacement is pending.
|
||||
let handle = holder_context
|
||||
.get_slot::<ChildWindowProxyFacadeContextHandle>()?
|
||||
.0;
|
||||
if !host.child_browsing_context_is_live(handle) {
|
||||
return None;
|
||||
}
|
||||
host.child_window_proxy_records
|
||||
.cross_origin_handler_data(scope, handle)
|
||||
}
|
||||
|
||||
fn child_window_cross_origin_identity_name(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
key: v8::Local<'_, v8::Name>,
|
||||
) -> bool {
|
||||
v8::Local::<v8::String>::try_from(key)
|
||||
.ok()
|
||||
.map(|key| key.to_rust_string_lossy(scope))
|
||||
.is_some_and(|key| matches!(key.as_str(), "self" | "window" | "globalThis" | "frames"))
|
||||
}
|
||||
|
||||
fn child_window_cross_origin_named_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
key: v8::Local<'s, v8::Name>,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) -> v8::Intercepted {
|
||||
let holder = args.holder();
|
||||
if child_window_cross_origin_identity_name(scope, key) {
|
||||
rv.set(child_window_cross_origin_proxy_self(scope, holder).into());
|
||||
return v8::Intercepted::kYes;
|
||||
}
|
||||
let Some(surface) = child_window_cross_origin_access_surface(scope, holder) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
if surface.has_own_property(scope, key).unwrap_or(false)
|
||||
&& let Some(value) = cross_origin_surface_get(scope, holder, surface, key.into())
|
||||
{
|
||||
rv.set(value);
|
||||
return v8::Intercepted::kYes;
|
||||
}
|
||||
v8::Intercepted::kNo
|
||||
}
|
||||
|
||||
fn cross_origin_surface_get<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
holder: v8::Local<'s, v8::Object>,
|
||||
surface: v8::Local<'s, v8::Object>,
|
||||
key: v8::Local<'s, v8::Value>,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
// Main-page accessors resolve the call receiver. Property lookup must pass
|
||||
// the WindowProxy through; otherwise `this` is the internal surface.
|
||||
if main_page::is_main_surface(scope, surface) {
|
||||
let window = child_window_cross_origin_proxy_self(scope, holder);
|
||||
return surface.get_with_receiver(scope, key, window);
|
||||
}
|
||||
surface.get(scope, key)
|
||||
}
|
||||
|
||||
fn child_window_cross_origin_named_setter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
key: v8::Local<'s, v8::Name>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'_, v8::Boolean>,
|
||||
) -> v8::Intercepted {
|
||||
let Some(surface) = child_window_cross_origin_access_surface(scope, args.holder()) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
let is_location = v8::Local::<v8::String>::try_from(key)
|
||||
.ok()
|
||||
.map(|key| key.to_rust_string_lossy(scope))
|
||||
.as_deref()
|
||||
== Some("location");
|
||||
if is_location {
|
||||
if main_page::is_main_surface(scope, surface) {
|
||||
let window = child_window_cross_origin_proxy_self(scope, args.holder());
|
||||
let _ = surface.set_with_receiver(scope, key.into(), value, window);
|
||||
} else {
|
||||
let _ = surface.set(scope, key.into(), value);
|
||||
}
|
||||
return v8::Intercepted::kYes;
|
||||
}
|
||||
v8::Intercepted::kNo
|
||||
}
|
||||
|
||||
fn child_window_cross_origin_named_query<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
key: v8::Local<'s, v8::Name>,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Integer>,
|
||||
) -> v8::Intercepted {
|
||||
if child_window_cross_origin_identity_name(scope, key) {
|
||||
rv.set_int32(cross_origin_property_attributes().as_u32() as i32);
|
||||
return v8::Intercepted::kYes;
|
||||
}
|
||||
let Some(surface) = child_window_cross_origin_access_surface(scope, args.holder()) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
if surface.has_own_property(scope, key).unwrap_or(false) {
|
||||
rv.set_int32(cross_origin_property_attributes().as_u32() as i32);
|
||||
return v8::Intercepted::kYes;
|
||||
}
|
||||
v8::Intercepted::kNo
|
||||
}
|
||||
|
||||
fn child_window_cross_origin_named_enumerator<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
callback_args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Array>,
|
||||
) {
|
||||
let mut property_names = v8::GetPropertyNamesArgsBuilder::new();
|
||||
property_names.mode(v8::KeyCollectionMode::OwnOnly);
|
||||
property_names.property_filter(v8::PropertyFilter::ALL_PROPERTIES);
|
||||
property_names.index_filter(v8::IndexFilter::SkipIndices);
|
||||
property_names.key_conversion(v8::KeyConversionMode::ConvertToString);
|
||||
let names = child_window_cross_origin_access_surface(scope, callback_args.holder())
|
||||
.and_then(|surface| surface.get_property_names(scope, property_names.build()))
|
||||
.unwrap_or_else(|| v8::Array::new(scope, 0));
|
||||
rv.set(names);
|
||||
}
|
||||
|
||||
fn child_window_cross_origin_named_descriptor<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
key: v8::Local<'s, v8::Name>,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) -> v8::Intercepted {
|
||||
if child_window_cross_origin_identity_name(scope, key) {
|
||||
let value = child_window_cross_origin_proxy_self(scope, args.holder()).into();
|
||||
let Ok(descriptor) =
|
||||
CrossOriginPropertyDescriptorDeclaration::new(value, false, false, false).bind(scope)
|
||||
else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
rv.set(descriptor.into());
|
||||
return v8::Intercepted::kYes;
|
||||
}
|
||||
let Some(surface) = child_window_cross_origin_access_surface(scope, args.holder()) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
let Some(descriptor) = surface.get_own_property_descriptor(scope, key) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
rv.set(descriptor);
|
||||
v8::Intercepted::kYes
|
||||
}
|
||||
|
||||
fn child_window_cross_origin_indexed_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
index: u32,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) -> v8::Intercepted {
|
||||
let Some(surface) = child_window_cross_origin_access_surface(scope, args.holder()) else {
|
||||
rv.set_undefined();
|
||||
return v8::Intercepted::kYes;
|
||||
};
|
||||
if main_page::is_main_surface(scope, surface)
|
||||
&& !surface
|
||||
.has_own_property(scope, v8_string(scope, &index.to_string()).unwrap().into())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return v8::Intercepted::kNo;
|
||||
}
|
||||
match surface.get_index(scope, index) {
|
||||
Some(value) => rv.set(value),
|
||||
None => rv.set_undefined(),
|
||||
}
|
||||
v8::Intercepted::kYes
|
||||
}
|
||||
|
||||
fn child_window_cross_origin_indexed_setter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
_index: u32,
|
||||
_value: v8::Local<'s, v8::Value>,
|
||||
_args: v8::PropertyCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'_, v8::Boolean>,
|
||||
) -> v8::Intercepted {
|
||||
let _ = scope;
|
||||
v8::Intercepted::kNo
|
||||
}
|
||||
|
||||
fn child_window_cross_origin_indexed_query<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
index: u32,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Integer>,
|
||||
) -> v8::Intercepted {
|
||||
let Some(surface) = child_window_cross_origin_access_surface(scope, args.holder()) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
let Some(key) = v8_string(scope, &index.to_string()) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
if !surface.has_own_property(scope, key.into()).unwrap_or(false) {
|
||||
return v8::Intercepted::kNo;
|
||||
}
|
||||
rv.set_int32(cross_origin_index_property_attributes().as_u32() as i32);
|
||||
v8::Intercepted::kYes
|
||||
}
|
||||
|
||||
fn child_window_cross_origin_indexed_enumerator<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Array>,
|
||||
) {
|
||||
let holder = args.holder();
|
||||
let count = child_window_cross_origin_access_surface(scope, holder)
|
||||
.and_then(|surface| {
|
||||
cross_origin_surface_get(scope, holder, surface, v8str(scope, "length").into())
|
||||
})
|
||||
.and_then(|length| length.uint32_value(scope))
|
||||
.unwrap_or(0);
|
||||
let array =
|
||||
serialize_v8_iter_array(scope, 0..count).unwrap_or_else(|| v8::Array::new(scope, 0));
|
||||
rv.set(array);
|
||||
}
|
||||
|
||||
fn child_window_cross_origin_indexed_descriptor<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
index: u32,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) -> v8::Intercepted {
|
||||
let Some(surface) = child_window_cross_origin_access_surface(scope, args.holder()) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
let Some(key) = v8_string(scope, &index.to_string()) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
let Some(value) = surface.get(scope, key.into()) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
let Ok(descriptor) =
|
||||
CrossOriginPropertyDescriptorDeclaration::new(value, false, true, false).bind(scope)
|
||||
else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
rv.set(descriptor.into());
|
||||
v8::Intercepted::kYes
|
||||
}
|
||||
|
||||
fn cross_origin_window_denied_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
_args: v8::FunctionCallbackArguments<'s>,
|
||||
@@ -2575,21 +2154,28 @@ fn cross_origin_location_proxy_get_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if is_cross_origin_location_href_key_value(scope, args.get(1)) {
|
||||
throw_cross_origin_location_security_error(scope);
|
||||
return;
|
||||
}
|
||||
let Ok(target) = v8::Local::<v8::Object>::try_from(args.get(0)) else {
|
||||
rv.set_undefined();
|
||||
let Ok(key) = v8::Local::<v8::Name>::try_from(args.get(1)) else {
|
||||
return;
|
||||
};
|
||||
let receiver = v8::Local::<v8::Object>::try_from(args.get(2)).unwrap_or(target);
|
||||
let surface = main_page::location_access_surface(scope, target).unwrap_or(target);
|
||||
let accessing_context = cross_origin_accessing_context(scope);
|
||||
let scope = &mut v8::ContextScope::new(scope, accessing_context);
|
||||
match surface.get_with_receiver(scope, args.get(1), receiver) {
|
||||
Some(value) => rv.set(value),
|
||||
None => rv.set_undefined(),
|
||||
let member = v8::Local::<v8::String>::try_from(key)
|
||||
.ok()
|
||||
.and_then(|key| LocationMembers::cross_origin_property(&key.to_rust_string_lossy(scope)));
|
||||
let Some(member) = member.filter(|member| member.has_getter()) else {
|
||||
if is_fallback_key(scope, key) {
|
||||
rv.set_undefined();
|
||||
} else {
|
||||
throw_cross_origin_location_security_error(scope);
|
||||
}
|
||||
return;
|
||||
};
|
||||
let Ok(receiver) = v8::Local::<v8::Object>::try_from(args.get(2)) else {
|
||||
throw_cross_origin_illegal_invocation(scope);
|
||||
return;
|
||||
};
|
||||
let context = cross_origin_accessing_context(scope);
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
if let Some(value) = member.get(scope, receiver) {
|
||||
rv.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2598,31 +2184,62 @@ fn cross_origin_location_proxy_descriptor_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Ok(target) = v8::Local::<v8::Object>::try_from(args.get(0)) else {
|
||||
return;
|
||||
};
|
||||
let Ok(key) = v8::Local::<v8::Name>::try_from(args.get(1)) else {
|
||||
return;
|
||||
};
|
||||
let main_surface = main_page::location_access_surface(scope, target);
|
||||
let surface = main_surface.unwrap_or(target);
|
||||
let accessing_context = cross_origin_accessing_context(scope);
|
||||
let scope = &mut v8::ContextScope::new(scope, accessing_context);
|
||||
let Some(descriptor) = surface.get_own_property_descriptor(scope, key) else {
|
||||
let member = v8::Local::<v8::String>::try_from(key)
|
||||
.ok()
|
||||
.and_then(|key| LocationMembers::cross_origin_property(&key.to_rust_string_lossy(scope)));
|
||||
let context = cross_origin_accessing_context(scope);
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
if let Some(member) = member {
|
||||
if let Some(descriptor) = member.descriptor(scope) {
|
||||
rv.set(descriptor.into());
|
||||
}
|
||||
} else if is_fallback_key(scope, key) {
|
||||
if let Ok(descriptor) = CrossOriginPropertyDescriptorDeclaration::new(
|
||||
v8::undefined(scope).into(),
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
)
|
||||
.bind(scope)
|
||||
{
|
||||
rv.set(descriptor.into());
|
||||
}
|
||||
} else {
|
||||
throw_cross_origin_location_security_error(scope);
|
||||
}
|
||||
}
|
||||
|
||||
fn cross_origin_location_proxy_own_keys_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
_args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let context = cross_origin_accessing_context(scope);
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let keys = cross_origin_location_keys(scope);
|
||||
rv.set(v8::Array::new_with_elements(scope, &keys).into());
|
||||
}
|
||||
|
||||
fn cross_origin_location_proxy_has_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Ok(key) = v8::Local::<v8::Name>::try_from(args.get(1)) else {
|
||||
return;
|
||||
};
|
||||
if main_surface.is_some()
|
||||
&& let Ok(descriptor) = v8::Local::<v8::Object>::try_from(descriptor)
|
||||
{
|
||||
// Main Location's target holds placeholders. Its descriptors are
|
||||
// projected into each accessor realm and must remain configurable.
|
||||
let _ = descriptor.set(
|
||||
scope,
|
||||
v8str(scope, "configurable").into(),
|
||||
v8::Boolean::new(scope, true).into(),
|
||||
);
|
||||
let known = v8::Local::<v8::String>::try_from(key)
|
||||
.ok()
|
||||
.and_then(|key| LocationMembers::cross_origin_property(&key.to_rust_string_lossy(scope)))
|
||||
.is_some();
|
||||
if known || is_fallback_key(scope, key) {
|
||||
rv.set_bool(true);
|
||||
} else {
|
||||
throw_cross_origin_location_security_error(scope);
|
||||
}
|
||||
rv.set(descriptor);
|
||||
}
|
||||
|
||||
fn cross_origin_location_proxy_set_receiver<'s>(
|
||||
@@ -2649,16 +2266,7 @@ fn cross_origin_location_proxy_set_callback<'s>(
|
||||
return;
|
||||
};
|
||||
if !is_cross_origin_location_href_key_value(scope, args.get(1)) {
|
||||
let receiver = v8::Local::<v8::Object>::try_from(args.get(3)).unwrap_or(target);
|
||||
rv.set(
|
||||
v8::Boolean::new(
|
||||
scope,
|
||||
target
|
||||
.set_with_receiver(scope, args.get(1), args.get(2), receiver)
|
||||
.unwrap_or(false),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
throw_cross_origin_location_security_error(scope);
|
||||
return;
|
||||
}
|
||||
let Some(receiver) = cross_origin_location_proxy_set_receiver(scope, target, args.get(3))
|
||||
@@ -2747,8 +2355,8 @@ fn cross_origin_location_navigate<'s>(
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
) -> bool {
|
||||
if main_page::is_main_location(scope, receiver) {
|
||||
return main_page::navigate(
|
||||
if location::is_window_location(scope, receiver) {
|
||||
return location::navigate(
|
||||
scope,
|
||||
receiver,
|
||||
value,
|
||||
@@ -2828,7 +2436,7 @@ fn cross_origin_location_replace_callback<'s>(
|
||||
);
|
||||
return;
|
||||
}
|
||||
if main_page::is_main_location(scope, args.this()) {
|
||||
if location::is_window_location(scope, args.this()) {
|
||||
let Some(parsed) = webidl::parse_args::<CrossOriginLocationReplaceArgs>(scope, &args)
|
||||
else {
|
||||
return;
|
||||
@@ -2836,7 +2444,7 @@ fn cross_origin_location_replace_callback<'s>(
|
||||
let Some(value) = v8_string(scope, &parsed.url) else {
|
||||
return;
|
||||
};
|
||||
main_page::navigate(
|
||||
location::navigate(
|
||||
scope,
|
||||
args.this(),
|
||||
value.into(),
|
||||
|
||||
+492
@@ -0,0 +1,492 @@
|
||||
//! Native access-check handlers. Static members never retain a target Window
|
||||
//! or an accessing realm; their functions are V8's per-context template instances.
|
||||
|
||||
use super::*;
|
||||
use crate::context_bootstrap;
|
||||
use moli_webapi_declare::{WebApiCrossOrigin, WebApiCrossOriginDeclaration};
|
||||
|
||||
// Fields are declaration metadata; no runtime object carries this state.
|
||||
#[allow(dead_code)]
|
||||
#[derive(WebApiCrossOrigin)]
|
||||
#[webapi(plain, receiver = context_bootstrap::is_window_receiver)]
|
||||
pub(super) struct WindowMembers {
|
||||
#[webapi(accessor_property, getter = context_bootstrap::window_self_value)]
|
||||
window: (),
|
||||
#[webapi(accessor_property, name = "self", getter = context_bootstrap::window_self_value)]
|
||||
self_window: (),
|
||||
#[webapi(accessor_property, getter = location::window_location, setter = location::set_window_location)]
|
||||
location: (),
|
||||
#[webapi(method, callback = context_bootstrap::window_close_callback)]
|
||||
close: (),
|
||||
#[webapi(accessor_property, getter = closed_value)]
|
||||
closed: (),
|
||||
#[webapi(method, callback = context_bootstrap::window_noop_callback)]
|
||||
focus: (),
|
||||
#[webapi(method, callback = context_bootstrap::window_noop_callback)]
|
||||
blur: (),
|
||||
#[webapi(accessor_property, getter = context_bootstrap::window_frames_value)]
|
||||
frames: (),
|
||||
#[webapi(accessor_property, getter = length_value)]
|
||||
length: (),
|
||||
#[webapi(accessor_property, getter = top_value)]
|
||||
top: (),
|
||||
#[webapi(accessor_property, getter = context_bootstrap::window_opener_value)]
|
||||
opener: (),
|
||||
#[webapi(accessor_property, getter = parent_value)]
|
||||
parent: (),
|
||||
#[webapi(method, length = 1, callback = window_host::window_post_message_callback)]
|
||||
post_message: (),
|
||||
}
|
||||
|
||||
// Fields are declaration metadata; no runtime object carries this state.
|
||||
#[allow(dead_code)]
|
||||
#[derive(WebApiCrossOrigin)]
|
||||
#[webapi(plain, receiver = is_location_receiver)]
|
||||
pub(super) struct LocationMembers {
|
||||
#[webapi(accessor_property, setter = set_href_value)]
|
||||
href: (),
|
||||
#[webapi(method, length = 1, callback = cross_origin_location_replace_callback)]
|
||||
replace: (),
|
||||
}
|
||||
|
||||
fn is_location_receiver<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
context_bootstrap::is_native_location(scope, receiver)
|
||||
|| is_cross_origin_location_proxy(scope, receiver)
|
||||
}
|
||||
|
||||
fn set_href_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
) -> Option<()> {
|
||||
if !context_bootstrap::navigate_borrowed_location(
|
||||
scope,
|
||||
receiver,
|
||||
value,
|
||||
context_bootstrap::LocationNavigationKind::Assign,
|
||||
) {
|
||||
cross_origin_location_navigate(scope, receiver, value);
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// A parked proxy has no LocalWindow identity yet. Its browsing context still
|
||||
/// supplies parent/top and children while a replacement document is pending.
|
||||
struct WindowTarget<'s> {
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
context: v8::Local<'s, v8::Context>,
|
||||
host: *mut JsContextHost,
|
||||
child: Option<DomHandle>,
|
||||
closed: bool,
|
||||
}
|
||||
|
||||
impl<'s> WindowTarget<'s> {
|
||||
fn resolve(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> Option<Self> {
|
||||
let context = receiver.get_creation_context(scope)?;
|
||||
let host = crate::util::context_host_ptr_from_context_slot(context)?;
|
||||
let owner = unsafe { &*host };
|
||||
let identity = owner.window_execution_context_identity_for_access_check(context);
|
||||
let current = identity
|
||||
.filter(|identity| owner.window_execution_context_identity_is_current(*identity));
|
||||
if current.is_some_and(|identity| {
|
||||
!owner.window_execution_context_identity_is_default_world(identity)
|
||||
}) {
|
||||
return None;
|
||||
}
|
||||
let facade = context
|
||||
.get_slot::<ChildWindowProxyFacadeContextHandle>()
|
||||
.map(|handle| handle.0);
|
||||
let child = current
|
||||
.and_then(|identity| identity.dispatch_scope().child_window())
|
||||
.or(facade);
|
||||
let closed = if let Some(handle) = facade {
|
||||
!owner.child_browsing_context_is_live(handle)
|
||||
} else if current.is_some()
|
||||
|| context
|
||||
.get_slot::<crate::util::MainDefaultWindowContext>()
|
||||
.is_some()
|
||||
{
|
||||
owner.browsing_context_is_closed()
|
||||
} else {
|
||||
true
|
||||
};
|
||||
Some(Self {
|
||||
window: context.global(scope),
|
||||
context,
|
||||
host,
|
||||
child,
|
||||
closed,
|
||||
})
|
||||
}
|
||||
|
||||
fn children(&self, scope: &mut v8::PinScope<'s, '_>) -> Vec<DomHandle> {
|
||||
if self.closed {
|
||||
return Vec::new();
|
||||
}
|
||||
let host = unsafe { &mut *self.host };
|
||||
let document = match self.child {
|
||||
Some(handle) => host.child_browsing_context_document_handle(handle),
|
||||
None => Some(host.document_handle()),
|
||||
};
|
||||
let scope = &mut v8::ContextScope::new(scope, self.context);
|
||||
if let Some(document) = document {
|
||||
host.sync_child_browsing_context_subtree(scope, document);
|
||||
}
|
||||
match self.child {
|
||||
Some(handle) => host
|
||||
.child_browsing_context_direct_host_handles(handle)
|
||||
.into_iter()
|
||||
.filter(|handle| host.child_browsing_context_is_live(*handle))
|
||||
.collect(),
|
||||
None => host.top_level_child_browsing_context_handles_in_frame_tree_order(),
|
||||
}
|
||||
}
|
||||
|
||||
fn indexed(
|
||||
&self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
index: u32,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let handle = self.children(scope).get(index as usize).copied()?;
|
||||
unsafe { &mut *self.host }.child_browsing_context_window_proxy_for_top(scope, handle)
|
||||
}
|
||||
|
||||
fn named(
|
||||
&self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
name: &str,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
self.children(scope);
|
||||
if self.closed {
|
||||
return None;
|
||||
}
|
||||
let host = unsafe { &mut *self.host };
|
||||
let handle = host.child_browsing_context_named_child_handle(self.child, name)?;
|
||||
host.child_browsing_context_window_proxy_for_top(scope, handle)
|
||||
}
|
||||
}
|
||||
|
||||
fn closed_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
if let Some(target) = WindowTarget::resolve(scope, receiver) {
|
||||
return Some(v8::Boolean::new(scope, target.closed).into());
|
||||
}
|
||||
context_bootstrap::window_closed_value(scope, receiver)
|
||||
}
|
||||
|
||||
fn length_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
if let Some(context) = receiver.get_creation_context(scope)
|
||||
&& let Some(handle) = context
|
||||
.get_slot::<ChildWindowProxyFacadeContextHandle>()
|
||||
.map(|handle| handle.0)
|
||||
&& let Some(host) = crate::util::context_host_ptr_from_context_slot(context)
|
||||
{
|
||||
return Some(
|
||||
v8::Number::new(
|
||||
scope,
|
||||
unsafe { &mut *host }.child_browsing_context_child_frame_count(handle) as f64,
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
context_bootstrap::window_length_value(scope, receiver)
|
||||
}
|
||||
|
||||
fn parent_top_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
top: bool,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
if let Some(context) = receiver.get_creation_context(scope)
|
||||
&& let Some(handle) = context
|
||||
.get_slot::<ChildWindowProxyFacadeContextHandle>()
|
||||
.map(|handle| handle.0)
|
||||
&& let Some(host) = crate::util::context_host_ptr_from_context_slot(context)
|
||||
{
|
||||
let records = &unsafe { &*host }.child_window_proxy_records;
|
||||
return if top {
|
||||
records.browsing_context_top(scope, handle)
|
||||
} else {
|
||||
records.browsing_context_parent(scope, handle)
|
||||
}
|
||||
.map(Into::into);
|
||||
}
|
||||
if top {
|
||||
context_bootstrap::window_top_value(scope, receiver)
|
||||
} else {
|
||||
context_bootstrap::window_parent_value(scope, receiver)
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
parent_top_value(scope, receiver, false)
|
||||
}
|
||||
fn top_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
parent_top_value(scope, receiver, true)
|
||||
}
|
||||
|
||||
fn member_name(scope: &mut v8::PinScope<'_, '_>, key: v8::Local<'_, v8::Name>) -> Option<String> {
|
||||
v8::Local::<v8::String>::try_from(key)
|
||||
.ok()
|
||||
.map(|key| key.to_rust_string_lossy(scope))
|
||||
}
|
||||
|
||||
pub(super) fn is_fallback_key(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
key: v8::Local<'_, v8::Name>,
|
||||
) -> bool {
|
||||
member_name(scope, key).as_deref() == Some("then")
|
||||
|| [
|
||||
v8::Symbol::get_to_string_tag(scope),
|
||||
v8::Symbol::get_has_instance(scope),
|
||||
v8::Symbol::get_is_concat_spreadable(scope),
|
||||
]
|
||||
.into_iter()
|
||||
.any(|symbol| key.strict_equals(symbol.into()))
|
||||
}
|
||||
|
||||
fn value_descriptor<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
enumerable: bool,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
CrossOriginPropertyDescriptorDeclaration::new(value, false, enumerable, true)
|
||||
.bind(scope)
|
||||
.ok()
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
pub(super) fn child_window_cross_origin_named_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
key: v8::Local<'s, v8::Name>,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) -> v8::Intercepted {
|
||||
let Some(target) = WindowTarget::resolve(scope, args.holder()) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
let name = member_name(scope, key);
|
||||
if let Some(member) = name
|
||||
.as_deref()
|
||||
.and_then(WindowMembers::cross_origin_property)
|
||||
{
|
||||
let context = cross_origin_accessing_context(scope);
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
if let Some(value) = member.get(scope, target.window) {
|
||||
rv.set(value);
|
||||
}
|
||||
return v8::Intercepted::kYes;
|
||||
}
|
||||
if let Some(value) = name.as_deref().and_then(|name| target.named(scope, name)) {
|
||||
rv.set(value.into());
|
||||
return v8::Intercepted::kYes;
|
||||
}
|
||||
if is_fallback_key(scope, key) {
|
||||
rv.set_undefined();
|
||||
return v8::Intercepted::kYes;
|
||||
}
|
||||
v8::Intercepted::kNo
|
||||
}
|
||||
|
||||
pub(super) fn child_window_cross_origin_named_setter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
key: v8::Local<'s, v8::Name>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'_, v8::Boolean>,
|
||||
) -> v8::Intercepted {
|
||||
let Some(member) = member_name(scope, key)
|
||||
.as_deref()
|
||||
.and_then(WindowMembers::cross_origin_property)
|
||||
.filter(|member| member.has_setter())
|
||||
else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
let Some(target) = WindowTarget::resolve(scope, args.holder()) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
let _ = member.set(scope, target.window, value);
|
||||
v8::Intercepted::kYes
|
||||
}
|
||||
|
||||
pub(super) fn child_window_cross_origin_named_query<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
key: v8::Local<'s, v8::Name>,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Integer>,
|
||||
) -> v8::Intercepted {
|
||||
let Some(target) = WindowTarget::resolve(scope, args.holder()) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
let name = member_name(scope, key);
|
||||
if name
|
||||
.as_deref()
|
||||
.and_then(WindowMembers::cross_origin_property)
|
||||
.is_some()
|
||||
|| is_fallback_key(scope, key)
|
||||
|| name.and_then(|name| target.named(scope, &name)).is_some()
|
||||
{
|
||||
rv.set_int32(v8::PropertyAttribute::DONT_ENUM.as_u32() as i32);
|
||||
return v8::Intercepted::kYes;
|
||||
}
|
||||
v8::Intercepted::kNo
|
||||
}
|
||||
|
||||
pub(super) fn child_window_cross_origin_named_enumerator<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Array>,
|
||||
) {
|
||||
if WindowTarget::resolve(scope, args.holder()).is_none() {
|
||||
return;
|
||||
}
|
||||
let mut names: Vec<v8::Local<v8::Value>> = WindowMembers::cross_origin_properties()
|
||||
.iter()
|
||||
.map(|member| v8str(scope, member.name()).into())
|
||||
.collect();
|
||||
// Named child access is dynamic, and never creates own properties on the
|
||||
// target Window. The cross-origin own-key list contains static members.
|
||||
names.push(v8str(scope, "then").into());
|
||||
for symbol in [
|
||||
v8::Symbol::get_to_string_tag(scope),
|
||||
v8::Symbol::get_has_instance(scope),
|
||||
v8::Symbol::get_is_concat_spreadable(scope),
|
||||
] {
|
||||
names.push(symbol.into());
|
||||
}
|
||||
let context = cross_origin_accessing_context(scope);
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
rv.set(v8::Array::new_with_elements(scope, &names));
|
||||
}
|
||||
|
||||
pub(super) fn child_window_cross_origin_named_descriptor<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
key: v8::Local<'s, v8::Name>,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) -> v8::Intercepted {
|
||||
let Some(target) = WindowTarget::resolve(scope, args.holder()) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
let name = member_name(scope, key);
|
||||
let context = cross_origin_accessing_context(scope);
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
if let Some(member) = name
|
||||
.as_deref()
|
||||
.and_then(WindowMembers::cross_origin_property)
|
||||
{
|
||||
if let Some(descriptor) = member.descriptor(scope) {
|
||||
rv.set(descriptor.into());
|
||||
}
|
||||
return v8::Intercepted::kYes;
|
||||
}
|
||||
let value = name
|
||||
.as_deref()
|
||||
.and_then(|name| target.named(scope, name))
|
||||
.map(Into::into)
|
||||
.or_else(|| is_fallback_key(scope, key).then(|| v8::undefined(scope).into()));
|
||||
if let Some(value) = value {
|
||||
if let Some(descriptor) = value_descriptor(scope, value, false) {
|
||||
rv.set(descriptor);
|
||||
}
|
||||
} else {
|
||||
throw_cross_origin_location_security_error(scope);
|
||||
}
|
||||
v8::Intercepted::kYes
|
||||
}
|
||||
|
||||
pub(super) fn child_window_cross_origin_indexed_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
index: u32,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) -> v8::Intercepted {
|
||||
if let Some(value) =
|
||||
WindowTarget::resolve(scope, args.holder()).and_then(|target| target.indexed(scope, index))
|
||||
{
|
||||
rv.set(value.into());
|
||||
return v8::Intercepted::kYes;
|
||||
}
|
||||
v8::Intercepted::kNo
|
||||
}
|
||||
|
||||
pub(super) fn child_window_cross_origin_indexed_setter<'s>(
|
||||
_scope: &mut v8::PinScope<'s, '_>,
|
||||
_index: u32,
|
||||
_value: v8::Local<'s, v8::Value>,
|
||||
_args: v8::PropertyCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'_, v8::Boolean>,
|
||||
) -> v8::Intercepted {
|
||||
v8::Intercepted::kNo
|
||||
}
|
||||
|
||||
pub(super) fn child_window_cross_origin_indexed_query<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
index: u32,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Integer>,
|
||||
) -> v8::Intercepted {
|
||||
if let Some(target) = WindowTarget::resolve(scope, args.holder())
|
||||
&& (index as usize) < target.children(scope).len()
|
||||
{
|
||||
rv.set_int32(v8::PropertyAttribute::READ_ONLY.as_u32() as i32);
|
||||
return v8::Intercepted::kYes;
|
||||
}
|
||||
v8::Intercepted::kNo
|
||||
}
|
||||
|
||||
pub(super) fn child_window_cross_origin_indexed_enumerator<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Array>,
|
||||
) {
|
||||
let Some(target) = WindowTarget::resolve(scope, args.holder()) else {
|
||||
return;
|
||||
};
|
||||
let count = target.children(scope).len() as u32;
|
||||
if let Some(array) = serialize_v8_iter_array(scope, 0..count) {
|
||||
rv.set(array);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn child_window_cross_origin_indexed_descriptor<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
index: u32,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) -> v8::Intercepted {
|
||||
let Some(target) = WindowTarget::resolve(scope, args.holder()) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
let Some(handle) = target.children(scope).get(index as usize).copied() else {
|
||||
throw_cross_origin_location_security_error(scope);
|
||||
return v8::Intercepted::kYes;
|
||||
};
|
||||
if let Some(value) =
|
||||
unsafe { &mut *target.host }.child_browsing_context_window_proxy_for_top(scope, handle)
|
||||
{
|
||||
let context = cross_origin_accessing_context(scope);
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
if let Some(descriptor) = value_descriptor(scope, value.into(), true) {
|
||||
rv.set(descriptor);
|
||||
}
|
||||
}
|
||||
v8::Intercepted::kYes
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
use super::*;
|
||||
const LOCATION: &str = "__moliCrossOriginLocationObject";
|
||||
const TARGET_WINDOW: &str = "__moliCrossOriginLocationWindow";
|
||||
const WINDOW_LOCATION_SLOT: &str = "__moliWindowLocation";
|
||||
|
||||
fn ensure_shared_location<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
anchor: v8::Local<'s, v8::Object>,
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
if let Some(location) = get_private_value(scope, anchor, LOCATION)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
{
|
||||
return Some(location);
|
||||
}
|
||||
let target = new_null_prototype_object(scope);
|
||||
let marker = v8::Boolean::new(scope, true).into();
|
||||
set_private_value(scope, target, CROSS_ORIGIN_LOCATION_PROXY_SLOT, marker);
|
||||
set_private_value(scope, target, TARGET_WINDOW, window.into());
|
||||
install_cross_origin_location_keys(scope, target)?;
|
||||
let location = wrap_cross_origin_location_proxy(scope, target)?;
|
||||
set_private_value(
|
||||
scope,
|
||||
target,
|
||||
CROSS_ORIGIN_LOCATION_PROXY_SELF_SLOT,
|
||||
location.into(),
|
||||
);
|
||||
set_private_value(scope, anchor, LOCATION, location.into());
|
||||
Some(location)
|
||||
}
|
||||
|
||||
fn target_window<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
get_cross_origin_proxy_private_value(scope, receiver, TARGET_WINDOW)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
}
|
||||
|
||||
pub(super) fn is_window_location<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
target_window(scope, receiver).is_some()
|
||||
}
|
||||
|
||||
pub(super) fn navigate<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
kind: crate::context_bootstrap::LocationNavigationKind,
|
||||
) -> bool {
|
||||
let Some(window) = target_window(scope, receiver) else {
|
||||
return false;
|
||||
};
|
||||
// The stable WindowProxy follows its successor, but a Location belongs to
|
||||
// the Window realm that created it. An old Location must not gain the new
|
||||
// Document's navigation authority through that proxy.
|
||||
if receiver.get_creation_context(scope) != window.get_creation_context(scope) {
|
||||
return false;
|
||||
}
|
||||
navigate_window(scope, window, value, kind, true)
|
||||
}
|
||||
|
||||
fn navigate_window<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
kind: crate::context_bootstrap::LocationNavigationKind,
|
||||
block_javascript: bool,
|
||||
) -> bool {
|
||||
let raw = match webidl::convert::<webidl::UsvString>(
|
||||
scope,
|
||||
value,
|
||||
webidl::Context::member("Location", "href"),
|
||||
) {
|
||||
Ok(value) => value.0,
|
||||
Err(error) => {
|
||||
webidl::throw_error(scope, &error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if block_javascript && url::Url::parse(&raw).is_ok_and(|url| url.scheme() == "javascript") {
|
||||
throw_cross_origin_location_security_error(scope);
|
||||
return false;
|
||||
}
|
||||
let raw = if kind != crate::context_bootstrap::LocationNavigationKind::Reload {
|
||||
crate::context_bootstrap::resolve_cross_window_location_target(scope, &raw).unwrap_or(raw)
|
||||
} else {
|
||||
raw
|
||||
};
|
||||
let Some(context) = window.get_creation_context(scope) else {
|
||||
return false;
|
||||
};
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let Some(location) = window
|
||||
.get(scope, v8str(scope, "location").into())
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
crate::context_bootstrap::navigate_location_object_with_child_navigate_event(
|
||||
scope,
|
||||
location,
|
||||
kind,
|
||||
Some(raw),
|
||||
);
|
||||
true
|
||||
}
|
||||
|
||||
fn shared_cross_origin_location<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let context = window.get_creation_context(scope)?;
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let anchor = context.get_extras_binding_object(scope);
|
||||
let window = context.global(scope);
|
||||
ensure_shared_location(scope, anchor, window)
|
||||
}
|
||||
|
||||
pub(super) fn window_location<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
if caller_can_access_window(scope, window) {
|
||||
return get_private_value(scope, window, WINDOW_LOCATION_SLOT);
|
||||
}
|
||||
shared_cross_origin_location(scope, window).map(Into::into)
|
||||
}
|
||||
|
||||
pub(super) fn set_window_location<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
) -> Option<()> {
|
||||
let block_javascript = !caller_can_access_window(scope, window);
|
||||
navigate_window(
|
||||
scope,
|
||||
window,
|
||||
value,
|
||||
crate::context_bootstrap::LocationNavigationKind::Assign,
|
||||
block_javascript,
|
||||
);
|
||||
Some(())
|
||||
}
|
||||
-370
@@ -1,370 +0,0 @@
|
||||
use super::*;
|
||||
use crate::context_bootstrap::{
|
||||
cached_cross_origin_window_surface, window_closed_getter, window_length_getter,
|
||||
window_opener_getter, window_parent_getter, window_top_getter,
|
||||
};
|
||||
|
||||
const LOCATION: &str = "__moliMainWindowCrossOriginLocationObject";
|
||||
const LOCATION_SURFACE: &str = "__moliMainWindowCrossOriginLocationSurface";
|
||||
const MAIN_LOCATION: &str = "__moliMainWindowCrossOriginLocation";
|
||||
const INDEXED_COUNT: &str = "__moliMainWindowCrossOriginIndexedCount";
|
||||
const WINDOW_LOCATION_SLOT: &str = "__moliWindowLocation";
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(plain)]
|
||||
struct MainWindowCrossOriginSurface {
|
||||
#[webapi(method, length = 1, callback = window_host::window_post_message_callback, readonly, dont_delete)]
|
||||
post_message: (),
|
||||
#[webapi(method, callback = crate::context_bootstrap::window_close_callback, readonly, dont_delete)]
|
||||
close: (),
|
||||
#[webapi(method, callback = crate::context_bootstrap::window_noop_callback, readonly, dont_delete)]
|
||||
focus: (),
|
||||
#[webapi(method, callback = crate::context_bootstrap::window_noop_callback, readonly, dont_delete)]
|
||||
blur: (),
|
||||
#[webapi(accessor_property, getter = window_closed_getter)]
|
||||
closed: (),
|
||||
#[webapi(accessor_property, getter = window_length_getter)]
|
||||
length: (),
|
||||
#[webapi(accessor_property, getter = window_opener_getter)]
|
||||
opener: (),
|
||||
#[webapi(accessor_property, getter = window_parent_getter)]
|
||||
parent: (),
|
||||
#[webapi(accessor_property, getter = window_top_getter)]
|
||||
top: (),
|
||||
#[webapi(accessor_property, getter = location_getter, setter = location_setter)]
|
||||
location: (),
|
||||
}
|
||||
|
||||
pub(super) fn access_surface<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
target_context: v8::Local<'s, v8::Context>,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let surface = cached_access_surface(scope, target_context);
|
||||
refresh_indexed_children(scope, target_context, surface);
|
||||
surface
|
||||
}
|
||||
|
||||
fn cached_access_surface<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
target_context: v8::Local<'s, v8::Context>,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let accessing_context = cross_origin_accessing_context(scope);
|
||||
cached_cross_origin_window_surface(scope, target_context, accessing_context, |scope| {
|
||||
let window = target_context.global(scope);
|
||||
let location = shared_cross_origin_location(scope, window)
|
||||
.expect("main Window cross-origin Location should initialize");
|
||||
build_accessing_surface(scope, window, location)
|
||||
})
|
||||
}
|
||||
|
||||
fn ensure_shared_location<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
anchor: v8::Local<'s, v8::Object>,
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
if let Some(location) = get_private_value(scope, anchor, LOCATION)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
{
|
||||
return location;
|
||||
}
|
||||
let target = new_null_prototype_object(scope);
|
||||
let marker = v8::Boolean::new(scope, true).into();
|
||||
set_private_value(scope, target, CROSS_ORIGIN_LOCATION_PROXY_SLOT, marker);
|
||||
set_private_value(scope, target, MAIN_LOCATION, window.into());
|
||||
// Preserve the target's keys without exposing realm-specific
|
||||
// functions. Configurable placeholders allow each observer's
|
||||
// get/descriptor projection to obey Proxy invariants.
|
||||
let undefined = v8::undefined(scope).into();
|
||||
for name in CROSS_ORIGIN_LOCATION_DENIED_PROPERTIES
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(["href", "replace", "then"])
|
||||
{
|
||||
let key = v8str(scope, name).into();
|
||||
let _ = target.define_own_property(scope, key, undefined, v8::PropertyAttribute::DONT_ENUM);
|
||||
}
|
||||
install_cross_origin_symbol_slots(scope, target, "Location");
|
||||
let location = wrap_cross_origin_location_proxy(scope, target)
|
||||
.expect("main Window cross-origin Location proxy should initialize");
|
||||
set_private_value(
|
||||
scope,
|
||||
target,
|
||||
CROSS_ORIGIN_LOCATION_PROXY_SELF_SLOT,
|
||||
location.into(),
|
||||
);
|
||||
set_private_value(scope, anchor, LOCATION, location.into());
|
||||
location
|
||||
}
|
||||
|
||||
fn build_accessing_surface<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
location: v8::Local<'s, v8::Object>,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let surface = new_null_prototype_object(scope);
|
||||
MainWindowCrossOriginSurface {
|
||||
post_message: (),
|
||||
close: (),
|
||||
focus: (),
|
||||
blur: (),
|
||||
closed: (),
|
||||
length: (),
|
||||
opener: (),
|
||||
parent: (),
|
||||
top: (),
|
||||
location: (),
|
||||
}
|
||||
.initialize(scope, surface)
|
||||
.expect("main Window cross-origin surface should initialize");
|
||||
for name in ["window", "self", "frames"] {
|
||||
set_cross_origin_object_slot(scope, surface, name, window.into());
|
||||
}
|
||||
let undefined = v8::undefined(scope).into();
|
||||
set_cross_origin_object_slot(scope, surface, "then", undefined);
|
||||
set_private_value(
|
||||
scope,
|
||||
surface,
|
||||
CROSS_ORIGIN_WINDOW_LOCATION_SLOT,
|
||||
location.into(),
|
||||
);
|
||||
install_cross_origin_symbol_slots(scope, surface, "Window");
|
||||
|
||||
let location_surface = new_null_prototype_object(scope);
|
||||
install_cross_origin_symbol_slots(scope, location_surface, "Location");
|
||||
install_cross_origin_denied_accessors(
|
||||
scope,
|
||||
location_surface,
|
||||
CROSS_ORIGIN_LOCATION_DENIED_PROPERTIES,
|
||||
);
|
||||
let setter = v8::Function::builder(cross_origin_location_navigate_setter_callback)
|
||||
.length(1)
|
||||
.constructor_behavior(v8::ConstructorBehavior::Throw)
|
||||
.build(scope)
|
||||
.expect("cross-origin Location href setter should initialize");
|
||||
setter.set_name(v8str(scope, "set href"));
|
||||
let href = v8str(scope, "href").into();
|
||||
define_get_set_property(
|
||||
scope,
|
||||
location_surface,
|
||||
href,
|
||||
undefined,
|
||||
setter.into(),
|
||||
cross_origin_property_attributes(),
|
||||
"href",
|
||||
)
|
||||
.expect("cross-origin Location href descriptor should initialize");
|
||||
install_cross_origin_location_methods(scope, location_surface);
|
||||
set_cross_origin_object_slot(scope, location_surface, "then", undefined);
|
||||
set_private_value(scope, surface, LOCATION_SURFACE, location_surface.into());
|
||||
surface
|
||||
}
|
||||
|
||||
pub(super) fn location_access_surface<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let window = main_location_window(scope, receiver)?;
|
||||
let context = window.get_creation_context(scope)?;
|
||||
let surface = cached_access_surface(scope, context);
|
||||
get_private_value(scope, surface, LOCATION_SURFACE)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
}
|
||||
|
||||
pub(super) fn is_main_surface<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
surface: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
get_private_value(scope, surface, INDEXED_COUNT).is_some()
|
||||
}
|
||||
|
||||
fn refresh_indexed_children<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
target_context: v8::Local<'s, v8::Context>,
|
||||
surface: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
let previous = get_private_value(scope, surface, INDEXED_COUNT)
|
||||
.and_then(|value| value.uint32_value(scope))
|
||||
.unwrap_or(0);
|
||||
let scope = &mut v8::ContextScope::new(scope, target_context);
|
||||
let count = crate::util::context_host_ptr_from_context_slot(target_context)
|
||||
.filter(|host| !unsafe { &**host }.browsing_context_is_closed())
|
||||
.map_or(0, |host| {
|
||||
let host = unsafe { &mut *host };
|
||||
host.sync_child_browsing_context_subtree(scope, host.document_handle());
|
||||
let count = host.child_browsing_context_count() as u32;
|
||||
for index in 0..count {
|
||||
if let Some(handle) = host.child_browsing_context_handle_by_index(index as usize)
|
||||
&& let Some(window) =
|
||||
host.child_browsing_context_window_proxy_for_top(scope, handle)
|
||||
{
|
||||
let key = v8_string(scope, &index.to_string()).unwrap();
|
||||
let _ = surface.define_own_property(
|
||||
scope,
|
||||
key.into(),
|
||||
window.into(),
|
||||
v8::PropertyAttribute::READ_ONLY,
|
||||
);
|
||||
}
|
||||
}
|
||||
count
|
||||
});
|
||||
for index in count..previous {
|
||||
let _ = surface.delete_index(scope, index);
|
||||
}
|
||||
let count = v8::Integer::new_from_unsigned(scope, count).into();
|
||||
set_private_value(scope, surface, INDEXED_COUNT, count);
|
||||
}
|
||||
|
||||
fn main_location_window<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
get_cross_origin_proxy_private_value(scope, receiver, MAIN_LOCATION)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
}
|
||||
|
||||
pub(super) fn is_main_location<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
main_location_window(scope, receiver).is_some()
|
||||
}
|
||||
|
||||
pub(super) fn navigate<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
kind: crate::context_bootstrap::LocationNavigationKind,
|
||||
) -> bool {
|
||||
let Some(window) = main_location_window(scope, receiver) else {
|
||||
return false;
|
||||
};
|
||||
// The stable WindowProxy follows its successor, but a Location belongs to
|
||||
// the Window realm that created it. An old Location must not gain the new
|
||||
// Document's navigation authority through that proxy.
|
||||
if receiver.get_creation_context(scope) != window.get_creation_context(scope) {
|
||||
return false;
|
||||
}
|
||||
navigate_window(scope, window, value, kind, true)
|
||||
}
|
||||
|
||||
fn navigate_window<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
kind: crate::context_bootstrap::LocationNavigationKind,
|
||||
block_javascript: bool,
|
||||
) -> bool {
|
||||
let raw = match webidl::convert::<webidl::UsvString>(
|
||||
scope,
|
||||
value,
|
||||
webidl::Context::member("Location", "href"),
|
||||
) {
|
||||
Ok(value) => value.0,
|
||||
Err(error) => {
|
||||
webidl::throw_error(scope, &error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if block_javascript && url::Url::parse(&raw).is_ok_and(|url| url.scheme() == "javascript") {
|
||||
throw_cross_origin_location_security_error(scope);
|
||||
return false;
|
||||
}
|
||||
let raw = if kind != crate::context_bootstrap::LocationNavigationKind::Reload {
|
||||
crate::context_bootstrap::resolve_cross_window_location_target(scope, &raw).unwrap_or(raw)
|
||||
} else {
|
||||
raw
|
||||
};
|
||||
let Some(context) = window.get_creation_context(scope) else {
|
||||
return false;
|
||||
};
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let Some(location) = window
|
||||
.get(scope, v8str(scope, "location").into())
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
crate::context_bootstrap::navigate_location_object_with_child_navigate_event(
|
||||
scope,
|
||||
location,
|
||||
kind,
|
||||
Some(raw),
|
||||
);
|
||||
true
|
||||
}
|
||||
|
||||
fn require_window_receiver<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: &v8::FunctionCallbackArguments<'s>,
|
||||
operation: &str,
|
||||
name: &str,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let receiver = args.this();
|
||||
// The internal surface carries the cross-origin location slot, but it is
|
||||
// not a Window. Callers must pass the real WindowProxy as `this`.
|
||||
if !is_main_surface(scope, receiver)
|
||||
&& (crate::context_bootstrap::is_window_receiver(scope, receiver)
|
||||
|| is_cross_origin_window_proxy(scope, receiver))
|
||||
{
|
||||
return Some(receiver);
|
||||
}
|
||||
throw_type_error(
|
||||
scope,
|
||||
&format!("Failed to {operation} the '{name}' property on 'Window': Illegal invocation"),
|
||||
);
|
||||
None
|
||||
}
|
||||
|
||||
fn shared_cross_origin_location<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let context = window.get_creation_context(scope)?;
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let anchor = context.get_extras_binding_object(scope);
|
||||
let window = context.global(scope);
|
||||
Some(ensure_shared_location(scope, anchor, window))
|
||||
}
|
||||
|
||||
fn location_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(window) = require_window_receiver(scope, &args, "get", "location") else {
|
||||
return;
|
||||
};
|
||||
if caller_can_access_window(scope, window) {
|
||||
if let Some(location) = get_private_value(scope, window, WINDOW_LOCATION_SLOT) {
|
||||
rv.set(location);
|
||||
return;
|
||||
}
|
||||
throw_type_error(
|
||||
scope,
|
||||
"Failed to get the 'location' property on 'Window': Illegal invocation",
|
||||
);
|
||||
return;
|
||||
}
|
||||
if let Some(location) = shared_cross_origin_location(scope, window) {
|
||||
rv.set(location.into());
|
||||
}
|
||||
}
|
||||
|
||||
fn location_setter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(window) = require_window_receiver(scope, &args, "set", "location") else {
|
||||
return;
|
||||
};
|
||||
let block_javascript = !caller_can_access_window(scope, window);
|
||||
navigate_window(
|
||||
scope,
|
||||
window,
|
||||
args.get(0),
|
||||
crate::context_bootstrap::LocationNavigationKind::Assign,
|
||||
block_javascript,
|
||||
);
|
||||
}
|
||||
+1
-1
@@ -1211,7 +1211,7 @@ frame.src = "data:text/html,<body>cross-origin</body>";
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
"true|true|true|[object Window]|SecurityError:true|SecurityError:true|SecurityError:true|SecurityError:true|SecurityError:true"
|
||||
"true|true|true|[object Object]|SecurityError:true|SecurityError:true|SecurityError:true|SecurityError:true|SecurityError:true"
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
|
||||
@@ -2859,6 +2859,11 @@ for (const tag of ['iframe', 'embed', 'object']) {
|
||||
}
|
||||
|
||||
async fn commit_opaque_child_for_test(vm: &mut StandaloneScriptVmHarness, element_id: &str) -> i64 {
|
||||
let previous: std::collections::HashSet<_> = vm
|
||||
.live_child_default_runtime_realm_inventory()
|
||||
.into_iter()
|
||||
.map(|realm| realm.context_id)
|
||||
.collect();
|
||||
let created = vm
|
||||
.eval(&format!(
|
||||
r#"
|
||||
@@ -2881,7 +2886,7 @@ async fn commit_opaque_child_for_test(vm: &mut StandaloneScriptVmHarness, elemen
|
||||
vm.live_child_default_runtime_realm_inventory()
|
||||
.into_iter()
|
||||
.map(|realm| realm.context_id)
|
||||
.next()
|
||||
.find(|id| !previous.contains(id))
|
||||
.unwrap_or_else(|| panic!("{element_id}: opaque child realm should exist"))
|
||||
}
|
||||
|
||||
@@ -2984,7 +2989,7 @@ async fn borrowed_cross_origin_window_accessors_follow_the_receiver() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cross_origin_surface_cache_releases_removed_accessor_realm() {
|
||||
async fn native_cross_origin_bindings_release_removed_accessor_realm() {
|
||||
let mut vm = new_storage_test_vm("https://cross-origin-surface-cache.test/");
|
||||
|
||||
let control_id = commit_opaque_child_for_test(&mut vm, "control-frame").await;
|
||||
@@ -3012,8 +3017,16 @@ document.getElementById("control-frame").remove();
|
||||
let accessor_id = commit_opaque_child_for_test(&mut vm, "accessor-frame").await;
|
||||
expose_real_top_window_for_test(&mut vm, accessor_id);
|
||||
assert_eq!(
|
||||
vm.eval_in_child_default_context(accessor_id, "String(__realTopWindow.closed)")
|
||||
.expect("cross-origin closed read should evaluate"),
|
||||
vm.eval_in_child_default_context(
|
||||
accessor_id,
|
||||
r#"
|
||||
void __realTopWindow.postMessage;
|
||||
void Object.getOwnPropertyDescriptor(__realTopWindow, 'parent').get;
|
||||
void __realTopWindow.location;
|
||||
String(__realTopWindow.closed)
|
||||
"#
|
||||
)
|
||||
.expect("cross-origin closed read should evaluate"),
|
||||
"false"
|
||||
);
|
||||
let accessor_weak = child_context_weak_for_test(&mut vm, accessor_id);
|
||||
@@ -3039,7 +3052,7 @@ document.getElementById("accessor-frame").remove();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cross_origin_window_cache_ignores_author_weak_map_overrides() {
|
||||
async fn native_cross_origin_bindings_ignore_author_weak_map_overrides() {
|
||||
let mut vm = new_storage_test_vm("https://cross-origin-cache-intrinsics.test/");
|
||||
let child_id = commit_opaque_child_for_test(&mut vm, "observer").await;
|
||||
expose_real_top_window_for_test(&mut vm, child_id);
|
||||
@@ -3170,6 +3183,73 @@ async fn cross_origin_ancestor_search_reaches_the_actual_top_window() {
|
||||
assert_eq!(result, "[true,2,true,true]");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cross_origin_functions_share_the_observer_realm_and_follow_borrowed_receivers() {
|
||||
let mut vm = new_storage_test_vm("https://cross-origin-native-members.test/");
|
||||
let observer = commit_opaque_child_for_test(&mut vm, "observer").await;
|
||||
commit_opaque_child_for_test(&mut vm, "target").await;
|
||||
let result = vm.eval_in_child_default_context(observer, r#"
|
||||
(() => {
|
||||
const a = top, b = top[1];
|
||||
const descriptor = Object.getOwnPropertyDescriptor(a, 'parent');
|
||||
const locationA = a.location, locationB = b.location;
|
||||
const href = Object.getOwnPropertyDescriptor(locationA, 'href');
|
||||
const names = ['window', 'self', 'location', 'close', 'closed', 'focus', 'blur', 'frames', 'length', 'top', 'opener', 'parent', 'postMessage'];
|
||||
function throwsTypeError(callback) { try { callback(); } catch (e) { return e instanceof TypeError; } return false; }
|
||||
return JSON.stringify({
|
||||
methodIdentity: a.postMessage === b.postMessage && a.postMessage === a.postMessage,
|
||||
getterIdentity: descriptor.get === Object.getOwnPropertyDescriptor(b, 'parent').get,
|
||||
locationIdentity: locationA === a.location && locationB === b.location && locationA !== locationB,
|
||||
replaceIdentity: locationA.replace === locationB.replace,
|
||||
realm: [a.postMessage, descriptor.get, href.set, locationA.replace].every(fn => Object.getPrototypeOf(fn) === Function.prototype),
|
||||
borrowed: descriptor.get.call(window) === parent,
|
||||
invalidReceiver: throwsTypeError(() => descriptor.get.call({})) && throwsTypeError(() => a.postMessage.call({})) && throwsTypeError(() => href.set.call({}, '#bad')),
|
||||
metadata: descriptor.get.name === 'get parent' && descriptor.get.length === 0 && href.set.name === 'set href' && href.set.length === 1 && a.postMessage.length === 1 && !Object.hasOwn(a.postMessage, 'prototype'),
|
||||
descriptors: names.every(name => { const d = Object.getOwnPropertyDescriptor(a, name); return d.configurable && !d.enumerable; }) && href.get === undefined && href.configurable && !href.enumerable,
|
||||
keys: Object.getOwnPropertyNames(locationA).join(',') === 'href,replace,then',
|
||||
fallbacks: a.then === undefined && a[Symbol.hasInstance] === undefined && locationA.then === undefined
|
||||
});
|
||||
})()
|
||||
"#).expect("native cross-origin member projection");
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"methodIdentity":true,"getterIdentity":true,"locationIdentity":true,"replaceIdentity":true,"realm":true,"borrowed":true,"invalidReceiver":true,"metadata":true,"descriptors":true,"keys":true,"fallbacks":true}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cross_origin_named_children_precede_fallbacks_and_refresh_after_removal() {
|
||||
let mut vm = new_storage_test_vm("https://cross-origin-named-fallback.test/");
|
||||
let observer = commit_opaque_child_for_test(&mut vm, "observer").await;
|
||||
vm.eval(
|
||||
r#"
|
||||
const named = document.createElement('iframe');
|
||||
named.id = 'named-then';
|
||||
named.name = 'then';
|
||||
document.body.appendChild(named);
|
||||
void named.contentWindow;
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let result = vm.eval_in_child_default_context(observer, r#"
|
||||
(() => {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(top, 'then');
|
||||
const errors = ['unknownProperty', '4294967294'].map(name => {
|
||||
try { Object.getOwnPropertyDescriptor(top, name); return 'accepted'; }
|
||||
catch (error) { return error.name + ':' + (error instanceof DOMException); }
|
||||
});
|
||||
return JSON.stringify([top.then === top[1], descriptor.value === top[1], !descriptor.writable && !descriptor.enumerable && descriptor.configurable, errors]);
|
||||
})()
|
||||
"#).unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"[true,true,true,["SecurityError:true","SecurityError:true"]]"#
|
||||
);
|
||||
vm.eval("document.getElementById('named-then').remove()")
|
||||
.unwrap();
|
||||
assert_eq!(vm.eval_in_child_default_context(observer, "top.then === undefined && Object.getOwnPropertyDescriptor(top, 'then').value === undefined").unwrap(), "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frame_owner_content_accessors_live_on_exact_owner_prototypes() {
|
||||
let mut vm = new_storage_html_test_vm("https://frame-owner-content-accessors.test/");
|
||||
|
||||
@@ -164,13 +164,6 @@ const DIRECT_V8_CALL_ALLOWLIST: &[AllowedDirectCallFile] = &[
|
||||
DirectCallOwner::BrowserAlgorithm,
|
||||
),
|
||||
// Captured native intrinsics, DOM/native forwarding, and compiled jobs.
|
||||
// These two calls invoke WeakMap.get/set captured before author code runs.
|
||||
// No page-supplied callback or mutable prototype method reaches this cache.
|
||||
allowed(
|
||||
"context_bootstrap/window_accessors/cross_origin_cache.rs",
|
||||
2,
|
||||
DirectCallOwner::NativeForwardingOrScript,
|
||||
),
|
||||
allowed(
|
||||
"context_bootstrap/constructors/document_nodes.rs",
|
||||
1,
|
||||
|
||||
Reference in New Issue
Block a user