• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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