Commit d8b9f643 authored by Martin Robinson's avatar Martin Robinson Committed by Commit Bot

Implement the AtkHypertext interface for Aura Linux

This allows accessibility clients to have access to the links available
in the text provided by the AtkText interface. Implementing this
interface also allows us to write a fully-fleshed out text for the
contents provided by the AtkText interface.

Bug: 866337
Change-Id: I38e200512a835014922b51df677203e853d6ef4b
Reviewed-on: https://chromium-review.googlesource.com/1227942
Commit-Queue: Martin Robinson <mrobinson@igalia.com>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#592096}
parent e8327c07
......@@ -113,6 +113,157 @@ TEST_F(BrowserAccessibilityAuraLinuxTest, TestCompositeAtkText) {
EXPECT_STREQ((text1_name + text2_name).c_str(), text);
g_free(text);
ASSERT_TRUE(ATK_IS_HYPERTEXT(root_atk_object));
AtkHypertext* atk_hypertext = ATK_HYPERTEXT(root_atk_object);
// There should be no hyperlinks in the node and trying to get one should
// always return -1.
EXPECT_EQ(0, atk_hypertext_get_n_links(atk_hypertext));
EXPECT_EQ(-1, atk_hypertext_get_link_index(atk_hypertext, 0));
EXPECT_EQ(-1, atk_hypertext_get_link_index(atk_hypertext, -1));
EXPECT_EQ(-1, atk_hypertext_get_link_index(atk_hypertext, 1));
g_object_unref(root_atk_object);
manager.reset();
}
TEST_F(BrowserAccessibilityAuraLinuxTest, TestComplexHypertext) {
const std::string text1_name = "One two three.";
const std::string combo_box_name = "City:";
const std::string combo_box_value = "Happyland";
const std::string text2_name = " Four five six.";
const std::string check_box_name = "I agree";
const std::string check_box_value = "Checked";
const std::string button_text_name = "Red";
const std::string link_text_name = "Blue";
// Each control (combo / check box, button and link) will be represented by an
// embedded object character.
const base::string16 string16_embed(
1, ui::AXPlatformNodeAuraLinux::kEmbeddedCharacter);
const std::string embed = base::UTF16ToUTF8(string16_embed);
const std::string root_hypertext =
text1_name + embed + text2_name + embed + embed + embed;
ui::AXNodeData text1;
text1.id = 11;
text1.role = ax::mojom::Role::kStaticText;
text1.SetName(text1_name);
ui::AXNodeData combo_box;
combo_box.id = 12;
combo_box.role = ax::mojom::Role::kTextFieldWithComboBox;
combo_box.AddState(ax::mojom::State::kEditable);
combo_box.SetName(combo_box_name);
combo_box.SetValue(combo_box_value);
ui::AXNodeData text2;
text2.id = 13;
text2.role = ax::mojom::Role::kStaticText;
text2.SetName(text2_name);
ui::AXNodeData check_box;
check_box.id = 14;
check_box.role = ax::mojom::Role::kCheckBox;
check_box.SetCheckedState(ax::mojom::CheckedState::kTrue);
check_box.SetName(check_box_name);
check_box.SetValue(check_box_value);
ui::AXNodeData button, button_text;
button.id = 15;
button_text.id = 17;
button_text.SetName(button_text_name);
button.role = ax::mojom::Role::kButton;
button_text.role = ax::mojom::Role::kStaticText;
button.child_ids.push_back(button_text.id);
ui::AXNodeData link, link_text;
link.id = 16;
link_text.id = 18;
link_text.SetName(link_text_name);
link.role = ax::mojom::Role::kLink;
link_text.role = ax::mojom::Role::kStaticText;
link.child_ids.push_back(link_text.id);
ui::AXNodeData root;
root.id = 1;
root.role = ax::mojom::Role::kRootWebArea;
root.child_ids.push_back(text1.id);
root.child_ids.push_back(combo_box.id);
root.child_ids.push_back(text2.id);
root.child_ids.push_back(check_box.id);
root.child_ids.push_back(button.id);
root.child_ids.push_back(link.id);
std::unique_ptr<BrowserAccessibilityManager> manager(
BrowserAccessibilityManager::Create(
MakeAXTreeUpdate(root, text1, combo_box, text2, check_box, button,
button_text, link, link_text),
nullptr, new BrowserAccessibilityFactory()));
ui::AXPlatformNodeAuraLinux* root_obj =
ToBrowserAccessibilityAuraLinux(manager->GetRoot())->GetNode();
AtkObject* root_atk_object(root_obj->GetNativeViewAccessible());
ASSERT_TRUE(ATK_IS_OBJECT(root_atk_object));
ASSERT_TRUE(ATK_IS_TEXT(root_atk_object));
g_object_ref(root_atk_object);
AtkText* atk_text = ATK_TEXT(root_atk_object);
EXPECT_EQ(g_utf8_strlen(root_hypertext.c_str(), -1),
atk_text_get_character_count(atk_text));
gchar* text = atk_text_get_text(atk_text, 0, -1);
EXPECT_STREQ(root_hypertext.c_str(), text);
g_free(text);
ASSERT_TRUE(ATK_IS_HYPERTEXT(root_atk_object));
AtkHypertext* atk_hypertext = ATK_HYPERTEXT(root_atk_object);
EXPECT_EQ(4, atk_hypertext_get_n_links(atk_hypertext));
auto verify_atk_link_text = [&](const char* expected_text, int link_index) {
AtkHyperlink* link = atk_hypertext_get_link(atk_hypertext, link_index);
ASSERT_NE(nullptr, link);
ASSERT_TRUE(ATK_IS_HYPERLINK(link));
AtkObject* object = atk_hyperlink_get_object(link, 0);
ASSERT_TRUE(ATK_IS_TEXT(object));
char* text = atk_text_get_text(ATK_TEXT(object), 0, -1);
EXPECT_STREQ(expected_text, text);
g_free(text);
};
AtkHyperlink* combo_box_link = atk_hypertext_get_link(atk_hypertext, 0);
ASSERT_NE(nullptr, combo_box_link);
ASSERT_TRUE(ATK_IS_HYPERLINK(combo_box_link));
// Get the text of the combo box. It should be its value.
verify_atk_link_text(combo_box_value.c_str(), 0);
// Get the text of the check box. It should be its name.
verify_atk_link_text(check_box_name.c_str(), 1);
// Get the text of the button.
verify_atk_link_text(button_text_name.c_str(), 2);
// Get the text of the link.
verify_atk_link_text(link_text_name.c_str(), 3);
// Now test that all the object indices map back to the correct link indices.
EXPECT_EQ(-1, atk_hypertext_get_link_index(atk_hypertext, -1));
EXPECT_EQ(-1, atk_hypertext_get_link_index(atk_hypertext, 0));
EXPECT_EQ(-1, atk_hypertext_get_link_index(atk_hypertext, 1));
EXPECT_EQ(-1, atk_hypertext_get_link_index(atk_hypertext, 5));
EXPECT_EQ(-1, atk_hypertext_get_link_index(atk_hypertext, 13));
EXPECT_EQ(0, atk_hypertext_get_link_index(atk_hypertext, 14));
EXPECT_EQ(1, atk_hypertext_get_link_index(atk_hypertext, 30));
EXPECT_EQ(2, atk_hypertext_get_link_index(atk_hypertext, 31));
EXPECT_EQ(3, atk_hypertext_get_link_index(atk_hypertext, 32));
EXPECT_EQ(-1, atk_hypertext_get_link_index(atk_hypertext, 33));
EXPECT_EQ(-1, atk_hypertext_get_link_index(atk_hypertext, 34));
g_object_unref(root_atk_object);
manager.reset();
......
......@@ -754,6 +754,59 @@ static const GInterfaceInfo HyperlinkImplInfo = {
reinterpret_cast<GInterfaceInitFunc>(ax_hyperlink_impl_interface_base_init),
nullptr, nullptr};
//
// AtkHypertext interface.
//
static AtkHyperlink* ax_platform_node_auralinux_hypertext_get_link(
AtkHypertext* hypertext,
int index) {
g_return_val_if_fail(ATK_HYPERTEXT(hypertext), 0);
auto* obj = AtkObjectToAXPlatformNodeAuraLinux(ATK_OBJECT(hypertext));
if (!obj)
return nullptr;
const ui::AXHypertext& ax_hypertext = obj->GetHypertext();
if (index > static_cast<int>(ax_hypertext.hyperlinks.size()) || index < 0)
return nullptr;
int32_t id = ax_hypertext.hyperlinks[index];
auto* link = ui::AXPlatformNodeAuraLinux::GetFromUniqueId(id);
if (!link)
return nullptr;
return link->GetAtkHyperlink();
}
static int ax_platform_node_auralinux_get_n_links(AtkHypertext* hypertext) {
g_return_val_if_fail(ATK_HYPERTEXT(hypertext), 0);
ui::AXPlatformNodeAuraLinux* obj =
AtkObjectToAXPlatformNodeAuraLinux(ATK_OBJECT(hypertext));
return obj ? obj->GetHypertext().hyperlinks.size() : 0;
}
static int ax_platform_node_auralinux_get_link_index(AtkHypertext* hypertext,
int char_index) {
g_return_val_if_fail(ATK_HYPERTEXT(hypertext), 0);
ui::AXPlatformNodeAuraLinux* obj =
AtkObjectToAXPlatformNodeAuraLinux(ATK_OBJECT(hypertext));
auto it = obj->GetHypertext().hyperlink_offset_to_index.find(char_index);
if (it == obj->GetHypertext().hyperlink_offset_to_index.end())
return -1;
return it->second;
}
void ax_hypertext_interface_base_init(AtkHypertextIface* iface) {
iface->get_link = ax_platform_node_auralinux_hypertext_get_link;
iface->get_n_links = ax_platform_node_auralinux_get_n_links;
iface->get_link_index = ax_platform_node_auralinux_get_link_index;
}
static const GInterfaceInfo HypertextInfo = {
reinterpret_cast<GInterfaceInitFunc>(ax_hypertext_interface_base_init),
nullptr, nullptr};
//
// AtkText interface.
//
......@@ -932,6 +985,9 @@ int AXPlatformNodeAuraLinux::GetGTypeInterfaceMask() {
// as well.
interface_mask |= 1 << ATK_TEXT_INTERFACE;
if (!IsPlainTextField() && !IsChildOfLeaf())
interface_mask |= 1 << ATK_HYPERTEXT_INTERFACE;
// Value Interface
AtkRole role = GetAtkRole();
if (IsRoleWithValueInterface(role)) {
......@@ -987,6 +1043,8 @@ GType AXPlatformNodeAuraLinux::GetAccessibilityGType() {
if (interface_mask_ & (1 << ATK_HYPERLINK_INTERFACE))
g_type_add_interface_static(type, ATK_TYPE_HYPERLINK_IMPL,
&HyperlinkImplInfo);
if (interface_mask_ & (1 << ATK_HYPERTEXT_INTERFACE))
g_type_add_interface_static(type, ATK_TYPE_HYPERTEXT, &HypertextInfo);
if (interface_mask_ & (1 << ATK_TEXT_INTERFACE))
g_type_add_interface_static(type, ATK_TYPE_TEXT, &TextInfo);
......@@ -1033,6 +1091,22 @@ AXPlatformNode* AXPlatformNode::FromNativeViewAccessible(
return AtkObjectToAXPlatformNodeAuraLinux(accessible);
}
using UniqueIdMap = base::hash_map<int32_t, AXPlatformNodeAuraLinux*>;
// Map from each AXPlatformNode's unique id to its instance.
base::LazyInstance<UniqueIdMap>::Leaky g_unique_id_map =
LAZY_INSTANCE_INITIALIZER;
// static
AXPlatformNodeAuraLinux* AXPlatformNodeAuraLinux::GetFromUniqueId(
int32_t unique_id) {
UniqueIdMap* unique_ids = g_unique_id_map.Pointer();
auto iter = unique_ids->find(unique_id);
if (iter != unique_ids->end())
return iter->second;
return nullptr;
}
//
// AXPlatformNodeAuraLinux implementation.
//
......@@ -1533,6 +1607,8 @@ AXPlatformNodeAuraLinux::~AXPlatformNodeAuraLinux() {
}
void AXPlatformNodeAuraLinux::Destroy() {
g_unique_id_map.Get().erase(GetUniqueId());
DestroyAtkObjects();
AXPlatformNodeBase::Destroy();
}
......@@ -1540,6 +1616,7 @@ void AXPlatformNodeAuraLinux::Destroy() {
void AXPlatformNodeAuraLinux::Init(AXPlatformNodeDelegate* delegate) {
// Initialize ATK.
AXPlatformNodeBase::Init(delegate);
g_unique_id_map.Get()[GetUniqueId()] = this;
DataChanged();
}
......@@ -1724,6 +1801,10 @@ void AXPlatformNodeAuraLinux::UpdateHypertext() {
hypertext_ = ComputeHypertext();
}
const AXHypertext& AXPlatformNodeAuraLinux::GetHypertext() {
return hypertext_;
}
int AXPlatformNodeAuraLinux::GetIndexInParent() {
if (!GetParent())
return -1;
......@@ -1876,9 +1957,6 @@ AtkAttributeSet* AXPlatformNodeAuraLinux::GetDocumentAttributes() const {
//
AtkHyperlink* AXPlatformNodeAuraLinux::GetAtkHyperlink() {
DCHECK(ATK_HYPERLINK_IMPL(atk_object_));
g_return_val_if_fail(ATK_HYPERLINK_IMPL(atk_object_), 0);
if (!atk_hyperlink_) {
atk_hyperlink_ =
ATK_HYPERLINK(g_object_new(AX_PLATFORM_ATK_HYPERLINK_TYPE, 0));
......
......@@ -65,6 +65,8 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
gint* x, gint* y, gint* width, gint* height,
AtkCoordType coord_type);
static AXPlatformNodeAuraLinux* GetFromUniqueId(int32_t unique_id);
// AtkDocument helpers
const gchar* GetDocumentAttributeValue(const gchar* attribute) const;
AtkAttributeSet* GetDocumentAttributes() const;
......@@ -95,6 +97,7 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
std::string GetTextForATK();
void UpdateHypertext();
const AXHypertext& GetHypertext();
protected:
AXHypertext hypertext_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment