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