blob: 21d0e1d89ac7ff45a8d48ecc1fe0842d24442d8d [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_browser.h"
#include <atspi/atspi.h>
#include <dbus/dbus.h>
#include <iostream>
#include <utility>
#include "base/logging.h"
#include "base/strings/pattern.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/accessibility_tree_formatter_utils_auralinux.h"
#include "content/browser/accessibility/browser_accessibility_auralinux.h"
#include "ui/accessibility/platform/ax_platform_node_auralinux.h"
#include "ui/base/x/x11_util.h"
#include "ui/gfx/x/x11.h"
namespace content {
class AccessibilityTreeFormatterAuraLinux
: public AccessibilityTreeFormatterBrowser {
public:
AccessibilityTreeFormatterAuraLinux();
~AccessibilityTreeFormatterAuraLinux() override;
private:
const base::FilePath::StringType GetExpectedFileSuffix() override;
const std::string GetAllowEmptyString() override;
const std::string GetAllowString() override;
const std::string GetDenyString() override;
void AddProperties(const BrowserAccessibility& node,
base::DictionaryValue* dict) override;
base::string16 ProcessTreeForOutput(
const base::DictionaryValue& node,
base::DictionaryValue* filtered_dict_result = nullptr) override;
std::unique_ptr<base::DictionaryValue> BuildAccessibilityTreeForProcess(
base::ProcessId pid) override;
std::unique_ptr<base::DictionaryValue> BuildAccessibilityTreeForWindow(
gfx::AcceleratedWidget hwnd) override;
std::unique_ptr<base::DictionaryValue> BuildAccessibilityTreeForPattern(
const base::StringPiece& pattern) override;
std::unique_ptr<base::DictionaryValue> BuildAccessibilityTreeWithNode(
AtspiAccessible* node);
void RecursiveBuildAccessibilityTree(AtspiAccessible* node,
base::DictionaryValue* dict);
virtual void AddProperties(AtspiAccessible* node,
base::DictionaryValue* dict);
};
// static
std::unique_ptr<AccessibilityTreeFormatter>
AccessibilityTreeFormatter::Create() {
return std::make_unique<AccessibilityTreeFormatterAuraLinux>();
}
AccessibilityTreeFormatterAuraLinux::AccessibilityTreeFormatterAuraLinux() {
}
AccessibilityTreeFormatterAuraLinux::~AccessibilityTreeFormatterAuraLinux() {
}
std::unique_ptr<base::DictionaryValue>
AccessibilityTreeFormatterAuraLinux::BuildAccessibilityTreeForPattern(
const base::StringPiece& pattern) {
// AT-SPI2 always expects the first parameter to this call to be zero.
AtspiAccessible* desktop = atspi_get_desktop(0);
CHECK(desktop);
GError* error = nullptr;
int child_count = atspi_accessible_get_child_count(desktop, &error);
if (error) {
LOG(ERROR) << "Failed to get children of root accessible object"
<< error->message;
g_clear_error(&error);
return nullptr;
}
std::vector<std::pair<std::string, AtspiAccessible*>> matched_children;
for (int i = 0; i < child_count; i++) {
AtspiAccessible* child =
atspi_accessible_get_child_at_index(desktop, i, &error);
if (error) {
g_clear_error(&error);
continue;
}
char* name = atspi_accessible_get_name(child, &error);
if (!error && name && base::MatchPattern(name, pattern)) {
matched_children.push_back(std::make_pair(name, child));
}
free(name);
}
if (matched_children.size() == 1) {
return BuildAccessibilityTreeWithNode(matched_children[0].second);
}
if (matched_children.size()) {
LOG(ERROR) << "Matched more than one application. "
<< "Try to make a more specific pattern.";
for (auto& match : matched_children) {
LOG(ERROR) << " * " << match.first;
}
}
return nullptr;
}
std::unique_ptr<base::DictionaryValue>
AccessibilityTreeFormatterAuraLinux::BuildAccessibilityTreeForProcess(
base::ProcessId pid) {
LOG(ERROR) << "Aura Linux does not yet support building trees for processes";
NOTIMPLEMENTED();
return nullptr;
}
std::unique_ptr<base::DictionaryValue>
AccessibilityTreeFormatterAuraLinux::BuildAccessibilityTreeForWindow(
gfx::AcceleratedWidget window) {
LOG(ERROR) << "Aura Linux does not yet support building trees for window ids";
NOTIMPLEMENTED();
return nullptr;
}
std::unique_ptr<base::DictionaryValue>
AccessibilityTreeFormatterAuraLinux::BuildAccessibilityTreeWithNode(
AtspiAccessible* node) {
CHECK(node);
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
RecursiveBuildAccessibilityTree(node, dict.get());
return dict;
}
void AccessibilityTreeFormatterAuraLinux::RecursiveBuildAccessibilityTree(
AtspiAccessible* node,
base::DictionaryValue* dict) {
AddProperties(node, dict);
GError* error = nullptr;
int child_count = atspi_accessible_get_child_count(node, &error);
if (error) {
g_clear_error(&error);
return;
}
if (child_count <= 0)
return;
auto children = std::make_unique<base::ListValue>();
for (int i = 0; i < child_count; i++) {
std::unique_ptr<base::DictionaryValue> child_dict(
new base::DictionaryValue);
AtspiAccessible* child =
atspi_accessible_get_child_at_index(node, i, &error);
if (error) {
child_dict->SetString("error", "[Error retrieving child]");
g_clear_error(&error);
continue;
}
CHECK(child);
RecursiveBuildAccessibilityTree(child, child_dict.get());
children->Append(std::move(child_dict));
}
dict->Set(kChildrenDictAttr, std::move(children));
}
void AccessibilityTreeFormatterAuraLinux::AddProperties(
const BrowserAccessibility& node,
base::DictionaryValue* dict) {
dict->SetInteger("id", node.GetId());
BrowserAccessibilityAuraLinux* acc_obj =
ToBrowserAccessibilityAuraLinux(const_cast<BrowserAccessibility*>(&node));
acc_obj->GetNode()->AddAccessibilityTreeProperties(dict);
}
void AccessibilityTreeFormatterAuraLinux::AddProperties(
AtspiAccessible* node,
base::DictionaryValue* dict) {
GError* error = nullptr;
char* role_name = atspi_accessible_get_role_name(node, &error);
if (!error)
dict->SetString("role", role_name);
g_clear_error(&error);
free(role_name);
char* name = atspi_accessible_get_name(node, &error);
if (!error)
dict->SetString("name", name);
g_clear_error(&error);
free(name);
error = nullptr;
char* description = atspi_accessible_get_description(node, &error);
if (!error)
dict->SetString("description", description);
g_clear_error(&error);
free(description);
error = nullptr;
GHashTable* attributes = atspi_accessible_get_attributes(node, &error);
if (!error) {
GHashTableIter i;
void* key = nullptr;
void* value = nullptr;
g_hash_table_iter_init(&i, attributes);
while (g_hash_table_iter_next(&i, &key, &value)) {
dict->SetString(static_cast<char*>(key), static_cast<char*>(value));
}
}
g_clear_error(&error);
g_hash_table_unref(attributes);
AtspiStateSet* atspi_states = atspi_accessible_get_state_set(node);
GArray* state_array = atspi_state_set_get_states(atspi_states);
auto states = std::make_unique<base::ListValue>();
for (unsigned i = 0; i < state_array->len; i++) {
AtspiStateType state_type = g_array_index(state_array, AtspiStateType, i);
states->AppendString(ATSPIStateToString(state_type));
}
dict->Set("states", std::move(states));
g_array_free(state_array, TRUE);
g_object_unref(atspi_states);
}
const char* const ATK_OBJECT_ATTRIBUTES[] = {
"atomic",
"autocomplete",
"busy",
"checkable",
"class",
"colcount",
"colindex",
"container-atomic",
"container-busy",
"container-live",
"container-relevant",
"display",
"explicit-name",
"haspopup",
"id",
"keyshortcuts",
"level",
"live",
"placeholder",
"posinset",
"relevant",
"roledescription",
"rowcount",
"rowindex",
"setsize",
"src",
"table-cell-index",
"tag",
"text-input-type",
"valuemin",
"valuemax",
"valuenow",
"valuetext",
"xml-roles",
};
base::string16 AccessibilityTreeFormatterAuraLinux::ProcessTreeForOutput(
const base::DictionaryValue& node,
base::DictionaryValue* filtered_dict_result) {
base::string16 error_value;
if (node.GetString("error", &error_value))
return error_value;
base::string16 line;
std::string role_value;
node.GetString("role", &role_value);
if (!role_value.empty()) {
WriteAttribute(true, base::StringPrintf("[%s]", role_value.c_str()), &line);
}
std::string name_value;
if (node.GetString("name", &name_value))
WriteAttribute(true, base::StringPrintf("name='%s'", name_value.c_str()),
&line);
std::string description_value;
node.GetString("description", &description_value);
WriteAttribute(
false, base::StringPrintf("description='%s'", description_value.c_str()),
&line);
const base::ListValue* states_value;
node.GetList("states", &states_value);
for (auto it = states_value->begin(); it != states_value->end(); ++it) {
std::string state_value;
if (it->GetAsString(&state_value))
WriteAttribute(false, state_value, &line);
}
for (const char* attribute_name : ATK_OBJECT_ATTRIBUTES) {
std::string attribute_value;
if (node.GetString(attribute_name, &attribute_value)) {
WriteAttribute(
false,
base::StringPrintf("%s:%s", attribute_name, attribute_value.c_str()),
&line);
}
}
return line;
}
const base::FilePath::StringType
AccessibilityTreeFormatterAuraLinux::GetExpectedFileSuffix() {
return FILE_PATH_LITERAL("-expected-auralinux.txt");
}
const std::string AccessibilityTreeFormatterAuraLinux::GetAllowEmptyString() {
return "@AURALINUX-ALLOW-EMPTY:";
}
const std::string AccessibilityTreeFormatterAuraLinux::GetAllowString() {
return "@AURALINUX-ALLOW:";
}
const std::string AccessibilityTreeFormatterAuraLinux::GetDenyString() {
return "@AURALINUX-DENY:";
}
} // namespace content