| // Copyright 2018 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/browser_accessibility_auralinux.h" |
| |
| #include <atk/atk.h> |
| |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "content/browser/accessibility/browser_accessibility_manager.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "content/public/test/scoped_accessibility_mode_override.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/accessibility/platform/ax_platform_node_auralinux.h" |
| #include "ui/accessibility/platform/test_ax_platform_tree_manager_delegate.h" |
| |
| namespace content { |
| |
| class BrowserAccessibilityAuraLinuxTest : public ::testing::Test { |
| public: |
| BrowserAccessibilityAuraLinuxTest(); |
| |
| BrowserAccessibilityAuraLinuxTest(const BrowserAccessibilityAuraLinuxTest&) = |
| delete; |
| BrowserAccessibilityAuraLinuxTest& operator=( |
| const BrowserAccessibilityAuraLinuxTest&) = delete; |
| |
| ~BrowserAccessibilityAuraLinuxTest() override; |
| |
| protected: |
| std::unique_ptr<ui::TestAXPlatformTreeManagerDelegate> |
| test_browser_accessibility_delegate_; |
| |
| private: |
| void SetUp() override; |
| |
| BrowserTaskEnvironment task_environment_; |
| ScopedAccessibilityModeOverride ax_mode_override_; |
| }; |
| |
| BrowserAccessibilityAuraLinuxTest::BrowserAccessibilityAuraLinuxTest() |
| : ax_mode_override_(ui::kAXModeComplete) {} |
| |
| BrowserAccessibilityAuraLinuxTest::~BrowserAccessibilityAuraLinuxTest() = |
| default; |
| |
| void BrowserAccessibilityAuraLinuxTest::SetUp() { |
| test_browser_accessibility_delegate_ = |
| std::make_unique<ui::TestAXPlatformTreeManagerDelegate>(); |
| } |
| |
| TEST_F(BrowserAccessibilityAuraLinuxTest, TestSimpleAtkText) { |
| ui::AXNodeData root_data; |
| root_data.id = 1; |
| root_data.role = ax::mojom::Role::kStaticText; |
| root_data.SetName("\xE2\x98\xBA Multiple Words"); |
| |
| std::unique_ptr<BrowserAccessibilityManager> manager( |
| BrowserAccessibilityManager::Create( |
| MakeAXTreeUpdateForTesting(root_data), |
| test_browser_accessibility_delegate_.get())); |
| |
| ui::AXPlatformNodeAuraLinux* root_obj = |
| ToBrowserAccessibilityAuraLinux(manager->GetBrowserAccessibilityRoot()) |
| ->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); |
| |
| auto verify_atk_text_contents = [&](const char* expected_text, |
| int start_offset, int end_offset) { |
| gchar* text = atk_text_get_text(atk_text, start_offset, end_offset); |
| EXPECT_STREQ(expected_text, text); |
| g_free(text); |
| }; |
| |
| verify_atk_text_contents("\xE2\x98\xBA Multiple Words", 0, -1); |
| verify_atk_text_contents("Multiple Words", 2, -1); |
| verify_atk_text_contents("\xE2\x98\xBA", 0, 1); |
| |
| EXPECT_EQ(16, atk_text_get_character_count(atk_text)); |
| |
| g_object_unref(root_atk_object); |
| |
| manager.reset(); |
| } |
| |
| TEST_F(BrowserAccessibilityAuraLinuxTest, TestCompositeAtkText) { |
| const std::string text1_name = "One two three."; |
| const std::string text2_name = " Four five six."; |
| const int text_name_len = text1_name.length() + text2_name.length(); |
| |
| ui::AXNodeData text1; |
| text1.id = 11; |
| text1.role = ax::mojom::Role::kStaticText; |
| text1.SetName(text1_name); |
| |
| ui::AXNodeData text2; |
| text2.id = 12; |
| text2.role = ax::mojom::Role::kStaticText; |
| text2.SetName(text2_name); |
| |
| ui::AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kRootWebArea; |
| root.child_ids.push_back(text1.id); |
| root.child_ids.push_back(text2.id); |
| |
| std::unique_ptr<BrowserAccessibilityManager> manager( |
| BrowserAccessibilityManager::Create( |
| MakeAXTreeUpdateForTesting(root, text1, text2), |
| test_browser_accessibility_delegate_.get())); |
| |
| ui::AXPlatformNodeAuraLinux* root_obj = |
| ToBrowserAccessibilityAuraLinux(manager->GetBrowserAccessibilityRoot()) |
| ->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(text_name_len, atk_text_get_character_count(atk_text)); |
| |
| gchar* text = atk_text_get_text(atk_text, 0, -1); |
| 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 radio_button_text_name = "Red"; |
| const std::string link_text_name = "Blue"; |
| // Each control (combo / check box, radio button and link) will be represented |
| // by an embedded object character. |
| const std::u16string 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.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, "input"); |
| combo_box.AddStringAttribute(ax::mojom::StringAttribute::kInputType, "text"); |
| 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); |
| // ARIA checkbox where the name is derived from its inner text. |
| check_box.SetNameFrom(ax::mojom::NameFrom::kContents); |
| check_box.SetValue(check_box_value); |
| |
| ui::AXNodeData radio_button, radio_button_text; |
| radio_button.id = 15; |
| radio_button_text.id = 17; |
| radio_button.role = ax::mojom::Role::kRadioButton; |
| radio_button.SetName(radio_button_text_name); |
| radio_button.SetNameFrom(ax::mojom::NameFrom::kContents); |
| // Even though text is being appended as a child the radio button will be |
| // be treated as a leaf because of AXNode::IsLeaf(). |
| radio_button_text.role = ax::mojom::Role::kStaticText; |
| radio_button_text.SetName(radio_button_text_name); |
| radio_button.child_ids.push_back(radio_button_text.id); |
| |
| ui::AXNodeData link, link_text; |
| link.id = 16; |
| link_text.id = 18; |
| link.role = ax::mojom::Role::kLink; |
| link_text.role = ax::mojom::Role::kStaticText; |
| link_text.SetName(link_text_name); |
| link.child_ids.push_back(link_text.id); |
| |
| ui::AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kRootWebArea; |
| root.child_ids = {text1.id, combo_box.id, text2.id, |
| check_box.id, radio_button.id, link.id}; |
| |
| std::unique_ptr<BrowserAccessibilityManager> manager( |
| BrowserAccessibilityManager::Create( |
| MakeAXTreeUpdateForTesting(root, text1, combo_box, text2, check_box, |
| radio_button, radio_button_text, link, |
| link_text), |
| test_browser_accessibility_delegate_.get())); |
| |
| ui::AXPlatformNodeAuraLinux* root_obj = |
| ToBrowserAccessibilityAuraLinux(manager->GetBrowserAccessibilityRoot()) |
| ->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, |
| int expected_start_index) { |
| AtkHyperlink* link = atk_hypertext_get_link(atk_hypertext, link_index); |
| ASSERT_NE(nullptr, link); |
| ASSERT_TRUE(ATK_IS_HYPERLINK(link)); |
| |
| AtkObject* link_object = atk_hyperlink_get_object(link, 0); |
| ASSERT_NE(link_object, nullptr); |
| ASSERT_TRUE(ATK_IS_HYPERLINK_IMPL(link_object)); |
| |
| AtkHyperlink* link_from_object = |
| atk_hyperlink_impl_get_hyperlink(ATK_HYPERLINK_IMPL(link_object)); |
| ASSERT_EQ(link_from_object, link); |
| g_object_unref(link_from_object); |
| |
| ASSERT_EQ(atk_hyperlink_get_start_index(link), expected_start_index); |
| ASSERT_EQ(atk_hyperlink_get_end_index(link), expected_start_index + 1); |
| |
| 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, 14); |
| |
| // Get the text of the check box. It should be its name. |
| verify_atk_link_text(check_box_name.c_str(), 1, 30); |
| |
| // Get the text of the radio button. |
| verify_atk_link_text(radio_button_text_name.c_str(), 2, 31); |
| |
| // Get the text of the link. |
| verify_atk_link_text(link_text_name.c_str(), 3, 32); |
| |
| // 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); |
| |
| text1.SetName(text1_name + text1_name); |
| ui::AXUpdatesAndEvents event_bundle; |
| event_bundle.updates.resize(1); |
| event_bundle.updates[0].nodes.push_back(text1); |
| event_bundle.updates[0].nodes.push_back(root); |
| ASSERT_TRUE(manager->OnAccessibilityEvents(event_bundle)); |
| |
| // The hypertext offsets should reflect the new length of the static text. |
| verify_atk_link_text(combo_box_value.c_str(), 0, 28); |
| verify_atk_link_text(check_box_name.c_str(), 1, 44); |
| verify_atk_link_text(radio_button_text_name.c_str(), 2, 45); |
| verify_atk_link_text(link_text_name.c_str(), 3, 46); |
| |
| manager.reset(); |
| } |
| |
| TEST_F(BrowserAccessibilityAuraLinuxTest, TestTextAttributesInButtons) { |
| ui::AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kRootWebArea; |
| root.AddState(ax::mojom::State::kFocusable); |
| |
| ui::AXNodeData button; |
| button.id = 2; |
| button.role = ax::mojom::Role::kButton; |
| button.AddStringAttribute(ax::mojom::StringAttribute::kFontFamily, "Times"); |
| root.child_ids.push_back(button.id); |
| |
| ui::AXNodeData text; |
| text.id = 3; |
| text.role = ax::mojom::Role::kStaticText; |
| text.SetName("OK"); |
| button.child_ids.push_back(text.id); |
| |
| ui::AXNodeData empty_button; |
| empty_button.id = 4; |
| empty_button.role = ax::mojom::Role::kButton; |
| empty_button.AddStringAttribute(ax::mojom::StringAttribute::kFontFamily, |
| "Times"); |
| root.child_ids.push_back(empty_button.id); |
| |
| ui::AXTreeUpdate update = |
| MakeAXTreeUpdateForTesting(root, button, text, empty_button); |
| std::unique_ptr<BrowserAccessibilityManager> manager( |
| BrowserAccessibilityManager::Create( |
| update, test_browser_accessibility_delegate_.get())); |
| |
| BrowserAccessibilityAuraLinux* ax_root = |
| ToBrowserAccessibilityAuraLinux(manager->GetBrowserAccessibilityRoot()); |
| |
| BrowserAccessibilityAuraLinux* ax_button = |
| ToBrowserAccessibilityAuraLinux(ax_root->PlatformGetChild(0)); |
| AtkObject* atk_button = ax_button->GetNode()->GetNativeViewAccessible(); |
| |
| int start_offset, end_offset; |
| AtkAttributeSet* attributes = atk_text_get_run_attributes( |
| ATK_TEXT(atk_button), 0, &start_offset, &end_offset); |
| ASSERT_EQ(1U, g_slist_length(attributes)); |
| atk_attribute_set_free(attributes); |
| |
| BrowserAccessibilityAuraLinux* ax_empty_button = |
| ToBrowserAccessibilityAuraLinux(ax_root->PlatformGetChild(1)); |
| AtkObject* atk_empty_button = |
| ax_empty_button->GetNode()->GetNativeViewAccessible(); |
| attributes = atk_text_get_run_attributes(ATK_TEXT(atk_empty_button), 0, |
| &start_offset, &end_offset); |
| ASSERT_EQ(1U, g_slist_length(attributes)); |
| atk_attribute_set_free(attributes); |
| } |
| |
| TEST_F(BrowserAccessibilityAuraLinuxTest, |
| TestTextAttributesInContentEditables) { |
| auto has_attribute = [](AtkAttributeSet* attributes, |
| AtkTextAttribute text_attribute, |
| std::optional<const char*> expected_value) { |
| const char* name = atk_text_attribute_get_name(text_attribute); |
| while (attributes) { |
| const AtkAttribute* attribute = |
| static_cast<AtkAttribute*>(attributes->data); |
| if (!g_strcmp0(attribute->name, name)) { |
| if (!expected_value.has_value()) |
| return true; |
| if (!g_strcmp0(attribute->value, *expected_value)) |
| return true; |
| } |
| attributes = g_slist_next(attributes); |
| } |
| return false; |
| }; |
| |
| ui::AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kRootWebArea; |
| root.AddState(ax::mojom::State::kFocusable); |
| |
| ui::AXNodeData div_editable; |
| div_editable.id = 2; |
| div_editable.role = ax::mojom::Role::kGenericContainer; |
| div_editable.AddState(ax::mojom::State::kEditable); |
| div_editable.AddState(ax::mojom::State::kFocusable); |
| div_editable.AddStringAttribute(ax::mojom::StringAttribute::kFontFamily, |
| "Helvetica"); |
| |
| ui::AXNodeData text_before; |
| text_before.id = 3; |
| text_before.role = ax::mojom::Role::kStaticText; |
| text_before.AddState(ax::mojom::State::kEditable); |
| text_before.SetName("Before "); |
| text_before.AddTextStyle(ax::mojom::TextStyle::kBold); |
| text_before.AddTextStyle(ax::mojom::TextStyle::kItalic); |
| |
| ui::AXNodeData link; |
| link.id = 4; |
| link.role = ax::mojom::Role::kLink; |
| link.AddState(ax::mojom::State::kEditable); |
| link.AddState(ax::mojom::State::kFocusable); |
| link.AddState(ax::mojom::State::kLinked); |
| link.SetName("lnk"); |
| link.SetNameFrom(ax::mojom::NameFrom::kContents); |
| link.AddTextStyle(ax::mojom::TextStyle::kUnderline); |
| |
| ui::AXNodeData link_text; |
| link_text.id = 5; |
| link_text.role = ax::mojom::Role::kStaticText; |
| link_text.AddState(ax::mojom::State::kEditable); |
| link_text.AddState(ax::mojom::State::kFocusable); |
| link_text.AddState(ax::mojom::State::kLinked); |
| link_text.SetName("lnk"); |
| link_text.AddTextStyle(ax::mojom::TextStyle::kUnderline); |
| link_text.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "fr"); |
| |
| // The name "lnk" is misspelled. |
| std::vector<int32_t> marker_types{ |
| static_cast<int32_t>(ax::mojom::MarkerType::kSpelling)}; |
| std::vector<int32_t> marker_starts{0}; |
| std::vector<int32_t> marker_ends{3}; |
| link_text.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerTypes, |
| marker_types); |
| link_text.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerStarts, |
| marker_starts); |
| link_text.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerEnds, |
| marker_ends); |
| |
| ui::AXNodeData text_after; |
| text_after.id = 6; |
| text_after.role = ax::mojom::Role::kStaticText; |
| text_after.AddState(ax::mojom::State::kEditable); |
| text_after.SetName(" after."); |
| // Leave text style as normal. |
| |
| root.child_ids.push_back(div_editable.id); |
| div_editable.child_ids = {text_before.id, link.id, text_after.id}; |
| link.child_ids.push_back(link_text.id); |
| |
| ui::AXTreeUpdate update = MakeAXTreeUpdateForTesting( |
| root, div_editable, text_before, link, link_text, text_after); |
| |
| std::unique_ptr<BrowserAccessibilityManager> manager( |
| BrowserAccessibilityManager::Create( |
| update, test_browser_accessibility_delegate_.get())); |
| |
| ASSERT_NE(nullptr, manager->GetBrowserAccessibilityRoot()); |
| BrowserAccessibilityAuraLinux* ax_root = |
| ToBrowserAccessibilityAuraLinux(manager->GetBrowserAccessibilityRoot()); |
| ASSERT_NE(nullptr, ax_root); |
| ASSERT_EQ(1U, ax_root->PlatformChildCount()); |
| |
| BrowserAccessibilityAuraLinux* ax_div = |
| ToBrowserAccessibilityAuraLinux(ax_root->PlatformGetChild(0)); |
| ASSERT_NE(nullptr, ax_div); |
| ASSERT_EQ(3U, ax_div->PlatformChildCount()); |
| |
| BrowserAccessibilityAuraLinux* ax_before = |
| ToBrowserAccessibilityAuraLinux(ax_div->PlatformGetChild(0)); |
| ASSERT_NE(nullptr, ax_before); |
| BrowserAccessibilityAuraLinux* ax_link = |
| ToBrowserAccessibilityAuraLinux(ax_div->PlatformGetChild(1)); |
| ASSERT_NE(nullptr, ax_link); |
| ASSERT_EQ(1U, ax_link->PlatformChildCount()); |
| BrowserAccessibilityAuraLinux* ax_after = |
| ToBrowserAccessibilityAuraLinux(ax_div->PlatformGetChild(2)); |
| ASSERT_NE(nullptr, ax_after); |
| |
| BrowserAccessibilityAuraLinux* ax_link_text = |
| ToBrowserAccessibilityAuraLinux(ax_link->PlatformGetChild(0)); |
| ASSERT_NE(nullptr, ax_link_text); |
| |
| AtkObject* root_atk_object = ax_root->GetNode()->GetNativeViewAccessible(); |
| AtkObject* ax_div_atk_object = ax_div->GetNode()->GetNativeViewAccessible(); |
| |
| ASSERT_EQ(1, atk_text_get_character_count(ATK_TEXT(root_atk_object))); |
| ASSERT_EQ(15, atk_text_get_character_count(ATK_TEXT(ax_div_atk_object))); |
| |
| // Test the style of the root. |
| int start_offset, end_offset; |
| AtkAttributeSet* attributes = atk_text_get_run_attributes( |
| ATK_TEXT(root_atk_object), 0, &start_offset, &end_offset); |
| |
| ASSERT_EQ(0, start_offset); |
| ASSERT_EQ(1, end_offset); |
| |
| ASSERT_TRUE( |
| has_attribute(attributes, ATK_TEXT_ATTR_FAMILY_NAME, "Helvetica")); |
| atk_attribute_set_free(attributes); |
| |
| // Test the style of text_before. |
| for (int offset = 0; offset < 7; ++offset) { |
| start_offset = end_offset = -1; |
| attributes = atk_text_get_run_attributes( |
| ATK_TEXT(ax_div_atk_object), offset, &start_offset, &end_offset); |
| |
| EXPECT_EQ(0, start_offset); |
| EXPECT_EQ(7, end_offset); |
| |
| ASSERT_TRUE( |
| has_attribute(attributes, ATK_TEXT_ATTR_FAMILY_NAME, "Helvetica")); |
| ASSERT_TRUE(has_attribute(attributes, ATK_TEXT_ATTR_WEIGHT, "700")); |
| ASSERT_TRUE(has_attribute(attributes, ATK_TEXT_ATTR_STYLE, "italic")); |
| |
| atk_attribute_set_free(attributes); |
| } |
| |
| // Test the style of the link. |
| AtkObject* ax_link_text_atk_object = |
| ax_link_text->GetNode()->GetNativeViewAccessible(); |
| for (int offset = 0; offset < 3; offset++) { |
| start_offset = end_offset = -1; |
| attributes = atk_text_get_run_attributes( |
| ATK_TEXT(ax_link_text_atk_object), offset, &start_offset, &end_offset); |
| |
| EXPECT_EQ(0, start_offset); |
| EXPECT_EQ(3, end_offset); |
| |
| ASSERT_TRUE( |
| has_attribute(attributes, ATK_TEXT_ATTR_FAMILY_NAME, "Helvetica")); |
| ASSERT_FALSE(has_attribute(attributes, ATK_TEXT_ATTR_WEIGHT, std::nullopt)); |
| ASSERT_FALSE(has_attribute(attributes, ATK_TEXT_ATTR_STYLE, std::nullopt)); |
| ASSERT_TRUE(has_attribute(attributes, ATK_TEXT_ATTR_UNDERLINE, "single")); |
| ASSERT_TRUE(has_attribute(attributes, ATK_TEXT_ATTR_LANGUAGE, "fr")); |
| |
| // For compatibility with Firefox, spelling attributes should also be |
| // propagated to the parent of static text leaves. |
| ASSERT_TRUE(has_attribute(attributes, ATK_TEXT_ATTR_UNDERLINE, "error")); |
| ASSERT_TRUE(has_attribute(attributes, ATK_TEXT_ATTR_INVALID, "spelling")); |
| |
| atk_attribute_set_free(attributes); |
| } |
| |
| // Test the style of text_after. |
| for (int offset = 8; offset < 15; ++offset) { |
| start_offset = end_offset = -1; |
| attributes = atk_text_get_run_attributes( |
| ATK_TEXT(ax_div_atk_object), offset, &start_offset, &end_offset); |
| EXPECT_EQ(8, start_offset); |
| EXPECT_EQ(15, end_offset); |
| |
| ASSERT_TRUE( |
| has_attribute(attributes, ATK_TEXT_ATTR_FAMILY_NAME, "Helvetica")); |
| ASSERT_FALSE(has_attribute(attributes, ATK_TEXT_ATTR_WEIGHT, std::nullopt)); |
| ASSERT_FALSE(has_attribute(attributes, ATK_TEXT_ATTR_STYLE, std::nullopt)); |
| ASSERT_FALSE( |
| has_attribute(attributes, ATK_TEXT_ATTR_UNDERLINE, std::nullopt)); |
| ASSERT_FALSE( |
| has_attribute(attributes, ATK_TEXT_ATTR_INVALID, std::nullopt)); |
| |
| atk_attribute_set_free(attributes); |
| } |
| |
| // Test the style of the static text nodes. |
| AtkObject* ax_before_atk_object = |
| ax_before->GetNode()->GetNativeViewAccessible(); |
| start_offset = end_offset = -1; |
| attributes = atk_text_get_run_attributes(ATK_TEXT(ax_before_atk_object), 6, |
| &start_offset, &end_offset); |
| EXPECT_EQ(0, start_offset); |
| EXPECT_EQ(7, end_offset); |
| ASSERT_TRUE( |
| has_attribute(attributes, ATK_TEXT_ATTR_FAMILY_NAME, "Helvetica")); |
| ASSERT_TRUE(has_attribute(attributes, ATK_TEXT_ATTR_WEIGHT, "700")); |
| ASSERT_TRUE(has_attribute(attributes, ATK_TEXT_ATTR_STYLE, "italic")); |
| ASSERT_FALSE( |
| has_attribute(attributes, ATK_TEXT_ATTR_UNDERLINE, std::nullopt)); |
| ASSERT_FALSE(has_attribute(attributes, ATK_TEXT_ATTR_INVALID, std::nullopt)); |
| atk_attribute_set_free(attributes); |
| |
| AtkObject* ax_after_atk_object = |
| ax_after->GetNode()->GetNativeViewAccessible(); |
| attributes = atk_text_get_run_attributes(ATK_TEXT(ax_after_atk_object), 6, |
| &start_offset, &end_offset); |
| EXPECT_EQ(0, start_offset); |
| EXPECT_EQ(7, end_offset); |
| ASSERT_TRUE( |
| has_attribute(attributes, ATK_TEXT_ATTR_FAMILY_NAME, "Helvetica")); |
| ASSERT_FALSE(has_attribute(attributes, ATK_TEXT_ATTR_WEIGHT, std::nullopt)); |
| ASSERT_FALSE(has_attribute(attributes, ATK_TEXT_ATTR_STYLE, "italic")); |
| ASSERT_FALSE( |
| has_attribute(attributes, ATK_TEXT_ATTR_UNDERLINE, std::nullopt)); |
| atk_attribute_set_free(attributes); |
| |
| manager.reset(); |
| } |
| |
| TEST_F(BrowserAccessibilityAuraLinuxTest, |
| TestAtkObjectsNotDeletedDuringTreeUpdate) { |
| ui::AXNodeData container; |
| container.id = 4; |
| container.role = ax::mojom::Role::kGenericContainer; |
| |
| ui::AXNodeData combo_box; |
| combo_box.id = 6; |
| combo_box.role = ax::mojom::Role::kComboBoxSelect; |
| combo_box.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, "select"); |
| combo_box.AddState(ax::mojom::State::kCollapsed); |
| combo_box.SetValue("1"); |
| container.child_ids.push_back(combo_box.id); |
| |
| ui::AXNodeData menu_list; |
| menu_list.id = 8; |
| menu_list.role = ax::mojom::Role::kMenuListPopup; |
| menu_list.AddState(ax::mojom::State::kInvisible); |
| combo_box.child_ids.push_back(menu_list.id); |
| |
| ui::AXNodeData menu_option_1; |
| menu_option_1.id = 9; |
| menu_option_1.role = ax::mojom::Role::kMenuListOption; |
| menu_option_1.SetName("1"); |
| menu_list.child_ids.push_back(menu_option_1.id); |
| |
| ui::AXNodeData menu_option_2; |
| menu_option_2.id = 10; |
| menu_option_2.role = ax::mojom::Role::kMenuListOption; |
| menu_option_2.SetName("2"); |
| menu_option_2.AddState(ax::mojom::State::kInvisible); |
| menu_list.child_ids.push_back(menu_option_2.id); |
| |
| ui::AXNodeData root; |
| root.id = 2; |
| root.role = ax::mojom::Role::kRootWebArea; |
| root.child_ids.push_back(container.id); |
| |
| std::unique_ptr<BrowserAccessibilityManager> manager( |
| BrowserAccessibilityManager::Create( |
| MakeAXTreeUpdateForTesting(root, container, combo_box, menu_list, |
| menu_option_1, menu_option_2), |
| test_browser_accessibility_delegate_.get())); |
| |
| ui::AXPlatformNodeAuraLinux* combo_box_node = |
| ToBrowserAccessibilityAuraLinux(manager->GetFromID(combo_box.id)) |
| ->GetNode(); |
| ASSERT_FALSE(combo_box_node->IsChildOfLeaf()); |
| AtkObject* original_atk_object = combo_box_node->GetNativeViewAccessible(); |
| g_object_ref(original_atk_object); |
| |
| // The interface mask is only dependent on IsChildOfLeaf. Create an update |
| // which won't modify this. |
| container.SetName("container"); |
| ui::AXTree* tree = const_cast<ui::AXTree*>(manager->ax_tree()); |
| ui::AXTreeUpdate container_update = MakeAXTreeUpdateForTesting(container); |
| container_update.tree_data.tree_id = tree->GetAXTreeID(); |
| ASSERT_TRUE(tree->Unserialize(container_update)); |
| ASSERT_FALSE(combo_box_node->IsChildOfLeaf()); |
| ASSERT_EQ(original_atk_object, combo_box_node->GetNativeViewAccessible()); |
| |
| g_object_unref(original_atk_object); |
| manager.reset(); |
| } |
| |
| TEST_F(BrowserAccessibilityAuraLinuxTest, |
| TestExistingMisspellingsInSimpleTextFields) { |
| std::string value1("Testing ."); |
| // The word "helo" is misspelled. |
| std::string value2("Helo there."); |
| |
| int value1_length = value1.length(); |
| int value2_length = value2.length(); |
| int combo_box_value_length = value1_length + value2_length; |
| |
| ui::AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kRootWebArea; |
| root.AddState(ax::mojom::State::kFocusable); |
| |
| ui::AXNodeData combo_box; |
| combo_box.id = 2; |
| combo_box.role = ax::mojom::Role::kTextFieldWithComboBox; |
| combo_box.AddState(ax::mojom::State::kEditable); |
| combo_box.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, "input"); |
| combo_box.AddStringAttribute(ax::mojom::StringAttribute::kInputType, "text"); |
| combo_box.AddState(ax::mojom::State::kFocusable); |
| combo_box.SetValue(value1 + value2); |
| |
| ui::AXNodeData combo_box_div; |
| combo_box_div.id = 3; |
| combo_box_div.role = ax::mojom::Role::kGenericContainer; |
| combo_box_div.AddState(ax::mojom::State::kEditable); |
| |
| ui::AXNodeData static_text1; |
| static_text1.id = 4; |
| static_text1.role = ax::mojom::Role::kStaticText; |
| static_text1.AddState(ax::mojom::State::kEditable); |
| static_text1.SetName(value1); |
| |
| ui::AXNodeData static_text2; |
| static_text2.id = 5; |
| static_text2.role = ax::mojom::Role::kStaticText; |
| static_text2.AddState(ax::mojom::State::kEditable); |
| static_text2.SetName(value2); |
| |
| std::vector<int32_t> marker_types; |
| marker_types.push_back( |
| static_cast<int32_t>(ax::mojom::MarkerType::kSpelling)); |
| std::vector<int32_t> marker_starts; |
| marker_starts.push_back(0); |
| std::vector<int32_t> marker_ends; |
| marker_ends.push_back(4); |
| static_text2.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerTypes, |
| marker_types); |
| static_text2.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerStarts, |
| marker_starts); |
| static_text2.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerEnds, |
| marker_ends); |
| |
| root.child_ids.push_back(combo_box.id); |
| combo_box.child_ids.push_back(combo_box_div.id); |
| combo_box_div.child_ids.push_back(static_text1.id); |
| combo_box_div.child_ids.push_back(static_text2.id); |
| |
| std::unique_ptr<BrowserAccessibilityManager> manager( |
| BrowserAccessibilityManager::Create( |
| MakeAXTreeUpdateForTesting(root, combo_box, combo_box_div, |
| static_text1, static_text2), |
| test_browser_accessibility_delegate_.get())); |
| |
| ASSERT_NE(nullptr, manager->GetBrowserAccessibilityRoot()); |
| BrowserAccessibilityAuraLinux* ax_root = |
| ToBrowserAccessibilityAuraLinux(manager->GetBrowserAccessibilityRoot()); |
| ASSERT_NE(nullptr, ax_root); |
| ASSERT_EQ(1U, ax_root->PlatformChildCount()); |
| |
| BrowserAccessibilityAuraLinux* ax_combo_box = |
| ToBrowserAccessibilityAuraLinux(ax_root->PlatformGetChild(0)); |
| ASSERT_NE(nullptr, ax_combo_box); |
| |
| AtkText* combo_box_text = |
| ATK_TEXT(ax_combo_box->GetNode()->GetNativeViewAccessible()); |
| int start_offset, end_offset; |
| |
| auto contains_spelling_attribute = [](AtkAttributeSet* attributes) { |
| const char* invalid_str = |
| atk_text_attribute_get_name(ATK_TEXT_ATTR_INVALID); |
| while (attributes) { |
| AtkAttribute* attribute = static_cast<AtkAttribute*>(attributes->data); |
| if (!g_strcmp0(attribute->name, invalid_str) && |
| !g_strcmp0(attribute->value, "spelling")) |
| return true; |
| attributes = g_slist_next(attributes); |
| } |
| |
| return false; |
| }; |
| |
| // Ensure that the first part of the value is not marked misspelled. |
| for (int offset = 0; offset < value1_length; ++offset) { |
| AtkAttributeSet* attributes = atk_text_get_run_attributes( |
| combo_box_text, offset, &start_offset, &end_offset); |
| EXPECT_FALSE(contains_spelling_attribute(attributes)); |
| EXPECT_EQ(0, start_offset); |
| EXPECT_EQ(value1_length, end_offset); |
| atk_attribute_set_free(attributes); |
| } |
| |
| // Ensure that "helo" is marked misspelled. |
| for (int offset = value1_length; offset < value1_length + 4; ++offset) { |
| AtkAttributeSet* attributes = atk_text_get_run_attributes( |
| combo_box_text, offset, &start_offset, &end_offset); |
| EXPECT_EQ(value1_length, start_offset); |
| EXPECT_EQ(value1_length + 4, end_offset); |
| EXPECT_TRUE(contains_spelling_attribute(attributes)); |
| atk_attribute_set_free(attributes); |
| } |
| |
| // Ensure that the last part of the value is not marked misspelled. |
| for (int offset = value1_length + 4; offset < combo_box_value_length; |
| ++offset) { |
| AtkAttributeSet* attributes = atk_text_get_run_attributes( |
| combo_box_text, offset, &start_offset, &end_offset); |
| EXPECT_FALSE(contains_spelling_attribute(attributes)); |
| EXPECT_EQ(value1_length + 4, start_offset); |
| EXPECT_EQ(combo_box_value_length, end_offset); |
| atk_attribute_set_free(attributes); |
| } |
| |
| manager.reset(); |
| } |
| |
| TEST_F(BrowserAccessibilityAuraLinuxTest, TextAtkStaticTextChange) { |
| ui::AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kRootWebArea; |
| root.AddState(ax::mojom::State::kFocusable); |
| |
| ui::AXNodeData div_editable; |
| div_editable.id = 2; |
| div_editable.role = ax::mojom::Role::kGenericContainer; |
| |
| ui::AXNodeData text; |
| text.id = 3; |
| text.role = ax::mojom::Role::kStaticText; |
| text.SetName("Text1 "); |
| |
| root.child_ids.push_back(div_editable.id); |
| div_editable.child_ids.push_back(text.id); |
| |
| std::unique_ptr<BrowserAccessibilityManager> manager( |
| BrowserAccessibilityManager::Create( |
| MakeAXTreeUpdateForTesting(root, div_editable, text), |
| test_browser_accessibility_delegate_.get())); |
| |
| text.SetName("Text2"); |
| ui::AXTree* tree = const_cast<ui::AXTree*>(manager->ax_tree()); |
| ui::AXTreeUpdate text_update = MakeAXTreeUpdateForTesting(text); |
| text_update.tree_data.tree_id = manager->GetTreeID(); |
| ASSERT_TRUE(tree->Unserialize(text_update)); |
| |
| // The change to the static text node should have triggered an update of the |
| // containing div's hypertext. |
| ui::AXPlatformNodeAuraLinux* div_node = |
| ToBrowserAccessibilityAuraLinux(manager->GetFromID(div_editable.id)) |
| ->GetNode(); |
| EXPECT_STREQ(base::UTF16ToUTF8(div_node->GetHypertext()).c_str(), "Text2"); |
| } |
| |
| TEST_F(BrowserAccessibilityAuraLinuxTest, TestAtkTextGetOffesetAtPoint) { |
| ui::AXNodeData static_text1; |
| static_text1.id = 1; |
| static_text1.role = ax::mojom::Role::kStaticText; |
| static_text1.SetName("Hello"); |
| static_text1.child_ids = {2}; |
| |
| ui::AXNodeData inline_box1; |
| inline_box1.id = 2; |
| inline_box1.role = ax::mojom::Role::kInlineTextBox; |
| inline_box1.SetName("Hello"); |
| inline_box1.relative_bounds.bounds = gfx::RectF(0, 50, 25, 30); |
| std::vector<int32_t> character_offsets1; |
| // The width of each character is 5px. |
| character_offsets1.push_back(5); |
| character_offsets1.push_back(10); |
| character_offsets1.push_back(15); |
| character_offsets1.push_back(20); |
| character_offsets1.push_back(25); |
| inline_box1.AddIntListAttribute( |
| ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets1); |
| inline_box1.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts, |
| std::vector<int32_t>{0}); |
| inline_box1.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds, |
| std::vector<int32_t>{5}); |
| |
| std::unique_ptr<BrowserAccessibilityManager> manager( |
| BrowserAccessibilityManager::Create( |
| MakeAXTreeUpdateForTesting(static_text1, inline_box1), |
| test_browser_accessibility_delegate_.get())); |
| |
| ASSERT_NE(nullptr, manager->GetBrowserAccessibilityRoot()); |
| BrowserAccessibilityAuraLinux* ax_root = |
| ToBrowserAccessibilityAuraLinux(manager->GetBrowserAccessibilityRoot()); |
| ASSERT_NE(nullptr, ax_root); |
| |
| AtkObject* root_atk_object = ax_root->GetNode()->GetNativeViewAccessible(); |
| g_object_ref(root_atk_object); |
| AtkText* atk_text = ATK_TEXT(root_atk_object); |
| ASSERT_TRUE(ATK_IS_TEXT(atk_text)); |
| |
| int x, y, width, height; |
| char* text = atk_text_get_text(atk_text, 0, -1); |
| int root_text_length = g_utf8_strlen(text, -1); |
| g_free(text); |
| for (int offset = 0; offset < root_text_length; offset++) { |
| atk_text_get_character_extents(atk_text, offset, &x, &y, &width, &height, |
| ATK_XY_SCREEN); |
| int result = atk_text_get_offset_at_point(atk_text, x, y, ATK_XY_SCREEN); |
| ASSERT_EQ(offset, result); |
| } |
| g_object_unref(root_atk_object); |
| manager.reset(); |
| } |
| |
| } // namespace content |