1 /**
2 * Copyright (c) 2021-2025 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 "plugins/ets/runtime/interop_js/js_job_queue.h"
17 #include <node_api.h>
18 #include "plugins/ets/runtime/ets_handle.h"
19 #include "plugins/ets/runtime/ets_handle_scope.h"
20 #include "plugins/ets/runtime/ets_vm.h"
21 #include "plugins/ets/runtime/ets_utils.h"
22 #include "plugins/ets/runtime/interop_js/code_scopes.h"
23 #include "plugins/ets/runtime/interop_js/interop_common.h"
24 #include "plugins/ets/runtime/interop_js/interop_context.h"
25 #include "plugins/ets/runtime/interop_js/js_convert.h"
26 #include "plugins/ets/runtime/interop_js/js_value.h"
27 #include "plugins/ets/runtime/types/ets_method.h"
28 #include "plugins/ets/runtime/types/ets_object.h"
29 #include "plugins/ets/runtime/types/ets_promise.h"
30 #include "runtime/coroutines/stackful_coroutine.h"
31 #include "runtime/handle_scope-inl.h"
32 #include "runtime/include/mem/panda_smart_pointers.h"
33 #include "runtime/mem/refstorage/reference.h"
34 #include "intrinsics.h"
35
36 namespace ark::ets::interop::js {
ThenCallback(napi_env env,napi_callback_info info)37 static napi_value ThenCallback(napi_env env, napi_callback_info info)
38 {
39 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
40 INTEROP_CODE_SCOPE_JS(coro);
41
42 JsJobQueue::JsCallback *jsCallback = nullptr;
43 [[maybe_unused]] napi_status status =
44 napi_get_cb_info(env, info, nullptr, nullptr, nullptr, reinterpret_cast<void **>(&jsCallback));
45 ASSERT(status == napi_ok);
46
47 bool hasException = false;
48 {
49 ScopedManagedCodeThread managedScope(coro);
50 jsCallback->Run();
51 Runtime::GetCurrent()->GetInternalAllocator()->Delete(jsCallback);
52 hasException = coro->HasPendingException();
53 }
54 if (hasException) {
55 napi_throw_error(env, nullptr, "EtsVM internal error");
56 }
57 napi_value undefined;
58 napi_get_undefined(env, &undefined);
59 return undefined;
60 }
61
Post(EtsObject * callback)62 void JsJobQueue::Post(EtsObject *callback)
63 {
64 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
65 INTEROP_CODE_SCOPE_ETS(coro);
66
67 auto *ctx = InteropCtx::Current(coro);
68 if (ctx == nullptr) {
69 ThrowNoInteropContextException();
70 return;
71 }
72 napi_env env = ctx->GetJSEnv();
73
74 napi_deferred deferred;
75 napi_value undefined;
76 napi_value jsPromise;
77 napi_value thenFn;
78
79 napi_get_undefined(env, &undefined);
80 napi_status status = napi_create_promise(env, &deferred, &jsPromise);
81 if (status != napi_ok) {
82 InteropCtx::Fatal("Cannot allocate a Promise instance");
83 }
84 status = napi_get_named_property(env, jsPromise, "then", &thenFn);
85 ASSERT(status == napi_ok);
86 (void)status;
87
88 auto *jsCallback = JsCallback::Create(coro, callback);
89 napi_value thenCallback;
90 status = napi_create_function(env, nullptr, 0, ThenCallback, jsCallback, &thenCallback);
91 if (status != napi_ok) {
92 InteropCtx::Fatal("Cannot create a function");
93 }
94
95 napi_value thenPromise;
96 status = napi_call_function(env, jsPromise, thenFn, 1, &thenCallback, &thenPromise);
97 ASSERT(status == napi_ok);
98 (void)status;
99
100 napi_resolve_deferred(env, deferred, undefined);
101 }
102
OnJsPromiseCompleted(napi_env env,napi_callback_info info,bool isResolved)103 static napi_value OnJsPromiseCompleted(napi_env env, [[maybe_unused]] napi_callback_info info, bool isResolved)
104 {
105 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
106 PandaEtsVM *vm = coro->GetPandaVM();
107 auto ctx = InteropCtx::Current(coro);
108 INTEROP_CODE_SCOPE_JS(coro);
109
110 mem::Reference *promiseRef = nullptr;
111 size_t argc = 1;
112 napi_value value;
113 napi_status status = napi_get_cb_info(env, info, &argc, &value, nullptr, reinterpret_cast<void **>(&promiseRef));
114 if (status != napi_ok) {
115 InteropCtx::Fatal("Cannot call napi_get_cb_info!");
116 }
117 ASSERT(promiseRef != nullptr);
118
119 {
120 ScopedManagedCodeThread managedScope(coro);
121 EtsHandleScope hScope(coro);
122 EtsHandle<EtsPromise> promiseHandle(coro,
123 EtsPromise::FromCoreType(vm->GetGlobalObjectStorage()->Get(promiseRef)));
124 vm->GetGlobalObjectStorage()->Remove(promiseRef);
125
126 if (isResolved) {
127 auto *jsval = JSValue::Create(coro, ctx, value);
128 ASSERT(jsval != nullptr);
129 ark::ets::intrinsics::EtsPromiseResolve(promiseHandle.GetPtr(), jsval->AsObject(),
130 ark::ets::ToEtsBoolean(false));
131 } else {
132 auto refconv = JSRefConvertResolve<true>(ctx, ctx->GetErrorClass());
133 ASSERT(refconv != nullptr);
134 bool isInstanceof = false;
135 EtsObject *error = nullptr;
136 NAPI_CHECK_FATAL(napi_is_error(env, value, &isInstanceof));
137 if (!isInstanceof) {
138 auto res = JSConvertESError::UnwrapImpl(ctx, env, value);
139 if (LIKELY(res.has_value())) {
140 error = AsEtsObject(res.value());
141 }
142 } else {
143 error = refconv->Unwrap(ctx, value);
144 }
145 ASSERT(error != nullptr);
146 ark::ets::intrinsics::EtsPromiseReject(promiseHandle.GetPtr(), error, ark::ets::ToEtsBoolean(false));
147 }
148 }
149
150 napi_value undefined;
151 napi_get_undefined(env, &undefined);
152 return undefined;
153 }
154
OnJsPromiseResolved(napi_env env,napi_callback_info info)155 static napi_value OnJsPromiseResolved(napi_env env, [[maybe_unused]] napi_callback_info info)
156 {
157 return OnJsPromiseCompleted(env, info, true);
158 }
159
OnJsPromiseRejected(napi_env env,napi_callback_info info)160 static napi_value OnJsPromiseRejected(napi_env env, [[maybe_unused]] napi_callback_info info)
161 {
162 return OnJsPromiseCompleted(env, info, false);
163 }
164
CreatePromiseLink(EtsObject * jsObject,EtsPromise * etsPromise)165 void JsJobQueue::CreatePromiseLink(EtsObject *jsObject, EtsPromise *etsPromise)
166 {
167 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
168 ASSERT(coro != nullptr);
169 PandaEtsVM *vm = coro->GetPandaVM();
170 InteropCtx *ctx = InteropCtx::Current(coro);
171 if (ctx == nullptr) {
172 ThrowNoInteropContextException();
173 return;
174 }
175 napi_env env = ctx->GetJSEnv();
176 ets_proxy::SharedReferenceStorage *storage = ctx->GetSharedRefStorage();
177 napi_value jsPromise = storage->GetJsObject(jsObject, env);
178
179 napi_value thenFn;
180 napi_status status = napi_get_named_property(env, jsPromise, "then", &thenFn);
181 if (status != napi_ok) {
182 InteropCtx::Fatal("Cannot call then() from a JS promise");
183 }
184
185 mem::Reference *promiseRef = vm->GetGlobalObjectStorage()->Add(etsPromise, mem::Reference::ObjectType::GLOBAL);
186 ScopedNativeCodeThread nativeScope(coro);
187 std::array<napi_value, 2U> thenCallback {};
188
189 status = napi_create_function(env, nullptr, 0, OnJsPromiseResolved, promiseRef, &thenCallback[0]);
190 if (status != napi_ok) {
191 InteropCtx::Fatal("Cannot create a function");
192 }
193
194 status = napi_create_function(env, nullptr, 0, OnJsPromiseRejected, promiseRef, &thenCallback[1]);
195 if (status != napi_ok) {
196 InteropCtx::Fatal("Cannot create a function");
197 }
198
199 napi_value thenResult;
200 status = napi_call_function(env, jsPromise, thenFn, 2U, thenCallback.data(), &thenResult);
201 if (status != napi_ok) {
202 InteropCtx::Fatal("Cannot call then() from a JS Promise");
203 }
204 }
205
CreateLink(EtsObject * source,EtsObject * target)206 void JsJobQueue::CreateLink(EtsObject *source, EtsObject *target)
207 {
208 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
209 INTEROP_CODE_SCOPE_ETS(coro);
210 CreatePromiseLink(source, EtsPromise::FromEtsObject(target));
211 }
212
213 /* static */
Create(EtsCoroutine * coro,const EtsObject * callback)214 JsJobQueue::JsCallback *JsJobQueue::JsCallback::Create(EtsCoroutine *coro, const EtsObject *callback)
215 {
216 auto *refStorage = coro->GetPandaVM()->GetGlobalObjectStorage();
217 auto *jsCallbackRef = refStorage->Add(callback->GetCoreType(), mem::Reference::ObjectType::GLOBAL);
218 ASSERT(jsCallbackRef != nullptr);
219 auto *jsCallback = Runtime::GetCurrent()->GetInternalAllocator()->New<JsCallback>(jsCallbackRef);
220 return jsCallback;
221 }
222
Run()223 void JsJobQueue::JsCallback::Run()
224 {
225 auto *coro = EtsCoroutine::GetCurrent();
226 ASSERT(coro != nullptr);
227 auto *refStorage = coro->GetPandaVM()->GetGlobalObjectStorage();
228 auto *callback = EtsObject::FromCoreType(refStorage->Get(jsCallbackRef_));
229 LambdaUtils::InvokeVoid(coro, callback);
230 }
231
~JsCallback()232 JsJobQueue::JsCallback::~JsCallback()
233 {
234 auto *coro = EtsCoroutine::GetCurrent();
235 ASSERT(coro != nullptr);
236 auto *refStorage = coro->GetPandaVM()->GetGlobalObjectStorage();
237 refStorage->Remove(jsCallbackRef_);
238 }
239
240 } // namespace ark::ets::interop::js
241