mirror of
https://github.com/lexmount/moli.git
synced 2026-10-07 16:01:01 +00:00
fix(events): implement beforeunload return semantics
This commit is contained in:
@@ -259,7 +259,7 @@ pub(crate) use self::dom_rect::build_dom_rect_object;
|
||||
pub(crate) use self::dom_rect_list::{build_dom_rect_list_object};
|
||||
pub(crate) use self::events::{
|
||||
EVENT_DISPATCHING_SLOT, EVENT_PASSIVE_SLOT, EVENT_STOP_IMMEDIATE_PROPAGATION_SLOT,
|
||||
EVENT_STOP_PROPAGATION_SLOT, EventHandlerType, apply_event_handler_return_value, clear_event_composed_path, event_attribute, event_backing,
|
||||
EVENT_STOP_PROPAGATION_SLOT, EventHandlerType, apply_before_unload_event_handler_return_value, apply_event_handler_return_value, clear_event_composed_path, event_attribute, event_backing,
|
||||
event_bool_attribute, event_initialized, event_internal_bool_flag, event_is_dispatching,
|
||||
event_is_error_event, event_is_mouse_event, event_private_value, initialize_event_object,
|
||||
initialize_event_wrapper, mark_event_trusted, new_event_state, new_event_wrapper,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::events::{new_uninitialized_text_event, set_event_initialized};
|
||||
use super::events::{
|
||||
new_uninitialized_before_unload_event, new_uninitialized_text_event, set_event_initialized,
|
||||
};
|
||||
use super::*;
|
||||
use crate::webidl;
|
||||
use std::str::FromStr;
|
||||
@@ -42,7 +44,7 @@ enum DocumentCreateEventKind {
|
||||
impl DocumentCreateEventKind {
|
||||
fn constructor_name(self) -> &'static str {
|
||||
match self {
|
||||
DocumentCreateEventKind::BeforeUnloadEvent => "Event",
|
||||
DocumentCreateEventKind::BeforeUnloadEvent => "BeforeUnloadEvent",
|
||||
DocumentCreateEventKind::Event => "Event",
|
||||
DocumentCreateEventKind::CustomEvent => "CustomEvent",
|
||||
DocumentCreateEventKind::DeviceMotionEvent => "DeviceMotionEvent",
|
||||
@@ -73,8 +75,12 @@ fn new_uninitialized_document_event<'s>(
|
||||
let name = kind.constructor_name();
|
||||
let constructor =
|
||||
super::exposed_interfaces::ensure_intrinsic_interface_constructor(scope, name).ok()?;
|
||||
if kind == DocumentCreateEventKind::TextEvent {
|
||||
return new_uninitialized_text_event(scope);
|
||||
match kind {
|
||||
DocumentCreateEventKind::BeforeUnloadEvent => {
|
||||
return new_uninitialized_before_unload_event(scope);
|
||||
}
|
||||
DocumentCreateEventKind::TextEvent => return new_uninitialized_text_event(scope),
|
||||
_ => {}
|
||||
}
|
||||
let empty_type = v8str(scope, "");
|
||||
let event = constructor.new_instance(scope, &[empty_type.into()])?;
|
||||
|
||||
@@ -7,16 +7,18 @@ use super::{
|
||||
ui_event_init_callback,
|
||||
},
|
||||
events::{
|
||||
close_event_code_getter_function, close_event_reason_getter_function,
|
||||
close_event_was_clean_getter_function, event_bubbles_getter_function,
|
||||
event_cancel_bubble_getter_function, event_cancel_bubble_setter_function,
|
||||
event_cancelable_getter_function, event_composed_getter_function,
|
||||
event_composed_path_callback, event_current_target_getter_function,
|
||||
event_default_prevented_getter_function, event_event_phase_getter_function,
|
||||
event_prevent_default_callback, event_return_value_getter_function,
|
||||
event_return_value_setter_function, event_src_element_getter_function,
|
||||
event_stop_immediate_propagation_callback, event_stop_propagation_callback,
|
||||
event_target_getter_function, event_time_stamp_getter_function, event_type_getter_function,
|
||||
before_unload_event_return_value_getter_function,
|
||||
before_unload_event_return_value_setter_function, close_event_code_getter_function,
|
||||
close_event_reason_getter_function, close_event_was_clean_getter_function,
|
||||
event_bubbles_getter_function, event_cancel_bubble_getter_function,
|
||||
event_cancel_bubble_setter_function, event_cancelable_getter_function,
|
||||
event_composed_getter_function, event_composed_path_callback,
|
||||
event_current_target_getter_function, event_default_prevented_getter_function,
|
||||
event_event_phase_getter_function, event_prevent_default_callback,
|
||||
event_return_value_getter_function, event_return_value_setter_function,
|
||||
event_src_element_getter_function, event_stop_immediate_propagation_callback,
|
||||
event_stop_propagation_callback, event_target_getter_function,
|
||||
event_time_stamp_getter_function, event_type_getter_function,
|
||||
focus_event_related_target_getter_function, form_data_event_form_data_getter_function,
|
||||
mouse_event_related_target_getter_function, pointer_event_get_predicted_events_callback,
|
||||
submit_event_submitter_getter_function, track_event_track_getter_function,
|
||||
@@ -130,6 +132,17 @@ struct EventBaseTemplateMethodsDeclaration {
|
||||
init_event: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(interface = web_api_interfaces::BeforeUnloadEvent, enumerable)]
|
||||
struct BeforeUnloadEventTemplateAccessorsDeclaration {
|
||||
#[webapi(
|
||||
accessor_property = "returnValue",
|
||||
getter = before_unload_event_return_value_getter_function,
|
||||
setter = before_unload_event_return_value_setter_function
|
||||
)]
|
||||
return_value: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(interface = web_api_interfaces::CloseEvent, enumerable, receiver)]
|
||||
struct CloseEventTemplateAccessorsDeclaration {
|
||||
@@ -339,6 +352,12 @@ pub(super) fn install_event_template_bindings<'s>(
|
||||
super::events::install_device_event_template_bindings(scope, template, spec.interface.name());
|
||||
|
||||
match spec.interface.name() {
|
||||
"BeforeUnloadEvent" => {
|
||||
let proto = template.prototype_template(scope);
|
||||
BeforeUnloadEventTemplateAccessorsDeclaration::initialize_prototype_template(
|
||||
scope, proto,
|
||||
);
|
||||
}
|
||||
"UIEvent" => {
|
||||
let proto = template.prototype_template(scope);
|
||||
UiEventTemplateMethodsDeclaration::initialize_prototype_template(scope, proto);
|
||||
|
||||
@@ -29,6 +29,7 @@ const SUBMIT_EVENT_SUBMITTER_SLOT: &str = "__moliSubmitEventSubmitter";
|
||||
const FORM_DATA_EVENT_FORM_DATA_SLOT: &str = "__moliFormDataEventFormData";
|
||||
const TRACK_EVENT_TRACK_SLOT: &str = "__moliTrackEventTrack";
|
||||
const EVENT_SUBCLASS_KIND_SLOT: &str = "__moliEventSubclassKind";
|
||||
const BEFORE_UNLOAD_EVENT_RETURN_VALUE_SLOT: &str = "__moliBeforeUnloadEventReturnValue";
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(plain)]
|
||||
struct PageTransitionEventInitDeclaration {
|
||||
@@ -82,6 +83,102 @@ pub(crate) fn construct_original_event<'s>(
|
||||
Some(event)
|
||||
}
|
||||
|
||||
fn new_before_unload_event<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event_type: &str,
|
||||
cancelable: bool,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let prototype = global_constructor_prototype(scope, "BeforeUnloadEvent")?;
|
||||
let event = v8::Object::new(scope);
|
||||
if event.set_prototype(scope, prototype.into()) != Some(true) {
|
||||
return None;
|
||||
}
|
||||
base::initialize_event_object(scope, event, event_type, false, cancelable);
|
||||
set_private_value(
|
||||
scope,
|
||||
event,
|
||||
BEFORE_UNLOAD_EVENT_RETURN_VALUE_SLOT,
|
||||
v8str(scope, "").into(),
|
||||
);
|
||||
Some(event)
|
||||
}
|
||||
|
||||
pub(crate) fn construct_original_before_unload_event<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
new_before_unload_event(scope, "beforeunload", true)
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn new_uninitialized_before_unload_event<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let event = new_before_unload_event(scope, "", false)?;
|
||||
base::set_event_initialized(scope, event, false);
|
||||
Some(event)
|
||||
}
|
||||
|
||||
fn before_unload_event_return_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::String>> {
|
||||
get_private_value(scope, event, BEFORE_UNLOAD_EVENT_RETURN_VALUE_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::String>::try_from(value).ok())
|
||||
}
|
||||
|
||||
pub(crate) fn event_is_before_unload_event<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
before_unload_event_return_value(scope, event).is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn set_before_unload_event_return_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event: v8::Local<'s, v8::Object>,
|
||||
value: v8::Local<'s, v8::String>,
|
||||
) {
|
||||
set_private_value(
|
||||
scope,
|
||||
event,
|
||||
BEFORE_UNLOAD_EVENT_RETURN_VALUE_SLOT,
|
||||
value.into(),
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn before_unload_event_return_value_is_empty<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
before_unload_event_return_value(scope, event).is_some_and(|value| value.length() == 0)
|
||||
}
|
||||
|
||||
pub(super) fn before_unload_event_return_value_getter_function<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(value) = before_unload_event_return_value(scope, args.this()) else {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
};
|
||||
rv.set(value.into());
|
||||
}
|
||||
|
||||
pub(super) fn before_unload_event_return_value_setter_function<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !event_is_before_unload_event(scope, args.this()) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let Some(value) = args.get(0).to_string(scope) else {
|
||||
return;
|
||||
};
|
||||
set_before_unload_event_return_value(scope, args.this(), value);
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn new_uninitialized_text_event<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
@@ -367,7 +464,10 @@ pub(super) use base::{
|
||||
};
|
||||
pub(in crate::context_bootstrap) use init::parse_event_init;
|
||||
pub(super) use kind::EventSubclassKind;
|
||||
pub(crate) use methods::{EventHandlerType, apply_event_handler_return_value};
|
||||
pub(crate) use methods::{
|
||||
EventHandlerType, apply_before_unload_event_handler_return_value,
|
||||
apply_event_handler_return_value,
|
||||
};
|
||||
pub(super) use methods::{
|
||||
event_cancel_bubble_getter_function, event_cancel_bubble_setter_function,
|
||||
event_composed_path_callback, event_prevent_default_callback,
|
||||
|
||||
@@ -43,6 +43,20 @@ pub(crate) fn apply_event_handler_return_value<'s>(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn apply_before_unload_event_handler_return_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event: v8::Local<'s, v8::Object>,
|
||||
returned: v8::Local<'s, v8::String>,
|
||||
) {
|
||||
if !event_is_before_unload_event(scope, event) {
|
||||
return;
|
||||
}
|
||||
set_event_default_prevented(scope, event);
|
||||
if before_unload_event_return_value_is_empty(scope, event) {
|
||||
set_before_unload_event_return_value(scope, event, returned);
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn event_prevent_default_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use super::events::{construct_original_event, run_navigate_event_precommit_handlers};
|
||||
use super::events::{
|
||||
construct_original_before_unload_event, construct_original_event,
|
||||
run_navigate_event_precommit_handlers,
|
||||
};
|
||||
use super::location_history_storage::{
|
||||
NAVIGATION_ENTRY_EVENT_LISTENERS_SLOT, NAVIGATION_EVENT_LISTENERS_SLOT,
|
||||
};
|
||||
@@ -630,7 +633,7 @@ pub(crate) fn dispatch_beforeunload_for_runtime_owner<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
let Some(event) = construct_original_event(scope, "beforeunload") else {
|
||||
let Some(event) = construct_original_before_unload_event(scope) else {
|
||||
return;
|
||||
};
|
||||
dispatch_unload_lifecycle_event_for_runtime_owner(scope, owner, "beforeunload", event);
|
||||
|
||||
@@ -32,7 +32,8 @@ pub(super) use self::events::{
|
||||
DispatchStatus, HostEventTargetRegistry, PublicEventDispatchResult, create_host_event,
|
||||
dispatch_host_event, dispatch_public_event, dispatch_public_event_with_original_target,
|
||||
event_dispatch_status, event_handler_content_attribute_owner, event_target_value,
|
||||
host_event_defaults, invoke_prepared_event_callback,
|
||||
host_event_defaults, invoke_prepared_before_unload_event_handler,
|
||||
invoke_prepared_event_callback,
|
||||
report_event_callback_exception, report_event_listener_exception,
|
||||
};
|
||||
pub(crate) use self::events::{EventListenerInspectorSnapshot, EventListenerRegistration};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use super::*;
|
||||
use crate::{
|
||||
context_bootstrap::{
|
||||
CHILD_BROWSING_CONTEXT_HANDLE_SLOT, EventHandlerType, apply_event_handler_return_value,
|
||||
CHILD_BROWSING_CONTEXT_HANDLE_SLOT, EventHandlerType,
|
||||
apply_before_unload_event_handler_return_value, apply_event_handler_return_value,
|
||||
clear_event_composed_path, event_is_error_event, mark_event_trusted,
|
||||
set_event_composed_path,
|
||||
},
|
||||
@@ -298,6 +299,98 @@ fn invoke_prepared_event_callback_with_receiver<'s>(
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn invoke_prepared_before_unload_event_handler<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
host_ptr: *mut JsContextHost,
|
||||
target: EventTargetHandle,
|
||||
invocation_target_in_shadow_tree: bool,
|
||||
event_type: &str,
|
||||
callback_name: &str,
|
||||
callback: crate::native_bridge::PreparedEventCallback,
|
||||
event: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
let receiver = event_target_receiver(scope, host_ptr, target, event);
|
||||
let _dom_debugger_pause = unsafe { &*host_ptr }
|
||||
.schedule_dom_debugger_event_listener_pause_for_target(event_type, target);
|
||||
let relevant_identity = callback.relevant_identity();
|
||||
let arguments = [event.into()];
|
||||
let invocation = CallbackInvocation::new(
|
||||
callback.callback(scope),
|
||||
receiver,
|
||||
callback.relevant_context(scope),
|
||||
callback.incumbent_context(scope),
|
||||
callback.is_callable(),
|
||||
"handleEvent",
|
||||
&arguments,
|
||||
(!invocation_target_in_shadow_tree).then_some(event),
|
||||
)
|
||||
.with_execution_context_currentness(host_ptr, relevant_identity);
|
||||
CallbackInvoker::invoke_event_and_then(
|
||||
scope,
|
||||
"event listener",
|
||||
"host event listener threw",
|
||||
crate::exception_reporting::CallbackExceptionLogLevel::Debug,
|
||||
callback_name,
|
||||
invocation,
|
||||
|scope, outcome| match outcome {
|
||||
CallbackInvocationOutcome::Returned(value) => {
|
||||
let value = v8::Local::new(scope, value);
|
||||
if value.is_null_or_undefined() {
|
||||
return;
|
||||
}
|
||||
let conversion_report = {
|
||||
let try_catch = std::pin::pin!(v8::TryCatch::new(scope));
|
||||
let mut conversion_scope = try_catch.init();
|
||||
match value.to_string(&conversion_scope) {
|
||||
Some(value) => {
|
||||
apply_before_unload_event_handler_return_value(
|
||||
&mut conversion_scope,
|
||||
event,
|
||||
value,
|
||||
);
|
||||
None
|
||||
}
|
||||
None if conversion_scope.has_caught() => {
|
||||
let exception = conversion_scope.exception();
|
||||
let message = conversion_scope.message();
|
||||
let stack_trace = conversion_scope.stack_trace();
|
||||
Some(build_event_handler_exception_report(
|
||||
&mut conversion_scope,
|
||||
exception,
|
||||
message,
|
||||
stack_trace,
|
||||
))
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
};
|
||||
if let Some(report) = conversion_report {
|
||||
report_event_callback_exception(
|
||||
scope,
|
||||
host_ptr,
|
||||
event_type,
|
||||
relevant_identity,
|
||||
None,
|
||||
&report,
|
||||
);
|
||||
}
|
||||
}
|
||||
CallbackInvocationOutcome::Threw(report) => {
|
||||
report_event_callback_exception(
|
||||
scope,
|
||||
host_ptr,
|
||||
event_type,
|
||||
relevant_identity,
|
||||
None,
|
||||
&report,
|
||||
);
|
||||
}
|
||||
CallbackInvocationOutcome::Retired => {}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn invoke_event_handler_property<'s>(
|
||||
registry: &mut HostEventTargetRegistry,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
@@ -445,6 +538,30 @@ fn invoke_registered_event_handler<'s>(
|
||||
return;
|
||||
}
|
||||
|
||||
if event_type == "beforeunload" {
|
||||
invoke_prepared_before_unload_event_handler(
|
||||
scope,
|
||||
host_ptr,
|
||||
target,
|
||||
invocation_target_in_shadow_tree,
|
||||
event_type,
|
||||
&handler_name,
|
||||
prepared,
|
||||
event,
|
||||
);
|
||||
if let Some(timing_started) = timing_started {
|
||||
tracing::info!(
|
||||
target: "moli_cdp_nav_timing",
|
||||
stage = "event_handler_property_invoked",
|
||||
event_type,
|
||||
handler_name,
|
||||
?target,
|
||||
elapsed_ms = timing_started.elapsed().as_millis(),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let returned = invoke_prepared_event_callback(
|
||||
scope,
|
||||
host_ptr,
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
frame_owner_model::{FrameDocumentTaskOwner, LocalWindowId},
|
||||
host::{
|
||||
ChildWindowEventTarget, DispatchStatus, create_host_event, event_dispatch_status,
|
||||
invoke_prepared_event_callback,
|
||||
invoke_prepared_before_unload_event_handler, invoke_prepared_event_callback,
|
||||
},
|
||||
native_bridge::{
|
||||
ACTIVE_CHILD_WINDOW_HANDLE_SLOT, EventCallbackId, PreparedEventCallback,
|
||||
@@ -626,6 +626,21 @@ impl JsContextHost {
|
||||
if !self.child_window_event_target_is_current(ready.target) {
|
||||
return (false, None);
|
||||
}
|
||||
if ready.registration_kind == ChildWindowEventRegistrationKind::EventHandlerProperty
|
||||
&& event_type == "beforeunload"
|
||||
{
|
||||
invoke_prepared_before_unload_event_handler(
|
||||
scope,
|
||||
self as *mut JsContextHost,
|
||||
EventTargetHandle::ChildWindow(ready.target),
|
||||
false,
|
||||
event_type,
|
||||
&format!("child window {event_type} listener"),
|
||||
ready.callback,
|
||||
event,
|
||||
);
|
||||
return (true, None);
|
||||
}
|
||||
let arguments = child_window_event_callback_arguments(
|
||||
scope,
|
||||
ready.registration_kind,
|
||||
|
||||
+71
@@ -1676,3 +1676,74 @@ fn selection_endpoints_follow_associated_live_range_mutations() {
|
||||
""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn before_unload_event_uses_its_string_return_value_interface() {
|
||||
let mut vm = new_storage_test_vm("https://before-unload-event-interface.test/");
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const errorName = callback => {
|
||||
try {
|
||||
callback();
|
||||
return "none";
|
||||
} catch (error) {
|
||||
return error.name;
|
||||
}
|
||||
};
|
||||
const descriptor = Object.getOwnPropertyDescriptor(
|
||||
BeforeUnloadEvent.prototype,
|
||||
"returnValue"
|
||||
);
|
||||
const event = document.createEvent("BeforeUnloadEvent");
|
||||
const initial = [
|
||||
event instanceof BeforeUnloadEvent,
|
||||
event instanceof Event,
|
||||
Object.hasOwn(event, "returnValue"),
|
||||
event.returnValue
|
||||
];
|
||||
event.initEvent("beforeunload", false, true);
|
||||
event.returnValue = null;
|
||||
const nullValue = event.returnValue;
|
||||
event.returnValue = undefined;
|
||||
const undefinedValue = event.returnValue;
|
||||
event.returnValue = { toString() { return "object value"; } };
|
||||
const objectValue = event.returnValue;
|
||||
const symbolError = errorName(() => {
|
||||
event.returnValue = Symbol("return value");
|
||||
});
|
||||
|
||||
return JSON.stringify({
|
||||
constructor: [
|
||||
typeof BeforeUnloadEvent,
|
||||
Object.getPrototypeOf(BeforeUnloadEvent.prototype) === Event.prototype,
|
||||
errorName(() => new BeforeUnloadEvent())
|
||||
],
|
||||
initial,
|
||||
initialized: [event.type, event.cancelable],
|
||||
values: [nullValue, undefinedValue, objectValue, symbolError, event.returnValue],
|
||||
metadata: [
|
||||
descriptor.get.name,
|
||||
descriptor.get.length,
|
||||
descriptor.set.name,
|
||||
descriptor.set.length,
|
||||
descriptor.enumerable,
|
||||
descriptor.configurable
|
||||
],
|
||||
receiverErrors: [
|
||||
errorName(() => descriptor.get.call(new Event("plain"))),
|
||||
errorName(() => descriptor.set.call({}, "value"))
|
||||
]
|
||||
});
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("BeforeUnloadEvent returnValue interface probe should evaluate");
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"constructor":["function",true,"TypeError"],"initial":[true,true,false,""],"initialized":["beforeunload",true],"values":["null","undefined","object value","TypeError","object value"],"metadata":["get returnValue",0,"set returnValue",1,true,true],"receiverErrors":["TypeError","TypeError"]}"#
|
||||
);
|
||||
}
|
||||
|
||||
@@ -175,6 +175,59 @@ fn cross_document_unload_lifecycle_orders_pagehide_before_unload_without_timer()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn before_unload_handler_coerces_its_result_while_window_event_is_current() {
|
||||
let mut vm = new_storage_test_vm("https://example.com/base");
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r##"
|
||||
(() => {
|
||||
let customCurrent = false;
|
||||
onbeforeunload = event => ({
|
||||
toString() {
|
||||
customCurrent = window.event === event;
|
||||
return "custom";
|
||||
}
|
||||
});
|
||||
const custom = new CustomEvent("beforeunload", { cancelable: true });
|
||||
const customDispatchResult = dispatchEvent(custom);
|
||||
|
||||
let realEvent;
|
||||
const realSteps = [];
|
||||
onbeforeunload = event => {
|
||||
realEvent = event;
|
||||
realSteps.push([
|
||||
event instanceof BeforeUnloadEvent,
|
||||
event.cancelable,
|
||||
event.returnValue,
|
||||
window.event === event
|
||||
]);
|
||||
return {
|
||||
toString() {
|
||||
realSteps.push(["coerce", window.event === event]);
|
||||
return "leave";
|
||||
}
|
||||
};
|
||||
};
|
||||
navigation.navigate("/next-document");
|
||||
|
||||
return JSON.stringify({
|
||||
custom: [customCurrent, custom.defaultPrevented, customDispatchResult],
|
||||
realSteps,
|
||||
real: [realEvent.defaultPrevented, realEvent.returnValue]
|
||||
});
|
||||
})()
|
||||
"##,
|
||||
)
|
||||
.expect("beforeunload handler return processing should evaluate");
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"custom":[true,false,true],"realSteps":[[true,true,"",true],["coerce",true]],"real":[true,"leave"]}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn navigate_event_intercept_option_stringification_preserves_thrown_exception() {
|
||||
let mut vm = new_storage_test_vm("https://example.com/base");
|
||||
|
||||
Reference in New Issue
Block a user