1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/accessibility/ax_event_generator.h"
6
7 #include <algorithm>
8
9 #include "base/stl_util.h"
10 #include "ui/accessibility/ax_enums.mojom.h"
11 #include "ui/accessibility/ax_node.h"
12 #include "ui/accessibility/ax_role_properties.h"
13
14 namespace ui {
15 namespace {
16
IsActiveLiveRegion(const AXTreeObserver::Change & change)17 bool IsActiveLiveRegion(const AXTreeObserver::Change& change) {
18 return change.node->data().HasStringAttribute(
19 ax::mojom::StringAttribute::kLiveStatus) &&
20 change.node->data().GetStringAttribute(
21 ax::mojom::StringAttribute::kLiveStatus) != "off";
22 }
23
IsContainedInLiveRegion(const AXTreeObserver::Change & change)24 bool IsContainedInLiveRegion(const AXTreeObserver::Change& change) {
25 return change.node->data().HasStringAttribute(
26 ax::mojom::StringAttribute::kContainerLiveStatus) &&
27 change.node->data().HasStringAttribute(
28 ax::mojom::StringAttribute::kName);
29 }
30
HasEvent(const std::set<AXEventGenerator::EventParams> & node_events,AXEventGenerator::Event event)31 bool HasEvent(const std::set<AXEventGenerator::EventParams>& node_events,
32 AXEventGenerator::Event event) {
33 for (auto& iter : node_events) {
34 if (iter.event == event)
35 return true;
36 }
37 return false;
38 }
39
RemoveEvent(std::set<AXEventGenerator::EventParams> * node_events,AXEventGenerator::Event event)40 void RemoveEvent(std::set<AXEventGenerator::EventParams>* node_events,
41 AXEventGenerator::Event event) {
42 for (auto& iter : *node_events) {
43 if (iter.event == event) {
44 node_events->erase(iter);
45 return;
46 }
47 }
48 }
49
HasOtherLiveRegionEvent(const std::set<AXEventGenerator::EventParams> & events)50 bool HasOtherLiveRegionEvent(
51 const std::set<AXEventGenerator::EventParams>& events) {
52 auto is_live_region_event = [](const AXEventGenerator::EventParams& params) {
53 return params.event == AXEventGenerator::Event::ALERT ||
54 params.event == AXEventGenerator::Event::LIVE_REGION_CREATED;
55 };
56
57 return std::find_if(events.begin(), events.end(), is_live_region_event) !=
58 events.end();
59 }
60
61 } // namespace
62
EventParams(Event event,ax::mojom::EventFrom event_from)63 AXEventGenerator::EventParams::EventParams(Event event,
64 ax::mojom::EventFrom event_from)
65 : event(event), event_from(event_from) {}
66
TargetedEvent(AXNode * node,const EventParams & event_params)67 AXEventGenerator::TargetedEvent::TargetedEvent(AXNode* node,
68 const EventParams& event_params)
69 : node(node), event_params(event_params) {
70 DCHECK(node);
71 }
72
operator ==(const EventParams & rhs)73 bool AXEventGenerator::EventParams::operator==(const EventParams& rhs) {
74 return rhs.event == event;
75 }
76
operator <(const EventParams & rhs) const77 bool AXEventGenerator::EventParams::operator<(const EventParams& rhs) const {
78 return event < rhs.event;
79 }
80
Iterator(const std::map<AXNode *,std::set<EventParams>> & map,const std::map<AXNode *,std::set<EventParams>>::const_iterator & head)81 AXEventGenerator::Iterator::Iterator(
82 const std::map<AXNode*, std::set<EventParams>>& map,
83 const std::map<AXNode*, std::set<EventParams>>::const_iterator& head)
84 : map_(map), map_iter_(head) {
85 if (map_iter_ != map.end())
86 set_iter_ = map_iter_->second.begin();
87 }
88
89 AXEventGenerator::Iterator::Iterator(const AXEventGenerator::Iterator& other) =
90 default;
91
92 AXEventGenerator::Iterator::~Iterator() = default;
93
operator !=(const AXEventGenerator::Iterator & rhs) const94 bool AXEventGenerator::Iterator::operator!=(
95 const AXEventGenerator::Iterator& rhs) const {
96 return map_iter_ != rhs.map_iter_ ||
97 (map_iter_ != map_.end() && set_iter_ != rhs.set_iter_);
98 }
99
operator ++()100 AXEventGenerator::Iterator& AXEventGenerator::Iterator::operator++() {
101 if (map_iter_ == map_.end())
102 return *this;
103
104 set_iter_++;
105 while (map_iter_ != map_.end() && set_iter_ == map_iter_->second.end()) {
106 map_iter_++;
107 if (map_iter_ != map_.end())
108 set_iter_ = map_iter_->second.begin();
109 }
110
111 return *this;
112 }
113
operator *() const114 AXEventGenerator::TargetedEvent AXEventGenerator::Iterator::operator*() const {
115 DCHECK(map_iter_ != map_.end() && set_iter_ != map_iter_->second.end());
116 return AXEventGenerator::TargetedEvent(map_iter_->first, *set_iter_);
117 }
118
119 AXEventGenerator::AXEventGenerator() = default;
120
AXEventGenerator(AXTree * tree)121 AXEventGenerator::AXEventGenerator(AXTree* tree) : tree_(tree) {
122 if (tree_)
123 tree_event_observer_.Add(tree_);
124 }
125
126 AXEventGenerator::~AXEventGenerator() = default;
127
SetTree(AXTree * new_tree)128 void AXEventGenerator::SetTree(AXTree* new_tree) {
129 if (tree_)
130 tree_event_observer_.Remove(tree_);
131 tree_ = new_tree;
132 if (tree_)
133 tree_event_observer_.Add(tree_);
134 }
135
ReleaseTree()136 void AXEventGenerator::ReleaseTree() {
137 tree_event_observer_.RemoveAll();
138 tree_ = nullptr;
139 }
140
ClearEvents()141 void AXEventGenerator::ClearEvents() {
142 tree_events_.clear();
143 }
144
AddEvent(AXNode * node,AXEventGenerator::Event event)145 void AXEventGenerator::AddEvent(AXNode* node, AXEventGenerator::Event event) {
146 DCHECK(node);
147
148 if (node->data().role == ax::mojom::Role::kInlineTextBox)
149 return;
150
151 std::set<EventParams>& node_events = tree_events_[node];
152
153 // A newly created live region or alert should not *also* fire a
154 // live region changed event.
155 if (event == Event::LIVE_REGION_CHANGED &&
156 HasOtherLiveRegionEvent(node_events)) {
157 return;
158 }
159
160 // We shouldn't fire children changed events on nodes that become
161 // ignored or unignored.
162 if (event == Event::IGNORED_CHANGED) {
163 for (auto& iter : node_events) {
164 if (iter.event == Event::CHILDREN_CHANGED) {
165 node_events.erase(iter);
166 break;
167 }
168 }
169 }
170 if (event == Event::CHILDREN_CHANGED) {
171 for (auto& iter : node_events) {
172 if (iter.event == Event::IGNORED_CHANGED)
173 return;
174 }
175 }
176
177 node_events.emplace(event, ax::mojom::EventFrom::kNone);
178 }
179
OnNodeDataChanged(AXTree * tree,const AXNodeData & old_node_data,const AXNodeData & new_node_data)180 void AXEventGenerator::OnNodeDataChanged(AXTree* tree,
181 const AXNodeData& old_node_data,
182 const AXNodeData& new_node_data) {
183 DCHECK_EQ(tree_, tree);
184 // Fire CHILDREN_CHANGED events when the list of children updates.
185 // Internally we store inline text box nodes as children of a static text
186 // node, which enables us to determine character bounds and line layout.
187 // We don't expose those to platform APIs, though, so suppress
188 // CHILDREN_CHANGED events on static text nodes.
189 if (new_node_data.child_ids != old_node_data.child_ids &&
190 new_node_data.role != ax::mojom::Role::kStaticText) {
191 AXNode* node = tree_->GetFromId(new_node_data.id);
192 tree_events_[node].emplace(Event::CHILDREN_CHANGED,
193 ax::mojom::EventFrom::kNone);
194 }
195 }
196
OnRoleChanged(AXTree * tree,AXNode * node,ax::mojom::Role old_role,ax::mojom::Role new_role)197 void AXEventGenerator::OnRoleChanged(AXTree* tree,
198 AXNode* node,
199 ax::mojom::Role old_role,
200 ax::mojom::Role new_role) {
201 DCHECK_EQ(tree_, tree);
202 AddEvent(node, Event::ROLE_CHANGED);
203 }
204
OnStateChanged(AXTree * tree,AXNode * node,ax::mojom::State state,bool new_value)205 void AXEventGenerator::OnStateChanged(AXTree* tree,
206 AXNode* node,
207 ax::mojom::State state,
208 bool new_value) {
209 DCHECK_EQ(tree_, tree);
210
211 if (state != ax::mojom::State::kIgnored)
212 AddEvent(node, Event::STATE_CHANGED);
213
214 switch (state) {
215 case ax::mojom::State::kExpanded:
216 AddEvent(node, new_value ? Event::EXPANDED : Event::COLLAPSED);
217
218 if (node->data().role == ax::mojom::Role::kRow ||
219 node->data().role == ax::mojom::Role::kTreeItem) {
220 AXNode* container = node;
221 while (container && !IsRowContainer(container->data().role))
222 container = container->parent();
223 if (container)
224 AddEvent(container, Event::ROW_COUNT_CHANGED);
225 }
226 break;
227 case ax::mojom::State::kIgnored: {
228 AXNode* unignored_parent = node->GetUnignoredParent();
229 if (unignored_parent)
230 AddEvent(unignored_parent, Event::CHILDREN_CHANGED);
231 AddEvent(node, Event::IGNORED_CHANGED);
232 if (!new_value)
233 AddEvent(node, Event::SUBTREE_CREATED);
234 break;
235 }
236 case ax::mojom::State::kMultiline:
237 AddEvent(node, Event::MULTILINE_STATE_CHANGED);
238 break;
239 case ax::mojom::State::kMultiselectable:
240 AddEvent(node, Event::MULTISELECTABLE_STATE_CHANGED);
241 break;
242 case ax::mojom::State::kRequired:
243 AddEvent(node, Event::REQUIRED_STATE_CHANGED);
244 break;
245 default:
246 break;
247 }
248 }
249
OnStringAttributeChanged(AXTree * tree,AXNode * node,ax::mojom::StringAttribute attr,const std::string & old_value,const std::string & new_value)250 void AXEventGenerator::OnStringAttributeChanged(AXTree* tree,
251 AXNode* node,
252 ax::mojom::StringAttribute attr,
253 const std::string& old_value,
254 const std::string& new_value) {
255 DCHECK_EQ(tree_, tree);
256
257 switch (attr) {
258 case ax::mojom::StringAttribute::kAccessKey:
259 AddEvent(node, Event::ACCESS_KEY_CHANGED);
260 break;
261 case ax::mojom::StringAttribute::kAriaInvalidValue:
262 AddEvent(node, Event::INVALID_STATUS_CHANGED);
263 break;
264 case ax::mojom::StringAttribute::kAutoComplete:
265 AddEvent(node, Event::AUTO_COMPLETE_CHANGED);
266 break;
267 case ax::mojom::StringAttribute::kClassName:
268 AddEvent(node, Event::CLASS_NAME_CHANGED);
269 break;
270 case ax::mojom::StringAttribute::kDescription:
271 AddEvent(node, Event::DESCRIPTION_CHANGED);
272 break;
273 case ax::mojom::StringAttribute::kKeyShortcuts:
274 AddEvent(node, Event::KEY_SHORTCUTS_CHANGED);
275 break;
276 case ax::mojom::StringAttribute::kLanguage:
277 AddEvent(node, Event::LANGUAGE_CHANGED);
278 break;
279 case ax::mojom::StringAttribute::kLiveRelevant:
280 AddEvent(node, Event::LIVE_RELEVANT_CHANGED);
281 break;
282 case ax::mojom::StringAttribute::kLiveStatus:
283 AddEvent(node, Event::LIVE_STATUS_CHANGED);
284
285 // Fire a LIVE_REGION_CREATED if the previous value was off, and the new
286 // value is not-off.
287 if (!IsAlert(node->data().role)) {
288 bool old_state = !old_value.empty() && old_value != "off";
289 bool new_state = !new_value.empty() && new_value != "off";
290 if (!old_state && new_state)
291 AddEvent(node, Event::LIVE_REGION_CREATED);
292 }
293 break;
294 case ax::mojom::StringAttribute::kName:
295 // If the name of the root node changes, we expect OnTreeDataChanged to
296 // add a DOCUMENT_TITLE_CHANGED event instead.
297 if (node != tree->root())
298 AddEvent(node, Event::NAME_CHANGED);
299
300 if (node->data().HasStringAttribute(
301 ax::mojom::StringAttribute::kContainerLiveStatus)) {
302 FireLiveRegionEvents(node);
303 }
304 break;
305 case ax::mojom::StringAttribute::kPlaceholder:
306 AddEvent(node, Event::PLACEHOLDER_CHANGED);
307 break;
308 case ax::mojom::StringAttribute::kValue:
309 AddEvent(node, Event::VALUE_CHANGED);
310 break;
311 case ax::mojom::StringAttribute::kImageAnnotation:
312 // The image annotation is reported as part of the accessible name.
313 AddEvent(node, Event::IMAGE_ANNOTATION_CHANGED);
314 break;
315 default:
316 AddEvent(node, Event::OTHER_ATTRIBUTE_CHANGED);
317 break;
318 }
319 }
320
OnIntAttributeChanged(AXTree * tree,AXNode * node,ax::mojom::IntAttribute attr,int32_t old_value,int32_t new_value)321 void AXEventGenerator::OnIntAttributeChanged(AXTree* tree,
322 AXNode* node,
323 ax::mojom::IntAttribute attr,
324 int32_t old_value,
325 int32_t new_value) {
326 DCHECK_EQ(tree_, tree);
327
328 switch (attr) {
329 case ax::mojom::IntAttribute::kActivedescendantId:
330 // Don't fire on invisible containers, as it confuses some screen readers,
331 // such as NVDA.
332 if (!node->data().HasState(ax::mojom::State::kInvisible)) {
333 AddEvent(node, Event::ACTIVE_DESCENDANT_CHANGED);
334 active_descendant_changed_.push_back(node);
335 }
336 break;
337 case ax::mojom::IntAttribute::kCheckedState:
338 AddEvent(node, Event::CHECKED_STATE_CHANGED);
339 break;
340 case ax::mojom::IntAttribute::kDropeffect:
341 AddEvent(node, Event::DROPEFFECT_CHANGED);
342 break;
343 case ax::mojom::IntAttribute::kHasPopup:
344 AddEvent(node, Event::HASPOPUP_CHANGED);
345 break;
346 case ax::mojom::IntAttribute::kHierarchicalLevel:
347 AddEvent(node, Event::HIERARCHICAL_LEVEL_CHANGED);
348 break;
349 case ax::mojom::IntAttribute::kInvalidState:
350 AddEvent(node, Event::INVALID_STATUS_CHANGED);
351 break;
352 case ax::mojom::IntAttribute::kPosInSet:
353 AddEvent(node, Event::POSITION_IN_SET_CHANGED);
354 break;
355 case ax::mojom::IntAttribute::kRestriction: {
356 bool was_enabled;
357 bool was_readonly;
358 GetRestrictionStates(static_cast<ax::mojom::Restriction>(old_value),
359 &was_enabled, &was_readonly);
360 bool is_enabled;
361 bool is_readonly;
362 GetRestrictionStates(static_cast<ax::mojom::Restriction>(new_value),
363 &is_enabled, &is_readonly);
364
365 if (was_enabled != is_enabled)
366 AddEvent(node, Event::ENABLED_CHANGED);
367 if (was_readonly != is_readonly)
368 AddEvent(node, Event::READONLY_CHANGED);
369 break;
370 }
371 case ax::mojom::IntAttribute::kScrollX:
372 AddEvent(node, Event::SCROLL_HORIZONTAL_POSITION_CHANGED);
373 break;
374 case ax::mojom::IntAttribute::kScrollY:
375 AddEvent(node, Event::SCROLL_VERTICAL_POSITION_CHANGED);
376 break;
377 case ax::mojom::IntAttribute::kSortDirection:
378 // Ignore sort direction changes on roles other than table headers and
379 // grid headers.
380 if (IsTableHeader(node->data().role))
381 AddEvent(node, Event::SORT_CHANGED);
382 break;
383 case ax::mojom::IntAttribute::kImageAnnotationStatus:
384 // The image annotation is reported as part of the accessible name.
385 AddEvent(node, Event::IMAGE_ANNOTATION_CHANGED);
386 break;
387 case ax::mojom::IntAttribute::kSetSize:
388 AddEvent(node, Event::SET_SIZE_CHANGED);
389 break;
390 default:
391 AddEvent(node, Event::OTHER_ATTRIBUTE_CHANGED);
392 break;
393 }
394 }
395
OnFloatAttributeChanged(AXTree * tree,AXNode * node,ax::mojom::FloatAttribute attr,float old_value,float new_value)396 void AXEventGenerator::OnFloatAttributeChanged(AXTree* tree,
397 AXNode* node,
398 ax::mojom::FloatAttribute attr,
399 float old_value,
400 float new_value) {
401 DCHECK_EQ(tree_, tree);
402
403 switch (attr) {
404 case ax::mojom::FloatAttribute::kMaxValueForRange:
405 AddEvent(node, Event::VALUE_MAX_CHANGED);
406 break;
407 case ax::mojom::FloatAttribute::kMinValueForRange:
408 AddEvent(node, Event::VALUE_MIN_CHANGED);
409 break;
410 case ax::mojom::FloatAttribute::kStepValueForRange:
411 AddEvent(node, Event::VALUE_STEP_CHANGED);
412 break;
413 case ax::mojom::FloatAttribute::kValueForRange:
414 AddEvent(node, Event::VALUE_CHANGED);
415 break;
416 default:
417 AddEvent(node, Event::OTHER_ATTRIBUTE_CHANGED);
418 break;
419 }
420 }
421
OnBoolAttributeChanged(AXTree * tree,AXNode * node,ax::mojom::BoolAttribute attr,bool new_value)422 void AXEventGenerator::OnBoolAttributeChanged(AXTree* tree,
423 AXNode* node,
424 ax::mojom::BoolAttribute attr,
425 bool new_value) {
426 DCHECK_EQ(tree_, tree);
427
428 switch (attr) {
429 case ax::mojom::BoolAttribute::kBusy:
430 AddEvent(node, Event::BUSY_CHANGED);
431 // Fire an 'invalidated' event when aria-busy becomes false
432 if (!new_value)
433 AddEvent(node, Event::LAYOUT_INVALIDATED);
434 break;
435 case ax::mojom::BoolAttribute::kGrabbed:
436 AddEvent(node, Event::GRABBED_CHANGED);
437 break;
438 case ax::mojom::BoolAttribute::kLiveAtomic:
439 AddEvent(node, Event::ATOMIC_CHANGED);
440 break;
441 case ax::mojom::BoolAttribute::kSelected: {
442 AddEvent(node, Event::SELECTED_CHANGED);
443 AXNode* container = node;
444 while (container &&
445 !IsContainerWithSelectableChildren(container->data().role))
446 container = container->parent();
447 if (container)
448 AddEvent(container, Event::SELECTED_CHILDREN_CHANGED);
449 break;
450 }
451 default:
452 AddEvent(node, Event::OTHER_ATTRIBUTE_CHANGED);
453 break;
454 }
455 }
456
OnIntListAttributeChanged(AXTree * tree,AXNode * node,ax::mojom::IntListAttribute attr,const std::vector<int32_t> & old_value,const std::vector<int32_t> & new_value)457 void AXEventGenerator::OnIntListAttributeChanged(
458 AXTree* tree,
459 AXNode* node,
460 ax::mojom::IntListAttribute attr,
461 const std::vector<int32_t>& old_value,
462 const std::vector<int32_t>& new_value) {
463 DCHECK_EQ(tree_, tree);
464
465 switch (attr) {
466 case ax::mojom::IntListAttribute::kControlsIds:
467 AddEvent(node, Event::CONTROLS_CHANGED);
468 break;
469 case ax::mojom::IntListAttribute::kDescribedbyIds:
470 AddEvent(node, Event::DESCRIBED_BY_CHANGED);
471 break;
472 case ax::mojom::IntListAttribute::kFlowtoIds: {
473 AddEvent(node, Event::FLOW_TO_CHANGED);
474
475 // Fire FLOW_FROM_CHANGED for all nodes added or removed
476 for (int32_t id : ComputeIntListDifference(old_value, new_value)) {
477 if (auto* target_node = tree->GetFromId(id))
478 AddEvent(target_node, Event::FLOW_FROM_CHANGED);
479 }
480 break;
481 }
482 case ax::mojom::IntListAttribute::kLabelledbyIds:
483 AddEvent(node, Event::LABELED_BY_CHANGED);
484 break;
485 default:
486 AddEvent(node, Event::OTHER_ATTRIBUTE_CHANGED);
487 break;
488 }
489 }
490
OnTreeDataChanged(AXTree * tree,const AXTreeData & old_tree_data,const AXTreeData & new_tree_data)491 void AXEventGenerator::OnTreeDataChanged(AXTree* tree,
492 const AXTreeData& old_tree_data,
493 const AXTreeData& new_tree_data) {
494 DCHECK_EQ(tree_, tree);
495
496 if (new_tree_data.loaded && !old_tree_data.loaded &&
497 ShouldFireLoadEvents(tree->root())) {
498 AddEvent(tree->root(), Event::LOAD_COMPLETE);
499 }
500
501 if (new_tree_data.sel_is_backward != old_tree_data.sel_is_backward ||
502 new_tree_data.sel_anchor_object_id !=
503 old_tree_data.sel_anchor_object_id ||
504 new_tree_data.sel_anchor_offset != old_tree_data.sel_anchor_offset ||
505 new_tree_data.sel_anchor_affinity != old_tree_data.sel_anchor_affinity ||
506 new_tree_data.sel_focus_object_id != old_tree_data.sel_focus_object_id ||
507 new_tree_data.sel_focus_offset != old_tree_data.sel_focus_offset ||
508 new_tree_data.sel_focus_affinity != old_tree_data.sel_focus_affinity) {
509 AddEvent(tree->root(), Event::DOCUMENT_SELECTION_CHANGED);
510 }
511 if (new_tree_data.title != old_tree_data.title)
512 AddEvent(tree->root(), Event::DOCUMENT_TITLE_CHANGED);
513 }
514
OnNodeWillBeDeleted(AXTree * tree,AXNode * node)515 void AXEventGenerator::OnNodeWillBeDeleted(AXTree* tree, AXNode* node) {
516 DCHECK_EQ(tree_, tree);
517 tree_events_.erase(node);
518 }
519
OnSubtreeWillBeDeleted(AXTree * tree,AXNode * node)520 void AXEventGenerator::OnSubtreeWillBeDeleted(AXTree* tree, AXNode* node) {
521 DCHECK_EQ(tree_, tree);
522 }
523
OnNodeWillBeReparented(AXTree * tree,AXNode * node)524 void AXEventGenerator::OnNodeWillBeReparented(AXTree* tree, AXNode* node) {
525 DCHECK_EQ(tree_, tree);
526 tree_events_.erase(node);
527 }
528
OnSubtreeWillBeReparented(AXTree * tree,AXNode * node)529 void AXEventGenerator::OnSubtreeWillBeReparented(AXTree* tree, AXNode* node) {
530 DCHECK_EQ(tree_, tree);
531 }
532
OnAtomicUpdateFinished(AXTree * tree,bool root_changed,const std::vector<Change> & changes)533 void AXEventGenerator::OnAtomicUpdateFinished(
534 AXTree* tree,
535 bool root_changed,
536 const std::vector<Change>& changes) {
537 DCHECK_EQ(tree_, tree);
538
539 if (root_changed && ShouldFireLoadEvents(tree->root())) {
540 if (tree->data().loaded)
541 AddEvent(tree->root(), Event::LOAD_COMPLETE);
542 else
543 AddEvent(tree->root(), Event::LOAD_START);
544 }
545
546 for (const auto& change : changes) {
547 if (change.type == SUBTREE_CREATED) {
548 AddEvent(change.node, Event::SUBTREE_CREATED);
549 } else if (change.type != NODE_CREATED) {
550 FireRelationSourceEvents(tree, change.node);
551 continue;
552 }
553
554 if (IsAlert(change.node->data().role))
555 AddEvent(change.node, Event::ALERT);
556 else if (IsActiveLiveRegion(change))
557 AddEvent(change.node, Event::LIVE_REGION_CREATED);
558 else if (IsContainedInLiveRegion(change))
559 FireLiveRegionEvents(change.node);
560 }
561
562 FireActiveDescendantEvents();
563
564 PostprocessEvents();
565 }
566
FireLiveRegionEvents(AXNode * node)567 void AXEventGenerator::FireLiveRegionEvents(AXNode* node) {
568 AXNode* live_root = node;
569 while (live_root && !live_root->data().HasStringAttribute(
570 ax::mojom::StringAttribute::kLiveStatus))
571 live_root = live_root->parent();
572
573 if (live_root &&
574 !live_root->data().GetBoolAttribute(ax::mojom::BoolAttribute::kBusy) &&
575 live_root->data().GetStringAttribute(
576 ax::mojom::StringAttribute::kLiveStatus) != "off") {
577 // Fire LIVE_REGION_NODE_CHANGED on each node that changed.
578 if (!node->data()
579 .GetStringAttribute(ax::mojom::StringAttribute::kName)
580 .empty())
581 AddEvent(node, Event::LIVE_REGION_NODE_CHANGED);
582 // Fire LIVE_REGION_CHANGED on the root of the live region.
583 AddEvent(live_root, Event::LIVE_REGION_CHANGED);
584 }
585 }
586
FireActiveDescendantEvents()587 void AXEventGenerator::FireActiveDescendantEvents() {
588 for (AXNode* node : active_descendant_changed_) {
589 AXNode* descendant = tree_->GetFromId(node->data().GetIntAttribute(
590 ax::mojom::IntAttribute::kActivedescendantId));
591 if (!descendant)
592 continue;
593 switch (descendant->data().role) {
594 case ax::mojom::Role::kMenuItem:
595 case ax::mojom::Role::kMenuItemCheckBox:
596 case ax::mojom::Role::kMenuItemRadio:
597 case ax::mojom::Role::kMenuListOption:
598 AddEvent(descendant, Event::MENU_ITEM_SELECTED);
599 break;
600 default:
601 break;
602 }
603 }
604 active_descendant_changed_.clear();
605 }
606
FireRelationSourceEvents(AXTree * tree,AXNode * target_node)607 void AXEventGenerator::FireRelationSourceEvents(AXTree* tree,
608 AXNode* target_node) {
609 int32_t target_id = target_node->id();
610 std::set<AXNode*> source_nodes;
611 auto callback = [&](const auto& entry) {
612 const auto& target_to_sources = entry.second;
613 auto sources_it = target_to_sources.find(target_id);
614 if (sources_it == target_to_sources.end())
615 return;
616
617 auto sources = sources_it->second;
618 std::for_each(sources.begin(), sources.end(), [&](int32_t source_id) {
619 AXNode* source_node = tree->GetFromId(source_id);
620
621 if (!source_node || source_nodes.count(source_node) > 0)
622 return;
623
624 source_nodes.insert(source_node);
625
626 // GCC < 6.4 requires this pointer when calling a member
627 // function in anonymous function
628 this->AddEvent(source_node, Event::RELATED_NODE_CHANGED);
629 });
630 };
631
632 std::for_each(tree->int_reverse_relations().begin(),
633 tree->int_reverse_relations().end(), callback);
634 std::for_each(
635 tree->intlist_reverse_relations().begin(),
636 tree->intlist_reverse_relations().end(), [&](auto& entry) {
637 // Explicitly exclude relationships for which an additional event on the
638 // source node would cause extra noise. For example, kRadioGroupIds
639 // forms relations among all radio buttons and serves little value for
640 // AT to get events on the previous radio button in the group.
641 if (entry.first != ax::mojom::IntListAttribute::kRadioGroupIds)
642 callback(entry);
643 });
644 }
645
646 // Attempts to suppress load-related events that we presume no AT will be
647 // interested in under any circumstances, such as pages which have no size.
ShouldFireLoadEvents(AXNode * node)648 bool AXEventGenerator::ShouldFireLoadEvents(AXNode* node) {
649 const AXNodeData& data = node->data();
650 return data.relative_bounds.bounds.width() ||
651 data.relative_bounds.bounds.height();
652 }
653
PostprocessEvents()654 void AXEventGenerator::PostprocessEvents() {
655 std::vector<AXNode*> nodes_to_remove_subtree_created;
656
657 for (auto& iter : tree_events_) {
658 AXNode* node = iter.first;
659 std::set<EventParams>& node_events = iter.second;
660
661 // A newly created live region or alert should not *also* fire a
662 // live region changed event.
663 if (HasEvent(node_events, Event::ALERT) ||
664 HasEvent(node_events, Event::LIVE_REGION_CREATED)) {
665 RemoveEvent(&node_events, Event::LIVE_REGION_CHANGED);
666 }
667
668 // If a node toggled its ignored state, we shouldn't also fire
669 // children changed events on it.
670 if (HasEvent(node_events, Event::IGNORED_CHANGED))
671 RemoveEvent(&node_events, Event::CHILDREN_CHANGED);
672
673 // We shouldn't fire subtree created if the parent also has subtree
674 // created on it.
675 AXNode* parent = node->GetUnignoredParent();
676 if (parent && HasEvent(node_events, Event::SUBTREE_CREATED)) {
677 if (tree_events_.find(parent) != tree_events_.end()) {
678 std::set<EventParams>& parent_events = tree_events_[parent];
679 if (HasEvent(parent_events, Event::SUBTREE_CREATED))
680 nodes_to_remove_subtree_created.push_back(node);
681 }
682 }
683 }
684
685 for (AXNode* node : nodes_to_remove_subtree_created) {
686 std::set<EventParams>& node_events = tree_events_[node];
687 RemoveEvent(&node_events, Event::SUBTREE_CREATED);
688 // If this was the only event, remove the node entirely from the
689 // tree events. Note that this can't happen with any of the other logic
690 // above since it's all dealing with one event superseding another in
691 // the same node.
692 if (node_events.size() == 0)
693 tree_events_.erase(node);
694 }
695 }
696
697 // static
GetRestrictionStates(ax::mojom::Restriction restriction,bool * is_enabled,bool * is_readonly)698 void AXEventGenerator::GetRestrictionStates(ax::mojom::Restriction restriction,
699 bool* is_enabled,
700 bool* is_readonly) {
701 switch (restriction) {
702 case ax::mojom::Restriction::kDisabled:
703 *is_enabled = false;
704 *is_readonly = true;
705 break;
706 case ax::mojom::Restriction::kReadOnly:
707 *is_enabled = true;
708 *is_readonly = true;
709 break;
710 case ax::mojom::Restriction::kNone:
711 *is_enabled = true;
712 *is_readonly = false;
713 break;
714 }
715 }
716
717 // static
ComputeIntListDifference(const std::vector<int32_t> & lhs,const std::vector<int32_t> & rhs)718 std::vector<int32_t> AXEventGenerator::ComputeIntListDifference(
719 const std::vector<int32_t>& lhs,
720 const std::vector<int32_t>& rhs) {
721 std::set<int32_t> sorted_lhs(lhs.cbegin(), lhs.cend());
722 std::set<int32_t> sorted_rhs(rhs.cbegin(), rhs.cend());
723
724 std::vector<int32_t> result;
725 std::set_symmetric_difference(sorted_lhs.cbegin(), sorted_lhs.cend(),
726 sorted_rhs.cbegin(), sorted_rhs.cend(),
727 std::back_inserter(result));
728 return result;
729 }
730
operator <<(std::ostream & os,AXEventGenerator::Event event)731 std::ostream& operator<<(std::ostream& os, AXEventGenerator::Event event) {
732 return os << ToString(event);
733 }
734
ToString(AXEventGenerator::Event event)735 const char* ToString(AXEventGenerator::Event event) {
736 switch (event) {
737 case AXEventGenerator::Event::ACCESS_KEY_CHANGED:
738 return "ACCESS_KEY_CHANGED";
739 case AXEventGenerator::Event::ATOMIC_CHANGED:
740 return "ATOMIC_CHANGED";
741 case AXEventGenerator::Event::ACTIVE_DESCENDANT_CHANGED:
742 return "ACTIVE_DESCENDANT_CHANGED";
743 case AXEventGenerator::Event::ALERT:
744 return "ALERT";
745 case AXEventGenerator::Event::BUSY_CHANGED:
746 return "BUSY_CHANGED";
747 case AXEventGenerator::Event::CHECKED_STATE_CHANGED:
748 return "CHECKED_STATE_CHANGED";
749 case AXEventGenerator::Event::CHILDREN_CHANGED:
750 return "CHILDREN_CHANGED";
751 case AXEventGenerator::Event::CLASS_NAME_CHANGED:
752 return "CLASS_NAME_CHANGED";
753 case AXEventGenerator::Event::COLLAPSED:
754 return "COLLAPSED";
755 case AXEventGenerator::Event::CONTROLS_CHANGED:
756 return "CONTROLS_CHANGED";
757 case AXEventGenerator::Event::DESCRIBED_BY_CHANGED:
758 return "DESCRIBED_BY_CHANGED";
759 case AXEventGenerator::Event::DESCRIPTION_CHANGED:
760 return "DESCRIPTION_CHANGED";
761 case AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED:
762 return "DOCUMENT_SELECTION_CHANGED";
763 case AXEventGenerator::Event::DOCUMENT_TITLE_CHANGED:
764 return "DOCUMENT_TITLE_CHANGED";
765 case AXEventGenerator::Event::DROPEFFECT_CHANGED:
766 return "DROPEFFECT_CHANGED";
767 case AXEventGenerator::Event::ENABLED_CHANGED:
768 return "ENABLED_CHANGED";
769 case AXEventGenerator::Event::EXPANDED:
770 return "EXPANDED";
771 case AXEventGenerator::Event::FLOW_FROM_CHANGED:
772 return "FLOW_FROM_CHANGED";
773 case AXEventGenerator::Event::FLOW_TO_CHANGED:
774 return "FLOW_TO_CHANGED";
775 case AXEventGenerator::Event::GRABBED_CHANGED:
776 return "GRABBED_CHANGED";
777 case AXEventGenerator::Event::HASPOPUP_CHANGED:
778 return "HASPOPUP_CHANGED";
779 case AXEventGenerator::Event::HIERARCHICAL_LEVEL_CHANGED:
780 return "HIERARCHICAL_LEVEL_CHANGED";
781 case ui::AXEventGenerator::Event::IGNORED_CHANGED:
782 return "IGNORED_CHANGED";
783 case AXEventGenerator::Event::IMAGE_ANNOTATION_CHANGED:
784 return "IMAGE_ANNOTATION_CHANGED";
785 case AXEventGenerator::Event::INVALID_STATUS_CHANGED:
786 return "INVALID_STATUS_CHANGED";
787 case AXEventGenerator::Event::KEY_SHORTCUTS_CHANGED:
788 return "KEY_SHORTCUTS_CHANGED";
789 case AXEventGenerator::Event::LABELED_BY_CHANGED:
790 return "LABELED_BY_CHANGED";
791 case AXEventGenerator::Event::LANGUAGE_CHANGED:
792 return "LANGUAGE_CHANGED";
793 case AXEventGenerator::Event::LAYOUT_INVALIDATED:
794 return "LAYOUT_INVALIDATED";
795 case AXEventGenerator::Event::LIVE_REGION_CHANGED:
796 return "LIVE_REGION_CHANGED";
797 case AXEventGenerator::Event::LIVE_REGION_CREATED:
798 return "LIVE_REGION_CREATED";
799 case AXEventGenerator::Event::LIVE_REGION_NODE_CHANGED:
800 return "LIVE_REGION_NODE_CHANGED";
801 case AXEventGenerator::Event::LIVE_RELEVANT_CHANGED:
802 return "LIVE_RELEVANT_CHANGED";
803 case AXEventGenerator::Event::LIVE_STATUS_CHANGED:
804 return "LIVE_STATUS_CHANGED";
805 case AXEventGenerator::Event::LOAD_COMPLETE:
806 return "LOAD_COMPLETE";
807 case AXEventGenerator::Event::LOAD_START:
808 return "LOAD_START";
809 case AXEventGenerator::Event::MENU_ITEM_SELECTED:
810 return "MENU_ITEM_SELECTED";
811 case AXEventGenerator::Event::MULTILINE_STATE_CHANGED:
812 return "MULTILINE_STATE_CHANGED";
813 case AXEventGenerator::Event::MULTISELECTABLE_STATE_CHANGED:
814 return "MULTISELECTABLE_STATE_CHANGED";
815 case AXEventGenerator::Event::NAME_CHANGED:
816 return "NAME_CHANGED";
817 case AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED:
818 return "OTHER_ATTRIBUTE_CHANGED";
819 case AXEventGenerator::Event::PLACEHOLDER_CHANGED:
820 return "PLACEHOLDER_CHANGED";
821 case AXEventGenerator::Event::PORTAL_ACTIVATED:
822 return "PORTAL_ACTIVATED";
823 case AXEventGenerator::Event::POSITION_IN_SET_CHANGED:
824 return "POSITION_IN_SET_CHANGED";
825 case AXEventGenerator::Event::READONLY_CHANGED:
826 return "READONLY_CHANGED";
827 case AXEventGenerator::Event::RELATED_NODE_CHANGED:
828 return "RELATED_NODE_CHANGED";
829 case AXEventGenerator::Event::REQUIRED_STATE_CHANGED:
830 return "REQUIRED_STATE_CHANGED";
831 case AXEventGenerator::Event::ROLE_CHANGED:
832 return "ROLE_CHANGED";
833 case AXEventGenerator::Event::ROW_COUNT_CHANGED:
834 return "ROW_COUNT_CHANGED";
835 case AXEventGenerator::Event::SCROLL_HORIZONTAL_POSITION_CHANGED:
836 return "SCROLL_HORIZONTAL_POSITION_CHANGED";
837 case AXEventGenerator::Event::SCROLL_VERTICAL_POSITION_CHANGED:
838 return "SCROLL_VERTICAL_POSITION_CHANGED";
839 case AXEventGenerator::Event::SELECTED_CHANGED:
840 return "SELECTED_CHANGED";
841 case AXEventGenerator::Event::SELECTED_CHILDREN_CHANGED:
842 return "SELECTED_CHILDREN_CHANGED";
843 case AXEventGenerator::Event::SET_SIZE_CHANGED:
844 return "SET_SIZE_CHANGED";
845 case AXEventGenerator::Event::STATE_CHANGED:
846 return "STATE_CHANGED";
847 case AXEventGenerator::Event::SUBTREE_CREATED:
848 return "SUBTREE_CREATED";
849 case AXEventGenerator::Event::VALUE_CHANGED:
850 return "VALUE_CHANGED";
851 case AXEventGenerator::Event::VALUE_MAX_CHANGED:
852 return "VALUE_MAX_CHANGED";
853 case AXEventGenerator::Event::VALUE_MIN_CHANGED:
854 return "VALUE_MIN_CHANGED";
855 case AXEventGenerator::Event::VALUE_STEP_CHANGED:
856 return "VALUE_STEP_CHANGED";
857 case AXEventGenerator::Event::AUTO_COMPLETE_CHANGED:
858 return "AUTO_COMPLETE_CHANGED";
859 case AXEventGenerator::Event::FOCUS_CHANGED:
860 return "FOCUS_CHANGED";
861 case AXEventGenerator::Event::SORT_CHANGED:
862 return "SORT_CHANGED";
863 }
864 NOTREACHED();
865 }
866
867 } // namespace ui
868