mirror of
https://github.com/lexmount/moli.git
synced 2026-10-09 00:01:06 +00:00
fix(webidl): enforce Window global receiver semantics
This commit is contained in:
@@ -176,7 +176,7 @@ mod window_accessors;
|
||||
mod window_events;
|
||||
mod window_lazy_surface;
|
||||
mod window_receiver;
|
||||
pub(crate) use window_receiver::is_window_receiver;
|
||||
pub(crate) use window_receiver::{is_window_receiver, mark_window_receiver};
|
||||
mod window_runtime;
|
||||
|
||||
pub(crate) use form_data_runtime::{
|
||||
|
||||
@@ -57,6 +57,7 @@ pub(crate) const ORIGINAL_WEBASSEMBLY_GLOBAL_VALUE_GETTER_SLOT: &str =
|
||||
"__moliOriginalWebAssemblyGlobalValueGetter";
|
||||
const WINDOW_INDEXED_DB_SURFACE_SLOT: &str = "moli.Window.indexedDB";
|
||||
const WINDOW_ORIGIN_RUNTIME_SLOT: &str = "__moliWindowOriginRuntime";
|
||||
const WINDOW_STATUS_RUNTIME_SLOT: &str = "__moliWindowStatusRuntime";
|
||||
const WINDOW_INTRINSIC_EVAL_SLOT: &str = "__moliWindowIntrinsicEval";
|
||||
pub(in crate::context_bootstrap) const WINDOW_SECURE_CONTEXT_AVAILABLE_SLOT: &str =
|
||||
"__moliWindowSecureContextAvailable";
|
||||
@@ -266,6 +267,13 @@ struct WindowPublicSurfaceAccessorsDeclaration<'scope> {
|
||||
setter = window_name_runtime_setter
|
||||
)]
|
||||
name: (),
|
||||
#[webapi(
|
||||
accessor_property,
|
||||
enumerable,
|
||||
getter = window_status_runtime_getter,
|
||||
setter = window_status_runtime_setter
|
||||
)]
|
||||
status: (),
|
||||
}
|
||||
|
||||
#[derive(Default, WebApiObject)]
|
||||
@@ -505,20 +513,25 @@ fn legacy_unforgeable_window_getter<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
rv.set(legacy_unforgeable_window_slot_value(
|
||||
scope,
|
||||
args.this(),
|
||||
WINDOW_SELF_SLOT,
|
||||
));
|
||||
if let Some(value) = legacy_unforgeable_window_slot_value(scope, args.this(), WINDOW_SELF_SLOT)
|
||||
{
|
||||
rv.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn legacy_unforgeable_window_slot_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
slot: &'static str,
|
||||
) -> v8::Local<'s, v8::Value> {
|
||||
object_hidden_value(scope, receiver, slot)
|
||||
.unwrap_or_else(|| scope.get_current_context().global(scope).into())
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
if !super::is_window_receiver(scope, receiver) {
|
||||
throw_type_error(scope, "Window getter called on incompatible receiver.");
|
||||
return None;
|
||||
}
|
||||
Some(
|
||||
object_hidden_value(scope, receiver, slot)
|
||||
.unwrap_or_else(|| scope.get_current_context().global(scope).into()),
|
||||
)
|
||||
}
|
||||
|
||||
fn document_fullscreen_enabled_getter<'s>(
|
||||
@@ -727,11 +740,10 @@ fn legacy_unforgeable_self_getter<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
rv.set(legacy_unforgeable_window_slot_value(
|
||||
scope,
|
||||
args.this(),
|
||||
WINDOW_SELF_SLOT,
|
||||
));
|
||||
if let Some(value) = legacy_unforgeable_window_slot_value(scope, args.this(), WINDOW_SELF_SLOT)
|
||||
{
|
||||
rv.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn replaceable_window_alias_set<'s>(
|
||||
@@ -739,6 +751,10 @@ fn replaceable_window_alias_set<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
name: &'static str,
|
||||
) {
|
||||
if !super::is_window_receiver(scope, args.this()) {
|
||||
throw_type_error(scope, "Window setter called on incompatible receiver.");
|
||||
return;
|
||||
}
|
||||
define_replaceable_window_property(scope, args.this(), name, args.get(0));
|
||||
}
|
||||
|
||||
@@ -755,11 +771,11 @@ fn legacy_unforgeable_parent_getter<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
rv.set(legacy_unforgeable_window_slot_value(
|
||||
scope,
|
||||
args.this(),
|
||||
WINDOW_PARENT_SLOT,
|
||||
));
|
||||
if let Some(value) =
|
||||
legacy_unforgeable_window_slot_value(scope, args.this(), WINDOW_PARENT_SLOT)
|
||||
{
|
||||
rv.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn replaceable_parent_setter<'s>(
|
||||
@@ -775,11 +791,9 @@ fn legacy_unforgeable_top_getter<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
rv.set(legacy_unforgeable_window_slot_value(
|
||||
scope,
|
||||
args.this(),
|
||||
WINDOW_TOP_SLOT,
|
||||
));
|
||||
if let Some(value) = legacy_unforgeable_window_slot_value(scope, args.this(), WINDOW_TOP_SLOT) {
|
||||
rv.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn legacy_unforgeable_frames_getter<'s>(
|
||||
@@ -787,11 +801,11 @@ fn legacy_unforgeable_frames_getter<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
rv.set(legacy_unforgeable_window_slot_value(
|
||||
scope,
|
||||
args.this(),
|
||||
WINDOW_FRAMES_SLOT,
|
||||
));
|
||||
if let Some(value) =
|
||||
legacy_unforgeable_window_slot_value(scope, args.this(), WINDOW_FRAMES_SLOT)
|
||||
{
|
||||
rv.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn replaceable_frames_setter<'s>(
|
||||
@@ -892,6 +906,10 @@ fn window_surface_replaceable_setter<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
if !super::is_window_receiver(scope, args.this()) {
|
||||
throw_type_error(scope, "Window setter called on incompatible receiver.");
|
||||
return;
|
||||
}
|
||||
let Some(name) = callback_data_item(
|
||||
scope,
|
||||
&args,
|
||||
@@ -910,6 +928,10 @@ fn window_name_runtime_getter<'s>(
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
let receiver = callback_this_object(scope, &args);
|
||||
if !super::is_window_receiver(scope, receiver) {
|
||||
throw_type_error(scope, "Window.name getter called on incompatible receiver.");
|
||||
return;
|
||||
}
|
||||
let value = object_hidden_value(scope, receiver, WINDOW_NAME_SLOT)
|
||||
.unwrap_or_else(|| v8::String::empty(scope).into());
|
||||
rv.set(value);
|
||||
@@ -921,6 +943,10 @@ fn window_name_runtime_setter<'s>(
|
||||
_rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
let receiver = callback_this_object(scope, &args);
|
||||
if !super::is_window_receiver(scope, receiver) {
|
||||
throw_type_error(scope, "Window.name setter called on incompatible receiver.");
|
||||
return;
|
||||
}
|
||||
let next = args
|
||||
.get(0)
|
||||
.to_string(scope)
|
||||
@@ -934,6 +960,44 @@ fn window_name_runtime_setter<'s>(
|
||||
define_non_enumerable_string_property(scope, receiver, WINDOW_NAME_SLOT, &next);
|
||||
}
|
||||
|
||||
fn window_status_runtime_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
let receiver = callback_this_object(scope, &args);
|
||||
if !super::is_window_receiver(scope, receiver) {
|
||||
throw_type_error(
|
||||
scope,
|
||||
"Window.status getter called on incompatible receiver.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
rv.set(
|
||||
get_private_value(scope, receiver, WINDOW_STATUS_RUNTIME_SLOT)
|
||||
.unwrap_or_else(|| v8::String::empty(scope).into()),
|
||||
);
|
||||
}
|
||||
|
||||
fn window_status_runtime_setter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
let receiver = callback_this_object(scope, &args);
|
||||
if !super::is_window_receiver(scope, receiver) {
|
||||
throw_type_error(
|
||||
scope,
|
||||
"Window.status setter called on incompatible receiver.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
let Some(next) = args.get(0).to_string(scope) else {
|
||||
return;
|
||||
};
|
||||
set_private_value(scope, receiver, WINDOW_STATUS_RUNTIME_SLOT, next.into());
|
||||
}
|
||||
|
||||
fn install_public_window_surface_accessors<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
global: v8::Local<'s, v8::Object>,
|
||||
@@ -1058,6 +1122,7 @@ fn install_public_window_surface_accessors<'s>(
|
||||
session_storage: (),
|
||||
indexed_db: (),
|
||||
name: (),
|
||||
status: (),
|
||||
}
|
||||
.initialize(scope, global)?;
|
||||
WindowAdditionalReplaceableAccessorsDeclaration {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use super::shared::WINDOW_NAVIGATOR_SLOT;
|
||||
use crate::util::{
|
||||
callback_data_index_value, context_host_ptr_from_context_slot,
|
||||
context_host_ptr_from_window_object, get_private_value, throw_type_error,
|
||||
context_host_ptr_from_window_object, get_private_value, set_private_value, throw_type_error,
|
||||
};
|
||||
|
||||
const WINDOW_BRAND_SLOT: &str = "__moliWindowBrand";
|
||||
|
||||
/// Recognizes a native Window receiver for WebIDL brand checks.
|
||||
///
|
||||
/// A retained, detached global proxy can lose both its creation context and
|
||||
@@ -15,7 +17,9 @@ pub(crate) fn is_window_receiver<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
if is_live_window_receiver(scope, receiver) {
|
||||
if is_live_window_receiver(scope, receiver)
|
||||
|| get_private_value(scope, receiver, WINDOW_BRAND_SLOT).is_some()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
let current_context = scope.get_current_context();
|
||||
@@ -23,6 +27,16 @@ pub(crate) fn is_window_receiver<'s>(
|
||||
&& context_host_ptr_from_context_slot(current_context).is_some()
|
||||
}
|
||||
|
||||
/// Marks a Window-shaped native object that intentionally has no execution
|
||||
/// context, such as an iframe owned by a DOMParser-created Document.
|
||||
pub(crate) fn mark_window_receiver(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
receiver: v8::Local<'_, v8::Object>,
|
||||
) {
|
||||
let branded = v8::Boolean::new(scope, true);
|
||||
set_private_value(scope, receiver, WINDOW_BRAND_SLOT, branded.into());
|
||||
}
|
||||
|
||||
/// Recognizes a Window whose V8 object still exposes its live realm markers.
|
||||
///
|
||||
/// This is deliberately narrower than the WebIDL brand check above. Bound
|
||||
|
||||
@@ -14,7 +14,8 @@ pub(super) use base64::{window_atob_callback, window_btoa_callback};
|
||||
pub(super) use dialogs::entered_window_api_base_url;
|
||||
pub(super) use dialogs::{window_alert_callback, window_confirm_callback, window_prompt_callback};
|
||||
pub(crate) use dialogs::{
|
||||
window_const_false_callback, window_noop_callback, window_open_callback, window_stop_callback,
|
||||
window_blur_callback, window_const_false_callback, window_focus_callback, window_noop_callback,
|
||||
window_open_callback, window_stop_callback,
|
||||
};
|
||||
pub(crate) use navigator::{
|
||||
LegacyStorageQuotaCallbackOutcome, LegacyStorageQuotaCallbackTask,
|
||||
@@ -68,7 +69,7 @@ struct ChildWindowOwnMethodsDeclaration {
|
||||
open: (),
|
||||
#[webapi(method, length = 0, callback = window_noop_callback)]
|
||||
close: (),
|
||||
#[webapi(method, length = 0, callback = window_noop_callback)]
|
||||
#[webapi(method, length = 0, callback = window_blur_callback)]
|
||||
blur: (),
|
||||
#[webapi(method, length = 0, callback = window_const_false_callback)]
|
||||
find: (),
|
||||
|
||||
@@ -68,6 +68,39 @@ pub(crate) fn window_noop_callback(
|
||||
) {
|
||||
}
|
||||
|
||||
pub(crate) fn window_focus_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let receiver = args.this();
|
||||
if !crate::context_bootstrap::is_window_receiver(scope, receiver) {
|
||||
webidl::throw_type_error(scope, "Window.focus called on incompatible receiver.");
|
||||
return;
|
||||
}
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
return;
|
||||
};
|
||||
if let Some(crate::native_bridge::OwnerDispatchScope::Child(handle)) =
|
||||
super::super::navigation_window::runtime_window_dispatch_scope(scope, receiver)
|
||||
{
|
||||
crate::native_bridge::element::focus_element(scope, host_ptr, handle);
|
||||
}
|
||||
rv.set_undefined();
|
||||
}
|
||||
|
||||
pub(crate) fn window_blur_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !crate::context_bootstrap::is_window_receiver(scope, args.this()) {
|
||||
webidl::throw_type_error(scope, "Window.blur called on incompatible receiver.");
|
||||
return;
|
||||
}
|
||||
rv.set_undefined();
|
||||
}
|
||||
|
||||
pub(crate) fn window_stop_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
|
||||
@@ -72,10 +72,10 @@ struct WindowEarlyTemplateMethodsDeclaration {
|
||||
#[webapi(method, length = 0, callback = window_noop_callback)]
|
||||
close: (),
|
||||
|
||||
#[webapi(method, length = 0, callback = window_noop_callback)]
|
||||
#[webapi(method, length = 0, callback = window_focus_callback)]
|
||||
focus: (),
|
||||
|
||||
#[webapi(method, length = 0, callback = window_noop_callback)]
|
||||
#[webapi(method, length = 0, callback = window_blur_callback)]
|
||||
blur: (),
|
||||
|
||||
#[webapi(method, length = 0, callback = window_const_false_callback)]
|
||||
|
||||
@@ -606,20 +606,13 @@ unsafe extern "C" fn window_access_check_callback(
|
||||
return true;
|
||||
}
|
||||
|
||||
let Some((accessing_host_ptr, accessing_identity)) = (|| {
|
||||
let host_ptr = crate::util::context_host_ptr_from_context_slot(accessing_context)?;
|
||||
let identity = unsafe { &*host_ptr }
|
||||
.window_execution_context_identity_for_access_check(accessing_context)?;
|
||||
Some((host_ptr, identity))
|
||||
})() else {
|
||||
let Some(accessing_host_ptr) =
|
||||
crate::util::context_host_ptr_from_context_slot(accessing_context)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
let Some((accessed_host_ptr, accessed_identity)) = (|| {
|
||||
let host_ptr = crate::util::context_host_ptr_from_context_slot(accessed_context)?;
|
||||
let identity = unsafe { &*host_ptr }
|
||||
.window_execution_context_identity_for_access_check(accessed_context)?;
|
||||
Some((host_ptr, identity))
|
||||
})() else {
|
||||
let Some(accessed_host_ptr) = crate::util::context_host_ptr_from_context_slot(accessed_context)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if accessing_host_ptr != accessed_host_ptr {
|
||||
@@ -627,7 +620,34 @@ unsafe extern "C" fn window_access_check_callback(
|
||||
}
|
||||
|
||||
let host = unsafe { &*accessing_host_ptr };
|
||||
host.window_execution_context_can_access(accessing_identity, accessed_identity)
|
||||
let Some(accessing_identity) =
|
||||
host.window_execution_context_identity_for_access_check(accessing_context)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if let Some(accessed_identity) =
|
||||
host.window_execution_context_identity_for_access_check(accessed_context)
|
||||
&& host.window_execution_context_can_access(accessing_identity, accessed_identity)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if !host.window_execution_context_identity_is_current(accessing_identity)
|
||||
|| accessed_context
|
||||
.get_slot::<super::super::RuntimeObservableContextToken>()
|
||||
.is_none()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Removing a same-origin iframe retires its LocalWindow registration, but
|
||||
// JavaScript can still retain that inner global long enough to clean up
|
||||
// listeners and other realm-owned state. The context's internalized
|
||||
// security token is the last live effective-origin snapshot, so equal
|
||||
// tokens preserve that access without reopening retired cross-origin or
|
||||
// opaque realms.
|
||||
accessing_context
|
||||
.get_security_token(scope)
|
||||
.strict_equals(accessed_context.get_security_token(scope))
|
||||
}
|
||||
|
||||
impl JsContextHost {
|
||||
|
||||
+1
@@ -45,6 +45,7 @@ pub(super) fn build_detached_iframe_content_window<'s>(
|
||||
let window = DetachedIframeWindowDeclaration::new(parent, top, document)
|
||||
.bind(scope)
|
||||
.ok()?;
|
||||
crate::context_bootstrap::mark_window_receiver(scope, window);
|
||||
set_document_associated_window(scope, document, window);
|
||||
crate::network_host::install_fetch_constructors_for_base_url(scope, window, base_url);
|
||||
install_detached_iframe_window_messaging(scope, window);
|
||||
|
||||
@@ -14493,7 +14493,7 @@ __domainAccessFrame.src = globalThis.__replacementDomainChildUrl;
|
||||
async fn one_sided_document_domain_disables_original_tuple_origin_fast_path() {
|
||||
const HOST: &str = "www.example.test";
|
||||
|
||||
let server = StaticHttpServer::spawn(1).await;
|
||||
let server = StaticHttpServer::spawn(2).await;
|
||||
let parent_url = server.url_for_host(HOST, "/page.html");
|
||||
let child_url = server.url_for_host(HOST, "/child.html");
|
||||
let loader = static_http_loader([server.resolve_entry(HOST)]);
|
||||
@@ -14573,7 +14573,52 @@ globalThis.__probeOneSidedDomainFrame = () => {
|
||||
.expect("restored exact-domain access probe should evaluate"),
|
||||
"www.example.test"
|
||||
);
|
||||
assert_eq!(server.finish_targets().await, vec!["/child.html"]);
|
||||
|
||||
vm.exec(
|
||||
r#"
|
||||
const retiredFrame = document.createElement("iframe");
|
||||
globalThis.__retiredOneSidedDomainChildLoaded = false;
|
||||
retiredFrame.onload = () => { globalThis.__retiredOneSidedDomainChildLoaded = true; };
|
||||
retiredFrame.src = globalThis.__oneSidedDomainChildUrl;
|
||||
(document.body || document.documentElement || document).appendChild(retiredFrame);
|
||||
globalThis.__retiredOneSidedDomainWindow = retiredFrame.contentWindow;
|
||||
"#,
|
||||
None,
|
||||
)
|
||||
.expect("one-sided document.domain retired Window setup should run");
|
||||
advance_page_task_executor_until_eval_equals(
|
||||
&mut vm,
|
||||
&loader,
|
||||
"String(globalThis.__retiredOneSidedDomainChildLoaded)",
|
||||
"true",
|
||||
"fresh child without document.domain should load",
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
vm.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const probe = () => {
|
||||
try {
|
||||
return __retiredOneSidedDomainWindow.document.domain;
|
||||
} catch (error) {
|
||||
return error && error.name;
|
||||
}
|
||||
};
|
||||
const beforeRemoval = probe();
|
||||
document.querySelectorAll("iframe")[1].remove();
|
||||
return [beforeRemoval, probe()].join("|");
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("retired one-sided document.domain Window probe should evaluate"),
|
||||
"SecurityError|SecurityError"
|
||||
);
|
||||
assert_eq!(
|
||||
server.finish_targets().await,
|
||||
vec!["/child.html", "/child.html"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -371,6 +371,126 @@ fn window_global_accessors_use_the_borrowed_window_receiver() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cross_realm_window_members_apply_global_interface_receiver_semantics() {
|
||||
let mut vm = new_storage_test_vm("https://window-global-receiver.test/");
|
||||
|
||||
vm.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const root = document.documentElement ||
|
||||
document.appendChild(document.createElement("html"));
|
||||
const body = document.body || root.appendChild(document.createElement("body"));
|
||||
const frame = document.createElement("iframe");
|
||||
frame.id = "global-receiver-frame";
|
||||
frame.name = "dummy";
|
||||
body.appendChild(frame);
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("cross-realm Window receiver child frame should be created");
|
||||
materialize_single_child_default_realm_for_test(
|
||||
&mut vm,
|
||||
"cross-realm Window receiver child Realm",
|
||||
);
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const frame = document.getElementById("global-receiver-frame");
|
||||
const other = frame.contentWindow;
|
||||
const notWindow = Object.create(Object.getPrototypeOf(other));
|
||||
const throwsOtherTypeError = callback => {
|
||||
try {
|
||||
callback();
|
||||
return false;
|
||||
} catch (error) {
|
||||
return error instanceof other.TypeError && !(error instanceof TypeError);
|
||||
}
|
||||
};
|
||||
|
||||
const invalidGetters = [
|
||||
() => { Object.create(other).window; },
|
||||
() => Object.getOwnPropertyDescriptor(other, "history").get.call(notWindow),
|
||||
() => Reflect.get(other, "screen", notWindow),
|
||||
() => new Proxy(other, {}).onclick
|
||||
].every(throwsOtherTypeError);
|
||||
|
||||
const invalidSetters = [
|
||||
() => { Object.create(other).name = "ignored"; },
|
||||
() => Object.getOwnPropertyDescriptor(other, "status").set.call(notWindow, "ignored"),
|
||||
() => Reflect.set(other, "parent", window, notWindow),
|
||||
() => { new Proxy(other, {}).location = location; }
|
||||
].every(throwsOtherTypeError);
|
||||
|
||||
const invalidOperations = [
|
||||
() => Object.create(other).focus(),
|
||||
() => other.clearInterval.call(notWindow, 0),
|
||||
() => Reflect.apply(other.blur, notWindow, []),
|
||||
() => new Proxy(other, {}).removeEventListener("foo", () => {})
|
||||
].every(throwsOtherTypeError);
|
||||
|
||||
const nameGetter = Object.getOwnPropertyDescriptor(other, "name").get;
|
||||
const statusDescriptor = Object.getOwnPropertyDescriptor(other, "status");
|
||||
const nullGetters =
|
||||
Reflect.get(other, "self", null) === other &&
|
||||
Reflect.get(other, "document", undefined) === other.document &&
|
||||
nameGetter.call(null) === "dummy";
|
||||
|
||||
const newSelf = {};
|
||||
const nullSetters =
|
||||
Reflect.set(other, "self", newSelf, null) &&
|
||||
Reflect.set(other, "name", "newName", undefined);
|
||||
statusDescriptor.set.call(null, "ready");
|
||||
|
||||
const extractedFocus = other.focus;
|
||||
extractedFocus();
|
||||
|
||||
let caughtEvent;
|
||||
const eventListener = event => {
|
||||
caughtEvent = event;
|
||||
};
|
||||
other.addEventListener.call(null, "global-receiver", eventListener);
|
||||
const dispatchedEvent = new other.Event("global-receiver");
|
||||
const extractedDispatch = other.dispatchEvent;
|
||||
const dispatchResult = extractedDispatch(dispatchedEvent);
|
||||
const focusedChild = document.activeElement === frame;
|
||||
frame.remove();
|
||||
let detachedCleanup = true;
|
||||
try {
|
||||
other.removeEventListener("global-receiver", eventListener);
|
||||
} catch (error) {
|
||||
detachedCleanup = false;
|
||||
}
|
||||
|
||||
return [
|
||||
invalidGetters,
|
||||
invalidSetters,
|
||||
invalidOperations,
|
||||
nullGetters,
|
||||
nullSetters,
|
||||
other.self === newSelf,
|
||||
other.name === "newName",
|
||||
statusDescriptor.get.call(null) === "ready",
|
||||
focusedChild,
|
||||
dispatchResult,
|
||||
caughtEvent === dispatchedEvent,
|
||||
detachedCleanup
|
||||
].join("|");
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("cross-realm Window receiver semantics should evaluate");
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
std::iter::repeat_n("true", 12)
|
||||
.collect::<Vec<_>>()
|
||||
.join("|")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn child_document_body_with_scope_exposes_insert_before() {
|
||||
let mut vm = new_storage_test_vm("https://child-window-with-body.test/");
|
||||
|
||||
@@ -416,6 +416,9 @@ pub(super) fn event_target_remove_event_listener_callback<'s>(
|
||||
event_target_handle_from_this(scope, &args, host_ptr, host)
|
||||
};
|
||||
let Some(target) = target else {
|
||||
if crate::context_bootstrap::is_window_receiver(scope, args.this()) {
|
||||
return;
|
||||
}
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
};
|
||||
@@ -746,6 +749,10 @@ pub(super) fn window_clear_timer_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !crate::context_bootstrap::is_window_receiver(scope, args.this()) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let id_val = args.get(0);
|
||||
let id = id_val.number_value(scope).unwrap_or(0.0) as u32;
|
||||
cancel_window_timer_for_receiver(scope, args.this(), id);
|
||||
|
||||
Reference in New Issue
Block a user