1/*
2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "ecmascript/js_promise.h"
17
18#include "ecmascript/builtins/builtins_promise_handler.h"
19#include "ecmascript/global_env.h"
20#include "ecmascript/interpreter/interpreter.h"
21#include "ecmascript/jobs/micro_job_queue.h"
22
23namespace panda::ecmascript {
24using BuiltinsPromiseHandler = builtins::BuiltinsPromiseHandler;
25
26JSHandle<ResolvingFunctionsRecord> JSPromise::CreateResolvingFunctions(JSThread *thread,
27                                                                       const JSHandle<JSPromise> &promise)
28{
29    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
30    // 1. Let alreadyResolved be a new Record { [[value]]: false }.
31    JSHandle<PromiseRecord> record = factory->NewPromiseRecord();
32    record->SetValue(thread, JSTaggedValue::False());
33
34    // 2. Let resolve be a new built-in function object as defined in Promise Resolve Functions (25.4.1.3.2).
35    JSHandle<JSPromiseReactionsFunction> resolve = factory->CreateJSPromiseReactionsFunction(
36        MethodIndex::BUILTINS_PROMISE_HANDLER_RESOLVE);
37    // 3. Set the [[Promise]] internal slot of resolve to promise.
38    resolve->SetPromise(thread, promise);
39    // 4. Set the [[AlreadyResolved]] internal slot of resolve to alreadyResolved.
40    resolve->SetAlreadyResolved(thread, record);
41    // 5. Let reject be a new built-in function object as defined in Promise Reject Functions (25.4.1.3.1).
42    JSHandle<JSPromiseReactionsFunction> reject = factory->CreateJSPromiseReactionsFunction(
43        MethodIndex::BUILTINS_PROMISE_HANDLER_REJECT);
44    // 6. Set the [[Promise]] internal slot of reject to promise.
45    reject->SetPromise(thread, promise);
46    // 7. Set the [[AlreadyResolved]] internal slot of reject to alreadyResolved.
47    reject->SetAlreadyResolved(thread, record);
48    // 8. Return a new Record { [[Resolve]]: resolve, [[Reject]]: reject }.
49    JSHandle<ResolvingFunctionsRecord> reactions = factory->NewResolvingFunctionsRecord();
50    reactions->SetResolveFunction(thread, resolve.GetTaggedValue());
51    reactions->SetRejectFunction(thread, reject.GetTaggedValue());
52    return reactions;
53}
54
55JSTaggedValue JSPromise::FulfillPromise(JSThread *thread, const JSHandle<JSPromise> &promise,
56                                        const JSHandle<JSTaggedValue> &value)
57{
58    const GlobalEnvConstants *globalConst = thread->GlobalConstants();
59    // 1. Assert: the value of promise's [[PromiseState]] internal slot is "pending".
60    ASSERT_PRINT(promise->GetPromiseState() == PromiseState::PENDING, "FulfillPromise: state must be pending");
61    // 2. Let reactions be the value of promise's [[PromiseFulfillReactions]] internal slot.
62    JSHandle<TaggedQueue> reactions(thread, promise->GetPromiseFulfillReactions());
63    // 3. Set the value of promise's [[PromiseResult]] internal slot to value.
64    promise->SetPromiseResult(thread, value);
65    // 4. Set the value of promise's [[PromiseFulfillReactions]] internal slot to undefined.
66    promise->SetPromiseFulfillReactions(thread, globalConst->GetHandledUndefined(), SKIP_BARRIER);
67    // 5. Set the value of promise's [[PromiseRejectReactions]] internal slot to undefined.
68    promise->SetPromiseRejectReactions(thread, globalConst->GetHandledUndefined(), SKIP_BARRIER);
69    // 6. Set the value of promise's [[PromiseState]] internal slot to "fulfilled".
70    promise->SetPromiseState(PromiseState::FULFILLED);
71    // 7. Return TriggerPromiseReactions(reactions, reason).
72    return TriggerPromiseReactions(thread, reactions, value);
73}
74
75JSHandle<PromiseCapability> JSPromise::NewPromiseCapability(JSThread *thread, const JSHandle<JSTaggedValue> &obj)
76{
77    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
78    // 1. If IsConstructor(C) is false, throw a TypeError exception.
79    if (!obj->IsConstructor()) {
80        THROW_TYPE_ERROR_AND_RETURN(thread, "NewPromiseCapability: obj is not constructor!",
81                                    factory->NewPromiseCapability());
82    }
83    // 2. NOTE C is assumed to be a constructor function that supports the parameter conventions of the Promise
84    //    constructor (see 25.4.3.1).
85    // 3. Let promiseCapability be a new PromiseCapability { [[Promise]]: undefined, [[Resolve]]: undefined,
86    //    [[Reject]]: undefined }.
87    JSHandle<PromiseCapability> promiseCapability = factory->NewPromiseCapability();
88    // 4. Let executor be a new built-in function object as defined in GetCapabilitiesExecutor Functions
89    //    (25.4.1.5.1).
90    JSHandle<JSPromiseExecutorFunction> executor = factory->CreateJSPromiseExecutorFunction();
91    // 5. Set the [[Capability]] internal slot of executor to promiseCapability.
92    executor->SetCapability(thread, promiseCapability.GetTaggedValue());
93    // 6. Let promise be Construct(C, «executor»).
94    // 7. ReturnIfAbrupt(promise).
95    JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
96    EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, obj, undefined, undefined, 1);
97    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, factory->NewPromiseCapability());
98    info->SetCallArg(executor.GetTaggedValue());
99    JSTaggedValue result = JSFunction::Construct(info);
100    JSHandle<JSPromise> promise(thread, result);
101    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, factory->NewPromiseCapability());
102    // 8. If IsCallable(promiseCapability.[[Resolve]]) is false, throw a TypeError exception.
103    if (!promiseCapability->GetResolve().IsCallable()) {
104        THROW_TYPE_ERROR_AND_RETURN(thread, "NewPromiseCapability: resolve is not a callable function!",
105                                    factory->NewPromiseCapability());
106    }
107    // 9. If IsCallable(promiseCapability.[[Reject]]) is false, throw a TypeError exception.
108    if (!promiseCapability->GetReject().IsCallable()) {
109        THROW_TYPE_ERROR_AND_RETURN(thread, "NewPromiseCapability: reject is not a callable function!",
110                                    factory->NewPromiseCapability());
111    }
112    // 10. Set promiseCapability.[[Promise]] to promise.
113    promiseCapability->SetPromise(thread, promise);
114    // 11. Return promiseCapability.
115    return promiseCapability;
116}
117
118bool JSPromise::IsPromise(const JSHandle<JSTaggedValue> &value)
119{
120    // 1. If Type(x) is not Object, return false.
121    if (!value->IsECMAObject()) {
122        return false;
123    }
124    // 2. If x does not have a [[PromiseState]] internal slot, return false.
125    if (!value->IsJSPromise()) {
126        return false;
127    }
128    // 3. Return true
129    return true;
130}
131
132JSTaggedValue JSPromise::RejectPromise(JSThread *thread, const JSHandle<JSPromise> &promise,
133                                       const JSHandle<JSTaggedValue> &reason)
134{
135    const GlobalEnvConstants *globalConst = thread->GlobalConstants();
136    // 1. Assert: the value of promise's [[PromiseState]] internal slot is "pending".
137    ASSERT_PRINT(promise->GetPromiseState() == PromiseState::PENDING, "RejectPromise: state must be pending");
138    // 2. Let reactions be the value of promise's [[PromiseRejectReactions]] internal slot.
139    JSHandle<TaggedQueue> reactions(thread, TaggedQueue::Cast(promise->GetPromiseRejectReactions().GetTaggedObject()));
140    // 3. Set the value of promise's [[PromiseResult]] internal slot to reason.
141    promise->SetPromiseResult(thread, reason);
142    // 4. Set the value of promise's [[PromiseFulfillReactions]] internal slot to undefined.
143    promise->SetPromiseFulfillReactions(thread, globalConst->GetHandledUndefined(), SKIP_BARRIER);
144    // 5. Set the value of promise's [[PromiseRejectReactions]] internal slot to undefined.
145    promise->SetPromiseRejectReactions(thread, globalConst->GetHandledUndefined(), SKIP_BARRIER);
146    // 6. Set the value of promise's [[PromiseState]] internal slot to "rejected".
147    promise->SetPromiseState(PromiseState::REJECTED);
148    // 7. When a promise is rejected without any handlers, it is called with its operation argument set to "reject".
149    if (!promise->GetPromiseIsHandled()) {
150        thread->GetCurrentEcmaContext()->PromiseRejectionTracker(promise, reason, PromiseRejectionEvent::REJECT);
151    }
152    // 8. Return TriggerPromiseReactions(reactions, reason).
153    return TriggerPromiseReactions(thread, reactions, reason);
154}
155
156JSTaggedValue JSPromise::TriggerPromiseReactions(JSThread *thread, const JSHandle<TaggedQueue> &reactions,
157                                                 const JSHandle<JSTaggedValue> &argument)
158{
159    // 1. Repeat for each reaction in reactions, in original insertion order
160    // a. Perform EnqueueJob("PromiseJobs", PromiseReactionJob, «reaction, argument»).
161    JSHandle<job::MicroJobQueue> job = thread->GetCurrentEcmaContext()->GetMicroJobQueue();
162    JSHandle<GlobalEnv> globalEnv = thread->GetEcmaVM()->GetGlobalEnv();
163    const GlobalEnvConstants *globalConst = thread->GlobalConstants();
164    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
165    JSHandle<JSFunction> promiseReactionsJob(globalEnv->GetPromiseReactionJob());
166    JSMutableHandle<PromiseReaction> reaction(thread, JSTaggedValue::Undefined());
167    while (!reactions->Empty()) {
168        reaction.Update(reactions->Pop(thread));
169        JSHandle<TaggedArray> arguments = factory->NewTaggedArray(2);  // 2 means the length of new array
170        arguments->Set(thread, 0, reaction);
171        arguments->Set(thread, 1, argument);
172        job::MicroJobQueue::EnqueueJob(thread, job, job::QueueType::QUEUE_PROMISE, promiseReactionsJob, arguments);
173    }
174    // 2. Return undefined.
175    return globalConst->GetUndefined();
176}
177
178JSHandle<JSTaggedValue> JSPromise::IfThrowGetThrowValue(JSThread *thread)
179{
180    return JSHandle<JSTaggedValue>(thread, thread->GetException());
181}
182}  // namespace panda::ecmascript
183