mirror of
https://github.com/lexmount/moli.git
synced 2026-10-05 00:00:55 +00:00
fix(window): preserve browsing context opener semantics
This commit is contained in:
@@ -33,7 +33,7 @@ pub(super) use interceptors::{
|
||||
pub(super) use surface::{
|
||||
window_custom_elements_getter, window_device_pixel_ratio_getter, window_frames_getter,
|
||||
window_inner_height_getter, window_inner_surface_height, window_inner_surface_width,
|
||||
window_inner_width_getter, window_navigator_getter, window_opener_getter,
|
||||
window_inner_width_getter, window_navigator_getter, window_opener_getter, window_opener_setter,
|
||||
window_outer_height_getter, window_outer_width_getter, window_parent_getter,
|
||||
window_performance_getter, window_screen_getter, window_scroll_x_getter,
|
||||
window_scroll_y_getter, window_self_getter, window_speech_synthesis_getter, window_top_getter,
|
||||
|
||||
@@ -3,6 +3,7 @@ use super::helpers::{
|
||||
window_host_ptr, window_receiver,
|
||||
};
|
||||
use super::*;
|
||||
use crate::{native_bridge::lightweight_popup_id_from_window, util::v8str, webidl};
|
||||
|
||||
fn window_inner_surface_dimension<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
@@ -124,8 +125,61 @@ pub(in crate::context_bootstrap) fn window_opener_getter<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if window_receiver(scope, &args).is_some() {
|
||||
let Some(receiver) = window_receiver(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
if window_has_discarded_child_browsing_context(scope, receiver) {
|
||||
rv.set_null();
|
||||
return;
|
||||
}
|
||||
if let Some(popup_id) = lightweight_popup_id_from_window(scope, receiver)
|
||||
&& let Some(host_ptr) = window_host_ptr(scope, receiver)
|
||||
&& let Some(opener) = unsafe { &*host_ptr }.lightweight_popup_opener_window(scope, popup_id)
|
||||
{
|
||||
rv.set(opener.into());
|
||||
return;
|
||||
}
|
||||
if let Some(handle) = window_child_context_handle(scope, receiver)
|
||||
&& let Some(host_ptr) = window_host_ptr(scope, receiver)
|
||||
&& let Some(opener) = unsafe { &*host_ptr }.child_browsing_context_opener(scope, handle)
|
||||
{
|
||||
rv.set(opener.into());
|
||||
return;
|
||||
}
|
||||
rv.set_null();
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn window_opener_setter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(receiver) = window_receiver(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let value = args.get(0);
|
||||
if value.is_null() {
|
||||
if let Some(host_ptr) = window_host_ptr(scope, receiver) {
|
||||
let host = unsafe { &mut *host_ptr };
|
||||
if let Some(popup_id) = lightweight_popup_id_from_window(scope, receiver) {
|
||||
host.clear_lightweight_popup_opener(popup_id);
|
||||
} else if let Some(handle) = window_child_context_handle(scope, receiver) {
|
||||
host.clear_child_browsing_context_opener(handle);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
match receiver.define_own_property(
|
||||
scope,
|
||||
v8str(scope, "opener").into(),
|
||||
value,
|
||||
v8::PropertyAttribute::NONE,
|
||||
) {
|
||||
Some(true) => {}
|
||||
Some(false) => {
|
||||
webidl::throw_type_error(scope, "Failed to replace Window.opener property.");
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@ use crate::{
|
||||
context_bootstrap::CHILD_BROWSING_CONTEXT_HANDLE_SLOT,
|
||||
document_runtime::{DocumentPolicyContainer, DomHandle},
|
||||
native_bridge::{
|
||||
InputNavigationPolicy, OwnerDispatchScope, child_window_handle_from_marker_data,
|
||||
InputNavigationPolicy, OwnerDispatchScope, PendingWindowMessageEndpoint,
|
||||
child_window_handle_from_marker_data,
|
||||
element::{
|
||||
SpecialBrowsingContextTarget, navigate_existing_browsing_context_target,
|
||||
navigate_named_iframe_target,
|
||||
},
|
||||
entered_child_window_handle,
|
||||
entered_child_window_handle, lightweight_popup_id_from_window,
|
||||
},
|
||||
runtime::{
|
||||
RendererPendingJavaScriptDialog, RendererPendingPopupActivation,
|
||||
@@ -224,13 +225,28 @@ pub(crate) fn window_open_callback<'s>(
|
||||
}
|
||||
return;
|
||||
}
|
||||
if let Some(target_window) =
|
||||
if let Some((target_handle, target_window)) =
|
||||
existing_named_child_window_for_window_open(scope, host_ptr, &parsed.target_name)
|
||||
&& !suppress_opener
|
||||
&& navigate_named_iframe_target(scope, host_ptr, &parsed.target_name, &url, None)
|
||||
{
|
||||
rv.set(target_window.into());
|
||||
return;
|
||||
let opener_child_handle = window_open_receiver_child_handle(scope, entered_window);
|
||||
let opener_endpoint = opener_child_handle
|
||||
.map(PendingWindowMessageEndpoint::ChildWindow)
|
||||
.or_else(|| {
|
||||
lightweight_popup_id_from_window(scope, entered_window)
|
||||
.map(PendingWindowMessageEndpoint::LightweightPopup)
|
||||
})
|
||||
.unwrap_or(PendingWindowMessageEndpoint::TopWindow);
|
||||
unsafe { &mut *host_ptr }.set_child_browsing_context_opener(
|
||||
scope,
|
||||
target_handle,
|
||||
opener_endpoint,
|
||||
entered_window,
|
||||
);
|
||||
if navigate_named_iframe_target(scope, host_ptr, &parsed.target_name, &url, None) {
|
||||
rv.set(target_window.into());
|
||||
return;
|
||||
}
|
||||
}
|
||||
let host = unsafe { &mut *host_ptr };
|
||||
let window_open_event = RendererPendingWindowOpenEvent {
|
||||
@@ -460,13 +476,14 @@ fn existing_named_child_window_for_window_open<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
host_ptr: *mut crate::native_bridge::JsContextHost,
|
||||
target_name: &str,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
) -> Option<(DomHandle, v8::Local<'s, v8::Object>)> {
|
||||
if target_name.is_empty() || SpecialBrowsingContextTarget::parse(target_name).is_some() {
|
||||
return None;
|
||||
}
|
||||
let host = unsafe { &mut *host_ptr };
|
||||
let handle = host.child_browsing_context_handle_by_name(target_name)?;
|
||||
host.child_browsing_context_window_wrapper(scope, handle)
|
||||
.map(|window| (handle, window))
|
||||
}
|
||||
|
||||
fn open_dialog(
|
||||
|
||||
@@ -268,7 +268,11 @@ struct WindowIdentityAccessorsDeclaration {
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(name = "Window", enumerable)]
|
||||
struct WindowPostRuntimeAccessorsDeclaration {
|
||||
#[webapi(accessor_property, getter = window_opener_getter)]
|
||||
#[webapi(
|
||||
accessor_property,
|
||||
getter = window_opener_getter,
|
||||
setter = window_opener_setter
|
||||
)]
|
||||
opener: (),
|
||||
|
||||
#[webapi(accessor_property, getter = window_inner_width_getter)]
|
||||
|
||||
@@ -41,6 +41,7 @@ struct ChildWindowProxyRecord {
|
||||
facade_context: Option<v8::Global<v8::Context>>,
|
||||
browsing_context_parent_window: Option<v8::Global<v8::Object>>,
|
||||
browsing_context_top_window: Option<v8::Global<v8::Object>>,
|
||||
browsing_context_opener: Option<ChildWindowProxyOpener>,
|
||||
cross_origin_endpoint_projections:
|
||||
HashMap<PendingWindowMessageEndpoint, v8::Global<v8::Object>>,
|
||||
realm_top_window_wrapper: Option<v8::Global<v8::Object>>,
|
||||
@@ -50,6 +51,11 @@ struct ChildWindowProxyRecord {
|
||||
default_execution_context_id: Option<i64>,
|
||||
}
|
||||
|
||||
struct ChildWindowProxyOpener {
|
||||
endpoint: PendingWindowMessageEndpoint,
|
||||
window: v8::Global<v8::Object>,
|
||||
}
|
||||
|
||||
impl ChildWindowProxyRecords {
|
||||
fn record_mut(&mut self, handle: DomHandle) -> &mut ChildWindowProxyRecord {
|
||||
self.records.entry(handle).or_default()
|
||||
@@ -161,6 +167,41 @@ impl ChildWindowProxyRecords {
|
||||
.map(|top| v8::Local::new(scope, top))
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::context_host) fn set_browsing_context_opener(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
handle: DomHandle,
|
||||
endpoint: PendingWindowMessageEndpoint,
|
||||
window: v8::Local<'_, v8::Object>,
|
||||
) {
|
||||
self.record_mut(handle).browsing_context_opener = Some(ChildWindowProxyOpener {
|
||||
endpoint,
|
||||
window: v8::Global::new(scope, window),
|
||||
});
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::context_host) fn clear_browsing_context_opener(
|
||||
&mut self,
|
||||
handle: DomHandle,
|
||||
) {
|
||||
if let Some(record) = self.records.get_mut(&handle) {
|
||||
record.browsing_context_opener = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::context_host) fn browsing_context_opener<'s>(
|
||||
&self,
|
||||
scope: &mut v8::PinScope<'s, '_, ()>,
|
||||
handle: DomHandle,
|
||||
) -> Option<(PendingWindowMessageEndpoint, v8::Local<'s, v8::Object>)> {
|
||||
let opener = self
|
||||
.records
|
||||
.get(&handle)?
|
||||
.browsing_context_opener
|
||||
.as_ref()?;
|
||||
Some((opener.endpoint, v8::Local::new(scope, &opener.window)))
|
||||
}
|
||||
|
||||
fn cross_origin_endpoint_projection<'s>(
|
||||
&self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
@@ -1285,6 +1326,67 @@ impl JsContextHost {
|
||||
self.child_window_proxy_records.realm_top(scope, handle)
|
||||
}
|
||||
|
||||
pub(crate) fn set_child_browsing_context_opener<'s>(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
handle: DomHandle,
|
||||
endpoint: PendingWindowMessageEndpoint,
|
||||
opener: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
if !self.child_browsing_context_is_live(handle) {
|
||||
return;
|
||||
}
|
||||
self.child_window_proxy_records
|
||||
.set_browsing_context_opener(scope, handle, endpoint, opener);
|
||||
}
|
||||
|
||||
pub(crate) fn clear_child_browsing_context_opener(&mut self, handle: DomHandle) {
|
||||
self.child_window_proxy_records
|
||||
.clear_browsing_context_opener(handle);
|
||||
}
|
||||
|
||||
pub(crate) fn child_browsing_context_opener<'s>(
|
||||
&self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
handle: DomHandle,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let (endpoint, opener) = self
|
||||
.child_window_proxy_records
|
||||
.browsing_context_opener(scope, handle)?;
|
||||
self.window_opener_endpoint_is_live(scope, endpoint, opener)
|
||||
.then_some(opener)
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::context_host) fn window_opener_endpoint_is_live<'s>(
|
||||
&self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
endpoint: PendingWindowMessageEndpoint,
|
||||
opener: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
match endpoint {
|
||||
PendingWindowMessageEndpoint::TopWindow => true,
|
||||
PendingWindowMessageEndpoint::ChildWindow(opener_handle) => {
|
||||
if !self.child_browsing_context_is_live(opener_handle) {
|
||||
false
|
||||
} else {
|
||||
opener
|
||||
.get_creation_context(scope)
|
||||
.and_then(|context| {
|
||||
self.window_execution_context_identity_for_access_check(context)
|
||||
})
|
||||
.is_some_and(|identity| {
|
||||
identity.dispatch_scope()
|
||||
== super::super::OwnerDispatchScope::Child(opener_handle)
|
||||
&& self.window_execution_context_identity_is_current(identity)
|
||||
})
|
||||
}
|
||||
}
|
||||
PendingWindowMessageEndpoint::LightweightPopup(popup_id) => {
|
||||
self.lightweight_popup_is_open(popup_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::context_host) fn child_browsing_context_parent_window<'s>(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
|
||||
@@ -173,6 +173,21 @@ struct LightweightPopupWindowNameDeclaration {
|
||||
name: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = "Object")]
|
||||
struct LightweightPopupWindowOpenerDeclaration<'scope> {
|
||||
popup_id: v8::Local<'scope, v8::BigInt>,
|
||||
#[webapi(
|
||||
accessor_property,
|
||||
enumerable,
|
||||
getter = lightweight_popup_window_opener_getter,
|
||||
setter = lightweight_popup_window_opener_setter,
|
||||
data = self.popup_id,
|
||||
setter_data = self.popup_id
|
||||
)]
|
||||
opener: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = "Object")]
|
||||
struct LightweightPopupComputedStyleMethodDeclaration<'scope> {
|
||||
@@ -424,6 +439,7 @@ enum LightweightPopupLifecycle {
|
||||
pub(super) struct LightweightPopupBrowsingContextRecord {
|
||||
window_proxy: v8::Global<v8::Object>,
|
||||
opener: Option<super::PendingWindowMessageEndpoint>,
|
||||
opener_window: Option<v8::Global<v8::Object>>,
|
||||
location_url: Url,
|
||||
opener_sandbox_policy: Option<DocumentSandboxPolicy>,
|
||||
lifecycle: LightweightPopupLifecycle,
|
||||
@@ -610,6 +626,7 @@ impl JsContextHost {
|
||||
.expect("lightweight popup navigation id space exhausted"),
|
||||
);
|
||||
record.opener = None;
|
||||
record.opener_window = None;
|
||||
Some(LightweightPopupCloseTransition {
|
||||
retired_owner: open.document.owner,
|
||||
retired_local_window_id: open.document.local_window_id,
|
||||
@@ -760,6 +777,12 @@ impl JsContextHost {
|
||||
LightweightPopupWindowNameDeclaration::default()
|
||||
.initialize(scope, window)
|
||||
.ok()?;
|
||||
LightweightPopupWindowOpenerDeclaration {
|
||||
popup_id: popup_id_private_value,
|
||||
opener: (),
|
||||
}
|
||||
.initialize(scope, window)
|
||||
.ok()?;
|
||||
install_window_location_history_navigation_runtime_state(
|
||||
scope,
|
||||
window,
|
||||
@@ -784,11 +807,7 @@ impl JsContextHost {
|
||||
set_object_slot(scope, window, "top", window.into());
|
||||
set_object_slot(scope, window, "frames", window.into());
|
||||
if let Some(opener) = opener {
|
||||
set_object_slot(scope, window, "opener", opener.into());
|
||||
install_lightweight_popup_viewport_surface_from_opener(scope, opener, window);
|
||||
} else {
|
||||
let opener = v8::null(scope);
|
||||
set_object_slot(scope, window, "opener", opener.into());
|
||||
}
|
||||
if let Ok(navigator) =
|
||||
crate::context_bootstrap::build_lightweight_popup_window_navigator_object(
|
||||
@@ -855,6 +874,7 @@ impl JsContextHost {
|
||||
LightweightPopupBrowsingContextRecord {
|
||||
window_proxy: v8::Global::new(scope, window),
|
||||
opener: opener_endpoint,
|
||||
opener_window: opener.map(|opener| v8::Global::new(scope, opener)),
|
||||
location_url: initial_url.clone(),
|
||||
opener_sandbox_policy,
|
||||
lifecycle: LightweightPopupLifecycle::Open(Box::new(LightweightPopupOpenState {
|
||||
@@ -1090,6 +1110,32 @@ impl JsContextHost {
|
||||
.and_then(|record| record.opener)
|
||||
}
|
||||
|
||||
pub(crate) fn lightweight_popup_opener_window<'s>(
|
||||
&self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
popup_id: u64,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let (endpoint, opener) = {
|
||||
let record = self.lightweight_popup_browsing_contexts.get(&popup_id)?;
|
||||
if !record.is_open() {
|
||||
return None;
|
||||
}
|
||||
let endpoint = record.opener?;
|
||||
let opener = v8::Local::new(scope, record.opener_window.as_ref()?);
|
||||
(endpoint, opener)
|
||||
};
|
||||
self.window_opener_endpoint_is_live(scope, endpoint, opener)
|
||||
.then_some(opener)
|
||||
}
|
||||
|
||||
pub(crate) fn clear_lightweight_popup_opener(&mut self, popup_id: u64) {
|
||||
let Some(record) = self.lightweight_popup_browsing_contexts.get_mut(&popup_id) else {
|
||||
return;
|
||||
};
|
||||
record.opener = None;
|
||||
record.opener_window = None;
|
||||
}
|
||||
|
||||
pub(crate) fn lightweight_popup_origin(&self, popup_id: u64) -> Option<String> {
|
||||
self.lightweight_popup_access_origin(popup_id)
|
||||
.map(|origin| origin.serialized_origin())
|
||||
@@ -4254,6 +4300,64 @@ fn lightweight_popup_window_name_setter<'s>(
|
||||
}
|
||||
}
|
||||
|
||||
fn lightweight_popup_window_opener_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(popup_id) = lightweight_popup_id_from_value(scope, args.data()) else {
|
||||
throw_type_error(
|
||||
scope,
|
||||
"Window.opener getter called with invalid popup data.",
|
||||
);
|
||||
return;
|
||||
};
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
rv.set_null();
|
||||
return;
|
||||
};
|
||||
match unsafe { &*host_ptr }.lightweight_popup_opener_window(scope, popup_id) {
|
||||
Some(opener) => rv.set(opener.into()),
|
||||
None => rv.set_null(),
|
||||
}
|
||||
}
|
||||
|
||||
fn lightweight_popup_window_opener_setter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(popup_id) = lightweight_popup_id_from_value(scope, args.data()) else {
|
||||
throw_type_error(
|
||||
scope,
|
||||
"Window.opener setter called with invalid popup data.",
|
||||
);
|
||||
return;
|
||||
};
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
return;
|
||||
};
|
||||
let host = unsafe { &mut *host_ptr };
|
||||
let value = args.get(0);
|
||||
if value.is_null() {
|
||||
host.clear_lightweight_popup_opener(popup_id);
|
||||
return;
|
||||
}
|
||||
let Some(window) = host.lightweight_popup_window(scope, popup_id) else {
|
||||
return;
|
||||
};
|
||||
match window.define_own_property(
|
||||
scope,
|
||||
v8str(scope, "opener").into(),
|
||||
value,
|
||||
v8::PropertyAttribute::NONE,
|
||||
) {
|
||||
Some(true) => {}
|
||||
Some(false) => throw_type_error(scope, "Failed to replace Window.opener property."),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn lightweight_popup_close_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
|
||||
@@ -25222,6 +25222,79 @@ fn window_open_resolves_relative_urls_against_the_entry_function_realm() {
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_open_existing_named_child_sets_and_can_disown_its_opener() {
|
||||
let mut vm = new_storage_test_vm("https://example.com/");
|
||||
|
||||
vm.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const frame = document.createElement("iframe");
|
||||
frame.name = "named-child";
|
||||
(document.body || document.documentElement || document).appendChild(frame);
|
||||
globalThis.__namedOpenerFrame = frame;
|
||||
return "created";
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("named child opener setup should evaluate");
|
||||
vm.drain_pending_child_frame_work_for_test();
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const child = __namedOpenerFrame.contentWindow;
|
||||
const before = child.opener;
|
||||
const opened = open("about:blank#opened", "named-child");
|
||||
const descriptor = Object.getOwnPropertyDescriptor(child, "opener");
|
||||
const openerGet = descriptor.get;
|
||||
const openerSet = descriptor.set;
|
||||
const selected = {
|
||||
initiallyNull: before === null,
|
||||
returnedExisting: opened === child,
|
||||
openerIsTop: child.opener === window,
|
||||
directGetterIsTop: openerGet() === window,
|
||||
descriptorShape: [
|
||||
typeof openerGet,
|
||||
typeof openerSet,
|
||||
descriptor.enumerable,
|
||||
descriptor.configurable
|
||||
].join(":")
|
||||
};
|
||||
|
||||
child.opener = null;
|
||||
const disowned = {
|
||||
openerIsNull: child.opener === null,
|
||||
directGetterIsNull: openerGet() === null,
|
||||
descriptorPreserved:
|
||||
Object.getOwnPropertyDescriptor(child, "opener").get === openerGet
|
||||
};
|
||||
|
||||
child.opener = "immaterial";
|
||||
const replacement = Object.getOwnPropertyDescriptor(child, "opener");
|
||||
return JSON.stringify({
|
||||
selected,
|
||||
disowned,
|
||||
replaced: {
|
||||
value: child.opener,
|
||||
directGetterIsNull: openerGet() === null,
|
||||
writable: replacement.writable,
|
||||
enumerable: replacement.enumerable,
|
||||
configurable: replacement.configurable
|
||||
}
|
||||
});
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("named child opener semantics should evaluate");
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"selected":{"initiallyNull":true,"returnedExisting":true,"openerIsTop":true,"directGetterIsTop":true,"descriptorShape":"function:function:true:true"},"disowned":{"openerIsNull":true,"directGetterIsNull":true,"descriptorPreserved":true},"replaced":{"value":"immaterial","directGetterIsNull":true,"writable":true,"enumerable":true,"configurable":true}}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_open_about_blank_returns_lightweight_popup_window() {
|
||||
let mut vm = new_storage_test_vm("https://example.com/");
|
||||
@@ -25270,6 +25343,71 @@ fn window_open_about_blank_returns_lightweight_popup_window() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lightweight_popup_opener_accessor_preserves_and_disowns_the_underlying_relation() {
|
||||
let mut vm = new_storage_test_vm("https://example.com/");
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const topOpenerGet = Object.getOwnPropertyDescriptor(window, "opener").get;
|
||||
const replaced = open();
|
||||
const replacedDescriptor = Object.getOwnPropertyDescriptor(replaced, "opener");
|
||||
const replacedGet = replacedDescriptor.get;
|
||||
replaced.opener = "replacement";
|
||||
const replacementDescriptor = Object.getOwnPropertyDescriptor(replaced, "opener");
|
||||
|
||||
const disowned = open();
|
||||
const disownedDescriptor = Object.getOwnPropertyDescriptor(disowned, "opener");
|
||||
const disownedGet = disownedDescriptor.get;
|
||||
disowned.opener = null;
|
||||
const descriptorAfterNull = Object.getOwnPropertyDescriptor(disowned, "opener");
|
||||
|
||||
const closed = open();
|
||||
const closedGet = Object.getOwnPropertyDescriptor(closed, "opener").get;
|
||||
closed.close();
|
||||
closed.opener = "closed replacement";
|
||||
|
||||
return JSON.stringify({
|
||||
replaced: {
|
||||
accessorShape: [
|
||||
typeof replacedGet,
|
||||
typeof replacedDescriptor.set,
|
||||
replacedDescriptor.writable,
|
||||
replacedDescriptor.enumerable,
|
||||
replacedDescriptor.configurable
|
||||
].join(":"),
|
||||
value: replaced.opener,
|
||||
boundGetterKeepsRelation: replacedGet() === window,
|
||||
borrowedGetterKeepsRelation: topOpenerGet.call(replaced) === window,
|
||||
dataShape: [
|
||||
replacementDescriptor.writable,
|
||||
replacementDescriptor.enumerable,
|
||||
replacementDescriptor.configurable
|
||||
].join(":")
|
||||
},
|
||||
disowned: {
|
||||
valueIsNull: disowned.opener === null,
|
||||
boundGetterIsNull: disownedGet() === null,
|
||||
accessorPreserved: descriptorAfterNull.get === disownedGet
|
||||
},
|
||||
closed: {
|
||||
boundGetterIsNull: closedGet() === null,
|
||||
value: closed.opener
|
||||
}
|
||||
});
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("lightweight popup opener accessor semantics should evaluate");
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"replaced":{"accessorShape":"function:function::true:true","value":"replacement","boundGetterKeepsRelation":true,"borrowedGetterKeepsRelation":true,"dataShape":"true:true:true"},"disowned":{"valueIsNull":true,"boundGetterIsNull":true,"accessorPreserved":true},"closed":{"boundGetterIsNull":true,"value":"closed replacement"}}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_open_blank_targets_expose_mutable_empty_browsing_context_names() {
|
||||
let mut vm = new_storage_test_vm("https://example.com/");
|
||||
|
||||
Reference in New Issue
Block a user