LibWeb: Properly reject abrupt completion in clean_up_on_return

This commit is contained in:
Matthew Olsson 2023-04-16 17:40:09 -07:00 committed by Linus Groh
parent 4ed71d5b40
commit c40109628d
8 changed files with 46 additions and 25 deletions

View file

@ -815,6 +815,11 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
auto callback_function_generator = scoped_generator.fork();
auto& callback_function = interface.callback_functions.find(parameter.type->name())->value;
if (callback_function.return_type->is_object() && callback_function.return_type->name() == "Promise")
callback_function_generator.set("operation_returns_promise", "OperationReturnsPromise::Yes");
else
callback_function_generator.set("operation_returns_promise", "OperationReturnsPromise::No");
// An ECMAScript value V is converted to an IDL callback function type value by running the following algorithm:
// 1. If the result of calling IsCallable(V) is false and the conversion to an IDL value is not being performed due to V being assigned to an attribute whose type is a nullable callback function that is annotated with [LegacyTreatNonObjectAsNull], then throw a TypeError.
if (!callback_function.is_legacy_treat_non_object_as_null) {
@ -828,11 +833,11 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
callback_function_generator.append(R"~~~(
WebIDL::CallbackType* @cpp_name@ = nullptr;
if (@js_name@@js_suffix@.is_object())
@cpp_name@ = vm.heap().allocate_without_realm<WebIDL::CallbackType>(@js_name@@js_suffix@.as_object(), HTML::incumbent_settings_object());
@cpp_name@ = vm.heap().allocate_without_realm<WebIDL::CallbackType>(@js_name@@js_suffix@.as_object(), HTML::incumbent_settings_object(), @operation_returns_promise@);
)~~~");
} else {
callback_function_generator.append(R"~~~(
auto @cpp_name@ = vm.heap().allocate_without_realm<WebIDL::CallbackType>(@js_name@@js_suffix@.as_object(), HTML::incumbent_settings_object());
auto @cpp_name@ = vm.heap().allocate_without_realm<WebIDL::CallbackType>(@js_name@@js_suffix@.as_object(), HTML::incumbent_settings_object(), @operation_returns_promise@);
)~~~");
}
} else if (parameter.type->name() == "sequence") {

View file

@ -2077,7 +2077,7 @@ bool is_close_sentinel(JS::Value value)
// Non-standard function to aid in converting a user-provided function into a WebIDL::Callback. This is essentially
// what the Bindings generator would do at compile time, but at runtime instead.
JS::ThrowCompletionOr<JS::Handle<WebIDL::CallbackType>> property_to_callback(JS::VM& vm, JS::Value value, JS::PropertyKey const& property_key)
JS::ThrowCompletionOr<JS::Handle<WebIDL::CallbackType>> property_to_callback(JS::VM& vm, JS::Value value, JS::PropertyKey const& property_key, WebIDL::OperationReturnsPromise operation_returns_promise)
{
auto property = TRY(value.get(vm, property_key));
@ -2087,7 +2087,7 @@ JS::ThrowCompletionOr<JS::Handle<WebIDL::CallbackType>> property_to_callback(JS:
if (!property.is_function())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAFunction, TRY_OR_THROW_OOM(vm, property.to_string_without_side_effects()));
return vm.heap().allocate_without_realm<WebIDL::CallbackType>(property.as_object(), HTML::incumbent_settings_object());
return vm.heap().allocate_without_realm<WebIDL::CallbackType>(property.as_object(), HTML::incumbent_settings_object(), operation_returns_promise);
}
}

View file

