#include "ui/accessibility/platform/automation/automation_tree_manager_owner.h"
#include <map>
#include <set>
#include "base/containers/flat_tree.h"
#include "base/i18n/string_search.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/accessibility/ax_enum_util.h"
#include "ui/accessibility/ax_enums.mojom-shared.h"
#include "ui/accessibility/ax_event.h"
#include "ui/accessibility/ax_event_generator.h"
#include "ui/accessibility/ax_node_position.h"
#include "ui/accessibility/ax_position.h"
#include "ui/accessibility/ax_range.h"
#include "ui/accessibility/ax_text_utils.h"
#include "ui/accessibility/platform/automation/automation_api_util.h"
#include "ui/accessibility/platform/automation/automation_ax_tree_wrapper.h"
#include "ui/accessibility/platform/automation/automation_v8_bindings.h"
#include "ui/gfx/geometry/rect_conversions.h"
namespace ui {
AutomationTreeManagerOwner::AutomationTreeManagerOwner() : receiver_(this) {}
AutomationTreeManagerOwner::~AutomationTreeManagerOwner() = default;
void AutomationTreeManagerOwner::SendNodesRemovedEvent(
AXTree* tree,
const std::vector<int>& ids) {
AXTreeID tree_id = tree->GetAXTreeID();
GetAutomationV8Bindings()->SendNodesRemovedEvent(tree_id, ids);
}
bool AutomationTreeManagerOwner::SendTreeChangeEvent(
ax::mojom::Mutation change_type,
AXTree* tree,
AXNode* node) {
if (node->HasStringAttribute(ax::mojom::StringAttribute::kChildTreeId)) {
const std::string& child_tree_id_str =
node->GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId);
AXTreeID child_tree_id = AXTreeID::FromString(child_tree_id_str);
auto* tree_wrapper = GetAutomationAXTreeWrapperFromTreeID(child_tree_id);
if (!tree_wrapper || !tree_wrapper->ax_tree()->data().loaded)
GetAutomationV8Bindings()->SendChildTreeIDEvent(child_tree_id);
}
if (!ShouldSendTreeChangeEvent(change_type, tree, node))
return false;
AXTreeID tree_id = tree->GetAXTreeID();
bool did_send_event = false;
for (const auto& observer : tree_change_observers_) {
switch (observer.filter) {
case TreeChangeObserverFilter::kNoTreeChanges:
default:
continue;
case TreeChangeObserverFilter::kLiveRegionTreeChanges:
if (!node->HasStringAttribute(
ax::mojom::StringAttribute::kContainerLiveStatus) &&
node->GetRole() != ax::mojom::Role::kAlert &&
change_type != ax::mojom::Mutation::kSubtreeUpdateEnd) {
continue;
}
break;
case TreeChangeObserverFilter::kTextMarkerChanges:
if (!node->HasIntListAttribute(
ax::mojom::IntListAttribute::kMarkerTypes))
continue;
break;
case TreeChangeObserverFilter::kAllTreeChanges:
break;
}
did_send_event = true;
GetAutomationV8Bindings()->SendTreeChangeEvent(observer.id, tree_id,
node->id(), change_type);
}
return did_send_event;
}
void AutomationTreeManagerOwner::SendAutomationEvent(
AXTreeID tree_id,
const gfx::Point& mouse_location,
const AXEvent& event,
std::optional<AXEventGenerator::Event> generated_event_type) {
AutomationAXTreeWrapper* tree_wrapper =
GetAutomationAXTreeWrapperFromTreeID(tree_id);
if (!tree_wrapper)
return;
bool is_type_none =
(event.event_type == ax::mojom::Event::kNone ||
ShouldIgnoreAXEventForAutomation(event.event_type)) &&
(!generated_event_type.has_value() ||
(generated_event_type.value() == AXEventGenerator::Event::NONE ||
ShouldIgnoreGeneratedEventForAutomation(generated_event_type.value())));
bool fire_ax_event =
(event.event_type == ax::mojom::Event::kHitTestResult ||
event.event_type == ax::mojom::Event::kMediaStartedPlaying ||
event.event_type == ax::mojom::Event::kMediaStoppedPlaying);
bool fire_event = is_type_none || fire_ax_event;
AXNode* node = tree_wrapper->GetNode(event.id);
if (!fire_event && (!node || node->data().IsIgnored()))
return;
std::tuple<ax::mojom::Event, AXEventGenerator::Event> event_type_tuple =
MakeTupleForAutomationFromEventTypes(event.event_type,
generated_event_type.has_value()
? generated_event_type.value()
: AXEventGenerator::Event::NONE);
while (node && tree_wrapper && !fire_event) {
if (tree_wrapper->HasEventListener(event_type_tuple, node)) {
fire_event = true;
break;
}
node = GetParent(node, &tree_wrapper);
}
if (!fire_event)
return;
GetAutomationV8Bindings()->SendAutomationEvent(tree_id, event, mouse_location,
event_type_tuple);
}
AXNode* AutomationTreeManagerOwner::GetHostInParentTree(
AutomationAXTreeWrapper** in_out_tree_wrapper) const {
AutomationAXTreeWrapper* parent_tree_wrapper = nullptr;
AXTreeID parent_tree_id =
(*in_out_tree_wrapper)->ax_tree()->data().parent_tree_id;
if (parent_tree_id != AXTreeIDUnknown()) {
parent_tree_wrapper = GetAutomationAXTreeWrapperFromTreeID(parent_tree_id);
} else {
parent_tree_wrapper = AutomationAXTreeWrapper::GetParentOfTreeId(
(*in_out_tree_wrapper)->GetTreeID());
}
if (!parent_tree_wrapper)
return nullptr;
std::set<int32_t> host_node_ids =
parent_tree_wrapper->ax_tree()->GetNodeIdsForChildTreeId(
(*in_out_tree_wrapper)->GetTreeID());
#if !defined(NDEBUG)
if (host_node_ids.size() > 1)
DLOG(WARNING) << "Multiple nodes claim the same child tree id.";
#endif
for (int32_t host_node_id : host_node_ids) {
AXNode* host_node = parent_tree_wrapper->GetNode(host_node_id);
if (host_node) {
DCHECK_EQ((*in_out_tree_wrapper)->GetTreeID(),
AXTreeID::FromString(host_node->GetStringAttribute(
ax::mojom::StringAttribute::kChildTreeId)));
*in_out_tree_wrapper = parent_tree_wrapper;
return host_node;
}
}
return nullptr;
}
AutomationAXTreeWrapper*
AutomationTreeManagerOwner::GetAutomationAXTreeWrapperFromTreeID(
AXTreeID tree_id) const {
const auto iter = tree_id_to_tree_wrapper_map_.find(tree_id);
if (iter == tree_id_to_tree_wrapper_map_.end())
return nullptr;
return iter->second.get();
}
AXNode* AutomationTreeManagerOwner::GetParent(
AXNode* node,
AutomationAXTreeWrapper** in_out_tree_wrapper,
bool should_use_app_id,
bool requires_unignored) const {
if (should_use_app_id &&
node->HasStringAttribute(ax::mojom::StringAttribute::kAppId)) {
AXNode* parent_app_node =
AutomationAXTreeWrapper::GetParentTreeNodeForAppID(
node->GetStringAttribute(ax::mojom::StringAttribute::kAppId), this);
if (parent_app_node) {
*in_out_tree_wrapper = GetAutomationAXTreeWrapperFromTreeID(
parent_app_node->tree()->GetAXTreeID());
return parent_app_node;
}
}
AXNode* parent = nullptr;
if (!requires_unignored) {
parent = node->GetParent();
if (parent)
return parent;
return GetHostInParentTree(in_out_tree_wrapper);
}
parent = node->GetUnignoredParent();
if (!parent) {
while ((parent = GetHostInParentTree(in_out_tree_wrapper))) {
if (*in_out_tree_wrapper && !(*in_out_tree_wrapper)->IsTreeIgnored())
break;
}
if (parent && parent->IsIgnored())
parent = parent->GetUnignoredParent();
}
return parent;
}
void AutomationTreeManagerOwner::MaybeSendFocusAndBlur(
AutomationAXTreeWrapper* tree,
const AXTreeID& tree_id,
const std::vector<AXTreeUpdate>& updates,
const std::vector<AXEvent>& events,
gfx::Point mouse_location) {
AXNode* old_node = nullptr;
AutomationAXTreeWrapper* old_wrapper =
GetAutomationAXTreeWrapperFromTreeID(focus_tree_id_);
if (old_wrapper) {
old_node = old_wrapper->GetNode(focus_id_);
}
bool lost_old_focus = old_node == nullptr;
ax::mojom::EventFrom event_from = ax::mojom::EventFrom::kNone;
ax::mojom::Action event_from_action = ax::mojom::Action::kNone;
AXNodeData::AXID raw_focus_target_id = AXNodeData::kInvalidAXID;
bool event_bundle_has_focus_or_blur = false;
for (const auto& event : events) {
bool is_blur = event.event_type == ax::mojom::Event::kBlur;
bool is_focus = event.event_type == ax::mojom::Event::kFocus;
if (is_blur || is_focus) {
event_from = event.event_from;
event_from_action = event.event_from_action;
event_bundle_has_focus_or_blur = true;
}
if (is_focus)
raw_focus_target_id = event.id;
}
AutomationAXTreeWrapper* desktop_tree =
GetAutomationAXTreeWrapperFromTreeID(desktop_tree_id_);
AXNode* new_node = nullptr;
AutomationAXTreeWrapper* new_wrapper = nullptr;
if (desktop_tree && !GetFocusInternal(desktop_tree, &new_wrapper, &new_node))
return;
if (!desktop_tree) {
new_wrapper = tree;
new_node = tree->ax_tree()->GetFromId(raw_focus_target_id);
if (!new_node)
return;
}
bool same_focused_tree = old_wrapper == new_wrapper;
if (same_focused_tree && new_node == old_node)
return;
bool is_from_desktop = tree->IsDesktopTree();
if (!event_bundle_has_focus_or_blur && !lost_old_focus && !is_from_desktop &&
same_focused_tree)
return;
if (old_node) {
AXEvent blur_event;
blur_event.id = old_node->id();
blur_event.event_from = event_from;
blur_event.event_from_action = event_from_action;
blur_event.event_type = ax::mojom::Event::kBlur;
SendAutomationEvent(old_wrapper->GetTreeID(), mouse_location, blur_event);
focus_id_ = -1;
focus_tree_id_ = AXTreeIDUnknown();
}
if (new_node) {
AXEvent focus_event;
focus_event.id = new_node->id();
focus_event.event_from = event_from;
focus_event.event_from_action = event_from_action;
focus_event.event_type = ax::mojom::Event::kFocus;
SendAutomationEvent(new_wrapper->GetTreeID(), mouse_location, focus_event);
focus_id_ = new_node->id();
focus_tree_id_ = new_wrapper->GetTreeID();
}
}
std::optional<gfx::Rect>
AutomationTreeManagerOwner::GetAccessibilityFocusedLocation() const {
if (accessibility_focused_tree_id_ == AXTreeIDUnknown())
return std::nullopt;
AutomationAXTreeWrapper* tree_wrapper =
GetAutomationAXTreeWrapperFromTreeID(accessibility_focused_tree_id_);
if (!tree_wrapper)
return std::nullopt;
AXNode* node = tree_wrapper->GetAccessibilityFocusedNode();
if (!node)
return std::nullopt;
return ComputeGlobalNodeBounds(tree_wrapper, node);
}
void AutomationTreeManagerOwner::SendAccessibilityFocusedLocationChange(
const gfx::Point& mouse_location) {
AutomationAXTreeWrapper* tree_wrapper =
GetAutomationAXTreeWrapperFromTreeID(accessibility_focused_tree_id_);
if (!tree_wrapper)
return;
AXEvent event;
event.id = tree_wrapper->accessibility_focused_id();
event.event_type = ax::mojom::Event::kLocationChanged;
SendAutomationEvent(accessibility_focused_tree_id_, mouse_location, event);
}
bool AutomationTreeManagerOwner::GetFocusInternal(
AutomationAXTreeWrapper* tree_wrapper,
AutomationAXTreeWrapper** out_tree_wrapper,
AXNode** out_node) const {
int focus_id = tree_wrapper->ax_tree()->data().focus_id;
AXNode* focus = tree_wrapper->GetNode(focus_id);
if (!focus)
return false;
while (true) {
const std::string& child_tree_id_str =
focus->GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId);
const std::string& child_tree_node_app_id_str = focus->GetStringAttribute(
ax::mojom::StringAttribute::kChildTreeNodeAppId);
if (child_tree_id_str.empty() && child_tree_node_app_id_str.empty())
break;
AutomationAXTreeWrapper* child_tree_wrapper = nullptr;
if (!child_tree_node_app_id_str.empty()) {
std::vector<AXNode*> child_app_nodes =
AutomationAXTreeWrapper::GetChildTreeNodesForAppID(
child_tree_node_app_id_str, this);
if (!child_app_nodes.empty()) {
AXNode* child_app_node = child_app_nodes[0];
auto* wrapper = GetAutomationAXTreeWrapperFromTreeID(
child_app_node->tree()->GetAXTreeID());
child_tree_wrapper = wrapper;
}
}
if (!child_tree_wrapper && !child_tree_id_str.empty()) {
AXTreeID child_tree_id = AXTreeID::FromString(child_tree_id_str);
child_tree_wrapper = GetAutomationAXTreeWrapperFromTreeID(child_tree_id);
}
if (!child_tree_wrapper)
break;
AXTreeID focused_tree_id =
child_tree_wrapper->ax_tree()->data().focused_tree_id;
if (focused_tree_id != AXTreeIDUnknown() &&
!child_tree_wrapper->IsDesktopTree()) {
AutomationAXTreeWrapper* focused_tree_wrapper =
GetAutomationAXTreeWrapperFromTreeID(
child_tree_wrapper->ax_tree()->data().focused_tree_id);
if (focused_tree_wrapper)
child_tree_wrapper = focused_tree_wrapper;
}
int child_focus_id = child_tree_wrapper->ax_tree()->data().focus_id;
AXNode* child_focus = child_tree_wrapper->GetNode(child_focus_id);
if (!child_focus)
break;
focus = child_focus;
tree_wrapper = child_tree_wrapper;
}
*out_tree_wrapper = tree_wrapper;
*out_node = focus;
return true;
}
gfx::Rect AutomationTreeManagerOwner::ComputeGlobalNodeBounds(
AutomationAXTreeWrapper* tree_wrapper,
AXNode* node,
gfx::RectF local_bounds,
bool* offscreen,
bool clip_bounds) const {
gfx::RectF bounds = local_bounds;
bool crossed_app_id = false;
while (node) {
bounds = tree_wrapper->ax_tree()->RelativeToTreeBounds(
node, bounds, offscreen, clip_bounds,
crossed_app_id);
bool should_use_app_id = tree_wrapper->ax_tree()->root() == node;
AutomationAXTreeWrapper* previous_tree_wrapper = tree_wrapper;
AXNode* parent_of_root = GetParent(tree_wrapper->ax_tree()->root(),
&tree_wrapper, should_use_app_id);
if (parent_of_root == node)
break;
crossed_app_id = false;
if (!parent_of_root && previous_tree_wrapper->GetParentTreeFromAnyAppID()) {
AXNode* found_node = node;
while (found_node && !found_node->HasStringAttribute(
ax::mojom::StringAttribute::kAppId)) {
found_node = found_node->parent();
}
if (found_node) {
const std::string& app_id =
found_node->GetStringAttribute(ax::mojom::StringAttribute::kAppId);
parent_of_root =
AutomationAXTreeWrapper::GetParentTreeNodeForAppID(app_id, this);
tree_wrapper =
AutomationAXTreeWrapper::GetParentTreeWrapperForAppID(app_id, this);
crossed_app_id = true;
}
}
if (previous_tree_wrapper->HasDeviceScaleFactor() &&
!tree_wrapper->HasDeviceScaleFactor()) {
const float scale_factor = parent_of_root->data().GetFloatAttribute(
ax::mojom::FloatAttribute::kChildTreeScale);
if (scale_factor > 0)
bounds.Scale(1.0 / scale_factor);
}
node = parent_of_root;
}
return gfx::ToEnclosingRect(bounds);
}
std::vector<AXNode*> AutomationTreeManagerOwner::GetRootsOfChildTree(
AXNode* node) const {
if (!node->HasStringAttribute(ax::mojom::StringAttribute::kChildTreeId) &&
!node->HasStringAttribute(
ax::mojom::StringAttribute::kChildTreeNodeAppId)) {
return std::vector<AXNode*>();
}
const std::string& child_tree_id_str =
node->GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId);
const std::string& child_tree_node_app_id_str =
node->GetStringAttribute(ax::mojom::StringAttribute::kChildTreeNodeAppId);
if (!child_tree_node_app_id_str.empty()) {
std::vector<AXNode*> child_app_nodes =
AutomationAXTreeWrapper::GetChildTreeNodesForAppID(
child_tree_node_app_id_str, this);
if (!child_app_nodes.empty())
return child_app_nodes;
}
AutomationAXTreeWrapper* child_tree_wrapper =
GetAutomationAXTreeWrapperFromTreeID(
AXTreeID::FromString(child_tree_id_str));
if (!child_tree_wrapper || !child_tree_wrapper->ax_tree()->root())
return std::vector<AXNode*>();
return {child_tree_wrapper->ax_tree()->root()};
}
AXNode* AutomationTreeManagerOwner::GetNextInTreeOrder(
AXNode* start,
AutomationAXTreeWrapper** in_out_tree_wrapper) const {
auto iter = start->UnignoredChildrenBegin();
if (iter != start->UnignoredChildrenEnd())
return &(*iter);
AXNode* walker = start;
std::vector<AXNode*> child_roots = GetRootsOfChildTree(walker);
if (!child_roots.empty())
return child_roots[0];
AXNode* parent;
while ((parent = GetParent(walker, in_out_tree_wrapper))) {
if ((walker->GetUnignoredIndexInParent() + 1) <
parent->GetUnignoredChildCount()) {
return parent->GetUnignoredChildAtIndex(
walker->GetUnignoredIndexInParent() + 1);
}
walker = parent;
}
return nullptr;
}
AXNode* AutomationTreeManagerOwner::GetPreviousInTreeOrder(
AXNode* start,
AutomationAXTreeWrapper** in_out_tree_wrapper) const {
AXNode* walker = start;
AXNode* parent = GetParent(start, in_out_tree_wrapper);
if (!parent)
return nullptr;
if (walker->GetUnignoredIndexInParent() == 0)
return parent;
walker =
parent->GetUnignoredChildAtIndex(walker->GetUnignoredIndexInParent() - 1);
while (true) {
auto iter = walker->UnignoredChildrenBegin();
if (iter != walker->UnignoredChildrenEnd() &&
--iter != walker->UnignoredChildrenEnd()) {
walker = &(*iter);
} else if (GetRootsOfChildTree(walker).empty()) {
break;
}
}
return walker;
}
std::vector<int> AutomationTreeManagerOwner::CalculateSentenceBoundary(
AutomationAXTreeWrapper* tree_wrapper,
AXNode* node,
bool start_boundary) {
std::vector<int> sentence_boundary;
std::u16string node_name =
node->GetString16Attribute(ax::mojom::StringAttribute::kName);
if (node_name.empty()) {
return sentence_boundary;
}
std::u16string pre_str;
std::u16string post_str;
AXNodePosition::AXPositionInstance head_pos =
AXNodePosition::CreatePosition(*node, 0 ,
ax::mojom::TextAffinity::kDownstream)
->CreatePositionAtStartOfAnchor();
if (!head_pos->AtStartOfParagraph()) {
AXNodePosition::AXPositionInstance start_para_pos =
head_pos->CreatePreviousParagraphStartPosition(
{AXBoundaryBehavior::kStopAtLastAnchorBoundary,
AXBoundaryDetection::kDontCheckInitialPosition});
AXRange<AXPosition<AXNodePosition, AXNode>> pre_range(
start_para_pos->Clone(), head_pos->Clone());
pre_str = pre_range.GetText();
}
AXNodePosition::AXPositionInstance end_para_pos =
head_pos->CreateNextParagraphEndPosition(
{AXBoundaryBehavior::kStopAtLastAnchorBoundary,
AXBoundaryDetection::kDontCheckInitialPosition});
AXRange<AXPosition<AXNodePosition, AXNode>> post_range(head_pos->Clone(),
end_para_pos->Clone());
post_str = post_range.GetText();
std::u16string combined_str = pre_str + post_str;
auto boundary_func =
start_boundary ? &GetSentenceStartOffsets : &GetSentenceEndOffsets;
std::vector<int> combined_sentence_boundary = boundary_func(combined_str);
int pre_str_length = pre_str.length();
int max_index = start_boundary ? node_name.length() - 1 : node_name.length();
for (int& index : combined_sentence_boundary) {
index -= pre_str_length;
if (index >= 0 && index <= max_index) {
sentence_boundary.push_back(index);
}
}
return sentence_boundary;
}
bool AutomationTreeManagerOwner::GetFocus(AXTreeID* focused_tree_id,
int* node_id) const {
AutomationAXTreeWrapper* desktop_tree =
GetAutomationAXTreeWrapperFromTreeID(desktop_tree_id_);
AutomationAXTreeWrapper* focused_wrapper = nullptr;
AXNode* focused_node = nullptr;
if (desktop_tree &&
!GetFocusInternal(desktop_tree, &focused_wrapper, &focused_node))
return false;
if (!desktop_tree) {
focused_wrapper = GetAutomationAXTreeWrapperFromTreeID(focus_tree_id_);
if (!focused_wrapper)
return false;
focused_node = focused_wrapper->GetNode(focus_id_);
if (!focused_node)
return false;
}
*focused_tree_id = focused_wrapper->GetTreeID();
*node_id = focused_node->id();
return true;
}
size_t AutomationTreeManagerOwner::GetChildCount(AXNode* node) const {
std::vector<AXNode*> child_roots = GetRootsOfChildTree(node);
if (child_roots.empty())
return node->GetUnignoredChildCount();
else
return child_roots.size();
}
std::vector<int> AutomationTreeManagerOwner::GetChildIDs(
AXNode* node,
AXTreeID* result_tree_id) const {
std::vector<int> child_ids;
std::vector child_roots = GetRootsOfChildTree(node);
if (!child_roots.empty()) {
*result_tree_id = child_roots[0]->tree()->GetAXTreeID();
for (AXNode* child_root : child_roots)
child_ids.push_back(child_root->id());
} else {
for (auto iter = node->UnignoredChildrenBegin(),
end = node->UnignoredChildrenEnd();
iter != end; ++iter) {
child_ids.push_back(iter->id());
*result_tree_id = iter->tree()->GetAXTreeID();
}
}
return child_ids;
}
bool AutomationTreeManagerOwner::GetBoundsForRange(
AutomationAXTreeWrapper* tree_wrapper,
AXNode* node,
int start,
int end,
bool clipped,
gfx::Rect* result) const {
if (node->GetRole() != ax::mojom::Role::kInlineTextBox)
return false;
gfx::RectF local_bounds(0, 0, node->data().relative_bounds.bounds.width(),
node->data().relative_bounds.bounds.height());
const std::string& name =
node->GetStringAttribute(ax::mojom::StringAttribute::kName);
std::vector<int> character_offsets =
node->GetIntListAttribute(ax::mojom::IntListAttribute::kCharacterOffsets);
int len = static_cast<int>(std::min(name.size(), character_offsets.size()));
if (start >= 0 && start <= end && end <= len) {
int start_offset = start > 0 ? character_offsets[start - 1] : 0;
int end_offset = end > 0 ? character_offsets[end - 1] : 0;
switch (node->data().GetTextDirection()) {
case ax::mojom::WritingDirection::kLtr:
default:
local_bounds.set_x(local_bounds.x() + start_offset);
local_bounds.set_width(end_offset - start_offset);
break;
case ax::mojom::WritingDirection::kRtl:
local_bounds.set_x(local_bounds.x() + local_bounds.width() -
end_offset);
local_bounds.set_width(end_offset - start_offset);
break;
case ax::mojom::WritingDirection::kTtb:
local_bounds.set_y(local_bounds.y() + start_offset);
local_bounds.set_height(end_offset - start_offset);
break;
case ax::mojom::WritingDirection::kBtt:
local_bounds.set_y(local_bounds.y() + local_bounds.height() -
end_offset);
local_bounds.set_height(end_offset - start_offset);
break;
}
}
*result = ComputeGlobalNodeBounds(tree_wrapper, node, local_bounds, nullptr,
clipped );
return true;
}
const char* AutomationTreeManagerOwner::GetName(AXNode* node) const {
if (node->HasStringAttribute(ax::mojom::StringAttribute::kName)) {
return node->GetStringAttribute(ax::mojom::StringAttribute::kName).c_str();
}
return nullptr;
}
bool AutomationTreeManagerOwner::GetNextTextMatch(
AutomationAXTreeWrapper* tree_wrapper,
AXNode* node,
const std::string& search_str,
bool backward,
AXTreeID* result_tree_id,
int* result_node_id) const {
std::u16string search_str_16 = base::UTF8ToUTF16(search_str);
auto next = backward ? &AutomationTreeManagerOwner::GetPreviousInTreeOrder
: &AutomationTreeManagerOwner::GetNextInTreeOrder;
AutomationAXTreeWrapper** target_tree_wrapper = &tree_wrapper;
while (true) {
node = (this->*next)(node, target_tree_wrapper);
if ((*target_tree_wrapper)->IsDesktopTree())
return false;
if (!node)
return false;
if (!node->HasStringAttribute(ax::mojom::StringAttribute::kName)) {
continue;
}
std::u16string name =
node->GetString16Attribute(ax::mojom::StringAttribute::kName);
if (base::i18n::StringSearchIgnoringCaseAndAccents(search_str_16, name,
nullptr, nullptr)) {
*result_tree_id = (*target_tree_wrapper)->GetTreeID();
*result_node_id = node->id();
return true;
}
}
}
bool AutomationTreeManagerOwner::GetChildIDAtIndex(const AXTreeID& tree_id,
int node_id,
int index,
AXTreeID* child_tree_id,
int* child_node_id) const {
if (index < 0)
return false;
AutomationAXTreeWrapper* tree_wrapper =
GetAutomationAXTreeWrapperFromTreeID(tree_id);
if (!tree_wrapper)
return false;
AXNode* node = tree_wrapper->GetNode(node_id);
if (!node)
return false;
std::vector<AXNode*> child_roots = GetRootsOfChildTree(node);
AXNode* child_node = nullptr;
if (!child_roots.empty() && static_cast<size_t>(index) < child_roots.size()) {
child_node = child_roots[index];
} else if (node->IsIgnored()) {
return false;
} else if (static_cast<size_t>(index) >= node->GetUnignoredChildCount()) {
return false;
} else {
child_node = node->GetUnignoredChildAtIndex(static_cast<size_t>(index));
}
DCHECK(child_node);
*child_tree_id = child_node->tree()->GetAXTreeID();
*child_node_id = child_node->id();
return true;
}
bool AutomationTreeManagerOwner::GetAccessibilityFocus(AXTreeID* tree_id,
int* node_id) const {
AutomationAXTreeWrapper* tree_wrapper =
GetAutomationAXTreeWrapperFromTreeID(accessibility_focused_tree_id_);
if (!tree_wrapper)
return false;
AXNode* node = tree_wrapper->GetAccessibilityFocusedNode();
if (!node)
return false;
*tree_id = accessibility_focused_tree_id_;
*node_id = node->id();
return true;
}
void AutomationTreeManagerOwner::AddTreeChangeObserver(
int observer_id,
TreeChangeObserverFilter filter) {
TreeChangeObserver observer;
observer.id = observer_id;
observer.filter = filter;
tree_change_observers_.push_back(observer);
UpdateOverallTreeChangeObserverFilter();
}
void AutomationTreeManagerOwner::RemoveTreeChangeObserver(int observer_id) {
for (auto iter = tree_change_observers_.begin();
iter != tree_change_observers_.end(); ++iter) {
if (iter->id == observer_id) {
tree_change_observers_.erase(iter);
break;
}
}
UpdateOverallTreeChangeObserverFilter();
}
bool AutomationTreeManagerOwner::ShouldSendTreeChangeEvent(
ax::mojom::Mutation change_type,
AXTree* tree,
AXNode* node) const {
if (node->IsIgnored())
return false;
auto* tree_wrapper =
GetAutomationAXTreeWrapperFromTreeID(tree->GetAXTreeID());
if (!tree_wrapper || tree_wrapper->IsTreeIgnored())
return false;
bool has_filter = false;
if (tree_change_observer_overall_filter_ &
(1 << TreeChangeObserverFilter::kLiveRegionTreeChanges)) {
if (node->HasStringAttribute(
ax::mojom::StringAttribute::kContainerLiveStatus) ||
node->GetRole() == ax::mojom::Role::kAlert ||
change_type == ax::mojom::Mutation::kSubtreeUpdateEnd) {
has_filter = true;
}
}
if (tree_change_observer_overall_filter_ &
(1 << TreeChangeObserverFilter::kTextMarkerChanges)) {
if (node->HasIntListAttribute(ax::mojom::IntListAttribute::kMarkerTypes))
has_filter = true;
}
if (tree_change_observer_overall_filter_ &
(1 << TreeChangeObserverFilter::kAllTreeChanges))
has_filter = true;
return has_filter;
}
void AutomationTreeManagerOwner::DestroyAccessibilityTree(
const AXTreeID& tree_id) {
auto& child_tree_id_reverse_map =
AutomationAXTreeWrapper::GetChildTreeIDReverseMap();
std::erase_if(
child_tree_id_reverse_map,
[tree_id](const std::pair<AXTreeID, AutomationAXTreeWrapper*>& pair) {
return pair.first == tree_id || pair.second->GetTreeID() == tree_id;
});
if (tree_id == accessibility_focused_tree_id_)
accessibility_focused_tree_id_ = AXTreeIDUnknown();
RemoveAutomationTreeWrapperFromCache(tree_id);
trees_with_event_listeners_.erase(tree_id);
}
bool AutomationTreeManagerOwner::CalculateNodeState(const AXTreeID& tree_id,
int node_id,
uint32_t* node_state,
bool* offscreen,
bool* focused) const {
AutomationAXTreeWrapper* tree_wrapper =
GetAutomationAXTreeWrapperFromTreeID(tree_id);
if (!tree_wrapper)
return false;
AXNode* node = tree_wrapper->GetNode(node_id);
if (!node)
return false;
*node_state = node->data().state.value();
AutomationAXTreeWrapper* top_tree_wrapper = nullptr;
AutomationAXTreeWrapper* walker = tree_wrapper;
while (walker && walker != top_tree_wrapper) {
top_tree_wrapper = walker;
GetParent(walker->ax_tree()->root(), &walker);
}
*focused = tree_wrapper->IsInFocusChain(node->id());
ComputeGlobalNodeBounds(tree_wrapper, node, gfx::RectF(), offscreen);
return true;
}
void AutomationTreeManagerOwner::SetAccessibilityFocus(AXTreeID tree_id) {
if (tree_id != accessibility_focused_tree_id_ &&
accessibility_focused_tree_id_ != AXTreeIDUnknown()) {
AutomationAXTreeWrapper* previous_tree_wrapper =
GetAutomationAXTreeWrapperFromTreeID(accessibility_focused_tree_id_);
if (previous_tree_wrapper) {
previous_tree_wrapper->SetAccessibilityFocus(kInvalidAXNodeID);
}
}
accessibility_focused_tree_id_ = tree_id;
}
void AutomationTreeManagerOwner::CacheAutomationTreeWrapperForTreeID(
const AXTreeID& tree_id,
AutomationAXTreeWrapper* tree_wrapper) {
tree_id_to_tree_wrapper_map_.insert(
std::make_pair(tree_id, base::WrapUnique(tree_wrapper)));
}
void AutomationTreeManagerOwner::RemoveAutomationTreeWrapperFromCache(
const AXTreeID& tree_id) {
tree_id_to_tree_wrapper_map_.erase(tree_id);
}
void AutomationTreeManagerOwner::ClearCachedAutomationTreeWrappers() {
tree_id_to_tree_wrapper_map_.clear();
}
void AutomationTreeManagerOwner::ClearCachedAccessibilityTrees() {
tree_change_observers_.clear();
ClearCachedAutomationTreeWrappers();
AutomationAXTreeWrapper::GetChildTreeIDReverseMap().clear();
}
void AutomationTreeManagerOwner::Invalidate() {
auto& child_tree_id_reverse_map =
AutomationAXTreeWrapper::GetChildTreeIDReverseMap();
std::erase_if(
child_tree_id_reverse_map,
[this](const std::pair<AXTreeID, AutomationAXTreeWrapper*>& pair) {
return pair.second->owner() == this;
});
ClearCachedAutomationTreeWrappers();
}
void AutomationTreeManagerOwner::TreeEventListenersChanged(
AutomationAXTreeWrapper* tree_wrapper) {
if (tree_wrapper->EventListenerCount() != 0) {
trees_with_event_listeners_.insert(tree_wrapper->GetTreeID());
return;
}
if (trees_with_event_listeners_.empty())
return;
trees_with_event_listeners_.erase(tree_wrapper->GetTreeID());
if (!trees_with_event_listeners_.empty())
return;
NotifyTreeEventListenersChanged();
}
bool AutomationTreeManagerOwner::HasTreesWithEventListeners() const {
return !trees_with_event_listeners_.empty();
}
void AutomationTreeManagerOwner::
MaybeSendOnAllAutomationEventListenersRemoved() {
if (HasTreesWithEventListeners())
return;
GetAutomationV8Bindings()->SendOnAllEventListenersRemoved();
}
void AutomationTreeManagerOwner::UpdateOverallTreeChangeObserverFilter() {
tree_change_observer_overall_filter_ = 0;
for (const auto& observer : tree_change_observers_)
tree_change_observer_overall_filter_ |= 1 << observer.filter;
}
void AutomationTreeManagerOwner::DispatchTreeDestroyedEvent(
const AXTreeID& tree_id) {
GetAutomationV8Bindings()->SendTreeDestroyedEvent(tree_id);
}
void AutomationTreeManagerOwner::DispatchAccessibilityEvents(
const AXTreeID& tree_id,
const std::vector<AXTreeUpdate>& updates,
const gfx::Point& mouse_location,
const std::vector<AXEvent>& events) {
AutomationAXTreeWrapper* tree_wrapper =
GetAutomationAXTreeWrapperFromTreeID(tree_id);
bool is_new_tree = tree_wrapper == nullptr;
if (is_new_tree) {
tree_wrapper = new AutomationAXTreeWrapper(this);
CacheAutomationTreeWrapperForTreeID(tree_id, tree_wrapper);
if (updates.size() == 1 && updates[0].nodes.empty()) {
return;
}
}
if (!tree_wrapper->OnAccessibilityEvents(tree_id, updates, events,
mouse_location)) {
DLOG(ERROR) << tree_wrapper->ax_tree()->error();
GetAutomationV8Bindings()->SendTreeSerializationError(tree_id);
return;
}
if (is_new_tree) {
AXEvent initial_event;
initial_event.id = -1;
initial_event.event_from = ax::mojom::EventFrom::kNone;
initial_event.event_type = ax::mojom::Event::kNone;
SendAutomationEvent(tree_id, gfx::Point(), initial_event);
}
TreeEventListenersChanged(tree_wrapper);
}
void AutomationTreeManagerOwner::DispatchAccessibilityLocationChange(
const AXTreeID& tree_id,
int32_t node_id,
const AXRelativeBounds& bounds) {
AutomationAXTreeWrapper* tree_wrapper =
GetAutomationAXTreeWrapperFromTreeID(tree_id);
if (!tree_wrapper) {
return;
}
AXNode* node = tree_wrapper->GetNode(node_id);
if (!node) {
return;
}
std::optional<gfx::Rect> previous_accessibility_focused_global_bounds =
GetAccessibilityFocusedLocation();
node->SetLocation(bounds.offset_container_id, bounds.bounds,
bounds.transform.get());
if (previous_accessibility_focused_global_bounds.has_value() &&
previous_accessibility_focused_global_bounds !=
GetAccessibilityFocusedLocation()) {
SendAccessibilityFocusedLocationChange(gfx::Point());
}
}
void AutomationTreeManagerOwner::DispatchAccessibilityScrollChange(
const AXTreeID& tree_id,
int32_t node_id,
int32_t scroll_x,
int32_t scroll_y) {
AutomationAXTreeWrapper* tree_wrapper =
GetAutomationAXTreeWrapperFromTreeID(tree_id);
if (!tree_wrapper) {
return;
}
AXNode* node = tree_wrapper->GetNode(node_id);
if (!node) {
return;
}
int old_scroll_x = 0;
int old_scroll_y = 0;
node->GetScrollInfo(&old_scroll_x, &old_scroll_y);
node->SetScrollInfo(scroll_x, scroll_y);
if (scroll_x == old_scroll_x && scroll_y == old_scroll_y) {
return;
}
AXEvent event;
event.id = tree_wrapper->accessibility_focused_id();
event.id = node_id;
event.event_type = ax::mojom::Event::kScrollPositionChanged;
SendAutomationEvent(tree_wrapper->GetTreeID(), gfx::Point(), event);
}
void AutomationTreeManagerOwner::DispatchActionResult(const AXActionData& data,
bool result) {
GetAutomationV8Bindings()->SendActionResultEvent(data, result);
}
#if BUILDFLAG(IS_CHROMEOS)
void AutomationTreeManagerOwner::DispatchGetTextLocationResult(
const AXActionData& data,
const std::optional<gfx::Rect>& rect) {
GetAutomationV8Bindings()->SendGetTextLocationResult(data, rect);
}
#endif
}