• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 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 <set>
17 #include <string>
18 #include <unistd.h>
19 #include <uv.h>
20 #include "nlohmann/json.hpp"
21 #include "common_utils.h"
22 #include "callback_code_napi.h"
23 
24 namespace OHOS::perftest {
25     using namespace nlohmann;
26     using namespace std;
27 
28     static constexpr size_t NAPI_MAX_BUF_LEN = 1024;
29 
30 
LockRelease(string errorMessage)31     void ThreadLock::LockRelease(string errorMessage)
32     {
33         errMsg = errorMessage;
34         ready = true;
35         condition.notify_all();
36     }
37 
38     /** Cached reference of callbackId and JsCallbackFunction, key is the unique id.*/
39     static map<string, napi_ref> g_jsRefs;
40     static size_t g_incJsCbId = 0;
41 
Get()42     CallbackCodeNapi &CallbackCodeNapi::Get()
43     {
44         static CallbackCodeNapi instance;
45         return instance;
46     }
47 
GetFunctionFromParam(napi_env env,napi_value jsParam,json & param,string funcName)48     static void GetFunctionFromParam(napi_env env, napi_value jsParam, json& param, string funcName)
49     {
50         if (param.contains(funcName)) {
51             LOG_D("%{public}s has been defined", funcName.c_str());
52             return;
53         }
54         bool funcExisted;
55         NAPI_CALL_RETURN_VOID(env, napi_has_named_property(env, jsParam, funcName.c_str(), &funcExisted));
56         if (funcExisted) {
57             napi_value funcValue = nullptr;
58             NAPI_CALL_RETURN_VOID(env, napi_get_named_property(env, jsParam, funcName.c_str(), &funcValue));
59             napi_valuetype valueType = napi_undefined;
60             NAPI_CALL_RETURN_VOID(env, napi_typeof(env, funcValue, &valueType));
61             if (valueType != napi_function) {
62                 LOG_E("GetFunctionFromParam failed, invalid callback function argument");
63                 return;
64             }
65             const auto jsCbId = string("js_callback#") + to_string(++g_incJsCbId);
66             // hold the const to avoid it be recycled, it's needed in performing callback
67             napi_ref ref = nullptr;
68             NAPI_CALL_RETURN_VOID(env, napi_create_reference(env, funcValue, 1, &ref));
69             LOG_I("CbId = %{public}s, CbRef = %{public}p", jsCbId.c_str(), ref);
70             g_jsRefs.insert({ jsCbId, ref }); // store jsCbId instread of the function body
71             param[funcName] = jsCbId;
72         }
73     }
74 
PreprocessCallback(napi_env env,ApiCallInfo & call,napi_value jsThis,napi_value jsParam,ApiCallErr & err)75     void CallbackCodeNapi::PreprocessCallback(napi_env env, ApiCallInfo &call, napi_value jsThis,
76                                               napi_value jsParam, ApiCallErr &err)
77     {
78         DCHECK(env != nullptr);
79         DCHECK(jsThis != nullptr);
80         napi_valuetype paramType = napi_undefined;
81         NAPI_CALL_RETURN_VOID(env, napi_typeof(env, jsParam, &paramType));
82         if (paramType != napi_object) {
83             err = ApiCallErr(ERR_INVALID_INPUT, "PreprocessCallback failed, Invalid type of param");
84             return;
85         }
86         auto &paramList = call.paramList_;
87         if (paramList.size() < 1 || paramList.at(0).type() != detail::value_t::object) {
88             err = ApiCallErr(ERR_INVALID_INPUT, "Missing PerfTestStrategy argument");
89             return;
90         }
91         GetFunctionFromParam(env, jsParam, paramList.at(0), ACTION_CODE_FUNC_NAME);
92         GetFunctionFromParam(env, jsParam, paramList.at(0), RESET_CODE_FUNC_NAME);
93     }
94 
InitCallbackContext(napi_env env,const ApiCallInfo & in,ApiReplyInfo & out,shared_ptr<CodeCallbackContext> ctx)95     void CallbackCodeNapi::InitCallbackContext(napi_env env, const ApiCallInfo &in, ApiReplyInfo &out,
96                                                shared_ptr<CodeCallbackContext> ctx)
97     {
98         LOG_I("Handler api callback: %{public}s", in.apiId_.c_str());
99         DCHECK(env != nullptr);
100         DCHECK(in.paramList_.size() == TWO);
101         DCHECK(in.paramList_.at(INDEX_ZERO).type() == detail::value_t::string);
102         DCHECK(in.paramList_.at(INDEX_ONE).type() == detail::value_t::number_unsigned);
103         auto callbackId = in.paramList_.at(INDEX_ZERO).get<string>();
104         auto timeout = in.paramList_.at(INDEX_ONE).get<uint32_t>();
105         LOG_I("Begin to callback function: callback=%{public}s", callbackId.c_str());
106         auto findCallback = g_jsRefs.find(callbackId);
107         if (findCallback == g_jsRefs.end()) {
108             out.exception_ = ApiCallErr(ERR_CALLBACK_FAILED, "JsCallbackFunction is not referenced: " + callbackId);
109             LOG_E("%{public}s", out.exception_.message_.c_str());
110             return;
111         }
112         auto cb = [](napi_env env, napi_callback_info info) -> napi_value {
113             LOG_D("ExecuteCallback jscallback return start");
114             size_t argc_in = 1;
115             napi_value argv_in[1] = {nullptr};
116             void* param_in = nullptr;
117             NAPI_CALL_BASE(env, napi_get_cb_info(env, info, &argc_in, argv_in, nullptr, &param_in), nullptr);
118             ThreadLock* threadLock = reinterpret_cast<ThreadLock*>(param_in);
119             if (threadLock == nullptr) {
120                 LOG_E("ExecuteCallback get threadLock failed");
121                 return nullptr;
122             }
123             NAPI_CALL_BASE(env, napi_get_value_bool(env, argv_in[0], &threadLock->res), nullptr);
124             threadLock->LockRelease("");
125             LOG_I("ExecuteCallback jscallback return end, res = %{public}d", threadLock->res);
126             return nullptr;
127         };
128         ctx->env = env;
129         ctx->callbackId = callbackId;
130         ctx->callbackRef = findCallback->second;
131         ctx->timeout = timeout;
132         ctx->cb = cb;
133     }
134 
BindPromiseCallbacks(napi_env env,napi_value promise,ThreadLock * threadLock)135     void CallbackCodeNapi::BindPromiseCallbacks(napi_env env, napi_value promise, ThreadLock* threadLock)
136     {
137         bool is_promise = false;
138         NAPI_CALL_RETURN_VOID(env, napi_is_promise(env, promise, &is_promise));
139         if (!is_promise) {
140             return;
141         }
142         napi_value catch_property;
143         NAPI_CALL_RETURN_VOID(env, napi_get_named_property(env, promise, "catch", &catch_property));
144         auto catch_handle = [](napi_env env, napi_callback_info info) -> napi_value {
145             size_t argc_in = 1;
146             napi_value argv_in[1] = {nullptr};
147             void* param_in = nullptr;
148             NAPI_CALL_BASE(env, napi_get_cb_info(env, info, &argc_in, argv_in, nullptr, &param_in), nullptr);
149             napi_value error_str;
150             napi_coerce_to_string(env, argv_in[0], &error_str);
151             size_t buf_size = 0;
152             char buf[NAPI_MAX_BUF_LEN] = {0};
153             napi_get_value_string_utf8(env, error_str, buf, NAPI_MAX_BUF_LEN, &buf_size);
154             ThreadLock* threadLock = reinterpret_cast<ThreadLock*>(param_in);
155             if (threadLock == nullptr) {
156                 LOG_E("ExecuteCallback get threadLock failed");
157                 return nullptr;
158             }
159             threadLock->LockRelease("Exception raised during call js_cb_function: " + string(buf, buf_size));
160             return nullptr;
161         };
162         napi_value catch_callback;
163         NAPI_CALL_RETURN_VOID(env, napi_create_function(env, "CatchCallack", NAPI_AUTO_LENGTH, catch_handle,
164                                                         threadLock, &catch_callback));
165         NAPI_CALL_RETURN_VOID(env, napi_call_function(env, promise, catch_property, 1, &catch_callback, nullptr));
166     }
167 
WaitforCallbackFinish(napi_env env,shared_ptr<CodeCallbackContext> context,ThreadLock * threadLock,ApiReplyInfo & out)168     void CallbackCodeNapi::WaitforCallbackFinish(napi_env env, shared_ptr<CodeCallbackContext> context,
169                                                  ThreadLock* threadLock, ApiReplyInfo &out)
170     {
171         unique_lock<mutex> lock(threadLock->mutex);
172         const auto timeout = chrono::milliseconds(context->timeout);
173         if (!threadLock->condition.wait_for(lock, timeout, [&threadLock] { return threadLock->ready; })) {
174             LOG_E("Jscallback call timeout, has been waiting for %{public}d ms", context->timeout);
175             out.exception_ = ApiCallErr(ERR_CALLBACK_FAILED, "Code execution has been timeout.");
176             return;
177         }
178         LOG_I("Jscallback call has finished, res = %{public}d", threadLock->res);
179         if (!threadLock->errMsg.empty()) {
180             out.exception_ = ApiCallErr(ERR_CALLBACK_FAILED, threadLock->errMsg);
181             LOG_E("%{public}s", out.exception_.message_.c_str());
182         } else if (!threadLock->res) {
183             out.exception_ = ApiCallErr(ERR_CALLBACK_FAILED, "Callback execution return false");
184         }
185         delete threadLock;
186         threadLock = nullptr;
187     }
188 
189     /**Handle api callback from server end.*/
ExecuteCallback(napi_env env,const ApiCallInfo & in,ApiReplyInfo & out)190     void CallbackCodeNapi::ExecuteCallback(napi_env env, const ApiCallInfo &in, ApiReplyInfo &out)
191     {
192         shared_ptr<CodeCallbackContext> context = make_shared<CodeCallbackContext>();
193         InitCallbackContext(env, in, out, context);
194         if (out.exception_.code_ != NO_ERROR) {
195             LOG_W("InitCallbackContext failed, cannot perform callback");
196             return;
197         }
198         ThreadLock* threadLock = new (nothrow) ThreadLock();
199         auto task = [context, threadLock, this]() {
200             napi_value argv[1] = { nullptr };
201             napi_value jsCallback = nullptr;
202             napi_get_reference_value(context->env, context->callbackRef, &jsCallback);
203             napi_create_function(context->env, "Complete", NAPI_AUTO_LENGTH, context->cb, threadLock, &argv[0]);
204             napi_value promise;
205             napi_call_function(context->env, nullptr, jsCallback, 1, argv, &promise);
206             // accessing callback from js-caller, do check and warn
207             auto hasError = false;
208             napi_is_exception_pending(context->env, &hasError);
209             if (hasError) {
210                 napi_value exception;
211                 napi_get_and_clear_last_exception(context->env, &exception);
212                 napi_value error_str;
213                 napi_coerce_to_string(context->env, exception, &error_str);
214                 size_t buf_size = 0;
215                 char buf[NAPI_MAX_BUF_LEN] = {0};
216                 napi_get_value_string_utf8(context->env, error_str, buf, NAPI_MAX_BUF_LEN, &buf_size);
217                 if (threadLock == nullptr) {
218                     return;
219                 }
220                 threadLock->LockRelease("Exception raised during call js_cb_function: " + string(buf, buf_size));
221                 return;
222             }
223             this->BindPromiseCallbacks(context->env, promise, threadLock);
224         };
225         if (napi_send_event(env, task, napi_eprio_immediate) != napi_status::napi_ok) {
226             LOG_E("Exception raised during call js_cb_function!");
227         }
228         WaitforCallbackFinish(env, context, threadLock, out);
229     }
230 
DestroyCallbacks(napi_env env,const ApiCallInfo & in,ApiReplyInfo & out)231     void CallbackCodeNapi::DestroyCallbacks(napi_env env, const ApiCallInfo &in, ApiReplyInfo &out)
232     {
233         DCHECK(in.paramList_.size() == ONE);
234         DCHECK(in.paramList_.at(INDEX_ZERO).type() == detail::value_t::array);
235         for (auto& callbackId : in.paramList_.at(INDEX_ZERO)) {
236             auto findCallback = g_jsRefs.find(callbackId);
237             if (findCallback == g_jsRefs.end()) {
238                 continue;
239             }
240             napi_delete_reference(env, findCallback->second);
241             g_jsRefs.erase(callbackId);
242         }
243     }
244 
HandleCallbackEvent(napi_env env,const ApiCallInfo & in,ApiReplyInfo & out)245     void CallbackCodeNapi::HandleCallbackEvent(napi_env env, const ApiCallInfo &in, ApiReplyInfo &out)
246     {
247         if (in.apiId_ == "PerfTest.run") {
248             ExecuteCallback(env, in, out);
249         } else if (in.apiId_ == "PerfTest.destroy") {
250             DestroyCallbacks(env, in, out);
251         } else {
252             out.exception_ = ApiCallErr(ERR_INTERNAL, "Api dose not support callback: " + in.apiId_);
253             LOG_E("%{public}s", out.exception_.message_.c_str());
254         }
255     }
256 }
257