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