blob: 82e3ab3b487bd1524fbe4cde3eab03efd2504cae [file] [log] [blame]
// Copyright (c) 2015 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 "content/browser/accessibility/accessibility_tree_formatter_blink.h"
#include <math.h>
#include <stddef.h>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "content/browser/accessibility/browser_accessibility_manager.h"
#include "ui/accessibility/ax_enums.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/transform.h"
namespace content {
AccessibilityTreeFormatterBlink::AccessibilityTreeFormatterBlink()
: AccessibilityTreeFormatterBrowser() {}
AccessibilityTreeFormatterBlink::~AccessibilityTreeFormatterBlink() {
}
const char* const TREE_DATA_ATTRIBUTES[] = {"TreeData.textSelStartOffset",
"TreeData.textSelEndOffset"};
const char* STATE_FOCUSED = "focused";
const char* STATE_OFFSCREEN = "offscreen";
uint32_t AccessibilityTreeFormatterBlink::ChildCount(
const BrowserAccessibility& node) const {
if (node.HasIntAttribute(ui::AX_ATTR_CHILD_TREE_ID))
return node.PlatformChildCount();
else
return node.InternalChildCount();
}
BrowserAccessibility* AccessibilityTreeFormatterBlink::GetChild(
const BrowserAccessibility& node,
uint32_t i) const {
if (node.HasIntAttribute(ui::AX_ATTR_CHILD_TREE_ID))
return node.PlatformGetChild(i);
else
return node.InternalGetChild(i);
}
// TODO(aleventhal) Convert ax enums to friendly strings, e.g. AXCheckedState.
std::string AccessibilityTreeFormatterBlink::IntAttrToString(
const BrowserAccessibility& node,
ui::AXIntAttribute attr,
int value) const {
if (ui::IsNodeIdIntAttribute(attr)) {
// Relation
BrowserAccessibility* target = node.manager()->GetFromID(value);
return target ? ui::ToString(target->GetData().role) : std::string("null");
}
switch (attr) {
case ui::AX_ATTR_ARIA_CURRENT_STATE:
return ui::ToString(static_cast<ui::AXAriaCurrentState>(value));
case ui::AX_ATTR_CHECKED_STATE:
return ui::ToString(static_cast<ui::AXCheckedState>(value));
case ui::AX_ATTR_DEFAULT_ACTION_VERB:
return ui::ToString(static_cast<ui::AXDefaultActionVerb>(value));
case ui::AX_ATTR_DESCRIPTION_FROM:
return ui::ToString(static_cast<ui::AXDescriptionFrom>(value));
case ui::AX_ATTR_INVALID_STATE:
return ui::ToString(static_cast<ui::AXInvalidState>(value));
case ui::AX_ATTR_NAME_FROM:
return ui::ToString(static_cast<ui::AXNameFrom>(value));
case ui::AX_ATTR_RESTRICTION:
return ui::ToString(static_cast<ui::AXRestriction>(value));
case ui::AX_ATTR_SORT_DIRECTION:
return ui::ToString(static_cast<ui::AXSortDirection>(value));
case ui::AX_ATTR_TEXT_DIRECTION:
return ui::ToString(static_cast<ui::AXTextDirection>(value));
// No pretty printing necessary for these:
case ui::AX_ATTR_ACTIVEDESCENDANT_ID:
case ui::AX_ATTR_ARIA_CELL_COLUMN_INDEX:
case ui::AX_ATTR_ARIA_CELL_ROW_INDEX:
case ui::AX_ATTR_ARIA_COLUMN_COUNT:
case ui::AX_ATTR_ARIA_ROW_COUNT:
case ui::AX_ATTR_BACKGROUND_COLOR:
case ui::AX_ATTR_CHILD_TREE_ID:
case ui::AX_ATTR_COLOR:
case ui::AX_ATTR_COLOR_VALUE:
case ui::AX_ATTR_DETAILS_ID:
case ui::AX_ATTR_ERRORMESSAGE_ID:
case ui::AX_ATTR_HIERARCHICAL_LEVEL:
case ui::AX_ATTR_IN_PAGE_LINK_TARGET_ID:
case ui::AX_ATTR_MEMBER_OF_ID:
case ui::AX_ATTR_NEXT_FOCUS_ID:
case ui::AX_ATTR_NEXT_ON_LINE_ID:
case ui::AX_ATTR_POS_IN_SET:
case ui::AX_ATTR_PREVIOUS_FOCUS_ID:
case ui::AX_ATTR_PREVIOUS_ON_LINE_ID:
case ui::AX_ATTR_SCROLL_X:
case ui::AX_ATTR_SCROLL_X_MAX:
case ui::AX_ATTR_SCROLL_X_MIN:
case ui::AX_ATTR_SCROLL_Y:
case ui::AX_ATTR_SCROLL_Y_MAX:
case ui::AX_ATTR_SCROLL_Y_MIN:
case ui::AX_ATTR_SET_SIZE:
case ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX:
case ui::AX_ATTR_TABLE_CELL_COLUMN_SPAN:
case ui::AX_ATTR_TABLE_CELL_ROW_INDEX:
case ui::AX_ATTR_TABLE_CELL_ROW_SPAN:
case ui::AX_ATTR_TABLE_COLUMN_COUNT:
case ui::AX_ATTR_TABLE_COLUMN_HEADER_ID:
case ui::AX_ATTR_TABLE_COLUMN_INDEX:
case ui::AX_ATTR_TABLE_HEADER_ID:
case ui::AX_ATTR_TABLE_ROW_COUNT:
case ui::AX_ATTR_TABLE_ROW_HEADER_ID:
case ui::AX_ATTR_TABLE_ROW_INDEX:
case ui::AX_ATTR_TEXT_SEL_END:
case ui::AX_ATTR_TEXT_SEL_START:
case ui::AX_ATTR_TEXT_STYLE:
case ui::AX_INT_ATTRIBUTE_NONE:
break;
}
// Just return the number
return std::to_string(value);
}
void AccessibilityTreeFormatterBlink::AddProperties(
const BrowserAccessibility& node,
base::DictionaryValue* dict) {
int id = node.GetId();
dict->SetInteger("id", id);
dict->SetString("internalRole", ui::ToString(node.GetData().role));
gfx::Rect bounds = gfx::ToEnclosingRect(node.GetData().location);
dict->SetInteger("boundsX", bounds.x());
dict->SetInteger("boundsY", bounds.y());
dict->SetInteger("boundsWidth", bounds.width());
dict->SetInteger("boundsHeight", bounds.height());
bool offscreen = false;
gfx::Rect page_bounds = node.GetPageBoundsRect(&offscreen);
dict->SetInteger("pageBoundsX", page_bounds.x());
dict->SetInteger("pageBoundsY", page_bounds.y());
dict->SetInteger("pageBoundsWidth", page_bounds.width());
dict->SetInteger("pageBoundsHeight", page_bounds.height());
dict->SetBoolean("transform",
node.GetData().transform &&
!node.GetData().transform->IsIdentity());
gfx::Rect unclipped_bounds = node.GetPageBoundsRect(&offscreen, false);
dict->SetInteger("unclippedBoundsX", unclipped_bounds.x());
dict->SetInteger("unclippedBoundsY", unclipped_bounds.y());
dict->SetInteger("unclippedBoundsWidth", unclipped_bounds.width());
dict->SetInteger("unclippedBoundsHeight", unclipped_bounds.height());
for (int state_index = ui::AX_STATE_NONE;
state_index <= ui::AX_STATE_LAST;
++state_index) {
auto state = static_cast<ui::AXState>(state_index);
if (node.HasState(state))
dict->SetBoolean(ui::ToString(state), true);
}
if (offscreen)
dict->SetBoolean(STATE_OFFSCREEN, true);
for (int attr_index = ui::AX_STRING_ATTRIBUTE_NONE;
attr_index <= ui::AX_STRING_ATTRIBUTE_LAST;
++attr_index) {
auto attr = static_cast<ui::AXStringAttribute>(attr_index);
if (node.HasStringAttribute(attr))
dict->SetString(ui::ToString(attr), node.GetStringAttribute(attr));
}
for (int attr_index = ui::AX_INT_ATTRIBUTE_NONE;
attr_index <= ui::AX_INT_ATTRIBUTE_LAST;
++attr_index) {
auto attr = static_cast<ui::AXIntAttribute>(attr_index);
if (node.HasIntAttribute(attr)) {
int value = node.GetIntAttribute(attr);
dict->SetString(ui::ToString(attr), IntAttrToString(node, attr, value));
}
}
for (int attr_index = ui::AX_FLOAT_ATTRIBUTE_NONE;
attr_index <= ui::AX_FLOAT_ATTRIBUTE_LAST;
++attr_index) {
auto attr = static_cast<ui::AXFloatAttribute>(attr_index);
if (node.HasFloatAttribute(attr) && isfinite(node.GetFloatAttribute(attr)))
dict->SetDouble(ui::ToString(attr), node.GetFloatAttribute(attr));
}
for (int attr_index = ui::AX_BOOL_ATTRIBUTE_NONE;
attr_index <= ui::AX_BOOL_ATTRIBUTE_LAST;
++attr_index) {
auto attr = static_cast<ui::AXBoolAttribute>(attr_index);
if (node.HasBoolAttribute(attr))
dict->SetBoolean(ui::ToString(attr), node.GetBoolAttribute(attr));
}
for (int attr_index = ui::AX_INT_LIST_ATTRIBUTE_NONE;
attr_index <= ui::AX_INT_LIST_ATTRIBUTE_LAST;
++attr_index) {
auto attr = static_cast<ui::AXIntListAttribute>(attr_index);
if (node.HasIntListAttribute(attr)) {
std::vector<int32_t> values;
node.GetIntListAttribute(attr, &values);
auto value_list = std::make_unique<base::ListValue>();
for (size_t i = 0; i < values.size(); ++i) {
if (ui::IsNodeIdIntListAttribute(attr)) {
BrowserAccessibility* target = node.manager()->GetFromID(values[i]);
if (target)
value_list->AppendString(ui::ToString(target->GetData().role));
else
value_list->AppendString("null");
} else {
value_list->AppendInteger(values[i]);
}
}
dict->Set(ui::ToString(attr), std::move(value_list));
}
}
// Check for relevant rich text selection info in AXTreeData
int anchor_id = node.manager()->GetTreeData().sel_anchor_object_id;
if (id == anchor_id) {
int anchor_offset = node.manager()->GetTreeData().sel_anchor_offset;
dict->SetInteger("TreeData.textSelStartOffset", anchor_offset);
}
int focus_id = node.manager()->GetTreeData().sel_focus_object_id;
if (id == focus_id) {
int focus_offset = node.manager()->GetTreeData().sel_focus_offset;
dict->SetInteger("TreeData.textSelEndOffset", focus_offset);
}
std::vector<std::string> actions_strings;
for (int action_index = ui::AX_ACTION_NONE + 1;
action_index <= ui::AX_ACTION_LAST; ++action_index) {
auto action = static_cast<ui::AXAction>(action_index);
if (node.HasAction(action))
actions_strings.push_back(ui::ToString(action));
}
if (!actions_strings.empty())
dict->SetString("actions", base::JoinString(actions_strings, ","));
}
base::string16 AccessibilityTreeFormatterBlink::ProcessTreeForOutput(
const base::DictionaryValue& dict,
base::DictionaryValue* filtered_dict_result) {
base::string16 error_value;
if (dict.GetString("error", &error_value))
return error_value;
base::string16 line;
if (show_ids()) {
int id_value;
dict.GetInteger("id", &id_value);
WriteAttribute(true, base::IntToString16(id_value), &line);
}
base::string16 role_value;
dict.GetString("internalRole", &role_value);
WriteAttribute(true, base::UTF16ToUTF8(role_value), &line);
for (int state_index = ui::AX_STATE_NONE;
state_index <= ui::AX_STATE_LAST;
++state_index) {
auto state = static_cast<ui::AXState>(state_index);
const base::Value* value;
if (!dict.Get(ui::ToString(state), &value))
continue;
WriteAttribute(false, ui::ToString(state), &line);
}
// Offscreen and Focused states are not in the state list.
bool offscreen = false;
dict.GetBoolean(STATE_OFFSCREEN, &offscreen);
if (offscreen)
WriteAttribute(false, STATE_OFFSCREEN, &line);
bool focused = false;
dict.GetBoolean(STATE_FOCUSED, &focused);
if (focused)
WriteAttribute(false, STATE_FOCUSED, &line);
WriteAttribute(false,
FormatCoordinates("location", "boundsX", "boundsY", dict),
&line);
WriteAttribute(false,
FormatCoordinates("size", "boundsWidth", "boundsHeight", dict),
&line);
WriteAttribute(false,
FormatCoordinates("pageLocation",
"pageBoundsX", "pageBoundsY", dict),
&line);
WriteAttribute(false,
FormatCoordinates("pageSize",
"pageBoundsWidth", "pageBoundsHeight", dict),
&line);
WriteAttribute(false,
FormatCoordinates("unclippedLocation", "unclippedBoundsX",
"unclippedBoundsY", dict),
&line);
WriteAttribute(false,
FormatCoordinates("unclippedSize", "unclippedBoundsWidth",
"unclippedBoundsHeight", dict),
&line);
bool transform;
if (dict.GetBoolean("transform", &transform) && transform)
WriteAttribute(false, "transform", &line);
for (int attr_index = ui::AX_STRING_ATTRIBUTE_NONE;
attr_index <= ui::AX_STRING_ATTRIBUTE_LAST;
++attr_index) {
auto attr = static_cast<ui::AXStringAttribute>(attr_index);
std::string string_value;
if (!dict.GetString(ui::ToString(attr), &string_value))
continue;
WriteAttribute(
false,
base::StringPrintf("%s='%s'", ui::ToString(attr), string_value.c_str()),
&line);
}
for (int attr_index = ui::AX_INT_ATTRIBUTE_NONE;
attr_index <= ui::AX_INT_ATTRIBUTE_LAST;
++attr_index) {
auto attr = static_cast<ui::AXIntAttribute>(attr_index);
std::string string_value;
if (!dict.GetString(ui::ToString(attr), &string_value))
continue;
WriteAttribute(
false,
base::StringPrintf("%s=%s", ui::ToString(attr), string_value.c_str()),
&line);
}
for (int attr_index = ui::AX_BOOL_ATTRIBUTE_NONE;
attr_index <= ui::AX_BOOL_ATTRIBUTE_LAST;
++attr_index) {
auto attr = static_cast<ui::AXBoolAttribute>(attr_index);
bool bool_value;
if (!dict.GetBoolean(ui::ToString(attr), &bool_value))
continue;
WriteAttribute(false,
base::StringPrintf("%s=%s", ui::ToString(attr),
bool_value ? "true" : "false"),
&line);
}
for (int attr_index = ui::AX_FLOAT_ATTRIBUTE_NONE;
attr_index <= ui::AX_FLOAT_ATTRIBUTE_LAST; ++attr_index) {
auto attr = static_cast<ui::AXFloatAttribute>(attr_index);
double float_value;
if (!dict.GetDouble(ui::ToString(attr), &float_value))
continue;
WriteAttribute(
false, base::StringPrintf("%s=%.2f", ui::ToString(attr), float_value),
&line);
}
for (int attr_index = ui::AX_INT_LIST_ATTRIBUTE_NONE;
attr_index <= ui::AX_INT_LIST_ATTRIBUTE_LAST;
++attr_index) {
auto attr = static_cast<ui::AXIntListAttribute>(attr_index);
const base::ListValue* value;
if (!dict.GetList(ui::ToString(attr), &value))
continue;
std::string attr_string(ui::ToString(attr));
attr_string.push_back('=');
for (size_t i = 0; i < value->GetSize(); ++i) {
if (i > 0)
attr_string += ",";
if (ui::IsNodeIdIntListAttribute(attr)) {
std::string string_value;
value->GetString(i, &string_value);
attr_string += string_value;
} else {
int int_value;
value->GetInteger(i, &int_value);
attr_string += base::IntToString(int_value);
}
}
WriteAttribute(false, attr_string, &line);
}
std::string actions_value;
if (dict.GetString("actions", &actions_value)) {
WriteAttribute(
false, base::StringPrintf("%s=%s", "actions", actions_value.c_str()),
&line);
}
for (const char* attribute_name : TREE_DATA_ATTRIBUTES) {
const base::Value* value;
if (!dict.Get(attribute_name, &value))
continue;
switch (value->type()) {
case base::Value::Type::STRING: {
std::string string_value;
value->GetAsString(&string_value);
WriteAttribute(
false,
base::StringPrintf("%s=%s", attribute_name, string_value.c_str()),
&line);
break;
}
case base::Value::Type::INTEGER: {
int int_value = 0;
value->GetAsInteger(&int_value);
WriteAttribute(false,
base::StringPrintf("%s=%d", attribute_name, int_value),
&line);
break;
}
case base::Value::Type::DOUBLE: {
double double_value = 0.0;
value->GetAsDouble(&double_value);
WriteAttribute(
false, base::StringPrintf("%s=%.2f", attribute_name, double_value),
&line);
break;
}
default:
NOTREACHED();
break;
}
}
return line;
}
const base::FilePath::StringType
AccessibilityTreeFormatterBlink::GetExpectedFileSuffix() {
return FILE_PATH_LITERAL("-expected-blink.txt");
}
const std::string AccessibilityTreeFormatterBlink::GetAllowEmptyString() {
return "@BLINK-ALLOW-EMPTY:";
}
const std::string AccessibilityTreeFormatterBlink::GetAllowString() {
return "@BLINK-ALLOW:";
}
const std::string AccessibilityTreeFormatterBlink::GetDenyString() {
return "@BLINK-DENY:";
}
} // namespace content