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