1 /*
2  * Copyright (c) 2022-2024 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_third_provider_interaction_operation.h"
16 
17 #include <algorithm>
18 
19 #include "accessibility_constants.h"
20 #include "accessibility_event_info.h"
21 #include "accessibility_system_ability_client.h"
22 #include "adapter/ohos/entrance/ace_application_info.h"
23 #include "adapter/ohos/entrance/ace_container.h"
24 #include "base/log/ace_trace.h"
25 #include "base/log/dump_log.h"
26 #include "base/log/event_report.h"
27 #include "base/log/log.h"
28 #include "base/utils/linear_map.h"
29 #include "base/utils/string_utils.h"
30 #include "base/utils/utils.h"
31 #include "core/accessibility/accessibility_manager_ng.h"
32 #include "core/components_ng/base/inspector.h"
33 #include "core/components_v2/inspector/inspector_constants.h"
34 #include "core/pipeline/pipeline_context.h"
35 #include "core/pipeline_ng/pipeline_context.h"
36 #include "frameworks/bridge/common/dom/dom_type.h"
37 #include "frameworks/core/components_ng/pattern/web/web_pattern.h"
38 #include "js_accessibility_manager.h"
39 #include "js_third_accessibility_hover_ng.h"
40 #include "nlohmann/json.hpp"
41 
42 using namespace OHOS::Accessibility;
43 using namespace OHOS::AccessibilityConfig;
44 using namespace std;
45 
46 namespace OHOS::Ace::Framework {
47 constexpr int32_t ACCESSIBILITY_FOCUS_WITHOUT_EVENT = -2100001;
48 
GetElementInfoForThird(int64_t elementId,AccessibilityElementInfo & info,int64_t hostElementId)49 bool AccessibilityHoverManagerForThirdNG::GetElementInfoForThird(
50     int64_t elementId,
51     AccessibilityElementInfo& info,
52     int64_t hostElementId)
53 {
54     auto jsThirdProviderOperator =
55         GetJsThirdProviderInteractionOperation(hostElementId).lock();
56     if (jsThirdProviderOperator == nullptr) {
57         TAG_LOGE(AceLogTag::ACE_ACCESSIBILITY,
58             "third jsThirdProviderOperator ptr is null, hostElementId %{public}" PRId64,
59             hostElementId);
60         return false;
61     }
62 
63     std::list<Accessibility::AccessibilityElementInfo> infos;
64     bool ret = jsThirdProviderOperator->FindAccessibilityNodeInfosByIdFromProvider(
65         elementId, 0, 0, infos);
66     if ((!ret) || (infos.size() == 0)) {
67         TAG_LOGE(AceLogTag::ACE_ACCESSIBILITY,
68             "cannot get third elementinfo :%{public}" PRId64 ", ret: %{public}d",
69             elementId, ret);
70         return false;
71     }
72     info = infos.front();
73     return true;
74 }
75 
UpdateSearchStrategyByHitTestModeStr(std::string & hitTestMode,bool & shouldSearchSelf,bool & shouldSearchChildren)76 void AccessibilityHoverManagerForThirdNG::UpdateSearchStrategyByHitTestModeStr(
77     std::string& hitTestMode,
78     bool& shouldSearchSelf,
79     bool& shouldSearchChildren)
80 {
81     if (hitTestMode == "HitTestMode.Block") {
82         shouldSearchChildren = false;
83     }  else if (hitTestMode == "HitTestMode.None") {
84         shouldSearchSelf = false;
85     }
86 }
87 
HasAccessibilityTextOrDescription(const AccessibilityElementInfo & nodeInfo)88 bool AccessibilityHoverManagerForThirdNG::HasAccessibilityTextOrDescription(
89     const AccessibilityElementInfo& nodeInfo)
90 {
91     std::optional<std::string> accessibilityText = nodeInfo.GetAccessibilityText();
92     std::optional<std::string> accessibilityDescription = nodeInfo.GetDescriptionInfo();
93     return !accessibilityText.value_or("").empty() ||
94         !accessibilityDescription.value_or("").empty();
95 }
96 
IsAccessibilityFocusable(const AccessibilityElementInfo & nodeInfo)97 bool AccessibilityHoverManagerForThirdNG::IsAccessibilityFocusable(
98     const AccessibilityElementInfo& nodeInfo)
99 {
100     auto level = nodeInfo.GetAccessibilityLevel();
101     if (level == NG::AccessibilityProperty::Level::YES_STR) {
102         return true;
103     }
104     if (level == NG::AccessibilityProperty::Level::NO_STR) {
105         return false;
106     }
107     if (nodeInfo.GetAccessibilityGroup() ||
108         !nodeInfo.GetActionList().empty() ||
109         HasAccessibilityTextOrDescription(nodeInfo) ||
110         !nodeInfo.GetContent().empty()) {
111         return true;
112     }
113     // expand to enabled and clickable
114     // default tag
115     if (NG::AccessibilityProperty::IsAccessibilityFocusableTag(
116         nodeInfo.GetComponentType()) == true) {
117         return true;
118     }
119     return false;
120 }
121 
GetSearchStrategyForThird(const AccessibilityElementInfo & nodeInfo)122 std::pair<bool, bool> AccessibilityHoverManagerForThirdNG::GetSearchStrategyForThird(
123     const AccessibilityElementInfo& nodeInfo)
124 {
125     bool shouldSearchSelf = true;
126     bool shouldSearchChildren = true;
127     auto level = NG::AccessibilityProperty::Level::AUTO;
128     do {
129         level = nodeInfo.GetAccessibilityLevel();
130         bool hasAccessibilityText = HasAccessibilityTextOrDescription(nodeInfo);
131         if (level == NG::AccessibilityProperty::Level::YES_STR) {
132             break;
133         } else if (level == NG::AccessibilityProperty::Level::NO_HIDE_DESCENDANTS) {
134             shouldSearchSelf = false;
135             shouldSearchChildren = false;
136             break;
137         } else {
138             if (level == NG::AccessibilityProperty::Level::NO_STR) {
139                 shouldSearchSelf = false;
140             } else {
141                 // shouldSearchSelf is true here
142                 if (hasAccessibilityText) {
143                     break;
144                 }
145             }
146         }
147 
148         auto hitTestMode = nodeInfo.GetHitTestBehavior();
149         UpdateSearchStrategyByHitTestModeStr(
150             hitTestMode, shouldSearchSelf, shouldSearchChildren);
151     } while (0);
152 
153     if (IsAccessibilityFocusable(nodeInfo) == false) {
154         shouldSearchSelf = false;
155     }
156 
157     return std::make_pair(shouldSearchSelf, shouldSearchChildren);
158 }
159 
160 
HoverPathForThirdRecursive(const int64_t hostElementId,const NG::PointF & hoverPoint,const AccessibilityElementInfo & nodeInfo,AccessibilityHoverTestPathForThird & path,NG::OffsetF hostOffset)161 bool AccessibilityHoverManagerForThirdNG::HoverPathForThirdRecursive(
162     const int64_t hostElementId,
163     const NG::PointF& hoverPoint,
164     const AccessibilityElementInfo& nodeInfo,
165     AccessibilityHoverTestPathForThird& path,
166     NG::OffsetF hostOffset)
167 {
168     bool hitTarget = false;
169     auto [shouldSearchSelf, shouldSearchChildren]
170         = GetSearchStrategyForThird(nodeInfo);
171     auto rectInScreen = nodeInfo.GetRectInScreen();
172     auto left = rectInScreen.GetLeftTopXScreenPostion();
173     auto right = rectInScreen.GetLeftTopYScreenPostion();
174     auto width = rectInScreen.GetRightBottomXScreenPostion() - rectInScreen.GetLeftTopXScreenPostion();
175     auto height = rectInScreen.GetRightBottomYScreenPostion() - rectInScreen.GetLeftTopYScreenPostion();
176     NG::RectF rect { left, right, width, height };
177     rect = rect - hostOffset;
178     bool hitSelf = rect.IsInnerRegion(hoverPoint);
179     if (hitSelf && shouldSearchSelf) {
180         hitTarget = true;
181         path.push_back(nodeInfo.GetAccessibilityId());
182     }
183     TAG_LOGD(AceLogTag::ACE_ACCESSIBILITY,
184             "third hover elementId :%{public}" PRId64\
185             ", shouldSearchSelf: %{public}d shouldSearchChildren: %{public}d hitTarget: %{public}d ",
186             nodeInfo.GetAccessibilityId(), shouldSearchSelf, shouldSearchChildren, hitTarget);
187     if (shouldSearchChildren) {
188         auto childrenIds = nodeInfo.GetChildIds();
189         for (auto childId = childrenIds.rbegin(); childId != childrenIds.rend(); ++childId) {
190             AccessibilityElementInfo childInfo;
191             if (GetElementInfoForThird(*childId, childInfo, hostElementId) == false) {
192                 break;
193             }
194             if (HoverPathForThirdRecursive(
195                 hostElementId, hoverPoint, childInfo, path, hostOffset)) {
196                 return true;
197             }
198         }
199     }
200     return hitTarget;
201 }
202 
HoverPathForThird(const int64_t hostElementId,const NG::PointF & point,AccessibilityElementInfo & rootInfo,NG::OffsetF hostOffset)203 AccessibilityHoverTestPathForThird AccessibilityHoverManagerForThirdNG::HoverPathForThird(
204     const int64_t hostElementId,
205     const NG::PointF& point,
206     AccessibilityElementInfo& rootInfo,
207     NG::OffsetF hostOffset)
208 {
209     AccessibilityHoverTestPathForThird path;
210     HoverPathForThirdRecursive(
211         hostElementId, point, rootInfo, path, hostOffset);
212     return path;
213 }
214 
ResetHoverForThirdState()215 void AccessibilityHoverManagerForThirdNG::ResetHoverForThirdState()
216 {
217     hoverForThirdState_.idle = true;
218     hoverForThirdState_.nodesHovering.clear();
219 }
220 
HandleAccessibilityHoverForThird(const AccessibilityHoverForThirdConfig & config)221 void AccessibilityHoverManagerForThirdNG::HandleAccessibilityHoverForThird(
222     const AccessibilityHoverForThirdConfig& config)
223 {
224     CHECK_NULL_VOID(config.hostNode);
225     if (config.eventType == NG::AccessibilityHoverEventType::ENTER) {
226         ResetHoverForThirdState();
227     }
228     std::vector<int64_t> currentNodesHovering;
229     std::vector<int64_t> lastNodesHovering = hoverForThirdState_.nodesHovering;
230     if (config.eventType != NG::AccessibilityHoverEventType::EXIT) {
231         AccessibilityElementInfo rootInfo;
232         if (GetElementInfoForThird(-1, rootInfo, config.hostElementId) == false) {
233             return;
234         }
235         auto [displayOffset, err] = config.hostNode->GetPaintRectGlobalOffsetWithTranslate();
236         AccessibilityHoverTestPathForThird path =
237             HoverPathForThird(config.hostElementId, config.point, rootInfo, displayOffset);
238         for (const auto& node: path) {
239             currentNodesHovering.push_back(node);
240         }
241     }
242     static constexpr int64_t INVALID_NODE_ID = -1;
243     int64_t lastHoveringId = INVALID_NODE_ID;
244     if (!lastNodesHovering.empty()) {
245         lastHoveringId = lastNodesHovering.back();
246     }
247     int64_t currentHoveringId = INVALID_NODE_ID;
248     if (!currentNodesHovering.empty()) {
249         currentHoveringId = currentNodesHovering.back();
250     }
251     auto jsThirdProviderOperator = GetJsThirdProviderInteractionOperation(
252         config.hostElementId).lock();
253     if (jsThirdProviderOperator == nullptr) {
254         TAG_LOGE(AceLogTag::ACE_ACCESSIBILITY, "jsThirdProviderOperator is null, "
255             "hostElementId %{public}" PRId64, config.hostElementId);
256         return;
257     }
258     if (lastHoveringId != INVALID_NODE_ID && lastHoveringId != currentHoveringId) {
259         jsThirdProviderOperator->SendAccessibilityAsyncEventForThird(lastHoveringId,
260             Accessibility::EventType::TYPE_VIEW_HOVER_EXIT_EVENT);
261     }
262     if ((currentHoveringId != INVALID_NODE_ID) && (currentHoveringId != lastHoveringId)) {
263         jsThirdProviderOperator->SendAccessibilityAsyncEventForThird(currentHoveringId,
264             Accessibility::EventType::TYPE_VIEW_HOVER_ENTER_EVENT);
265     }
266     hoverForThirdState_.nodesHovering = std::move(currentNodesHovering);
267     hoverForThirdState_.time = config.time;
268     hoverForThirdState_.source = config.sourceType;
269     hoverForThirdState_.idle = config.eventType == NG::AccessibilityHoverEventType::EXIT;
270 }
271 
ClearThirdAccessibilityFocus(const RefPtr<NG::FrameNode> & hostNode)272 bool AccessibilityHoverManagerForThirdNG::ClearThirdAccessibilityFocus(
273     const RefPtr<NG::FrameNode>& hostNode)
274 {
275     CHECK_NULL_RETURN(hostNode, false);
276     RefPtr<NG::RenderContext> renderContext = hostNode->GetRenderContext();
277     CHECK_NULL_RETURN(renderContext, false);
278     renderContext->UpdateAccessibilityFocus(false);
279     return true;
280 }
281 
ActThirdAccessibilityFocus(int64_t elementId,const AccessibilityElementInfo & nodeInfo,const RefPtr<NG::FrameNode> & hostNode,const RefPtr<NG::PipelineContext> & context,bool isNeedClear)282 bool AccessibilityHoverManagerForThirdNG::ActThirdAccessibilityFocus(
283     int64_t elementId,
284     const AccessibilityElementInfo& nodeInfo,
285     const RefPtr<NG::FrameNode>& hostNode,
286     const RefPtr<NG::PipelineContext>& context,
287     bool isNeedClear)
288 {
289     CHECK_NULL_RETURN(hostNode, false);
290     RefPtr<NG::RenderContext> renderContext = nullptr;
291     renderContext = hostNode->GetRenderContext();
292     CHECK_NULL_RETURN(renderContext, false);
293     if (isNeedClear) {
294         renderContext->UpdateAccessibilityFocus(false);
295         TAG_LOGD(AceLogTag::ACE_ACCESSIBILITY,
296             "third act Accessibility element Id %{public}" PRId64 "Focus clear",
297             nodeInfo.GetAccessibilityId());
298         return true;
299     }
300     renderContext->UpdateAccessibilityFocus(false);
301     auto [displayOffset, err] = hostNode->GetPaintRectGlobalOffsetWithTranslate();
302     auto rectInScreen = nodeInfo.GetRectInScreen();
303     auto left = rectInScreen.GetLeftTopXScreenPostion() - static_cast<int32_t>(displayOffset.GetX());
304     auto right = rectInScreen.GetLeftTopYScreenPostion() - static_cast<int32_t>(displayOffset.GetY());
305     auto width = rectInScreen.GetRightBottomXScreenPostion() - rectInScreen.GetLeftTopXScreenPostion();
306     auto height = rectInScreen.GetRightBottomYScreenPostion() - rectInScreen.GetLeftTopYScreenPostion();
307     NG::RectT<int32_t> rectInt { static_cast<int32_t>(left), static_cast<int32_t>(right),
308         static_cast<int32_t>(width), static_cast<int32_t>(height) };
309 
310     renderContext->UpdateAccessibilityFocusRect(rectInt);
311     renderContext->UpdateAccessibilityFocus(true, ACCESSIBILITY_FOCUS_WITHOUT_EVENT);
312     TAG_LOGD(AceLogTag::ACE_ACCESSIBILITY,
313             "third act Accessibility element Id %{public}" PRId64 "Focus",
314             nodeInfo.GetAccessibilityId());
315     return true;
316 }
317 
RegisterJsThirdProviderInteractionOperation(int64_t hostElementId,const std::shared_ptr<JsThirdProviderInteractionOperation> & jsThirdProviderOperator)318 void AccessibilityHoverManagerForThirdNG::RegisterJsThirdProviderInteractionOperation(
319     int64_t hostElementId,
320     const std::shared_ptr<JsThirdProviderInteractionOperation>& jsThirdProviderOperator)
321 {
322     jsThirdProviderOperator_[hostElementId] = jsThirdProviderOperator;
323 }
324 
DeregisterJsThirdProviderInteractionOperation(int64_t hostElementId)325 void AccessibilityHoverManagerForThirdNG::DeregisterJsThirdProviderInteractionOperation(
326     int64_t hostElementId)
327 {
328     jsThirdProviderOperator_.erase(hostElementId);
329 }
330 
331 namespace {
332 enum class DumpMode {
333     TREE,
334     NODE,
335     HANDLE_EVENT,
336     HOVER_TEST
337 };
338 
339 struct DumpInfoArgument {
340     bool useWindowId = false;
341     DumpMode mode = DumpMode::TREE;
342     bool isDumpSimplify = false;
343     bool verbose = false;
344     int64_t rootId = -1;
345     int32_t pointX = 0;
346     int32_t pointY = 0;
347     int64_t nodeId = -1;
348     int32_t action = 0;
349 };
350 
GetDumpInfoArgument(const std::vector<std::string> & params,DumpInfoArgument & argument)351 bool GetDumpInfoArgument(const std::vector<std::string>& params, DumpInfoArgument& argument)
352 {
353     argument.isDumpSimplify = params[0].compare("-simplify") == 0;
354     for (auto arg = params.begin() + 1; arg != params.end(); ++arg) {
355         if (*arg == "-w") {
356             argument.useWindowId = true;
357         } else if (*arg == "--root") {
358             ++arg;
359             if (arg == params.end()) {
360                 DumpLog::GetInstance().Print(std::string("Error: --root is used to set the root node, ") +
361                     "e.g. '--root ${AccessibilityId}'!");
362                 return false;
363             }
364             argument.rootId = StringUtils::StringToLongInt(*arg);
365         } else if (*arg == "--hover-test") {
366             argument.mode = DumpMode::HOVER_TEST;
367             static constexpr int32_t NUM_POINT_DIMENSION = 2;
368             if (std::distance(arg, params.end()) <= NUM_POINT_DIMENSION) {
369                 DumpLog::GetInstance().Print(std::string("Error: --hover-test is used to get nodes at a point ") +
370                     "relative to the root node, e.g. '--hover-test ${x} ${y}'!");
371                 return false;
372             }
373             ++arg;
374             argument.pointX = StringUtils::StringToInt(*arg);
375             ++arg;
376             argument.pointY = StringUtils::StringToInt(*arg);
377         } else if (*arg == "-v") {
378             argument.verbose = true;
379         } else if (*arg == "-json") {
380             argument.mode = DumpMode::TREE;
381         } else {
382             if (argument.mode == DumpMode::NODE) {
383                 argument.mode = DumpMode::HANDLE_EVENT;
384                 argument.action = StringUtils::StringToInt(*arg);
385                 break;
386             } else {
387                 argument.mode = DumpMode::NODE;
388                 argument.nodeId = StringUtils::StringToLongInt(*arg);
389             }
390         }
391     }
392     return true;
393 }
394 } // namespace
395 
DumpPropertyForThird(int64_t elementId,const WeakPtr<JsAccessibilityManager> & jsAccessibilityManager,const std::shared_ptr<JsThirdProviderInteractionOperation> & jsThirdProviderOperator)396 void AccessibilityHoverManagerForThirdNG::DumpPropertyForThird(
397     int64_t elementId,
398     const WeakPtr<JsAccessibilityManager>& jsAccessibilityManager,
399     const std::shared_ptr<JsThirdProviderInteractionOperation>& jsThirdProviderOperator)
400 {
401     auto jsAccessibilityManagerTemp = jsAccessibilityManager.Upgrade();
402     CHECK_NULL_VOID(jsAccessibilityManagerTemp);
403     int64_t splitElementId = AccessibilityElementInfo::UNDEFINED_ACCESSIBILITY_ID;
404     int32_t splitTreeId = AccessibilityElementInfo::UNDEFINED_TREE_ID;
405     AccessibilitySystemAbilityClient::GetTreeIdAndElementIdBySplitElementId(
406         elementId, splitElementId, splitTreeId);
407     std::list<Accessibility::AccessibilityElementInfo> infos;
408     bool ret = jsThirdProviderOperator->FindAccessibilityNodeInfosByIdFromProvider(
409         splitElementId, 0, 0, infos);
410     if ((!ret) || (infos.size() == 0)) {
411         return;
412     }
413 
414     Accessibility::AccessibilityElementInfo info = infos.front();
415     jsAccessibilityManagerTemp->DumpCommonPropertyNG(info, splitTreeId);
416     jsAccessibilityManagerTemp->DumpAccessibilityPropertyNG(info);
417     DumpLog::GetInstance().Print(0, info.GetComponentType(), info.GetChildCount());
418 }
419 
OnDumpChildInfoForThirdRecursive(int64_t hostElementId,const std::vector<std::string> & params,std::vector<std::string> & info,const WeakPtr<JsAccessibilityManager> & jsAccessibilityManager)420 bool AccessibilityHoverManagerForThirdNG::OnDumpChildInfoForThirdRecursive(
421     int64_t hostElementId,
422     const std::vector<std::string>& params,
423     std::vector<std::string>& info,
424     const WeakPtr<JsAccessibilityManager>& jsAccessibilityManager)
425 {
426     DumpInfoArgument argument;
427     if (GetDumpInfoArgument(params, argument) == false) {
428         return true;
429     }
430     auto jsThirdProviderOperator =
431         GetJsThirdProviderInteractionOperation(hostElementId).lock();
432     if (jsThirdProviderOperator == nullptr) {
433         return true;
434     }
435     switch (argument.mode) {
436         case DumpMode::NODE:
437             DumpPropertyForThird(argument.nodeId, jsAccessibilityManager, jsThirdProviderOperator);
438             break;
439         case DumpMode::TREE:
440         case DumpMode::HANDLE_EVENT:
441         case DumpMode::HOVER_TEST:
442         default:
443             DumpLog::GetInstance().Print("Error: invalid arguments!");
444             break;
445     }
446     return true;
447 }
448 
449 
450 } // namespace OHOS::Ace::Framework
451