blob: 106364adb5273fb055b04ed21fa4aba3b5b0c5aa [file] [log] [blame]
// 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/stl_util.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node.h"
#include "ui/accessibility/ax_role_properties.h"
namespace ui {
namespace {
bool IsActiveLiveRegion(const AXTreeObserver::Change& change) {
return change.node->data().HasStringAttribute(
ax::mojom::StringAttribute::kLiveStatus) &&
change.node->data().GetStringAttribute(
ax::mojom::StringAttribute::kLiveStatus) != "off";
}
bool IsContainedInLiveRegion(const AXTreeObserver::Change& change) {
return change.node->data().HasStringAttribute(
ax::mojom::StringAttribute::kContainerLiveStatus) &&
change.node->data().HasStringAttribute(
ax::mojom::StringAttribute::kName);
}
bool HasOtherLiveRegionEvent(
const std::set<AXEventGenerator::EventParams>& events) {
auto is_live_region_event = [](const AXEventGenerator::EventParams& params) {
return params.event == AXEventGenerator::Event::ALERT ||
params.event == AXEventGenerator::Event::LIVE_REGION_CREATED;
};
return std::find_if(events.begin(), events.end(), is_live_region_event) !=
events.end();
}
} // namespace
AXEventGenerator::EventParams::EventParams(Event event,
ax::mojom::EventFrom event_from)
: event(event), event_from(event_from) {}
AXEventGenerator::TargetedEvent::TargetedEvent(AXNode* node,
const EventParams& event_params)
: node(node), event_params(event_params) {
DCHECK(node);
}
bool AXEventGenerator::EventParams::operator==(const EventParams& rhs) {
return rhs.event == event;
}
bool AXEventGenerator::EventParams::operator<(const EventParams& rhs) const {
return event < rhs.event;
}
AXEventGenerator::Iterator::Iterator(
const std::map<AXNode*, std::set<EventParams>>& map,
const std::map<AXNode*, std::set<EventParams>>::const_iterator& head)
: map_(map), map_iter_(head) {
if (map_iter_ != map.end())
set_iter_ = map_iter_->second.begin();
}
AXEventGenerator::Iterator::Iterator(const AXEventGenerator::Iterator& other) =
default;
AXEventGenerator::Iterator::~Iterator() = default;
bool AXEventGenerator::Iterator::operator!=(
const AXEventGenerator::Iterator& rhs) const {
return map_iter_ != rhs.map_iter_ ||
(map_iter_ != map_.end() && set_iter_ != rhs.set_iter_);
}
AXEventGenerator::Iterator& AXEventGenerator::Iterator::operator++() {
if (map_iter_ == map_.end())
return *this;
set_iter_++;
while (map_iter_ != map_.end() && set_iter_ == map_iter_->second.end()) {
map_iter_++;
if (map_iter_ != map_.end())
set_iter_ = map_iter_->second.begin();
}
return *this;
}
AXEventGenerator::TargetedEvent AXEventGenerator::Iterator::operator*() const {
DCHECK(map_iter_ != map_.end() && set_iter_ != map_iter_->second.end());
return AXEventGenerator::TargetedEvent(map_iter_->first, *set_iter_);
}
AXEventGenerator::AXEventGenerator() = default;
AXEventGenerator::AXEventGenerator(AXTree* tree) : tree_(tree) {
if (tree_)
tree_->AddObserver(this);
}
AXEventGenerator::~AXEventGenerator() {
if (tree_)
tree_->RemoveObserver(this);
}
void AXEventGenerator::SetTree(AXTree* new_tree) {
if (tree_)
tree_->RemoveObserver(this);
tree_ = new_tree;
if (tree_)
tree_->AddObserver(this);
}
void AXEventGenerator::ReleaseTree() {
tree_ = nullptr;
}
void AXEventGenerator::ClearEvents() {
tree_events_.clear();
}
void AXEventGenerator::AddEvent(AXNode* node, AXEventGenerator::Event event) {
DCHECK(node);
if (node->data().role == ax::mojom::Role::kInlineTextBox)
return;
std::set<EventParams>& node_events = tree_events_[node];
// A newly created live region or alert should not *also* fire a
// live region changed event.
if (event == Event::LIVE_REGION_CHANGED &&
HasOtherLiveRegionEvent(node_events)) {
return;
}
node_events.emplace(event, ax::mojom::EventFrom::kNone);
}
void AXEventGenerator::OnNodeDataChanged(AXTree* tree,
const AXNodeData& old_node_data,
const AXNodeData& new_node_data) {
DCHECK_EQ(tree_, tree);
// Fire CHILDREN_CHANGED events when the list of children updates.
// Internally we store inline text box nodes as children of a static text
// 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 &&
new_node_data.role != ax::mojom::Role::kStaticText) {
AXNode* node = tree_->GetFromId(new_node_data.id);
tree_events_[node].emplace(Event::CHILDREN_CHANGED,
ax::mojom::EventFrom::kNone);
}
}
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::OnStateChanged(AXTree* tree,
AXNode* node,
ax::mojom::State state,
bool new_value) {
DCHECK_EQ(tree_, tree);
AddEvent(node, Event::STATE_CHANGED);
switch (state) {
case ax::mojom::State::kExpanded:
AddEvent(node, new_value ? Event::EXPANDED : Event::COLLAPSED);
if (node->data().role == ax::mojom::Role::kRow ||
node->data().role == ax::mojom::Role::kTreeItem) {
AXNode* container = node;
while (container && !IsRowContainer(container->data().role))
container = container->parent();
if (container)
AddEvent(container, Event::ROW_COUNT_CHANGED);
}
break;
case ax::mojom::State::kIgnored: {
AXNode* unignored_parent = node->GetUnignoredParent();
if (unignored_parent)
AddEvent(unignored_parent, Event::CHILDREN_CHANGED);
AddEvent(node, Event::IGNORED_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::kAriaInvalidValue:
AddEvent(node, Event::INVALID_STATUS_CHANGED);
break;
case ax::mojom::StringAttribute::kAutoComplete:
AddEvent(node, Event::AUTO_COMPLETE_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::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.
if (!IsAlert(node->data().role)) {
bool old_state = !old_value.empty() && old_value != "off";
bool new_state = !new_value.empty() && new_value != "off";
if (!old_state && new_state)
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 (node->data().HasStringAttribute(
ax::mojom::StringAttribute::kContainerLiveStatus)) {
FireLiveRegionEvents(node);
}
break;
case ax::mojom::StringAttribute::kPlaceholder:
AddEvent(node, Event::PLACEHOLDER_CHANGED);
break;
case ax::mojom::StringAttribute::kValue:
AddEvent(node, Event::VALUE_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;
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().HasState(ax::mojom::State::kInvisible)) {
AddEvent(node, Event::ACTIVE_DESCENDANT_CHANGED);
active_descendant_changed_.push_back(node);
}
break;
case ax::mojom::IntAttribute::kCheckedState:
AddEvent(node, Event::CHECKED_STATE_CHANGED);
break;
case ax::mojom::IntAttribute::kDropeffect:
AddEvent(node, Event::DROPEFFECT_CHANGED);
break;
case ax::mojom::IntAttribute::kHasPopup:
AddEvent(node, Event::HASPOPUP_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);
if (was_readonly != is_readonly)
AddEvent(node, Event::READONLY_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->data().role))
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;
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::VALUE_MAX_CHANGED);
break;
case ax::mojom::FloatAttribute::kMinValueForRange:
AddEvent(node, Event::VALUE_MIN_CHANGED);
break;
case ax::mojom::FloatAttribute::kStepValueForRange:
AddEvent(node, Event::VALUE_STEP_CHANGED);
break;
case ax::mojom::FloatAttribute::kValueForRange:
AddEvent(node, Event::VALUE_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);
// 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);
AXNode* container = node;
while (container &&
!IsContainerWithSelectableChildren(container->data().role))
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::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
for (int32_t id : ComputeIntListDifference(old_value, new_value)) {
if (auto* 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;
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);
if (new_tree_data.loaded && !old_tree_data.loaded &&
ShouldFireLoadEvents(tree->root())) {
AddEvent(tree->root(), Event::LOAD_COMPLETE);
}
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);
}
if (new_tree_data.title != old_tree_data.title)
AddEvent(tree->root(), Event::DOCUMENT_TITLE_CHANGED);
}
void AXEventGenerator::OnNodeWillBeDeleted(AXTree* tree, AXNode* node) {
DCHECK_EQ(tree_, tree);
tree_events_.erase(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);
}
void AXEventGenerator::OnSubtreeWillBeReparented(AXTree* tree, AXNode* node) {
DCHECK_EQ(tree_, tree);
}
void AXEventGenerator::OnAtomicUpdateFinished(
AXTree* tree,
bool root_changed,
const std::vector<Change>& changes) {
DCHECK_EQ(tree_, tree);
if (root_changed && ShouldFireLoadEvents(tree->root())) {
if (tree->data().loaded)
AddEvent(tree->root(), Event::LOAD_COMPLETE);
else
AddEvent(tree->root(), Event::LOAD_START);
}
for (const auto& change : changes) {
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->data().role))
AddEvent(change.node, Event::ALERT);
else if (IsActiveLiveRegion(change))
AddEvent(change.node, Event::LIVE_REGION_CREATED);
else if (IsContainedInLiveRegion(change))
FireLiveRegionEvents(change.node);
}
FireActiveDescendantEvents();
}
void AXEventGenerator::FireLiveRegionEvents(AXNode* node) {
AXNode* live_root = node;
while (live_root && !live_root->data().HasStringAttribute(
ax::mojom::StringAttribute::kLiveStatus))
live_root = live_root->parent();
if (live_root &&
!live_root->data().GetBoolAttribute(ax::mojom::BoolAttribute::kBusy) &&
live_root->data().GetStringAttribute(
ax::mojom::StringAttribute::kLiveStatus) != "off") {
// Fire LIVE_REGION_NODE_CHANGED on each node that changed.
if (!node->data()
.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->data().GetIntAttribute(
ax::mojom::IntAttribute::kActivedescendantId));
if (!descendant)
continue;
switch (descendant->data().role) {
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::FireRelationSourceEvents(AXTree* tree,
AXNode* target_node) {
int32_t 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(), [&](int32_t 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);
});
}
// Attempts to suppress load-related events that we presume no AT will be
// interested in under any circumstances, such as pages which have no size.
bool AXEventGenerator::ShouldFireLoadEvents(AXNode* node) {
const AXNodeData& data = node->data();
return data.relative_bounds.bounds.width() ||
data.relative_bounds.bounds.height();
}
// 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 "ACCESS_KEY_CHANGED";
case AXEventGenerator::Event::ATOMIC_CHANGED:
return "ATOMIC_CHANGED";
case AXEventGenerator::Event::ACTIVE_DESCENDANT_CHANGED:
return "ACTIVE_DESCENDANT_CHANGED";
case AXEventGenerator::Event::ALERT:
return "ALERT";
case AXEventGenerator::Event::BUSY_CHANGED:
return "BUSY_CHANGED";
case AXEventGenerator::Event::CHECKED_STATE_CHANGED:
return "CHECKED_STATE_CHANGED";
case AXEventGenerator::Event::CHILDREN_CHANGED:
return "CHILDREN_CHANGED";
case AXEventGenerator::Event::CLASS_NAME_CHANGED:
return "CLASS_NAME_CHANGED";
case AXEventGenerator::Event::COLLAPSED:
return "COLLAPSED";
case AXEventGenerator::Event::CONTROLS_CHANGED:
return "CONTROLS_CHANGED";
case AXEventGenerator::Event::DESCRIBED_BY_CHANGED:
return "DESCRIBED_BY_CHANGED";
case AXEventGenerator::Event::DESCRIPTION_CHANGED:
return "DESCRIPTION_CHANGED";
case AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED:
return "DOCUMENT_SELECTION_CHANGED";
case AXEventGenerator::Event::DOCUMENT_TITLE_CHANGED:
return "DOCUMENT_TITLE_CHANGED";
case AXEventGenerator::Event::DROPEFFECT_CHANGED:
return "DROPEFFECT_CHANGED";
case AXEventGenerator::Event::ENABLED_CHANGED:
return "ENABLED_CHANGED";
case AXEventGenerator::Event::EXPANDED:
return "EXPANDED";
case AXEventGenerator::Event::FLOW_FROM_CHANGED:
return "FLOW_FROM_CHANGED";
case AXEventGenerator::Event::FLOW_TO_CHANGED:
return "FLOW_TO_CHANGED";
case AXEventGenerator::Event::GRABBED_CHANGED:
return "GRABBED_CHANGED";
case AXEventGenerator::Event::HASPOPUP_CHANGED:
return "HASPOPUP_CHANGED";
case AXEventGenerator::Event::HIERARCHICAL_LEVEL_CHANGED:
return "HIERARCHICAL_LEVEL_CHANGED";
case ui::AXEventGenerator::Event::IGNORED_CHANGED:
return "IGNORED_CHANGED";
case AXEventGenerator::Event::IMAGE_ANNOTATION_CHANGED:
return "IMAGE_ANNOTATION_CHANGED";
case AXEventGenerator::Event::INVALID_STATUS_CHANGED:
return "INVALID_STATUS_CHANGED";
case AXEventGenerator::Event::KEY_SHORTCUTS_CHANGED:
return "KEY_SHORTCUTS_CHANGED";
case AXEventGenerator::Event::LABELED_BY_CHANGED:
return "LABELED_BY_CHANGED";
case AXEventGenerator::Event::LANGUAGE_CHANGED:
return "LANGUAGE_CHANGED";
case AXEventGenerator::Event::LAYOUT_INVALIDATED:
return "LAYOUT_INVALIDATED";
case AXEventGenerator::Event::LIVE_REGION_CHANGED:
return "LIVE_REGION_CHANGED";
case AXEventGenerator::Event::LIVE_REGION_CREATED:
return "LIVE_REGION_CREATED";
case AXEventGenerator::Event::LIVE_REGION_NODE_CHANGED:
return "LIVE_REGION_NODE_CHANGED";
case AXEventGenerator::Event::LIVE_RELEVANT_CHANGED:
return "LIVE_RELEVANT_CHANGED";
case AXEventGenerator::Event::LIVE_STATUS_CHANGED:
return "LIVE_STATUS_CHANGED";
case AXEventGenerator::Event::LOAD_COMPLETE:
return "LOAD_COMPLETE";
case AXEventGenerator::Event::LOAD_START:
return "LOAD_START";
case AXEventGenerator::Event::MENU_ITEM_SELECTED:
return "MENU_ITEM_SELECTED";
case AXEventGenerator::Event::MULTILINE_STATE_CHANGED:
return "MULTILINE_STATE_CHANGED";
case AXEventGenerator::Event::MULTISELECTABLE_STATE_CHANGED:
return "MULTISELECTABLE_STATE_CHANGED";
case AXEventGenerator::Event::NAME_CHANGED:
return "NAME_CHANGED";
case AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED:
return "OTHER_ATTRIBUTE_CHANGED";
case AXEventGenerator::Event::PLACEHOLDER_CHANGED:
return "PLACEHOLDER_CHANGED";
case AXEventGenerator::Event::POSITION_IN_SET_CHANGED:
return "POSITION_IN_SET_CHANGED";
case AXEventGenerator::Event::READONLY_CHANGED:
return "READONLY_CHANGED";
case AXEventGenerator::Event::RELATED_NODE_CHANGED:
return "RELATED_NODE_CHANGED";
case AXEventGenerator::Event::REQUIRED_STATE_CHANGED:
return "REQUIRED_STATE_CHANGED";
case AXEventGenerator::Event::ROLE_CHANGED:
return "ROLE_CHANGED";
case AXEventGenerator::Event::ROW_COUNT_CHANGED:
return "ROW_COUNT_CHANGED";
case AXEventGenerator::Event::SCROLL_HORIZONTAL_POSITION_CHANGED:
return "SCROLL_HORIZONTAL_POSITION_CHANGED";
case AXEventGenerator::Event::SCROLL_VERTICAL_POSITION_CHANGED:
return "SCROLL_VERTICAL_POSITION_CHANGED";
case AXEventGenerator::Event::SELECTED_CHANGED:
return "SELECTED_CHANGED";
case AXEventGenerator::Event::SELECTED_CHILDREN_CHANGED:
return "SELECTED_CHILDREN_CHANGED";
case AXEventGenerator::Event::SET_SIZE_CHANGED:
return "SET_SIZE_CHANGED";
case AXEventGenerator::Event::STATE_CHANGED:
return "STATE_CHANGED";
case AXEventGenerator::Event::SUBTREE_CREATED:
return "SUBTREE_CREATED";
case AXEventGenerator::Event::VALUE_CHANGED:
return "VALUE_CHANGED";
case AXEventGenerator::Event::VALUE_MAX_CHANGED:
return "VALUE_MAX_CHANGED";
case AXEventGenerator::Event::VALUE_MIN_CHANGED:
return "VALUE_MIN_CHANGED";
case AXEventGenerator::Event::VALUE_STEP_CHANGED:
return "VALUE_STEP_CHANGED";
case AXEventGenerator::Event::AUTO_COMPLETE_CHANGED:
return "AUTO_COMPLETE_CHANGED";
case AXEventGenerator::Event::FOCUS_CHANGED:
return "FOCUS_CHANGED";
case AXEventGenerator::Event::SORT_CHANGED:
return "SORT_CHANGED";
}
NOTREACHED();
}
} // namespace ui