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 <node_api.h>
17 #include "runtime/handle_scope-inl.h"
18 #include "runtime/include/mem/panda_smart_pointers.h"
19 #include "runtime/mem/refstorage/reference.h"
20 #include "plugins/ets/runtime/ets_vm.h"
21 #include "plugins/ets/runtime/ets_utils.h"
22 #include "plugins/ets/runtime/types/ets_method.h"
23 #include "plugins/ets/runtime/interop_js/js_job_queue.h"
24 #include "plugins/ets/runtime/interop_js/interop_common.h"
25 #include "plugins/ets/runtime/interop_js/interop_context.h"
26 #include "plugins/ets/runtime/interop_js/js_value.h"
27 #include "plugins/ets/runtime/types/ets_promise.h"
28 #include "plugins/ets/runtime/ets_handle_scope.h"
29 #include "plugins/ets/runtime/ets_handle.h"
30 #include "plugins/ets/runtime/interop_js/code_scopes.h"
31 #include "runtime/coroutines/stackful_coroutine.h"
32 #include "intrinsics.h"
33
34 namespace ark::ets::interop::js {
ThenCallback(napi_env env,napi_callback_info info)35 static napi_value ThenCallback(napi_env env, napi_callback_info info)
36 {
37 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
38 INTEROP_CODE_SCOPE_JS(coro, env);
39
40 JsJobQueue::JsCallback *jsCallback = nullptr;
41 [[maybe_unused]] napi_status status =
42 napi_get_cb_info(env, info, nullptr, nullptr, nullptr, reinterpret_cast<void **>(&jsCallback));
43 ASSERT(status == napi_ok);
44
45 jsCallback->Run();
46 Runtime::GetCurrent()->GetInternalAllocator()->Delete(jsCallback);
47
48 if (coro->HasPendingException()) {
49 napi_throw_error(env, nullptr, "EtsVM internal error");
50 }
51 napi_value undefined;
52 napi_get_undefined(env, &undefined);
53 return undefined;
54 }
55
Post(EtsObject * callback)56 void JsJobQueue::Post(EtsObject *callback)
57 {
58 auto postProc = [callback] {
59 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
60 napi_env env = InteropCtx::Current(coro)->GetJSEnv();
61 napi_deferred deferred;
62 napi_value undefined;
63 napi_value jsPromise;
64 napi_value thenFn;
65
66 napi_get_undefined(env, &undefined);
67 napi_status status = napi_create_promise(env, &deferred, &jsPromise);
68 if (status != napi_ok) {
69 InteropCtx::Fatal("Cannot allocate a Promise instance");
70 }
71 status = napi_get_named_property(env, jsPromise, "then", &thenFn);
72 ASSERT(status == napi_ok);
73 (void)status;
74
75 auto *jsCallback = JsCallback::Create(coro, callback);
76 napi_value thenCallback;
77 status = napi_create_function(env, nullptr, 0, ThenCallback, jsCallback, &thenCallback);
78 if (status != napi_ok) {
79 InteropCtx::Fatal("Cannot create a function");
80 }
81
82 napi_value thenPromise;
83 status = napi_call_function(env, jsPromise, thenFn, 1, &thenCallback, &thenPromise);
84 ASSERT(status == napi_ok);
85 (void)status;
86
87 napi_resolve_deferred(env, deferred, undefined);
88 };
89
90 auto *mainT = EtsCoroutine::GetCurrent()->GetPandaVM()->GetCoroutineManager()->GetMainThread();
91 Coroutine *mainCoro = Coroutine::CastFromThread(mainT);
92 if (Coroutine::GetCurrent() != mainCoro) {
93 // NOTE(konstanting, #I67QXC): figure out if we need to ExecuteOnThisContext() for OHOS
94 mainCoro->GetContext<StackfulCoroutineContext>()->ExecuteOnThisContext(
95 &postProc, EtsCoroutine::GetCurrent()->GetContext<StackfulCoroutineContext>());
96 } else {
97 postProc();
98 }
99 }
100
OnJsPromiseResolved(napi_env env,napi_callback_info info)101 static napi_value OnJsPromiseResolved(napi_env env, [[maybe_unused]] napi_callback_info info)
102 {
103 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
104 PandaEtsVM *vm = coro->GetPandaVM();
105 auto ctx = InteropCtx::Current(coro);
106 INTEROP_CODE_SCOPE_JS(coro, env);
107
108 mem::Reference *promiseRef = nullptr;
109 size_t argc = 1;
110 napi_value value;
111 napi_status status = napi_get_cb_info(env, info, &argc, &value, nullptr, reinterpret_cast<void **>(&promiseRef));
112 if (status != napi_ok) {
113 InteropCtx::Fatal("Cannot call napi_get_cb_info!");
114 }
115
116 EtsHandleScope hScope(coro);
117 EtsHandle<EtsPromise> promiseHandle(coro, EtsPromise::FromCoreType(vm->GetGlobalObjectStorage()->Get(promiseRef)));
118 vm->GetGlobalObjectStorage()->Remove(promiseRef);
119
120 auto jsval = JSValue::Create(coro, ctx, value);
121 ark::ets::intrinsics::EtsPromiseResolve(promiseHandle.GetPtr(), jsval->AsObject());
122
123 vm->GetCoroutineManager()->Schedule();
124
125 napi_value undefined;
126 napi_get_undefined(env, &undefined);
127 return undefined;
128 }
129
CreatePromiseLink(EtsObject * jsObject,EtsPromise * etsPromise)130 void JsJobQueue::CreatePromiseLink(EtsObject *jsObject, EtsPromise *etsPromise)
131 {
132 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
133 PandaEtsVM *vm = coro->GetPandaVM();
134 InteropCtx *ctx = InteropCtx::Current(coro);
135 napi_env env = ctx->GetJSEnv();
136 ets_proxy::SharedReferenceStorage *storage = ctx->GetSharedRefStorage();
137 napi_value jsPromise = storage->GetReference(jsObject)->GetJsObject(env);
138
139 napi_value thenFn;
140 napi_status status = napi_get_named_property(env, jsPromise, "then", &thenFn);
141 if (status != napi_ok) {
142 InteropCtx::Fatal("Cannot call then() from a JS promise");
143 }
144
145 mem::Reference *promiseRef = vm->GetGlobalObjectStorage()->Add(etsPromise, mem::Reference::ObjectType::GLOBAL);
146
147 // NOTE(konstanting, #I67QXC): OnJsPromiseRejected
148 napi_value thenCallback;
149 status = napi_create_function(env, nullptr, 0, OnJsPromiseResolved, promiseRef, &thenCallback);
150 if (status != napi_ok) {
151 InteropCtx::Fatal("Cannot create a function");
152 }
153
154 napi_value thenResult;
155 status = napi_call_function(env, jsPromise, thenFn, 1, &thenCallback, &thenResult);
156 if (status != napi_ok) {
157 InteropCtx::Fatal("Cannot call then() from a JS Promise");
158 }
159 }
160
CreateLink(EtsObject * source,EtsObject * target)161 void JsJobQueue::CreateLink(EtsObject *source, EtsObject *target)
162 {
163 auto addLinkProc = [&]() { CreatePromiseLink(source, EtsPromise::FromEtsObject(target)); };
164
165 auto *mainT = EtsCoroutine::GetCurrent()->GetPandaVM()->GetCoroutineManager()->GetMainThread();
166 Coroutine *mainCoro = Coroutine::CastFromThread(mainT);
167 if (Coroutine::GetCurrent() != mainCoro) {
168 // NOTE(konstanting, #I67QXC): figure out if we need to ExecuteOnThisContext() for OHOS
169 mainCoro->GetContext<StackfulCoroutineContext>()->ExecuteOnThisContext(
170 &addLinkProc, EtsCoroutine::GetCurrent()->GetContext<StackfulCoroutineContext>());
171 } else {
172 addLinkProc();
173 }
174 }
175
176 /* static */
Create(EtsCoroutine * coro,const EtsObject * callback)177 JsJobQueue::JsCallback *JsJobQueue::JsCallback::Create(EtsCoroutine *coro, const EtsObject *callback)
178 {
179 auto *refStorage = coro->GetPandaVM()->GetGlobalObjectStorage();
180 auto *jsCallbackRef = refStorage->Add(callback->GetCoreType(), mem::Reference::ObjectType::GLOBAL);
181 ASSERT(jsCallbackRef != nullptr);
182 auto *jsCallback = Runtime::GetCurrent()->GetInternalAllocator()->New<JsCallback>(jsCallbackRef);
183 return jsCallback;
184 }
185
Run()186 void JsJobQueue::JsCallback::Run()
187 {
188 auto *coro = EtsCoroutine::GetCurrent();
189 auto *refStorage = coro->GetPandaVM()->GetGlobalObjectStorage();
190 auto *callback = EtsObject::FromCoreType(refStorage->Get(jsCallbackRef_));
191 LambdaUtils::InvokeVoid(coro, callback);
192 }
193
~JsCallback()194 JsJobQueue::JsCallback::~JsCallback()
195 {
196 auto *refStorage = EtsCoroutine::GetCurrent()->GetPandaVM()->GetGlobalObjectStorage();
197 refStorage->Remove(jsCallbackRef_);
198 }
199
200 } // namespace ark::ets::interop::js
201