123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362 |
- // Copyright 2017 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "ui/accessibility/ax_event_generator.h"
- #include <algorithm>
- #include "base/containers/contains.h"
- #include "ui/accessibility/ax_enums.mojom.h"
- #include "ui/accessibility/ax_event.h"
- #include "ui/accessibility/ax_live_region_tracker.h"
- #include "ui/accessibility/ax_node.h"
- #include "ui/accessibility/ax_role_properties.h"
- namespace ui {
- namespace {
- bool HasEvent(const std::set<AXEventGenerator::EventParams>& node_events,
- AXEventGenerator::Event event) {
- return node_events.count(AXEventGenerator::EventParams(event));
- }
- void RemoveEvent(std::set<AXEventGenerator::EventParams>* node_events,
- AXEventGenerator::Event event) {
- node_events->erase(AXEventGenerator::EventParams(event));
- }
- // If a node toggled its ignored state, don't also fire children-changed because
- // platforms likely will do that in response to ignored-changed. Also do not
- // fire parent-changed on ignored nodes because functionally the parent did not
- // change as far as platform assistive technologies are concerned.
- // Suppress name- and description-changed because those can be emitted as a side
- // effect of calculating alternative text values for a newly-displayed object.
- // Ditto for text attributes such as foreground and background colors, or
- // display changing from "none" to "block."
- void RemoveEventsDueToIgnoredChanged(
- std::set<AXEventGenerator::EventParams>* node_events) {
- RemoveEvent(node_events,
- AXEventGenerator::Event::ATK_TEXT_OBJECT_ATTRIBUTE_CHANGED);
- RemoveEvent(node_events, AXEventGenerator::Event::CHILDREN_CHANGED);
- RemoveEvent(node_events, AXEventGenerator::Event::DESCRIPTION_CHANGED);
- RemoveEvent(node_events, AXEventGenerator::Event::NAME_CHANGED);
- RemoveEvent(node_events, AXEventGenerator::Event::OBJECT_ATTRIBUTE_CHANGED);
- RemoveEvent(node_events, AXEventGenerator::Event::PARENT_CHANGED);
- RemoveEvent(node_events, AXEventGenerator::Event::SORT_CHANGED);
- RemoveEvent(node_events, AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED);
- RemoveEvent(node_events,
- AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED);
- }
- // Add a particular AXEventGenerator::IgnoredChangedState to
- // |ignored_changed_states|.
- void AddIgnoredChangedState(
- const AXEventGenerator::IgnoredChangedState& state,
- AXEventGenerator::IgnoredChangedStatesBitset* ignored_changed_states) {
- ignored_changed_states->set(static_cast<size_t>(state));
- }
- // Returns true if |ignored_changed_states| contains a particular
- // AXEventGenerator::IgnoredChangedState.
- bool HasIgnoredChangedState(
- const AXEventGenerator::IgnoredChangedStatesBitset& ignored_changed_states,
- const AXEventGenerator::IgnoredChangedState& state) {
- return ignored_changed_states[static_cast<size_t>(state)];
- }
- } // namespace
- //
- // AXEventGenerator::EventParams
- //
- AXEventGenerator::EventParams::EventParams(const Event event) : event(event) {}
- AXEventGenerator::EventParams::EventParams(
- const Event event,
- const ax::mojom::EventFrom event_from,
- const ax::mojom::Action event_from_action,
- const std::vector<AXEventIntent>& event_intents)
- : event(event),
- event_from(event_from),
- event_from_action(event_from_action),
- event_intents(event_intents) {}
- AXEventGenerator::EventParams::EventParams(const EventParams& other) = default;
- AXEventGenerator::EventParams::~EventParams() = default;
- AXEventGenerator::EventParams& AXEventGenerator::EventParams::operator=(
- const EventParams& other) = default;
- bool AXEventGenerator::EventParams::operator==(const EventParams& rhs) const {
- return rhs.event == event;
- }
- bool AXEventGenerator::EventParams::operator<(const EventParams& rhs) const {
- return event < rhs.event;
- }
- //
- // AXEventGenerator::TargetedEvent
- //
- AXEventGenerator::TargetedEvent::TargetedEvent(AXNodeID node_id,
- const EventParams& event_params)
- : node_id(node_id), event_params(event_params) {
- DCHECK_NE(node_id, kInvalidAXNodeID);
- }
- AXEventGenerator::TargetedEvent::~TargetedEvent() = default;
- //
- // AXEventGenerator::Iterator
- //
- AXEventGenerator::Iterator::Iterator(
- std::map<AXNodeID, std::set<EventParams>>::const_iterator map_start_iter,
- std::map<AXNodeID, std::set<EventParams>>::const_iterator map_end_iter)
- : map_iter_(map_start_iter), map_end_iter_(map_end_iter) {
- if (map_iter_ != map_end_iter_)
- set_iter_ = map_iter_->second.begin();
- }
- AXEventGenerator::Iterator::Iterator(const AXEventGenerator::Iterator& other) =
- default;
- AXEventGenerator::Iterator::~Iterator() = default;
- AXEventGenerator::Iterator& AXEventGenerator::Iterator::operator=(
- const Iterator& other) = default;
- AXEventGenerator::Iterator& AXEventGenerator::Iterator::operator++() {
- if (map_iter_ == map_end_iter_)
- return *this;
- DCHECK(set_iter_ != map_iter_->second.end());
- set_iter_++;
- // The map pointed to by |map_end_iter_| may contain empty sets of events in
- // its entries (i.e. |set_iter_| is at the iterator's end). In this case, we
- // want to increment |map_iter_| to point to the next entry of the map that
- // contains a non-empty set of events.
- while (map_iter_ != map_end_iter_ && set_iter_ == map_iter_->second.end()) {
- map_iter_++;
- if (map_iter_ != map_end_iter_)
- set_iter_ = map_iter_->second.begin();
- }
- return *this;
- }
- AXEventGenerator::Iterator AXEventGenerator::Iterator::operator++(int) {
- if (map_iter_ == map_end_iter_)
- return *this;
- Iterator iter = *this;
- ++(*this);
- return iter;
- }
- AXEventGenerator::TargetedEvent AXEventGenerator::Iterator::operator*() const {
- DCHECK(map_iter_ != map_end_iter_);
- DCHECK(set_iter_ != map_iter_->second.end());
- return AXEventGenerator::TargetedEvent(map_iter_->first, *set_iter_);
- }
- bool operator==(const AXEventGenerator::Iterator& lhs,
- const AXEventGenerator::Iterator& rhs) {
- if (lhs.map_iter_ == lhs.map_end_iter_ && rhs.map_iter_ == rhs.map_end_iter_)
- return true;
- return lhs.map_iter_ == rhs.map_iter_ && lhs.set_iter_ == rhs.set_iter_;
- }
- bool operator!=(const AXEventGenerator::Iterator& lhs,
- const AXEventGenerator::Iterator& rhs) {
- return !(lhs == rhs);
- }
- void swap(AXEventGenerator::Iterator& lhs, AXEventGenerator::Iterator& rhs) {
- if (lhs == rhs)
- return;
- std::map<AXNodeID, std::set<AXEventGenerator::EventParams>>::const_iterator
- map_iter = lhs.map_iter_;
- lhs.map_iter_ = rhs.map_iter_;
- rhs.map_iter_ = map_iter;
- std::map<AXNodeID, std::set<AXEventGenerator::EventParams>>::const_iterator
- map_end_iter = lhs.map_end_iter_;
- lhs.map_end_iter_ = rhs.map_end_iter_;
- rhs.map_end_iter_ = map_end_iter;
- std::set<AXEventGenerator::EventParams>::const_iterator set_iter =
- lhs.set_iter_;
- lhs.set_iter_ = rhs.set_iter_;
- rhs.set_iter_ = set_iter;
- }
- //
- // AXEventGenerator
- //
- AXEventGenerator::AXEventGenerator() = default;
- AXEventGenerator::AXEventGenerator(AXTree* tree) : tree_(tree) {
- if (tree_) {
- tree_event_observation_.Observe(tree_.get());
- live_region_tracker_ = std::make_unique<AXLiveRegionTracker>(*tree_);
- }
- }
- AXEventGenerator::~AXEventGenerator() = default;
- void AXEventGenerator::SetTree(AXTree* new_tree) {
- if (tree_) {
- DCHECK(tree_event_observation_.IsObservingSource(tree_.get()));
- tree_event_observation_.Reset();
- live_region_tracker_.reset();
- }
- tree_ = new_tree;
- if (tree_) {
- tree_event_observation_.Observe(tree_.get());
- live_region_tracker_ = std::make_unique<AXLiveRegionTracker>(*tree_);
- }
- }
- void AXEventGenerator::ReleaseTree() {
- tree_event_observation_.Reset();
- tree_ = nullptr;
- }
- bool AXEventGenerator::empty() const {
- return tree_events_.empty();
- }
- size_t AXEventGenerator::size() const {
- return tree_events_.size();
- }
- AXEventGenerator::Iterator AXEventGenerator::begin() const {
- auto map_iter = tree_events_.begin();
- if (map_iter != tree_events_.end()) {
- auto set_iter = map_iter->second.begin();
- // |tree_events_| may contain empty sets of events in its first entry
- // (i.e. |set_iter| is at the iterator's end). In this case, we want to
- // increment |map_iter| to point to the next entry of |tree_events_| that
- // contains a non-empty set of events.
- while (map_iter != tree_events_.end() &&
- set_iter == map_iter->second.end()) {
- map_iter++;
- if (map_iter != tree_events_.end())
- set_iter = map_iter->second.begin();
- }
- }
- return AXEventGenerator::Iterator(map_iter, tree_events_.end());
- }
- AXEventGenerator::Iterator AXEventGenerator::end() const {
- return AXEventGenerator::Iterator(tree_events_.end(), tree_events_.end());
- }
- void AXEventGenerator::ClearEvents() {
- tree_events_.clear();
- }
- void AXEventGenerator::AddEvent(AXNode* node, AXEventGenerator::Event event) {
- DCHECK(node);
- if (node->GetRole() == ax::mojom::Role::kInlineTextBox)
- return;
- // Extra Mac node creation and deletion in `AXTableInfo` directly call AXTree
- // observer methods, which skips all unserialization logic found in
- // `AXTree::Unserialize`.
- //
- // It only makes sense to generate events when we are called here within
- // `AXTree::Unserialize`. The below condition also guards against any future
- // callers of this type, whether Mac or not.
- if (!tree_->event_data())
- return;
- std::set<EventParams>& node_events = tree_events_[node->id()];
- node_events.emplace(event, tree_->event_data()->event_from,
- tree_->event_data()->event_from_action,
- tree_->event_data()->event_intents);
- }
- void AXEventGenerator::OnIgnoredWillChange(AXTree* tree,
- AXNode* node,
- bool is_ignored_new_value) {
- // If the node had been ignored and invisible before it changes to unignored,
- // then we should not fire `EVENT::PARENT_CHANGED` on its children because
- // they were previously unknown to ATs as they were in a hidden subtree.
- // TODO(nektar): Handle this by instead removing the `PARENT_CHANGED` event in
- // post-processing, because invisibility is not an accurate indicator of
- // whether nodes are known to the AT. In fact, a node can be invisible and
- // have visible descendants.
- if (!is_ignored_new_value && node->data().IsInvisible())
- nodes_to_suppress_parent_changed_on_.insert(node->id());
- }
- void AXEventGenerator::OnNodeDataChanged(AXTree* tree,
- const AXNodeData& old_node_data,
- const AXNodeData& new_node_data) {
- DCHECK_EQ(tree_, tree);
- AXNode* node = tree_->GetFromId(new_node_data.id);
- if (!node)
- return;
- // Fire CHILDREN_CHANGED events when the list of children updates.
- // Internally we store inline text box nodes as children of a static text
- // node or a line break node, which enables us to determine character bounds
- // and line layout. We don't expose those to platform APIs, though, so
- // suppress CHILDREN_CHANGED events on static text nodes.
- if (new_node_data.child_ids != old_node_data.child_ids && !node->IsText()) {
- if (node->IsIgnored()) {
- AXNode* unignored_parent = node->GetUnignoredParent();
- if (unignored_parent)
- AddEvent(unignored_parent, Event::CHILDREN_CHANGED);
- return;
- }
- AddEvent(node, Event::CHILDREN_CHANGED);
- }
- }
- void AXEventGenerator::OnRoleChanged(AXTree* tree,
- AXNode* node,
- ax::mojom::Role old_role,
- ax::mojom::Role new_role) {
- DCHECK_EQ(tree_, tree);
- AddEvent(node, Event::ROLE_CHANGED);
- }
- void AXEventGenerator::OnIgnoredChanged(AXTree* tree,
- AXNode* node,
- bool is_ignored_new_value) {
- DCHECK_EQ(tree_, tree);
- AXNode* unignored_parent = node->GetUnignoredParent();
- if (unignored_parent)
- AddEvent(unignored_parent, Event::CHILDREN_CHANGED);
- AddEvent(node, Event::IGNORED_CHANGED);
- if (!is_ignored_new_value)
- AddEvent(node, Event::SUBTREE_CREATED);
- if (node->GetRole() == ax::mojom::Role::kMenu) {
- if (is_ignored_new_value) {
- AddEvent(node, Event::MENU_POPUP_END);
- } else {
- AddEvent(node, Event::MENU_POPUP_START);
- }
- }
- // If the ignored state of a node has changed, the inclusion/exclusion of that
- // node in platform accessibility trees will change. Fire PARENT_CHANGED on
- // the unignored children of a node whose ignored state changed in order to
- // notify ATs that existing children may have been reparented.
- //
- // Note that we should not fire parent-changed if the invisible state of the
- // node has changed because when invisibility changes, the entire subtree is
- // being inserted / removed. For example if the 'hidden' property is changed
- // on list item, we should not fire parent-changed on the list marker or
- // static text children because: A) If the 'hidden' property has been removed,
- // both the list marker and the static text were not previously known to ATs
- // since they were not in the accessibility tree, and B) If the 'hidden'
- // property has been added then the children will be ignored and thus no
- // longer be in the accessibility tree that is exposed to the platform layer.
- // The second condition is automatically taken care of by the fact that we are
- // only iterating through unignored children.
- // TODO(nektar): Handle this by instead removing the `PARENT_CHANGED` event in
- // post-processing, because invisibility is not an accurate indicator of
- // whether nodes are known to the AT. In fact, a node can be invisible and
- // have visible descendants.
- const bool was_in_invisible_subtree =
- !base::Contains(nodes_to_suppress_parent_changed_on_, node->id());
- if (was_in_invisible_subtree) {
- for (auto iter = node->UnignoredChildrenBegin();
- iter != node->UnignoredChildrenEnd(); ++iter) {
- AddEvent(iter.get(), Event::PARENT_CHANGED);
- }
- }
- }
- void AXEventGenerator::OnStateChanged(AXTree* tree,
- AXNode* node,
- ax::mojom::State state,
- bool new_value) {
- DCHECK_EQ(tree_, tree);
- DCHECK_NE(state, ax::mojom::State::kIgnored)
- << "The ignored state should be handled in "
- "`AXEventGenerator::OnIgnoredChanged` and not in this method.";
- AddEvent(node, Event::STATE_CHANGED);
- AddEvent(node, Event::WIN_IACCESSIBLE_STATE_CHANGED);
- switch (state) {
- case ax::mojom::State::kExpanded:
- AddEvent(node, new_value ? Event::EXPANDED : Event::COLLAPSED);
- if (IsTableRow(node->GetRole()) ||
- node->GetRole() == ax::mojom::Role::kTreeItem) {
- AXNode* container = node;
- while (container && !IsRowContainer(container->GetRole()))
- container = container->parent();
- if (container)
- AddEvent(container, Event::ROW_COUNT_CHANGED);
- }
- break;
- case ax::mojom::State::kMultiline:
- AddEvent(node, Event::MULTILINE_STATE_CHANGED);
- break;
- case ax::mojom::State::kMultiselectable:
- AddEvent(node, Event::MULTISELECTABLE_STATE_CHANGED);
- break;
- case ax::mojom::State::kRequired:
- AddEvent(node, Event::REQUIRED_STATE_CHANGED);
- break;
- default:
- break;
- }
- }
- void AXEventGenerator::OnStringAttributeChanged(AXTree* tree,
- AXNode* node,
- ax::mojom::StringAttribute attr,
- const std::string& old_value,
- const std::string& new_value) {
- DCHECK_EQ(tree_, tree);
- switch (attr) {
- case ax::mojom::StringAttribute::kAccessKey:
- AddEvent(node, Event::ACCESS_KEY_CHANGED);
- break;
- case ax::mojom::StringAttribute::kAutoComplete:
- AddEvent(node, Event::AUTO_COMPLETE_CHANGED);
- break;
- case ax::mojom::StringAttribute::kCheckedStateDescription:
- AddEvent(node, Event::CHECKED_STATE_DESCRIPTION_CHANGED);
- break;
- case ax::mojom::StringAttribute::kClassName:
- AddEvent(node, Event::CLASS_NAME_CHANGED);
- break;
- case ax::mojom::StringAttribute::kDescription:
- AddEvent(node, Event::DESCRIPTION_CHANGED);
- break;
- case ax::mojom::StringAttribute::kFontFamily:
- AddEvent(node, Event::TEXT_ATTRIBUTE_CHANGED);
- break;
- case ax::mojom::StringAttribute::kImageAnnotation:
- // The image annotation is reported as part of the accessible name.
- AddEvent(node, Event::IMAGE_ANNOTATION_CHANGED);
- break;
- case ax::mojom::StringAttribute::kKeyShortcuts:
- AddEvent(node, Event::KEY_SHORTCUTS_CHANGED);
- break;
- case ax::mojom::StringAttribute::kLanguage:
- AddEvent(node, Event::LANGUAGE_CHANGED);
- break;
- case ax::mojom::StringAttribute::kLiveRelevant:
- AddEvent(node, Event::LIVE_RELEVANT_CHANGED);
- break;
- case ax::mojom::StringAttribute::kLiveStatus:
- AddEvent(node, Event::LIVE_STATUS_CHANGED);
- // Fire a LIVE_REGION_CREATED if the previous value was off, and the new
- // value is not-off. According to the ARIA spec, "When the property is not
- // set on an object that needs to send updates, the politeness level is
- // the value of the nearest ancestor that sets the aria-live attribute."
- // Example: A new chat message is added to the room with aria-live="off",
- // then the author removes aria-live.
- if (!IsAlert(node->GetRole())) {
- bool was_off = !old_value.empty()
- ? old_value == "off"
- : !node->data().IsContainedInActiveLiveRegion();
- bool is_off = !new_value.empty()
- ? new_value == "off"
- : !node->data().IsContainedInActiveLiveRegion();
- if (was_off && !is_off)
- AddEvent(node, Event::LIVE_REGION_CREATED);
- }
- break;
- case ax::mojom::StringAttribute::kName:
- // If the name of the root node changes, we expect OnTreeDataChanged to
- // add a DOCUMENT_TITLE_CHANGED event instead.
- if (node != tree->root())
- AddEvent(node, Event::NAME_CHANGED);
- // If it's in a live region, fire live region events.
- if (node->HasStringAttribute(
- ax::mojom::StringAttribute::kContainerLiveStatus)) {
- FireLiveRegionEvents(node);
- }
- FireValueInTextFieldChangedEventIfNecessary(tree, node);
- break;
- case ax::mojom::StringAttribute::kPlaceholder:
- AddEvent(node, Event::PLACEHOLDER_CHANGED);
- break;
- case ax::mojom::StringAttribute::kValue:
- if (node->data().IsRangeValueSupported()) {
- AddEvent(node, Event::RANGE_VALUE_CHANGED);
- } else if (IsSelectElement(node->GetRole())) {
- AddEvent(node, Event::SELECTED_VALUE_CHANGED);
- } else if (node->data().IsTextField()) {
- AddEvent(node, Event::VALUE_IN_TEXT_FIELD_CHANGED);
- }
- break;
- default:
- AddEvent(node, Event::OTHER_ATTRIBUTE_CHANGED);
- break;
- }
- }
- void AXEventGenerator::OnIntAttributeChanged(AXTree* tree,
- AXNode* node,
- ax::mojom::IntAttribute attr,
- int32_t old_value,
- int32_t new_value) {
- DCHECK_EQ(tree_, tree);
- switch (attr) {
- case ax::mojom::IntAttribute::kActivedescendantId:
- // Don't fire on invisible containers, as it confuses some screen readers,
- // such as NVDA.
- if (!node->data().IsInvisible()) {
- AddEvent(node, Event::ACTIVE_DESCENDANT_CHANGED);
- active_descendant_changed_.push_back(node);
- }
- break;
- case ax::mojom::IntAttribute::kCheckedState:
- AddEvent(node, Event::CHECKED_STATE_CHANGED);
- AddEvent(node, Event::WIN_IACCESSIBLE_STATE_CHANGED);
- break;
- case ax::mojom::IntAttribute::kAriaCurrentState:
- AddEvent(node, Event::ARIA_CURRENT_CHANGED);
- break;
- case ax::mojom::IntAttribute::kDropeffect:
- AddEvent(node, Event::DROPEFFECT_CHANGED);
- break;
- case ax::mojom::IntAttribute::kHasPopup:
- AddEvent(node, Event::HASPOPUP_CHANGED);
- AddEvent(node, Event::WIN_IACCESSIBLE_STATE_CHANGED);
- break;
- case ax::mojom::IntAttribute::kHierarchicalLevel:
- AddEvent(node, Event::HIERARCHICAL_LEVEL_CHANGED);
- break;
- case ax::mojom::IntAttribute::kInvalidState:
- AddEvent(node, Event::INVALID_STATUS_CHANGED);
- break;
- case ax::mojom::IntAttribute::kPosInSet:
- AddEvent(node, Event::POSITION_IN_SET_CHANGED);
- break;
- case ax::mojom::IntAttribute::kRestriction: {
- bool was_enabled;
- bool was_readonly;
- GetRestrictionStates(static_cast<ax::mojom::Restriction>(old_value),
- &was_enabled, &was_readonly);
- bool is_enabled;
- bool is_readonly;
- GetRestrictionStates(static_cast<ax::mojom::Restriction>(new_value),
- &is_enabled, &is_readonly);
- if (was_enabled != is_enabled) {
- AddEvent(node, Event::ENABLED_CHANGED);
- AddEvent(node, Event::WIN_IACCESSIBLE_STATE_CHANGED);
- }
- if (was_readonly != is_readonly) {
- AddEvent(node, Event::READONLY_CHANGED);
- AddEvent(node, Event::WIN_IACCESSIBLE_STATE_CHANGED);
- }
- break;
- }
- case ax::mojom::IntAttribute::kScrollX:
- AddEvent(node, Event::SCROLL_HORIZONTAL_POSITION_CHANGED);
- break;
- case ax::mojom::IntAttribute::kScrollY:
- AddEvent(node, Event::SCROLL_VERTICAL_POSITION_CHANGED);
- break;
- case ax::mojom::IntAttribute::kSortDirection:
- // Ignore sort direction changes on roles other than table headers and
- // grid headers.
- if (IsTableHeader(node->GetRole()))
- AddEvent(node, Event::SORT_CHANGED);
- break;
- case ax::mojom::IntAttribute::kImageAnnotationStatus:
- // The image annotation is reported as part of the accessible name.
- AddEvent(node, Event::IMAGE_ANNOTATION_CHANGED);
- break;
- case ax::mojom::IntAttribute::kSetSize:
- AddEvent(node, Event::SET_SIZE_CHANGED);
- break;
- case ax::mojom::IntAttribute::kBackgroundColor:
- case ax::mojom::IntAttribute::kColor:
- case ax::mojom::IntAttribute::kTextDirection:
- case ax::mojom::IntAttribute::kTextPosition:
- case ax::mojom::IntAttribute::kTextStyle:
- case ax::mojom::IntAttribute::kTextOverlineStyle:
- case ax::mojom::IntAttribute::kTextStrikethroughStyle:
- case ax::mojom::IntAttribute::kTextUnderlineStyle:
- AddEvent(node, Event::TEXT_ATTRIBUTE_CHANGED);
- break;
- case ax::mojom::IntAttribute::kTextAlign:
- // Alignment is exposed as an object attribute because it cannot apply to
- // a substring. However, for some platforms (e.g. ATK), alignment is a
- // text attribute. Therefore fire both events to ensure platforms get the
- // expected notifications.
- AddEvent(node, Event::ATK_TEXT_OBJECT_ATTRIBUTE_CHANGED);
- AddEvent(node, Event::OBJECT_ATTRIBUTE_CHANGED);
- break;
- default:
- AddEvent(node, Event::OTHER_ATTRIBUTE_CHANGED);
- break;
- }
- }
- void AXEventGenerator::OnFloatAttributeChanged(AXTree* tree,
- AXNode* node,
- ax::mojom::FloatAttribute attr,
- float old_value,
- float new_value) {
- DCHECK_EQ(tree_, tree);
- switch (attr) {
- case ax::mojom::FloatAttribute::kMaxValueForRange:
- AddEvent(node, Event::RANGE_VALUE_MAX_CHANGED);
- break;
- case ax::mojom::FloatAttribute::kMinValueForRange:
- AddEvent(node, Event::RANGE_VALUE_MIN_CHANGED);
- break;
- case ax::mojom::FloatAttribute::kStepValueForRange:
- AddEvent(node, Event::RANGE_VALUE_STEP_CHANGED);
- break;
- case ax::mojom::FloatAttribute::kValueForRange:
- AddEvent(node, Event::RANGE_VALUE_CHANGED);
- break;
- case ax::mojom::FloatAttribute::kFontSize:
- case ax::mojom::FloatAttribute::kFontWeight:
- AddEvent(node, Event::TEXT_ATTRIBUTE_CHANGED);
- break;
- case ax::mojom::FloatAttribute::kTextIndent:
- // Indentation is exposed as an object attribute because it cannot apply
- // to a substring. However, for some platforms (e.g. ATK), alignment is a
- // text attribute. Therefore fire both events to ensure platforms get the
- // expected notifications.
- AddEvent(node, Event::ATK_TEXT_OBJECT_ATTRIBUTE_CHANGED);
- AddEvent(node, Event::OBJECT_ATTRIBUTE_CHANGED);
- break;
- default:
- AddEvent(node, Event::OTHER_ATTRIBUTE_CHANGED);
- break;
- }
- }
- void AXEventGenerator::OnBoolAttributeChanged(AXTree* tree,
- AXNode* node,
- ax::mojom::BoolAttribute attr,
- bool new_value) {
- DCHECK_EQ(tree_, tree);
- switch (attr) {
- case ax::mojom::BoolAttribute::kBusy:
- AddEvent(node, Event::BUSY_CHANGED);
- AddEvent(node, Event::WIN_IACCESSIBLE_STATE_CHANGED);
- // Fire an 'invalidated' event when aria-busy becomes false.
- if (!new_value)
- AddEvent(node, Event::LAYOUT_INVALIDATED);
- break;
- case ax::mojom::BoolAttribute::kGrabbed:
- AddEvent(node, Event::GRABBED_CHANGED);
- break;
- case ax::mojom::BoolAttribute::kLiveAtomic:
- AddEvent(node, Event::ATOMIC_CHANGED);
- break;
- case ax::mojom::BoolAttribute::kSelected: {
- AddEvent(node, Event::SELECTED_CHANGED);
- AddEvent(node, Event::WIN_IACCESSIBLE_STATE_CHANGED);
- AXNode* container = node;
- while (container &&
- !IsContainerWithSelectableChildren(container->GetRole()))
- container = container->parent();
- if (container)
- AddEvent(container, Event::SELECTED_CHILDREN_CHANGED);
- break;
- }
- default:
- AddEvent(node, Event::OTHER_ATTRIBUTE_CHANGED);
- break;
- }
- }
- void AXEventGenerator::OnIntListAttributeChanged(
- AXTree* tree,
- AXNode* node,
- ax::mojom::IntListAttribute attr,
- const std::vector<int32_t>& old_value,
- const std::vector<int32_t>& new_value) {
- DCHECK_EQ(tree_, tree);
- switch (attr) {
- case ax::mojom::IntListAttribute::kControlsIds:
- AddEvent(node, Event::CONTROLS_CHANGED);
- break;
- case ax::mojom::IntListAttribute::kDetailsIds:
- AddEvent(node, Event::DETAILS_CHANGED);
- break;
- case ax::mojom::IntListAttribute::kDescribedbyIds:
- AddEvent(node, Event::DESCRIBED_BY_CHANGED);
- break;
- case ax::mojom::IntListAttribute::kFlowtoIds: {
- AddEvent(node, Event::FLOW_TO_CHANGED);
- // Fire FLOW_FROM_CHANGED for all nodes added or removed.
- // TODO(nektar): Consider removing this because flowto is unused by AT.
- for (AXNodeID id : ComputeIntListDifference(old_value, new_value)) {
- if (AXNode* target_node = tree->GetFromId(id))
- AddEvent(target_node, Event::FLOW_FROM_CHANGED);
- }
- break;
- }
- case ax::mojom::IntListAttribute::kLabelledbyIds:
- AddEvent(node, Event::LABELED_BY_CHANGED);
- break;
- case ax::mojom::IntListAttribute::kMarkerEnds:
- case ax::mojom::IntListAttribute::kMarkerStarts:
- case ax::mojom::IntListAttribute::kMarkerTypes:
- // On a native text field, the spelling- and grammar-error markers are
- // associated with children not exposed on any platform. Therefore, we
- // adjust the node we fire that event on here.
- if (AXNode* text_field = node->GetTextFieldAncestor()) {
- AddEvent(text_field, Event::TEXT_ATTRIBUTE_CHANGED);
- } else {
- AddEvent(node, Event::TEXT_ATTRIBUTE_CHANGED);
- }
- break;
- case ax::mojom::IntListAttribute::kCaretBounds:
- AddEvent(node, Event::CARET_BOUNDS_CHANGED);
- break;
- default:
- AddEvent(node, Event::OTHER_ATTRIBUTE_CHANGED);
- break;
- }
- }
- void AXEventGenerator::OnTreeDataChanged(AXTree* tree,
- const AXTreeData& old_tree_data,
- const AXTreeData& new_tree_data) {
- DCHECK_EQ(tree_, tree);
- DCHECK(tree->root());
- if (new_tree_data.title != old_tree_data.title)
- AddEvent(tree->root(), Event::DOCUMENT_TITLE_CHANGED);
- if (new_tree_data.sel_is_backward != old_tree_data.sel_is_backward ||
- new_tree_data.sel_anchor_object_id !=
- old_tree_data.sel_anchor_object_id ||
- new_tree_data.sel_anchor_offset != old_tree_data.sel_anchor_offset ||
- new_tree_data.sel_anchor_affinity != old_tree_data.sel_anchor_affinity ||
- new_tree_data.sel_focus_object_id != old_tree_data.sel_focus_object_id ||
- new_tree_data.sel_focus_offset != old_tree_data.sel_focus_offset ||
- new_tree_data.sel_focus_affinity != old_tree_data.sel_focus_affinity) {
- AddEvent(tree->root(), Event::DOCUMENT_SELECTION_CHANGED);
- // A special event is also fired internally for selection changes in text
- // fields. The reasons are both historical and in order to have a unified
- // way of handling selection changes between Web and Views. Views don't have
- // the concept of a document selection but some individual Views controls
- // have the ability for the user to select text inside them.
- const AXNode* selection_focus =
- tree_->GetFromId(new_tree_data.sel_focus_object_id);
- if (selection_focus) {
- // Even if it is possible for the document selection to span multiple text
- // fields, an event should still fire on the field where the selection
- // ends.
- if (AXNode* text_field = selection_focus->GetTextFieldAncestor())
- AddEvent(text_field, Event::TEXT_SELECTION_CHANGED);
- }
- }
- }
- void AXEventGenerator::OnNodeWillBeDeleted(AXTree* tree, AXNode* node) {
- DCHECK_EQ(tree_, tree);
- live_region_tracker_->OnNodeWillBeDeleted(*node);
- FireValueInTextFieldChangedEventIfNecessary(tree, node);
- }
- void AXEventGenerator::OnSubtreeWillBeDeleted(AXTree* tree, AXNode* node) {
- DCHECK_EQ(tree_, tree);
- }
- void AXEventGenerator::OnNodeWillBeReparented(AXTree* tree, AXNode* node) {
- DCHECK_EQ(tree_, tree);
- tree_events_.erase(node->id());
- }
- void AXEventGenerator::OnSubtreeWillBeReparented(AXTree* tree, AXNode* node) {
- DCHECK_EQ(tree_, tree);
- }
- void AXEventGenerator::OnNodeDeleted(AXTree* tree, AXNodeID node_id) {
- DCHECK_EQ(tree_, tree);
- tree_events_.erase(node_id);
- }
- void AXEventGenerator::OnNodeReparented(AXTree* tree, AXNode* node) {
- DCHECK_EQ(tree_, tree);
- AddEvent(node, Event::PARENT_CHANGED);
- }
- void AXEventGenerator::OnNodeCreated(AXTree* tree, AXNode* node) {
- DCHECK_EQ(tree_, tree);
- FireValueInTextFieldChangedEventIfNecessary(tree, node);
- }
- void AXEventGenerator::OnAtomicUpdateFinished(
- AXTree* tree,
- bool root_changed,
- const std::vector<Change>& changes) {
- DCHECK_EQ(tree_, tree);
- DCHECK(tree->root());
- for (const auto& change : changes) {
- DCHECK(change.node);
- if ((change.type == NODE_CREATED || change.type == SUBTREE_CREATED ||
- change.type == NODE_REPARENTED || change.type == SUBTREE_REPARENTED)) {
- if (change.node->HasStringAttribute(
- ax::mojom::StringAttribute::kContainerLiveStatus)) {
- live_region_tracker_->UpdateCachedLiveRootForNode(*change.node);
- }
- }
- if (change.type == SUBTREE_CREATED) {
- AddEvent(change.node, Event::SUBTREE_CREATED);
- } else if (change.type != NODE_CREATED) {
- FireRelationSourceEvents(tree, change.node);
- continue;
- }
- if (IsAlert(change.node->GetRole()))
- AddEvent(change.node, Event::ALERT);
- else if (change.node->data().IsActiveLiveRegionRoot())
- AddEvent(change.node, Event::LIVE_REGION_CREATED);
- else if (change.node->data().IsContainedInActiveLiveRegion())
- FireLiveRegionEvents(change.node);
- }
- FireActiveDescendantEvents();
- nodes_to_suppress_parent_changed_on_.clear();
- // If we queued any live region change events during node deletion, add them
- // here. It's necessary to wait to add these events, because an update might
- // destroy and recreate live region roots after OnNodeWillBeDeleted is called.
- // TODO(mrobinson): Consider designing AXEventGenerator to have a more
- // resilient way to queue up events for nodes that might be destroyed and
- // recreated in a single update.
- for (auto& id : live_region_tracker_->live_region_roots_with_changes()) {
- // If node is null, the live region root with a change was deleted during
- // the course of this update and we should not trigger an event.
- if (AXNode* node = tree_->GetFromId(id)) {
- AddEvent(node, Event::LIVE_REGION_CHANGED);
- }
- }
- live_region_tracker_->OnAtomicUpdateFinished();
- PostprocessEvents();
- }
- void AXEventGenerator::AddEventsForTesting(
- const AXNode& node,
- const std::set<EventParams>& events) {
- tree_events_[node.id()] = events;
- }
- void AXEventGenerator::FireLiveRegionEvents(AXNode* node) {
- AXNode* live_root = live_region_tracker_->GetLiveRootIfNotBusy(*node);
- // Note that |live_root| might be nullptr if a live region was just added,
- // or if it has aria-busy="true".
- if (!live_root)
- return;
- // Fire LIVE_REGION_NODE_CHANGED on each node that changed.
- if (!node->GetStringAttribute(ax::mojom::StringAttribute::kName).empty()) {
- AddEvent(node, Event::LIVE_REGION_NODE_CHANGED);
- }
- // Fire LIVE_REGION_CHANGED on the root of the live region.
- AddEvent(live_root, Event::LIVE_REGION_CHANGED);
- }
- void AXEventGenerator::FireActiveDescendantEvents() {
- for (AXNode* node : active_descendant_changed_) {
- AXNode* descendant = tree_->GetFromId(
- node->GetIntAttribute(ax::mojom::IntAttribute::kActivedescendantId));
- if (!descendant)
- continue;
- switch (descendant->GetRole()) {
- case ax::mojom::Role::kMenuItem:
- case ax::mojom::Role::kMenuItemCheckBox:
- case ax::mojom::Role::kMenuItemRadio:
- case ax::mojom::Role::kMenuListOption:
- AddEvent(descendant, Event::MENU_ITEM_SELECTED);
- break;
- default:
- break;
- }
- }
- active_descendant_changed_.clear();
- }
- void AXEventGenerator::FireValueInTextFieldChangedEventIfNecessary(
- AXTree* tree,
- AXNode* target_node) {
- // Text is only found on leaf nodes, so the text in a text field would change
- // if any of the leaf nodes in it have changed their names.
- if (!target_node->IsLeaf())
- return;
- AXNode* text_field_ancestor = target_node->GetTextFieldAncestor();
- if (!text_field_ancestor || text_field_ancestor == target_node)
- return;
- AddEvent(text_field_ancestor, Event::EDITABLE_TEXT_CHANGED);
- AddEvent(text_field_ancestor, Event::VALUE_IN_TEXT_FIELD_CHANGED);
- }
- void AXEventGenerator::FireRelationSourceEvents(AXTree* tree,
- AXNode* target_node) {
- AXNodeID target_id = target_node->id();
- std::set<AXNode*> source_nodes;
- auto callback = [&](const auto& entry) {
- const auto& target_to_sources = entry.second;
- auto sources_it = target_to_sources.find(target_id);
- if (sources_it == target_to_sources.end())
- return;
- auto sources = sources_it->second;
- std::for_each(sources.begin(), sources.end(), [&](AXNodeID source_id) {
- AXNode* source_node = tree->GetFromId(source_id);
- if (!source_node || source_nodes.count(source_node) > 0)
- return;
- source_nodes.insert(source_node);
- // GCC < 6.4 requires this pointer when calling a member
- // function in anonymous function
- this->AddEvent(source_node, Event::RELATED_NODE_CHANGED);
- });
- };
- std::for_each(tree->int_reverse_relations().begin(),
- tree->int_reverse_relations().end(), callback);
- std::for_each(
- tree->intlist_reverse_relations().begin(),
- tree->intlist_reverse_relations().end(), [&](auto& entry) {
- // Explicitly exclude relationships for which an additional event on the
- // source node would cause extra noise. For example, kRadioGroupIds
- // forms relations among all radio buttons and serves little value for
- // AT to get events on the previous radio button in the group.
- if (entry.first != ax::mojom::IntListAttribute::kRadioGroupIds)
- callback(entry);
- });
- }
- void AXEventGenerator::TrimEventsDueToAncestorIgnoredChanged(
- AXNode* node,
- std::map<AXNode*, IgnoredChangedStatesBitset>&
- ancestor_ignored_changed_map) {
- DCHECK(node);
- // Recursively compute and cache ancestor ignored changed results in
- // |ancestor_ignored_changed_map|, if |node|'s ancestors have become ignored
- // and the ancestor's ignored changed results have not been cached.
- if (node->parent() &&
- !base::Contains(ancestor_ignored_changed_map, node->parent())) {
- TrimEventsDueToAncestorIgnoredChanged(node->parent(),
- ancestor_ignored_changed_map);
- }
- // If an ancestor of |node| changed to ignored state (hide), append hide state
- // to the corresponding entry in the map for |node|. Similarly, if an ancestor
- // of |node| removed its ignored state (show), we append show state to the
- // corresponding entry in map for |node| as well. If |node| flipped its
- // ignored state as well, we want to remove various events related to
- // IGNORED_CHANGED event.
- const auto& parent_map_iter =
- ancestor_ignored_changed_map.find(node->parent());
- const auto& curr_events_iter = tree_events_.find(node->id());
- // Initialize |ancestor_ignored_changed_map[node]| with an empty bitset,
- // representing neither |node| nor its ancestor has IGNORED_CHANGED.
- IgnoredChangedStatesBitset& ancestor_ignored_changed_states =
- ancestor_ignored_changed_map[node];
- // If |ancestor_ignored_changed_map| contains an entry for |node|'s
- // ancestor's and the ancestor has either show/hide state, we want to populate
- // |node|'s show/hide state in the map based on its cached ancestor result.
- // An empty entry in |ancestor_ignored_changed_map| for |node| means that
- // neither |node| nor its ancestor has IGNORED_CHANGED.
- if (parent_map_iter != ancestor_ignored_changed_map.end()) {
- // Propagate ancestor's show/hide states to |node|'s entry in the map.
- if (HasIgnoredChangedState(parent_map_iter->second,
- IgnoredChangedState::kHide)) {
- AddIgnoredChangedState(IgnoredChangedState::kHide,
- &ancestor_ignored_changed_states);
- }
- if (HasIgnoredChangedState(parent_map_iter->second,
- IgnoredChangedState::kShow)) {
- AddIgnoredChangedState(IgnoredChangedState::kShow,
- &ancestor_ignored_changed_states);
- }
- // If |node| has IGNORED changed with show/hide state that matches one of
- // its ancestors' IGNORED changed show/hide states, we want to remove
- // |node|'s IGNORED_CHANGED related events.
- if (curr_events_iter != tree_events_.end() &&
- HasEvent(curr_events_iter->second, Event::IGNORED_CHANGED)) {
- if ((HasIgnoredChangedState(parent_map_iter->second,
- IgnoredChangedState::kHide) &&
- node->IsIgnored()) ||
- (HasIgnoredChangedState(parent_map_iter->second,
- IgnoredChangedState::kShow) &&
- !node->IsIgnored())) {
- RemoveEvent(&(curr_events_iter->second), Event::IGNORED_CHANGED);
- RemoveEventsDueToIgnoredChanged(&(curr_events_iter->second));
- }
- if (node->IsIgnored()) {
- AddIgnoredChangedState(IgnoredChangedState::kHide,
- &ancestor_ignored_changed_states);
- } else {
- AddIgnoredChangedState(IgnoredChangedState::kShow,
- &ancestor_ignored_changed_states);
- }
- }
- return;
- }
- // If ignored changed results for ancestors are not cached, calculate the
- // corresponding entry for |node| in the map using the ignored states and
- // events of |node|.
- if (curr_events_iter != tree_events_.end() &&
- HasEvent(curr_events_iter->second, Event::IGNORED_CHANGED)) {
- if (node->IsIgnored()) {
- AddIgnoredChangedState(IgnoredChangedState::kHide,
- &ancestor_ignored_changed_states);
- } else {
- AddIgnoredChangedState(IgnoredChangedState::kShow,
- &ancestor_ignored_changed_states);
- }
- return;
- }
- }
- void AXEventGenerator::PostprocessEvents() {
- std::map<AXNode*, IgnoredChangedStatesBitset> ancestor_ignored_changed_map;
- std::set<AXNode*> removed_subtree_created_nodes;
- std::set<AXNode*> removed_parent_changed_nodes;
- // First pass through |tree_events_|, remove events that we do not need.
- for (auto& iter : tree_events_) {
- AXNodeID node_id = iter.first;
- AXNode* node = tree_->GetFromId(node_id);
- // TODO(http://crbug.com/2279799): remove all of the cases that could
- // add a null node to |tree_events|.
- DCHECK(node);
- if (!node)
- continue;
- std::set<EventParams>& node_events = iter.second;
- // A newly created live region or alert should not *also* fire a
- // live region changed event.
- if (HasEvent(node_events, Event::ALERT) ||
- HasEvent(node_events, Event::LIVE_REGION_CREATED)) {
- RemoveEvent(&node_events, Event::LIVE_REGION_CHANGED);
- }
- if (HasEvent(node_events, Event::IGNORED_CHANGED)) {
- // If a node toggled its ignored state, we only want to fire
- // IGNORED_CHANGED event on the top most ancestor where this ignored state
- // change takes place and suppress all the descendants's IGNORED_CHANGED
- // events.
- TrimEventsDueToAncestorIgnoredChanged(node, ancestor_ignored_changed_map);
- RemoveEventsDueToIgnoredChanged(&node_events);
- }
- // When the selected option in an expanded select element changes, the
- // foreground and background colors change. But we don't want to treat
- // those as text attribute changes. This can also happen when a widget
- // such as a button becomes enabled/disabled.
- if (HasEvent(node_events, Event::SELECTED_CHANGED) ||
- HasEvent(node_events, Event::ENABLED_CHANGED)) {
- RemoveEvent(&node_events, Event::TEXT_ATTRIBUTE_CHANGED);
- }
- AXNode* parent = node->GetUnignoredParent();
- // Don't fire text attribute changed on this node if its immediate parent
- // also has text attribute changed.
- if (parent && HasEvent(node_events, Event::TEXT_ATTRIBUTE_CHANGED) &&
- tree_events_.find(parent->id()) != tree_events_.end() &&
- HasEvent(tree_events_[parent->id()], Event::TEXT_ATTRIBUTE_CHANGED)) {
- RemoveEvent(&node_events, Event::TEXT_ATTRIBUTE_CHANGED);
- }
- // Don't fire parent changed on this node if any of its ancestors also has
- // parent changed. However, if the ancestor also has subtree created, it is
- // possible that the created subtree is actually a newly unignored parent
- // of an existing node. In that instance, we need to inform ATs that the
- // existing node's parent has changed on the platform.
- if (HasEvent(node_events, Event::PARENT_CHANGED)) {
- while (parent && (tree_events_.find(parent->id()) != tree_events_.end() ||
- base::Contains(removed_parent_changed_nodes, parent))) {
- if ((base::Contains(removed_parent_changed_nodes, parent) ||
- HasEvent(tree_events_[parent->id()], Event::PARENT_CHANGED)) &&
- !HasEvent(tree_events_[parent->id()], Event::SUBTREE_CREATED)) {
- RemoveEvent(&node_events, Event::PARENT_CHANGED);
- removed_parent_changed_nodes.insert(node);
- break;
- }
- parent = parent->GetUnignoredParent();
- }
- }
- // Don't fire parent changed on ignored events, because these nodes do not
- // exist for platform accessibility. If the node toggles the ignored state,
- // that's an IGNORED_CHANGED event and it's treated differently. In some
- // occasions it may result in a PARENT_CHANGED event on a different node
- // (see AXEventGenerator::OnIgnoredChanged).
- if (node->IsIgnored())
- RemoveEvent(&node_events, Event::PARENT_CHANGED);
- // Don't fire subtree created on this node if any of its ancestors also has
- // subtree created.
- parent = node->GetUnignoredParent();
- if (HasEvent(node_events, Event::SUBTREE_CREATED)) {
- while (parent &&
- (tree_events_.find(parent->id()) != tree_events_.end() ||
- base::Contains(removed_subtree_created_nodes, parent))) {
- if (base::Contains(removed_subtree_created_nodes, parent) ||
- HasEvent(tree_events_[parent->id()], Event::SUBTREE_CREATED)) {
- RemoveEvent(&node_events, Event::SUBTREE_CREATED);
- removed_subtree_created_nodes.insert(node);
- break;
- }
- parent = parent->GetUnignoredParent();
- }
- }
- }
- // Second pass through |tree_events_|, remove nodes that do not have any
- // events left.
- auto iter = tree_events_.begin();
- while (iter != tree_events_.end()) {
- std::set<EventParams>& node_events = iter->second;
- if (node_events.empty())
- iter = tree_events_.erase(iter);
- else
- ++iter;
- }
- }
- // static
- void AXEventGenerator::GetRestrictionStates(ax::mojom::Restriction restriction,
- bool* is_enabled,
- bool* is_readonly) {
- switch (restriction) {
- case ax::mojom::Restriction::kDisabled:
- *is_enabled = false;
- *is_readonly = true;
- break;
- case ax::mojom::Restriction::kReadOnly:
- *is_enabled = true;
- *is_readonly = true;
- break;
- case ax::mojom::Restriction::kNone:
- *is_enabled = true;
- *is_readonly = false;
- break;
- }
- }
- // static
- std::vector<int32_t> AXEventGenerator::ComputeIntListDifference(
- const std::vector<int32_t>& lhs,
- const std::vector<int32_t>& rhs) {
- std::set<int32_t> sorted_lhs(lhs.cbegin(), lhs.cend());
- std::set<int32_t> sorted_rhs(rhs.cbegin(), rhs.cend());
- std::vector<int32_t> result;
- std::set_symmetric_difference(sorted_lhs.cbegin(), sorted_lhs.cend(),
- sorted_rhs.cbegin(), sorted_rhs.cend(),
- std::back_inserter(result));
- return result;
- }
- std::ostream& operator<<(std::ostream& os, AXEventGenerator::Event event) {
- return os << ToString(event);
- }
- const char* ToString(AXEventGenerator::Event event) {
- switch (event) {
- case AXEventGenerator::Event::ACCESS_KEY_CHANGED:
- return "accessKeyChanged";
- case AXEventGenerator::Event::ACTIVE_DESCENDANT_CHANGED:
- return "activeDescendantChanged";
- case AXEventGenerator::Event::ALERT:
- return "alert";
- case AXEventGenerator::Event::ARIA_CURRENT_CHANGED:
- return "ariaCurrentChanged";
- case AXEventGenerator::Event::ATK_TEXT_OBJECT_ATTRIBUTE_CHANGED:
- return "atkTextObjectAttributeChanged";
- case AXEventGenerator::Event::ATOMIC_CHANGED:
- return "atomicChanged";
- case AXEventGenerator::Event::AUTO_COMPLETE_CHANGED:
- return "autoCompleteChanged";
- case AXEventGenerator::Event::BUSY_CHANGED:
- return "busyChanged";
- case AXEventGenerator::Event::CARET_BOUNDS_CHANGED:
- return "caretBoundsChanged";
- case AXEventGenerator::Event::CHECKED_STATE_CHANGED:
- return "checkedStateChanged";
- case AXEventGenerator::Event::CHECKED_STATE_DESCRIPTION_CHANGED:
- return "checkedStateDescriptionChanged";
- case AXEventGenerator::Event::CHILDREN_CHANGED:
- return "childrenChanged";
- case AXEventGenerator::Event::CLASS_NAME_CHANGED:
- return "classNameChanged";
- case AXEventGenerator::Event::COLLAPSED:
- return "collapsed";
- case AXEventGenerator::Event::CONTROLS_CHANGED:
- return "controlsChanged";
- case AXEventGenerator::Event::DETAILS_CHANGED:
- return "detailsChanged";
- case AXEventGenerator::Event::DESCRIBED_BY_CHANGED:
- return "describedByChanged";
- case AXEventGenerator::Event::DESCRIPTION_CHANGED:
- return "descriptionChanged";
- case AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED:
- return "documentSelectionChanged";
- case AXEventGenerator::Event::DOCUMENT_TITLE_CHANGED:
- return "documentTitleChanged";
- case AXEventGenerator::Event::DROPEFFECT_CHANGED:
- return "dropeffectChanged";
- case ui::AXEventGenerator::Event::EDITABLE_TEXT_CHANGED:
- return "editableTextChanged";
- case AXEventGenerator::Event::ENABLED_CHANGED:
- return "enabledChanged";
- case AXEventGenerator::Event::EXPANDED:
- return "expanded";
- case AXEventGenerator::Event::FOCUS_CHANGED:
- return "focusChanged";
- case AXEventGenerator::Event::FLOW_FROM_CHANGED:
- return "flowFromChanged";
- case AXEventGenerator::Event::FLOW_TO_CHANGED:
- return "flowToChanged";
- case AXEventGenerator::Event::GRABBED_CHANGED:
- return "grabbedChanged";
- case AXEventGenerator::Event::HASPOPUP_CHANGED:
- return "haspopupChanged";
- case AXEventGenerator::Event::HIERARCHICAL_LEVEL_CHANGED:
- return "hierarchicalLevelChanged";
- case ui::AXEventGenerator::Event::IGNORED_CHANGED:
- return "ignoredChanged";
- case AXEventGenerator::Event::IMAGE_ANNOTATION_CHANGED:
- return "imageAnnotationChanged";
- case AXEventGenerator::Event::INVALID_STATUS_CHANGED:
- return "invalidStatusChanged";
- case AXEventGenerator::Event::KEY_SHORTCUTS_CHANGED:
- return "keyShortcutsChanged";
- case AXEventGenerator::Event::LABELED_BY_CHANGED:
- return "labeledByChanged";
- case AXEventGenerator::Event::LANGUAGE_CHANGED:
- return "languageChanged";
- case AXEventGenerator::Event::LAYOUT_INVALIDATED:
- return "layoutInvalidated";
- case AXEventGenerator::Event::LIVE_REGION_CHANGED:
- return "liveRegionChanged";
- case AXEventGenerator::Event::LIVE_REGION_CREATED:
- return "liveRegionCreated";
- case AXEventGenerator::Event::LIVE_REGION_NODE_CHANGED:
- return "liveRegionNodeChanged";
- case AXEventGenerator::Event::LIVE_RELEVANT_CHANGED:
- return "liveRelevantChanged";
- case AXEventGenerator::Event::LIVE_STATUS_CHANGED:
- return "liveStatusChanged";
- case AXEventGenerator::Event::MENU_ITEM_SELECTED:
- return "menuItemSelected";
- case ui::AXEventGenerator::Event::MENU_POPUP_END:
- return "menuPopupEnd";
- case ui::AXEventGenerator::Event::MENU_POPUP_START:
- return "menuPopupStart";
- case AXEventGenerator::Event::MULTILINE_STATE_CHANGED:
- return "multilineStateChanged";
- case AXEventGenerator::Event::MULTISELECTABLE_STATE_CHANGED:
- return "multiselectableStateChanged";
- case AXEventGenerator::Event::NAME_CHANGED:
- return "nameChanged";
- case AXEventGenerator::Event::OBJECT_ATTRIBUTE_CHANGED:
- return "objectAttributeChanged";
- case AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED:
- return "otherAttributeChanged";
- case AXEventGenerator::Event::PARENT_CHANGED:
- return "parentChanged";
- case AXEventGenerator::Event::PLACEHOLDER_CHANGED:
- return "placeholderChanged";
- case AXEventGenerator::Event::PORTAL_ACTIVATED:
- return "portalActivated";
- case AXEventGenerator::Event::POSITION_IN_SET_CHANGED:
- return "positionInSetChanged";
- case AXEventGenerator::Event::RANGE_VALUE_CHANGED:
- return "rangeValueChanged";
- case AXEventGenerator::Event::RANGE_VALUE_MAX_CHANGED:
- return "rangeValueMaxChanged";
- case AXEventGenerator::Event::RANGE_VALUE_MIN_CHANGED:
- return "rangeValueMinChanged";
- case AXEventGenerator::Event::RANGE_VALUE_STEP_CHANGED:
- return "rangeValueStepChanged";
- case AXEventGenerator::Event::READONLY_CHANGED:
- return "readonlyChanged";
- case AXEventGenerator::Event::RELATED_NODE_CHANGED:
- return "relatedNodeChanged";
- case AXEventGenerator::Event::REQUIRED_STATE_CHANGED:
- return "requiredStateChanged";
- case AXEventGenerator::Event::ROLE_CHANGED:
- return "roleChanged";
- case AXEventGenerator::Event::ROW_COUNT_CHANGED:
- return "rowCountChanged";
- case AXEventGenerator::Event::SCROLL_HORIZONTAL_POSITION_CHANGED:
- return "scrollHorizontalPositionChanged";
- case AXEventGenerator::Event::SCROLL_VERTICAL_POSITION_CHANGED:
- return "scrollVerticalPositionChanged";
- case AXEventGenerator::Event::SELECTED_CHANGED:
- return "selectedChanged";
- case AXEventGenerator::Event::SELECTED_CHILDREN_CHANGED:
- return "selectedChildrenChanged";
- case AXEventGenerator::Event::SELECTED_VALUE_CHANGED:
- return "selectedValueChanged";
- case AXEventGenerator::Event::TEXT_SELECTION_CHANGED:
- return "textSelectionChanged";
- case AXEventGenerator::Event::SET_SIZE_CHANGED:
- return "setSizeChanged";
- case AXEventGenerator::Event::SORT_CHANGED:
- return "sortChanged";
- case AXEventGenerator::Event::STATE_CHANGED:
- return "stateChanged";
- case AXEventGenerator::Event::SUBTREE_CREATED:
- return "subtreeCreated";
- case AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED:
- return "textAttributeChanged";
- case AXEventGenerator::Event::VALUE_IN_TEXT_FIELD_CHANGED:
- return "valueInTextFieldChanged";
- case AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED:
- return "winIaccessibleStateChanged";
- }
- }
- } // namespace ui
|