mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-24 18:02:20 +00:00
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.
This commit is contained in:
parent
07635d4554
commit
1f57df34f1
Notes:
github-actions[bot]
2024-12-19 15:26:54 +00:00
Author: https://github.com/tcl3
Commit: 1f57df34f1
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2739
Reviewed-by: https://github.com/shannonbooth
2 changed files with 83 additions and 14 deletions
|
@ -255,8 +255,16 @@ JS::ThrowCompletionOr<String> 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<JS::Value> this_argument, GC::MarkedVector<JS::Value> args)
|
||||
JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional<JS::Value> this_argument, ExceptionBehavior exception_behavior, GC::MarkedVector<JS::Value> 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, Optional<JS::Valu
|
|||
return { JS::js_undefined() };
|
||||
}
|
||||
|
||||
// 5. Let relevant realm be F’s associated Realm.
|
||||
// 5. Let relevant realm be F’s associated realm.
|
||||
auto& relevant_realm = function_object->shape().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<JS::FunctionObject>(*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.
|
||||
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);
|
||||
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<HTML::WindowOrWorkerGlobalScopeMixin*>(&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();
|
||||
}
|
||||
|
||||
// 13. Set completion to the result of converting callResult.[[Value]] to an IDL value of the same type as the operation’s return type.
|
||||
// 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 return_steps(completion);
|
||||
}
|
||||
|
||||
// 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<JS::Value> this_argument, GC::MarkedVector<JS::Value> args)
|
||||
{
|
||||
return invoke_callback(callback, move(this_argument), ExceptionBehavior::NotSpecified, move(args));
|
||||
}
|
||||
|
||||
JS::Completion construct(WebIDL::CallbackType& callback, GC::MarkedVector<JS::Value> args)
|
||||
|
|
|
@ -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<JS::Value> this_argument, ExceptionBehavior exception_behavior, GC::MarkedVector<JS::Value> args);
|
||||
JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional<JS::Value> this_argument, GC::MarkedVector<JS::Value> args);
|
||||
|
||||
// https://webidl.spec.whatwg.org/#invoke-a-callback-function
|
||||
template<typename... Args>
|
||||
JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional<JS::Value> this_argument, Args&&... args)
|
||||
JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional<JS::Value> this_argument, ExceptionBehavior exception_behavior, Args&&... args)
|
||||
{
|
||||
auto& function_object = callback.callback;
|
||||
|
||||
GC::MarkedVector<JS::Value> arguments_list { function_object->heap() };
|
||||
(arguments_list.append(forward<Args>(args)), ...);
|
||||
|
||||
return invoke_callback(callback, move(this_argument), move(arguments_list));
|
||||
return invoke_callback(callback, move(this_argument), exception_behavior, move(arguments_list));
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional<JS::Value> this_argument, Args&&... args)
|
||||
{
|
||||
return invoke_callback(callback, move(this_argument), ExceptionBehavior::NotSpecified, forward<Args>(args)...);
|
||||
}
|
||||
|
||||
JS::Completion construct(WebIDL::CallbackType& callback, GC::MarkedVector<JS::Value> args);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue