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 "core/common/recorder/event_controller.h"
16 
17 #include "base/thread/background_task_executor.h"
18 #include "core/common/recorder/node_data_cache.h"
19 #include "core/pipeline_ng/pipeline_context.h"
20 
21 namespace OHOS::Ace::Recorder {
22 constexpr int32_t PAGE_URL_SUFFIX_LENGTH = 3;
23 constexpr uint32_t EXPOSURE_REGISTER_DELAY = 500;
24 
25 struct ExposureWrapper {
26     WeakPtr<NG::FrameNode> node;
27     RefPtr<ExposureProcessor> processor;
28 
ExposureWrapperOHOS::Ace::Recorder::ExposureWrapper29     ExposureWrapper(const WeakPtr<NG::FrameNode>& node, RefPtr<ExposureProcessor>&& processor)
30         : node(node), processor(processor)
31     {}
32 };
33 
Get()34 EventController& EventController::Get()
35 {
36     static EventController eventController;
37     return eventController;
38 }
39 
Register(const std::string & config,const std::shared_ptr<UIEventObserver> & observer)40 void EventController::Register(const std::string& config, const std::shared_ptr<UIEventObserver>& observer)
41 {
42     TAG_LOGI(AceLogTag::ACE_UIEVENT, "Register config");
43     UIEventClient client;
44     client.config.Init(config);
45     if (!client.config.IsEnable()) {
46         return;
47     }
48     client.observer = observer;
49     std::unique_lock<std::shared_mutex> lock(cacheLock_);
50     clientList_.emplace_back(std::move(client));
51     lock.unlock();
52     bool isOriginEnable = EventRecorder::Get().IsExposureRecordEnable();
53     NotifyConfigChange();
54     bool isCurrentEnable = EventRecorder::Get().IsExposureRecordEnable();
55     if (!isOriginEnable && isCurrentEnable) {
56         ApplyNewestConfig();
57     }
58     TAG_LOGI(AceLogTag::ACE_UIEVENT, "Register config end");
59 }
60 
NotifyConfigChange()61 void EventController::NotifyConfigChange()
62 {
63     std::shared_lock<std::shared_mutex> lock(cacheLock_);
64     auto mergedConfig = std::make_shared<MergedConfig>();
65     EventSwitch eventSwitch;
66     for (auto&& client : clientList_) {
67         if (!client.config.IsEnable()) {
68             continue;
69         }
70         eventSwitch.pageEnable = eventSwitch.pageEnable || client.config.IsCategoryEnable(EventCategory::CATEGORY_PAGE);
71         eventSwitch.exposureEnable =
72             eventSwitch.exposureEnable || client.config.IsCategoryEnable(EventCategory::CATEGORY_EXPOSURE);
73         eventSwitch.componentEnable =
74             eventSwitch.componentEnable || client.config.IsCategoryEnable(EventCategory::CATEGORY_COMPONENT);
75         eventSwitch.pageParamEnable =
76             eventSwitch.pageParamEnable || client.config.IsCategoryEnable(EventCategory::CATEGORY_PAGE_PARAM);
77         for (auto iter = client.config.GetConfig()->begin(); iter != client.config.GetConfig()->end(); iter++) {
78             auto nodeIt = mergedConfig->shareNodes.find(iter->first);
79             if (nodeIt != mergedConfig->shareNodes.end()) {
80                 std::for_each(iter->second.shareNodes.begin(), iter->second.shareNodes.end(),
81                     [&nodeIt](const std::list<std::string>::value_type& id) { nodeIt->second.emplace(id); });
82             } else {
83                 std::unordered_set<std::string> nodeSet;
84                 std::for_each(iter->second.shareNodes.begin(), iter->second.shareNodes.end(),
85                     [&nodeSet](const std::list<std::string>::value_type& id) { nodeSet.emplace(id); });
86                 mergedConfig->shareNodes.emplace(iter->first, std::move(nodeSet));
87             }
88 
89             auto exposureIt = mergedConfig->exposureNodes.find(iter->first);
90             if (exposureIt != mergedConfig->exposureNodes.end()) {
91                 std::for_each(iter->second.exposureCfgs.begin(), iter->second.exposureCfgs.end(),
92                     [&exposureIt](
93                         const std::list<ExposureCfg>::value_type& cfg) { exposureIt->second.emplace(cfg); });
94             } else {
95                 std::unordered_set<ExposureCfg, ExposureCfgHash> exposureSet;
96                 std::for_each(iter->second.exposureCfgs.begin(), iter->second.exposureCfgs.end(),
97                     [&exposureSet](const std::list<ExposureCfg>::value_type& cfg) { exposureSet.emplace(cfg); });
98                 mergedConfig->exposureNodes.emplace(iter->first, std::move(exposureSet));
99             }
100         }
101     }
102     NodeDataCache::Get().UpdateConfig(std::move(mergedConfig));
103     EventRecorder::Get().UpdateEventSwitch(eventSwitch);
104 }
105 
GetPageUrlByContainerId(const int32_t containerId)106 std::string GetPageUrlByContainerId(const int32_t containerId)
107 {
108     auto container = Container::GetContainer(containerId);
109     CHECK_NULL_RETURN(container, "");
110     if (!container->IsUseNewPipeline()) {
111         return "";
112     }
113     auto frontEnd = container->GetFrontend();
114     CHECK_NULL_RETURN(frontEnd, "");
115     auto pageUrl = frontEnd->GetCurrentPageUrl();
116     if (StringUtils::EndWith(pageUrl, ".js")) {
117         pageUrl = pageUrl.substr(0, pageUrl.length() - PAGE_URL_SUFFIX_LENGTH);
118     }
119     return pageUrl;
120 }
121 
GetMatchedNodes(const std::string & pageUrl,const RefPtr<NG::UINode> & root,const std::unordered_set<ExposureCfg,ExposureCfgHash> & exposureSet,std::list<ExposureWrapper> & outputList)122 void GetMatchedNodes(const std::string& pageUrl, const RefPtr<NG::UINode>& root,
123     const std::unordered_set<ExposureCfg, ExposureCfgHash>& exposureSet, std::list<ExposureWrapper>& outputList)
124 {
125     std::queue<RefPtr<NG::UINode>> elements;
126     ExposureCfg targetCfg = { "", 0.0, 0 };
127     elements.push(root);
128     while (!elements.empty()) {
129         auto current = elements.front();
130         elements.pop();
131         targetCfg.id = current->GetInspectorIdValue("");
132         if (!targetCfg.id.empty() && AceType::InstanceOf<NG::FrameNode>(current)) {
133             auto frameNode = AceType::DynamicCast<NG::FrameNode>(current);
134             auto cfgIter = exposureSet.find(targetCfg);
135             if (cfgIter != exposureSet.end()) {
136                 outputList.emplace_back(ExposureWrapper(Referenced::WeakClaim(Referenced::RawPtr(frameNode)),
137                     Referenced::MakeRefPtr<ExposureProcessor>(
138                         pageUrl, targetCfg.id, cfgIter->ratio, cfgIter->duration)));
139             }
140         }
141         for (const auto& child : current->GetChildren()) {
142             elements.push(child);
143         }
144     }
145 }
146 
ApplyNewestConfig() const147 void EventController::ApplyNewestConfig() const
148 {
149     std::shared_lock<std::shared_mutex> lock(cacheLock_);
150     if (clientList_.empty()) {
151         return;
152     }
153     auto containerId = EventRecorder::Get().GetContainerId();
154     auto config = clientList_.back().config.GetConfig();
155 
156     auto context = NG::PipelineContext::GetContextByContainerId(containerId);
157     CHECK_NULL_VOID(context);
158     auto taskExecutor = context->GetTaskExecutor();
159     CHECK_NULL_VOID(taskExecutor);
160     taskExecutor->PostDelayedTask([config]() { EventController::Get().ApplyExposureCfgInner(config); },
161         TaskExecutor::TaskType::UI, EXPOSURE_REGISTER_DELAY, "EventController");
162 }
163 
ApplyExposureCfgInner(const std::shared_ptr<Config> & config) const164 void EventController::ApplyExposureCfgInner(const std::shared_ptr<Config>& config) const
165 {
166     auto containerId = EventRecorder::Get().GetContainerId();
167     auto pageUrl = GetPageUrlByContainerId(containerId);
168     if (pageUrl.empty()) {
169         return;
170     }
171     auto pageIter = config->find(pageUrl);
172     if (pageIter == config->end()) {
173         return;
174     }
175     if (pageIter->second.exposureCfgs.empty()) {
176         return;
177     }
178     auto context = NG::PipelineContext::GetContextByContainerId(containerId);
179     CHECK_NULL_VOID(context);
180     auto rootNode = context->GetRootElement();
181     CHECK_NULL_VOID(rootNode);
182     std::unordered_set<ExposureCfg, ExposureCfgHash> exposureSet;
183     std::for_each(pageIter->second.exposureCfgs.begin(), pageIter->second.exposureCfgs.end(),
184         [&exposureSet](const std::list<ExposureCfg>::value_type& cfg) { exposureSet.emplace(cfg); });
185     std::list<ExposureWrapper> targets;
186     GetMatchedNodes(pageUrl, rootNode, exposureSet, targets);
187     for (auto& item : targets) {
188         item.processor->SetContainerId(containerId);
189         auto node = item.node.Upgrade();
190         CHECK_NULL_VOID(node);
191         node->SetExposureProcessor(item.processor);
192     }
193 }
194 
Unregister(const std::shared_ptr<UIEventObserver> & observer)195 void EventController::Unregister(const std::shared_ptr<UIEventObserver>& observer)
196 {
197     std::unique_lock<std::shared_mutex> lock(cacheLock_);
198     auto iter = std::remove_if(clientList_.begin(), clientList_.end(),
199         [&observer](UIEventClient client) { return client.observer == observer; });
200     bool change = iter != clientList_.end();
201     clientList_.erase(iter, clientList_.end());
202     lock.unlock();
203     if (change) {
204         NotifyConfigChange();
205     }
206 }
207 
NotifyEvent(EventCategory category,int32_t eventType,const std::shared_ptr<std::unordered_map<std::string,std::string>> & eventParams)208 void EventController::NotifyEvent(EventCategory category, int32_t eventType,
209     const std::shared_ptr<std::unordered_map<std::string, std::string>>& eventParams)
210 {
211     {
212         std::shared_lock<std::shared_mutex> lock(cacheLock_);
213         if (clientList_.empty()) {
214             return;
215         }
216     }
217     BackgroundTaskExecutor::GetInstance().PostTask([category, eventType, eventParams]() {
218         EventController::Get().NotifyEventSync(category, eventType, eventParams);
219     });
220 }
221 
NotifyEventSync(EventCategory category,int32_t eventType,const std::shared_ptr<std::unordered_map<std::string,std::string>> & eventParams)222 void EventController::NotifyEventSync(EventCategory category, int32_t eventType,
223     const std::shared_ptr<std::unordered_map<std::string, std::string>>& eventParams)
224 {
225     std::shared_lock<std::shared_mutex> lock(cacheLock_);
226     for (auto&& client : clientList_) {
227         if (client.config.IsEnable() && client.config.IsCategoryEnable(category)) {
228             client.observer->NotifyUIEvent(eventType, *eventParams);
229         }
230     }
231 }
232 } // namespace OHOS::Ace::Recorder
233