blob: bdfd1601bbe033cdb4ff9a1827ebda1f70feb18a [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/accessibility/accessibility_tree_formatter_blink.h"
#include <cmath>
#include <cstddef>
#include <optional>
#include <utility>
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/to_string.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_selection.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
#include "ui/accessibility/platform/browser_accessibility.h"
#include "ui/accessibility/platform/browser_accessibility_manager.h"
#include "ui/accessibility/platform/compute_attributes.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/transform.h"
namespace content {
namespace {
std::optional<std::string> GetStringAttribute(const ui::AXNode& node,
ax::mojom::StringAttribute attr) {
// Language is different from other string attributes as it inherits and has
// a method to compute it.
if (attr == ax::mojom::StringAttribute::kLanguage) {
std::string value = node.GetLanguage();
if (value.empty()) {
return std::nullopt;
}
return value;
}
// Font Family is different from other string attributes as it inherits.
if (attr == ax::mojom::StringAttribute::kFontFamily) {
std::string value = node.GetInheritedStringAttribute(attr);
if (value.empty()) {
return std::nullopt;
}
return value;
}
// Always return the attribute if the node has it, even if the value is an
// empty string.
if (node.HasStringAttribute(attr)) {
return node.GetStringAttribute(attr);
}
return std::nullopt;
}
std::string FormatColor(int argb) {
// Don't output the alpha component; only the red, green and blue
// actually matter.
int rgb = (static_cast<uint32_t>(argb) & 0xffffff);
return base::StringPrintf("%06x", rgb);
}
std::string IntAttrToString(const ui::AXNode& node,
ax::mojom::IntAttribute attr,
int32_t value) {
if (ui::IsNodeIdIntAttribute(attr)) {
// Relation
const ui::AXNode* target = node.tree()->GetFromId(value);
if (!target) {
return "null";
}
std::string result = ui::ToString(target->GetRole());
// Provide some extra info about the related object via the name or
// possibly the class (if an element).
// TODO(accessibility) Include all relational attributes here.
// TODO(accessibility) Consider using line numbers from the results instead.
if (attr == ax::mojom::IntAttribute::kNextOnLineId ||
attr == ax::mojom::IntAttribute::kPreviousOnLineId) {
if (target->HasStringAttribute(ax::mojom::StringAttribute::kName)) {
result += ":\"";
result += target->GetStringAttribute(ax::mojom::StringAttribute::kName);
result += "\"";
} else if (target->HasStringAttribute(
ax::mojom::StringAttribute::kClassName)) {
result += ".";
result +=
target->GetStringAttribute(ax::mojom::StringAttribute::kClassName);
}
}
return result;
}
switch (attr) {
case ax::mojom::IntAttribute::kAriaCurrentState:
return ui::ToString(static_cast<ax::mojom::AriaCurrentState>(value));
case ax::mojom::IntAttribute::kAriaNotificationInterruptDeprecated:
return ui::ToString(
static_cast<ax::mojom::AriaNotificationInterrupt>(value));
case ax::mojom::IntAttribute::kAriaNotificationPriorityDeprecated:
return ui::ToString(
static_cast<ax::mojom::AriaNotificationPriority>(value));
case ax::mojom::IntAttribute::kCheckedState:
return ui::ToString(static_cast<ax::mojom::CheckedState>(value));
case ax::mojom::IntAttribute::kDefaultActionVerb:
return ui::ToString(static_cast<ax::mojom::DefaultActionVerb>(value));
case ax::mojom::IntAttribute::kDescriptionFrom:
return ui::ToString(static_cast<ax::mojom::DescriptionFrom>(value));
case ax::mojom::IntAttribute::kDetailsFrom:
return ui::ToString(static_cast<ax::mojom::DetailsFrom>(value));
case ax::mojom::IntAttribute::kHasPopup:
return ui::ToString(static_cast<ax::mojom::HasPopup>(value));
case ax::mojom::IntAttribute::kInvalidState:
return ui::ToString(static_cast<ax::mojom::InvalidState>(value));
case ax::mojom::IntAttribute::kIsPopup:
return ui::ToString(static_cast<ax::mojom::IsPopup>(value));
case ax::mojom::IntAttribute::kListStyle:
return ui::ToString(static_cast<ax::mojom::ListStyle>(value));
case ax::mojom::IntAttribute::kNameFrom:
return ui::ToString(static_cast<ax::mojom::NameFrom>(value));
case ax::mojom::IntAttribute::kRestriction:
return ui::ToString(static_cast<ax::mojom::Restriction>(value));
case ax::mojom::IntAttribute::kSortDirection:
return ui::ToString(static_cast<ax::mojom::SortDirection>(value));
case ax::mojom::IntAttribute::kTextAlign:
return ui::ToString(static_cast<ax::mojom::TextAlign>(value));
case ax::mojom::IntAttribute::kTextOverlineStyle:
case ax::mojom::IntAttribute::kTextStrikethroughStyle:
case ax::mojom::IntAttribute::kTextUnderlineStyle:
return ui::ToString(static_cast<ax::mojom::TextDecorationStyle>(value));
case ax::mojom::IntAttribute::kTextDirection:
return ui::ToString(static_cast<ax::mojom::WritingDirection>(value));
case ax::mojom::IntAttribute::kTextPosition:
return ui::ToString(static_cast<ax::mojom::TextPosition>(value));
case ax::mojom::IntAttribute::kImageAnnotationStatus:
return ui::ToString(static_cast<ax::mojom::ImageAnnotationStatus>(value));
case ax::mojom::IntAttribute::kBackgroundColor:
return FormatColor(node.ComputeBackgroundColor());
case ax::mojom::IntAttribute::kColor:
return FormatColor(node.ComputeColor());
// No pretty printing necessary for these:
case ax::mojom::IntAttribute::kActivedescendantId:
case ax::mojom::IntAttribute::kAriaCellColumnIndex:
case ax::mojom::IntAttribute::kAriaCellRowIndex:
case ax::mojom::IntAttribute::kAriaColumnCount:
case ax::mojom::IntAttribute::kAriaCellColumnSpan:
case ax::mojom::IntAttribute::kAriaCellRowSpan:
case ax::mojom::IntAttribute::kAriaRowCount:
case ax::mojom::IntAttribute::kColorValue:
case ax::mojom::IntAttribute::kDOMNodeIdDeprecated:
case ax::mojom::IntAttribute::kDropeffectDeprecated:
case ax::mojom::IntAttribute::kErrormessageIdDeprecated:
case ax::mojom::IntAttribute::kHierarchicalLevel:
case ax::mojom::IntAttribute::kInPageLinkTargetId:
case ax::mojom::IntAttribute::kMemberOfId:
case ax::mojom::IntAttribute::kNextFocusId:
case ax::mojom::IntAttribute::kNextOnLineId:
case ax::mojom::IntAttribute::kNextWindowFocusId:
case ax::mojom::IntAttribute::kPosInSet:
case ax::mojom::IntAttribute::kPopupForId:
case ax::mojom::IntAttribute::kPreviousFocusId:
case ax::mojom::IntAttribute::kPreviousOnLineId:
case ax::mojom::IntAttribute::kPreviousWindowFocusId:
case ax::mojom::IntAttribute::kScrollX:
case ax::mojom::IntAttribute::kScrollXMax:
case ax::mojom::IntAttribute::kScrollXMin:
case ax::mojom::IntAttribute::kScrollY:
case ax::mojom::IntAttribute::kScrollYMax:
case ax::mojom::IntAttribute::kScrollYMin:
case ax::mojom::IntAttribute::kSetSize:
case ax::mojom::IntAttribute::kTableCellColumnIndex:
case ax::mojom::IntAttribute::kTableCellColumnSpan:
case ax::mojom::IntAttribute::kTableCellRowIndex:
case ax::mojom::IntAttribute::kTableCellRowSpan:
case ax::mojom::IntAttribute::kTableColumnCount:
case ax::mojom::IntAttribute::kTableColumnHeaderId:
case ax::mojom::IntAttribute::kTableColumnIndex:
case ax::mojom::IntAttribute::kTableHeaderId:
case ax::mojom::IntAttribute::kTableRowCount:
case ax::mojom::IntAttribute::kTableRowHeaderId:
case ax::mojom::IntAttribute::kTableRowIndex:
case ax::mojom::IntAttribute::kTextSelEnd:
case ax::mojom::IntAttribute::kTextSelStart:
case ax::mojom::IntAttribute::kTextStyle:
case ax::mojom::IntAttribute::kMaxLength:
case ax::mojom::IntAttribute::kPaintOrder:
case ax::mojom::IntAttribute::kNone:
break;
}
// Just return the number
return base::NumberToString(value);
}
} // namespace
AccessibilityTreeFormatterBlink::AccessibilityTreeFormatterBlink() = default;
AccessibilityTreeFormatterBlink::~AccessibilityTreeFormatterBlink() = default;
void AccessibilityTreeFormatterBlink::AddDefaultFilters(
std::vector<AXPropertyFilter>* property_filters) {
// Noisy, perhaps add later:
// editable, focus*, horizontal, linked, richlyEditable, vertical
// Too flaky: hovered, offscreen
// States
AddPropertyFilter(property_filters, "collapsed");
AddPropertyFilter(property_filters, "invisible");
AddPropertyFilter(property_filters, "multiline");
AddPropertyFilter(property_filters, "protected");
AddPropertyFilter(property_filters, "required");
AddPropertyFilter(property_filters, "select*");
AddPropertyFilter(property_filters, "selectedFromFocus=*",
AXPropertyFilter::DENY);
AddPropertyFilter(property_filters, "visited");
// Other attributes
AddPropertyFilter(property_filters, "busy=true");
AddPropertyFilter(property_filters, "valueForRange*");
AddPropertyFilter(property_filters, "minValueForRange*");
AddPropertyFilter(property_filters, "maxValueForRange*");
AddPropertyFilter(property_filters, "autoComplete*");
AddPropertyFilter(property_filters, "restriction*");
AddPropertyFilter(property_filters, "keyShortcuts*");
AddPropertyFilter(property_filters, "activedescendantId*");
AddPropertyFilter(property_filters, "controlsIds*");
AddPropertyFilter(property_filters, "flowtoIds*");
AddPropertyFilter(property_filters, "detailsIds*");
AddPropertyFilter(property_filters, "invalidState=*");
AddPropertyFilter(property_filters, "ignored*");
AddPropertyFilter(property_filters, "invalidState=false",
AXPropertyFilter::DENY); // Don't show false value
AddPropertyFilter(property_filters, "roleDescription=*");
AddPropertyFilter(property_filters, "errormessageId=*");
AddPropertyFilter(property_filters, "virtualContent=*");
AddPropertyFilter(property_filters, "descriptionFrom=prohibitedNameRepair");
// Add the rare name-from values.
AddPropertyFilter(property_filters, "nameFrom=caption");
AddPropertyFilter(property_filters, "nameFrom=placeholder");
AddPropertyFilter(property_filters, "nameFrom=prohibited");
AddPropertyFilter(property_filters, "nameFrom=title");
AddPropertyFilter(property_filters, "nameFrom=value");
}
const char* const TREE_DATA_ATTRIBUTES[] = {"TreeData.textSelStartOffset",
"TreeData.textSelEndOffset"};
const char* STATE_FOCUSED = "focused";
const char* STATE_OFFSCREEN = "offscreen";
base::Value::Dict AccessibilityTreeFormatterBlink::BuildTree(
ui::AXPlatformNodeDelegate* root) const {
if (!root) {
return base::Value::Dict();
}
ui::BrowserAccessibility* root_internal =
ui::BrowserAccessibility::FromAXPlatformNodeDelegate(root);
base::Value::Dict dict;
RecursiveBuildTree(*root_internal, &dict);
return dict;
}
base::Value::Dict AccessibilityTreeFormatterBlink::BuildTreeForSelector(
const AXTreeSelector& selector) const {
NOTREACHED();
}
base::Value::Dict AccessibilityTreeFormatterBlink::BuildTreeForNode(
ui::AXNode* node) const {
CHECK(node);
base::Value::Dict dict;
RecursiveBuildTree(*node, &dict);
return dict;
}
base::Value::Dict AccessibilityTreeFormatterBlink::BuildNode(
ui::AXPlatformNodeDelegate* node) const {
CHECK(node);
base::Value::Dict dict;
AddProperties(*ui::BrowserAccessibility::FromAXPlatformNodeDelegate(node),
&dict);
return dict;
}
std::string AccessibilityTreeFormatterBlink::DumpInternalAccessibilityTree(
ui::AXTreeID tree_id,
const std::vector<AXPropertyFilter>& property_filters) {
ui::AXTreeManager* ax_mgr = ui::AXTreeManager::FromID(tree_id);
DCHECK(ax_mgr);
SetPropertyFilters(property_filters, kFiltersDefaultSet);
return FormatTree(BuildTreeForNode(ax_mgr->GetRoot()));
}
void AccessibilityTreeFormatterBlink::RecursiveBuildTree(
const ui::BrowserAccessibility& node,
base::Value::Dict* dict) const {
if (!ShouldDumpNode(node)) {
return;
}
AddProperties(node, dict);
if (!ShouldDumpChildren(node)) {
return;
}
base::Value::List children;
for (const auto* child_node : node.AllChildren()) {
base::Value::Dict child_dict;
RecursiveBuildTree(*child_node, &child_dict);
children.Append(std::move(child_dict));
}
dict->Set(kChildrenDictAttr, base::Value(std::move(children)));
}
void AccessibilityTreeFormatterBlink::RecursiveBuildTree(
const ui::AXNode& node,
base::Value::Dict* dict) const {
AddProperties(node, dict);
base::Value::List children;
for (ui::AXNode* child_node : node.children()) {
base::Value::Dict child_dict;
RecursiveBuildTree(*child_node, &child_dict);
children.Append(std::move(child_dict));
}
dict->Set(kChildrenDictAttr, base::Value(std::move(children)));
}
void AccessibilityTreeFormatterBlink::AddProperties(
const ui::BrowserAccessibility& node,
base::Value::Dict* dict) const {
int id = node.GetId();
dict->Set("id", id);
dict->Set("internalRole", ui::ToString(node.GetRole()));
gfx::Rect bounds =
gfx::ToEnclosingRect(node.GetData().relative_bounds.bounds);
dict->Set("boundsX", bounds.x());
dict->Set("boundsY", bounds.y());
dict->Set("boundsWidth", bounds.width());
dict->Set("boundsHeight", bounds.height());
ui::AXOffscreenResult offscreen_result = ui::AXOffscreenResult::kOnscreen;
gfx::Rect page_bounds = node.GetClippedRootFrameBoundsRect(&offscreen_result);
dict->Set("pageBoundsX", page_bounds.x());
dict->Set("pageBoundsY", page_bounds.y());
dict->Set("pageBoundsWidth", page_bounds.width());
dict->Set("pageBoundsHeight", page_bounds.height());
dict->Set("transform",
node.GetData().relative_bounds.transform &&
!node.GetData().relative_bounds.transform->IsIdentity());
gfx::Rect unclipped_bounds =
node.GetUnclippedRootFrameBoundsRect(&offscreen_result);
dict->Set("unclippedBoundsX", unclipped_bounds.x());
dict->Set("unclippedBoundsY", unclipped_bounds.y());
dict->Set("unclippedBoundsWidth", unclipped_bounds.width());
dict->Set("unclippedBoundsHeight", unclipped_bounds.height());
for (int32_t state_index = static_cast<int32_t>(ax::mojom::State::kNone);
state_index <= static_cast<int32_t>(ax::mojom::State::kMaxValue);
++state_index) {
auto state = static_cast<ax::mojom::State>(state_index);
if (state == ax::mojom::State::kFocusable ? node.IsFocusable()
: node.HasState(state)) {
dict->SetByDottedPath(ui::ToString(state), true);
}
}
if (offscreen_result == ui::AXOffscreenResult::kOffscreen) {
dict->Set(STATE_OFFSCREEN, true);
}
for (int32_t attr_index =
static_cast<int32_t>(ax::mojom::StringAttribute::kNone);
attr_index <=
static_cast<int32_t>(ax::mojom::StringAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::StringAttribute>(attr_index);
auto maybe_value = GetStringAttribute(*node.node(), attr);
if (maybe_value.has_value()) {
dict->SetByDottedPath(ui::ToString(attr), maybe_value.value());
}
}
for (int32_t attr_index =
static_cast<int32_t>(ax::mojom::IntAttribute::kNone);
attr_index <= static_cast<int32_t>(ax::mojom::IntAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::IntAttribute>(attr_index);
auto maybe_value = ui::ComputeAttribute(&node, attr);
if (maybe_value.has_value()) {
dict->SetByDottedPath(
ui::ToString(attr),
IntAttrToString(*node.node(), attr, maybe_value.value()));
}
}
for (int32_t attr_index =
static_cast<int32_t>(ax::mojom::FloatAttribute::kNone);
attr_index <= static_cast<int32_t>(ax::mojom::FloatAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::FloatAttribute>(attr_index);
if (node.HasFloatAttribute(attr) &&
std::isfinite(node.GetFloatAttribute(attr))) {
dict->SetByDottedPath(ui::ToString(attr), node.GetFloatAttribute(attr));
}
}
for (int32_t attr_index =
static_cast<int32_t>(ax::mojom::BoolAttribute::kNone);
attr_index <= static_cast<int32_t>(ax::mojom::BoolAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::BoolAttribute>(attr_index);
if (node.HasBoolAttribute(attr)) {
dict->SetByDottedPath(ui::ToString(attr), node.GetBoolAttribute(attr));
}
}
for (int32_t attr_index =
static_cast<int32_t>(ax::mojom::IntListAttribute::kNone);
attr_index <=
static_cast<int32_t>(ax::mojom::IntListAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::IntListAttribute>(attr_index);
if (node.HasIntListAttribute(attr)) {
std::vector<int32_t> values;
node.GetIntListAttribute(attr, &values);
base::Value::List value_list;
for (const int& value : values) {
if (ui::IsNodeIdIntListAttribute(attr)) {
ui::BrowserAccessibility* target = node.manager()->GetFromID(value);
if (target) {
value_list.Append(ui::ToString(target->GetRole()));
} else {
value_list.Append("null");
}
} else {
value_list.Append(value);
}
}
dict->Set(ui::ToString(attr), std::move(value_list));
}
}
// Check for relevant rich text selection info in AXTreeData
ui::AXSelection unignored_selection =
node.manager()->ax_tree()->GetUnignoredSelection();
int anchor_id = unignored_selection.anchor_object_id;
if (id == anchor_id) {
int anchor_offset = unignored_selection.anchor_offset;
dict->SetByDottedPath("TreeData.textSelStartOffset", anchor_offset);
}
int focus_id = unignored_selection.focus_object_id;
if (id == focus_id) {
int focus_offset = unignored_selection.focus_offset;
dict->SetByDottedPath("TreeData.textSelEndOffset", focus_offset);
}
std::vector<std::string> actions_strings;
for (int32_t action_index =
static_cast<int32_t>(ax::mojom::Action::kNone) + 1;
action_index <= static_cast<int32_t>(ax::mojom::Action::kMaxValue);
++action_index) {
auto action = static_cast<ax::mojom::Action>(action_index);
if (node.HasAction(action)) {
actions_strings.push_back(ui::ToString(action));
}
}
if (!actions_strings.empty()) {
dict->Set("actions", base::JoinString(actions_strings, ","));
}
}
void AccessibilityTreeFormatterBlink::AddProperties(
const ui::AXNode& node,
base::Value::Dict* dict) const {
int id = node.id();
dict->Set("id", id);
dict->Set("internalRole", ui::ToString(node.GetRole()));
gfx::Rect bounds = gfx::ToEnclosingRect(node.data().relative_bounds.bounds);
dict->Set("boundsX", bounds.x());
dict->Set("boundsY", bounds.y());
dict->Set("boundsWidth", bounds.width());
dict->Set("boundsHeight", bounds.height());
// TODO(kschmi): Add support for the following (potentially via AXTree):
// GetClippedRootFrameBoundsRect
// pageBoundsX
// pageBoundsY
// pageBoundsWidth
// pageBoundsHeight
// GetUnclippedRootFrameBoundsRect
// unclippedBoundsX
// unclippedBoundsY
// unclippedBoundsWidth
// unclippedBoundsHeight
// STATE_OFFSCREEN
// ComputeAttribute
// TableCellAriaColIndex
// TableCellAriaRowIndex
// TableCellColIndex
// TableCellRowIndex
// TableCellColSpan
// TableCellRowSpan
// TableRowRowIndex
// TableColCount
// TableRowCount
// TableAriaColCount
// TableAriaRowCount
// PosInSet
// SetSize
// SetSize
dict->Set("transform",
node.data().relative_bounds.transform &&
!node.data().relative_bounds.transform->IsIdentity());
for (int32_t state_index = static_cast<int32_t>(ax::mojom::State::kNone);
state_index <= static_cast<int32_t>(ax::mojom::State::kMaxValue);
++state_index) {
auto state = static_cast<ax::mojom::State>(state_index);
if (node.HasState(state)) {
dict->SetByDottedPath(ui::ToString(state), true);
}
}
for (int32_t attr_index =
static_cast<int32_t>(ax::mojom::StringAttribute::kNone);
attr_index <=
static_cast<int32_t>(ax::mojom::StringAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::StringAttribute>(attr_index);
auto maybe_value = GetStringAttribute(node, attr);
if (maybe_value.has_value()) {
dict->SetByDottedPath(ui::ToString(attr), maybe_value.value());
}
}
for (int32_t attr_index =
static_cast<int32_t>(ax::mojom::IntAttribute::kNone);
attr_index <= static_cast<int32_t>(ax::mojom::IntAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::IntAttribute>(attr_index);
if (node.HasIntAttribute(attr)) {
int32_t value = node.GetIntAttribute(attr);
dict->SetByDottedPath(ui::ToString(attr),
IntAttrToString(node, attr, value));
}
}
for (int32_t attr_index =
static_cast<int32_t>(ax::mojom::FloatAttribute::kNone);
attr_index <= static_cast<int32_t>(ax::mojom::FloatAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::FloatAttribute>(attr_index);
if (node.HasFloatAttribute(attr) &&
std::isfinite(node.GetFloatAttribute(attr))) {
dict->SetByDottedPath(ui::ToString(attr), node.GetFloatAttribute(attr));
}
}
for (int32_t attr_index =
static_cast<int32_t>(ax::mojom::BoolAttribute::kNone);
attr_index <= static_cast<int32_t>(ax::mojom::BoolAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::BoolAttribute>(attr_index);
if (node.HasBoolAttribute(attr)) {
dict->SetByDottedPath(ui::ToString(attr), node.GetBoolAttribute(attr));
}
}
for (int32_t attr_index =
static_cast<int32_t>(ax::mojom::IntListAttribute::kNone);
attr_index <=
static_cast<int32_t>(ax::mojom::IntListAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::IntListAttribute>(attr_index);
if (node.HasIntListAttribute(attr)) {
const std::vector<int32_t>& values = node.GetIntListAttribute(attr);
base::Value::List value_list;
for (auto value : values) {
if (ui::IsNodeIdIntListAttribute(attr)) {
ui::AXTreeID tree_id = node.tree()->GetAXTreeID();
ui::AXNode* target =
ui::AXTreeManager::FromID(tree_id)->GetNode(node.id());
if (target) {
value_list.Append(ui::ToString(target->GetRole()));
} else {
value_list.Append("null");
}
} else {
value_list.Append(value);
}
}
dict->Set(ui::ToString(attr), std::move(value_list));
}
}
// Check for relevant rich text selection info in AXTreeData
ui::AXSelection unignored_selection = node.tree()->GetUnignoredSelection();
int anchor_id = unignored_selection.anchor_object_id;
if (id == anchor_id) {
int anchor_offset = unignored_selection.anchor_offset;
dict->SetByDottedPath("TreeData.textSelStartOffset", anchor_offset);
}
int focus_id = unignored_selection.focus_object_id;
if (id == focus_id) {
int focus_offset = unignored_selection.focus_offset;
dict->SetByDottedPath("TreeData.textSelEndOffset", focus_offset);
}
std::vector<std::string> actions_strings;
for (int32_t action_index =
static_cast<int32_t>(ax::mojom::Action::kNone) + 1;
action_index <= static_cast<int32_t>(ax::mojom::Action::kMaxValue);
++action_index) {
auto action = static_cast<ax::mojom::Action>(action_index);
if (node.HasAction(action)) {
actions_strings.push_back(ui::ToString(action));
}
}
if (!actions_strings.empty()) {
dict->Set("actions", base::JoinString(actions_strings, ","));
}
}
std::string AccessibilityTreeFormatterBlink::ProcessTreeForOutput(
const base::Value::Dict& dict) const {
const std::string* error_value = dict.FindString("error");
if (error_value) {
return *error_value;
}
std::string line;
std::string id_value = base::NumberToString(dict.FindInt("id").value_or(0));
if (show_ids()) { // Show id on every line.
WriteAttribute(true, id_value, &line);
} else { // Show id if @BlINK-ALLOW:id#=* specified.
WriteAttribute(false, std::string("id#=") + id_value, &line);
}
const std::string* role_value = dict.FindString("internalRole");
if (role_value) {
WriteAttribute(true, *role_value, &line);
}
for (int state_index = static_cast<int32_t>(ax::mojom::State::kNone);
state_index <= static_cast<int32_t>(ax::mojom::State::kMaxValue);
++state_index) {
auto state = static_cast<ax::mojom::State>(state_index);
const base::Value* value = dict.Find(ui::ToString(state));
if (!value) {
continue;
}
WriteAttribute(false, ui::ToString(state), &line);
}
// Offscreen and Focused states are not in the state list.
if (dict.FindBool(STATE_OFFSCREEN).value_or(false)) {
WriteAttribute(false, STATE_OFFSCREEN, &line);
}
if (dict.FindBool(STATE_FOCUSED).value_or(false)) {
WriteAttribute(false, STATE_FOCUSED, &line);
}
if (dict.Find("boundsX") && dict.Find("boundsY")) {
WriteAttribute(false,
FormatCoordinates(dict, "location", "boundsX", "boundsY"),
&line);
}
if (dict.Find("boundsWidth") && dict.Find("boundsHeight")) {
WriteAttribute(
false, FormatCoordinates(dict, "size", "boundsWidth", "boundsHeight"),
&line);
}
if (!dict.FindBool("ignored").value_or(false)) {
if (dict.Find("pageBoundsX") && dict.Find("pageBoundsY")) {
WriteAttribute(
false,
FormatCoordinates(dict, "pageLocation", "pageBoundsX", "pageBoundsY"),
&line);
}
if (dict.Find("pageBoundsWidth") && dict.Find("pageBoundsHeight")) {
WriteAttribute(false,
FormatCoordinates(dict, "pageSize", "pageBoundsWidth",
"pageBoundsHeight"),
&line);
}
if (dict.Find("unclippedBoundsX") && dict.Find("unclippedBoundsY")) {
WriteAttribute(false,
FormatCoordinates(dict, "unclippedLocation",
"unclippedBoundsX", "unclippedBoundsY"),
&line);
}
if (dict.Find("unclippedBoundsWidth") &&
dict.Find("unclippedBoundsHeight")) {
WriteAttribute(
false,
FormatCoordinates(dict, "unclippedSize", "unclippedBoundsWidth",
"unclippedBoundsHeight"),
&line);
}
}
if (dict.FindBool("transform").value_or(false)) {
WriteAttribute(false, "transform", &line);
}
for (int attr_index = static_cast<int32_t>(ax::mojom::StringAttribute::kNone);
attr_index <=
static_cast<int32_t>(ax::mojom::StringAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::StringAttribute>(attr_index);
const std::string* string_value = dict.FindString(ui::ToString(attr));
if (!string_value) {
continue;
}
WriteAttribute(false,
base::StringPrintf("%s='%s'", ui::ToString(attr),
string_value->c_str()),
&line);
}
for (int attr_index = static_cast<int32_t>(ax::mojom::IntAttribute::kNone);
attr_index <= static_cast<int32_t>(ax::mojom::IntAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::IntAttribute>(attr_index);
const std::string* string_value = dict.FindString(ui::ToString(attr));
if (!string_value) {
continue;
}
WriteAttribute(
false,
base::StringPrintf("%s=%s", ui::ToString(attr), string_value->c_str()),
&line);
}
for (int attr_index = static_cast<int32_t>(ax::mojom::BoolAttribute::kNone);
attr_index <= static_cast<int32_t>(ax::mojom::BoolAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::BoolAttribute>(attr_index);
std::optional<bool> bool_value = dict.FindBool(ui::ToString(attr));
if (!bool_value.has_value()) {
continue;
}
WriteAttribute(false,
base::StringPrintf("%s=%s", ui::ToString(attr),
base::ToString(*bool_value)),
&line);
}
for (int attr_index = static_cast<int32_t>(ax::mojom::FloatAttribute::kNone);
attr_index <= static_cast<int32_t>(ax::mojom::FloatAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::FloatAttribute>(attr_index);
std::optional<double> float_value = dict.FindDouble(ui::ToString(attr));
if (!float_value) {
continue;
}
WriteAttribute(
false, base::StringPrintf("%s=%.2f", ui::ToString(attr), *float_value),
&line);
}
for (int attr_index =
static_cast<int32_t>(ax::mojom::IntListAttribute::kNone);
attr_index <=
static_cast<int32_t>(ax::mojom::IntListAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::IntListAttribute>(attr_index);
const base::Value::List* value = dict.FindList(ui::ToString(attr));
if (!value) {
continue;
}
const base::Value::List& list = *value;
std::string attr_string(ui::ToString(attr));
attr_string.push_back('=');
for (size_t i = 0; i < list.size(); ++i) {
if (i > 0) {
attr_string += ",";
}
if (ui::IsNodeIdIntListAttribute(attr)) {
if (list[i].is_string()) {
attr_string += list[i].GetString();
}
} else {
attr_string += base::NumberToString(list[i].GetIfInt().value_or(0));
}
}
WriteAttribute(false, attr_string, &line);
}
const std::string* actions_value = dict.FindString("actions");
if (actions_value) {
WriteAttribute(
false, base::StringPrintf("actions=%s", actions_value->c_str()), &line);
}
for (const char* attribute_name : TREE_DATA_ATTRIBUTES) {
const base::Value* value = dict.FindByDottedPath(attribute_name);
if (!value) {
continue;
}
switch (value->type()) {
case base::Value::Type::STRING:
WriteAttribute(false,
base::StringPrintf("%s=%s", attribute_name,
value->GetString().c_str()),
&line);
break;
case base::Value::Type::INTEGER:
WriteAttribute(
false, base::StringPrintf("%s=%d", attribute_name, value->GetInt()),
&line);
break;
case base::Value::Type::DOUBLE:
WriteAttribute(
false,
base::StringPrintf("%s=%.2f", attribute_name, value->GetDouble()),
&line);
break;
default:
NOTREACHED();
}
}
return line;
}
} // namespace content