diff options
author | Linus Groh <mail@linusgroh.de> | 2022-09-22 23:36:09 +0100 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2022-09-27 14:56:17 +0100 |
commit | 37972e9f0cd206071d63c9d489651c87cce58e37 (patch) | |
tree | 3e79f682aef68f509e3220146b3759b0610a4ede | |
parent | e68e92b3041eb914fd22f7e690b914f7f99c2e3e (diff) | |
download | serenity-37972e9f0cd206071d63c9d489651c87cce58e37.zip |
LibWeb: Implement most WebIDL promise operations
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/Promise.h | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/WebIDL/Promise.cpp | 178 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/WebIDL/Promise.h | 29 |
4 files changed, 209 insertions, 0 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/Promise.h b/Userland/Libraries/LibJS/Runtime/Promise.h index a4ef47c143..82692393dc 100644 --- a/Userland/Libraries/LibJS/Runtime/Promise.h +++ b/Userland/Libraries/LibJS/Runtime/Promise.h @@ -45,6 +45,7 @@ public: Value perform_then(Value on_fulfilled, Value on_rejected, Optional<PromiseCapability> result_capability); bool is_handled() const { return m_is_handled; } + void set_is_handled() { m_is_handled = true; } protected: explicit Promise(Object& prototype); diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index be29c7b82a..bd46f8b1e3 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -415,6 +415,7 @@ set(SOURCES WebIDL/CallbackType.cpp WebIDL/DOMException.cpp WebIDL/OverloadResolution.cpp + WebIDL/Promise.cpp WebSockets/WebSocket.cpp XHR/EventNames.cpp XHR/ProgressEvent.cpp diff --git a/Userland/Libraries/LibWeb/WebIDL/Promise.cpp b/Userland/Libraries/LibWeb/WebIDL/Promise.cpp new file mode 100644 index 0000000000..17b42d8c0a --- /dev/null +++ b/Userland/Libraries/LibWeb/WebIDL/Promise.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2022, Linus Groh <linusg@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/Function.h> +#include <LibJS/Runtime/PromiseConstructor.h> +#include <LibJS/Runtime/Realm.h> +#include <LibWeb/Bindings/ExceptionOrUtils.h> +#include <LibWeb/WebIDL/ExceptionOr.h> +#include <LibWeb/WebIDL/Promise.h> + +namespace Web::WebIDL { + +// https://webidl.spec.whatwg.org/#a-new-promise +JS::PromiseCapability create_promise(JS::Realm& realm) +{ + auto& vm = realm.vm(); + + // 1. Let constructor be realm.[[Intrinsics]].[[%Promise%]]. + auto* constructor = realm.intrinsics().promise_constructor(); + + // Return ? NewPromiseCapability(constructor). + // NOTE: When called with %Promise%, NewPromiseCapability can't throw. + return MUST(JS::new_promise_capability(vm, constructor)); +} + +// https://webidl.spec.whatwg.org/#a-promise-resolved-with +JS::PromiseCapability create_resolved_promise(JS::Realm& realm, JS::Value value) +{ + auto& vm = realm.vm(); + + // 1. Let value be the result of converting x to an ECMAScript value. + + // 2. Let constructor be realm.[[Intrinsics]].[[%Promise%]]. + auto* constructor = realm.intrinsics().promise_constructor(); + + // 3. Let promiseCapability be ? NewPromiseCapability(constructor). + // NOTE: When called with %Promise%, NewPromiseCapability can't throw. + auto promise_capability = MUST(JS::new_promise_capability(vm, constructor)); + + // 4. Perform ! Call(promiseCapability.[[Resolve]], undefined, « value »). + MUST(JS::call(vm, promise_capability.resolve, JS::js_undefined(), value)); + + // 5. Return promiseCapability. + return promise_capability; +} + +// https://webidl.spec.whatwg.org/#a-promise-rejected-with +JS::PromiseCapability create_rejected_promise(JS::Realm& realm, JS::Value reason) +{ + auto& vm = realm.vm(); + + // 1. Let constructor be realm.[[Intrinsics]].[[%Promise%]]. + auto* constructor = realm.intrinsics().promise_constructor(); + + // 2. Let promiseCapability be ? NewPromiseCapability(constructor). + // NOTE: When called with %Promise%, NewPromiseCapability can't throw. + auto promise_capability = MUST(JS::new_promise_capability(vm, constructor)); + + // 3. Perform ! Call(promiseCapability.[[Reject]], undefined, « r »). + MUST(JS::call(vm, promise_capability.reject, JS::js_undefined(), reason)); + + // 4. Return promiseCapability. + return promise_capability; +} + +// https://webidl.spec.whatwg.org/#resolve +void resolve_promise(JS::VM& vm, JS::PromiseCapability const& promise_capability, JS::Value value) +{ + // 1. If x is not given, then let it be the undefined value. + // NOTE: This is done via the default argument. + + // 2. Let value be the result of converting x to an ECMAScript value. + + // 3. Perform ! Call(p.[[Resolve]], undefined, « value »). + MUST(JS::call(vm, promise_capability.resolve, JS::js_undefined(), value)); +} + +// https://webidl.spec.whatwg.org/#reject +void reject_promise(JS::VM& vm, JS::PromiseCapability const& promise_capability, JS::Value reason) +{ + // 1. Perform ! Call(p.[[Reject]], undefined, « r »). + MUST(JS::call(vm, promise_capability.reject, JS::js_undefined(), reason)); +} + +// https://webidl.spec.whatwg.org/#dfn-perform-steps-once-promise-is-settled +JS::NonnullGCPtr<JS::Promise> react_to_promise(JS::PromiseCapability const& promise_capability, Optional<ReactionSteps> on_fulfilled_callback, Optional<ReactionSteps> on_rejected_callback) +{ + auto& realm = promise_capability.promise->shape().realm(); + auto& vm = realm.vm(); + + // 1. Let onFulfilledSteps be the following steps given argument V: + auto on_fulfilled_steps = [on_fulfilled_callback = move(on_fulfilled_callback)](JS::VM& vm) -> JS::ThrowCompletionOr<JS::Value> { + // 1. Let value be the result of converting V to an IDL value of type T. + auto value = vm.argument(0); + + // 2. If there is a set of steps to be run if the promise was fulfilled, then let result be the result of performing them, given value if T is not undefined. Otherwise, let result be value. + auto result = on_fulfilled_callback.has_value() + ? TRY(Bindings::throw_dom_exception_if_needed(vm, [&] { return (*on_fulfilled_callback)(value); })) + : value; + + // 3. Return result, converted to an ECMAScript value. + return result; + }; + + // 2. Let onFulfilled be CreateBuiltinFunction(onFulfilledSteps, « »): + auto* on_fulfilled = JS::NativeFunction::create(realm, move(on_fulfilled_steps), 1, ""); + + // 3. Let onRejectedSteps be the following steps given argument R: + auto on_rejected_steps = [&realm, on_rejected_callback = move(on_rejected_callback)](JS::VM& vm) -> JS::ThrowCompletionOr<JS::Value> { + // 1. Let reason be the result of converting R to an IDL value of type any. + auto reason = vm.argument(0); + + // 2. If there is a set of steps to be run if the promise was rejected, then let result be the result of performing them, given reason. Otherwise, let result be a promise rejected with reason. + auto result = on_rejected_callback.has_value() + ? TRY(Bindings::throw_dom_exception_if_needed(vm, [&] { return (*on_rejected_callback)(reason); })) + : WebIDL::create_rejected_promise(realm, reason).promise; + + // 3. Return result, converted to an ECMAScript value. + return result; + }; + + // 4. Let onRejected be CreateBuiltinFunction(onRejectedSteps, « »): + auto* on_rejected = JS::NativeFunction::create(realm, move(on_rejected_steps), 1, ""); + + // 5. Let constructor be promise.[[Promise]].[[Realm]].[[Intrinsics]].[[%Promise%]]. + auto* constructor = realm.intrinsics().promise_constructor(); + + // 6. Let newCapability be ? NewPromiseCapability(constructor). + // NOTE: When called with %Promise%, NewPromiseCapability can't throw. + auto new_capability = MUST(JS::new_promise_capability(vm, constructor)); + + // 7. Return PerformPromiseThen(promise.[[Promise]], onFulfilled, onRejected, newCapability). + auto* promise = static_cast<JS::Promise*>(promise_capability.promise); + auto value = promise->perform_then(on_fulfilled, on_rejected, new_capability); + return verify_cast<JS::Promise>(value.as_object()); +} + +// https://webidl.spec.whatwg.org/#upon-fulfillment +JS::NonnullGCPtr<JS::Promise> upon_fulfillment(JS::PromiseCapability const& promise_capability, ReactionSteps steps) +{ + // 1. Return the result of reacting to promise: + return react_to_promise(promise_capability, + // - If promise was fulfilled with value v, then: + [steps = move(steps)](auto value) { + // 1. Perform steps with v. + // NOTE: The `return` is not immediately obvious, but `steps` may be something like + // "Return the result of ...", which we also need to do _here_. + return steps(value); + }, + {}); +} + +// https://webidl.spec.whatwg.org/#upon-rejection +JS::NonnullGCPtr<JS::Promise> upon_rejection(JS::PromiseCapability const& promise_capability, ReactionSteps steps) +{ + // 1. Return the result of reacting to promise: + return react_to_promise(promise_capability, {}, + // - If promise was rejected with reason r, then: + [steps = move(steps)](auto reason) { + // 1. Perform steps with r. + // NOTE: The `return` is not immediately obvious, but `steps` may be something like + // "Return the result of ...", which we also need to do _here_. + return steps(reason); + }); +} + +// https://webidl.spec.whatwg.org/#mark-a-promise-as-handled +void mark_promise_as_handled(JS::PromiseCapability const& promise_capability) +{ + // To mark as handled a Promise<T> promise, set promise.[[Promise]].[[PromiseIsHandled]] to true. + auto* promise = static_cast<JS::Promise*>(promise_capability.promise); + promise->set_is_handled(); +} + +} diff --git a/Userland/Libraries/LibWeb/WebIDL/Promise.h b/Userland/Libraries/LibWeb/WebIDL/Promise.h new file mode 100644 index 0000000000..65122b59df --- /dev/null +++ b/Userland/Libraries/LibWeb/WebIDL/Promise.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022, Linus Groh <linusg@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibJS/Forward.h> +#include <LibJS/Runtime/PromiseReaction.h> +#include <LibJS/Runtime/Value.h> +#include <LibJS/SafeFunction.h> +#include <LibWeb/Forward.h> + +namespace Web::WebIDL { + +using ReactionSteps = JS::SafeFunction<WebIDL::ExceptionOr<JS::Value>(JS::Value)>; + +JS::PromiseCapability create_promise(JS::Realm&); +JS::PromiseCapability create_resolved_promise(JS::Realm&, JS::Value); +JS::PromiseCapability create_rejected_promise(JS::Realm&, JS::Value); +void resolve_promise(JS::VM&, JS::PromiseCapability const&, JS::Value = JS::js_undefined()); +void reject_promise(JS::VM&, JS::PromiseCapability const&, JS::Value); +JS::NonnullGCPtr<JS::Promise> react_to_promise(JS::PromiseCapability const&, Optional<ReactionSteps> on_fulfilled_callback, Optional<ReactionSteps> on_rejected_callback); +JS::NonnullGCPtr<JS::Promise> upon_fulfillment(JS::PromiseCapability const&, ReactionSteps); +JS::NonnullGCPtr<JS::Promise> upon_rejection(JS::PromiseCapability const&, ReactionSteps); +void mark_promise_as_handled(JS::PromiseCapability const&); + +} |