1 /*
2 * Copyright (c) 2025 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 <ani.h>
16 #include <string>
17 #include <unistd.h>
18 #include "base/log/log_wrapper.h"
19 #include "base/memory/ace_type.h"
20 #include "bridge/arkts_frontend/arkts_frontend.h"
21 #include "core/components_ng/base/inspector.h"
22 #include "core/pipeline_ng/pipeline_context.h"
23
24 namespace {
25 const char LAYOUT_TYPE[] = "layout";
26 const char DRAW_TYPE[] = "draw";
27 const char ANI_INSPECTOR_NS[] = "L@ohos/arkui/inspector/inspector;";
28 const char ANI_COMPONENT_OBSERVER_CLS[] = "L@ohos/arkui/inspector/inspector/ComponentObserverImpl;";
29 const char KOALA_INSPECTOR_CLS[] = "L@koalaui/arkts-arkui/generated/arkts/ohos/arkui/inspector/Inspector;";
30 const char KOALA_COMPONENT_CLS[] = "L@koalaui/arkts-arkui/generated/arkts/ohos/arkui/inspector/ComponentObserver;";
31 } // namespace
32
33 namespace OHOS::Ace {
34 class ComponentObserver {
35 public:
ComponentObserver(std::string key)36 explicit ComponentObserver(std::string key): id_(key) {}
37
getFronted()38 static RefPtr<ArktsFrontend> getFronted()
39 {
40 auto context = NG::PipelineContext::GetCurrentContextSafely();
41 if (context == nullptr) {
42 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani can not get current context.");
43 return nullptr;
44 }
45 auto frontend = context->GetFrontend();
46 if (frontend == nullptr) {
47 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani can not get current frontend.");
48 return nullptr;
49 }
50 auto arkTsFrontend = AceType::DynamicCast<ArktsFrontend>(frontend);
51 if (frontend == nullptr) {
52 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani can not convert to arkts frontend.");
53 return nullptr;
54 }
55 return arkTsFrontend;
56 }
57
FindCbList(ani_ref & cb,const std::string & eventType,ani_env * env)58 std::list<ani_ref>::iterator FindCbList(ani_ref& cb, const std::string& eventType, ani_env* env)
59 {
60 if (strcmp(LAYOUT_TYPE, eventType.c_str()) == 0) {
61 return std::find_if(cbLayoutList_.begin(), cbLayoutList_.end(), [env, cb](const ani_ref& item) -> bool {
62 ani_boolean rs;
63 env->Reference_StrictEquals(cb, item, &rs);
64 return rs == ANI_TRUE;
65 });
66 } else {
67 return std::find_if(cbDrawList_.begin(), cbDrawList_.end(), [env, cb](const ani_ref& item) -> bool {
68 ani_boolean rs;
69 env->Reference_StrictEquals(cb, item, &rs);
70 return rs == ANI_TRUE;
71 });
72 }
73 }
74
AddCallbackToList(std::list<ani_ref> & fnList,ani_ref & cb,const std::string & eventType,ani_env * env)75 void AddCallbackToList(std::list<ani_ref>& fnList, ani_ref& cb, const std::string& eventType, ani_env* env)
76 {
77 auto iter = FindCbList(cb, eventType, env);
78 if (iter != fnList.end()) {
79 return;
80 }
81 TAG_LOGI(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani add %{public}s call back on %{public}s",
82 eventType.c_str(), id_.c_str());
83 fnList.emplace_back(cb);
84 }
85
RemoveCallbackToList(std::list<ani_ref> & fnList,ani_ref & cb,const std::string & eventType,ani_env * env)86 void RemoveCallbackToList(std::list<ani_ref>& fnList, ani_ref& cb, const std::string& eventType, ani_env* env)
87 {
88 if (cb == nullptr) {
89 TAG_LOGW(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani start to clear all %{public}s callback list",
90 eventType.c_str());
91 for (auto& ref : fnList) {
92 env->GlobalReference_Delete(ref);
93 }
94 fnList.clear();
95 } else {
96 auto iter = FindCbList(cb, eventType, env);
97 if (iter != fnList.end()) {
98 auto& deleteRef = *iter;
99 fnList.erase(iter);
100 env->GlobalReference_Delete(deleteRef);
101 }
102 }
103 if (fnList.empty()) {
104 auto arkTsFrontend = ComponentObserver::getFronted();
105 if (arkTsFrontend == nullptr) {
106 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani Can not convert to arkts frontend.");
107 return;
108 }
109 if (strcmp(LAYOUT_TYPE, eventType.c_str()) == 0) {
110 arkTsFrontend->UnregisterLayoutInspectorCallback(id_);
111 } else {
112 arkTsFrontend->UnregisterDrawInspectorCallback(id_);
113 }
114 }
115 }
116
CallUserFunction(ani_env * env,std::list<ani_ref> & cbList)117 void CallUserFunction(ani_env* env, std::list<ani_ref>& cbList)
118 {
119 std::vector<ani_ref> vec;
120 ani_ref fnReturnVal;
121 for (auto& cb : cbList) {
122 TAG_LOGD(AceLogTag::ACE_LAYOUT_INSPECTOR,
123 "inspector-ani start to call user function for component %{public}s", id_.c_str());
124 env->FunctionalObject_Call(reinterpret_cast<ani_fn_object>(cb), vec.size(), vec.data(), &fnReturnVal);
125 }
126 }
127
GetCbListByType(const std::string & eventType)128 std::list<ani_ref>& GetCbListByType(const std::string& eventType)
129 {
130 if (strcmp(LAYOUT_TYPE, eventType.c_str()) == 0) {
131 return cbLayoutList_;
132 }
133 return cbDrawList_;
134 }
135
GetInspectorFuncByType(const std::string & eventType)136 RefPtr<InspectorEvent> GetInspectorFuncByType(const std::string& eventType)
137 {
138 if (strcmp(LAYOUT_TYPE, eventType.c_str()) == 0) {
139 return layoutEvent_;
140 }
141 return drawEvent_;
142 }
143
SetInspectorFuncByType(const std::string & eventType,const RefPtr<InspectorEvent> & fun)144 void SetInspectorFuncByType(const std::string& eventType, const RefPtr<InspectorEvent>& fun)
145 {
146 if (strcmp(LAYOUT_TYPE, eventType.c_str()) == 0) {
147 layoutEvent_ = fun;
148 } else {
149 drawEvent_ = fun;
150 }
151 }
152 private:
153 std::string id_;
154 std::list<ani_ref> cbLayoutList_;
155 std::list<ani_ref> cbDrawList_;
156 RefPtr<InspectorEvent> layoutEvent_;
157 RefPtr<InspectorEvent> drawEvent_;
158 };
159
Unwrapp(ani_env * env,ani_object object)160 static ComponentObserver* Unwrapp(ani_env *env, ani_object object)
161 {
162 ani_long nativeAddr;
163 if (ANI_OK != env->Object_GetFieldByName_Long(object, "nativeComponentObserver", &nativeAddr)) {
164 return nullptr;
165 }
166 return reinterpret_cast<ComponentObserver *>(nativeAddr);
167 }
168
ANIUtils_ANIStringToStdString(ani_env * env,ani_string ani_str,std::string & str)169 ani_status ANIUtils_ANIStringToStdString(ani_env *env, ani_string ani_str, std::string& str)
170 {
171 ani_size strSize;
172 ani_status status = env->String_GetUTF8Size(ani_str, &strSize);
173 if (status != ANI_OK) {
174 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani String_GetUTF8Size failed %{public}d.", status);
175 return status;
176 }
177
178 std::vector<char> buffer(strSize + 1); // +1 for null terminator
179 char* utf8Buffer = buffer.data();
180
181 ani_size bytes_written = 0;
182 status = env->String_GetUTF8(ani_str, utf8Buffer, buffer.size(), &bytes_written);
183 if (status != ANI_OK) {
184 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani String_GetUTF8 failed %{public}d.", status);
185 return status;
186 }
187
188 utf8Buffer[bytes_written] = '\0';
189 str = std::string(utf8Buffer);
190 return status;
191 }
192
On(ani_env * env,ani_object object,ani_string type,ani_fn_object fnObj)193 static void On(ani_env *env, ani_object object, ani_string type, ani_fn_object fnObj)
194 {
195 if (fnObj == nullptr) {
196 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani callback is undefined.");
197 return;
198 }
199 std::string typeStr;
200 ANIUtils_ANIStringToStdString(env, type, typeStr);
201 if (strcmp(LAYOUT_TYPE, typeStr.c_str()) != 0 && strcmp(DRAW_TYPE, typeStr.c_str()) != 0) {
202 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani method on not support event type %{public}s",
203 typeStr.c_str());
204 return;
205 }
206
207 auto *observer = Unwrapp(env, object);
208 if (observer == nullptr) {
209 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani context is null.");
210 return;
211 }
212 ani_ref fnObjGlobalRef = nullptr;
213 env->GlobalReference_Create(reinterpret_cast<ani_ref>(fnObj), &fnObjGlobalRef);
214 observer->AddCallbackToList(observer->GetCbListByType(typeStr), fnObjGlobalRef, typeStr, env);
215 }
216
Off(ani_env * env,ani_object object,ani_string type,ani_fn_object fnObj)217 static void Off(ani_env *env, ani_object object, ani_string type, ani_fn_object fnObj)
218 {
219 std::string typeStr;
220 ANIUtils_ANIStringToStdString(env, type, typeStr);
221 if (strcmp(LAYOUT_TYPE, typeStr.c_str()) != 0 && strcmp(DRAW_TYPE, typeStr.c_str()) != 0) {
222 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani method on not support event type %{public}s",
223 typeStr.c_str());
224 return;
225 }
226
227 auto *observer = Unwrapp(env, object);
228 if (observer == nullptr) {
229 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani context is null.");
230 return;
231 }
232 ani_ref fnObjGlobalRef = nullptr;
233 env->GlobalReference_Create(reinterpret_cast<ani_ref>(fnObj), &fnObjGlobalRef);
234 observer->RemoveCallbackToList(observer->GetCbListByType(typeStr), fnObjGlobalRef, typeStr, env);
235 }
236
AniSendEventByKey(ani_env * env,ani_string id,ani_double action,ani_string params)237 static ani_boolean AniSendEventByKey(ani_env *env, ani_string id, ani_double action, ani_string params)
238 {
239 std::string keyStr;
240 ani_status status = ANIUtils_ANIStringToStdString(env, id, keyStr);
241 if (status != ANI_OK) {
242 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani get send event key error.");
243 return ANI_FALSE;
244 }
245 std::string paramsStr;
246 status = ANIUtils_ANIStringToStdString(env, params, paramsStr);
247 if (status != ANI_OK) {
248 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani get send event params error.");
249 return ANI_FALSE;
250 }
251 ContainerScope scope {Container::CurrentIdSafelyWithCheck()};
252 bool result = NG::Inspector::SendEventByKey(keyStr, action, paramsStr);
253 if (result) {
254 return ANI_TRUE;
255 }
256 return ANI_FALSE;
257 }
258
AniGetInspectorTree(ani_env * env)259 static ani_object AniGetInspectorTree(ani_env *env)
260 {
261 ContainerScope scope {Container::CurrentIdSafelyWithCheck()};
262 std::string resultStr = NG::Inspector::GetInspector(false);
263 if (resultStr.empty()) {
264 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani inspector tree is empty.");
265 return nullptr;
266 }
267 ani_string aniResult;
268 ani_status status = env->String_NewUTF8(resultStr.c_str(), resultStr.size(), &aniResult);
269 if (ANI_OK != status) {
270 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani Can not convert string to ani_string.");
271 return nullptr;
272 }
273 return aniResult;
274 }
275
AniGetInspectorByKey(ani_env * env,ani_string key)276 static ani_string AniGetInspectorByKey(ani_env *env, ani_string key)
277 {
278 std::string keyStr;
279 ani_status getStdStringStatus = ANIUtils_ANIStringToStdString(env, key, keyStr);
280 if (getStdStringStatus != ANI_OK) {
281 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani get key failed.");
282 return nullptr;
283 }
284 ContainerScope scope{Container::CurrentIdSafelyWithCheck()};
285 std::string resultStr = NG::Inspector::GetInspectorNodeByKey(keyStr);
286 if (resultStr.empty()) {
287 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani node %{public}s is empty.", keyStr.c_str());
288 return nullptr;
289 }
290 ani_string ani_str;
291 ani_status status = env->String_NewUTF8(resultStr.c_str(), resultStr.size(), &ani_str);
292 if (ANI_OK != status) {
293 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani Can not convert string to ani_string.");
294 return nullptr;
295 }
296 return ani_str;
297 }
298
CreateComponentObserver(ani_env * env,ani_string id,const char * className)299 static ani_object CreateComponentObserver(ani_env *env, ani_string id, const char *className)
300 {
301 ani_class cls;
302 if (ANI_OK != env->FindClass(className, &cls)) {
303 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani not found class");
304 return nullptr;
305 }
306
307 ani_method ctor;
308 if (ANI_OK != env->Class_FindMethod(cls, "<ctor>", nullptr, &ctor)) {
309 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani can not get construct method.");
310 return nullptr;
311 }
312
313 std::string key;
314 ani_status status = ANIUtils_ANIStringToStdString(env, id, key);
315 if (status != ANI_OK) {
316 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani get key of observer failed.");
317 return nullptr;
318 }
319 TAG_LOGI(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani start to CreateComponentObserver key is %{public}s",
320 key.c_str());
321
322 auto arkTsFrontend = ComponentObserver::getFronted();
323 if (arkTsFrontend == nullptr) {
324 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani Can not convert to arkts frontend.");
325 return nullptr;
326 }
327 auto* observer = new ComponentObserver(key);
328
329 ani_object context_object;
330 if (ANI_OK != env->Object_New(cls, ctor, &context_object, reinterpret_cast<ani_long>(observer))) {
331 TAG_LOGE(AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani Can not new object.");
332 delete observer;
333 return nullptr;
334 }
335
336 auto layoutCallback = [observer, env]() -> void {
337 observer->CallUserFunction(env, observer->GetCbListByType(LAYOUT_TYPE));
338 };
339 observer->SetInspectorFuncByType(LAYOUT_TYPE, AceType::MakeRefPtr<InspectorEvent>(std::move(layoutCallback)));
340
341 auto drawCallback = [observer, env]() -> void {
342 observer->CallUserFunction(env, observer->GetCbListByType(DRAW_TYPE));
343 };
344 observer->SetInspectorFuncByType(DRAW_TYPE, AceType::MakeRefPtr<InspectorEvent>(std::move(drawCallback)));
345
346 arkTsFrontend->RegisterLayoutInspectorCallback(observer->GetInspectorFuncByType(LAYOUT_TYPE), key);
347 arkTsFrontend->RegisterDrawInspectorCallback(observer->GetInspectorFuncByType(DRAW_TYPE), key);
348 return context_object;
349 }
350
CreateComponentObserverForAni(ani_env * env,ani_string id)351 static ani_object CreateComponentObserverForAni(ani_env *env, ani_string id)
352 {
353 return CreateComponentObserver(env, id, ANI_COMPONENT_OBSERVER_CLS);
354 }
355
CreateComponentObserverForKoala(ani_env * env,ani_object object,ani_string id)356 static ani_object CreateComponentObserverForKoala(ani_env *env, [[maybe_unused]] ani_object object, ani_string id)
357 {
358 return CreateComponentObserver(env, id, KOALA_COMPONENT_CLS);
359 }
360 } // namespace OHOS::Ace
361
ANI_ConstructorForAni(ani_env * env)362 bool ANI_ConstructorForAni(ani_env *env)
363 {
364 ani_namespace ns;
365 if (ANI_OK != env->FindNamespace(ANI_INSPECTOR_NS, &ns)) {
366 TAG_LOGE(OHOS::Ace::AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani Not found ns");
367 return false;
368 }
369 std::array methods = {
370 ani_native_function {"createComponentObserver", nullptr,
371 reinterpret_cast<void *>(OHOS::Ace::CreateComponentObserverForAni)},
372 ani_native_function {"getInspectorByKey", nullptr,
373 reinterpret_cast<void *>(OHOS::Ace::AniGetInspectorByKey)},
374 ani_native_function {"sendEventByKey", nullptr,
375 reinterpret_cast<void *>(OHOS::Ace::AniSendEventByKey)},
376 ani_native_function {"getInspectorTree", nullptr,
377 reinterpret_cast<void *>(OHOS::Ace::AniGetInspectorTree)},
378 };
379
380 if (ANI_OK != env->Namespace_BindNativeFunctions(ns, methods.data(), methods.size())) {
381 TAG_LOGE(OHOS::Ace::AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani Namespace_BindNativeFunctions error");
382 return false;
383 }
384
385 ani_class clsInspector;
386 if (ANI_OK != env->FindClass(ANI_COMPONENT_OBSERVER_CLS, &clsInspector)) {
387 TAG_LOGE(OHOS::Ace::AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani not found class");
388 return false;
389 }
390
391 std::array methodsInspector = {
392 ani_native_function {"on", nullptr, reinterpret_cast<void *>(OHOS::Ace::On)},
393 ani_native_function {"off", nullptr, reinterpret_cast<void *>(OHOS::Ace::Off)},
394 };
395
396 if (ANI_OK != env->Class_BindNativeMethods(clsInspector, methodsInspector.data(), methodsInspector.size())) {
397 TAG_LOGE(OHOS::Ace::AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani Class_BindNativeFunctions error");
398 return false;
399 }
400 return true;
401 }
402
ANI_ConstructorForKoala(ani_env * env)403 bool ANI_ConstructorForKoala(ani_env *env)
404 {
405 ani_class clsInspector;
406 if (ANI_OK != env->FindClass(KOALA_INSPECTOR_CLS, &clsInspector)) {
407 TAG_LOGE(OHOS::Ace::AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-koala not found class");
408 return false;
409 }
410 std::array methodsInspector = {
411 ani_native_function {"createComponentObserver", nullptr,
412 reinterpret_cast<void *>(OHOS::Ace::CreateComponentObserverForKoala)},
413 };
414 if (ANI_OK != env->Class_BindNativeMethods(clsInspector, methodsInspector.data(), methodsInspector.size())) {
415 TAG_LOGE(OHOS::Ace::AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-koala Class_BindNativeFunctions error");
416 return false;
417 }
418
419 ani_class clsObserver;
420 if (ANI_OK != env->FindClass(KOALA_COMPONENT_CLS, &clsObserver)) {
421 TAG_LOGE(OHOS::Ace::AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-koala not found class");
422 return false;
423 }
424 std::array methodsObserver = {
425 ani_native_function {"on", nullptr, reinterpret_cast<void *>(OHOS::Ace::On)},
426 ani_native_function {"off", nullptr, reinterpret_cast<void *>(OHOS::Ace::Off)},
427 };
428 if (ANI_OK != env->Class_BindNativeMethods(clsObserver, methodsObserver.data(), methodsObserver.size())) {
429 TAG_LOGE(OHOS::Ace::AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-koala Class_BindNativeFunctions error");
430 return false;
431 }
432 return true;
433 }
434
ANI_Constructor(ani_vm * vm,uint32_t * result)435 ANI_EXPORT ani_status ANI_Constructor(ani_vm *vm, uint32_t *result)
436 {
437 ani_env *env;
438 if (ANI_OK != vm->GetEnv(ANI_VERSION_1, &env)) {
439 TAG_LOGE(OHOS::Ace::AceLogTag::ACE_LAYOUT_INSPECTOR, "inspector-ani Unsupported ANI_VERSION_1");
440 return ANI_ERROR;
441 }
442 if (ANI_ConstructorForAni(env) || ANI_ConstructorForKoala(env)) {
443 *result = ANI_VERSION_1;
444 return ANI_OK;
445 }
446 return ANI_ERROR;
447 }