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