From 8bbcfb8df5528cb705818bc44f85fcb0ffef083a Mon Sep 17 00:00:00 2001 From: ldm0 Date: Thu, 16 Jul 2026 14:41:48 +0800 Subject: [PATCH] fix(events): apply exact handler return cancellation --- .../wpt-cross-current/failed-cases.txt | 1 - .../wpt-cross-current/passed-cases.txt | 1 + moli-renderer-v8/src/context_bootstrap.rs | 8 +-- .../src/context_bootstrap/events.rs | 1 + .../src/context_bootstrap/events/methods.rs | 41 ++++++++++-- .../events/simple_event_target/dispatch.rs | 23 ++++++- .../context_bootstrap/shared_worker_host.rs | 12 ++-- .../context_bootstrap/worker_host/dispatch.rs | 10 +-- .../context_bootstrap/worker_host/tests.rs | 66 +++++++++++++++++++ moli-renderer-v8/src/host/events.rs | 26 +++----- .../src/runtime/page_vm/tests/fetch_xhr.rs | 12 ++-- .../browser_api/events_selection_storage.rs | 25 +++++++ .../src/worker/thread/dispatch.rs | 16 ++--- .../wpt/ported/worker/worker-error-basic.html | 9 ++- .../worker/worker-window-error-basic.html | 28 +++++--- 15 files changed, 212 insertions(+), 67 deletions(-) diff --git a/moli-benchmark/wpt-cross-current/failed-cases.txt b/moli-benchmark/wpt-cross-current/failed-cases.txt index ca4db55aad..79aba2dd14 100644 --- a/moli-benchmark/wpt-cross-current/failed-cases.txt +++ b/moli-benchmark/wpt-cross-current/failed-cases.txt @@ -3236,7 +3236,6 @@ html/webappapis/scripting/events/event-handler-attributes-frameset-alt.html html/webappapis/scripting/events/event-handler-attributes-frameset-window.html html/webappapis/scripting/events/event-handler-attributes-window-frameset.html html/webappapis/scripting/events/event-handler-processing-algorithm-error/frameset-element-synthetic-errorevent.html -html/webappapis/scripting/events/event-handler-processing-algorithm-error/worker.html html/webappapis/scripting/processing-model-2/compile-error-cross-origin.html html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-detached-iframe.html html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-incumbent.html diff --git a/moli-benchmark/wpt-cross-current/passed-cases.txt b/moli-benchmark/wpt-cross-current/passed-cases.txt index 9d7705e3f6..d21edf0e65 100644 --- a/moli-benchmark/wpt-cross-current/passed-cases.txt +++ b/moli-benchmark/wpt-cross-current/passed-cases.txt @@ -7329,6 +7329,7 @@ html/webappapis/scripting/events/event-handler-processing-algorithm-error/synthe html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-runtime-error.html html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-synthetic-errorevent.html html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-synthetic-event.html +html/webappapis/scripting/events/event-handler-processing-algorithm-error/worker.html html/webappapis/scripting/events/event-handler-processing-algorithm.html html/webappapis/scripting/events/event-handler-sourcetext.html html/webappapis/scripting/events/eventhandler-cancellation.html diff --git a/moli-renderer-v8/src/context_bootstrap.rs b/moli-renderer-v8/src/context_bootstrap.rs index 253f886dd3..6a66d91f51 100644 --- a/moli-renderer-v8/src/context_bootstrap.rs +++ b/moli-renderer-v8/src/context_bootstrap.rs @@ -232,10 +232,10 @@ pub(crate) use self::css_stylesheet_runtime::{ pub(crate) use self::dom_rect::build_dom_rect_object; pub(crate) use self::events::{ EVENT_DISPATCHING_SLOT, EVENT_PASSIVE_SLOT, EVENT_STOP_IMMEDIATE_PROPAGATION_SLOT, - EVENT_STOP_PROPAGATION_SLOT, clear_event_composed_path, event_initialized, - event_internal_bool_flag, event_is_dispatching, event_is_error_event, event_is_mouse_event, - initialize_event_object, mark_event_trusted, set_event_composed_path, set_event_internal_flag, - set_event_trusted, + EVENT_STOP_PROPAGATION_SLOT, EventHandlerType, apply_event_handler_return_value, + clear_event_composed_path, event_initialized, event_internal_bool_flag, event_is_dispatching, + event_is_error_event, event_is_mouse_event, initialize_event_object, mark_event_trusted, + set_event_composed_path, set_event_internal_flag, set_event_trusted, }; pub(crate) use self::file_api::{ DataTransferStringCallbackTask, DataTransferStringCallbackTaskEffect, diff --git a/moli-renderer-v8/src/context_bootstrap/events.rs b/moli-renderer-v8/src/context_bootstrap/events.rs index d63004b239..7ca85ca837 100644 --- a/moli-renderer-v8/src/context_bootstrap/events.rs +++ b/moli-renderer-v8/src/context_bootstrap/events.rs @@ -340,6 +340,7 @@ pub(super) use base::{ set_event_initialized, }; pub(super) use kind::EventSubclassKind; +pub(crate) use methods::{EventHandlerType, 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, diff --git a/moli-renderer-v8/src/context_bootstrap/events/methods.rs b/moli-renderer-v8/src/context_bootstrap/events/methods.rs index f5c35deddf..d477b61e25 100644 --- a/moli-renderer-v8/src/context_bootstrap/events/methods.rs +++ b/moli-renderer-v8/src/context_bootstrap/events/methods.rs @@ -1,5 +1,11 @@ use super::*; +#[derive(Clone, Copy)] +pub(crate) enum EventHandlerType { + EventHandler, + OnErrorEventHandler, +} + pub(super) fn set_event_default_prevented( scope: &mut v8::PinScope<'_, '_>, event: v8::Local<'_, v8::Object>, @@ -9,19 +15,40 @@ pub(super) fn set_event_default_prevented( let _ = event.define_own_property(scope, key.into(), value, Default::default()); } +fn prevent_event_default_if_allowed<'s>( + scope: &mut v8::PinScope<'s, '_>, + event: v8::Local<'s, v8::Object>, +) { + if !object_bool_property(scope, event, "cancelable").unwrap_or(false) + || event_internal_bool_flag(scope, event, EVENT_PASSIVE_SLOT) + { + return; + } + set_event_default_prevented(scope, event); +} + +pub(crate) fn apply_event_handler_return_value<'s>( + scope: &mut v8::PinScope<'s, '_>, + event: v8::Local<'s, v8::Object>, + returned: v8::Local<'s, v8::Value>, + handler_type: EventHandlerType, +) { + let cancels = match handler_type { + EventHandlerType::EventHandler => returned.is_boolean() && !returned.boolean_value(scope), + EventHandlerType::OnErrorEventHandler => returned.is_true(), + }; + if cancels { + prevent_event_default_if_allowed(scope, event); + } +} + pub(in crate::context_bootstrap) fn event_prevent_default_callback<'s>( scope: &mut v8::PinScope<'s, '_>, args: v8::FunctionCallbackArguments<'s>, _rv: v8::ReturnValue<'_, v8::Value>, ) { let event = args.this(); - if !object_bool_property(scope, event, "cancelable").unwrap_or(false) { - return; - } - if event_internal_bool_flag(scope, event, EVENT_PASSIVE_SLOT) { - return; - } - set_event_default_prevented(scope, event); + prevent_event_default_if_allowed(scope, event); } pub(in crate::context_bootstrap) fn event_return_value_getter_function<'s>( diff --git a/moli-renderer-v8/src/context_bootstrap/media_queries/events/simple_event_target/dispatch.rs b/moli-renderer-v8/src/context_bootstrap/media_queries/events/simple_event_target/dispatch.rs index 3f93bfac39..c8be775661 100644 --- a/moli-renderer-v8/src/context_bootstrap/media_queries/events/simple_event_target/dispatch.rs +++ b/moli-renderer-v8/src/context_bootstrap/media_queries/events/simple_event_target/dispatch.rs @@ -6,6 +6,7 @@ use crate::{ clear_event_dispatch_fields, event_internal_bool_flag, set_event_composed_path, set_event_dispatch_fields, set_event_internal_flag, }, + context_bootstrap::{EventHandlerType, apply_event_handler_return_value}, exception_reporting::CallbackExceptionLogLevel, host::report_event_callback_exception, util::{context_host_ptr_from_global_bridge, serialize_v8_array}, @@ -78,7 +79,7 @@ pub(crate) fn dispatch_simple_event_target_event<'s>( .get_creation_context(scope) .unwrap_or(current_context); let incumbent_context = scope.get_incumbent_context().unwrap_or(current_context); - let _ = invoke_simple_event_callback( + let returned = invoke_simple_event_callback( scope, event_type, &format!("simple event target {handler_name}"), @@ -90,6 +91,14 @@ pub(crate) fn dispatch_simple_event_target_event<'s>( &[event.into()], event, ); + if let Some(returned) = returned { + apply_event_handler_return_value( + scope, + event, + v8::Local::new(scope, &returned), + EventHandlerType::EventHandler, + ); + } } } @@ -122,7 +131,7 @@ pub(crate) fn dispatch_simple_event_target_event<'s>( ); } set_event_internal_flag(scope, event, EVENT_PASSIVE_SLOT, listener.passive); - let _ = invoke_simple_event_listener( + let returned = invoke_simple_event_listener( scope, event_type, &format!("simple event target {event_type} listener"), @@ -131,6 +140,16 @@ pub(crate) fn dispatch_simple_event_target_event<'s>( &[event.into()], event, ); + if listener.handler_slot.is_some() + && let Some(returned) = returned + { + apply_event_handler_return_value( + scope, + event, + v8::Local::new(scope, &returned), + EventHandlerType::EventHandler, + ); + } set_event_internal_flag(scope, event, EVENT_PASSIVE_SLOT, false); if event_stop_immediate_propagation(scope, event) { break 'phases; diff --git a/moli-renderer-v8/src/context_bootstrap/shared_worker_host.rs b/moli-renderer-v8/src/context_bootstrap/shared_worker_host.rs index de48fd5090..a32c1aa5f8 100644 --- a/moli-renderer-v8/src/context_bootstrap/shared_worker_host.rs +++ b/moli-renderer-v8/src/context_bootstrap/shared_worker_host.rs @@ -1,6 +1,6 @@ use super::{ - CHILD_BROWSING_CONTEXT_HANDLE_SLOT, MessagePortRealmBinding, - ensure_message_port_wrapper_for_id_in_realm, + CHILD_BROWSING_CONTEXT_HANDLE_SLOT, EventHandlerType, MessagePortRealmBinding, + apply_event_handler_return_value, ensure_message_port_wrapper_for_id_in_realm, events::{clear_event_dispatch_fields, set_event_dispatch_fields}, invoke_simple_event_listener, navigation_serialize::{ @@ -817,12 +817,12 @@ fn dispatch_shared_worker_error_event<'s>( ); if listener.handler_slot.as_deref() == Some(SHARED_WORKER_ONERROR_SLOT) && let Some(returned) = callback_result - && v8::Local::new(scope, &returned).boolean_value(scope) { - let _ = event.set( + apply_event_handler_return_value( scope, - v8str(scope, "defaultPrevented").into(), - v8::Boolean::new(scope, true).into(), + event, + v8::Local::new(scope, &returned), + EventHandlerType::EventHandler, ); } if listener.once { diff --git a/moli-renderer-v8/src/context_bootstrap/worker_host/dispatch.rs b/moli-renderer-v8/src/context_bootstrap/worker_host/dispatch.rs index 5523fa31f5..0de94a780e 100644 --- a/moli-renderer-v8/src/context_bootstrap/worker_host/dispatch.rs +++ b/moli-renderer-v8/src/context_bootstrap/worker_host/dispatch.rs @@ -5,7 +5,7 @@ use moli_webapi_declare::WebApiObject; use super::{WORKER_LISTENERS_SLOT, WORKER_ONERROR_SLOT}; use crate::context_bootstrap::{ - dispatch_simple_event_target_event, + EventHandlerType, apply_event_handler_return_value, dispatch_simple_event_target_event, events::{clear_event_dispatch_fields, set_event_dispatch_fields}, invoke_simple_event_listener, simple_object_event_listeners_snapshot, simple_object_event_remove_listener_value_for_type, @@ -313,12 +313,12 @@ pub(crate) fn dispatch_worker_error_event_with_kind<'s>( ); if listener.handler_slot.as_deref() == Some(WORKER_ONERROR_SLOT) && let Some(returned) = callback_result - && v8::Local::new(scope, &returned).boolean_value(scope) { - let _ = event.set( + apply_event_handler_return_value( scope, - v8str(scope, "defaultPrevented").into(), - v8::Boolean::new(scope, true).into(), + event, + v8::Local::new(scope, &returned), + EventHandlerType::EventHandler, ); } if listener.once { diff --git a/moli-renderer-v8/src/context_bootstrap/worker_host/tests.rs b/moli-renderer-v8/src/context_bootstrap/worker_host/tests.rs index 450d18fa48..c2e37670ad 100644 --- a/moli-renderer-v8/src/context_bootstrap/worker_host/tests.rs +++ b/moli-renderer-v8/src/context_bootstrap/worker_host/tests.rs @@ -1442,6 +1442,72 @@ async fn constructor_onerror_has_event_fields() { panic!("timed out waiting for error event fields"); } +#[tokio::test] +async fn constructor_onerror_return_true_does_not_cancel_syntax_error_event() { + ensure_v8(); + let (mut isolate, ctx) = setup_worker_context(); + + eval_ok( + &mut isolate, + &ctx, + r#" + var errorReturnObservations = null; + var w = new Worker("function {"); + w.onerror = function(e) { + errorReturnObservations = [arguments.length, e.defaultPrevented === true]; + return true; + }; + w.addEventListener("error", function(e) { + errorReturnObservations.push(e.defaultPrevented === true); + }); + "#, + ); + + for _ in 0..50 { + sleep(Duration::from_millis(20)).await; + let result = eval_ok( + &mut isolate, + &ctx, + "__drainWorkerMessages(w); JSON.stringify(errorReturnObservations)", + ); + if result != "null" { + assert_eq!(result, "[1,false,false]"); + eval_ok(&mut isolate, &ctx, "w.terminate()"); + return; + } + } + panic!("timed out waiting for worker syntax error event"); +} + +#[test] +fn worker_event_handler_only_boolean_false_cancels_synthetic_event() { + ensure_v8(); + let (mut isolate, ctx) = setup_worker_context(); + + let result = eval_ok( + &mut isolate, + &ctx, + r#" + (() => { + const w = new Worker(""); + const run = returned => { + w.onerror = () => returned; + const event = { type: "error", cancelable: true, defaultPrevented: false }; + return [w.dispatchEvent(event), event.defaultPrevented]; + }; + const observations = [run(true), run(false), run(0), run("")]; + w.terminate(); + return JSON.stringify(observations); + })() + "#, + ); + + assert_eq!( + result, + "[[true,false],[false,true],[true,false],[true,false]]" + ); +} + #[tokio::test] async fn constructor_error_listener_receives_event() { ensure_v8(); diff --git a/moli-renderer-v8/src/host/events.rs b/moli-renderer-v8/src/host/events.rs index 0fe8846eee..3affb807d0 100644 --- a/moli-renderer-v8/src/host/events.rs +++ b/moli-renderer-v8/src/host/events.rs @@ -1,8 +1,9 @@ use super::*; use crate::{ context_bootstrap::{ - CHILD_BROWSING_CONTEXT_HANDLE_SLOT, clear_event_composed_path, event_is_error_event, - mark_event_trusted, set_event_composed_path, + CHILD_BROWSING_CONTEXT_HANDLE_SLOT, EventHandlerType, apply_event_handler_return_value, + clear_event_composed_path, event_is_error_event, mark_event_trusted, + set_event_composed_path, }, document_runtime::DocumentRuntime, dom_parser::DOM_PARSER_FOREIGN_NODE_SLOT, @@ -452,13 +453,12 @@ fn invoke_registered_event_handler<'s>( ); if let Some(returned) = returned { let returned = v8::Local::new(scope, returned); - if returned.boolean_value(scope) { - let _ = event.set( - scope, - v8str(scope, "defaultPrevented").into(), - v8::Boolean::new(scope, true).into(), - ); - } + apply_event_handler_return_value( + scope, + event, + returned, + EventHandlerType::OnErrorEventHandler, + ); } if let Some(timing_started) = timing_started { tracing::info!( @@ -486,13 +486,7 @@ fn invoke_registered_event_handler<'s>( ); if let Some(returned) = returned { let returned = v8::Local::new(scope, returned); - if returned.is_boolean() && !returned.boolean_value(scope) { - let _ = event.set( - scope, - v8str(scope, "defaultPrevented").into(), - v8::Boolean::new(scope, true).into(), - ); - } + apply_event_handler_return_value(scope, event, returned, EventHandlerType::EventHandler); } if let Some(timing_started) = timing_started { tracing::info!( diff --git a/moli-renderer-v8/src/runtime/page_vm/tests/fetch_xhr.rs b/moli-renderer-v8/src/runtime/page_vm/tests/fetch_xhr.rs index 01d0e70cf2..5727c75846 100644 --- a/moli-renderer-v8/src/runtime/page_vm/tests/fetch_xhr.rs +++ b/moli-renderer-v8/src/runtime/page_vm/tests/fetch_xhr.rs @@ -6722,7 +6722,7 @@ async fn worker_runtime_error_promise_reaction_can_prevent_window_propagation() } #[tokio::test] -async fn worker_runtime_error_return_truthy_suppresses_window_onerror() { +async fn worker_runtime_error_return_non_boolean_truthy_propagates_to_window_onerror() { run_page_vm_async_test(async move { let (base_url, server) = spawn_single_response_http_server( "HTTP/1.1 200 OK", @@ -6759,8 +6759,8 @@ async fn worker_runtime_error_return_truthy_suppresses_window_onerror() { )?; drive_websocket_until_done( &mut page_vm, - "String(globalThis.__workerDone === true)", - "truthy worker onerror should suppress window.onerror", + "String(globalThis.__workerDone === true && globalThis.__windowErrorCalled === true)", + "non-boolean truthy worker onerror should propagate to window.onerror", ) .await?; page_vm.vm_mut().eval( @@ -6768,14 +6768,14 @@ async fn worker_runtime_error_return_truthy_suppresses_window_onerror() { ) }) .await - .expect("worker truthy onerror suppression test should run on owner lane"); + .expect("worker non-boolean truthy onerror propagation test should run on owner lane"); server .await - .expect("worker truthy onerror suppression server should finish"); + .expect("worker non-boolean truthy onerror propagation server should finish"); assert_eq!( result, - r#"{"windowErrorCalled":false,"listenerSawDefaultPrevented":true}"# + r#"{"windowErrorCalled":true,"listenerSawDefaultPrevented":false}"# ); }) .await; diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/events_selection_storage.rs b/moli-renderer-v8/src/script_vm/tests/browser_api/events_selection_storage.rs index 52d29a1d01..08f035d53b 100644 --- a/moli-renderer-v8/src/script_vm/tests/browser_api/events_selection_storage.rs +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/events_selection_storage.rs @@ -1719,6 +1719,31 @@ fn synthetic_error_event_uses_normal_window_event_handler_arguments() { ); } +#[test] +fn window_error_handler_only_boolean_true_cancels() { + let mut vm = new_storage_test_vm("https://window-error-return-value.test/"); + + let result = vm + .eval( + r#" + (() => { + const run = returned => { + window.onerror = () => returned; + const event = new ErrorEvent("error", { cancelable: true }); + return [window.dispatchEvent(event), event.defaultPrevented]; + }; + return JSON.stringify([run(true), run(1), run(false), run(0)]); + })() + "#, + ) + .expect("window error handler return-value probe should evaluate"); + + assert_eq!( + result, + "[[false,true],[true,false],[true,false],[true,false]]" + ); +} + #[test] fn window_onerror_null_assignment_supersedes_uncompiled_body_attribute() { let mut vm = new_storage_test_vm("https://window-onerror-null-override.test/"); diff --git a/moli-renderer-v8/src/worker/thread/dispatch.rs b/moli-renderer-v8/src/worker/thread/dispatch.rs index 2a78b8c658..e66db6bc53 100644 --- a/moli-renderer-v8/src/worker/thread/dispatch.rs +++ b/moli-renderer-v8/src/worker/thread/dispatch.rs @@ -12,7 +12,8 @@ use moli_webapi_declare::WebApiObject; use crate::callback_invocation::{CallbackInvocationOutcome, CallbackInvoker}; use crate::context_bootstrap::{ EVENT_DISPATCHING_SLOT, EVENT_STOP_IMMEDIATE_PROPAGATION_SLOT, EVENT_STOP_PROPAGATION_SLOT, - SimpleObjectEventListenerSnapshot, dispatch_message_port_events_for_port_collecting_errors, + EventHandlerType, SimpleObjectEventListenerSnapshot, apply_event_handler_return_value, + dispatch_message_port_events_for_port_collecting_errors, dispatch_service_worker_controller_change, ensure_message_port_wrapper_for_id, event_internal_bool_flag, mark_event_trusted, runtime_message_allowed_for_current_target, set_event_internal_flag, simple_object_event_listeners_snapshot, @@ -1048,13 +1049,12 @@ pub(super) fn dispatch_worker_error_event<'s>( ], ) { Ok(returned) => { - if v8::Local::new(scope, &returned).is_true() { - let _ = event.set( - scope, - v8str(scope, "defaultPrevented").into(), - v8::Boolean::new(scope, true).into(), - ); - } + apply_event_handler_return_value( + scope, + event, + v8::Local::new(scope, &returned), + EventHandlerType::OnErrorEventHandler, + ); } Err(nested_report) => { report_exception_to_parent( diff --git a/moli-wpt-compat/fixtures/wpt/ported/worker/worker-error-basic.html b/moli-wpt-compat/fixtures/wpt/ported/worker/worker-error-basic.html index 19f2704c2d..876fe2c8f5 100644 --- a/moli-wpt-compat/fixtures/wpt/ported/worker/worker-error-basic.html +++ b/moli-wpt-compat/fixtures/wpt/ported/worker/worker-error-basic.html @@ -92,13 +92,16 @@ promise_test(async function () { }); const defaultPrevented = await wait_for(listenerSeen, "worker error listener"); - await wait_for(handlerSeen, "worker.onerror truthy handler"); + await wait_for(handlerSeen, "worker.onerror non-boolean truthy handler"); await wait(30); - assert_true(defaultPrevented, "truthy worker.onerror should mark the error as handled"); + assert_false( + defaultPrevented, + "a non-boolean truthy worker.onerror return should not mark the error as handled", + ); } finally { window.onerror = previousOnerror; worker.terminate(); } -}, "Truthy worker.onerror marks the worker error as handled for later listeners"); +}, "A non-boolean truthy Worker.onerror return does not cancel the error event"); diff --git a/moli-wpt-compat/fixtures/wpt/ported/worker/worker-window-error-basic.html b/moli-wpt-compat/fixtures/wpt/ported/worker/worker-window-error-basic.html index 8b9aa9c01f..51019e3172 100644 --- a/moli-wpt-compat/fixtures/wpt/ported/worker/worker-window-error-basic.html +++ b/moli-wpt-compat/fixtures/wpt/ported/worker/worker-window-error-basic.html @@ -97,17 +97,20 @@ async function assert_worker_error_prevent_default_suppresses_window() { } } -async function assert_truthy_worker_onerror_suppresses_window() { +async function assert_non_boolean_truthy_worker_onerror_propagates_to_window() { const previousOnerror = window.onerror; let worker = null; let windowErrorCalled = false; let handlerSeen = false; try { - window.onerror = function () { - windowErrorCalled = true; - return true; - }; + const windowSeen = new Promise(function (resolve) { + window.onerror = function () { + windowErrorCalled = true; + resolve(); + return true; + }; + }); worker = new Worker(worker_error_url()); worker.onerror = function () { @@ -125,12 +128,19 @@ async function assert_truthy_worker_onerror_suppresses_window() { { once: true }, ); }), - "worker error listener after truthy onerror", + "worker error listener after non-boolean truthy onerror", ); + await wait_for(windowSeen, "window.onerror after non-boolean truthy worker.onerror"); assert_true(handlerSeen, "worker.onerror should run before the later listener"); - assert_true(listenerDefaultPrevented, "truthy worker.onerror should mark the worker error handled"); - assert_false(windowErrorCalled, "truthy worker.onerror should suppress window.onerror"); + assert_false( + listenerDefaultPrevented, + "a non-boolean truthy worker.onerror return should not mark the worker error handled", + ); + assert_true( + windowErrorCalled, + "a non-boolean truthy worker.onerror return should propagate to window.onerror", + ); } finally { window.onerror = previousOnerror; if (worker) { @@ -142,6 +152,6 @@ async function assert_truthy_worker_onerror_suppresses_window() { promise_test(async function () { await assert_unhandled_worker_error_propagates_to_window(); await assert_worker_error_prevent_default_suppresses_window(); - await assert_truthy_worker_onerror_suppresses_window(); + await assert_non_boolean_truthy_worker_onerror_propagates_to_window(); }, "Dedicated worker runtime errors propagate to page window.onerror unless handled");