/src/serenity/Userland/Libraries/LibJS/Runtime/PromiseJobs.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org> |
3 | | * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include <AK/Debug.h> |
9 | | #include <LibJS/Runtime/AbstractOperations.h> |
10 | | #include <LibJS/Runtime/GlobalObject.h> |
11 | | #include <LibJS/Runtime/JobCallback.h> |
12 | | #include <LibJS/Runtime/Promise.h> |
13 | | #include <LibJS/Runtime/PromiseCapability.h> |
14 | | #include <LibJS/Runtime/PromiseJobs.h> |
15 | | #include <LibJS/Runtime/PromiseReaction.h> |
16 | | |
17 | | namespace JS { |
18 | | |
19 | | // 27.2.2.1 NewPromiseReactionJob ( reaction, argument ), https://tc39.es/ecma262/#sec-newpromisereactionjob |
20 | | static ThrowCompletionOr<Value> run_reaction_job(VM& vm, PromiseReaction& reaction, Value argument) |
21 | 0 | { |
22 | | // a. Let promiseCapability be reaction.[[Capability]]. |
23 | 0 | auto promise_capability = reaction.capability(); |
24 | | |
25 | | // b. Let type be reaction.[[Type]]. |
26 | 0 | auto type = reaction.type(); |
27 | | |
28 | | // c. Let handler be reaction.[[Handler]]. |
29 | 0 | auto handler = reaction.handler(); |
30 | |
|
31 | 0 | Completion handler_result; |
32 | | |
33 | | // d. If handler is empty, then |
34 | 0 | if (!handler) { |
35 | 0 | dbgln_if(PROMISE_DEBUG, "run_reaction_job: Handler is empty"); |
36 | | |
37 | | // i. If type is Fulfill, let handlerResult be NormalCompletion(argument). |
38 | 0 | if (type == PromiseReaction::Type::Fulfill) { |
39 | 0 | dbgln_if(PROMISE_DEBUG, "run_reaction_job: Reaction type is Type::Fulfill, setting handler result to {}", argument); |
40 | 0 | handler_result = normal_completion(argument); |
41 | 0 | } |
42 | | // ii. Else, |
43 | 0 | else { |
44 | | // 1. Assert: type is Reject. |
45 | 0 | VERIFY(type == PromiseReaction::Type::Reject); |
46 | | |
47 | | // 2. Let handlerResult be ThrowCompletion(argument). |
48 | 0 | dbgln_if(PROMISE_DEBUG, "run_reaction_job: Reaction type is Type::Reject, throwing exception with argument {}", argument); |
49 | 0 | handler_result = throw_completion(argument); |
50 | 0 | } |
51 | 0 | } |
52 | | // e. Else, let handlerResult be Completion(HostCallJobCallback(handler, undefined, « argument »)). |
53 | 0 | else { |
54 | 0 | dbgln_if(PROMISE_DEBUG, "run_reaction_job: Calling handler callback {} @ {} with argument {}", handler->callback().class_name(), &handler->callback(), argument); |
55 | 0 | handler_result = vm.host_call_job_callback(*handler, js_undefined(), ReadonlySpan<Value> { &argument, 1 }); |
56 | 0 | } |
57 | | |
58 | | // f. If promiseCapability is undefined, then |
59 | 0 | if (promise_capability == nullptr) { |
60 | | // i. Assert: handlerResult is not an abrupt completion. |
61 | 0 | VERIFY(!handler_result.is_abrupt()); |
62 | | |
63 | | // ii. Return empty. |
64 | 0 | dbgln_if(PROMISE_DEBUG, "run_reaction_job: Reaction has no PromiseCapability, returning empty value"); |
65 | | // TODO: This can't return an empty value at the moment, because the implicit conversion to Completion would fail. |
66 | | // Change it back when this is using completions (`return normal_completion({})`) |
67 | 0 | return js_undefined(); |
68 | 0 | } |
69 | | |
70 | | // g. Assert: promiseCapability is a PromiseCapability Record. |
71 | | |
72 | | // h. If handlerResult is an abrupt completion, then |
73 | 0 | if (handler_result.is_abrupt()) { |
74 | | // i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »). |
75 | 0 | auto reject_function = promise_capability->reject(); |
76 | 0 | dbgln_if(PROMISE_DEBUG, "run_reaction_job: Calling PromiseCapability's reject function @ {}", reject_function.ptr()); |
77 | 0 | return call(vm, *reject_function, js_undefined(), *handler_result.value()); |
78 | 0 | } |
79 | | // i. Else, |
80 | 0 | else { |
81 | | // i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »). |
82 | 0 | auto resolve_function = promise_capability->resolve(); |
83 | 0 | dbgln_if(PROMISE_DEBUG, "[PromiseReactionJob]: Calling PromiseCapability's resolve function @ {}", resolve_function.ptr()); |
84 | 0 | return call(vm, *resolve_function, js_undefined(), *handler_result.value()); |
85 | 0 | } |
86 | 0 | } |
87 | | |
88 | | // 27.2.2.1 NewPromiseReactionJob ( reaction, argument ), https://tc39.es/ecma262/#sec-newpromisereactionjob |
89 | | PromiseJob create_promise_reaction_job(VM& vm, PromiseReaction& reaction, Value argument) |
90 | 0 | { |
91 | | // 1. Let job be a new Job Abstract Closure with no parameters that captures reaction and argument and performs the following steps when called: |
92 | | // See run_reaction_job for "the following steps". |
93 | 0 | auto job = create_heap_function(vm.heap(), [&vm, &reaction, argument] { |
94 | 0 | return run_reaction_job(vm, reaction, argument); |
95 | 0 | }); |
96 | | |
97 | | // 2. Let handlerRealm be null. |
98 | 0 | Realm* handler_realm { nullptr }; |
99 | | |
100 | | // 3. If reaction.[[Handler]] is not empty, then |
101 | 0 | auto handler = reaction.handler(); |
102 | 0 | if (handler) { |
103 | | // a. Let getHandlerRealmResult be Completion(GetFunctionRealm(reaction.[[Handler]].[[Callback]])). |
104 | 0 | auto get_handler_realm_result = get_function_realm(vm, handler->callback()); |
105 | | |
106 | | // b. If getHandlerRealmResult is a normal completion, set handlerRealm to getHandlerRealmResult.[[Value]]. |
107 | 0 | if (!get_handler_realm_result.is_throw_completion()) { |
108 | 0 | handler_realm = get_handler_realm_result.release_value(); |
109 | 0 | } else { |
110 | | // c. Else, set handlerRealm to the current Realm Record. |
111 | 0 | handler_realm = vm.current_realm(); |
112 | 0 | } |
113 | | |
114 | | // d. NOTE: handlerRealm is never null unless the handler is undefined. When the handler is a revoked Proxy and no ECMAScript code runs, handlerRealm is used to create error objects. |
115 | 0 | } |
116 | | |
117 | | // 4. Return the Record { [[Job]]: job, [[Realm]]: handlerRealm }. |
118 | 0 | return { job, handler_realm }; |
119 | 0 | } |
120 | | |
121 | | // 27.2.2.2 NewPromiseResolveThenableJob ( promiseToResolve, thenable, then ), https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob |
122 | | static ThrowCompletionOr<Value> run_resolve_thenable_job(VM& vm, Promise& promise_to_resolve, Value thenable, JobCallback& then) |
123 | 0 | { |
124 | | // a. Let resolvingFunctions be CreateResolvingFunctions(promiseToResolve). |
125 | 0 | auto [resolve_function, reject_function] = promise_to_resolve.create_resolving_functions(); |
126 | | |
127 | | // b. Let thenCallResult be Completion(HostCallJobCallback(then, thenable, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)). |
128 | 0 | dbgln_if(PROMISE_DEBUG, "run_resolve_thenable_job: Calling then job callback for thenable {}", &thenable); |
129 | 0 | AK::Array<Value, 2> arguments; |
130 | 0 | arguments[0] = Value(resolve_function); |
131 | 0 | arguments[1] = Value(reject_function); |
132 | 0 | auto then_call_result = vm.host_call_job_callback(then, thenable, arguments.span()); |
133 | | |
134 | | // c. If thenCallResult is an abrupt completion, then |
135 | 0 | if (then_call_result.is_error()) { |
136 | | // i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »). |
137 | 0 | dbgln_if(PROMISE_DEBUG, "run_resolve_thenable_job: then_call_result is an abrupt completion, calling reject function with value {}", *then_call_result.throw_completion().value()); |
138 | 0 | return call(vm, *reject_function, js_undefined(), *then_call_result.throw_completion().value()); |
139 | 0 | } |
140 | | |
141 | | // d. Return ? thenCallResult. |
142 | 0 | dbgln_if(PROMISE_DEBUG, "run_resolve_thenable_job: Returning then call result {}", then_call_result.value()); |
143 | 0 | return then_call_result; |
144 | 0 | } |
145 | | |
146 | | // 27.2.2.2 NewPromiseResolveThenableJob ( promiseToResolve, thenable, then ), https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob |
147 | | PromiseJob create_promise_resolve_thenable_job(VM& vm, Promise& promise_to_resolve, Value thenable, JS::NonnullGCPtr<JobCallback> then) |
148 | 0 | { |
149 | | // 2. Let getThenRealmResult be Completion(GetFunctionRealm(then.[[Callback]])). |
150 | 0 | auto get_then_realm_result = get_function_realm(vm, then->callback()); |
151 | |
|
152 | 0 | Realm* then_realm; |
153 | | |
154 | | // 3. If getThenRealmResult is a normal completion, let thenRealm be getThenRealmResult.[[Value]]. |
155 | 0 | if (!get_then_realm_result.is_throw_completion()) { |
156 | 0 | then_realm = get_then_realm_result.release_value(); |
157 | 0 | } else { |
158 | | // 4. Else, let thenRealm be the current Realm Record. |
159 | 0 | then_realm = vm.current_realm(); |
160 | 0 | } |
161 | | |
162 | | // 5. NOTE: thenRealm is never null. When then.[[Callback]] is a revoked Proxy and no code runs, thenRealm is used to create error objects. |
163 | 0 | VERIFY(then_realm); |
164 | | |
165 | | // 1. Let job be a new Job Abstract Closure with no parameters that captures promiseToResolve, thenable, and then and performs the following steps when called: |
166 | | // See run_resolve_thenable_job() for "the following steps". |
167 | 0 | auto job = create_heap_function(vm.heap(), [&vm, &promise_to_resolve, thenable, then]() mutable { |
168 | 0 | return run_resolve_thenable_job(vm, promise_to_resolve, thenable, then); |
169 | 0 | }); |
170 | | |
171 | | // 6. Return the Record { [[Job]]: job, [[Realm]]: thenRealm }. |
172 | 0 | return { job, then_realm }; |
173 | 0 | } |
174 | | |
175 | | } |