summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibJS/Runtime/Completion.cpp
blob: 3b1db9a70b29e25ccfa1eaa4a5368c7446aab097 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/*
 * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
 * Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/TypeCasts.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/Promise.h>
#include <LibJS/Runtime/PromiseCapability.h>
#include <LibJS/Runtime/PromiseConstructor.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/Value.h>

namespace JS {

Completion::Completion(ThrowCompletionOr<Value> const& throw_completion_or_value)
{
    if (throw_completion_or_value.is_throw_completion()) {
        m_type = Type::Throw;
        m_value = throw_completion_or_value.throw_completion().value();
    } else {
        m_type = Type::Normal;
        m_value = throw_completion_or_value.value();
    }
}

// 6.2.3.1 Await, https://tc39.es/ecma262/#await
ThrowCompletionOr<Value> await(VM& vm, Value value)
{
    auto& realm = *vm.current_realm();

    // 1. Let asyncContext be the running execution context.
    // NOTE: This is not needed, as we don't suspend anything.

    // 2. Let promise be ? PromiseResolve(%Promise%, value).
    auto* promise_object = TRY(promise_resolve(vm, *realm.intrinsics().promise_constructor(), value));

    Optional<bool> success;
    Value result;
    // 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called:
    auto fulfilled_closure = [&success, &result](VM& vm) -> ThrowCompletionOr<Value> {
        // a. Let prevContext be the running execution context.
        // b. Suspend prevContext.
        // FIXME: We don't have this concept yet.

        // NOTE: Since we don't support context suspension, we exfiltrate the result to await()'s scope instead
        success = true;
        result = vm.argument(0);

        // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
        // NOTE: This is not done, because we're not suspending anything (see above).

        // d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it.
        // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
        // FIXME: We don't have this concept yet.

        // f. Return undefined.
        return js_undefined();
    };

    // 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
    auto on_fulfilled = NativeFunction::create(realm, move(fulfilled_closure), 1, "");

    // 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the following steps when called:
    auto rejected_closure = [&success, &result](VM& vm) -> ThrowCompletionOr<Value> {
        // a. Let prevContext be the running execution context.
        // b. Suspend prevContext.
        // FIXME: We don't have this concept yet.

        // NOTE: Since we don't support context suspension, we exfiltrate the result to await()'s scope instead
        success = false;
        result = vm.argument(0);

        // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
        // NOTE: This is not done, because we're not suspending anything (see above).

        // d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it.
        // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
        // FIXME: We don't have this concept yet.

        // f. Return undefined.
        return js_undefined();
    };

    // 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
    auto on_rejected = NativeFunction::create(realm, move(rejected_closure), 1, "");

    // 7. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
    auto* promise = verify_cast<Promise>(promise_object);
    promise->perform_then(on_fulfilled, on_rejected, {});

    // FIXME: Since we don't support context suspension, we attempt to "wait" for the promise to resolve
    //        by letting the event loop spin until our promise is no longer pending, and then synchronously
    //        running all queued promise jobs.
    // Note: This is not used by LibJS itself, and is performed for the embedder (i.e. LibWeb).
    if (auto* custom_data = vm.custom_data()) {
        custom_data->spin_event_loop_until([&] {
            return success.has_value();
        });
    }

    // 8. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
    // NOTE: Since we don't push any EC, this step is not performed.

    // 9. Set the code evaluation state of asyncContext such that when evaluation is resumed with a Completion Record completion, the following steps of the algorithm that invoked Await will be performed, with completion available.
    // 10. Return NormalCompletion(unused).
    // 11. NOTE: This returns to the evaluation of the operation that had most previously resumed evaluation of asyncContext.

    vm.run_queued_promise_jobs();

    // Make sure that the promise _actually_ resolved.
    // Note that this is checked down the chain (result.is_empty()) anyway, but let's make the source of the issue more clear.
    VERIFY(success.has_value());

    if (success.value())
        return result;
    return throw_completion(result);
}

// 6.2.3.3 ThrowCompletion ( value ), https://tc39.es/ecma262/#sec-throwcompletion
Completion throw_completion(Value value)
{
    return { Completion::Type::Throw, value, {} };
}

}