1 /*
2 * Copyright (c) 2023 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 "js_inspector.h"
16
17 #include <algorithm>
18
19 #include "napi/native_common.h"
20 #include "napi/native_node_api.h"
21
22 #include "base/log/log.h"
23 #include "base/memory/referenced.h"
24
25 namespace OHOS::Ace::Napi {
26 namespace {
27 constexpr size_t STR_BUFFER_SIZE = 1024;
28 constexpr uint8_t PARA_COUNT = 2;
29 } // namespace
30
GetObserver(napi_env env,napi_value thisVar)31 static ComponentObserver* GetObserver(napi_env env, napi_value thisVar)
32 {
33 ComponentObserver* observer = nullptr;
34 napi_unwrap(env, thisVar, (void**)&observer);
35 if (observer->thisVarRef_ == nullptr) {
36 observer->Initialize(env, thisVar);
37 }
38 return observer;
39 }
40
ParseArgs(napi_env & env,napi_callback_info & info,napi_value & thisVar,napi_value & cb,CalloutType & calloutType)41 static size_t ParseArgs(
42 napi_env& env, napi_callback_info& info, napi_value& thisVar, napi_value& cb, CalloutType& calloutType)
43 {
44 const size_t argNum = 2;
45 size_t argc = argNum;
46 napi_value argv[argNum] = { 0 };
47 void* data = nullptr;
48 napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
49 NAPI_ASSERT_BASE(env, argc > 0, "too few parameter", 0);
50
51 napi_valuetype napiType;
52 NAPI_CALL_BASE(env, napi_typeof(env, argv[0], &napiType), 0);
53 NAPI_ASSERT_BASE(env, napiType == napi_string, "parameter 1 should be string", 0);
54 char type[STR_BUFFER_SIZE] = { 0 };
55 size_t len = 0;
56 napi_get_value_string_utf8(env, argv[0], type, STR_BUFFER_SIZE, &len);
57 NAPI_ASSERT_BASE(env, len < STR_BUFFER_SIZE, "condition string too long", 0);
58 NAPI_ASSERT_BASE(
59 env, (strcmp("layout", type) == 0 || strcmp("draw", type) == 0), "type mismatch('layout' or 'draw')", 0);
60 if (strcmp("layout", type) == 0) {
61 calloutType = CalloutType::LAYOUTCALLOUT;
62 } else if (strcmp("draw", type) == 0) {
63 calloutType = CalloutType::DRAWCALLOUT;
64 } else {
65 calloutType = CalloutType::UNKNOW;
66 }
67
68 if (argc <= 1) {
69 return argc;
70 }
71
72 NAPI_CALL_BASE(env, napi_typeof(env, argv[1], &napiType), 0);
73 NAPI_ASSERT_BASE(env, napiType == napi_function, "type mismatch for parameter 2", 0);
74 cb = argv[1];
75 return argc;
76 }
77
callUserFunction(std::list<napi_ref> & cbList)78 void ComponentObserver::callUserFunction(std::list<napi_ref>& cbList)
79 {
80 for (auto& cbRef : cbList) {
81 napi_handle_scope scope = nullptr;
82 napi_open_handle_scope(env_, &scope);
83 if (scope == nullptr) {
84 return;
85 }
86 napi_value thisVal = nullptr;
87 napi_get_reference_value(env_, thisVarRef_, &thisVal);
88
89 napi_value cb = nullptr;
90 napi_get_reference_value(env_, cbRef, &cb);
91
92 napi_value resultArg = nullptr;
93 napi_value result = nullptr;
94 napi_call_function(env_, thisVal, cb, 1, &resultArg, &result);
95 napi_close_handle_scope(env_, scope);
96 }
97 }
98
FindCbList(napi_value cb,CalloutType calloutType)99 std::list<napi_ref>::iterator ComponentObserver::FindCbList(napi_value cb, CalloutType calloutType)
100 {
101 if (calloutType == CalloutType::LAYOUTCALLOUT) {
102 return std::find_if(cbLayoutList_.begin(), cbLayoutList_.end(), [env = env_, cb](const napi_ref& item) -> bool {
103 bool result = false;
104 napi_value refItem;
105 napi_get_reference_value(env, item, &refItem);
106 napi_strict_equals(env, refItem, cb, &result);
107 return result;
108 });
109 } else {
110 return std::find_if(cbDrawList_.begin(), cbDrawList_.end(), [env = env_, cb](const napi_ref& item) -> bool {
111 bool result = false;
112 napi_value refItem;
113 napi_get_reference_value(env, item, &refItem);
114 napi_strict_equals(env, refItem, cb, &result);
115 return result;
116 });
117 }
118 }
119
AddCallbackToList(napi_value cb,std::list<napi_ref> & cbList,CalloutType calloutType,napi_env env,napi_handle_scope scope)120 void ComponentObserver::AddCallbackToList(
121 napi_value cb, std::list<napi_ref>& cbList, CalloutType calloutType, napi_env env, napi_handle_scope scope)
122 {
123 auto iter = FindCbList(cb, calloutType);
124 if (iter != cbList.end()) {
125 napi_close_handle_scope(env, scope);
126 return;
127 }
128 napi_ref ref = nullptr;
129 napi_create_reference(env, cb, 1, &ref);
130 cbList.emplace_back(ref);
131 napi_close_handle_scope(env, scope);
132 }
133
DeleteCallbackFromList(size_t argc,std::list<napi_ref> & cbList,CalloutType calloutType,napi_value cb,napi_env env)134 void ComponentObserver::DeleteCallbackFromList(
135 size_t argc, std::list<napi_ref>& cbList, CalloutType calloutType, napi_value cb, napi_env env)
136 {
137 if (argc == 1) {
138 for (auto& item : cbList) {
139 napi_delete_reference(env_, item);
140 }
141 cbList.clear();
142 } else {
143 NAPI_ASSERT_RETURN_VOID(env, (argc == PARA_COUNT && cb != nullptr), "Invalid arguments");
144 auto iter = FindCbList(cb, calloutType);
145 if (iter != cbList.end()) {
146 napi_delete_reference(env_, *iter);
147 cbList.erase(iter);
148 }
149 }
150 }
151
FunctionOn(napi_env & env,napi_value result,const char * funName)152 void ComponentObserver::FunctionOn(napi_env& env, napi_value result, const char* funName)
153 {
154 napi_value funcValue = nullptr;
155 auto On = [](napi_env env, napi_callback_info info) -> napi_value {
156 auto jsEngine = EngineHelper::GetCurrentEngine();
157 if (!jsEngine) {
158 LOGE("get jsEngine failed");
159 return nullptr;
160 }
161
162 napi_handle_scope scope = nullptr;
163 napi_open_handle_scope(env, &scope);
164 if (scope == nullptr) {
165 return nullptr;
166 }
167 napi_value thisVar = nullptr;
168 napi_value cb = nullptr;
169 CalloutType calloutType = CalloutType::UNKNOW;
170 size_t argc = ParseArgs(env, info, thisVar, cb, calloutType);
171 NAPI_ASSERT(env, (argc == 2 && thisVar != nullptr && cb != nullptr), "Invalid arguments");
172
173 ComponentObserver* observer = GetObserver(env, thisVar);
174 if (!observer) {
175 LOGE("observer is null");
176 napi_close_handle_scope(env, scope);
177 return nullptr;
178 }
179
180 if (calloutType == CalloutType::LAYOUTCALLOUT) {
181 observer->AddCallbackToList(cb, observer->cbLayoutList_, calloutType, env, scope);
182 } else if (calloutType == CalloutType::DRAWCALLOUT) {
183 observer->AddCallbackToList(cb, observer->cbDrawList_, calloutType, env, scope);
184 }
185 return nullptr;
186 };
187 napi_create_function(env, funName, NAPI_AUTO_LENGTH, On, nullptr, &funcValue);
188 napi_set_named_property(env, result, funName, funcValue);
189 }
190
FunctionOff(napi_env & env,napi_value result,const char * funName)191 void ComponentObserver::FunctionOff(napi_env& env, napi_value result, const char* funName)
192 {
193 napi_value funcValue = nullptr;
194 auto Off = [](napi_env env, napi_callback_info info) -> napi_value {
195 LOGI("NAPI ComponentObserver off called");
196 napi_value thisVar = nullptr;
197 napi_value cb = nullptr;
198 CalloutType calloutType = CalloutType::UNKNOW;
199 size_t argc = ParseArgs(env, info, thisVar, cb, calloutType);
200 ComponentObserver* observer = GetObserver(env, thisVar);
201 if (!observer) {
202 LOGE("observer is null");
203 return nullptr;
204 }
205 if (calloutType == CalloutType::LAYOUTCALLOUT) {
206 LOGI("NAPI ComponentObserver Off called NapiLayoutCallback");
207 observer->DeleteCallbackFromList(argc, observer->cbLayoutList_, calloutType, cb, env);
208 } else if (calloutType == CalloutType::DRAWCALLOUT) {
209 LOGI("NAPI ComponentObserver Off called NapiDrawCallback");
210 observer->DeleteCallbackFromList(argc, observer->cbDrawList_, calloutType, cb, env);
211 }
212 return nullptr;
213 };
214
215 napi_create_function(env, funName, NAPI_AUTO_LENGTH, Off, nullptr, &funcValue);
216 napi_set_named_property(env, result, funName, funcValue);
217 }
218
NapiSerializer(napi_env & env,napi_value & result)219 void ComponentObserver::NapiSerializer(napi_env& env, napi_value& result)
220 {
221 napi_create_object(env, &result);
222 napi_handle_scope scope = nullptr;
223 napi_open_handle_scope(env, &scope);
224 if (scope == nullptr) {
225 return;
226 }
227
228 napi_value componentIdVal = nullptr;
229 napi_create_string_utf8(env, componentId_.c_str(), componentId_.size(), &componentIdVal);
230 napi_set_named_property(env, result, "componentId", componentIdVal);
231 napi_close_handle_scope(env, scope);
232
233 napi_wrap(
234 env, result, this,
235 [](napi_env env, void* data, void* hint) {
236 ComponentObserver* observer = static_cast<ComponentObserver*>(data);
237 if (observer != nullptr) {
238 delete observer;
239 }
240 },
241 nullptr, nullptr);
242
243 FunctionOn(env, result, "on");
244 FunctionOff(env, result, "off");
245 }
246
Initialize(napi_env env,napi_value thisVar)247 void ComponentObserver::Initialize(napi_env env, napi_value thisVar)
248 {
249 napi_handle_scope scope = nullptr;
250 napi_open_handle_scope(env, &scope);
251 if (scope == nullptr) {
252 return;
253 }
254 if (env_ == nullptr) {
255 env_ = env;
256 }
257 napi_create_reference(env, thisVar, 1, &thisVarRef_);
258 napi_close_handle_scope(env, scope);
259 }
260
JSCreateComponentObserver(napi_env env,napi_callback_info info)261 static napi_value JSCreateComponentObserver(napi_env env, napi_callback_info info)
262 {
263 LOGI("napi_value JSCreateComponentObserve");
264 /* Get arguments */
265 size_t argc = 1;
266 napi_value argv = nullptr;
267 napi_value thisVar = nullptr;
268 void* data = nullptr;
269 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &argv, &thisVar, &data));
270 NAPI_ASSERT(env, argc == 1, "requires 1 parameter");
271
272 /* Checkout arguments */
273 napi_valuetype type;
274 NAPI_CALL(env, napi_typeof(env, argv, &type));
275 NAPI_ASSERT(env, type == napi_string, "type mismatch");
276 char componentId[STR_BUFFER_SIZE] = { 0 };
277 size_t len = 0;
278 napi_get_value_string_utf8(env, argv, componentId, STR_BUFFER_SIZE, &len);
279 NAPI_ASSERT(env, len < STR_BUFFER_SIZE, "condition string too long");
280
281 /* construct object for query */
282 std::string componentIdStr(componentId, len);
283 ComponentObserver* observer = new ComponentObserver(componentIdStr);
284 napi_value result = nullptr;
285 observer->NapiSerializer(env, result);
286 auto layoutCallback = [observer]() { observer->callUserFunction(observer->cbLayoutList_); };
287 observer->layoutEvent_ = AceType::MakeRefPtr<InspectorEvent>(std::move(layoutCallback));
288
289 auto drawCallback = [observer]() { observer->callUserFunction(observer->cbDrawList_); };
290 observer->drawEvent_ = AceType::MakeRefPtr<InspectorEvent>(std::move(drawCallback));
291
292 auto jsEngine = EngineHelper::GetCurrentEngine();
293 if (!jsEngine) {
294 LOGE("get jsEngine failed");
295 return nullptr;
296 }
297
298 jsEngine->RegisterLayoutInspectorCallback(observer->layoutEvent_, observer->componentId_);
299 jsEngine->RegisterDrawInspectorCallback(observer->drawEvent_, observer->componentId_);
300 #if defined(PREVIEW)
301 layoutCallback();
302 drawCallback();
303 #endif
304 return result;
305 }
306
Export(napi_env env,napi_value exports)307 static napi_value Export(napi_env env, napi_value exports)
308 {
309 napi_property_descriptor properties[] = { DECLARE_NAPI_FUNCTION(
310 "createComponentObserver", JSCreateComponentObserver) };
311
312 NAPI_CALL(env, napi_define_properties(env, exports, sizeof(properties) / sizeof(properties[0]), properties));
313 return exports;
314 }
315
316 static napi_module inspector_module = {
317 .nm_version = 1,
318 .nm_flags = 0,
319 .nm_filename = nullptr,
320 .nm_register_func = Export,
321 .nm_modname = "arkui.inspector", // relative to the dynamic library's name
322 .nm_priv = ((void*)0),
323 .reserved = { 0 },
324 };
325
Register()326 extern "C" __attribute__((constructor)) void Register()
327 {
328 napi_module_register(&inspector_module);
329 }
330 } // namespace OHOS::Ace::Napi
331