1 /**
2 * Copyright (c) 2024-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 #include "plugins/ets/runtime/interop_js/js_proxy/js_proxy.h"
16 #include "plugins/ets/runtime/interop_js/js_refconvert_function.h"
17 #include "plugins/ets/runtime/interop_js/code_scopes.h"
18 #include "plugins/ets/runtime/types/ets_type.h"
19 namespace ark::ets::interop::js {
20
EtsLambdaProxyInvoke(napi_env env,napi_callback_info cbinfo)21 napi_value EtsLambdaProxyInvoke(napi_env env, napi_callback_info cbinfo)
22 {
23 auto coro = EtsCoroutine::GetCurrent();
24 auto ctx = InteropCtx::Current(coro);
25 if (ctx == nullptr) {
26 ThrowNoInteropContextException();
27 return nullptr;
28 }
29 INTEROP_CODE_SCOPE_JS(coro);
30
31 size_t argc;
32 napi_value athis;
33 void *data;
34 NAPI_CHECK_FATAL(napi_get_cb_info(env, cbinfo, &argc, nullptr, &athis, &data));
35 auto jsArgs = ctx->GetTempArgs<napi_value>(argc);
36 NAPI_CHECK_FATAL(napi_get_cb_info(env, cbinfo, &argc, jsArgs->data(), &athis, &data));
37
38 // Atomic with acquire order reason: load visibility after shared reference initialization
39 auto *sharedRef = AtomicLoad(static_cast<ets_proxy::SharedReference **>(data), std::memory_order_acquire);
40 ASSERT(sharedRef != nullptr);
41
42 ScopedManagedCodeThread managedScope(coro);
43 auto *etsThis = sharedRef->GetEtsObject();
44 ASSERT(etsThis != nullptr);
45 EtsMethod *method = etsThis->GetClass()->GetInstanceMethod(INVOKE_METHOD_NAME, nullptr);
46 method = method == nullptr ? etsThis->GetClass()->GetInstanceMethod(STD_CORE_FUNCTION_UNSAFECALL_METHOD, nullptr)
47 : method;
48 ASSERT(method != nullptr);
49
50 return CallETSInstance(coro, ctx, method->GetPandaMethod(), *jsArgs, etsThis);
51 }
52
WrapImpl(InteropCtx * ctx,EtsObject * obj)53 napi_value JSRefConvertFunction::WrapImpl(InteropCtx *ctx, EtsObject *obj)
54 {
55 auto coro = EtsCoroutine::GetCurrent();
56 ASSERT(ctx == InteropCtx::Current(coro));
57 auto env = ctx->GetJSEnv();
58
59 ASSERT(obj->GetClass() == klass_);
60
61 JSValue *jsValue;
62 {
63 NapiScope jsHandleScope(env);
64
65 ets_proxy::SharedReferenceStorage *storage = ctx->GetSharedRefStorage();
66 if (LIKELY(storage->HasReference(obj, env))) {
67 jsValue = JSValue::CreateRefValue(coro, ctx, storage->GetJsObject(obj, env), napi_function);
68 } else {
69 napi_value jsFn;
70 auto preInitCallback = [&env, &jsFn](ets_proxy::SharedReference **uninitializedRef) {
71 ASSERT(uninitializedRef != nullptr);
72 NAPI_CHECK_FATAL(napi_create_function(env, ark::ets::INVOKE_METHOD_NAME, NAPI_AUTO_LENGTH,
73 EtsLambdaProxyInvoke, uninitializedRef, &jsFn));
74 return jsFn;
75 };
76 ets_proxy::SharedReference *sharedRef = storage->CreateETSObjectRef(ctx, obj, jsFn, preInitCallback);
77 if (UNLIKELY(sharedRef == nullptr)) {
78 ASSERT(InteropCtx::SanityJSExceptionPending());
79 return nullptr;
80 }
81 jsValue = JSValue::CreateRefValue(coro, ctx, jsFn, napi_function);
82 }
83 }
84 if (UNLIKELY(jsValue == nullptr)) {
85 return nullptr;
86 }
87 return jsValue->GetRefValue(env);
88 }
89
UnwrapImpl(InteropCtx * ctx,napi_value jsFun)90 EtsObject *JSRefConvertFunction::UnwrapImpl(InteropCtx *ctx, napi_value jsFun)
91 {
92 // Check if object has SharedReference
93 ets_proxy::SharedReference *sharedRef = ctx->GetSharedRefStorage()->GetReference(ctx->GetJSEnv(), jsFun);
94 if (LIKELY(sharedRef != nullptr)) {
95 EtsObject *jsFunctionProxy = sharedRef->GetEtsObject();
96 return jsFunctionProxy;
97 }
98
99 return this->CreateJSFunctionProxy(ctx, jsFun);
100 }
101
LazyInitJsFunctionProxyWrapper(InteropCtx * ctx)102 void JSRefConvertFunction::LazyInitJsFunctionProxyWrapper(InteropCtx *ctx)
103 {
104 // register the function interface
105 auto etsClass = EtsClass::FromRuntimeClass(this->klass_->GetRuntimeClass());
106
107 // create a JSProxy wrapper for the function
108 this->jsFunctionProxyWrapper_ = js_proxy::JSProxy::CreateFunctionProxy(etsClass);
109 ctx->SetJsProxyInstance(etsClass, this->jsFunctionProxyWrapper_);
110 }
111
CreateJSFunctionProxy(InteropCtx * ctx,napi_value jsFun)112 EtsObject *JSRefConvertFunction::CreateJSFunctionProxy(InteropCtx *ctx, napi_value jsFun)
113 {
114 // lazy init the function proxy wrapper
115 this->LazyInitJsFunctionProxyWrapper(ctx);
116
117 // JS Function => ETS Function Object
118 ASSERT(this->jsFunctionProxyWrapper_ != nullptr);
119 auto *storage = ctx->GetSharedRefStorage();
120 ASSERT(storage->GetReference(ctx->GetJSEnv(), jsFun) == nullptr);
121
122 EtsObject *etsObject = EtsObject::Create(jsFunctionProxyWrapper_->GetProxyClass());
123 if (UNLIKELY(etsObject == nullptr)) {
124 ctx->ForwardEtsException(EtsCoroutine::GetCurrent());
125 return nullptr;
126 }
127
128 auto *sharedRef = storage->CreateJSObjectRefwithWrap(ctx, etsObject, jsFun);
129 if (UNLIKELY(sharedRef == nullptr)) {
130 ASSERT(InteropCtx::SanityJSExceptionPending());
131 return nullptr;
132 }
133 return sharedRef->GetEtsObject(); // fetch again after gc
134 }
135 } // namespace ark::ets::interop::js
136