mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-20 11:36:10 +00:00
LibWeb: Add an alternative to WebIDL::invoke_callback to return promises
When we need the callback to return a promise, we can use this alternate invoker to construct the WebIDL::Promise for us. Currently, the Streams API will use WebIDL::invoke_callback to create a JS::Promise, and then wrap that result in a resolved WebIDL::Promise. This results in rejected JS::Promise instances not being propagated.
This commit is contained in:
parent
b324b876f2
commit
525343ba79
Notes:
github-actions[bot]
2025-04-16 08:06:27 +00:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/525343ba793 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4372
2 changed files with 65 additions and 32 deletions
|
@ -243,18 +243,10 @@ 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, ExceptionBehavior exception_behavior, GC::RootVector<JS::Value> args)
|
||||
template<typename ReturnSteps>
|
||||
static auto invoke_callback_impl(WebIDL::CallbackType& callback, Optional<JS::Value> this_argument, GC::RootVector<JS::Value> args, ReturnSteps&& return_steps)
|
||||
{
|
||||
// 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;
|
||||
|
||||
// 2. If thisArg was not given, let thisArg be undefined.
|
||||
if (!this_argument.has_value())
|
||||
|
@ -263,18 +255,17 @@ JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional<JS::Valu
|
|||
// 3. Let F be the ECMAScript object corresponding to callable.
|
||||
auto& function_object = callback.callback;
|
||||
|
||||
// 5. Let relevant realm be F’s associated realm.
|
||||
auto& relevant_realm = function_object->shape().realm();
|
||||
|
||||
// 4. If ! IsCallable(F) is false:
|
||||
if (!function_object->is_function()) {
|
||||
// 1. Note: This is only possible when the callback function came from an attribute marked with [LegacyTreatNonObjectAsNull].
|
||||
|
||||
// 2. Return the result of converting undefined to the callback function’s return type.
|
||||
// FIXME: This does no conversion.
|
||||
return { JS::js_undefined() };
|
||||
return return_steps(relevant_realm, JS::js_undefined());
|
||||
}
|
||||
|
||||
// 5. Let relevant realm be F’s associated realm.
|
||||
auto& relevant_realm = function_object->shape().realm();
|
||||
|
||||
// 6. Let stored realm be callable’s callback context.
|
||||
auto& stored_realm = callback.callback_context;
|
||||
|
||||
|
@ -291,7 +282,11 @@ JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional<JS::Valu
|
|||
auto& vm = function_object->vm();
|
||||
auto call_result = JS::call(vm, as<JS::FunctionObject>(*function_object), this_argument.value(), args.span());
|
||||
|
||||
auto return_steps = [&](JS::Completion completion) -> JS::Completion {
|
||||
// 11. If callResult is an abrupt completion, set completion to callResult and jump to the step labeled return.
|
||||
// 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.
|
||||
// 13. Return: at this point completion will be set to an IDL value or an abrupt completion.
|
||||
{
|
||||
// 1. Clean up after running a callback with stored realm.
|
||||
HTML::clean_up_after_running_callback(stored_realm);
|
||||
|
||||
|
@ -299,6 +294,21 @@ JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional<JS::Valu
|
|||
// 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);
|
||||
|
||||
return return_steps(relevant_realm, move(call_result));
|
||||
}
|
||||
}
|
||||
|
||||
JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional<JS::Value> this_argument, ExceptionBehavior exception_behavior, GC::RootVector<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);
|
||||
|
||||
return invoke_callback_impl(callback, move(this_argument), move(args), [&](JS::Realm& relevant_realm, JS::Completion completion) -> JS::Completion {
|
||||
// 3. If completion is an IDL value, return completion.
|
||||
if (!completion.is_abrupt())
|
||||
return completion;
|
||||
|
@ -308,10 +318,11 @@ JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional<JS::Valu
|
|||
|
||||
// 5. If exceptionBehavior is "rethrow", throw completion.[[Value]].
|
||||
if (exception_behavior == ExceptionBehavior::Rethrow) {
|
||||
TRY(JS::throw_completion(completion.release_value()));
|
||||
return JS::throw_completion(completion.release_value());
|
||||
}
|
||||
|
||||
// 6. Otherwise, if exceptionBehavior is "report":
|
||||
else if (exception_behavior == ExceptionBehavior::Report) {
|
||||
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.
|
||||
|
@ -330,20 +341,7 @@ JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional<JS::Valu
|
|||
|
||||
// 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 return_steps(completion);
|
||||
});
|
||||
}
|
||||
|
||||
JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional<JS::Value> this_argument, GC::RootVector<JS::Value> args)
|
||||
|
@ -351,6 +349,28 @@ JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional<JS::Valu
|
|||
return invoke_callback(callback, move(this_argument), ExceptionBehavior::NotSpecified, move(args));
|
||||
}
|
||||
|
||||
// AD-HOC: This may be used as an alternative to WebIDL::invoke_callback when you know the callback returns a promise,
|
||||
// and the caller needs a WebIDL::Promise rather than a JS::Promise.
|
||||
GC::Ref<WebIDL::Promise> invoke_promise_callback(WebIDL::CallbackType& callback, Optional<JS::Value> this_argument, GC::RootVector<JS::Value> args)
|
||||
{
|
||||
VERIFY(callback.operation_returns_promise == OperationReturnsPromise::Yes);
|
||||
|
||||
return invoke_callback_impl(callback, move(this_argument), move(args), [&](JS::Realm& relevant_realm, JS::Completion completion) -> GC::Ref<WebIDL::Promise> {
|
||||
// 3. If completion is an IDL value, return completion.
|
||||
if (!completion.is_abrupt())
|
||||
return WebIDL::create_resolved_promise(relevant_realm, completion.release_value());
|
||||
|
||||
// 4. Assert: completion is an abrupt completion.
|
||||
VERIFY(completion.is_abrupt());
|
||||
|
||||
// NOTE: The intermediate steps to handle exception behavior are not relevant for promise-returning callbacks.
|
||||
|
||||
// 8. Let rejectedPromise be ! Call(%Promise.reject%, %Promise%, «completion.[[Value]]»).
|
||||
// 9. Return the result of converting rejectedPromise to the callback function’s return type.
|
||||
return create_rejected_promise(relevant_realm, completion.release_value());
|
||||
});
|
||||
}
|
||||
|
||||
JS::Completion construct(WebIDL::CallbackType& callback, GC::RootVector<JS::Value> args)
|
||||
{
|
||||
// 1. Let completion be an uninitialized variable.
|
||||
|
|
|
@ -66,6 +66,19 @@ JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional<JS::Valu
|
|||
return invoke_callback(callback, move(this_argument), ExceptionBehavior::NotSpecified, forward<Args>(args)...);
|
||||
}
|
||||
|
||||
GC::Ref<WebIDL::Promise> invoke_promise_callback(WebIDL::CallbackType& callback, Optional<JS::Value> this_argument, GC::RootVector<JS::Value> args);
|
||||
|
||||
template<typename... Args>
|
||||
GC::Ref<WebIDL::Promise> invoke_promise_callback(WebIDL::CallbackType& callback, Optional<JS::Value> this_argument, Args&&... args)
|
||||
{
|
||||
auto& function_object = callback.callback;
|
||||
|
||||
GC::RootVector<JS::Value> arguments_list { function_object->heap() };
|
||||
(arguments_list.append(forward<Args>(args)), ...);
|
||||
|
||||
return invoke_promise_callback(callback, move(this_argument), move(arguments_list));
|
||||
}
|
||||
|
||||
JS::Completion construct(WebIDL::CallbackType& callback, GC::RootVector<JS::Value> args);
|
||||
|
||||
// https://webidl.spec.whatwg.org/#construct-a-callback-function
|
||||
|
|
Loading…
Add table
Reference in a new issue