@ -9,6 +9,7 @@
#include <LibJS/Heap/GCPtr.h>
#include <LibWeb/Forward.h>
#include <LibWeb/WebIDL/CallbackType.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
#include <LibWeb/WebIDL/Promise.h>
@ -107,7 +108,7 @@ bool is_non_negative_number(JS::Value);
JS::Value create_close_sentinel();
bool is_close_sentinel(JS::Value);
JS::ThrowCompletionOr<JS::Handle<WebIDL::CallbackType>> property_to_callback(JS::VM& vm, JS::Value value, JS::PropertyKey const& property_key);
JS::ThrowCompletionOr<JS::Handle<WebIDL::CallbackType>> property_to_callback(JS::VM& vm, JS::Value value, JS::PropertyKey const& property_key, WebIDL::OperationReturnsPromise);
// https://streams.spec.whatwg.org/#value-with-size
struct ValueWithSize {

View file

@ -19,10 +19,10 @@ JS::ThrowCompletionOr<UnderlyingSink> UnderlyingSink::from_value(JS::VM& vm, JS:
auto& object = value.as_object();
UnderlyingSink underlying_sink {
.start = TRY(property_to_callback(vm, value, "start")),
.write = TRY(property_to_callback(vm, value, "write")),
.close = TRY(property_to_callback(vm, value, "close")),
.abort = TRY(property_to_callback(vm, value, "abort")),
.start = TRY(property_to_callback(vm, value, "start", WebIDL::OperationReturnsPromise::No)),
.write = TRY(property_to_callback(vm, value, "write", WebIDL::OperationReturnsPromise::Yes)),
.close = TRY(property_to_callback(vm, value, "close", WebIDL::OperationReturnsPromise::Yes)),
.abort = TRY(property_to_callback(vm, value, "abort", WebIDL::OperationReturnsPromise::Yes)),
.type = {},
};

View file

@ -19,9 +19,9 @@ JS::ThrowCompletionOr<UnderlyingSource> UnderlyingSource::from_value(JS::VM& vm,
auto& object = value.as_object();
UnderlyingSource underlying_source {
.start = TRY(property_to_callback(vm, value, "start")),
.pull = TRY(property_to_callback(vm, value, "pull")),
.cancel = TRY(property_to_callback(vm, value, "cancel")),
.start = TRY(property_to_callback(vm, value, "start", WebIDL::OperationReturnsPromise::No)),
.pull = TRY(property_to_callback(vm, value, "pull", WebIDL::OperationReturnsPromise::Yes)),
.cancel = TRY(property_to_callback(vm, value, "cancel", WebIDL::OperationReturnsPromise::Yes)),
.type = {},
.auto_allocate_chunk_size = {},
};

View file

@ -13,6 +13,7 @@
#include <LibJS/Runtime/PropertyKey.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/Promise.h>
namespace Web::WebIDL {
@ -82,8 +83,10 @@ ErrorOr<ByteBuffer> get_buffer_source_copy(JS::Object const& buffer_source)
}
// https://webidl.spec.whatwg.org/#call-user-object-operation-return
inline JS::Completion clean_up_on_return(HTML::EnvironmentSettingsObject& stored_settings, HTML::EnvironmentSettingsObject& relevant_settings, JS::Completion& completion)
inline JS::Completion clean_up_on_return(HTML::EnvironmentSettingsObject& stored_settings, HTML::EnvironmentSettingsObject& relevant_settings, JS::Completion& completion, OperationReturnsPromise operation_returns_promise)
{
auto& realm = stored_settings.realm();
// Return: at this point completion will be set to an ECMAScript completion value.
// 1. Clean up after running a callback with stored settings.
@ -97,12 +100,15 @@ inline JS::Completion clean_up_on_return(HTML::EnvironmentSettingsObject& stored
return completion;
// 4. If completion is an abrupt completion and the operation has a return type that is not a promise type, return completion.
// FIXME: This does not handle promises and thus always returns completion at this point.
return completion;
if (completion.is_abrupt() && operation_returns_promise == OperationReturnsPromise::No)
return completion;
// FIXME: 5. Let rejectedPromise be ! Call(%Promise.reject%, %Promise%, «completion.[[Value]]»).
// 5. Let rejectedPromise be ! Call(%Promise.reject%, %Promise%, «completion.[[Value]]»).
auto rejected_promise = create_rejected_promise(realm, *completion.release_value());
// FIXME: 6. Return the result of converting rejectedPromise to the operations return type.
// 6. Return the result of converting rejectedPromise to the operations return type.
// Note: The operation must return a promise, so no conversion is necessary
return JS::Value { rejected_promise->promise() };
}
JS::Completion call_user_object_operation(WebIDL::CallbackType& callback, DeprecatedString const& operation_name, Optional<JS::Value> this_argument, JS::MarkedVector<JS::Value> args)
@ -143,13 +149,13 @@ JS::Completion call_user_object_operation(WebIDL::CallbackType& callback, Deprec
// 2. If getResult is an abrupt completion, set completion to getResult and jump to the step labeled return.
if (get_result.is_throw_completion()) {
completion = get_result.throw_completion();
return clean_up_on_return(stored_settings, relevant_settings, completion);
return clean_up_on_return(stored_settings, relevant_settings, completion, callback.operation_returns_promise);
}
// 4. If ! IsCallable(X) is false, then set completion to a new Completion{[[Type]]: throw, [[Value]]: a newly created TypeError object, [[Target]]: empty}, and jump to the step labeled return.
if (!get_result.value().is_function()) {
completion = realm.vm().template throw_completion<JS::TypeError>(JS::ErrorType::NotAFunction, TRY_OR_THROW_OOM(realm.vm(), get_result.value().to_string_without_side_effects()));
return clean_up_on_return(stored_settings, relevant_settings, completion);
return clean_up_on_return(stored_settings, relevant_settings, completion, callback.operation_returns_promise);
}
// 3. Set X to getResult.[[Value]].
@ -171,14 +177,14 @@ JS::Completion call_user_object_operation(WebIDL::CallbackType& callback, Deprec
// 13. 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_settings, relevant_settings, completion);
return clean_up_on_return(stored_settings, relevant_settings, completion, callback.operation_returns_promise);
}
// 14. Set completion to the result of converting callResult.[[Value]] to an IDL value of the same type as the operations return type.
// FIXME: This does no conversion.
completion = call_result.value();
return clean_up_on_return(stored_settings, relevant_settings, completion);
return clean_up_on_return(stored_settings, relevant_settings, completion, callback.operation_returns_promise);
}
// https://webidl.spec.whatwg.org/#invoke-a-callback-function
@ -229,14 +235,14 @@ JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional<JS::Valu
// 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_settings, relevant_settings, completion);
return clean_up_on_return(stored_settings, relevant_settings, completion, callback.operation_returns_promise);
}
// 13. Set completion to the result of converting callResult.[[Value]] to an IDL value of the same type as the operations return type.
// FIXME: This does no conversion.
completion = call_result.value();
return clean_up_on_return(stored_settings, relevant_settings, completion);
return clean_up_on_return(stored_settings, relevant_settings, completion, callback.operation_returns_promise);
}
JS::Completion construct(WebIDL::CallbackType& callback, JS::MarkedVector<JS::Value> args)

View file

@ -10,9 +10,10 @@
namespace Web::WebIDL {
CallbackType::CallbackType(JS::Object& callback, HTML::EnvironmentSettingsObject& callback_context)
CallbackType::CallbackType(JS::Object& callback, HTML::EnvironmentSettingsObject& callback_context, OperationReturnsPromise operation_returns_promise)
: callback(callback)
, callback_context(callback_context)
, operation_returns_promise(operation_returns_promise)
{
}

View file

@ -12,16 +12,24 @@
namespace Web::WebIDL {
enum class OperationReturnsPromise {
Yes,
No,
};
// https://webidl.spec.whatwg.org/#idl-callback-interface
class CallbackType final : public JS::Cell {
public:
CallbackType(JS::Object& callback, HTML::EnvironmentSettingsObject& callback_context);
CallbackType(JS::Object& callback, HTML::EnvironmentSettingsObject& callback_context, OperationReturnsPromise = OperationReturnsPromise::No);
JS::NonnullGCPtr<JS::Object> callback;
// https://webidl.spec.whatwg.org/#dfn-callback-context
JS::NonnullGCPtr<HTML::EnvironmentSettingsObject> callback_context;
// Non-standard property used to distinguish Promise-returning callbacks in callback-related AOs
OperationReturnsPromise operation_returns_promise;
private:
virtual StringView class_name() const override;
virtual void visit_edges(Cell::Visitor&) override;