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