Coverage Report

Created: 2025-11-16 07:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/WebIDL/Promise.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <AK/Function.h>
8
#include <AK/TypeCasts.h>
9
#include <LibJS/Heap/HeapFunction.h>
10
#include <LibJS/Runtime/PromiseCapability.h>
11
#include <LibJS/Runtime/PromiseConstructor.h>
12
#include <LibJS/Runtime/Realm.h>
13
#include <LibWeb/Bindings/ExceptionOrUtils.h>
14
#include <LibWeb/Bindings/HostDefined.h>
15
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
16
#include <LibWeb/WebIDL/Promise.h>
17
18
namespace Web::WebIDL {
19
20
// https://webidl.spec.whatwg.org/#a-new-promise
21
JS::NonnullGCPtr<Promise> create_promise(JS::Realm& realm)
22
0
{
23
0
    auto& vm = realm.vm();
24
25
    // 1. Let constructor be realm.[[Intrinsics]].[[%Promise%]].
26
0
    auto constructor = realm.intrinsics().promise_constructor();
27
28
    // Return ? NewPromiseCapability(constructor).
29
    // NOTE: When called with %Promise%, NewPromiseCapability can't throw.
30
0
    return MUST(JS::new_promise_capability(vm, constructor));
31
0
}
32
33
// https://webidl.spec.whatwg.org/#a-promise-resolved-with
34
JS::NonnullGCPtr<Promise> create_resolved_promise(JS::Realm& realm, JS::Value value)
35
0
{
36
0
    auto& vm = realm.vm();
37
38
    // 1. Let value be the result of converting x to an ECMAScript value.
39
40
    // 2. Let constructor be realm.[[Intrinsics]].[[%Promise%]].
41
0
    auto constructor = realm.intrinsics().promise_constructor();
42
43
    // 3. Let promiseCapability be ? NewPromiseCapability(constructor).
44
    // NOTE: When called with %Promise%, NewPromiseCapability can't throw.
45
0
    auto promise_capability = MUST(JS::new_promise_capability(vm, constructor));
46
47
    // 4. Perform ! Call(promiseCapability.[[Resolve]], undefined, « value »).
48
0
    MUST(JS::call(vm, *promise_capability->resolve(), JS::js_undefined(), value));
49
50
    // 5. Return promiseCapability.
51
0
    return promise_capability;
52
0
}
53
54
// https://webidl.spec.whatwg.org/#a-promise-rejected-with
55
JS::NonnullGCPtr<Promise> create_rejected_promise(JS::Realm& realm, JS::Value reason)
56
0
{
57
0
    auto& vm = realm.vm();
58
59
    // 1. Let constructor be realm.[[Intrinsics]].[[%Promise%]].
60
0
    auto constructor = realm.intrinsics().promise_constructor();
61
62
    // 2. Let promiseCapability be ? NewPromiseCapability(constructor).
63
    // NOTE: When called with %Promise%, NewPromiseCapability can't throw.
64
0
    auto promise_capability = MUST(JS::new_promise_capability(vm, constructor));
65
66
    // 3. Perform ! Call(promiseCapability.[[Reject]], undefined, « r »).
67
0
    MUST(JS::call(vm, *promise_capability->reject(), JS::js_undefined(), reason));
68
69
    // 4. Return promiseCapability.
70
0
    return promise_capability;
71
0
}
72
73
// https://webidl.spec.whatwg.org/#resolve
74
void resolve_promise(JS::Realm& realm, Promise const& promise, JS::Value value)
75
0
{
76
0
    auto& vm = realm.vm();
77
78
    // 1. If x is not given, then let it be the undefined value.
79
    // NOTE: This is done via the default argument.
80
81
    // 2. Let value be the result of converting x to an ECMAScript value.
82
    // 3. Perform ! Call(p.[[Resolve]], undefined, « value »).
83
0
    MUST(JS::call(vm, *promise.resolve(), JS::js_undefined(), value));
84
0
}
85
86
// https://webidl.spec.whatwg.org/#reject
87
void reject_promise(JS::Realm& realm, Promise const& promise, JS::Value reason)
88
0
{
89
0
    auto& vm = realm.vm();
90
91
    // 1. Perform ! Call(p.[[Reject]], undefined, « r »).
92
0
    MUST(JS::call(vm, *promise.reject(), JS::js_undefined(), reason));
93
0
}
94
95
// https://webidl.spec.whatwg.org/#dfn-perform-steps-once-promise-is-settled
96
JS::NonnullGCPtr<JS::Promise> react_to_promise(Promise const& promise, JS::GCPtr<ReactionSteps> on_fulfilled_callback, JS::GCPtr<ReactionSteps> on_rejected_callback)
97
0
{
98
0
    auto& realm = promise.promise()->shape().realm();
99
0
    auto& vm = realm.vm();
100
101
    // 1. Let onFulfilledSteps be the following steps given argument V:
102
0
    auto on_fulfilled_steps = [on_fulfilled_callback = move(on_fulfilled_callback)](JS::VM& vm) -> JS::ThrowCompletionOr<JS::Value> {
103
        // 1. Let value be the result of converting V to an IDL value of type T.
104
0
        auto value = vm.argument(0);
105
106
        // 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.
107
0
        auto result = on_fulfilled_callback
108
0
            ? TRY(Bindings::throw_dom_exception_if_needed(vm, [&] { return on_fulfilled_callback->function()(value); }))
109
0
            : value;
110
111
        // 3. Return result, converted to an ECMAScript value.
112
0
        return result;
113
0
    };
114
115
    // 2. Let onFulfilled be CreateBuiltinFunction(onFulfilledSteps, « »):
116
0
    auto on_fulfilled = JS::NativeFunction::create(realm, move(on_fulfilled_steps), 1, "");
117
118
    // 3. Let onRejectedSteps be the following steps given argument R:
119
0
    auto on_rejected_steps = [&realm, on_rejected_callback = move(on_rejected_callback)](JS::VM& vm) -> JS::ThrowCompletionOr<JS::Value> {
120
        // 1. Let reason be the result of converting R to an IDL value of type any.
121
0
        auto reason = vm.argument(0);
122
123
        // 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.
124
0
        auto result = on_rejected_callback
125
0
            ? TRY(Bindings::throw_dom_exception_if_needed(vm, [&] { return on_rejected_callback->function()(reason); }))
126
0
            : WebIDL::create_rejected_promise(realm, reason)->promise();
127
128
        // 3. Return result, converted to an ECMAScript value.
129
0
        return result;
130
0
    };
131
132
    // 4. Let onRejected be CreateBuiltinFunction(onRejectedSteps, « »):
133
0
    auto on_rejected = JS::NativeFunction::create(realm, move(on_rejected_steps), 1, "");
134
135
    // 5. Let constructor be promise.[[Promise]].[[Realm]].[[Intrinsics]].[[%Promise%]].
136
0
    auto constructor = realm.intrinsics().promise_constructor();
137
138
    // 6. Let newCapability be ? NewPromiseCapability(constructor).
139
    // NOTE: When called with %Promise%, NewPromiseCapability can't throw.
140
0
    auto new_capability = MUST(JS::new_promise_capability(vm, constructor));
141
142
    // 7. Return PerformPromiseThen(promise.[[Promise]], onFulfilled, onRejected, newCapability).
143
0
    auto promise_object = verify_cast<JS::Promise>(promise.promise().ptr());
144
0
    auto value = promise_object->perform_then(on_fulfilled, on_rejected, new_capability);
145
0
    return verify_cast<JS::Promise>(value.as_object());
146
0
}
147
148
// https://webidl.spec.whatwg.org/#upon-fulfillment
149
JS::NonnullGCPtr<JS::Promise> upon_fulfillment(Promise const& promise, JS::NonnullGCPtr<ReactionSteps> steps)
150
0
{
151
    // 1. Return the result of reacting to promise:
152
0
    return react_to_promise(promise,
153
        // - If promise was fulfilled with value v, then:
154
        // 1. Perform steps with v.
155
0
        steps,
156
0
        {});
157
0
}
158
159
// https://webidl.spec.whatwg.org/#upon-rejection
160
JS::NonnullGCPtr<JS::Promise> upon_rejection(Promise const& promise, JS::NonnullGCPtr<ReactionSteps> steps)
161
0
{
162
    // 1. Return the result of reacting to promise:
163
0
    return react_to_promise(promise, {},
164
        // - If promise was rejected with reason r, then:
165
        // 1. Perform steps with r.
166
0
        steps);
167
0
}
168
169
// https://webidl.spec.whatwg.org/#mark-a-promise-as-handled
170
void mark_promise_as_handled(Promise const& promise)
171
0
{
172
    // To mark as handled a Promise<T> promise, set promise.[[Promise]].[[PromiseIsHandled]] to true.
173
0
    auto promise_object = verify_cast<JS::Promise>(promise.promise().ptr());
174
0
    promise_object->set_is_handled();
175
0
}
176
177
struct WaitForAllResults : JS::Cell {
178
    JS_CELL(WaitForAllResults, JS::Cell);
179
    JS_DECLARE_ALLOCATOR(WaitForAllResults);
180
181
    WaitForAllResults(JS::NonnullGCPtr<JS::HeapFunction<void(Vector<JS::Value> const&)>> s, size_t t)
182
0
        : success_steps(s)
183
0
        , total(t)
184
0
    {
185
        // 8. Let result be a list containing total null values.
186
0
        result.ensure_capacity(total);
187
0
        for (size_t i = 0; i < total; ++i)
188
0
            result.unchecked_append(JS::js_null());
189
0
    }
190
191
    virtual void visit_edges(JS::Cell::Visitor& visitor) override
192
0
    {
193
0
        Base::visit_edges(visitor);
194
0
        visitor.visit(success_steps);
195
0
        visitor.visit(result);
196
0
    }
197
198
    JS::NonnullGCPtr<JS::HeapFunction<void(Vector<JS::Value> const&)>> success_steps;
199
    Vector<JS::Value> result;
200
    size_t total = 0;
201
    size_t fulfilled_count = 0;
202
};
203
204
JS_DEFINE_ALLOCATOR(WaitForAllResults);
205
206
// https://webidl.spec.whatwg.org/#wait-for-all
207
void wait_for_all(JS::Realm& realm, Vector<JS::NonnullGCPtr<Promise>> const& promises, Function<void(Vector<JS::Value> const&)> success_steps, Function<void(JS::Value)> failure_steps)
208
0
{
209
    // FIXME: Fix spec typo, fullfilled --> fulfilled
210
    // 1. Let fullfilledCount be 0.
211
    // Handled later in WaitForAllResults
212
213
    // 2. Let rejected be false.
214
0
    auto rejected = false;
215
216
    // 3. Let rejectionHandlerSteps be the following steps given arg:
217
0
    auto rejection_handler_steps = [rejected, failure_steps = JS::create_heap_function(realm.heap(), move(failure_steps))](JS::VM& vm) mutable -> JS::ThrowCompletionOr<JS::Value> {
218
        // 1. If rejected is true, abort these steps.
219
0
        if (rejected)
220
0
            return JS::js_undefined();
221
222
        // 2. Set rejected to true.
223
0
        rejected = true;
224
225
        // 3. Perform failureSteps given arg.
226
0
        failure_steps->function()(vm.argument(0));
227
228
0
        return JS::js_undefined();
229
0
    };
230
231
    // 4. Let rejectionHandler be CreateBuiltinFunction(rejectionHandlerSteps, « »):
232
0
    auto rejection_handler = JS::NativeFunction::create(realm, move(rejection_handler_steps), 1, "");
233
234
    // 5. Let total be promises’s size.
235
0
    auto total = promises.size();
236
237
    // 6. If total is 0, then:
238
0
    if (total == 0) {
239
        // 1. Queue a microtask to perform successSteps given « ».
240
0
        HTML::queue_a_microtask(nullptr, JS::create_heap_function(realm.heap(), [success_steps = JS::create_heap_function(realm.heap(), move(success_steps))] {
241
0
            success_steps->function()({});
242
0
        }));
243
244
        // 2. Return.
245
0
        return;
246
0
    }
247
248
    // 7. Let index be 0.
249
0
    auto index = 0;
250
251
    // 8. Let result be a list containing total null values.
252
    // Handled in WaitForAllResults
253
254
0
    auto results = realm.heap().allocate<WaitForAllResults>(realm, JS::create_heap_function(realm.heap(), move(success_steps)), total);
255
256
    // 9. For each promise of promises:
257
0
    for (auto const& promise : promises) {
258
        // 1. Let promiseIndex be index.
259
0
        auto promise_index = index;
260
261
        // FIXME: This should be fulfillmentHandlerSteps
262
        // 2. Let fulfillmentHandler be the following steps given arg:
263
0
        auto fulfillment_handler_steps = [results, promise_index](JS::VM& vm) -> JS::ThrowCompletionOr<JS::Value> {
264
0
            auto arg = vm.argument(0);
265
266
            // 1. Set result[promiseIndex] to arg.
267
0
            results->result[promise_index] = arg;
268
269
            // 2. Set fullfilledCount to fullfilledCount + 1.
270
0
            ++results->fulfilled_count;
271
272
            // 3. If fullfilledCount equals total, then perform successSteps given result.
273
0
            if (results->fulfilled_count == results->total)
274
0
                results->success_steps->function()(results->result);
275
276
0
            return JS::js_undefined();
277
0
        };
278
279
        // 3. Let fulfillmentHandler be CreateBuiltinFunction(fulfillmentHandler, « »):
280
0
        auto fulfillment_handler = JS::NativeFunction::create(realm, move(fulfillment_handler_steps), 1, "");
281
282
        // 4. Perform PerformPromiseThen(promise, fulfillmentHandler, rejectionHandler).
283
0
        static_cast<JS::Promise&>(*promise->promise()).perform_then(fulfillment_handler, rejection_handler, nullptr);
284
285
        // 5. Set index to index + 1.
286
0
        ++index;
287
0
    }
288
0
}
289
290
JS::NonnullGCPtr<JS::Promise> create_rejected_promise_from_exception(JS::Realm& realm, Exception exception)
291
0
{
292
0
    auto throw_completion = Bindings::dom_exception_to_throw_completion(realm.vm(), move(exception));
293
0
    auto promise_capability = WebIDL::create_rejected_promise(realm, *throw_completion.value());
294
0
    return JS::NonnullGCPtr { verify_cast<JS::Promise>(*promise_capability->promise().ptr()) };
295
0
}
296
297
}