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