From 1f57df34f148d306d0e44d2ed7b3efe3349b7205 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Tue, 3 Dec 2024 22:18:02 +0000 Subject: [PATCH] LibWeb: Update `WebIDL::invoke_callback()` to follow the latest spec `WebIDL::invoke_callback()` now takes an `exception_behavior` parameter, which can be set to `report` to report an exception before returning. Setting `exception_behavior` to `rethrow` retains the previous behavior. --- .../LibWeb/WebIDL/AbstractOperations.cpp | 80 ++++++++++++++++--- Libraries/LibWeb/WebIDL/AbstractOperations.h | 17 +++- 2 files changed, 83 insertions(+), 14 deletions(-) diff --git a/Libraries/LibWeb/WebIDL/AbstractOperations.cpp b/Libraries/LibWeb/WebIDL/AbstractOperations.cpp index 0054fb51194..0ac9b95b25f 100644 --- a/Libraries/LibWeb/WebIDL/AbstractOperations.cpp +++ b/Libraries/LibWeb/WebIDL/AbstractOperations.cpp @@ -255,8 +255,16 @@ JS::ThrowCompletionOr to_usv_string(JS::VM& vm, JS::Value value) // https://webidl.spec.whatwg.org/#invoke-a-callback-function // https://whatpr.org/webidl/1437.html#invoke-a-callback-function -JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional this_argument, GC::MarkedVector args) +JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional this_argument, ExceptionBehavior exception_behavior, GC::MarkedVector args) { + // https://webidl.spec.whatwg.org/#js-invoking-callback-functions + // The exceptionBehavior argument must be supplied if, and only if, callable’s return type is not a promise type. If callable’s return type is neither undefined nor any, it must be "rethrow". + // NOTE: Until call sites are updated to respect this, specifications which fail to provide a value here when it would be mandatory should be understood as supplying "rethrow". + if (exception_behavior == ExceptionBehavior::NotSpecified && callback.operation_returns_promise == OperationReturnsPromise::No) + exception_behavior = ExceptionBehavior::Rethrow; + + VERIFY(exception_behavior == ExceptionBehavior::NotSpecified || callback.operation_returns_promise == OperationReturnsPromise::No); + // 1. Let completion be an uninitialized variable. JS::Completion completion; @@ -276,36 +284,84 @@ JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optionalshape().realm(); - // 6. Let stored realm be value’s callback context. + // 6. Let stored realm be callable’s callback context. auto& stored_realm = callback.callback_context; - // 8. Prepare to run script with relevant realm. + // 7. Prepare to run script with relevant realm. HTML::prepare_to_run_script(relevant_realm); - // 9. Prepare to run a callback with stored realm. + // 8. Prepare to run a callback with stored realm. HTML::prepare_to_run_callback(stored_realm); - // FIXME: 10. Let esArgs be the result of converting args to an ECMAScript arguments list. If this throws an exception, set completion to the completion value representing the thrown exception and jump to the step labeled return. - // For simplicity, we currently make the caller do this. However, this means we can't throw exceptions at this point like the spec wants us to. + // FIXME: 9. Let jsArgs be the result of converting args to a JavaScript arguments list. + // If this throws an exception, set completion to the completion value representing the thrown exception and jump to the step labeled return. - // 11. Let callResult be Call(F, thisArg, esArgs). + // 10. Let callResult be Call(F, thisArg, jsArgs). auto& vm = function_object->vm(); auto call_result = JS::call(vm, verify_cast(*function_object), this_argument.value(), args.span()); - // 12. If callResult is an abrupt completion, set completion to callResult and jump to the step labeled return. + auto return_steps = [&](JS::Completion completion) -> JS::Completion { + // 1. Clean up after running a callback with stored realm. + HTML::clean_up_after_running_callback(stored_realm); + + // 2. Clean up after running script with relevant realm. + // FIXME: This method follows an older version of the spec, which takes a realm, so we use F's associated realm instead. + HTML::clean_up_after_running_script(relevant_realm); + + // 3. If completion is an IDL value, return completion. + if (!completion.is_abrupt()) + return completion; + + // 4. Assert: completion is an abrupt completion. + VERIFY(completion.is_abrupt()); + + // 5. If exceptionBehavior is "rethrow", throw completion.[[Value]]. + if (exception_behavior == ExceptionBehavior::Rethrow) { + TRY(JS::throw_completion(*completion.release_value())); + } + // 6. Otherwise, if exceptionBehavior is "report": + else if (exception_behavior == ExceptionBehavior::Report) { + // FIXME: 1. Assert: callable’s return type is undefined or any. + + // 2. Report an exception completion.[[Value]] for relevant realm’s global object. + auto* window_or_worker = dynamic_cast(&relevant_realm.global_object()); + VERIFY(window_or_worker); + window_or_worker->report_an_exception(*completion.release_value()); + + // 3. Return the unique undefined IDL value. + return JS::js_undefined(); + } + + // 7. Assert: callable’s return type is a promise type. + VERIFY(callback.operation_returns_promise == OperationReturnsPromise::Yes); + + // 8. Let rejectedPromise be ! Call(%Promise.reject%, %Promise%, «completion.[[Value]]»). + auto rejected_promise = create_rejected_promise(relevant_realm, *completion.release_value()); + + // 9. Return the result of converting rejectedPromise to the callback function’s return type. + return JS::Value { rejected_promise->promise() }; + }; + + // 11. If callResult is an abrupt completion, set completion to callResult and jump to the step labeled return. if (call_result.is_throw_completion()) { completion = call_result.throw_completion(); - return clean_up_on_return(stored_realm, relevant_realm, completion, callback.operation_returns_promise); + return return_steps(completion); } - // 13. Set completion to the result of converting callResult.[[Value]] to an IDL value of the same type as the operation’s return type. + // 12. Set completion to the result of converting callResult.[[Value]] to an IDL value of the same type as callable’s return type. + // If this throws an exception, set completion to the completion value representing the thrown exception. // FIXME: This does no conversion. completion = call_result.value(); - return clean_up_on_return(stored_realm, relevant_realm, completion, callback.operation_returns_promise); + return return_steps(completion); +} + +JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional this_argument, GC::MarkedVector args) +{ + return invoke_callback(callback, move(this_argument), ExceptionBehavior::NotSpecified, move(args)); } JS::Completion construct(WebIDL::CallbackType& callback, GC::MarkedVector args) diff --git a/Libraries/LibWeb/WebIDL/AbstractOperations.h b/Libraries/LibWeb/WebIDL/AbstractOperations.h index 7728588a3d8..301055f0714 100644 --- a/Libraries/LibWeb/WebIDL/AbstractOperations.h +++ b/Libraries/LibWeb/WebIDL/AbstractOperations.h @@ -39,18 +39,31 @@ JS::Completion call_user_object_operation(WebIDL::CallbackType& callback, String return call_user_object_operation(callback, operation_name, move(this_argument), move(arguments_list)); } +enum class ExceptionBehavior { + NotSpecified, + Report, + Rethrow, +}; + +JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional this_argument, ExceptionBehavior exception_behavior, GC::MarkedVector args); JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional this_argument, GC::MarkedVector args); // https://webidl.spec.whatwg.org/#invoke-a-callback-function template -JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional this_argument, Args&&... args) +JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional this_argument, ExceptionBehavior exception_behavior, Args&&... args) { auto& function_object = callback.callback; GC::MarkedVector arguments_list { function_object->heap() }; (arguments_list.append(forward(args)), ...); - return invoke_callback(callback, move(this_argument), move(arguments_list)); + return invoke_callback(callback, move(this_argument), exception_behavior, move(arguments_list)); +} + +template +JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional this_argument, Args&&... args) +{ + return invoke_callback(callback, move(this_argument), ExceptionBehavior::NotSpecified, forward(args)...); } JS::Completion construct(WebIDL::CallbackType& callback, GC::MarkedVector args);