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 #include <cstdint>
17 #include <vector>
18
19 #include "base/thread/background_task_executor.h"
20 #include "core/common/recorder/event_definition.h"
21 #include "core/common/recorder/event_recorder.h"
22 #include "core/common/recorder/node_data_cache.h"
23 #include "core/common/recorder/web_event_recorder.h"
24 #include "core/components_v2/inspector/inspector_constants.h"
25 #include "core/pipeline_ng/pipeline_context.h"
26
27 namespace OHOS::Ace::Recorder {
28 constexpr uint32_t EXPOSURE_REGISTER_DELAY = 500;
29 constexpr int32_t MAX_CACHE_SIZE = 5;
30
31 struct ExposureWrapper {
32 WeakPtr<NG::FrameNode> node;
33 RefPtr<ExposureProcessor> processor;
34
ExposureWrapperOHOS::Ace::Recorder::ExposureWrapper35 ExposureWrapper(const WeakPtr<NG::FrameNode>& node, RefPtr<ExposureProcessor>&& processor)
36 : node(node), processor(processor)
37 {}
38 };
39
Get()40 EventController& EventController::Get()
41 {
42 static EventController eventController;
43 return eventController;
44 }
45
Register(const std::string & config,const std::shared_ptr<UIEventObserver> & observer)46 void EventController::Register(const std::string& config, const std::shared_ptr<UIEventObserver>& observer)
47 {
48 TAG_LOGI(AceLogTag::ACE_UIEVENT, "Register config");
49 UIEventClient client;
50 client.config.Init(config);
51 if (!client.config.IsEnable()) {
52 return;
53 }
54 client.observer = observer;
55 bool isWebEnable = client.config.IsCategoryEnable(static_cast<int32_t>(EventCategory::CATEGORY_WEB)) &&
56 !client.config.GetWebJsCode().empty();
57 std::unique_lock<std::shared_mutex> lock(cacheLock_);
58 clientList_.emplace_back(std::move(client));
59 lock.unlock();
60 bool isOriginExposureEnable = EventRecorder::Get().IsExposureRecordEnable();
61 NotifyConfigChange();
62 bool isExposureChanged = EventRecorder::Get().IsExposureRecordEnable() && !isOriginExposureEnable;
63 bool isWebChanged = false;
64 if (!hasWebProcessed_ && isWebEnable) {
65 isWebChanged = true;
66 hasWebProcessed_ = true;
67 cacheJsCode_ = client.config.GetWebJsCode();
68 }
69 if (isExposureChanged || isWebChanged) {
70 ApplyNewestConfig(isExposureChanged, isWebChanged);
71 }
72 NotifyCacheEventsIfNeed();
73 TAG_LOGI(AceLogTag::ACE_UIEVENT, "Register config end");
74 }
75
NotifyConfigChange()76 void EventController::NotifyConfigChange()
77 {
78 std::shared_lock<std::shared_mutex> lock(cacheLock_);
79 auto mergedConfig = std::make_shared<MergedConfig>();
80 int32_t size = static_cast<int32_t>(EventCategory::CATEGORY_END);
81 std::vector<bool> eventSwitch;
82 eventSwitch.resize(size, false);
83 eventSwitch[static_cast<int32_t>(EventCategory::CATEGORY_PAGE)] = true;
84 std::unordered_map<std::string, std::string> webIdentifierMap;
85 for (auto&& client : clientList_) {
86 if (!client.config.IsEnable()) {
87 continue;
88 }
89 for (int32_t i = 0; i < size; i++) {
90 eventSwitch[i] = eventSwitch[i] || client.config.IsCategoryEnable(i);
91 }
92 for (auto iter = client.config.GetConfig()->begin(); iter != client.config.GetConfig()->end(); iter++) {
93 auto nodeIt = mergedConfig->shareNodes.find(iter->first);
94 if (nodeIt != mergedConfig->shareNodes.end()) {
95 std::for_each(iter->second.shareNodes.begin(), iter->second.shareNodes.end(),
96 [&nodeIt](const std::list<std::string>::value_type& id) { nodeIt->second.emplace(id); });
97 } else {
98 std::unordered_set<std::string> nodeSet;
99 std::for_each(iter->second.shareNodes.begin(), iter->second.shareNodes.end(),
100 [&nodeSet](const std::list<std::string>::value_type& id) { nodeSet.emplace(id); });
101 mergedConfig->shareNodes.emplace(iter->first, std::move(nodeSet));
102 }
103
104 auto exposureIt = mergedConfig->exposureNodes.find(iter->first);
105 if (exposureIt != mergedConfig->exposureNodes.end()) {
106 std::for_each(iter->second.exposureCfgs.begin(), iter->second.exposureCfgs.end(),
107 [&exposureIt](
108 const std::list<ExposureCfg>::value_type& cfg) { exposureIt->second.emplace(cfg); });
109 } else {
110 std::unordered_set<ExposureCfg, ExposureCfgHash> exposureSet;
111 std::for_each(iter->second.exposureCfgs.begin(), iter->second.exposureCfgs.end(),
112 [&exposureSet](const std::list<ExposureCfg>::value_type& cfg) { exposureSet.emplace(cfg); });
113 mergedConfig->exposureNodes.emplace(iter->first, std::move(exposureSet));
114 }
115 }
116 if (!client.config.GetWebCategory().empty()) {
117 webIdentifierMap[client.config.GetWebCategory()] = client.config.GetWebIdentifier();
118 }
119 }
120 NodeDataCache::Get().UpdateConfig(std::move(mergedConfig));
121 EventRecorder::Get().UpdateEventSwitch(eventSwitch);
122 EventRecorder::Get().UpdateWebIdentifier(webIdentifierMap);
123 }
124
IsAllowNotify(const EventConfig & config,EventCategory category,int32_t eventType,const std::shared_ptr<std::unordered_map<std::string,std::string>> & eventParams)125 bool IsAllowNotify(const EventConfig& config, EventCategory category, int32_t eventType,
126 const std::shared_ptr<std::unordered_map<std::string, std::string>>& eventParams)
127 {
128 auto enable = config.IsEnable() && config.IsCategoryEnable(static_cast<int32_t>(category));
129 if (!enable) {
130 return false;
131 }
132 if (eventType == EventType::WEB_ACTION) {
133 return eventParams->count(KEY_WEB_CATEGORY) > 0 && eventParams->at(KEY_WEB_CATEGORY) == config.GetWebCategory();
134 }
135 return true;
136 }
137
NotifyCacheEventsIfNeed() const138 void EventController::NotifyCacheEventsIfNeed() const
139 {
140 std::shared_lock<std::shared_mutex> lock(cacheLock_);
141 if (cacheEvents_.empty()) {
142 return;
143 }
144 BackgroundTaskExecutor::GetInstance().PostTask([events = cacheEvents_, client = clientList_.back()]() {
145 for (const auto& event : events) {
146 if (IsAllowNotify(client.config, event.category, event.eventType, event.eventParams)) {
147 client.observer->NotifyUIEvent(event.eventType, *event.eventParams);
148 }
149 }
150 });
151 }
152
RecordWebEvent(const RefPtr<NG::UINode> & uiNode)153 inline void RecordWebEvent(const RefPtr<NG::UINode>& uiNode)
154 {
155 auto frameNode = AceType::DynamicCast<NG::FrameNode>(uiNode);
156 CHECK_NULL_VOID(frameNode);
157 auto pattern = frameNode->GetPattern<NG::Pattern>();
158 CHECK_NULL_VOID(pattern);
159 auto recorder = AceType::DynamicCast<WebEventRecorder>(pattern);
160 CHECK_NULL_VOID(recorder);
161 recorder->RecordWebEvent(false);
162 }
163
GetMatchedNodes(const std::string & pageUrl,const RefPtr<NG::UINode> & root,const std::unordered_set<ExposureCfg,ExposureCfgHash> & exposureSet,std::list<ExposureWrapper> & outputList,bool isWebChanged)164 void GetMatchedNodes(const std::string& pageUrl, const RefPtr<NG::UINode>& root,
165 const std::unordered_set<ExposureCfg, ExposureCfgHash>& exposureSet, std::list<ExposureWrapper>& outputList,
166 bool isWebChanged)
167 {
168 std::queue<RefPtr<NG::UINode>> elements;
169 ExposureCfg targetCfg = { "", 0.0, 0 };
170 elements.push(root);
171 while (!elements.empty()) {
172 auto current = elements.front();
173 elements.pop();
174 targetCfg.id = current->GetInspectorIdValue("");
175 if (!targetCfg.id.empty() && AceType::InstanceOf<NG::FrameNode>(current)) {
176 auto frameNode = AceType::DynamicCast<NG::FrameNode>(current);
177 auto cfgIter = exposureSet.find(targetCfg);
178 if (cfgIter != exposureSet.end()) {
179 outputList.emplace_back(ExposureWrapper(Referenced::WeakClaim(Referenced::RawPtr(frameNode)),
180 Referenced::MakeRefPtr<ExposureProcessor>(
181 pageUrl, targetCfg.id, cfgIter->ratio, cfgIter->duration)));
182 }
183 }
184 if (isWebChanged && current->GetTag() == V2::WEB_ETS_TAG) {
185 RecordWebEvent(current);
186 }
187 for (const auto& child : current->GetChildren()) {
188 elements.push(child);
189 }
190 }
191 }
192
ApplyNewestConfig(bool isExposureChanged,bool isWebChanged) const193 void EventController::ApplyNewestConfig(bool isExposureChanged, bool isWebChanged) const
194 {
195 TAG_LOGI(AceLogTag::ACE_UIEVENT, "ApplyNewestConfig isExposureChanged:%{public}d, isWebChanged:%{public}d",
196 isExposureChanged, isWebChanged);
197 std::shared_lock<std::shared_mutex> lock(cacheLock_);
198 if (clientList_.empty()) {
199 return;
200 }
201 auto containerId = EventRecorder::Get().GetContainerId();
202 auto config = clientList_.back().config.GetConfig();
203
204 auto context = NG::PipelineContext::GetContextByContainerId(containerId);
205 CHECK_NULL_VOID(context);
206 auto taskExecutor = context->GetTaskExecutor();
207 CHECK_NULL_VOID(taskExecutor);
208 taskExecutor->PostDelayedTask(
209 [config, isExposureChanged, isWebChanged]() {
210 EventController::Get().ApplyExposureCfgInner(config, isExposureChanged, isWebChanged);
211 EventController::Get().cacheJsCode_ = "";
212 },
213 TaskExecutor::TaskType::UI, EXPOSURE_REGISTER_DELAY, "EventController");
214 }
215
ApplyExposureCfgInner(const std::shared_ptr<Config> & config,bool isExposureChanged,bool isWebChanged) const216 void EventController::ApplyExposureCfgInner(
217 const std::shared_ptr<Config>& config, bool isExposureChanged, bool isWebChanged) const
218 {
219 auto containerId = EventRecorder::Get().GetContainerId();
220 auto pageUrl = GetPageUrlByContainerId(containerId);
221 if (pageUrl.empty()) {
222 return;
223 }
224 auto context = NG::PipelineContext::GetContextByContainerId(containerId);
225 CHECK_NULL_VOID(context);
226 auto rootNode = context->GetRootElement();
227 CHECK_NULL_VOID(rootNode);
228 std::unordered_set<ExposureCfg, ExposureCfgHash> exposureSet;
229 std::list<ExposureWrapper> targets;
230
231 if (isExposureChanged) {
232 auto pageIter = config->find(pageUrl);
233 if (pageIter != config->end() && pageIter->second.exposureCfgs.size() > 0) {
234 std::for_each(pageIter->second.exposureCfgs.begin(), pageIter->second.exposureCfgs.end(),
235 [&exposureSet](const std::list<ExposureCfg>::value_type& cfg) { exposureSet.emplace(cfg); });
236 }
237 }
238 GetMatchedNodes(pageUrl, rootNode, exposureSet, targets, isWebChanged);
239 for (auto& item : targets) {
240 item.processor->SetContainerId(containerId);
241 auto node = item.node.Upgrade();
242 CHECK_NULL_VOID(node);
243 node->SetExposureProcessor(item.processor);
244 }
245 }
246
Unregister(const std::shared_ptr<UIEventObserver> & observer)247 void EventController::Unregister(const std::shared_ptr<UIEventObserver>& observer)
248 {
249 std::unique_lock<std::shared_mutex> lock(cacheLock_);
250 auto iter = std::remove_if(clientList_.begin(), clientList_.end(),
251 [&observer](const UIEventClient& client) { return client.observer == observer; });
252 bool change = iter != clientList_.end();
253 clientList_.erase(iter, clientList_.end());
254 lock.unlock();
255 if (change) {
256 NotifyConfigChange();
257 }
258 }
259
CacheEventIfNeed(EventCategory category,int32_t eventType,const std::shared_ptr<std::unordered_map<std::string,std::string>> & eventParams)260 void EventController::CacheEventIfNeed(EventCategory category, int32_t eventType,
261 const std::shared_ptr<std::unordered_map<std::string, std::string>>& eventParams)
262 {
263 if (cacheEvents_.empty()) {
264 if (hasCached_) {
265 return;
266 } else {
267 cacheEvents_.emplace_back(CacheEvent { category, eventType, eventParams });
268 }
269 } else if (cacheEvents_.size() < MAX_CACHE_SIZE) {
270 cacheEvents_.emplace_back(CacheEvent { category, eventType, eventParams });
271 } else {
272 hasCached_ = true;
273 cacheEvents_.clear();
274 EventRecorder::Get().NotifyEventCacheEnd();
275 }
276 }
277
NotifyEvent(EventCategory category,int32_t eventType,const std::shared_ptr<std::unordered_map<std::string,std::string>> & eventParams)278 void EventController::NotifyEvent(EventCategory category, int32_t eventType,
279 const std::shared_ptr<std::unordered_map<std::string, std::string>>& eventParams)
280 {
281 {
282 std::shared_lock<std::shared_mutex> lock(cacheLock_);
283 CacheEventIfNeed(category, eventType, eventParams);
284 if (clientList_.empty()) {
285 return;
286 }
287 }
288 BackgroundTaskExecutor::GetInstance().PostTask([category, eventType, eventParams]() {
289 EventController::Get().NotifyEventSync(category, eventType, eventParams);
290 });
291 }
292
NotifyEventSync(EventCategory category,int32_t eventType,const std::shared_ptr<std::unordered_map<std::string,std::string>> & eventParams)293 void EventController::NotifyEventSync(EventCategory category, int32_t eventType,
294 const std::shared_ptr<std::unordered_map<std::string, std::string>>& eventParams)
295 {
296 std::shared_lock<std::shared_mutex> lock(cacheLock_);
297 for (auto&& client : clientList_) {
298 if (IsAllowNotify(client.config, category, eventType, eventParams)) {
299 client.observer->NotifyUIEvent(eventType, *eventParams);
300 }
301 }
302 }
303
GetWebJsCodeList()304 std::vector<std::string> EventController::GetWebJsCodeList()
305 {
306 std::vector<std::string> codeList;
307 std::shared_lock<std::shared_mutex> lock(cacheLock_);
308 for (auto&& client : clientList_) {
309 if (client.config.IsCategoryEnable(static_cast<int32_t>(EventCategory::CATEGORY_WEB)) &&
310 !client.config.GetWebJsCode().empty()) {
311 codeList.emplace_back(client.config.GetWebJsCode());
312 }
313 }
314 return codeList;
315 }
316 } // namespace OHOS::Ace::Recorder
317