mirror of
https://github.com/lexmount/moli.git
synced 2026-10-05 08:00:56 +00:00
fix(events): suppress reentrant rejectionhandled events
Extract an independently mergeable topic from wpt-misc-fix ata70a96f9d1. Source commits: -3fd12f80b7Validation: - cargo fmt --all - cargo clippy --workspace --all-targets --all-features -- -D warnings - cargo nextest run --no-fail-fast
This commit is contained in:
@@ -121,7 +121,6 @@ pub(super) fn flush_pending_promise_rejections(scope: &mut v8::PinScope<'_, '_>)
|
||||
.reason
|
||||
.as_ref()
|
||||
.map(|reason| v8::Local::new(scope, reason));
|
||||
remember_reported_promise_rejection(&state.reported, rejection.clone());
|
||||
let outcome = dispatch_window_promise_rejection_event(
|
||||
scope,
|
||||
host_ptr,
|
||||
@@ -130,6 +129,13 @@ pub(super) fn flush_pending_promise_rejections(scope: &mut v8::PinScope<'_, '_>)
|
||||
promise,
|
||||
reason,
|
||||
);
|
||||
// A handler added by the `unhandledrejection` listener must not
|
||||
// synchronously turn this notification into `rejectionhandled`.
|
||||
// Only promises that remain unhandled after dispatch belong in
|
||||
// the outstanding reported-rejection set.
|
||||
if !promise.has_handler() {
|
||||
remember_reported_promise_rejection(&state.reported, rejection.clone());
|
||||
}
|
||||
if matches!(outcome, Ok(true)) {
|
||||
log_unhandled_promise_rejection(scope, reason);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,40 @@ Promise.reject("main-owned");
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handler_added_during_unhandled_rejection_does_not_dispatch_rejectionhandled() {
|
||||
let mut vm = new_storage_test_vm("https://handled-during-notification.test/");
|
||||
|
||||
vm.eval(
|
||||
r#"
|
||||
globalThis.__handledDuringNotificationEvents = [];
|
||||
const reason = new Error("handled-during-notification");
|
||||
const rejected = Promise.reject(reason);
|
||||
onunhandledrejection = event => {
|
||||
__handledDuringNotificationEvents.push(event.type);
|
||||
event.preventDefault();
|
||||
rejected.catch(value => {
|
||||
__handledDuringNotificationEvents.push(value === reason ? "handler" : "wrong-reason");
|
||||
});
|
||||
};
|
||||
onrejectionhandled = event => {
|
||||
__handledDuringNotificationEvents.push(event.type);
|
||||
};
|
||||
"#,
|
||||
)
|
||||
.expect("rejection handled during notification setup should evaluate");
|
||||
vm.eval("0")
|
||||
.expect("unhandled rejection notification checkpoint should evaluate");
|
||||
vm.eval("0")
|
||||
.expect("rejection handler reaction checkpoint should evaluate");
|
||||
|
||||
assert_eq!(
|
||||
vm.eval("JSON.stringify(__handledDuringNotificationEvents)")
|
||||
.expect("rejection handled during notification result should evaluate"),
|
||||
r#"["unhandledrejection","handler"]"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn universal_isolated_world_rejection_uses_its_registry_backed_realm() {
|
||||
let mut vm = new_storage_test_vm("https://isolated-promise-rejection.test/");
|
||||
|
||||
@@ -500,10 +500,6 @@ fn flush_pending_worker_promise_rejections(scope: &mut v8::PinScope<'_, '_>) {
|
||||
.reason
|
||||
.as_ref()
|
||||
.map(|reason| v8::Local::new(scope, reason));
|
||||
remember_reported_worker_promise_rejection(
|
||||
&reported_unhandled_rejections,
|
||||
rejection.clone(),
|
||||
);
|
||||
let allows_default = dispatch_worker_promise_rejection_event(
|
||||
scope,
|
||||
"unhandledrejection",
|
||||
@@ -512,6 +508,14 @@ fn flush_pending_worker_promise_rejections(scope: &mut v8::PinScope<'_, '_>) {
|
||||
&parent_tx,
|
||||
&script_url,
|
||||
);
|
||||
// Do not expose a `rejectionhandled` transition when the handler was
|
||||
// attached by the `unhandledrejection` listener itself.
|
||||
if !promise.has_handler() {
|
||||
remember_reported_worker_promise_rejection(
|
||||
&reported_unhandled_rejections,
|
||||
rejection.clone(),
|
||||
);
|
||||
}
|
||||
if allows_default {
|
||||
log_unhandled_promise_rejection(scope, reason);
|
||||
}
|
||||
@@ -582,14 +586,13 @@ unsafe extern "C" fn worker_promise_reject_callback(message: v8::PromiseRejectMe
|
||||
})
|
||||
.map(|index| reported.swap_remove(index))
|
||||
};
|
||||
let reason = reported
|
||||
let Some(rejection) = reported else {
|
||||
return;
|
||||
};
|
||||
let reason = rejection
|
||||
.reason
|
||||
.as_ref()
|
||||
.and_then(|rejection| {
|
||||
rejection
|
||||
.reason
|
||||
.as_ref()
|
||||
.map(|reason| v8::Local::new(scope, reason))
|
||||
})
|
||||
.map(|reason| v8::Local::new(scope, reason))
|
||||
.or_else(|| message.get_value());
|
||||
let _ = dispatch_worker_promise_rejection_event(
|
||||
scope,
|
||||
|
||||
@@ -8661,6 +8661,38 @@ async fn worker_global_rejectionhandled_event_dispatches_for_late_handler() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn worker_handler_added_during_unhandled_rejection_suppresses_rejectionhandled() {
|
||||
ensure_v8();
|
||||
let mut handle = spawn_worker(
|
||||
r#"
|
||||
const events = [];
|
||||
const rejected = Promise.reject("handled-during-worker-notification");
|
||||
onunhandledrejection = event => {
|
||||
event.preventDefault();
|
||||
events.push(event.type);
|
||||
rejected.catch(reason => {
|
||||
events.push(reason === "handled-during-worker-notification"
|
||||
? "handler"
|
||||
: "wrong-reason");
|
||||
});
|
||||
setTimeout(() => {
|
||||
postMessage(events);
|
||||
close();
|
||||
}, 0);
|
||||
};
|
||||
onrejectionhandled = event => events.push(event.type);
|
||||
"#
|
||||
.into(),
|
||||
"test://worker_handled_during_unhandledrejection".into(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
recv_post_json(&mut handle).await,
|
||||
r#"["unhandledrejection","handler"]"#
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn worker_unhandledrejection_notification_allows_one_message_port_turn() {
|
||||
ensure_v8();
|
||||
|
||||
Reference in New Issue
Block a user