fix(events): align lifecycle event initialization

This commit is contained in:
ldm0
2026-09-22 21:44:07 +08:00
parent 3cc63f8f02
commit 7b2a7a4cf8
5 changed files with 45 additions and 27 deletions
@@ -19,6 +19,10 @@ const BEFORE_UNLOAD_EVENT_RETURN_VALUE_SLOT: &str = "__moliBeforeUnloadEventRetu
#[derive(WebApiObject)]
#[webapi(plain)]
struct PageTransitionEventInitDeclaration {
#[webapi(data_property, enumerable)]
bubbles: bool,
#[webapi(data_property, enumerable)]
cancelable: bool,
#[webapi(data_property, enumerable)]
persisted: bool,
}
@@ -56,11 +60,13 @@ pub(crate) fn construct_original_event<'s>(
let event_ctor =
super::exposed_interfaces::ensure_intrinsic_interface_constructor(scope, "Event").ok()?;
let event_type = v8_string(scope, event_type)?;
{
let event = {
let try_catch = std::pin::pin!(v8::TryCatch::new(scope));
let scope = try_catch.init();
event_ctor.new_instance(&scope, &[event_type.into()])
}
}?;
mark_event_trusted(scope, event);
Some(event)
}
fn new_before_unload_event<'s>(
@@ -86,7 +92,9 @@ fn new_before_unload_event<'s>(
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)
let event = new_before_unload_event(scope, "beforeunload", true)?;
mark_event_trusted(scope, event);
Some(event)
}
pub(in crate::context_bootstrap) fn new_uninitialized_before_unload_event<'s>(
@@ -192,14 +200,16 @@ pub(crate) fn construct_original_page_transition_event<'s>(
)
.ok()?;
let event_type = v8_string(scope, event_type)?;
let init = PageTransitionEventInitDeclaration::new(persisted)
let init = PageTransitionEventInitDeclaration::new(true, true, persisted)
.bind(scope)
.expect("PageTransitionEvent init declaration should bind");
{
let event = {
let try_catch = std::pin::pin!(v8::TryCatch::new(scope));
let scope = try_catch.init();
event_ctor.new_instance(&scope, &[event_type.into(), init.into()])
}
}?;
mark_event_trusted(scope, event);
Some(event)
}
pub(crate) fn construct_original_storage_event_utf16<'s>(
@@ -1,6 +1,6 @@
use super::events::{
construct_original_before_unload_event, construct_original_event,
run_navigate_event_precommit_handlers,
construct_original_page_transition_event, run_navigate_event_precommit_handlers,
};
use super::location_history_storage::{
NAVIGATION_ENTRY_EVENT_LISTENERS_SLOT, NAVIGATION_EVENT_LISTENERS_SLOT,
@@ -106,13 +106,6 @@ struct HashChangeEventStateDeclaration {
new_url: String,
}
#[derive(WebApiObject)]
#[webapi(plain)]
struct PageTransitionEventStateDeclaration {
#[webapi(data_property)]
persisted: bool,
}
#[derive(WebApiObject)]
#[webapi(plain)]
struct NavigateEventInitDeclaration<'scope> {
@@ -645,10 +638,9 @@ pub(crate) fn dispatch_pagehide_for_runtime_owner<'s>(
scope: &mut v8::PinScope<'s, '_>,
owner: v8::Local<'s, v8::Object>,
) {
let Some(event) = construct_original_event(scope, "pagehide") else {
let Some(event) = construct_original_page_transition_event(scope, "pagehide", false) else {
return;
};
let _ = PageTransitionEventStateDeclaration::new(false).initialize(scope, event);
dispatch_unload_lifecycle_event_for_runtime_owner(scope, owner, "pagehide", event);
}
@@ -10,7 +10,7 @@ use crate::{
content_security_policy::{
ContentSecurityPolicyViolationEventFields, send_content_security_policy_reports,
},
context_bootstrap::{construct_original_event, mark_event_trusted},
context_bootstrap::construct_original_event,
document_runtime::{
DocumentNavigationEmbeddingContext, DocumentPolicyContainer, DomHandle, EventTargetHandle,
},
@@ -651,7 +651,6 @@ impl JsContextHost {
if object_fallback_required {
self.enter_object_fallback_state(scope, handle);
if let Some(event) = construct_original_event(scope, "error") {
mark_event_trusted(scope, event);
let host_ptr: *mut JsContextHost = self;
let runtime = unsafe { &mut *self.runtime };
let _ = runtime.dispatch_public_event_best_effort(
@@ -2029,9 +2029,16 @@ fn cross_document_unload_lifecycle_orders_pagehide_before_unload_without_timer()
r##"
(() => {
const log = [];
addEventListener("beforeunload", () => log.push("beforeunload"));
addEventListener("pagehide", event => log.push(`pagehide:${event.persisted}`));
addEventListener("unload", () => log.push("unload"));
addEventListener("beforeunload", event => log.push(`beforeunload:${event.isTrusted}`));
addEventListener("pagehide", event => log.push([
"pagehide",
event instanceof PageTransitionEvent,
event.persisted,
event.bubbles,
event.cancelable,
event.isTrusted
].join(":")));
addEventListener("unload", event => log.push(`unload:${event.isTrusted}`));
navigation.navigate("/next-document");
return log.join("|");
})()
@@ -2039,7 +2046,10 @@ fn cross_document_unload_lifecycle_orders_pagehide_before_unload_without_timer()
)
.expect("cross-document unload lifecycle should evaluate");
assert_eq!(lifecycle, "beforeunload|pagehide:false|unload");
assert_eq!(
lifecycle,
"beforeunload:true|pagehide:true:false:true:true:true|unload:true"
);
assert!(
!vm.has_ready_timeout(),
"pagehide is part of the unload step and must not create an independent timer task"
@@ -13780,7 +13780,14 @@ fn window_pageshow_uses_original_page_transition_event() {
throw new Error("page replacement should not be invoked");
};
addEventListener("pageshow", event => {
__pageshowShape = `${event.type}:${event.persisted === false}:${'persisted' in event}`;
__pageshowShape = [
event.type,
event.persisted === false,
'persisted' in event,
event.bubbles,
event.cancelable,
event.isTrusted
].join(':');
});
return "ready";
})()
@@ -13793,7 +13800,7 @@ fn window_pageshow_uses_original_page_transition_event() {
let shape = vm
.eval("globalThis.__pageshowShape")
.expect("pageshow shape should evaluate");
assert_eq!(shape, "pageshow:true:true");
assert_eq!(shape, "pageshow:true:true:true:true:true");
}
#[test]
@@ -13808,12 +13815,12 @@ fn window_load_uses_original_event_after_global_constructors_are_deleted() {
globalThis.__windowLifecycleEvents = [];
addEventListener('load', event => {
__windowLifecycleEvents.push(
`load:${event instanceof OriginalEvent}:${event.target === document}:${event.currentTarget === window}`
`load:${event instanceof OriginalEvent}:${event.target === document}:${event.currentTarget === window}:${event.isTrusted}:${event.bubbles}:${event.cancelable}`
);
});
addEventListener('pageshow', event => {
__windowLifecycleEvents.push(
`pageshow:${event instanceof OriginalPageTransitionEvent}:${event.persisted}`
`pageshow:${event instanceof OriginalPageTransitionEvent}:${event.persisted}:${event.isTrusted}:${event.bubbles}:${event.cancelable}`
);
});
delete globalThis.Event;
@@ -13829,7 +13836,7 @@ fn window_load_uses_original_event_after_global_constructors_are_deleted() {
assert_eq!(
vm.eval("__windowLifecycleEvents.join('|')")
.expect("window lifecycle results should evaluate"),
"load:true:true:true|pageshow:true:false"
"load:true:true:true:true:false:false|pageshow:true:false:true:true:true"
);
}