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