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
16#include "ui_observer.h"
17
18#include "bridge/common/utils/engine_helper.h"
19
20
21namespace OHOS::Ace::Napi {
22std::list<std::shared_ptr<UIObserverListener>> UIObserver::unspecifiedNavigationListeners_;
23std::unordered_map<std::string, std::list<std::shared_ptr<UIObserverListener>>>
24    UIObserver::specifiedCNavigationListeners_;
25
26std::list<std::shared_ptr<UIObserverListener>> UIObserver::scrollEventListeners_;
27std::unordered_map<std::string, std::list<std::shared_ptr<UIObserverListener>>>
28    UIObserver::specifiedScrollEventListeners_;
29
30std::unordered_map<napi_ref, std::list<std::shared_ptr<UIObserverListener>>>
31    UIObserver::abilityContextRouterPageListeners_;
32std::unordered_map<int32_t, std::list<std::shared_ptr<UIObserverListener>>>
33    UIObserver::specifiedRouterPageListeners_;
34std::unordered_map<napi_ref, NG::AbilityContextInfo> UIObserver::infosForRouterPage_;
35
36std::unordered_map<int32_t, std::list<std::shared_ptr<UIObserverListener>>>
37    UIObserver::specifiedDensityListeners_;
38std::unordered_map<int32_t, std::list<std::shared_ptr<UIObserverListener>>> UIObserver::specifiedDrawListeners_;
39std::unordered_map<int32_t, std::list<std::shared_ptr<UIObserverListener>>> UIObserver::specifiedLayoutListeners_;
40
41std::unordered_map<napi_ref, UIObserver::NavIdAndListenersMap> UIObserver::abilityUIContextNavDesSwitchListeners_;
42std::unordered_map<int32_t, UIObserver::NavIdAndListenersMap> UIObserver::uiContextNavDesSwitchListeners_;
43std::unordered_map<napi_ref, NG::AbilityContextInfo> UIObserver::infosForNavDesSwitch_;
44
45std::unordered_map<napi_ref, std::list<std::shared_ptr<UIObserverListener>>>
46    UIObserver::abilityContextWillClickListeners_;
47std::unordered_map<int32_t, std::list<std::shared_ptr<UIObserverListener>>>
48    UIObserver::specifiedWillClickListeners_;
49std::unordered_map<napi_ref, NG::AbilityContextInfo> UIObserver::willClickInfos_;
50
51std::unordered_map<napi_ref, std::list<std::shared_ptr<UIObserverListener>>>
52    UIObserver::abilityContextDidClickListeners_;
53std::unordered_map<int32_t, std::list<std::shared_ptr<UIObserverListener>>>
54    UIObserver::specifiedDidClickListeners_;
55std::unordered_map<napi_ref, NG::AbilityContextInfo> UIObserver::didClickInfos_;
56
57std::list<std::shared_ptr<UIObserverListener>> UIObserver::tabContentStateListeners_;
58std::unordered_map<std::string, std::list<std::shared_ptr<UIObserverListener>>>
59    UIObserver::specifiedTabContentStateListeners_;
60
61// UIObserver.on(type: "navDestinationUpdate", callback)
62// register a global listener without options
63void UIObserver::RegisterNavigationCallback(const std::shared_ptr<UIObserverListener>& listener)
64{
65    if (std::find(unspecifiedNavigationListeners_.begin(), unspecifiedNavigationListeners_.end(), listener) !=
66        unspecifiedNavigationListeners_.end()) {
67        return;
68    }
69    unspecifiedNavigationListeners_.emplace_back(listener);
70}
71
72// UIObserver.on(type: "navDestinationUpdate", options, callback)
73// register a listener on a specified Navigation
74void UIObserver::RegisterNavigationCallback(
75    std::string navigationId, const std::shared_ptr<UIObserverListener>& listener)
76{
77    auto iter = specifiedCNavigationListeners_.find(navigationId);
78    if (iter == specifiedCNavigationListeners_.end()) {
79        specifiedCNavigationListeners_.emplace(
80            navigationId, std::list<std::shared_ptr<UIObserverListener>>({ listener }));
81        return;
82    }
83    auto& holder = iter->second;
84    if (std::find(holder.begin(), holder.end(), listener) != holder.end()) {
85        return;
86    }
87    holder.emplace_back(listener);
88}
89
90// UIObserver.off(type: "navDestinationUpdate", callback)
91void UIObserver::UnRegisterNavigationCallback(napi_value cb)
92{
93    if (cb == nullptr) {
94        unspecifiedNavigationListeners_.clear();
95        return;
96    }
97
98    unspecifiedNavigationListeners_.erase(
99        std::remove_if(
100            unspecifiedNavigationListeners_.begin(),
101            unspecifiedNavigationListeners_.end(),
102            [cb](const std::shared_ptr<UIObserverListener>& registeredListener) {
103                return registeredListener->NapiEqual(cb);
104            }
105        ),
106        unspecifiedNavigationListeners_.end()
107    );
108}
109
110// UIObserver.off(type: "navDestinationUpdate", options, callback)
111void UIObserver::UnRegisterNavigationCallback(std::string navigationId, napi_value cb)
112{
113    auto iter = specifiedCNavigationListeners_.find(navigationId);
114    if (iter == specifiedCNavigationListeners_.end()) {
115        return;
116    }
117    auto& holder = iter->second;
118    if (cb == nullptr) {
119        holder.clear();
120        return;
121    }
122    holder.erase(
123        std::remove_if(
124            holder.begin(),
125            holder.end(),
126            [cb](const std::shared_ptr<UIObserverListener>& registeredListener) {
127                return registeredListener->NapiEqual(cb);
128            }
129        ),
130        holder.end()
131    );
132}
133
134void UIObserver::HandleNavigationStateChange(const NG::NavDestinationInfo& info)
135{
136    auto unspecifiedHolder = unspecifiedNavigationListeners_;
137    for (const auto& listener : unspecifiedHolder) {
138        listener->OnNavigationStateChange(info);
139    }
140    auto iter = specifiedCNavigationListeners_.find(info.navigationId);
141    if (iter == specifiedCNavigationListeners_.end()) {
142        return;
143    }
144
145    auto holder = iter->second;
146
147    for (const auto& listener : holder) {
148        listener->OnNavigationStateChange(info);
149    }
150}
151
152// UIObserver.on(type: "scrollEvent", callback)
153// register a global listener without options
154void UIObserver::RegisterScrollEventCallback(const std::shared_ptr<UIObserverListener>& listener)
155{
156    if (std::find(scrollEventListeners_.begin(), scrollEventListeners_.end(), listener) !=
157        scrollEventListeners_.end()) {
158        return;
159    }
160    scrollEventListeners_.emplace_back(listener);
161}
162
163// UIObserver.on(type: "scrollEvent", options, callback)
164// register a listener on a specified scrollEvent
165void UIObserver::RegisterScrollEventCallback(
166    const std::string& id, const std::shared_ptr<UIObserverListener>& listener)
167{
168    auto iter = specifiedScrollEventListeners_.find(id);
169    if (iter == specifiedScrollEventListeners_.end()) {
170        specifiedScrollEventListeners_.emplace(id, std::list<std::shared_ptr<UIObserverListener>>({ listener }));
171        return;
172    }
173    auto& holder = iter->second;
174    if (std::find(holder.begin(), holder.end(), listener) != holder.end()) {
175        return;
176    }
177    holder.emplace_back(listener);
178}
179
180// UIObserver.off(type: "scrollEvent", callback)
181void UIObserver::UnRegisterScrollEventCallback(napi_value cb)
182{
183    if (cb == nullptr) {
184        scrollEventListeners_.clear();
185        return;
186    }
187
188    scrollEventListeners_.erase(
189        std::remove_if(
190            scrollEventListeners_.begin(),
191            scrollEventListeners_.end(),
192            [cb](const std::shared_ptr<UIObserverListener>& registeredListener) {
193                return registeredListener->NapiEqual(cb);
194            }),
195        scrollEventListeners_.end()
196    );
197}
198
199// UIObserver.off(type: "scrollEvent", options, callback)
200void UIObserver::UnRegisterScrollEventCallback(const std::string& id, napi_value cb)
201{
202    auto iter = specifiedScrollEventListeners_.find(id);
203    if (iter == specifiedScrollEventListeners_.end()) {
204        return;
205    }
206    auto& holder = iter->second;
207    if (cb == nullptr) {
208        holder.clear();
209        return;
210    }
211    holder.erase(
212        std::remove_if(
213            holder.begin(),
214            holder.end(),
215            [cb](const std::shared_ptr<UIObserverListener>& registeredListener) {
216                return registeredListener->NapiEqual(cb);
217            }),
218        holder.end()
219    );
220}
221
222void UIObserver::HandleScrollEventStateChange(const std::string& id, int32_t uniqueId,
223    NG::ScrollEventType eventType, float offset)
224{
225    for (const auto& listener : scrollEventListeners_) {
226        listener->OnScrollEventStateChange(id, uniqueId, eventType, offset);
227    }
228
229    auto iter = specifiedScrollEventListeners_.find(id);
230    if (iter == specifiedScrollEventListeners_.end()) {
231        return;
232    }
233
234    auto& holder = iter->second;
235
236    for (const auto& listener : holder) {
237        listener->OnScrollEventStateChange(id, uniqueId, eventType, offset);
238    }
239}
240
241// UIObserver.on(type: "routerPageUpdate", UIAbilityContext, callback)
242// register a listener on current page
243void UIObserver::RegisterRouterPageCallback(
244    napi_env env, napi_value uiAbilityContext, const std::shared_ptr<UIObserverListener>& listener)
245{
246    NG::AbilityContextInfo info;
247    GetAbilityInfos(env, uiAbilityContext, info);
248    for (auto listenerPair : abilityContextRouterPageListeners_) {
249        auto ref = listenerPair.first;
250        auto localInfo = infosForRouterPage_[ref];
251        if (info.IsEqual(localInfo)) {
252            auto& holder = abilityContextRouterPageListeners_[ref];
253            if (std::find(holder.begin(), holder.end(), listener) != holder.end()) {
254                return;
255            }
256            holder.emplace_back(listener);
257            return;
258        }
259    }
260    napi_ref newRef = nullptr;
261    napi_create_reference(env, uiAbilityContext, 1, &newRef);
262    abilityContextRouterPageListeners_[newRef] = std::list<std::shared_ptr<UIObserverListener>>({ listener });
263    infosForRouterPage_[newRef] = info;
264}
265
266// UIObserver.on(type: "routerPageUpdate", uiContext | null, callback)
267// register a listener on current page
268void UIObserver::RegisterRouterPageCallback(
269    int32_t uiContextInstanceId, const std::shared_ptr<UIObserverListener>& listener)
270{
271    if (uiContextInstanceId == 0) {
272        uiContextInstanceId = Container::CurrentId();
273    }
274    auto iter = specifiedRouterPageListeners_.find(uiContextInstanceId);
275    if (iter == specifiedRouterPageListeners_.end()) {
276        specifiedRouterPageListeners_.emplace(
277            uiContextInstanceId, std::list<std::shared_ptr<UIObserverListener>>({ listener }));
278        return;
279    }
280    auto& holder = iter->second;
281    if (std::find(holder.begin(), holder.end(), listener) != holder.end()) {
282        return;
283    }
284    holder.emplace_back(listener);
285}
286
287// UIObserver.off(type: "routerPageUpdate", uiAbilityContext, callback)
288void UIObserver::UnRegisterRouterPageCallback(napi_env env, napi_value uiAbilityContext, napi_value callback)
289{
290    NG::AbilityContextInfo info;
291    GetAbilityInfos(env, uiAbilityContext, info);
292    for (auto listenerPair : abilityContextRouterPageListeners_) {
293        auto ref = listenerPair.first;
294        auto localInfo = infosForRouterPage_[ref];
295        if (info.IsEqual(localInfo)) {
296            auto& holder = abilityContextRouterPageListeners_[listenerPair.first];
297            if (callback == nullptr) {
298                holder.clear();
299            } else {
300                holder.erase(
301                    std::remove_if(
302                        holder.begin(),
303                        holder.end(),
304                        [callback](const std::shared_ptr<UIObserverListener>& registeredListener) {
305                            return registeredListener->NapiEqual(callback);
306                        }),
307                    holder.end());
308            }
309            if (holder.empty()) {
310                infosForRouterPage_.erase(ref);
311                abilityContextRouterPageListeners_.erase(ref);
312                napi_delete_reference(env, ref);
313            }
314            return;
315        }
316    }
317}
318
319// UIObserver.off(type: "routerPageUpdate", uiContext | null, callback)
320void UIObserver::UnRegisterRouterPageCallback(int32_t uiContextInstanceId, napi_value callback)
321{
322    if (uiContextInstanceId == 0) {
323        uiContextInstanceId = Container::CurrentId();
324    }
325    auto iter = specifiedRouterPageListeners_.find(uiContextInstanceId);
326    if (iter == specifiedRouterPageListeners_.end()) {
327        return;
328    }
329    auto& holder = iter->second;
330    if (callback == nullptr) {
331        holder.clear();
332        return;
333    }
334    holder.erase(
335        std::remove_if(
336            holder.begin(),
337            holder.end(),
338            [callback](const std::shared_ptr<UIObserverListener>& registeredListener) {
339                return registeredListener->NapiEqual(callback);
340            }),
341        holder.end());
342}
343
344// UIObserver.on(type: "willDraw", uiContext | null, callback)
345// register a listener on current page
346void UIObserver::RegisterDrawCallback(int32_t uiContextInstanceId, const std::shared_ptr<UIObserverListener>& listener)
347{
348    if (uiContextInstanceId == 0) {
349        uiContextInstanceId = Container::CurrentId();
350    }
351    if (specifiedDrawListeners_.find(uiContextInstanceId) == specifiedDrawListeners_.end()) {
352        specifiedDrawListeners_[uiContextInstanceId] = std::list<std::shared_ptr<UIObserverListener>>({ listener });
353        return;
354    }
355    auto& holder = specifiedDrawListeners_[uiContextInstanceId];
356    if (std::find(holder.begin(), holder.end(), listener) != holder.end()) {
357        return;
358    }
359    holder.emplace_back(listener);
360}
361
362// UIObserver.off(type: "willDraw", uiContext | null, callback)
363void UIObserver::UnRegisterDrawCallback(int32_t uiContextInstanceId, napi_value callback)
364{
365    if (uiContextInstanceId == 0) {
366        uiContextInstanceId = Container::CurrentId();
367    }
368    if (specifiedDrawListeners_.find(uiContextInstanceId) == specifiedDrawListeners_.end()) {
369        return;
370    }
371    auto& holder = specifiedDrawListeners_[uiContextInstanceId];
372    if (callback == nullptr) {
373        holder.clear();
374        return;
375    }
376    holder.erase(
377        std::remove_if(
378            holder.begin(),
379            holder.end(),
380            [callback](const std::shared_ptr<UIObserverListener>& registeredListener) {
381                return registeredListener->NapiEqual(callback);
382            }),
383        holder.end());
384}
385
386// UIObserver.on(type: "didLayout", uiContext | null, callback)
387// register a listener on current page
388void UIObserver::RegisterLayoutCallback(
389    int32_t uiContextInstanceId, const std::shared_ptr<UIObserverListener>& listener)
390{
391    if (uiContextInstanceId == 0) {
392        uiContextInstanceId = Container::CurrentId();
393    }
394    if (specifiedLayoutListeners_.find(uiContextInstanceId) == specifiedLayoutListeners_.end()) {
395        specifiedLayoutListeners_[uiContextInstanceId] = std::list<std::shared_ptr<UIObserverListener>>({ listener });
396        return;
397    }
398    auto& holder = specifiedLayoutListeners_[uiContextInstanceId];
399    if (std::find(holder.begin(), holder.end(), listener) != holder.end()) {
400        return;
401    }
402    holder.emplace_back(listener);
403}
404
405// UIObserver.off(type: "didLayout", uiContext | null, callback)
406void UIObserver::UnRegisterLayoutCallback(int32_t uiContextInstanceId, napi_value callback)
407{
408    if (uiContextInstanceId == 0) {
409        uiContextInstanceId = Container::CurrentId();
410    }
411    if (specifiedLayoutListeners_.find(uiContextInstanceId) == specifiedLayoutListeners_.end()) {
412        return;
413    }
414    auto& holder = specifiedLayoutListeners_[uiContextInstanceId];
415    if (callback == nullptr) {
416        holder.clear();
417        return;
418    }
419    holder.erase(
420        std::remove_if(
421            holder.begin(),
422            holder.end(),
423            [callback](const std::shared_ptr<UIObserverListener>& registeredListener) {
424                return registeredListener->NapiEqual(callback);
425            }),
426        holder.end());
427}
428
429void UIObserver::HandleRouterPageStateChange(NG::AbilityContextInfo& info, const NG::RouterPageInfoNG& pageInfo)
430{
431    for (auto listenerPair : abilityContextRouterPageListeners_) {
432        auto ref = listenerPair.first;
433        auto localInfo = infosForRouterPage_[ref];
434        if (info.IsEqual(localInfo)) {
435            auto env = GetCurrentNapiEnv();
436            napi_value abilityContext = nullptr;
437            napi_get_reference_value(env, ref, &abilityContext);
438
439            NG::RouterPageInfoNG abilityPageInfo(
440                abilityContext, pageInfo.index, pageInfo.name, pageInfo.path, pageInfo.state, pageInfo.pageId);
441            auto holder = abilityContextRouterPageListeners_[ref];
442            for (const auto& listener : holder) {
443                listener->OnRouterPageStateChange(abilityPageInfo);
444            }
445            break;
446        }
447    }
448
449    auto currentId = Container::CurrentId();
450    auto iter = specifiedRouterPageListeners_.find(currentId);
451    if (iter == specifiedRouterPageListeners_.end()) {
452        return;
453    }
454    auto holder = iter->second;
455    for (const auto& listener : holder) {
456        listener->OnRouterPageStateChange(pageInfo);
457    }
458}
459
460// UIObserver.on(type: "densityUpdate", uiContext | null, callback)
461// register a listener on current page
462void UIObserver::RegisterDensityCallback(
463    int32_t uiContextInstanceId, const std::shared_ptr<UIObserverListener>& listener)
464{
465    if (uiContextInstanceId == 0) {
466        uiContextInstanceId = Container::CurrentId();
467    }
468    auto iter = specifiedDensityListeners_.find(uiContextInstanceId);
469    if (iter == specifiedDensityListeners_.end()) {
470        specifiedDensityListeners_.emplace(
471            uiContextInstanceId, std::list<std::shared_ptr<UIObserverListener>>({ listener }));
472        return;
473    }
474    auto& holder = iter->second;
475    if (std::find(holder.begin(), holder.end(), listener) != holder.end()) {
476        return;
477    }
478    holder.emplace_back(listener);
479}
480
481// UIObserver.off(type: "densityUpdate", uiContext | null, callback)
482void UIObserver::UnRegisterDensityCallback(int32_t uiContextInstanceId, napi_value callback)
483{
484    if (uiContextInstanceId == 0) {
485        uiContextInstanceId = Container::CurrentId();
486    }
487    auto iter = specifiedDensityListeners_.find(uiContextInstanceId);
488    if (iter == specifiedDensityListeners_.end()) {
489        return;
490    }
491    auto& holder = iter->second;
492    if (callback == nullptr) {
493        holder.clear();
494        return;
495    }
496    holder.erase(
497        std::remove_if(
498            holder.begin(),
499            holder.end(),
500            [callback](const std::shared_ptr<UIObserverListener>& registeredListener) {
501                return registeredListener->NapiEqual(callback);
502            }),
503        holder.end());
504}
505
506void UIObserver::HandleDensityChange(NG::AbilityContextInfo& info, double density)
507{
508    auto currentId = Container::CurrentId();
509    auto iter = specifiedDensityListeners_.find(currentId);
510    if (iter == specifiedDensityListeners_.end()) {
511        return;
512    }
513    auto& holder = iter->second;
514    for (const auto& listener : holder) {
515        listener->OnDensityChange(density);
516    }
517}
518
519void UIObserver::HandDrawCommandSendChange()
520{
521    auto currentId = Container::CurrentId();
522    if (specifiedDrawListeners_.find(currentId) == specifiedDrawListeners_.end()) {
523        return;
524    }
525    auto& holder = specifiedDrawListeners_[currentId];
526    for (const auto& listener : holder) {
527        listener->OnDrawOrLayout();
528    }
529}
530
531void UIObserver::HandLayoutDoneChange()
532{
533    auto currentId = Container::CurrentId();
534    if (specifiedLayoutListeners_.find(currentId) == specifiedLayoutListeners_.end()) {
535        return;
536    }
537    auto& holder = specifiedLayoutListeners_[currentId];
538    for (const auto& listener : holder) {
539        listener->OnDrawOrLayout();
540    }
541}
542
543/**
544 * observer.on('navDestinationSwitch', context: UIAbilityContext, callback)
545 * observer.on('navDestinationSwitch', context: UIAbilityContext, { navigationId: navId }, callback)
546 */
547void UIObserver::RegisterNavDestinationSwitchCallback(napi_env env, napi_value uiAbilityContext,
548    const std::optional<std::string>& navigationId, const std::shared_ptr<UIObserverListener>& listener)
549{
550    NG::AbilityContextInfo info;
551    GetAbilityInfos(env, uiAbilityContext, info);
552    for (auto& listenerPair : abilityUIContextNavDesSwitchListeners_) {
553        auto ref = listenerPair.first;
554        auto localInfo = infosForNavDesSwitch_[ref];
555        if (!info.IsEqual(localInfo)) {
556            continue;
557        }
558
559        auto& listenersMap = listenerPair.second;
560        auto it = listenersMap.find(navigationId);
561        if (it == listenersMap.end()) {
562            listenersMap[navigationId] = std::list<std::shared_ptr<UIObserverListener>>({listener});
563            return;
564        }
565        if (std::find(it->second.begin(), it->second.end(), listener) == it->second.end()) {
566            it->second.emplace_back(listener);
567        }
568        return;
569    }
570    napi_ref newRef = nullptr;
571    napi_create_reference(env, uiAbilityContext, 1, &newRef);
572    NavIdAndListenersMap listenersMap;
573    listenersMap.emplace(navigationId, std::list<std::shared_ptr<UIObserverListener>>({ listener }));
574    abilityUIContextNavDesSwitchListeners_[newRef] = listenersMap;
575    infosForNavDesSwitch_[newRef] = info;
576}
577
578/**
579 * UIObserver.on('navDestinationSwitch', { navigationId: navId }, callback)
580 * UIObserver.on('navDestinationSwitch', callback)
581 * observer.on('navDestinationSwitch', context: UIContext, { navigationId?: navId }, callback)
582 */
583void UIObserver::RegisterNavDestinationSwitchCallback(int32_t uiContextInstanceId,
584    const std::optional<std::string>& navigationId, const std::shared_ptr<UIObserverListener>& listener)
585{
586    if (uiContextInstanceId == 0) {
587        uiContextInstanceId = Container::CurrentId();
588    }
589    auto listenersMapIter = uiContextNavDesSwitchListeners_.find(uiContextInstanceId);
590    if (listenersMapIter == uiContextNavDesSwitchListeners_.end()) {
591        NavIdAndListenersMap listenersMap;
592        listenersMap.emplace(navigationId, std::list<std::shared_ptr<UIObserverListener>>({ listener }));
593        uiContextNavDesSwitchListeners_[uiContextInstanceId] = listenersMap;
594        return;
595    }
596
597    auto& listenersMap = listenersMapIter->second;
598    auto it = listenersMap.find(navigationId);
599    if (it == listenersMap.end()) {
600        listenersMap[navigationId] = std::list<std::shared_ptr<UIObserverListener>>({listener});
601        return;
602    }
603
604    if (std::find(it->second.begin(), it->second.end(), listener) == it->second.end()) {
605        it->second.emplace_back(listener);
606    }
607}
608
609/**
610 * observer.off('navDestinationSwitch', context: AbilityUIContext})
611 * observer.off('navDestinationSwitch', context: AbilityUIContext, callback })
612 * observer.off('navDestinationSwitch', context: AbilityUIContext, { navigationId: navId } })
613 * observer.off('navDestinationSwitch', context: AbilityUIContext, { navigationId: navId }, callback })
614 */
615void UIObserver::UnRegisterNavDestinationSwitchCallback(napi_env env, napi_value uiAbilityContext,
616    const std::optional<std::string>& navigationId, napi_value callback)
617{
618    NG::AbilityContextInfo info;
619    GetAbilityInfos(env, uiAbilityContext, info);
620    for (auto listenerPair : abilityUIContextNavDesSwitchListeners_) {
621        auto ref = listenerPair.first;
622        auto localInfo = infosForNavDesSwitch_[ref];
623        if (!info.IsEqual(localInfo)) {
624            continue;
625        }
626
627        auto& listenersMap = listenerPair.second;
628        auto it = listenersMap.find(navigationId);
629        if (it == listenersMap.end()) {
630            return;
631        }
632        auto& listeners = it->second;
633        if (callback == nullptr) {
634            listeners.clear();
635        } else {
636            listeners.erase(std::remove_if(listeners.begin(), listeners.end(),
637                [callback](const std::shared_ptr<UIObserverListener>& registeredListener) {
638                    return registeredListener->NapiEqual(callback);
639                }), listeners.end());
640        }
641        if (listeners.empty()) {
642            listenersMap.erase(it);
643        }
644        if (listenersMap.empty()) {
645            infosForNavDesSwitch_.erase(ref);
646            abilityUIContextNavDesSwitchListeners_.erase(ref);
647            napi_delete_reference(env, ref);
648        }
649        return;
650    }
651}
652
653/**
654 * observer.off('navDestinationSwitch', context: UIContext})
655 * observer.off('navDestinationSwitch', context: UIContext, callback })
656 * observer.off('navDestinationSwitch', context: UIContext, { navigationId: navId })
657 * observer.off('navDestinationSwitch', context: UIContext, { navigationId: navId }, callback )
658 * UIObserver.off('navDestinationSwitch')
659 * UIObserver.off('navDestinationSwitch', callback)
660 * UIObserver.off('navDestinationSwitch', { navigationId: navId })
661 * UIObserver.off('navDestinationSwitch', { navigationId: navId }, callback )
662 */
663void UIObserver::UnRegisterNavDestinationSwitchCallback(int32_t uiContextInstanceId,
664    const std::optional<std::string>& navigationId, napi_value callback)
665{
666    if (uiContextInstanceId == 0) {
667        uiContextInstanceId = Container::CurrentId();
668    }
669    auto listenersMapIter = uiContextNavDesSwitchListeners_.find(uiContextInstanceId);
670    if (listenersMapIter == uiContextNavDesSwitchListeners_.end()) {
671        return;
672    }
673    auto& listenersMap = listenersMapIter->second;
674    auto it = listenersMap.find(navigationId);
675    if (it == listenersMap.end()) {
676        return;
677    }
678    auto& listeners = it->second;
679    if (callback == nullptr) {
680        listeners.clear();
681    } else {
682        listeners.erase(std::remove_if(listeners.begin(), listeners.end(),
683            [callback](const std::shared_ptr<UIObserverListener>& registeredListener) {
684                return registeredListener->NapiEqual(callback);
685            }), listeners.end());
686    }
687    if (listeners.empty()) {
688        listenersMap.erase(it);
689    }
690    if (listenersMap.empty()) {
691        uiContextNavDesSwitchListeners_.erase(listenersMapIter);
692    }
693}
694
695void UIObserver::HandleNavDestinationSwitch(
696    const NG::AbilityContextInfo& info, NG::NavDestinationSwitchInfo& switchInfo)
697{
698    HandleAbilityUIContextNavDestinationSwitch(info, switchInfo);
699    HandleUIContextNavDestinationSwitch(switchInfo);
700}
701
702void UIObserver::HandleAbilityUIContextNavDestinationSwitch(
703    const NG::AbilityContextInfo& info, NG::NavDestinationSwitchInfo& switchInfo)
704{
705    napi_value uiContextBackup = switchInfo.context;
706    for (auto listenerPair : abilityUIContextNavDesSwitchListeners_) {
707        auto ref = listenerPair.first;
708        auto localInfo = infosForNavDesSwitch_[ref];
709        if (!info.IsEqual(localInfo)) {
710            continue;
711        }
712
713        auto env = GetCurrentNapiEnv();
714        napi_value abilityContext = nullptr;
715        napi_get_reference_value(env, ref, &abilityContext);
716
717        switchInfo.context = abilityContext;
718        auto listenersMap = listenerPair.second;
719        HandleListenersWithEmptyNavigationId(listenersMap, switchInfo);
720        HandleListenersWithSpecifiedNavigationId(listenersMap, switchInfo);
721        break;
722    }
723    switchInfo.context = uiContextBackup;
724}
725
726void UIObserver::HandleUIContextNavDestinationSwitch(const NG::NavDestinationSwitchInfo& switchInfo)
727{
728    auto currentId = Container::CurrentId();
729    auto listenersMapIter = uiContextNavDesSwitchListeners_.find(currentId);
730    if (listenersMapIter == uiContextNavDesSwitchListeners_.end()) {
731        return;
732    }
733    auto listenersMap = listenersMapIter->second;
734    HandleListenersWithEmptyNavigationId(listenersMap, switchInfo);
735    HandleListenersWithSpecifiedNavigationId(listenersMap, switchInfo);
736}
737
738void UIObserver::HandleListenersWithEmptyNavigationId(
739    const NavIdAndListenersMap& listenersMap, const NG::NavDestinationSwitchInfo& switchInfo)
740{
741    std::optional<std::string> navId;
742    auto it = listenersMap.find(navId);
743    if (it != listenersMap.end()) {
744        const auto listeners = it->second;
745        for (const auto& listener : listeners) {
746            listener->OnNavDestinationSwitch(switchInfo);
747        }
748    }
749}
750
751void UIObserver::HandleListenersWithSpecifiedNavigationId(
752    const NavIdAndListenersMap& listenersMap, const NG::NavDestinationSwitchInfo& switchInfo)
753{
754    std::string navigationId;
755    if (switchInfo.from.has_value()) {
756        navigationId = switchInfo.from.value().navigationId;
757    } else if (switchInfo.to.has_value()) {
758        navigationId = switchInfo.to.value().navigationId;
759    }
760    if (!navigationId.empty()) {
761        std::optional<std::string> navId{navigationId};
762        auto it = listenersMap.find(navId);
763        if (it != listenersMap.end()) {
764            const auto listeners = it->second;
765            for (const auto& listener : listeners) {
766                listener->OnNavDestinationSwitch(switchInfo);
767            }
768        }
769    }
770}
771
772void UIObserver::RegisterWillClickCallback(
773    napi_env env, napi_value uiAbilityContext, const std::shared_ptr<UIObserverListener>& listener)
774{
775    napi_handle_scope scope = nullptr;
776    auto status = napi_open_handle_scope(env, &scope);
777    if (status != napi_ok) {
778        return;
779    }
780    NG::AbilityContextInfo info;
781    GetAbilityInfos(env, uiAbilityContext, info);
782    for (auto listenerPair : abilityContextWillClickListeners_) {
783        auto ref = listenerPair.first;
784        auto localInfo = willClickInfos_[ref];
785        if (info.IsEqual(localInfo)) {
786            auto& holder = abilityContextWillClickListeners_[ref];
787            if (std::find(holder.begin(), holder.end(), listener) != holder.end()) {
788                napi_close_handle_scope(env, scope);
789                return;
790            }
791            holder.emplace_back(listener);
792            napi_close_handle_scope(env, scope);
793            return;
794        }
795    }
796    napi_ref newRef = nullptr;
797    napi_create_reference(env, uiAbilityContext, 1, &newRef);
798    abilityContextWillClickListeners_[newRef] = std::list<std::shared_ptr<UIObserverListener>>({ listener });
799    willClickInfos_[newRef] = info;
800    napi_close_handle_scope(env, scope);
801}
802
803void UIObserver::RegisterWillClickCallback(
804    int32_t uiContextInstanceId, const std::shared_ptr<UIObserverListener>& listener)
805{
806    if (uiContextInstanceId == 0) {
807        uiContextInstanceId = Container::CurrentId();
808    }
809    auto iter = specifiedWillClickListeners_.find(uiContextInstanceId);
810    if (iter == specifiedWillClickListeners_.end()) {
811        specifiedWillClickListeners_.emplace(
812            uiContextInstanceId, std::list<std::shared_ptr<UIObserverListener>>({ listener }));
813        return;
814    }
815    auto& holder = iter->second;
816    if (std::find(holder.begin(), holder.end(), listener) != holder.end()) {
817        return;
818    }
819    holder.emplace_back(listener);
820}
821
822void UIObserver::UnRegisterWillClickCallback(napi_env env, napi_value uiAbilityContext, napi_value callback)
823{
824    napi_handle_scope scope = nullptr;
825    auto status = napi_open_handle_scope(env, &scope);
826    if (status != napi_ok) {
827        return;
828    }
829    NG::AbilityContextInfo info;
830    GetAbilityInfos(env, uiAbilityContext, info);
831    for (auto listenerPair : abilityContextWillClickListeners_) {
832        auto ref = listenerPair.first;
833        auto localInfo = willClickInfos_[ref];
834        if (!info.IsEqual(localInfo)) {
835            continue;
836        }
837        auto& holder = abilityContextWillClickListeners_[listenerPair.first];
838        if (callback == nullptr) {
839            holder.clear();
840        } else {
841            holder.erase(
842                std::remove_if(
843                    holder.begin(),
844                    holder.end(),
845                    [callback](const std::shared_ptr<UIObserverListener>& registeredListener) {
846                        return registeredListener->NapiEqual(callback);
847                    }),
848                holder.end());
849        }
850        if (holder.empty()) {
851            willClickInfos_.erase(ref);
852            abilityContextWillClickListeners_.erase(ref);
853            napi_delete_reference(env, ref);
854        }
855    }
856    napi_close_handle_scope(env, scope);
857}
858
859void UIObserver::UnRegisterWillClickCallback(int32_t uiContextInstanceId, napi_value callback)
860{
861    if (uiContextInstanceId == 0) {
862        uiContextInstanceId = Container::CurrentId();
863    }
864    auto iter = specifiedWillClickListeners_.find(uiContextInstanceId);
865    if (iter == specifiedWillClickListeners_.end()) {
866        return;
867    }
868    auto& holder = iter->second;
869    if (callback == nullptr) {
870        holder.clear();
871        return;
872    }
873    holder.erase(
874        std::remove_if(
875            holder.begin(),
876            holder.end(),
877            [callback](const std::shared_ptr<UIObserverListener>& registeredListener) {
878                return registeredListener->NapiEqual(callback);
879            }),
880        holder.end());
881}
882
883void UIObserver::HandleWillClick(NG::AbilityContextInfo& info, const GestureEvent& gestureEventInfo,
884    const ClickInfo& clickInfo, const RefPtr<NG::FrameNode>& frameNode)
885{
886    auto env = GetCurrentNapiEnv();
887    napi_handle_scope scope = nullptr;
888    auto status = napi_open_handle_scope(env, &scope);
889    if (status != napi_ok) {
890        return;
891    }
892    for (auto listenerPair : abilityContextWillClickListeners_) {
893        auto ref = listenerPair.first;
894        auto localInfo = willClickInfos_[ref];
895        if (info.IsEqual(localInfo)) {
896            napi_value abilityContext = nullptr;
897            napi_get_reference_value(env, ref, &abilityContext);
898
899            auto& holder = abilityContextWillClickListeners_[ref];
900            for (const auto& listener : holder) {
901                listener->OnWillClick(gestureEventInfo, clickInfo, frameNode);
902            }
903            break;
904        }
905    }
906
907    auto currentId = Container::CurrentId();
908    auto iter = specifiedWillClickListeners_.find(currentId);
909    if (iter == specifiedWillClickListeners_.end()) {
910        napi_close_handle_scope(env, scope);
911        return;
912    }
913    auto& holder = iter->second;
914    for (const auto& listener : holder) {
915        listener->OnWillClick(gestureEventInfo, clickInfo, frameNode);
916    }
917    napi_close_handle_scope(env, scope);
918}
919
920void UIObserver::RegisterDidClickCallback(
921    napi_env env, napi_value uiAbilityContext, const std::shared_ptr<UIObserverListener>& listener)
922{
923    napi_handle_scope scope = nullptr;
924    auto status = napi_open_handle_scope(env, &scope);
925    if (status != napi_ok) {
926        return;
927    }
928    NG::AbilityContextInfo info;
929    GetAbilityInfos(env, uiAbilityContext, info);
930    for (auto listenerPair : abilityContextDidClickListeners_) {
931        auto ref = listenerPair.first;
932        auto localInfo = didClickInfos_[ref];
933        if (info.IsEqual(localInfo)) {
934            auto& holder = abilityContextDidClickListeners_[ref];
935            if (std::find(holder.begin(), holder.end(), listener) != holder.end()) {
936                napi_close_handle_scope(env, scope);
937                return;
938            }
939            holder.emplace_back(listener);
940            napi_close_handle_scope(env, scope);
941            return;
942        }
943    }
944    napi_ref newRef = nullptr;
945    napi_create_reference(env, uiAbilityContext, 1, &newRef);
946    abilityContextDidClickListeners_[newRef] = std::list<std::shared_ptr<UIObserverListener>>({ listener });
947    didClickInfos_[newRef] = info;
948    napi_close_handle_scope(env, scope);
949}
950
951void UIObserver::RegisterDidClickCallback(
952    int32_t uiContextInstanceId, const std::shared_ptr<UIObserverListener>& listener)
953{
954    if (uiContextInstanceId == 0) {
955        uiContextInstanceId = Container::CurrentId();
956    }
957    auto iter = specifiedDidClickListeners_.find(uiContextInstanceId);
958    if (iter == specifiedDidClickListeners_.end()) {
959        specifiedDidClickListeners_.emplace(
960            uiContextInstanceId, std::list<std::shared_ptr<UIObserverListener>>({ listener }));
961        return;
962    }
963    auto& holder = iter->second;
964    if (std::find(holder.begin(), holder.end(), listener) != holder.end()) {
965        return;
966    }
967    holder.emplace_back(listener);
968}
969
970void UIObserver::UnRegisterDidClickCallback(napi_env env, napi_value uiAbilityContext, napi_value callback)
971{
972    napi_handle_scope scope = nullptr;
973    auto status = napi_open_handle_scope(env, &scope);
974    if (status != napi_ok) {
975        return;
976    }
977    NG::AbilityContextInfo info;
978    GetAbilityInfos(env, uiAbilityContext, info);
979    for (auto listenerPair : abilityContextDidClickListeners_) {
980        auto ref = listenerPair.first;
981        auto localInfo = didClickInfos_[ref];
982        if (!info.IsEqual(localInfo)) {
983            continue;
984        }
985        auto& holder = abilityContextDidClickListeners_[listenerPair.first];
986        if (callback == nullptr) {
987            holder.clear();
988        } else {
989            holder.erase(
990                std::remove_if(
991                    holder.begin(),
992                    holder.end(),
993                    [callback](const std::shared_ptr<UIObserverListener>& registeredListener) {
994                        return registeredListener->NapiEqual(callback);
995                    }),
996                holder.end());
997        }
998        if (holder.empty()) {
999            didClickInfos_.erase(ref);
1000            abilityContextDidClickListeners_.erase(ref);
1001            napi_delete_reference(env, ref);
1002        }
1003    }
1004    napi_close_handle_scope(env, scope);
1005}
1006
1007void UIObserver::UnRegisterDidClickCallback(int32_t uiContextInstanceId, napi_value callback)
1008{
1009    if (uiContextInstanceId == 0) {
1010        uiContextInstanceId = Container::CurrentId();
1011    }
1012    auto iter = specifiedDidClickListeners_.find(uiContextInstanceId);
1013    if (iter == specifiedDidClickListeners_.end()) {
1014        return;
1015    }
1016    auto& holder = iter->second;
1017    if (callback == nullptr) {
1018        holder.clear();
1019        return;
1020    }
1021    holder.erase(
1022        std::remove_if(
1023            holder.begin(),
1024            holder.end(),
1025            [callback](const std::shared_ptr<UIObserverListener>& registeredListener) {
1026                return registeredListener->NapiEqual(callback);
1027            }),
1028        holder.end());
1029}
1030
1031void UIObserver::HandleDidClick(NG::AbilityContextInfo& info, const GestureEvent& gestureEventInfo,
1032    const ClickInfo& clickInfo, const RefPtr<NG::FrameNode>& frameNode)
1033{
1034    auto env = GetCurrentNapiEnv();
1035    napi_handle_scope scope = nullptr;
1036    auto status = napi_open_handle_scope(env, &scope);
1037    if (status != napi_ok) {
1038        return;
1039    }
1040    for (auto listenerPair : abilityContextDidClickListeners_) {
1041        auto ref = listenerPair.first;
1042        auto localInfo = didClickInfos_[ref];
1043        if (info.IsEqual(localInfo)) {
1044            napi_value abilityContext = nullptr;
1045            napi_get_reference_value(env, ref, &abilityContext);
1046
1047            auto& holder = abilityContextDidClickListeners_[ref];
1048            for (const auto& listener : holder) {
1049                listener->OnDidClick(gestureEventInfo, clickInfo, frameNode);
1050            }
1051            break;
1052        }
1053    }
1054
1055    auto currentId = Container::CurrentId();
1056    auto iter = specifiedDidClickListeners_.find(currentId);
1057    if (iter == specifiedDidClickListeners_.end()) {
1058        napi_close_handle_scope(env, scope);
1059        return;
1060    }
1061    auto& holder = iter->second;
1062    for (const auto& listener : holder) {
1063        listener->OnDidClick(gestureEventInfo, clickInfo, frameNode);
1064    }
1065    napi_close_handle_scope(env, scope);
1066}
1067
1068// UIObserver.on(type: "tabContentState", callback)
1069// register a global listener without options
1070void UIObserver::RegisterTabContentStateCallback(const std::shared_ptr<UIObserverListener>& listener)
1071{
1072    if (std::find(tabContentStateListeners_.begin(), tabContentStateListeners_.end(), listener) !=
1073        tabContentStateListeners_.end()) {
1074        return;
1075    }
1076    tabContentStateListeners_.emplace_back(listener);
1077}
1078
1079// UIObserver.on(type: "tabContentState", options, callback)
1080// register a listener on a specified tabContentState
1081void UIObserver::RegisterTabContentStateCallback(
1082    const std::string& id, const std::shared_ptr<UIObserverListener>& listener)
1083{
1084    auto iter = specifiedTabContentStateListeners_.find(id);
1085    if (iter == specifiedTabContentStateListeners_.end()) {
1086        specifiedTabContentStateListeners_.emplace(id, std::list<std::shared_ptr<UIObserverListener>>({ listener }));
1087        return;
1088    }
1089    auto& holder = iter->second;
1090    if (std::find(holder.begin(), holder.end(), listener) != holder.end()) {
1091        return;
1092    }
1093    holder.emplace_back(listener);
1094}
1095
1096// UIObserver.off(type: "tabContentState", callback)
1097void UIObserver::UnRegisterTabContentStateCallback(napi_value cb)
1098{
1099    if (cb == nullptr) {
1100        tabContentStateListeners_.clear();
1101        return;
1102    }
1103
1104    tabContentStateListeners_.erase(
1105        std::remove_if(
1106            tabContentStateListeners_.begin(),
1107            tabContentStateListeners_.end(),
1108            [cb](const std::shared_ptr<UIObserverListener>& registeredListener) {
1109                return registeredListener->NapiEqual(cb);
1110            }),
1111        tabContentStateListeners_.end()
1112    );
1113}
1114
1115// UIObserver.off(type: "tabContentState", options, callback)
1116void UIObserver::UnRegisterTabContentStateCallback(const std::string& id, napi_value cb)
1117{
1118    auto iter = specifiedTabContentStateListeners_.find(id);
1119    if (iter == specifiedTabContentStateListeners_.end()) {
1120        return;
1121    }
1122    auto& holder = iter->second;
1123    if (cb == nullptr) {
1124        holder.clear();
1125        return;
1126    }
1127    holder.erase(
1128        std::remove_if(
1129            holder.begin(),
1130            holder.end(),
1131            [cb](const std::shared_ptr<UIObserverListener>& registeredListener) {
1132                return registeredListener->NapiEqual(cb);
1133            }),
1134        holder.end()
1135    );
1136}
1137
1138void UIObserver::HandleTabContentStateChange(const NG::TabContentInfo& tabContentInfo)
1139{
1140    for (const auto& listener : tabContentStateListeners_) {
1141        listener->OnTabContentStateChange(tabContentInfo);
1142    }
1143
1144    auto iter = specifiedTabContentStateListeners_.find(tabContentInfo.id);
1145    if (iter == specifiedTabContentStateListeners_.end()) {
1146        return;
1147    }
1148
1149    auto& holder = iter->second;
1150
1151    for (const auto& listener : holder) {
1152        listener->OnTabContentStateChange(tabContentInfo);
1153    }
1154}
1155
1156void UIObserver::GetAbilityInfos(napi_env env, napi_value abilityContext, NG::AbilityContextInfo& info)
1157{
1158    if (!env || !abilityContext) {
1159        return;
1160    }
1161    napi_value napiInfo = nullptr;
1162    napi_get_named_property(env, abilityContext, "abilityInfo", &napiInfo);
1163    CHECK_NULL_VOID(napiInfo);
1164    napi_value name = nullptr;
1165    napi_get_named_property(env, napiInfo, "name", &name);
1166    ParseStringFromNapi(env, name, info.name);
1167    napi_get_named_property(env, napiInfo, "bundleName", &name);
1168    ParseStringFromNapi(env, name, info.bundleName);
1169    napi_get_named_property(env, napiInfo, "moduleName", &name);
1170    ParseStringFromNapi(env, name, info.moduleName);
1171}
1172
1173bool UIObserver::ParseStringFromNapi(napi_env env, napi_value val, std::string& str)
1174{
1175    if (!val || !MatchValueType(env, val, napi_string)) {
1176        return false;
1177    }
1178    size_t len = 0;
1179    napi_get_value_string_utf8(env, val, nullptr, 0, &len);
1180    std::unique_ptr<char[]> result = std::make_unique<char[]>(len + 1);
1181    napi_get_value_string_utf8(env, val, result.get(), len + 1, &len);
1182    str = result.get();
1183    return true;
1184}
1185
1186bool UIObserver::MatchValueType(napi_env env, napi_value value, napi_valuetype targetType)
1187{
1188    napi_valuetype valueType = napi_undefined;
1189    napi_typeof(env, value, &valueType);
1190    return valueType == targetType;
1191}
1192
1193napi_env UIObserver::GetCurrentNapiEnv()
1194{
1195    auto engine = EngineHelper::GetCurrentEngine();
1196    CHECK_NULL_RETURN(engine, nullptr);
1197    NativeEngine* nativeEngine = engine->GetNativeEngine();
1198    CHECK_NULL_RETURN(nativeEngine, nullptr);
1199    return reinterpret_cast<napi_env>(nativeEngine);
1200}
1201} // namespace OHOS::Ace::Napi
1202