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