1 /*
2  * Copyright (c) 2021-2022 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 "core/accessibility/accessibility_node.h"
17 
18 #include "core/common/container.h"
19 namespace OHOS::Ace {
20 namespace {
21 
22 const char ACCESSIBILITY_VALUE[] = "value";
23 const char ACCESSIBILITY_TYPE[] = "type";
24 const char ACCESSIBILITY_DISABLED[] = "disabled";
25 const char ACCESSIBILITY_GROUP[] = "accessibilitygroup";
26 const char ACCESSIBILITY_TEXT[] = "accessibilitytext";
27 const char ACCESSIBILITY_DESCRIPTION[] = "accessibilitydescription";
28 const char ACCESSIBILITY_IMPORTANCE[] = "accessibilityimportance";
29 const char ACCESSIBILITY_SHOW[] = "show";
30 const char ID[] = "id";
31 
32 const char INPUT_TYPE_CHECKBOX[] = "checkbox";
33 const char INPUT_TYPE_RADIO[] = "radio";
34 const char INPUT_TYPE_PASSWORD[] = "password";
35 
36 // common event definition
37 const char ACCESSIBILITY_EVENT[] = "accessibility";
38 const char CLICK[] = "click";
39 const char LONG_PRESS[] = "longpress";
40 const char FOCUS[] = "focus";
41 const char BLUR[] = "blur";
42 
StringToBool(const std::string & value)43 inline bool StringToBool(const std::string& value)
44 {
45     return value == "true";
46 }
47 
MergeItems(const std::vector<std::pair<std::string,std::string>> & newItems,std::vector<std::pair<std::string,std::string>> & items)48 void MergeItems(const std::vector<std::pair<std::string, std::string>>& newItems,
49     std::vector<std::pair<std::string, std::string>>& items)
50 {
51     if (items.empty()) {
52         items = newItems;
53     } else {
54         std::unordered_map<std::string, std::string> originItems;
55         std::vector<std::pair<std::string, std::string>> sameVec;
56         std::vector<std::pair<std::string, std::string>> diffVec;
57         for (const auto& item: items) {
58             originItems[item.first] = item.second;
59         }
60 
61         for (const auto& item: newItems) {
62             originItems[item.first] = item.second;
63         }
64 
65         // find same element
66         for (auto item = items.begin(); item != items.end(); item++) {
67             const auto iter = originItems.find(item->first);
68             if (iter != originItems.end()) {
69                 sameVec.emplace_back(iter->first, iter->second);
70             }
71         }
72 
73         // find different element
74         for (auto newItem = newItems.begin(); newItem != newItems.end(); newItem++) {
75             size_t diffFlagCount = 0;
76             for (auto item = items.begin(); item != items.end(); item++) {
77                 if (newItem->first != item->first) {
78                     diffFlagCount++;
79                 }
80             }
81             if (diffFlagCount == items.size()) {
82                 diffVec.emplace_back(newItem->first, newItem->second);
83             }
84         }
85         sameVec.insert(sameVec.end(), diffVec.begin(), diffVec.end());
86         items.clear();
87         items = sameVec;
88     }
89 }
90 
91 } // namespace
92 
AccessibilityNode(NodeId nodeId,const std::string & nodeName)93 AccessibilityNode::AccessibilityNode(NodeId nodeId, const std::string& nodeName) : nodeId_(nodeId), tag_(nodeName)
94 {
95     // Initialize member variable in bitfield
96     isEnabled_ = true;
97     visible_ = true;
98     shown_ = true;
99 }
100 
SetActionClickImpl(const ActionClickImpl & actionClickImpl)101 void AccessibilityNode::SetActionClickImpl(const ActionClickImpl& actionClickImpl)
102 {
103     actionClickImpl_ = actionClickImpl;
104 }
105 
ActionClick()106 bool AccessibilityNode::ActionClick()
107 {
108     if (actionClickImpl_) {
109         actionClickImpl_();
110         return true;
111     }
112     return false;
113 }
114 
SetActionLongClickImpl(const ActionLongClickImpl & actionLongClickImpl)115 void AccessibilityNode::SetActionLongClickImpl(const ActionLongClickImpl& actionLongClickImpl)
116 {
117     actionLongClickImpl_ = actionLongClickImpl;
118 }
119 
ActionLongClick()120 bool AccessibilityNode::ActionLongClick()
121 {
122     if (actionLongClickImpl_) {
123         actionLongClickImpl_();
124         return true;
125     }
126     return false;
127 }
128 
SetActionSetTextImpl(const ActionSetTextImpl & actionSetTextImpl)129 void AccessibilityNode::SetActionSetTextImpl(const ActionSetTextImpl& actionSetTextImpl)
130 {
131     actionSetTextImpl_ = actionSetTextImpl;
132 }
133 
ActionSetText(const std::string & text)134 bool AccessibilityNode::ActionSetText(const std::string& text)
135 {
136     if (actionSetTextImpl_) {
137         actionSetTextImpl_(text);
138         return true;
139     }
140     return false;
141 }
142 
SetActionScrollForward(const ActionScrollForwardImpl & actionScrollForwardImpl)143 void AccessibilityNode::SetActionScrollForward(const ActionScrollForwardImpl& actionScrollForwardImpl)
144 {
145     actionScrollForwardImpl_ = actionScrollForwardImpl;
146 }
147 
ActionScrollForward()148 bool AccessibilityNode::ActionScrollForward()
149 {
150     if (actionScrollForwardImpl_) {
151         return actionScrollForwardImpl_();
152     }
153     return false;
154 }
155 
SetActionScrollBackward(const ActionScrollBackwardImpl & actionScrollBackwardImpl)156 void AccessibilityNode::SetActionScrollBackward(const ActionScrollBackwardImpl& actionScrollBackwardImpl)
157 {
158     actionScrollBackwardImpl_ = actionScrollBackwardImpl;
159 }
160 
ActionScrollBackward()161 bool AccessibilityNode::ActionScrollBackward()
162 {
163     if (actionScrollBackwardImpl_) {
164         return actionScrollBackwardImpl_();
165     }
166     return false;
167 }
168 
SetActionAccessibilityFocusImpl(const ActionAccessibilityFocusImpl & actionAccessibilityFocusImpl)169 void AccessibilityNode::SetActionAccessibilityFocusImpl(
170     const ActionAccessibilityFocusImpl& actionAccessibilityFocusImpl)
171 {
172     actionAccessibilityFocusIdsImpl_ = actionAccessibilityFocusImpl;
173 }
174 
ActionAccessibilityFocus(bool isFocus)175 bool AccessibilityNode::ActionAccessibilityFocus(bool isFocus)
176 {
177     if (actionAccessibilityFocusIdsImpl_) {
178         actionAccessibilityFocusIdsImpl_(isFocus);
179         return true;
180     }
181     return false;
182 }
183 
SetActionFocusImpl(const ActionFocusImpl & actionFocusImpl)184 void AccessibilityNode::SetActionFocusImpl(const ActionFocusImpl& actionFocusImpl)
185 {
186     actionFocusImpl_ = actionFocusImpl;
187 }
188 
ActionFocus()189 bool AccessibilityNode::ActionFocus()
190 {
191     if (actionFocusImpl_) {
192         actionFocusImpl_();
193         return true;
194     }
195     return false;
196 }
197 
SetActionUpdateIdsImpl(const ActionUpdateIdsImpl & actionUpdateIdsImpl)198 void AccessibilityNode::SetActionUpdateIdsImpl(const ActionUpdateIdsImpl& actionUpdateIdsImpl)
199 {
200     actionUpdateIdsImpl_ = actionUpdateIdsImpl;
201 }
202 
ActionUpdateIds()203 void AccessibilityNode::ActionUpdateIds()
204 {
205     if (actionUpdateIdsImpl_) {
206         actionUpdateIdsImpl_();
207     }
208 }
209 
SetParentNode(const RefPtr<AccessibilityNode> & parentNode)210 void AccessibilityNode::SetParentNode(const RefPtr<AccessibilityNode>& parentNode)
211 {
212     parentNode_ = parentNode;
213 }
214 
SetPositionInfo(const PositionInfo & positionInfo)215 void AccessibilityNode::SetPositionInfo(const PositionInfo& positionInfo)
216 {
217     rect_.SetRect(positionInfo.left, positionInfo.top, positionInfo.width, positionInfo.height);
218 }
219 
SetAttr(const std::vector<std::pair<std::string,std::string>> & attrs)220 void AccessibilityNode::SetAttr(const std::vector<std::pair<std::string, std::string>>& attrs)
221 {
222     MergeItems(attrs, attrs_);
223 
224     for (const auto& attr : attrs) {
225         if (attr.first == ACCESSIBILITY_VALUE) {
226             text_ = attr.second;
227             if (tag_ == ACCESSIBILITY_TAG_TEXT && parentNode_.Upgrade() &&
228                 parentNode_.Upgrade()->GetTag() == ACCESSIBILITY_TAG_POPUP) {
229                 auto spParent = parentNode_.Upgrade();
230                 auto parentText = spParent->GetText() + text_;
231                 spParent->SetText(parentText);
232             }
233         } else if (attr.first == ACCESSIBILITY_DISABLED) {
234             isEnabled_ = !StringToBool(attr.second);
235         } else if (attr.first == ACCESSIBILITY_TYPE) {
236             inputType_ = attr.second;
237         } else if (attr.first == ACCESSIBILITY_GROUP) {
238             accessible_ = StringToBool(attr.second);
239         } else if (attr.first == ACCESSIBILITY_TEXT) {
240             accessibilityLabel_ = attr.second;
241         } else if (attr.first == ACCESSIBILITY_DESCRIPTION) {
242             accessibilityHint_ = attr.second;
243         } else if (attr.first == ACCESSIBILITY_IMPORTANCE) {
244             importantForAccessibility_ = attr.second;
245         } else if (attr.first == ID) {
246             jsComponentId_ = attr.second;
247         } else if (attr.first == ACCESSIBILITY_SHOW) {
248             shown_ = attr.second == "true";
249         }
250     }
251     SetOperableInfo();
252 }
253 
SetStyle(const std::vector<std::pair<std::string,std::string>> & styles)254 void AccessibilityNode::SetStyle(const std::vector<std::pair<std::string, std::string>>& styles)
255 {
256     MergeItems(styles, styles_);
257 }
258 
AddEvent(int32_t pageId,const std::vector<std::string> & events)259 void AccessibilityNode::AddEvent(int32_t pageId, const std::vector<std::string>& events)
260 {
261     for (const auto& event : events) {
262         if (event == ACCESSIBILITY_EVENT) {
263             onAccessibilityEventId_ = EventMarker(std::to_string(nodeId_), event, pageId);
264         } else if (event == CLICK) {
265             onClickId_ = EventMarker(std::to_string(nodeId_), event, pageId);
266             SetClickableState(true);
267         } else if (event == LONG_PRESS) {
268             onLongPressId_ = EventMarker(std::to_string(nodeId_), event, pageId);
269             SetLongClickableState(true);
270         } else if (event == FOCUS) {
271             onFocusId_ = EventMarker(std::to_string(nodeId_), event, pageId);
272         } else if (event == BLUR) {
273             onBlurId_ = EventMarker(std::to_string(nodeId_), event, pageId);
274         }
275     }
276     AddSupportAction(AceAction::CUSTOM_ACTION);
277     AddSupportAction(AceAction::GLOBAL_ACTION_BACK);
278 }
279 
AddNode(const RefPtr<AccessibilityNode> & node,int32_t slot)280 void AccessibilityNode::AddNode(const RefPtr<AccessibilityNode>& node, int32_t slot)
281 {
282     CHECK_NULL_VOID(node);
283     auto isExist = std::find_if(children_.begin(), children_.end(),
284         [node](const RefPtr<AccessibilityNode>& child) { return child->GetNodeId() == node->GetNodeId(); });
285     if (isExist != children_.end()) {
286         return;
287     }
288     auto pos = children_.begin();
289     std::advance(pos, slot);
290     children_.insert(pos, node);
291 }
292 
RemoveNode(const RefPtr<AccessibilityNode> & node)293 void AccessibilityNode::RemoveNode(const RefPtr<AccessibilityNode>& node)
294 {
295     CHECK_NULL_VOID(node);
296     children_.remove_if(
297         [node](const RefPtr<AccessibilityNode>& child) { return node->GetNodeId() == child->GetNodeId(); });
298 }
299 
Mount(int32_t slot)300 void AccessibilityNode::Mount(int32_t slot)
301 {
302     auto parentNode = parentNode_.Upgrade();
303     CHECK_NULL_VOID(parentNode);
304     parentNode->AddNode(AceType::Claim(this), slot);
305 }
306 
AddOffsetForChildren(const Offset & offset)307 void AccessibilityNode::AddOffsetForChildren(const Offset& offset)
308 {
309     SetLeft(GetLeft() + offset.GetX());
310     SetTop(GetTop() + offset.GetY());
311     for (const auto& child : GetChildList()) {
312         child->AddOffsetForChildren(offset);
313     }
314 }
315 
SetOperableInfo()316 void AccessibilityNode::SetOperableInfo()
317 {
318     static const LinearMapNode<OperableInfo> nodeOperatorMap[] = {
319         { ACCESSIBILITY_TAG_BUTTON,
320             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
321         { ACCESSIBILITY_TAG_CALENDAR,
322             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
323         { ACCESSIBILITY_TAG_CANVAS,
324             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
325         { ACCESSIBILITY_TAG_CHART,
326             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
327         { ACCESSIBILITY_TAG_CLOCK,
328             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
329         { ACCESSIBILITY_TAG_DIALOG,
330             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
331         { ACCESSIBILITY_TAG_DIV,
332             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
333         { ACCESSIBILITY_TAG_DIVIDER,
334             { .checkable = false, .clickable = false, .scrollable = false,
335               .longClickable = false, .focusable = false } },
336         { ACCESSIBILITY_TAG_IMAGE,
337             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
338         { ACCESSIBILITY_TAG_INPUT,
339             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
340         { ACCESSIBILITY_TAG_LABEL,
341             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
342         { ACCESSIBILITY_TAG_LIST,
343             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
344         { ACCESSIBILITY_TAG_LIST_ITEM,
345             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
346         { ACCESSIBILITY_TAG_LIST_ITEM_GROUP,
347             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
348         { ACCESSIBILITY_TAG_MARQUEE,
349             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
350         { ACCESSIBILITY_TAG_NAVIGATION_BAR,
351             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
352         { ACCESSIBILITY_TAG_OPTION,
353             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
354         { ACCESSIBILITY_TAG_POPUP,
355             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
356         { ACCESSIBILITY_TAG_PROGRESS,
357             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
358         { ACCESSIBILITY_TAG_RATING,
359             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
360         { ACCESSIBILITY_TAG_REFRESH,
361             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
362         { ACCESSIBILITY_TAG_SELECT,
363             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
364         { ACCESSIBILITY_TAG_SLIDER,
365             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
366         { ACCESSIBILITY_TAG_SPAN,
367             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
368         { ACCESSIBILITY_TAG_STACK,
369             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
370         { ACCESSIBILITY_TAG_SWIPER,
371             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
372         { ACCESSIBILITY_TAG_SWITCH,
373             { .checkable = true, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
374         { ACCESSIBILITY_TAG_TAB_BAR,
375             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
376         { ACCESSIBILITY_TAG_TAB_CONTENT,
377             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
378         { ACCESSIBILITY_TAG_TABS,
379             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
380         { ACCESSIBILITY_TAG_TEXT,
381             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
382         { ACCESSIBILITY_TAG_TEXTAREA,
383             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
384         { ACCESSIBILITY_TAG_VIDEO,
385             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
386     };
387 
388     // set node operable info
389     int64_t operateIter = BinarySearchFindIndex(nodeOperatorMap, ArraySize(nodeOperatorMap), tag_.c_str());
390     if (operateIter != -1) {
391         isCheckable_ = nodeOperatorMap[operateIter].value.checkable;
392         isScrollable_ = nodeOperatorMap[operateIter].value.scrollable;
393         isFocusable_ = nodeOperatorMap[operateIter].value.focusable;
394         if (isFocusable_) {
395             AddSupportAction(AceAction::ACTION_FOCUS);
396         }
397     } else {
398         isCheckable_ = false;
399         isClickable_ = false;
400         isScrollable_ = false;
401         isLongClickable_ = false;
402         isFocusable_ = false;
403     }
404 
405     if (tag_ == ACCESSIBILITY_TAG_INPUT) {
406         if (inputType_ == INPUT_TYPE_CHECKBOX || inputType_ == INPUT_TYPE_RADIO) {
407             isCheckable_ = true;
408         } else if (inputType_ == INPUT_TYPE_PASSWORD) {
409             isPassword_ = true;
410         }
411     }
412 }
413 
GetSupportAction(uint64_t enableActions) const414 std::unordered_set<AceAction> AccessibilityNode::GetSupportAction(uint64_t enableActions) const
415 {
416     static const AceAction allActions[] = {
417         AceAction::ACTION_NONE, AceAction::GLOBAL_ACTION_BACK, AceAction::CUSTOM_ACTION, AceAction::ACTION_CLICK,
418         AceAction::ACTION_LONG_CLICK, AceAction::ACTION_SCROLL_FORWARD, AceAction::ACTION_SCROLL_BACKWARD,
419         AceAction::ACTION_FOCUS, AceAction::ACTION_ACCESSIBILITY_FOCUS, AceAction::ACTION_CLEAR_ACCESSIBILITY_FOCUS,
420         AceAction::ACTION_NEXT_AT_MOVEMENT_GRANULARITY, AceAction::ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
421         AceAction::ACTION_SET_TEXT,
422     };
423 
424     std::unordered_set<AceAction> supportActions;
425     if (supportActions_ == 0) {
426         return supportActions;
427     }
428 
429     auto finalSupportActions = supportActions_ & enableActions;
430     for (auto action : allActions) {
431         if ((finalSupportActions & (1UL << static_cast<uint32_t>(action))) != 0) {
432             supportActions.emplace(action);
433         }
434     }
435     return supportActions;
436 }
437 
SetFocusChangeEventMarker(const EventMarker & eventId)438 void AccessibilityNode::SetFocusChangeEventMarker(const EventMarker& eventId)
439 {
440     if (eventId.IsEmpty()) {
441         return;
442     }
443 
444     auto container = Container::Current();
445     CHECK_NULL_VOID(container);
446 #ifndef NG_BUILD
447     auto pipelineContext = AceType::DynamicCast<PipelineContext>(container->GetPipelineContext());
448     CHECK_NULL_VOID(pipelineContext);
449     focusChangeEventId_ =
450         AceAsyncEvent<void(const std::string&)>::Create(eventId, pipelineContext);
451 #endif
452 }
453 
OnFocusChange(bool isFocus)454 void AccessibilityNode::OnFocusChange(bool isFocus)
455 {
456     CHECK_NULL_VOID(focusChangeEventId_);
457     auto json = JsonUtil::Create(true);
458     json->Put("eventType", isFocused_ ? "1" : "2");
459     focusChangeEventId_(json->ToString());
460 }
461 
462 } // namespace OHOS::Ace
463