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