blob: a3fcd73cac0dc31c2e34e47b7b781eacfaae5fd2 [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 "ui/accessibility/ax_node.h"
#include "ui/accessibility/ax_role_properties.h"
namespace ui {
AXEventGenerator::TargetedEvent::TargetedEvent(ui::AXNode* node, Event event)
: node(node), event(event) {}
AXEventGenerator::Iterator::Iterator(
const std::map<AXNode*, std::set<Event>>& map,
const std::map<AXNode*, std::set<Event>>::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(AXTree* tree) : tree_(tree) {
DCHECK(tree_);
tree_->SetDelegate(this);
}
AXEventGenerator::~AXEventGenerator() {
DCHECK(tree_);
tree_->SetDelegate(nullptr);
}
void AXEventGenerator::Clear() {
tree_events_.clear();
}
void AXEventGenerator::AddEvent(ui::AXNode* node,
AXEventGenerator::Event event) {
tree_events_[node].insert(event);
}
void AXEventGenerator::OnNodeDataWillChange(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 != ui::AX_ROLE_STATIC_TEXT) {
AXNode* node = tree_->GetFromId(new_node_data.id);
tree_events_[node].insert(Event::CHILDREN_CHANGED);
}
}
void AXEventGenerator::OnRoleChanged(AXTree* tree,
AXNode* node,
AXRole old_role,
AXRole new_role) {
DCHECK_EQ(tree_, tree);
AddEvent(node, Event::ROLE_CHANGED);
}
void AXEventGenerator::OnStateChanged(AXTree* tree,
AXNode* node,
AXState state,
bool new_value) {
DCHECK_EQ(tree_, tree);
AddEvent(node, Event::STATE_CHANGED);
if (state == ui::AX_STATE_EXPANDED) {
AddEvent(node, new_value ? Event::EXPANDED : Event::COLLAPSED);
if (node->data().role == ui::AX_ROLE_ROW ||
node->data().role == ui::AX_ROLE_TREE_ITEM) {
ui::AXNode* container = node;
while (container && !ui::IsRowContainer(container->data().role))
container = container->parent();
if (container)
AddEvent(container, Event::ROW_COUNT_CHANGED);
}
}
if (state == ui::AX_STATE_SELECTED) {
AddEvent(node, Event::SELECTED_CHANGED);
ui::AXNode* container = node;
while (container &&
!ui::IsContainerWithSelectableChildrenRole(container->data().role))
container = container->parent();
if (container)
AddEvent(container, Event::SELECTED_CHILDREN_CHANGED);
}
}
void AXEventGenerator::OnStringAttributeChanged(AXTree* tree,
AXNode* node,
AXStringAttribute attr,
const std::string& old_value,
const std::string& new_value) {
DCHECK_EQ(tree_, tree);
switch (attr) {
case ui::AX_ATTR_NAME:
AddEvent(node, Event::NAME_CHANGED);
break;
case ui::AX_ATTR_DESCRIPTION:
AddEvent(node, Event::DESCRIPTION_CHANGED);
break;
case ui::AX_ATTR_VALUE:
AddEvent(node, Event::VALUE_CHANGED);
break;
case ui::AX_ATTR_ARIA_INVALID_VALUE:
AddEvent(node, Event::INVALID_STATUS_CHANGED);
break;
case ui::AX_ATTR_LIVE_STATUS:
if (node->data().role != ui::AX_ROLE_ALERT)
AddEvent(node, Event::LIVE_REGION_CREATED);
break;
default:
AddEvent(node, Event::OTHER_ATTRIBUTE_CHANGED);
break;
}
}
void AXEventGenerator::OnIntAttributeChanged(AXTree* tree,
AXNode* node,
AXIntAttribute attr,
int32_t old_value,
int32_t new_value) {
DCHECK_EQ(tree_, tree);
switch (attr) {
case ui::AX_ATTR_ACTIVEDESCENDANT_ID:
AddEvent(node, Event::ACTIVE_DESCENDANT_CHANGED);
break;
case ui::AX_ATTR_CHECKED_STATE:
AddEvent(node, Event::CHECKED_STATE_CHANGED);
break;
case ui::AX_ATTR_INVALID_STATE:
AddEvent(node, Event::INVALID_STATUS_CHANGED);
break;
case ui::AX_ATTR_RESTRICTION:
AddEvent(node, Event::STATE_CHANGED);
break;
case ui::AX_ATTR_SCROLL_X:
case ui::AX_ATTR_SCROLL_Y:
AddEvent(node, Event::SCROLL_POSITION_CHANGED);
break;
default:
AddEvent(node, Event::OTHER_ATTRIBUTE_CHANGED);
break;
}
}
void AXEventGenerator::OnFloatAttributeChanged(AXTree* tree,
AXNode* node,
AXFloatAttribute attr,
float old_value,
float new_value) {
DCHECK_EQ(tree_, tree);
if (attr == ui::AX_ATTR_VALUE_FOR_RANGE)
AddEvent(node, Event::VALUE_CHANGED);
else
AddEvent(node, Event::OTHER_ATTRIBUTE_CHANGED);
}
void AXEventGenerator::OnBoolAttributeChanged(AXTree* tree,
AXNode* node,
AXBoolAttribute attr,
bool new_value) {
DCHECK_EQ(tree_, tree);
AddEvent(node, Event::OTHER_ATTRIBUTE_CHANGED);
}
void AXEventGenerator::OnIntListAttributeChanged(
AXTree* tree,
AXNode* node,
AXIntListAttribute attr,
const std::vector<int32_t>& old_value,
const std::vector<int32_t>& new_value) {
DCHECK_EQ(tree_, tree);
AddEvent(node, Event::OTHER_ATTRIBUTE_CHANGED);
}
void AXEventGenerator::OnStringListAttributeChanged(
AXTree* tree,
AXNode* node,
AXStringListAttribute attr,
const std::vector<std::string>& old_value,
const std::vector<std::string>& new_value) {
DCHECK_EQ(tree_, tree);
}
void AXEventGenerator::OnTreeDataChanged(AXTree* tree,
const ui::AXTreeData& old_tree_data,
const ui::AXTreeData& new_tree_data) {
DCHECK_EQ(tree_, tree);
if (new_tree_data.loaded && !old_tree_data.loaded)
AddEvent(tree->root(), Event::LOAD_COMPLETE);
if (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);
}
void AXEventGenerator::OnSubtreeWillBeReparented(AXTree* tree, AXNode* node) {
DCHECK_EQ(tree_, tree);
}
void AXEventGenerator::OnNodeCreated(AXTree* tree, AXNode* node) {
DCHECK_EQ(tree_, tree);
}
void AXEventGenerator::OnNodeReparented(AXTree* tree, AXNode* node) {
DCHECK_EQ(tree_, tree);
}
void AXEventGenerator::OnNodeChanged(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 && tree->data().loaded)
AddEvent(tree->root(), Event::LOAD_COMPLETE);
for (const auto& change : changes) {
if ((change.type == NODE_CREATED || change.type == SUBTREE_CREATED) &&
change.node->data().HasStringAttribute(ui::AX_ATTR_LIVE_STATUS)) {
if (change.node->data().role == ui::AX_ROLE_ALERT)
AddEvent(change.node, Event::ALERT);
else
AddEvent(change.node, Event::LIVE_REGION_CREATED);
continue;
}
if (change.node->data().HasStringAttribute(
ui::AX_ATTR_CONTAINER_LIVE_STATUS)) {
if (!change.node->data().GetStringAttribute(ui::AX_ATTR_NAME).empty())
AddEvent(change.node, Event::LIVE_REGION_NODE_CHANGED);
ui::AXNode* live_root = change.node;
while (live_root &&
!live_root->data().HasStringAttribute(ui::AX_ATTR_LIVE_STATUS))
live_root = live_root->parent();
if (live_root)
AddEvent(live_root, Event::LIVE_REGION_CHANGED);
}
}
}
} // namespace ui