summaryrefslogtreecommitdiff
path: root/chromium/content/browser/accessibility
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/content/browser/accessibility')
-rw-r--r--chromium/content/browser/accessibility/accessibility_auralinux_browsertest.cc322
-rw-r--r--chromium/content/browser/accessibility/accessibility_event_recorder_uia_win.cc11
-rw-r--r--chromium/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc2
-rw-r--r--chromium/content/browser/accessibility/accessibility_tree_formatter_base.cc265
-rw-r--r--chromium/content/browser/accessibility/accessibility_tree_formatter_base.h71
-rw-r--r--chromium/content/browser/accessibility/accessibility_tree_formatter_base_unittest.cc100
-rw-r--r--chromium/content/browser/accessibility/accessibility_tree_formatter_blink.cc2
-rw-r--r--chromium/content/browser/accessibility/accessibility_tree_formatter_mac.mm693
-rw-r--r--chromium/content/browser/accessibility/accessibility_tree_formatter_mac_browsertest.mm293
-rw-r--r--chromium/content/browser/accessibility/accessibility_win_browsertest.cc664
-rw-r--r--chromium/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc55
-rw-r--r--chromium/content/browser/accessibility/ax_platform_node_win_browsertest.cc95
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility.cc242
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility.h59
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_android.cc128
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_android.h6
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_android_unittest.cc283
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_auralinux_unittest.cc15
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_cocoa.h17
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_cocoa.mm113
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_com_win.cc49
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_com_win.h3
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_mac_unittest.mm2
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_manager.cc40
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_manager.h20
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_manager_android.cc87
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_manager_android.h3
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_manager_mac.mm13
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_manager_unittest.cc44
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_manager_win.cc50
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_manager_win.h8
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_state_impl.cc10
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_state_impl_mac.mm5
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_unittest.cc36
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_win.cc13
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_win.h6
-rw-r--r--chromium/content/browser/accessibility/browser_accessibility_win_unittest.cc49
-rw-r--r--chromium/content/browser/accessibility/dump_accessibility_browsertest_base.cc27
-rw-r--r--chromium/content/browser/accessibility/dump_accessibility_browsertest_base.h2
-rw-r--r--chromium/content/browser/accessibility/dump_accessibility_events_browsertest.cc5
-rw-r--r--chromium/content/browser/accessibility/dump_accessibility_tree_browsertest.cc39
-rw-r--r--chromium/content/browser/accessibility/fullscreen_browsertest.cc3
-rw-r--r--chromium/content/browser/accessibility/hit_testing_browsertest.cc71
-rw-r--r--chromium/content/browser/accessibility/hit_testing_browsertest.h8
-rw-r--r--chromium/content/browser/accessibility/hit_testing_mac_browsertest.mm82
-rw-r--r--chromium/content/browser/accessibility/one_shot_accessibility_tree_search_unittest.cc12
-rw-r--r--chromium/content/browser/accessibility/site_per_process_accessibility_browsertest.cc17
-rw-r--r--chromium/content/browser/accessibility/snapshot_ax_tree_browsertest.cc98
-rw-r--r--chromium/content/browser/accessibility/test_browser_accessibility_delegate.cc7
-rw-r--r--chromium/content/browser/accessibility/test_browser_accessibility_delegate.h6
-rw-r--r--chromium/content/browser/accessibility/web_contents_accessibility_android.cc46
51 files changed, 3542 insertions, 755 deletions
diff --git a/chromium/content/browser/accessibility/accessibility_auralinux_browsertest.cc b/chromium/content/browser/accessibility/accessibility_auralinux_browsertest.cc
index 4b64570478c..71a757df88e 100644
--- a/chromium/content/browser/accessibility/accessibility_auralinux_browsertest.cc
+++ b/chromium/content/browser/accessibility/accessibility_auralinux_browsertest.cc
@@ -60,6 +60,8 @@ class AccessibilityAuraLinuxBrowserTest : public AccessibilityBrowserTest {
return false;
}
+ // Ensures that the text and the start and end offsets retrieved using
+ // get_textAtOffset match the expected values.
static void CheckTextAtOffset(AtkText* text_object,
int offset,
AtkTextBoundary boundary_type,
@@ -67,54 +69,69 @@ class AccessibilityAuraLinuxBrowserTest : public AccessibilityBrowserTest {
int expected_end_offset,
const char* expected_text);
+ // Loads a page with an input text field and places sample text in it.
+ // Returns a pointer to the field's AtkText interface.
AtkText* SetUpInputField();
+
+ // Loads a page with a textarea text field, places sample text in it, and
+ // places the caret after the last character.
+ // Returns a pointer to the field's AtkText interface.
AtkText* SetUpTextareaField();
+
+ // Loads a page with a paragraph of sample text and returns its AtkText
+ // interface.
AtkText* SetUpSampleParagraph();
- AtkText* SetUpSampleParagraphInScrollableDocument();
+ // Retrieves a pointer to the already loaded paragraph's AtkText interface.
AtkText* GetSampleParagraph();
- AtkText* GetAtkTextForChild(AtkRole expected_role);
+
+ // Searches the accessibility tree in pre-order debth-first traversal for a
+ // node with the given role and returns its AtkText interface if found,
+ // otherwise returns nullptr.
+ AtkText* FindNode(const AtkRole role);
private:
+ // Searches the accessibility tree in pre-order debth-first traversal starting
+ // at a given node and for a node with the given role and returns its AtkText
+ // interface if found, otherwise returns nullptr.
+ AtkText* FindNode(AtkObject* root, const AtkRole role) const;
+
DISALLOW_COPY_AND_ASSIGN(AccessibilityAuraLinuxBrowserTest);
};
-AtkText* AccessibilityAuraLinuxBrowserTest::GetAtkTextForChild(
- AtkRole expected_role) {
- AtkObject* document = GetRendererAccessible();
- EXPECT_EQ(1, atk_object_get_n_accessible_children(document));
-
- AtkObject* parent_element = atk_object_ref_accessible_child(document, 0);
- int number_of_children = atk_object_get_n_accessible_children(parent_element);
- EXPECT_LT(0, number_of_children);
-
- // The input field is always the last child.
- AtkObject* input =
- atk_object_ref_accessible_child(parent_element, number_of_children - 1);
- EXPECT_EQ(expected_role, atk_object_get_role(input));
-
- EXPECT_TRUE(ATK_IS_TEXT(input));
- AtkText* atk_text = ATK_TEXT(input);
-
- g_object_unref(parent_element);
+void AccessibilityAuraLinuxBrowserTest::CheckTextAtOffset(
+ AtkText* text_object,
+ int offset,
+ AtkTextBoundary boundary_type,
+ int expected_start_offset,
+ int expected_end_offset,
+ const char* expected_text) {
+ testing::Message message;
+ message << "While checking at index \'" << offset << "\' for \'"
+ << expected_text << "\' at " << expected_start_offset << '-'
+ << expected_end_offset << '.';
+ SCOPED_TRACE(message);
- return atk_text;
+ int start_offset = 0;
+ int end_offset = 0;
+ char* text = atk_text_get_text_at_offset(text_object, offset, boundary_type,
+ &start_offset, &end_offset);
+ EXPECT_EQ(expected_start_offset, start_offset);
+ EXPECT_EQ(expected_end_offset, end_offset);
+ EXPECT_STREQ(expected_text, text);
+ g_free(text);
}
-// Loads a page with an input text field and places sample text in it.
AtkText* AccessibilityAuraLinuxBrowserTest::SetUpInputField() {
LoadInputField();
- return GetAtkTextForChild(ATK_ROLE_ENTRY);
+ return FindNode(ATK_ROLE_ENTRY);
}
-// Loads a page with a textarea text field and places sample text in it. Also,
-// places the caret before the last character.
AtkText* AccessibilityAuraLinuxBrowserTest::SetUpTextareaField() {
LoadTextareaField();
- return GetAtkTextForChild(ATK_ROLE_ENTRY);
+ return FindNode(ATK_ROLE_ENTRY);
}
-// Loads a page with a paragraph of sample text.
AtkText* AccessibilityAuraLinuxBrowserTest::SetUpSampleParagraph() {
LoadSampleParagraph();
@@ -139,37 +156,48 @@ AtkText* AccessibilityAuraLinuxBrowserTest::GetSampleParagraph() {
int number_of_children = atk_object_get_n_accessible_children(document);
EXPECT_LT(0, number_of_children);
- // The input field is always the last child.
- AtkObject* input = atk_object_ref_accessible_child(document, 0);
- EXPECT_EQ(ATK_ROLE_PARAGRAPH, atk_object_get_role(input));
+ // The paragraph is the last child.
+ AtkObject* paragraph = atk_object_ref_accessible_child(document, 0);
+ EXPECT_EQ(ATK_ROLE_PARAGRAPH, atk_object_get_role(paragraph));
- EXPECT_TRUE(ATK_IS_TEXT(input));
- return ATK_TEXT(input);
+ EXPECT_TRUE(ATK_IS_TEXT(paragraph));
+ return ATK_TEXT(paragraph);
}
-// Ensures that the text and the start and end offsets retrieved using
-// get_textAtOffset match the expected values.
-void AccessibilityAuraLinuxBrowserTest::CheckTextAtOffset(
- AtkText* text_object,
- int offset,
- AtkTextBoundary boundary_type,
- int expected_start_offset,
- int expected_end_offset,
- const char* expected_text) {
- testing::Message message;
- message << "While checking at index \'" << offset << "\' for \'"
- << expected_text << "\' at " << expected_start_offset << '-'
- << expected_end_offset << '.';
- SCOPED_TRACE(message);
+AtkText* AccessibilityAuraLinuxBrowserTest::FindNode(const AtkRole role) {
+ AtkObject* document = GetRendererAccessible();
+ EXPECT_NE(nullptr, document);
+ return FindNode(document, role);
+}
- int start_offset = 0;
- int end_offset = 0;
- char* text = atk_text_get_text_at_offset(text_object, offset, boundary_type,
- &start_offset, &end_offset);
- EXPECT_EQ(expected_start_offset, start_offset);
- EXPECT_EQ(expected_end_offset, end_offset);
- EXPECT_STREQ(expected_text, text);
- g_free(text);
+AtkText* AccessibilityAuraLinuxBrowserTest::FindNode(AtkObject* root,
+ const AtkRole role) const {
+ EXPECT_NE(nullptr, root);
+ if (atk_object_get_role(root) == role) {
+ EXPECT_TRUE(ATK_IS_TEXT(root));
+ g_object_ref(root);
+ AtkText* root_text = ATK_TEXT(root);
+ return root_text;
+ }
+
+ for (int i = 0; i < atk_object_get_n_accessible_children(root); ++i) {
+ AtkObject* child = atk_object_ref_accessible_child(root, i);
+ EXPECT_NE(nullptr, child);
+ if (atk_object_get_role(child) == role) {
+ EXPECT_TRUE(ATK_IS_TEXT(child));
+ AtkText* child_text = ATK_TEXT(child);
+ return child_text;
+ }
+
+ if (AtkText* descendant_text = FindNode(child, role)) {
+ g_object_unref(child);
+ return descendant_text;
+ }
+
+ g_object_unref(child);
+ }
+
+ return nullptr;
}
IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
@@ -228,7 +256,7 @@ IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
// "Before".
//
// The embedded object character representing the image is at offset 6.
- for (int i = 0; i <= 6; ++i) {
+ for (int i = 0; i < 6; ++i) {
CheckTextAtOffset(contenteditable_text, i, ATK_TEXT_BOUNDARY_CHAR, i,
(i + 1), expected_hypertext[i].c_str());
}
@@ -1142,6 +1170,126 @@ IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
g_object_unref(atk_text);
}
+IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
+ SetSelectionWithIgnoredObjects) {
+ LoadInitialAccessibilityTreeFromHtml(R"HTML(<!DOCTYPE html>
+ <html>
+ <body>
+ <ul>
+ <li>
+ <div role="presentation"></div>
+ <p role="presentation">
+ <span>Banana</span>
+ </p>
+ <span>fruit.</span>
+ </li>
+ </ul>
+ </body>
+ </html>)HTML");
+
+ AtkText* atk_list_item = FindNode(ATK_ROLE_LIST_ITEM);
+ ASSERT_NE(nullptr, atk_list_item);
+
+ // The hypertext expose by "list_item_text" includes an embedded object
+ // character for the list bullet and the joined word "Bananafruit.". The word
+ // "Banana" is exposed as text because its container paragraph is ignored.
+ int n_characters = atk_text_get_character_count(atk_list_item);
+ ASSERT_EQ(13, n_characters);
+
+ AccessibilityNotificationWaiter waiter(
+ shell()->web_contents(), ui::kAXModeComplete,
+ ax::mojom::Event::kDocumentSelectionChanged);
+
+ // First select the whole of the text found in the hypertext.
+ int start_offset = 0;
+ int end_offset = n_characters;
+ std::string embedded_character;
+ ASSERT_TRUE(
+ base::UTF16ToUTF8(&ui::AXPlatformNodeAuraLinux::kEmbeddedCharacter, 1,
+ &embedded_character));
+ char* selected_text = nullptr;
+
+ EXPECT_TRUE(
+ atk_text_set_selection(atk_list_item, 0, start_offset, end_offset));
+ waiter.WaitForNotification();
+
+ selected_text =
+ atk_text_get_selection(atk_list_item, 0, &start_offset, &end_offset);
+ ASSERT_NE(nullptr, selected_text);
+ EXPECT_EQ(0, start_offset);
+ EXPECT_EQ(n_characters, end_offset);
+ // The list bullet should be represented by an embedded object character.
+ EXPECT_STREQ((embedded_character + std::string("Bananafruit.")).c_str(),
+ selected_text);
+ g_free(selected_text);
+
+ // Select only the list bullet.
+ start_offset = 0;
+ end_offset = 1;
+ EXPECT_TRUE(
+ atk_text_set_selection(atk_list_item, 0, start_offset, end_offset));
+ waiter.WaitForNotification();
+
+ selected_text =
+ atk_text_get_selection(atk_list_item, 0, &start_offset, &end_offset);
+ ASSERT_NE(nullptr, selected_text);
+ EXPECT_EQ(0, start_offset);
+ EXPECT_EQ(1, end_offset);
+ // The list bullet should be represented by an embedded object character.
+ EXPECT_STREQ(embedded_character.c_str(), selected_text);
+ g_free(selected_text);
+
+ // Select the word "Banana" in the ignored paragraph.
+ start_offset = 1;
+ end_offset = 7;
+ EXPECT_TRUE(
+ atk_text_set_selection(atk_list_item, 0, start_offset, end_offset));
+ waiter.WaitForNotification();
+
+ selected_text =
+ atk_text_get_selection(atk_list_item, 0, &start_offset, &end_offset);
+ ASSERT_NE(nullptr, selected_text);
+ EXPECT_EQ(1, start_offset);
+ EXPECT_EQ(7, end_offset);
+ EXPECT_STREQ("Banana", selected_text);
+ g_free(selected_text);
+
+ // Select both the list bullet and the word "Banana" in the ignored paragraph.
+ start_offset = 0;
+ end_offset = 7;
+ EXPECT_TRUE(
+ atk_text_set_selection(atk_list_item, 0, start_offset, end_offset));
+ waiter.WaitForNotification();
+
+ selected_text =
+ atk_text_get_selection(atk_list_item, 0, &start_offset, &end_offset);
+ ASSERT_NE(nullptr, selected_text);
+ EXPECT_EQ(0, start_offset);
+ EXPECT_EQ(7, end_offset);
+ // The list bullet should be represented by an embedded object character.
+ EXPECT_STREQ((embedded_character + std::string("Banana")).c_str(),
+ selected_text);
+ g_free(selected_text);
+
+ // Select the joined word "Bananafruit." both in the ignored paragraph and in
+ // the unignored span.
+ start_offset = 1;
+ end_offset = n_characters;
+ EXPECT_TRUE(
+ atk_text_set_selection(atk_list_item, 0, start_offset, end_offset));
+ waiter.WaitForNotification();
+
+ selected_text =
+ atk_text_get_selection(atk_list_item, 0, &start_offset, &end_offset);
+ ASSERT_NE(nullptr, selected_text);
+ EXPECT_EQ(1, start_offset);
+ EXPECT_EQ(n_characters, end_offset);
+ EXPECT_STREQ("Bananafruit.", selected_text);
+ g_free(selected_text);
+
+ g_object_unref(atk_list_item);
+}
+
IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest, TestAtkTextListItem) {
LoadInitialAccessibilityTreeFromHtml(
R"HTML(<!DOCTYPE html>
@@ -1731,4 +1879,68 @@ IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
}
}
+IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
+ HitTestOnAncestorOfWebRoot) {
+ // Load the page.
+ LoadInitialAccessibilityTreeFromHtml(R"HTML(
+ <button>This is a button</button>
+ )HTML");
+
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(shell()->web_contents());
+ BrowserAccessibilityManager* manager =
+ web_contents->GetRootBrowserAccessibilityManager();
+
+ // Find a node to hit test. Note that this is a really simple page,
+ // so synchronous hit testing will work fine.
+ BrowserAccessibility* node = manager->GetRoot();
+ while (node && node->GetRole() != ax::mojom::Role::kButton)
+ node = manager->NextInTreeOrder(node);
+ DCHECK(node);
+
+ // Get the screen bounds of the hit target and find the point in the middle.
+ gfx::Rect bounds = node->GetClippedScreenBoundsRect();
+ gfx::Point point = bounds.CenterPoint();
+
+ // Get the root AXPlatformNodeAuraLinux.
+ ui::AXPlatformNodeAuraLinux* root_platform_node =
+ static_cast<ui::AXPlatformNodeAuraLinux*>(
+ ui::AXPlatformNode::FromNativeViewAccessible(
+ manager->GetRoot()->GetNativeViewAccessible()));
+
+ // First test that calling accHitTest on the root node returns the button.
+ {
+ gfx::NativeViewAccessible hit_child = root_platform_node->HitTestSync(
+ point.x(), point.y(), AtkCoordType::ATK_XY_SCREEN);
+ ASSERT_NE(nullptr, hit_child);
+ ui::AXPlatformNode* hit_child_node =
+ ui::AXPlatformNode::FromNativeViewAccessible(hit_child);
+ ASSERT_NE(nullptr, hit_child_node);
+ EXPECT_EQ(node->GetId(), hit_child_node->GetDelegate()->GetData().id);
+ }
+
+ // Now test it again, but this time caliing accHitTest on the parent
+ // IAccessible of the web root node.
+ {
+ RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>(
+ shell()->web_contents()->GetRenderWidgetHostView());
+ gfx::NativeViewAccessible ancestor = rwhva->GetParentNativeViewAccessible();
+
+ ASSERT_NE(nullptr, ancestor);
+
+ ui::AXPlatformNodeAuraLinux* ancestor_node =
+ static_cast<ui::AXPlatformNodeAuraLinux*>(
+ ui::AXPlatformNode::FromNativeViewAccessible(ancestor));
+ ASSERT_NE(nullptr, ancestor_node);
+
+ gfx::NativeViewAccessible hit_child = ancestor_node->HitTestSync(
+ point.x(), point.y(), AtkCoordType::ATK_XY_SCREEN);
+ ASSERT_NE(nullptr, hit_child);
+ ui::AXPlatformNode* hit_child_node =
+ ui::AXPlatformNode::FromNativeViewAccessible(hit_child);
+ ASSERT_NE(nullptr, hit_child_node);
+ EXPECT_EQ(node->GetId(), hit_child_node->GetDelegate()->GetData().id);
+ }
+}
+
} // namespace content
diff --git a/chromium/content/browser/accessibility/accessibility_event_recorder_uia_win.cc b/chromium/content/browser/accessibility/accessibility_event_recorder_uia_win.cc
index 5e5895010af..8f52006bc78 100644
--- a/chromium/content/browser/accessibility/accessibility_event_recorder_uia_win.cc
+++ b/chromium/content/browser/accessibility/accessibility_event_recorder_uia_win.cc
@@ -20,6 +20,7 @@
#include "content/browser/accessibility/browser_accessibility_com_win.h"
#include "content/browser/accessibility/browser_accessibility_manager.h"
#include "content/browser/accessibility/browser_accessibility_manager_win.h"
+#include "ui/accessibility/platform/uia_registrar_win.h"
#include "ui/base/win/atl_module.h"
namespace content {
@@ -110,14 +111,8 @@ void AccessibilityEventRecorderUia::Thread::ThreadMain() {
CHECK(uia_.Get());
// Register the custom event to mark the end of the test.
- Microsoft::WRL::ComPtr<IUIAutomationRegistrar> registrar;
- CoCreateInstance(CLSID_CUIAutomationRegistrar, NULL, CLSCTX_INPROC_SERVER,
- IID_IUIAutomationRegistrar, &registrar);
- CHECK(registrar.Get());
- UIAutomationEventInfo custom_event = {kUiaTestCompleteSentinelGuid,
- kUiaTestCompleteSentinel};
- CHECK(
- SUCCEEDED(registrar->RegisterEvent(&custom_event, &shutdown_sentinel_)));
+ shutdown_sentinel_ =
+ ui::UiaRegistrarWin::GetInstance().GetUiaTestCompleteEventId();
// Find the IUIAutomationElement for the root content window
uia_->ElementFromHandle(hwnd_, &root_);
diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc b/chromium/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc
index 85a308d7591..b09bf27719f 100644
--- a/chromium/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc
+++ b/chromium/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc
@@ -21,8 +21,6 @@
#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 {
diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter_base.cc b/chromium/content/browser/accessibility/accessibility_tree_formatter_base.cc
index 14ccfb92527..706b036cab9 100644
--- a/chromium/content/browser/accessibility/accessibility_tree_formatter_base.cc
+++ b/chromium/content/browser/accessibility/accessibility_tree_formatter_base.cc
@@ -8,6 +8,7 @@
#include <memory>
#include <utility>
+#include <vector>
#include "base/check_op.h"
#include "base/strings/pattern.h"
@@ -32,6 +33,235 @@ const char kSkipChildren[] = "@NO_CHILDREN_DUMP";
} // namespace
+//
+// PropertyNode
+//
+
+// static
+PropertyNode PropertyNode::FromPropertyFilter(
+ const AccessibilityTreeFormatter::PropertyFilter& filter) {
+ // Property invocation: property_str expected format is
+ // prop_name or prop_name(arg1, ... argN).
+ PropertyNode root;
+ Parse(&root, filter.property_str.begin(), filter.property_str.end());
+
+ PropertyNode* node = &root.parameters[0];
+ node->original_property = filter.property_str;
+
+ // Line indexes filter: filter_str expected format is
+ // :line_num_1, ... :line_num_N, a comma separated list of line indexes
+ // the property should be queried for. For example, ":1,:5,:7" indicates that
+ // the property should called for objects placed on 1, 5 and 7 lines only.
+ if (!filter.filter_str.empty()) {
+ node->line_indexes =
+ base::SplitString(filter.filter_str, base::string16(1, ','),
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+ }
+
+ return std::move(*node);
+}
+
+PropertyNode::PropertyNode() = default;
+PropertyNode::PropertyNode(PropertyNode&& o)
+ : key(std::move(o.key)),
+ name_or_value(std::move(o.name_or_value)),
+ parameters(std::move(o.parameters)),
+ original_property(std::move(o.original_property)),
+ line_indexes(std::move(o.line_indexes)) {}
+PropertyNode::~PropertyNode() = default;
+
+PropertyNode& PropertyNode::operator=(PropertyNode&& o) {
+ key = std::move(o.key);
+ name_or_value = std::move(o.name_or_value);
+ parameters = std::move(o.parameters);
+ original_property = std::move(o.original_property);
+ line_indexes = std::move(o.line_indexes);
+ return *this;
+}
+
+PropertyNode::operator bool() const {
+ return !name_or_value.empty();
+}
+
+bool PropertyNode::IsArray() const {
+ return name_or_value == base::ASCIIToUTF16("[]");
+}
+
+bool PropertyNode::IsDict() const {
+ return name_or_value == base::ASCIIToUTF16("{}");
+}
+
+base::Optional<int> PropertyNode::AsInt() const {
+ int value = 0;
+ if (!base::StringToInt(name_or_value, &value)) {
+ return base::nullopt;
+ }
+ return value;
+}
+
+base::Optional<base::string16> PropertyNode::FindKey(const char* refkey) const {
+ for (const auto& param : parameters) {
+ if (param.key == base::ASCIIToUTF16(refkey)) {
+ return param.name_or_value;
+ }
+ }
+ return base::nullopt;
+}
+
+base::Optional<int> PropertyNode::FindIntKey(const char* refkey) const {
+ for (const auto& param : parameters) {
+ if (param.key == base::ASCIIToUTF16(refkey)) {
+ return param.AsInt();
+ }
+ }
+ return base::nullopt;
+}
+
+std::string PropertyNode::ToString() const {
+ std::string out;
+ for (const auto& index : line_indexes) {
+ if (!out.empty()) {
+ out += ',';
+ }
+ out += base::UTF16ToUTF8(index);
+ }
+ if (!out.empty()) {
+ out += ';';
+ }
+
+ if (!key.empty()) {
+ out += base::UTF16ToUTF8(key) + ": ";
+ }
+ out += base::UTF16ToUTF8(name_or_value);
+ if (parameters.size()) {
+ out += '(';
+ for (size_t i = 0; i < parameters.size(); i++) {
+ if (i != 0) {
+ out += ", ";
+ }
+ out += parameters[i].ToString();
+ }
+ out += ')';
+ }
+ return out;
+}
+
+// private
+PropertyNode::PropertyNode(PropertyNode::iterator key_begin,
+ PropertyNode::iterator key_end,
+ const base::string16& name_or_value)
+ : key(key_begin, key_end), name_or_value(name_or_value) {}
+PropertyNode::PropertyNode(PropertyNode::iterator begin,
+ PropertyNode::iterator end)
+ : name_or_value(begin, end) {}
+PropertyNode::PropertyNode(PropertyNode::iterator key_begin,
+ PropertyNode::iterator key_end,
+ PropertyNode::iterator value_begin,
+ PropertyNode::iterator value_end)
+ : key(key_begin, key_end), name_or_value(value_begin, value_end) {}
+
+// private static
+PropertyNode::iterator PropertyNode::Parse(PropertyNode* node,
+ PropertyNode::iterator begin,
+ PropertyNode::iterator end) {
+ auto iter = begin;
+ auto key_begin = end, key_end = end;
+ while (iter != end) {
+ // Subnode begins: create a new node, record its name and parse its
+ // arguments.
+ if (*iter == '(') {
+ node->parameters.push_back(PropertyNode(key_begin, key_end, begin, iter));
+ key_begin = key_end = end;
+ begin = iter = Parse(&node->parameters.back(), ++iter, end);
+ continue;
+ }
+
+ // Subnode begins: a special case for arrays, which have [arg1, ..., argN]
+ // form.
+ if (*iter == '[') {
+ node->parameters.push_back(
+ PropertyNode(key_begin, key_end, base::UTF8ToUTF16("[]")));
+ key_begin = key_end = end;
+ begin = iter = Parse(&node->parameters.back(), ++iter, end);
+ continue;
+ }
+
+ // Subnode begins: a special case for dictionaries of {key1: value1, ...,
+ // key2: value2} form.
+ if (*iter == '{') {
+ node->parameters.push_back(
+ PropertyNode(key_begin, key_end, base::UTF8ToUTF16("{}")));
+ key_begin = key_end = end;
+ begin = iter = Parse(&node->parameters.back(), ++iter, end);
+ continue;
+ }
+
+ // Subnode ends.
+ if (*iter == ')' || *iter == ']' || *iter == '}') {
+ if (begin != iter) {
+ node->parameters.push_back(
+ PropertyNode(key_begin, key_end, begin, iter));
+ key_begin = key_end = end;
+ }
+ return ++iter;
+ }
+
+ // Dictionary key
+ auto maybe_key_end = end;
+ if (*iter == ':') {
+ maybe_key_end = iter++;
+ }
+
+ // Skip spaces, adjust new node start.
+ if (*iter == ' ') {
+ if (maybe_key_end != end) {
+ key_begin = begin;
+ key_end = maybe_key_end;
+ }
+ begin = ++iter;
+ continue;
+ }
+
+ // Subsequent scalar param case.
+ if (*iter == ',' && begin != iter) {
+ node->parameters.push_back(PropertyNode(key_begin, key_end, begin, iter));
+ iter++;
+ key_begin = key_end = end;
+ begin = iter;
+ continue;
+ }
+
+ iter++;
+ }
+
+ // Single scalar param case.
+ if (begin != iter) {
+ node->parameters.push_back(PropertyNode(begin, iter));
+ }
+ return iter;
+}
+
+//
+// AccessibilityTreeFormatter
+//
+
+AccessibilityTreeFormatter::PropertyFilter::PropertyFilter(
+ const PropertyFilter&) = default;
+
+AccessibilityTreeFormatter::PropertyFilter::PropertyFilter(
+ const base::string16& str,
+ Type type)
+ : match_str(str), type(type) {
+ size_t index = str.find(';');
+ if (index != std::string::npos) {
+ filter_str = str.substr(0, index);
+ if (index + 1 < str.length()) {
+ match_str = str.substr(index + 1, std::string::npos);
+ }
+ }
+ property_str = match_str.substr(0, match_str.find('='));
+}
+
AccessibilityTreeFormatter::TestPass AccessibilityTreeFormatter::GetTestPass(
size_t index) {
std::vector<content::AccessibilityTreeFormatter::TestPass> passes =
@@ -189,26 +419,38 @@ AccessibilityTreeFormatterBase::GetVersionSpecificExpectedFileSuffix() {
return FILE_PATH_LITERAL("");
}
-bool AccessibilityTreeFormatterBase::FilterPropertyName(
- const base::string16& text) {
- // Find the first allow-filter matching the property name. The filter should
- // be either an exact property match or a wildcard matching to support filter
- // collections like AXRole* which matches AXRoleDescription.
- const base::string16 delim = base::ASCIIToUTF16("=");
+PropertyNode AccessibilityTreeFormatterBase::GetMatchingPropertyNode(
+ const base::string16& line_index,
+ const base::string16& property_name) {
+ // Find the first allow-filter matching the line index and the property name.
for (const auto& filter : property_filters_) {
- base::String16Tokenizer tokenizer(filter.match_str, delim);
- if (tokenizer.GetNext() && (text == tokenizer.token() ||
- base::MatchPattern(text, tokenizer.token()))) {
+ PropertyNode property_node = PropertyNode::FromPropertyFilter(filter);
+
+ // Skip if the line index filter doesn't matched (if specified).
+ if (!property_node.line_indexes.empty() &&
+ std::find(property_node.line_indexes.begin(),
+ property_node.line_indexes.end(),
+ line_index) == property_node.line_indexes.end()) {
+ continue;
+ }
+
+ // The filter should be either an exact property match or a wildcard
+ // matching to support filter collections like AXRole* which matches
+ // AXRoleDescription.
+ if (property_name == property_node.name_or_value ||
+ base::MatchPattern(property_name, property_node.name_or_value)) {
switch (filter.type) {
case PropertyFilter::ALLOW_EMPTY:
case PropertyFilter::ALLOW:
- return true;
+ return property_node;
+ case PropertyFilter::DENY:
+ break;
default:
break;
}
}
}
- return false;
+ return PropertyNode();
}
bool AccessibilityTreeFormatterBase::MatchesPropertyFilters(
@@ -282,4 +524,5 @@ void AccessibilityTreeFormatterBase::AddPropertyFilter(
void AccessibilityTreeFormatterBase::AddDefaultFilters(
std::vector<PropertyFilter>* property_filters) {}
+
} // namespace content
diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter_base.h b/chromium/content/browser/accessibility/accessibility_tree_formatter_base.h
index 702bcd6811a..13ebfad47d3 100644
--- a/chromium/content/browser/accessibility/accessibility_tree_formatter_base.h
+++ b/chromium/content/browser/accessibility/accessibility_tree_formatter_base.h
@@ -27,6 +27,70 @@ const char kChildrenDictAttr[] = "children";
namespace content {
+// Property node is a tree-like structure, representing a property or collection
+// of properties and its invocation parameters. A collection of properties is
+// specified by putting a wildcard into a property name, for exampe, AXRole*
+// will match both AXRole and AXRoleDescription properties. Parameters of a
+// property are given in parentheses like a conventional function call, for
+// example, AXCellForColumnAndRow([0, 0]) will call AXCellForColumnAndRow
+// parameterized property for column/row 0 indexes.
+class CONTENT_EXPORT PropertyNode final {
+ public:
+ // Parses a property node from a string.
+ static PropertyNode FromPropertyFilter(
+ const AccessibilityTreeFormatter::PropertyFilter& filter);
+
+ PropertyNode();
+ PropertyNode(PropertyNode&&);
+ ~PropertyNode();
+
+ PropertyNode& operator=(PropertyNode&& other);
+ explicit operator bool() const;
+
+ // Key name in case of { key: value } dictionary.
+ base::string16 key;
+
+ // Value or a property name, for example 3 or AXLineForIndex
+ base::string16 name_or_value;
+
+ // Parameters if it's a property, for example, it is a vector of a single
+ // value 3 in case of AXLineForIndex(3)
+ std::vector<PropertyNode> parameters;
+
+ // Used to store the origianl unparsed property including invocation
+ // parameters if any.
+ base::string16 original_property;
+
+ // The list of line indexes of accessible objects the property is allowed to
+ // be called for.
+ std::vector<base::string16> line_indexes;
+
+ // Argument conversion methods.
+ bool IsArray() const;
+ bool IsDict() const;
+ base::Optional<int> AsInt() const;
+ base::Optional<base::string16> FindKey(const char* refkey) const;
+ base::Optional<int> FindIntKey(const char* key) const;
+
+ std::string ToString() const;
+
+ private:
+ using iterator = base::string16::const_iterator;
+
+ explicit PropertyNode(iterator key_begin,
+ iterator key_end,
+ const base::string16&);
+ PropertyNode(iterator begin, iterator end);
+ PropertyNode(iterator key_begin,
+ iterator key_end,
+ iterator value_begin,
+ iterator value_end);
+
+ // Builds a property node struct for a string of NAME(ARG1, ..., ARGN) format,
+ // where each ARG is a scalar value or a string of the same format.
+ static iterator Parse(PropertyNode* node, iterator begin, iterator end);
+};
+
// A utility class for formatting platform-specific accessibility information,
// for use in testing, debugging, and developer tools.
// This is extended by a subclass for each platform where accessibility is
@@ -86,8 +150,11 @@ class CONTENT_EXPORT AccessibilityTreeFormatterBase
// Overridden by platform subclasses.
//
- // Returns true if the property name matches a property filter.
- bool FilterPropertyName(const base::string16& text);
+ // Returns a property node struct built for a matching property filter,
+ // which includes a property name and invocation parameters if any.
+ // If no matching property filter, then empty property node is returned.
+ PropertyNode GetMatchingPropertyNode(const base::string16& line_index,
+ const base::string16& property_name);
// Process accessibility tree with filters for output.
// Given a dictionary that contains a platform-specific dictionary
diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter_base_unittest.cc b/chromium/content/browser/accessibility/accessibility_tree_formatter_base_unittest.cc
new file mode 100644
index 00000000000..a2d5fdfb547
--- /dev/null
+++ b/chromium/content/browser/accessibility/accessibility_tree_formatter_base_unittest.cc
@@ -0,0 +1,100 @@
+// Copyright (c) 2020 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_base.h"
+
+#include "content/browser/accessibility/browser_accessibility.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/browser/accessibility/test_browser_accessibility_delegate.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/accessibility/ax_enums.mojom.h"
+
+namespace content {
+
+class AccessibilityTreeFormatterBaseTest : public testing::Test {
+ public:
+ AccessibilityTreeFormatterBaseTest() = default;
+ ~AccessibilityTreeFormatterBaseTest() override = default;
+
+ protected:
+ std::unique_ptr<TestBrowserAccessibilityDelegate>
+ test_browser_accessibility_delegate_;
+
+ private:
+ void SetUp() override {
+ test_browser_accessibility_delegate_ =
+ std::make_unique<TestBrowserAccessibilityDelegate>();
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(AccessibilityTreeFormatterBaseTest);
+};
+
+PropertyNode Parse(const char* input) {
+ AccessibilityTreeFormatter::PropertyFilter filter(
+ base::UTF8ToUTF16(input),
+ AccessibilityTreeFormatter::PropertyFilter::ALLOW);
+ return PropertyNode::FromPropertyFilter(filter);
+}
+
+PropertyNode GetArgumentNode(const char* input) {
+ auto got = Parse(input);
+ if (got.parameters.size() == 0) {
+ return PropertyNode();
+ }
+ return std::move(got.parameters[0]);
+}
+
+void ParseAndCheck(const char* input, const char* expected) {
+ auto got = Parse(input).ToString();
+ EXPECT_EQ(got, expected);
+}
+
+TEST_F(AccessibilityTreeFormatterBaseTest, ParseProperty) {
+ // Properties and methods.
+ ParseAndCheck("Role", "Role");
+ ParseAndCheck("ChildAt(3)", "ChildAt(3)");
+ ParseAndCheck("Cell(3, 4)", "Cell(3, 4)");
+ ParseAndCheck("Volume(3, 4, 5)", "Volume(3, 4, 5)");
+ ParseAndCheck("TableFor(CellBy(id))", "TableFor(CellBy(id))");
+ ParseAndCheck("A(B(1), 2)", "A(B(1), 2)");
+ ParseAndCheck("A(B(1), 2, C(3, 4))", "A(B(1), 2, C(3, 4))");
+ ParseAndCheck("[3, 4]", "[](3, 4)");
+ ParseAndCheck("Cell([3, 4])", "Cell([](3, 4))");
+
+ // Arguments
+ ParseAndCheck("Text({val: 1})", "Text({}(val: 1))");
+ ParseAndCheck("Text({lat: 1, len: 1})", "Text({}(lat: 1, len: 1))");
+ ParseAndCheck("Text({dict: {val: 1}})", "Text({}(dict: {}(val: 1)))");
+ ParseAndCheck("Text({dict: {val: 1}, 3})", "Text({}(dict: {}(val: 1), 3))");
+ ParseAndCheck("Text({dict: [1, 2]})", "Text({}(dict: [](1, 2)))");
+ ParseAndCheck("Text({dict: ValueFor(1)})", "Text({}(dict: ValueFor(1)))");
+
+ // Line indexes filter.
+ ParseAndCheck(":3,:5;AXDOMClassList", ":3,:5;AXDOMClassList");
+
+ // Wrong format.
+ ParseAndCheck("Role(3", "Role(3)");
+ ParseAndCheck("TableFor(CellBy(id", "TableFor(CellBy(id))");
+ ParseAndCheck("[3, 4", "[](3, 4)");
+
+ // Arguments conversion
+ EXPECT_EQ(GetArgumentNode("ChildAt([3])").IsArray(), true);
+ EXPECT_EQ(GetArgumentNode("Text({loc: 3, len: 2})").IsDict(), true);
+ EXPECT_EQ(GetArgumentNode("ChildAt(3)").IsDict(), false);
+ EXPECT_EQ(GetArgumentNode("ChildAt(3)").IsArray(), false);
+ EXPECT_EQ(GetArgumentNode("ChildAt(3)").AsInt(), 3);
+ EXPECT_EQ(GetArgumentNode("Text({start: :1, dir: forward})").FindKey("start"),
+ base::ASCIIToUTF16(":1"));
+ EXPECT_EQ(GetArgumentNode("Text({start: :1, dir: forward})").FindKey("dir"),
+ base::ASCIIToUTF16("forward"));
+ EXPECT_EQ(
+ GetArgumentNode("Text({start: :1, dir: forward})").FindKey("notexists"),
+ base::nullopt);
+ EXPECT_EQ(GetArgumentNode("Text({loc: 3, len: 2})").FindIntKey("loc"), 3);
+ EXPECT_EQ(GetArgumentNode("Text({loc: 3, len: 2})").FindIntKey("len"), 2);
+ EXPECT_EQ(GetArgumentNode("Text({loc: 3, len: 2})").FindIntKey("notexists"),
+ base::nullopt);
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter_blink.cc b/chromium/content/browser/accessibility/accessibility_tree_formatter_blink.cc
index 7bf60d5408d..abc989bacd5 100644
--- a/chromium/content/browser/accessibility/accessibility_tree_formatter_blink.cc
+++ b/chromium/content/browser/accessibility/accessibility_tree_formatter_blink.cc
@@ -170,6 +170,8 @@ void AccessibilityTreeFormatterBlink::AddDefaultFilters(
AddPropertyFilter(property_filters, "protected");
AddPropertyFilter(property_filters, "required");
AddPropertyFilter(property_filters, "select*");
+ AddPropertyFilter(property_filters, "selectedFromFocus=*",
+ PropertyFilter::DENY);
AddPropertyFilter(property_filters, "visited");
// Other attributes
AddPropertyFilter(property_filters, "busy=true");
diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter_mac.mm b/chromium/content/browser/accessibility/accessibility_tree_formatter_mac.mm
index 7388129d79b..bc23c9cea29 100644
--- a/chromium/content/browser/accessibility/accessibility_tree_formatter_mac.mm
+++ b/chromium/content/browser/accessibility/accessibility_tree_formatter_mac.mm
@@ -41,116 +41,38 @@ const char kHeightDictAttr[] = "height";
const char kRangeLocDictAttr[] = "loc";
const char kRangeLenDictAttr[] = "len";
-std::unique_ptr<base::DictionaryValue> PopulatePosition(
- const BrowserAccessibility& node) {
- BrowserAccessibilityManager* root_manager = node.manager()->GetRootManager();
- DCHECK(root_manager);
-
- std::unique_ptr<base::DictionaryValue> position(new base::DictionaryValue);
- // The NSAccessibility position of an object is in global coordinates and
- // based on the lower-left corner of the object. To make this easier and less
- // confusing, convert it to local window coordinates using the top-left
- // corner when dumping the position.
- BrowserAccessibility* root = root_manager->GetRoot();
- BrowserAccessibilityCocoa* cocoa_root = ToBrowserAccessibilityCocoa(root);
- NSPoint root_position = [[cocoa_root position] pointValue];
- NSSize root_size = [[cocoa_root size] sizeValue];
- int root_top = -static_cast<int>(root_position.y + root_size.height);
- int root_left = static_cast<int>(root_position.x);
-
- BrowserAccessibilityCocoa* cocoa_node =
- ToBrowserAccessibilityCocoa(const_cast<BrowserAccessibility*>(&node));
- NSPoint node_position = [[cocoa_node position] pointValue];
- NSSize node_size = [[cocoa_node size] sizeValue];
-
- position->SetInteger(kXCoordDictAttr,
- static_cast<int>(node_position.x - root_left));
- position->SetInteger(
- kYCoordDictAttr,
- static_cast<int>(-node_position.y - node_size.height - root_top));
- return position;
-}
-
-std::unique_ptr<base::DictionaryValue> PopulateSize(
- const BrowserAccessibilityCocoa* cocoa_node) {
- std::unique_ptr<base::DictionaryValue> size(new base::DictionaryValue);
- NSSize node_size = [[cocoa_node size] sizeValue];
- size->SetInteger(kHeightDictAttr, static_cast<int>(node_size.height));
- size->SetInteger(kWidthDictAttr, static_cast<int>(node_size.width));
- return size;
-}
-
-std::unique_ptr<base::DictionaryValue> PopulateRange(NSRange range) {
- std::unique_ptr<base::DictionaryValue> rangeDict(new base::DictionaryValue);
- rangeDict->SetInteger(kRangeLocDictAttr, static_cast<int>(range.location));
- rangeDict->SetInteger(kRangeLenDictAttr, static_cast<int>(range.length));
- return rangeDict;
-}
-
-// Returns true if |value| is an NSValue containing a NSRange.
-bool IsRangeValue(id value) {
- if (![value isKindOfClass:[NSValue class]])
- return false;
- return 0 == strcmp([value objCType], @encode(NSRange));
-}
-
-std::unique_ptr<base::Value> PopulateObject(id value);
-
-std::unique_ptr<base::ListValue> PopulateArray(NSArray* array) {
- std::unique_ptr<base::ListValue> list(new base::ListValue);
- for (NSUInteger i = 0; i < [array count]; i++)
- list->Append(PopulateObject([array objectAtIndex:i]));
- return list;
-}
-
-std::unique_ptr<base::Value> StringForBrowserAccessibility(
- BrowserAccessibilityCocoa* obj) {
- NSMutableArray* tokens = [[NSMutableArray alloc] init];
-
- // Always include the role
- id role = [obj role];
- [tokens addObject:role];
-
- // If the role is "group", include the role description as well.
- id roleDescription = [obj roleDescription];
- if ([role isEqualToString:NSAccessibilityGroupRole] &&
- roleDescription != nil && ![roleDescription isEqualToString:@""] &&
- ![roleDescription isEqualToString:@"group"]) {
- [tokens addObject:roleDescription];
- }
-
- // Include the description, title, or value - the first one not empty.
- id title = [obj title];
- id description = [obj descriptionForAccessibility];
- id value = [obj value];
- if (description && ![description isEqual:@""]) {
- [tokens addObject:description];
- } else if (title && ![title isEqual:@""]) {
- [tokens addObject:title];
- } else if (value && ![value isEqual:@""]) {
- [tokens addObject:value];
- }
-
- NSString* result = [tokens componentsJoinedByString:@" "];
- return std::unique_ptr<base::Value>(
- new base::Value(SysNSStringToUTF16(result)));
-}
-
-std::unique_ptr<base::Value> PopulateObject(id value) {
- if ([value isKindOfClass:[NSArray class]])
- return std::unique_ptr<base::Value>(PopulateArray((NSArray*)value));
- if (IsRangeValue(value))
- return std::unique_ptr<base::Value>(PopulateRange([value rangeValue]));
- if ([value isKindOfClass:[BrowserAccessibilityCocoa class]]) {
- std::string str;
- StringForBrowserAccessibility(value)->GetAsString(&str);
- return std::unique_ptr<base::Value>(
- StringForBrowserAccessibility((BrowserAccessibilityCocoa*)value));
- }
-
- return std::unique_ptr<base::Value>(new base::Value(
- SysNSStringToUTF16([NSString stringWithFormat:@"%@", value])));
-}
+const char kSetKeyPrefixDictAttr[] = "_setkey_";
+const char kConstValuePrefix[] = "_const_";
+const char kNULLValue[] = "_const_NULL";
+const char kFailedToParseArgsError[] = "_const_ERROR:FAILED_TO_PARSE_ARGS";
+
+#define INT_FAIL(propnode, msg) \
+ LOG(ERROR) << "Failed to parse " << propnode.original_property \
+ << " to Int: " << msg; \
+ return nil;
+
+#define INTARRAY_FAIL(propnode, msg) \
+ LOG(ERROR) << "Failed to parse " << propnode.original_property \
+ << " to IntArray: " << msg; \
+ return nil;
+
+#define NSRANGE_FAIL(propnode, msg) \
+ LOG(ERROR) << "Failed to parse " << propnode.original_property \
+ << " to NSRange: " << msg; \
+ return nil;
+
+#define UIELEMENT_FAIL(propnode, msg) \
+ LOG(ERROR) << "Failed to parse " << propnode.original_property \
+ << " to UIElement: " << msg; \
+ return nil;
+
+#define TEXTMARKER_FAIL(propnode, msg) \
+ LOG(ERROR) << "Failed to parse " << propnode.original_property \
+ << " to AXTextMarker: " << msg \
+ << ". Expected format: {anchor, offset, affinity}, where anchor " \
+ "is :line_num, offset is integer, affinity is either down, " \
+ "up or none"; \
+ return nil;
} // namespace
@@ -173,19 +95,79 @@ class AccessibilityTreeFormatterMac : public AccessibilityTreeFormatterBase {
const base::StringPiece& pattern) override;
private:
+ using LineIndexesMap =
+ std::map<const gfx::NativeViewAccessible, base::string16>;
+
void RecursiveBuildAccessibilityTree(const BrowserAccessibilityCocoa* node,
+ const LineIndexesMap& line_indexes_map,
base::DictionaryValue* dict);
+ void RecursiveBuildLineIndexesMap(const BrowserAccessibilityCocoa* node,
+ LineIndexesMap* line_indexes_map,
+ int* counter);
base::FilePath::StringType GetExpectedFileSuffix() override;
const std::string GetAllowEmptyString() override;
const std::string GetAllowString() override;
const std::string GetDenyString() override;
const std::string GetDenyNodeString() override;
+
void AddProperties(const BrowserAccessibilityCocoa* node,
- base::DictionaryValue* dict);
+ const LineIndexesMap& line_indexes_map,
+ base::Value* dict);
+
+ // Helper class used to compute a parameter for a parameterized attribute
+ // call. Can be either id or error. Similar to base::Optional, but allows nil
+ // id as a valid value.
+ class IdOrError {
+ public:
+ IdOrError() : value(nil), error(false) {}
+
+ IdOrError& operator=(id other_value) {
+ error = !other_value;
+ value = other_value;
+ return *this;
+ }
+
+ bool IsError() const { return error; }
+ bool IsNotNil() const { return !!value; }
+ constexpr const id& operator*() const& { return value; }
+
+ private:
+ id value;
+ bool error;
+ };
+
+ IdOrError ParamByPropertyNode(const PropertyNode&,
+ const LineIndexesMap&) const;
+ NSNumber* PropertyNodeToInt(const PropertyNode&) const;
+ NSArray* PropertyNodeToIntArray(const PropertyNode&) const;
+ NSValue* PropertyNodeToRange(const PropertyNode&) const;
+ gfx::NativeViewAccessible PropertyNodeToUIElement(
+ const PropertyNode&,
+ const LineIndexesMap&) const;
+ id PropertyNodeToTextMarker(const PropertyNode&, const LineIndexesMap&) const;
+
+ base::Value PopulateSize(const BrowserAccessibilityCocoa*) const;
+ base::Value PopulatePosition(const BrowserAccessibilityCocoa*) const;
+ base::Value PopulateRange(NSRange) const;
+ base::Value PopulateTextPosition(
+ BrowserAccessibilityPosition::AXPositionInstance::pointer,
+ const LineIndexesMap&) const;
+ base::Value PopulateTextMarkerRange(id, const LineIndexesMap&) const;
+ base::Value PopulateObject(id, const LineIndexesMap& line_indexes_map) const;
+ base::Value PopulateArray(NSArray*,
+ const LineIndexesMap& line_indexes_map) const;
+
+ std::string NodeToLineIndex(id, const LineIndexesMap&) const;
+ gfx::NativeViewAccessible LineIndexToNode(
+ const base::string16 line_index,
+ const LineIndexesMap& line_indexes_map) const;
+
base::string16 ProcessTreeForOutput(
const base::DictionaryValue& node,
base::DictionaryValue* filtered_dict_result = nullptr) override;
+
+ std::string FormatAttributeValue(const base::Value& value);
};
// static
@@ -228,9 +210,14 @@ AccessibilityTreeFormatterMac::BuildAccessibilityTree(
BrowserAccessibility* root) {
DCHECK(root);
- std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
BrowserAccessibilityCocoa* cocoa_root = ToBrowserAccessibilityCocoa(root);
- RecursiveBuildAccessibilityTree(cocoa_root, dict.get());
+
+ int counter = 0;
+ LineIndexesMap line_indexes_map;
+ RecursiveBuildLineIndexesMap(cocoa_root, &line_indexes_map, &counter);
+
+ std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+ RecursiveBuildAccessibilityTree(cocoa_root, line_indexes_map, dict.get());
return dict;
}
@@ -257,36 +244,397 @@ AccessibilityTreeFormatterMac::BuildAccessibilityTreeForPattern(
void AccessibilityTreeFormatterMac::RecursiveBuildAccessibilityTree(
const BrowserAccessibilityCocoa* cocoa_node,
+ const LineIndexesMap& line_indexes_map,
base::DictionaryValue* dict) {
- AddProperties(cocoa_node, dict);
+ AddProperties(cocoa_node, line_indexes_map, dict);
auto children = std::make_unique<base::ListValue>();
for (BrowserAccessibilityCocoa* cocoa_child in [cocoa_node children]) {
std::unique_ptr<base::DictionaryValue> child_dict(
new base::DictionaryValue);
- RecursiveBuildAccessibilityTree(cocoa_child, child_dict.get());
+ RecursiveBuildAccessibilityTree(cocoa_child, line_indexes_map,
+ child_dict.get());
children->Append(std::move(child_dict));
}
dict->Set(kChildrenDictAttr, std::move(children));
}
+void AccessibilityTreeFormatterMac::RecursiveBuildLineIndexesMap(
+ const BrowserAccessibilityCocoa* cocoa_node,
+ LineIndexesMap* line_indexes_map,
+ int* counter) {
+ const base::string16 line_index =
+ base::string16(1, ':') + base::NumberToString16(++(*counter));
+ line_indexes_map->insert({cocoa_node, line_index});
+ for (BrowserAccessibilityCocoa* cocoa_child in [cocoa_node children]) {
+ RecursiveBuildLineIndexesMap(cocoa_child, line_indexes_map, counter);
+ }
+}
+
void AccessibilityTreeFormatterMac::AddProperties(
const BrowserAccessibilityCocoa* cocoa_node,
- base::DictionaryValue* dict) {
+ const LineIndexesMap& line_indexes_map,
+ base::Value* dict) {
+ // DOM element id
BrowserAccessibility* node = [cocoa_node owner];
- dict->SetString("id", base::NumberToString16(node->GetId()));
+ dict->SetKey("id", base::Value(base::NumberToString16(node->GetId())));
+
+ base::string16 line_index = base::ASCIIToUTF16("-1");
+ if (line_indexes_map.find(cocoa_node) != line_indexes_map.end()) {
+ line_index = line_indexes_map.at(cocoa_node);
+ }
+ // Attributes
for (NSString* supportedAttribute in
[cocoa_node accessibilityAttributeNames]) {
- if (FilterPropertyName(SysNSStringToUTF16(supportedAttribute))) {
+ if (GetMatchingPropertyNode(line_index,
+ SysNSStringToUTF16(supportedAttribute))) {
id value = [cocoa_node accessibilityAttributeValue:supportedAttribute];
if (value != nil) {
- dict->Set(SysNSStringToUTF8(supportedAttribute), PopulateObject(value));
+ dict->SetPath(SysNSStringToUTF8(supportedAttribute),
+ PopulateObject(value, line_indexes_map));
}
}
}
- dict->Set(kPositionDictAttr, PopulatePosition(*node));
- dict->Set(kSizeDictAttr, PopulateSize(cocoa_node));
+
+ // Parameterized attributes
+ for (NSString* supportedAttribute in
+ [cocoa_node accessibilityParameterizedAttributeNames]) {
+ auto propnode = GetMatchingPropertyNode(
+ line_index, SysNSStringToUTF16(supportedAttribute));
+ IdOrError param = ParamByPropertyNode(propnode, line_indexes_map);
+ if (param.IsError()) {
+ dict->SetPath(base::UTF16ToUTF8(propnode.original_property),
+ base::Value(kFailedToParseArgsError));
+ continue;
+ }
+
+ if (param.IsNotNil()) {
+ id value = [cocoa_node accessibilityAttributeValue:supportedAttribute
+ forParameter:*param];
+ dict->SetPath(base::UTF16ToUTF8(propnode.original_property),
+ PopulateObject(value, line_indexes_map));
+ }
+ }
+
+ // Position and size
+ dict->SetPath(kPositionDictAttr, PopulatePosition(cocoa_node));
+ dict->SetPath(kSizeDictAttr, PopulateSize(cocoa_node));
+}
+
+AccessibilityTreeFormatterMac::IdOrError
+AccessibilityTreeFormatterMac::ParamByPropertyNode(
+ const PropertyNode& property_node,
+ const LineIndexesMap& line_indexes_map) const {
+ IdOrError param;
+ std::string property_name = base::UTF16ToASCII(property_node.name_or_value);
+
+ if (property_name == "AXLineForIndex") { // Int
+ param = PropertyNodeToInt(property_node);
+ } else if (property_name == "AXCellForColumnAndRow") { // IntArray
+ param = PropertyNodeToIntArray(property_node);
+ } else if (property_name == "AXStringForRange") { // NSRange
+ param = PropertyNodeToRange(property_node);
+ } else if (property_name == "AXIndexForChildUIElement") { // UIElement
+ param = PropertyNodeToUIElement(property_node, line_indexes_map);
+ } else if (property_name == "AXIndexForTextMarker") { // TextMarker
+ param = PropertyNodeToTextMarker(property_node, line_indexes_map);
+ }
+
+ return param;
+}
+
+// NSNumber. Format: integer.
+NSNumber* AccessibilityTreeFormatterMac::PropertyNodeToInt(
+ const PropertyNode& propnode) const {
+ if (propnode.parameters.size() != 1) {
+ INT_FAIL(propnode, "single argument is expected")
+ }
+
+ const auto& intnode = propnode.parameters[0];
+ base::Optional<int> param = intnode.AsInt();
+ if (!param) {
+ INT_FAIL(propnode, "not a number")
+ }
+ return [NSNumber numberWithInt:*param];
+}
+
+// NSArray of two NSNumber. Format: [integer, integer].
+NSArray* AccessibilityTreeFormatterMac::PropertyNodeToIntArray(
+ const PropertyNode& propnode) const {
+ if (propnode.parameters.size() != 1) {
+ INTARRAY_FAIL(propnode, "single argument is expected")
+ }
+
+ const auto& arraynode = propnode.parameters[0];
+ if (arraynode.name_or_value != base::ASCIIToUTF16("[]")) {
+ INTARRAY_FAIL(propnode, "not array")
+ }
+
+ NSMutableArray* array =
+ [[NSMutableArray alloc] initWithCapacity:arraynode.parameters.size()];
+ for (const auto& paramnode : arraynode.parameters) {
+ base::Optional<int> param = paramnode.AsInt();
+ if (!param) {
+ INTARRAY_FAIL(propnode, paramnode.name_or_value +
+ base::UTF8ToUTF16(" is not a number"))
+ }
+ [array addObject:@(*param)];
+ }
+ return array;
+}
+
+// NSRange. Format: {loc: integer, len: integer}.
+NSValue* AccessibilityTreeFormatterMac::PropertyNodeToRange(
+ const PropertyNode& propnode) const {
+ if (propnode.parameters.size() != 1) {
+ NSRANGE_FAIL(propnode, "single argument is expected")
+ }
+
+ const auto& dictnode = propnode.parameters[0];
+ if (!dictnode.IsDict()) {
+ NSRANGE_FAIL(propnode, "dictionary is expected")
+ }
+
+ base::Optional<int> loc = dictnode.FindIntKey("loc");
+ if (!loc) {
+ NSRANGE_FAIL(propnode, "no loc or loc is not a number")
+ }
+
+ base::Optional<int> len = dictnode.FindIntKey("len");
+ if (!len) {
+ NSRANGE_FAIL(propnode, "no len or len is not a number")
+ }
+
+ return [NSValue valueWithRange:NSMakeRange(*loc, *len)];
+}
+
+// UIElement. Format: :line_num.
+gfx::NativeViewAccessible
+AccessibilityTreeFormatterMac::PropertyNodeToUIElement(
+ const PropertyNode& propnode,
+ const LineIndexesMap& line_indexes_map) const {
+ if (propnode.parameters.size() != 1) {
+ UIELEMENT_FAIL(propnode, "single argument is expected")
+ }
+
+ gfx::NativeViewAccessible uielement =
+ LineIndexToNode(propnode.parameters[0].name_or_value, line_indexes_map);
+ if (!uielement) {
+ UIELEMENT_FAIL(propnode, "no corresponding UIElement was found in the tree")
+ }
+ return uielement;
+}
+
+id AccessibilityTreeFormatterMac::PropertyNodeToTextMarker(
+ const PropertyNode& propnode,
+ const LineIndexesMap& line_indexes_map) const {
+ if (propnode.parameters.size() != 1) {
+ TEXTMARKER_FAIL(propnode, "single argument is expected")
+ }
+
+ const auto& tmnode = propnode.parameters[0];
+ if (!tmnode.IsDict()) {
+ TEXTMARKER_FAIL(propnode, "dictionary is expected")
+ }
+ if (tmnode.parameters.size() != 3) {
+ TEXTMARKER_FAIL(propnode, "wrong number of dictionary elements")
+ }
+
+ BrowserAccessibilityCocoa* anchor_cocoa =
+ LineIndexToNode(tmnode.parameters[0].name_or_value, line_indexes_map);
+ if (!anchor_cocoa) {
+ TEXTMARKER_FAIL(propnode, "1st argument: wrong anchor")
+ }
+
+ base::Optional<int> offset = tmnode.parameters[1].AsInt();
+ if (!offset) {
+ TEXTMARKER_FAIL(propnode, "2nd argument: wrong offset")
+ }
+
+ ax::mojom::TextAffinity affinity;
+ const base::string16& affinity_str = tmnode.parameters[2].name_or_value;
+ if (affinity_str == base::UTF8ToUTF16("none")) {
+ affinity = ax::mojom::TextAffinity::kNone;
+ } else if (affinity_str == base::UTF8ToUTF16("down")) {
+ affinity = ax::mojom::TextAffinity::kDownstream;
+ } else if (affinity_str == base::UTF8ToUTF16("up")) {
+ affinity = ax::mojom::TextAffinity::kUpstream;
+ } else {
+ TEXTMARKER_FAIL(propnode, "3rd argument: wrong affinity")
+ }
+
+ return content::AXTextMarkerFrom(anchor_cocoa, *offset, affinity);
+}
+
+base::Value AccessibilityTreeFormatterMac::PopulateSize(
+ const BrowserAccessibilityCocoa* cocoa_node) const {
+ base::Value size(base::Value::Type::DICTIONARY);
+ NSSize node_size = [[cocoa_node size] sizeValue];
+ size.SetIntPath(kHeightDictAttr, static_cast<int>(node_size.height));
+ size.SetIntPath(kWidthDictAttr, static_cast<int>(node_size.width));
+ return size;
+}
+
+base::Value AccessibilityTreeFormatterMac::PopulatePosition(
+ const BrowserAccessibilityCocoa* cocoa_node) const {
+ BrowserAccessibility* node = [cocoa_node owner];
+ BrowserAccessibilityManager* root_manager = node->manager()->GetRootManager();
+ DCHECK(root_manager);
+
+ // The NSAccessibility position of an object is in global coordinates and
+ // based on the lower-left corner of the object. To make this easier and less
+ // confusing, convert it to local window coordinates using the top-left
+ // corner when dumping the position.
+ BrowserAccessibility* root = root_manager->GetRoot();
+ BrowserAccessibilityCocoa* cocoa_root = ToBrowserAccessibilityCocoa(root);
+ NSPoint root_position = [[cocoa_root position] pointValue];
+ NSSize root_size = [[cocoa_root size] sizeValue];
+ int root_top = -static_cast<int>(root_position.y + root_size.height);
+ int root_left = static_cast<int>(root_position.x);
+
+ NSPoint node_position = [[cocoa_node position] pointValue];
+ NSSize node_size = [[cocoa_node size] sizeValue];
+
+ base::Value position(base::Value::Type::DICTIONARY);
+ position.SetIntPath(kXCoordDictAttr,
+ static_cast<int>(node_position.x - root_left));
+ position.SetIntPath(
+ kYCoordDictAttr,
+ static_cast<int>(-node_position.y - node_size.height - root_top));
+ return position;
+}
+
+base::Value AccessibilityTreeFormatterMac::PopulateObject(
+ id value,
+ const LineIndexesMap& line_indexes_map) const {
+ if (value == nil) {
+ return base::Value(kNULLValue);
+ }
+
+ // NSArray
+ if ([value isKindOfClass:[NSArray class]]) {
+ return PopulateArray((NSArray*)value, line_indexes_map);
+ }
+
+ // NSNumber
+ if ([value isKindOfClass:[NSNumber class]]) {
+ return base::Value([value intValue]);
+ }
+
+ // NSRange
+ if ([value isKindOfClass:[NSValue class]] &&
+ 0 == strcmp([value objCType], @encode(NSRange))) {
+ return PopulateRange([value rangeValue]);
+ }
+
+ // AXTextMarker
+ if (content::IsAXTextMarker(value)) {
+ return PopulateTextPosition(content::AXTextMarkerToPosition(value).get(),
+ line_indexes_map);
+ }
+
+ // AXTextMarkerRange
+ if (content::IsAXTextMarkerRange(value)) {
+ return PopulateTextMarkerRange(value, line_indexes_map);
+ }
+
+ // Accessible object
+ if ([value isKindOfClass:[BrowserAccessibilityCocoa class]]) {
+ return base::Value(NodeToLineIndex(value, line_indexes_map));
+ }
+
+ // Scalar value.
+ return base::Value(
+ SysNSStringToUTF16([NSString stringWithFormat:@"%@", value]));
+}
+
+base::Value AccessibilityTreeFormatterMac::PopulateRange(
+ NSRange node_range) const {
+ base::Value range(base::Value::Type::DICTIONARY);
+ range.SetIntPath(kRangeLocDictAttr, static_cast<int>(node_range.location));
+ range.SetIntPath(kRangeLenDictAttr, static_cast<int>(node_range.length));
+ return range;
+}
+
+base::Value AccessibilityTreeFormatterMac::PopulateTextPosition(
+ BrowserAccessibilityPosition::AXPositionInstance::pointer position,
+ const LineIndexesMap& line_indexes_map) const {
+ if (position->IsNullPosition()) {
+ return base::Value(kNULLValue);
+ }
+
+ BrowserAccessibility* anchor = position->GetAnchor();
+ BrowserAccessibilityCocoa* cocoa_anchor = ToBrowserAccessibilityCocoa(anchor);
+
+ std::string affinity;
+ switch (position->affinity()) {
+ case ax::mojom::TextAffinity::kNone:
+ affinity = "none";
+ break;
+ case ax::mojom::TextAffinity::kDownstream:
+ affinity = "down";
+ break;
+ case ax::mojom::TextAffinity::kUpstream:
+ affinity = "up";
+ break;
+ }
+
+ base::Value set(base::Value::Type::DICTIONARY);
+ const std::string setkey_prefix = kSetKeyPrefixDictAttr;
+ set.SetStringPath(setkey_prefix + "index1_anchor",
+ NodeToLineIndex(cocoa_anchor, line_indexes_map));
+ set.SetIntPath(setkey_prefix + "index2_offset", position->text_offset());
+ set.SetStringPath(setkey_prefix + "index3_affinity",
+ kConstValuePrefix + affinity);
+ return set;
+}
+
+base::Value AccessibilityTreeFormatterMac::PopulateTextMarkerRange(
+ id object,
+ const LineIndexesMap& line_indexes_map) const {
+ auto range = content::AXTextMarkerRangeToRange(object);
+ if (range.IsNull()) {
+ return base::Value(kNULLValue);
+ }
+
+ base::Value dict(base::Value::Type::DICTIONARY);
+ dict.SetPath("anchor",
+ PopulateTextPosition(range.anchor(), line_indexes_map));
+ dict.SetPath("focus", PopulateTextPosition(range.focus(), line_indexes_map));
+ return dict;
+}
+
+base::Value AccessibilityTreeFormatterMac::PopulateArray(
+ NSArray* node_array,
+ const LineIndexesMap& line_indexes_map) const {
+ base::Value list(base::Value::Type::LIST);
+ for (NSUInteger i = 0; i < [node_array count]; i++)
+ list.Append(PopulateObject([node_array objectAtIndex:i], line_indexes_map));
+ return list;
+}
+
+std::string AccessibilityTreeFormatterMac::NodeToLineIndex(
+ id cocoa_node,
+ const LineIndexesMap& line_indexes_map) const {
+ std::string line_index = ":unknown";
+ auto index_iterator = line_indexes_map.find(cocoa_node);
+ if (index_iterator != line_indexes_map.end()) {
+ line_index = base::UTF16ToUTF8(index_iterator->second);
+ }
+ return kConstValuePrefix + line_index;
+}
+
+gfx::NativeViewAccessible AccessibilityTreeFormatterMac::LineIndexToNode(
+ const base::string16 line_index,
+ const LineIndexesMap& line_indexes_map) const {
+ for (std::pair<const gfx::NativeViewAccessible, base::string16> item :
+ line_indexes_map) {
+ if (item.second == line_index) {
+ return item.first;
+ }
+ }
+ return nil;
}
base::string16 AccessibilityTreeFormatterMac::ProcessTreeForOutput(
@@ -314,47 +662,94 @@ base::string16 AccessibilityTreeFormatterMac::ProcessTreeForOutput(
// Expose all other attributes.
for (auto item : dict.DictItems()) {
- if (item.second.is_string()) {
- if (item.first != role_attr && item.first != subrole_attr) {
- WriteAttribute(false,
- StringPrintf("%s='%s'", item.first.c_str(),
- item.second.GetString().c_str()),
- &line);
- }
+ if (item.second.is_string() &&
+ (item.first == role_attr || item.first == subrole_attr)) {
continue;
}
// Special processing for position and size.
- if (item.second.is_dict()) {
- if (item.first == kPositionDictAttr) {
- WriteAttribute(false,
- FormatCoordinates(
- base::Value::AsDictionaryValue(item.second),
- kPositionDictAttr, kXCoordDictAttr, kYCoordDictAttr),
- &line);
- continue;
- }
- if (item.first == kSizeDictAttr) {
- WriteAttribute(
- false,
- FormatCoordinates(base::Value::AsDictionaryValue(item.second),
- kSizeDictAttr, kWidthDictAttr, kHeightDictAttr),
- &line);
- continue;
- }
+ if (item.first == kPositionDictAttr) {
+ WriteAttribute(false,
+ FormatCoordinates(
+ base::Value::AsDictionaryValue(item.second),
+ kPositionDictAttr, kXCoordDictAttr, kYCoordDictAttr),
+ &line);
+ continue;
+ }
+ if (item.first == kSizeDictAttr) {
+ WriteAttribute(
+ false,
+ FormatCoordinates(base::Value::AsDictionaryValue(item.second),
+ kSizeDictAttr, kWidthDictAttr, kHeightDictAttr),
+ &line);
+ continue;
}
- // Write everything else as JSON.
- std::string json_value;
- base::JSONWriter::Write(item.second, &json_value);
+ // Write formatted value.
+ std::string formatted_value = FormatAttributeValue(item.second);
WriteAttribute(
- false, StringPrintf("%s=%s", item.first.c_str(), json_value.c_str()),
+ false,
+ StringPrintf("%s=%s", item.first.c_str(), formatted_value.c_str()),
&line);
}
return line;
}
+std::string AccessibilityTreeFormatterMac::FormatAttributeValue(
+ const base::Value& value) {
+ // String.
+ if (value.is_string()) {
+ // Special handling for constants which are exposed as is, i.e. with no
+ // quotation marks.
+ std::string const_prefix = kConstValuePrefix;
+ if (base::StartsWith(value.GetString(), const_prefix,
+ base::CompareCase::SENSITIVE)) {
+ return value.GetString().substr(const_prefix.length());
+ }
+ return "'" + value.GetString() + "'";
+ }
+
+ // Integer.
+ if (value.is_int()) {
+ return base::NumberToString(value.GetInt());
+ }
+
+ // List: exposed as [value1, ..., valueN];
+ if (value.is_list()) {
+ std::string output;
+ for (const auto& item : value.GetList()) {
+ if (!output.empty()) {
+ output += ", ";
+ }
+ output += FormatAttributeValue(item);
+ }
+ return "[" + output + "]";
+ }
+
+ // Dictionary. Exposed as {key1: value1, ..., keyN: valueN}. Set-like
+ // dictionary is exposed as {value1, ..., valueN}.
+ if (value.is_dict()) {
+ const std::string setkey_prefix(kSetKeyPrefixDictAttr);
+ std::string output;
+ for (const auto& item : value.DictItems()) {
+ if (!output.empty()) {
+ output += ", ";
+ }
+ // Special set-like dictionaries handling: keys are prefixed by
+ // "_setkey_".
+ if (base::StartsWith(item.first, setkey_prefix,
+ base::CompareCase::SENSITIVE)) {
+ output += FormatAttributeValue(item.second);
+ } else {
+ output += item.first + ": " + FormatAttributeValue(item.second);
+ }
+ }
+ return "{" + output + "}";
+ }
+ return "";
+}
+
base::FilePath::StringType
AccessibilityTreeFormatterMac::GetExpectedFileSuffix() {
return FILE_PATH_LITERAL("-expected-mac.txt");
diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter_mac_browsertest.mm b/chromium/content/browser/accessibility/accessibility_tree_formatter_mac_browsertest.mm
new file mode 100644
index 00000000000..5f6db82d33d
--- /dev/null
+++ b/chromium/content/browser/accessibility/accessibility_tree_formatter_mac_browsertest.mm
@@ -0,0 +1,293 @@
+// Copyright 2020 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_base.h"
+#include "content/browser/accessibility/browser_accessibility.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/test/accessibility_notification_waiter.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/browser/shell.h"
+#include "net/base/data_url.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/gtest_mac.h"
+#include "url/gurl.h"
+
+namespace content {
+
+namespace {
+
+class AccessibilityTreeFormatterMacBrowserTest : public ContentBrowserTest {
+ public:
+ AccessibilityTreeFormatterMacBrowserTest() {}
+ ~AccessibilityTreeFormatterMacBrowserTest() override {}
+
+ // Checks the formatted accessible tree for the given data URL.
+ void TestAndCheck(const char* url,
+ const std::vector<const char*>& filters,
+ const char* expected) const;
+
+ // Tests wrong parameters for an attribute in a single run
+ void TestWrongParameters(const char* url,
+ const std::vector<const char*>& parameters,
+ const char* filter_pattern,
+ const char* expected_pattern) const;
+
+ protected:
+ BrowserAccessibilityManager* GetManager() const {
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(shell()->web_contents());
+ return web_contents->GetRootBrowserAccessibilityManager();
+ }
+};
+
+void AccessibilityTreeFormatterMacBrowserTest::TestAndCheck(
+ const char* url,
+ const std::vector<const char*>& filters,
+ const char* expected) const {
+ EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
+
+ AccessibilityNotificationWaiter waiter(shell()->web_contents(),
+ ui::kAXModeComplete,
+ ax::mojom::Event::kLoadComplete);
+
+ EXPECT_TRUE(NavigateToURL(shell(), GURL(url)));
+ waiter.WaitForNotification();
+
+ // Set property filters
+ std::unique_ptr<AccessibilityTreeFormatter> formatter =
+ AccessibilityTreeFormatter::Create();
+
+ std::vector<AccessibilityTreeFormatter::PropertyFilter> property_filters;
+
+ for (const char* filter : filters) {
+ property_filters.push_back(AccessibilityTreeFormatter::PropertyFilter(
+ base::UTF8ToUTF16(filter),
+ AccessibilityTreeFormatter::PropertyFilter::ALLOW_EMPTY));
+ }
+
+ formatter->AddDefaultFilters(&property_filters);
+ formatter->SetPropertyFilters(property_filters);
+
+ // Format the tree
+ BrowserAccessibility* root = GetManager()->GetRoot();
+ CHECK(root);
+
+ base::string16 contents;
+ formatter->FormatAccessibilityTreeForTesting(root, &contents);
+
+ auto got = base::UTF16ToUTF8(contents);
+ EXPECT_EQ(got, expected);
+}
+
+void AccessibilityTreeFormatterMacBrowserTest::TestWrongParameters(
+ const char* url,
+ const std::vector<const char*>& parameters,
+ const char* filter_pattern,
+ const char* expected_pattern) const {
+ std::string placeholder("Argument");
+ size_t expected_pos = std::string(expected_pattern).find(placeholder);
+ ASSERT_FALSE(expected_pos == std::string::npos);
+
+ size_t filter_pos = std::string(filter_pattern).find(placeholder);
+ ASSERT_FALSE(filter_pos == std::string::npos);
+
+ for (const char* parameter : parameters) {
+ std::string expected(expected_pattern);
+ expected.replace(expected_pos, placeholder.length(), parameter);
+
+ std::string filter(filter_pattern);
+ filter.replace(filter_pos, placeholder.length(), parameter);
+
+ TestAndCheck(url, {filter.c_str()}, expected.c_str());
+ }
+}
+
+} // namespace
+
+IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
+ DefaultAttributes) {
+ TestAndCheck(R"~~(data:text/html,
+ <input aria-label='input'>)~~",
+ {},
+ R"~~(AXWebArea
+++AXGroup
+++++AXTextField AXDescription='input'
+)~~");
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
+ LineIndexFilter) {
+ TestAndCheck(R"~~(data:text/html,
+ <input class='input_at_3rd_line'>
+ <input class='input_at_4th_line'>
+ <input class='input_at_5th_line'>)~~",
+ {":3,:5;AXDOMClassList=*"}, R"~~(AXWebArea
+++AXGroup
+++++AXTextField AXDOMClassList=['input_at_3rd_line']
+++++AXTextField
+++++AXTextField AXDOMClassList=['input_at_5th_line']
+)~~");
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
+ Serialize_AXTextMarker) {
+ TestAndCheck(R"~~(data:text/html,
+ <p>Paragraph</p>)~~",
+ {":3;AXStartTextMarker=*"}, R"~~(AXWebArea
+++AXGroup
+++++AXStaticText AXStartTextMarker={:1, 0, down} AXValue='Paragraph'
+)~~");
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
+ Serialize_AXTextMarkerRange) {
+ TestAndCheck(R"~~(data:text/html,
+ <p id='p'>Paragraph</p>
+ <script>
+ window.getSelection().selectAllChildren(document.getElementById('p'));
+ </script>)~~",
+ {":3;AXSelectedTextMarkerRange=*"}, R"~~(AXWebArea
+++AXGroup
+++++AXStaticText AXSelectedTextMarkerRange={anchor: {:3, 0, down}, focus: {:2, -1, down}} AXValue='Paragraph'
+)~~");
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
+ ParameterizedAttributes_Int) {
+ TestAndCheck(R"~~(data:text/html,
+ <p contentEditable='true'>Text</p>)~~",
+ {":2;AXLineForIndex(0)=*"}, R"~~(AXWebArea
+++AXTextArea AXLineForIndex(0)=0 AXValue='Text'
+++++AXStaticText AXValue='Text'
+)~~");
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
+ ParameterizedAttributes_Int_WrongParameters) {
+ TestWrongParameters(R"~~(data:text/html,
+ <p contentEditable='true'>Text</p>)~~",
+ {"1, 2", "NaN"}, ":2;AXLineForIndex(Argument)=*",
+ R"~~(AXWebArea
+++AXTextArea AXLineForIndex(Argument)=ERROR:FAILED_TO_PARSE_ARGS AXValue='Text'
+++++AXStaticText AXValue='Text'
+)~~");
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
+ ParameterizedAttributes_IntArray) {
+ TestAndCheck(R"~~(data:text/html,
+ <table role="grid"><tr><td>CELL</td></tr></table>)~~",
+ {"AXCellForColumnAndRow([0, 0])=*"}, R"~~(AXWebArea
+++AXTable AXCellForColumnAndRow([0, 0])=:4
+++++AXRow
+++++++AXCell
+++++++++AXStaticText AXValue='CELL'
+++++AXColumn
+++++++AXCell
+++++++++AXStaticText AXValue='CELL'
+++++AXGroup
+)~~");
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
+ ParameterizedAttributes_IntArray_NilValue) {
+ TestAndCheck(R"~~(data:text/html,
+ <table role="grid"></table>)~~",
+ {"AXCellForColumnAndRow([0, 0])=*"}, R"~~(AXWebArea
+++AXTable AXCellForColumnAndRow([0, 0])=NULL
+++++AXGroup
+)~~");
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
+ ParameterizedAttributes_IntArray_WrongParameters) {
+ TestWrongParameters(R"~~(data:text/html,
+ <table role="grid"><tr><td>CELL</td></tr></table>)~~",
+ {"0, 0", "{1, 2}", "[1, NaN]", "[NaN, 1]"},
+ "AXCellForColumnAndRow(Argument)=*", R"~~(AXWebArea
+++AXTable AXCellForColumnAndRow(Argument)=ERROR:FAILED_TO_PARSE_ARGS
+++++AXRow
+++++++AXCell
+++++++++AXStaticText AXValue='CELL'
+++++AXColumn
+++++++AXCell
+++++++++AXStaticText AXValue='CELL'
+++++AXGroup
+)~~");
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
+ ParameterizedAttributes_NSRange) {
+ TestAndCheck(R"~~(data:text/html,
+ <p contentEditable='true'>Text</p>)~~",
+ {":2;AXStringForRange({loc: 1, len: 2})=*"}, R"~~(AXWebArea
+++AXTextArea AXStringForRange({loc: 1, len: 2})='ex' AXValue='Text'
+++++AXStaticText AXValue='Text'
+)~~");
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
+ ParameterizedAttributes_NSRange_WrongParameters) {
+ TestWrongParameters(R"~~(data:text/html,
+ <p contentEditable='true'>Text</p>)~~",
+ {"1, 2", "[]", "{loc: 1, leno: 2}", "{loco: 1, len: 2}",
+ "{loc: NaN, len: 2}", "{loc: 2, len: NaN}"},
+ ":2;AXStringForRange(Argument)=*", R"~~(AXWebArea
+++AXTextArea AXStringForRange(Argument)=ERROR:FAILED_TO_PARSE_ARGS AXValue='Text'
+++++AXStaticText AXValue='Text'
+)~~");
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
+ ParameterizedAttributes_UIElement) {
+ TestAndCheck(R"~~(data:text/html,
+ <p contentEditable='true'>Text</p>)~~",
+ {":2;AXIndexForChildUIElement(:3)=*"}, R"~~(AXWebArea
+++AXTextArea AXIndexForChildUIElement(:3)=0 AXValue='Text'
+++++AXStaticText AXValue='Text'
+)~~");
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
+ ParameterizedAttributes_UIElement_WrongParameters) {
+ TestWrongParameters(R"~~(data:text/html,
+ <p contentEditable='true'>Text</p>)~~",
+ {"1, 2", "2", ":4"},
+ ":2;AXIndexForChildUIElement(Argument)=*",
+ R"~~(AXWebArea
+++AXTextArea AXIndexForChildUIElement(Argument)=ERROR:FAILED_TO_PARSE_ARGS AXValue='Text'
+++++AXStaticText AXValue='Text'
+)~~");
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
+ ParameterizedAttributes_TextMarker) {
+ TestAndCheck(R"~~(data:text/html,
+ <p>Text</p>)~~",
+ {":1;AXIndexForTextMarker({:2, 1, down})=*"},
+ R"~~(AXWebArea AXIndexForTextMarker({:2, 1, down})=1
+++AXGroup
+++++AXStaticText AXValue='Text'
+)~~");
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
+ ParameterizedAttributes_TextMarker_WrongParameters) {
+ TestWrongParameters(
+ R"~~(data:text/html,
+ <p>Text</p>)~~",
+ {"1, 2", "2", "{2, 1, down}", "{:2, NaN, down}", "{:2, 1, hoho}"},
+ ":1;AXIndexForTextMarker(Argument)=*",
+ R"~~(AXWebArea AXIndexForTextMarker(Argument)=ERROR:FAILED_TO_PARSE_ARGS
+++AXGroup
+++++AXStaticText AXValue='Text'
+)~~");
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/accessibility_win_browsertest.cc b/chromium/content/browser/accessibility/accessibility_win_browsertest.cc
index bda863e74b4..1c33a05fbef 100644
--- a/chromium/content/browser/accessibility/accessibility_win_browsertest.cc
+++ b/chromium/content/browser/accessibility/accessibility_win_browsertest.cc
@@ -52,6 +52,7 @@
#include "ui/accessibility/accessibility_switches.h"
#include "ui/accessibility/ax_event_generator.h"
#include "ui/accessibility/platform/ax_fragment_root_win.h"
+#include "ui/accessibility/platform/uia_registrar_win.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
@@ -457,7 +458,7 @@ BrowserAccessibility* AccessibilityWinBrowserTest::FindNode(
BrowserAccessibilityManager* AccessibilityWinBrowserTest::GetManager() {
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
- return web_contents->GetRootBrowserAccessibilityManager();
+ return web_contents->GetOrCreateRootBrowserAccessibilityManager();
}
// Retrieve the accessibility node in the subtree that matches the accessibility
@@ -882,10 +883,12 @@ class WebContentsUIAParentNavigationInDestroyedWatcher
private:
// Overridden WebContentsObserver methods.
void WebContentsDestroyed() override {
- // Test navigating to the parent node via UIA
+ // Test navigating to the parent node via UIA.
Microsoft::WRL::ComPtr<IUIAutomationElement> parent;
tree_walker_->GetParentElement(root_.Get(), &parent);
- CHECK(parent.Get() == nullptr);
+
+ // The original bug resulted in a crash when making this call.
+ parent.Get();
run_loop_.Quit();
}
@@ -2633,13 +2636,13 @@ IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, TestSetSelection) {
hr = input_text->get_selection(0, &start_offset, &end_offset);
EXPECT_EQ(S_OK, hr);
- // Start and end offsets are always swapped to be in ascending order.
+ // Start and end offsets should always be swapped to be in ascending order
+ // according to the IA2 Spec.
EXPECT_EQ(1, start_offset);
EXPECT_EQ(contents_string_length, end_offset);
}
-IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
- DISABLED_TestSetSelectionRanges) {
+IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, TestSetSelectionRanges) {
Microsoft::WRL::ComPtr<IAccessibleText> input_text;
SetUpInputField(&input_text);
Microsoft::WRL::ComPtr<IAccessible2_4> ax_input;
@@ -2672,11 +2675,15 @@ IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
EXPECT_EQ(S_OK, hr);
EXPECT_EQ(1, n_ranges);
ASSERT_NE(nullptr, ranges);
+ ASSERT_NE(nullptr, ranges[0].anchor);
EXPECT_EQ(ax_input.Get(), ranges[0].anchor);
EXPECT_EQ(0, ranges[0].anchorOffset);
+ ASSERT_NE(nullptr, ranges[0].active);
EXPECT_EQ(ax_input.Get(), ranges[0].active);
EXPECT_EQ(contents_string_length, ranges[0].activeOffset);
+ ranges[0].anchor->Release();
+ ranges[0].active->Release();
n_ranges = 1;
ranges =
reinterpret_cast<IA2Range*>(CoTaskMemRealloc(ranges, sizeof(IA2Range)));
@@ -2690,14 +2697,22 @@ IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
ranges = nullptr;
n_ranges = 0;
+ // For native plain text fields, e.g. input and textarea, anchor and active
+ // offsets are always swapped to be in ascending order by the renderer. The
+ // selection's directionality is lost.
hr = ax_input->get_selectionRanges(&ranges, &n_ranges);
EXPECT_EQ(S_OK, hr);
EXPECT_EQ(1, n_ranges);
ASSERT_NE(nullptr, ranges);
+ ASSERT_NE(nullptr, ranges[0].anchor);
EXPECT_EQ(ax_input.Get(), ranges[0].anchor);
- EXPECT_EQ(contents_string_length, ranges[0].anchorOffset);
+ EXPECT_EQ(1, ranges[0].anchorOffset);
+ ASSERT_NE(nullptr, ranges[0].active);
EXPECT_EQ(ax_input.Get(), ranges[0].active);
- EXPECT_EQ(1, ranges[0].activeOffset);
+ EXPECT_EQ(contents_string_length, ranges[0].activeOffset);
+
+ ranges[0].anchor->Release();
+ ranges[0].active->Release();
CoTaskMemFree(ranges);
ranges = nullptr;
}
@@ -2744,7 +2759,7 @@ IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, TestMultiLineSetSelection) {
}
IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
- DISABLED_TestMultiLineSetSelectionRanges) {
+ TestMultiLineSetSelectionRanges) {
Microsoft::WRL::ComPtr<IAccessibleText> textarea_text;
SetUpTextareaField(&textarea_text);
Microsoft::WRL::ComPtr<IAccessible2_4> ax_textarea;
@@ -2777,32 +2792,46 @@ IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
EXPECT_EQ(S_OK, hr);
EXPECT_EQ(1, n_ranges);
ASSERT_NE(nullptr, ranges);
+ ASSERT_NE(nullptr, ranges[0].anchor);
EXPECT_EQ(ax_textarea.Get(), ranges[0].anchor);
EXPECT_EQ(0, ranges[0].anchorOffset);
+ ASSERT_NE(nullptr, ranges[0].active);
EXPECT_EQ(ax_textarea.Get(), ranges[0].active);
EXPECT_EQ(contents_string_length, ranges[0].activeOffset);
+ ranges[0].anchor->Release();
+ ranges[0].active->Release();
n_ranges = 1;
ranges =
reinterpret_cast<IA2Range*>(CoTaskMemRealloc(ranges, sizeof(IA2Range)));
+
ranges[0].anchor = ax_textarea.Get();
ranges[0].anchorOffset = contents_string_length - 1;
ranges[0].active = ax_textarea.Get();
ranges[0].activeOffset = 0;
EXPECT_HRESULT_SUCCEEDED(ax_textarea->setSelectionRanges(n_ranges, ranges));
waiter.WaitForNotification();
+
CoTaskMemFree(ranges);
ranges = nullptr;
n_ranges = 0;
+ // For native plain text fields, e.g. input and textarea, anchor and active
+ // offsets are always swapped to be in ascending order by the renderer. The
+ // selection's directionality is lost.
hr = ax_textarea->get_selectionRanges(&ranges, &n_ranges);
EXPECT_EQ(S_OK, hr);
EXPECT_EQ(1, n_ranges);
ASSERT_NE(nullptr, ranges);
+ ASSERT_NE(nullptr, ranges[0].anchor);
EXPECT_EQ(ax_textarea.Get(), ranges[0].anchor);
- EXPECT_EQ(contents_string_length - 1, ranges[0].anchorOffset);
+ EXPECT_EQ(0, ranges[0].anchorOffset);
+ ASSERT_NE(nullptr, ranges[0].active);
EXPECT_EQ(ax_textarea.Get(), ranges[0].active);
- EXPECT_EQ(0, ranges[0].activeOffset);
+ EXPECT_EQ(contents_string_length - 1, ranges[0].activeOffset);
+
+ ranges[0].anchor->Release();
+ ranges[0].active->Release();
CoTaskMemFree(ranges);
ranges = nullptr;
}
@@ -2846,7 +2875,7 @@ IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
}
IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
- DISABLED_TestStaticTextSetSelectionRanges) {
+ TestStaticTextSetSelectionRanges) {
Microsoft::WRL::ComPtr<IAccessibleText> paragraph_text;
SetUpSampleParagraph(&paragraph_text);
Microsoft::WRL::ComPtr<IAccessible2_4> ax_paragraph;
@@ -2856,6 +2885,20 @@ IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
ASSERT_HRESULT_SUCCEEDED(ax_paragraph->get_accChildCount(&child_count));
ASSERT_LT(0, child_count);
+ // IAccessible retrieves children using an one-based index.
+ base::win::ScopedVariant one_variant(1);
+ base::win::ScopedVariant child_count_variant(child_count);
+
+ Microsoft::WRL::ComPtr<IDispatch> ax_first_static_text_child;
+ ASSERT_HRESULT_SUCCEEDED(
+ ax_paragraph->get_accChild(one_variant, &ax_first_static_text_child));
+ ASSERT_NE(nullptr, ax_first_static_text_child);
+
+ Microsoft::WRL::ComPtr<IDispatch> ax_last_static_text_child;
+ ASSERT_HRESULT_SUCCEEDED(ax_paragraph->get_accChild(
+ child_count_variant, &ax_last_static_text_child));
+ ASSERT_NE(nullptr, ax_last_static_text_child);
+
LONG n_ranges = 1;
IA2Range* ranges =
reinterpret_cast<IA2Range*>(CoTaskMemAlloc(sizeof(IA2Range)));
@@ -2868,6 +2911,8 @@ IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
ranges[0].activeOffset = child_count + 1;
EXPECT_HRESULT_FAILED(ax_paragraph->setSelectionRanges(n_ranges, ranges));
+ // Select the entire paragraph's contents but not the paragraph itself, i.e.
+ // in the selected HTML the "p" tag will not be included.
ranges[0].activeOffset = child_count;
AccessibilityNotificationWaiter waiter(
shell()->web_contents(), ui::kAXModeComplete,
@@ -2882,14 +2927,21 @@ IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
EXPECT_EQ(S_OK, hr);
EXPECT_EQ(1, n_ranges);
ASSERT_NE(nullptr, ranges);
- EXPECT_EQ(ax_paragraph.Get(), ranges[0].anchor);
+ ASSERT_NE(nullptr, ranges[0].anchor);
+ EXPECT_EQ(ax_first_static_text_child.Get(), ranges[0].anchor);
EXPECT_EQ(0, ranges[0].anchorOffset);
+ ASSERT_NE(nullptr, ranges[0].active);
EXPECT_EQ(ax_paragraph.Get(), ranges[0].active);
EXPECT_EQ(child_count, ranges[0].activeOffset);
+ ranges[0].anchor->Release();
+ ranges[0].active->Release();
n_ranges = 1;
ranges =
reinterpret_cast<IA2Range*>(CoTaskMemRealloc(ranges, sizeof(IA2Range)));
+
+ // Select from the beginning of the paragraph's text up to the start of the
+ // last static text child.
ranges[0].anchor = ax_paragraph.Get();
ranges[0].anchorOffset = child_count - 1;
ranges[0].active = ax_paragraph.Get();
@@ -2904,11 +2956,344 @@ IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
EXPECT_EQ(S_OK, hr);
EXPECT_EQ(1, n_ranges);
ASSERT_NE(nullptr, ranges);
- EXPECT_EQ(ax_paragraph.Get(), ranges[0].anchor);
- EXPECT_EQ(static_cast<LONG>(InputContentsString().size() - 1),
- ranges[0].anchorOffset);
- EXPECT_EQ(ax_paragraph.Get(), ranges[0].active);
+ ASSERT_NE(nullptr, ranges[0].anchor);
+ EXPECT_EQ(ax_last_static_text_child.Get(), ranges[0].anchor);
+ EXPECT_EQ(0, ranges[0].anchorOffset);
+ ASSERT_NE(nullptr, ranges[0].active);
+ EXPECT_EQ(ax_first_static_text_child.Get(), ranges[0].active);
+ EXPECT_EQ(0, ranges[0].activeOffset);
+
+ ranges[0].anchor->Release();
+ ranges[0].active->Release();
+ CoTaskMemFree(ranges);
+ ranges = nullptr;
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
+ SetSelectionWithIgnoredObjects) {
+ LoadInitialAccessibilityTreeFromHtml(R"HTML(<!DOCTYPE html>
+ <html>
+ <body>
+ <ul>
+ <li>
+ <div role="presentation"></div>
+ <p role="presentation">
+ <span>Banana</span>
+ </p>
+ <span>fruit.</span>
+ </li>
+ </ul>
+ </body>
+ </html>)HTML");
+
+ BrowserAccessibility* list_item = FindNode(ax::mojom::Role::kListItem, "");
+ ASSERT_NE(nullptr, list_item);
+ gfx::NativeViewAccessible list_item_win =
+ list_item->GetNativeViewAccessible();
+ ASSERT_NE(nullptr, list_item_win);
+
+ Microsoft::WRL::ComPtr<IAccessibleText> list_item_text;
+ ASSERT_HRESULT_SUCCEEDED(
+ list_item_win->QueryInterface(IID_PPV_ARGS(&list_item_text)));
+
+ // The hypertext expose by "list_item_text" includes an embedded object
+ // character for the list bullet and the joined word "Bananafruit.". The word
+ // "Banana" is exposed as text because its container paragraph is ignored.
+ LONG n_characters;
+ ASSERT_HRESULT_SUCCEEDED(list_item_text->get_nCharacters(&n_characters));
+ ASSERT_EQ(13, n_characters);
+
+ AccessibilityNotificationWaiter waiter(
+ shell()->web_contents(), ui::kAXModeComplete,
+ ax::mojom::Event::kDocumentSelectionChanged);
+
+ // First select the whole of the text found in the hypertext.
+ LONG start_offset = 0;
+ LONG end_offset = n_characters;
+ EXPECT_HRESULT_SUCCEEDED(
+ list_item_text->setSelection(0, start_offset, end_offset));
+ waiter.WaitForNotification();
+
+ HRESULT hr = list_item_text->get_selection(0, &start_offset, &end_offset);
+ EXPECT_EQ(S_OK, hr);
+ EXPECT_EQ(0, start_offset);
+ EXPECT_EQ(n_characters, end_offset);
+
+ // Select only the list bullet.
+ start_offset = 0;
+ end_offset = 1;
+ EXPECT_HRESULT_SUCCEEDED(
+ list_item_text->setSelection(0, start_offset, end_offset));
+ waiter.WaitForNotification();
+
+ hr = list_item_text->get_selection(0, &start_offset, &end_offset);
+ EXPECT_EQ(S_OK, hr);
+ EXPECT_EQ(0, start_offset);
+ EXPECT_EQ(1, end_offset);
+
+ // Select the word "Banana" in the ignored paragraph.
+ start_offset = 1;
+ end_offset = 7;
+ EXPECT_HRESULT_SUCCEEDED(
+ list_item_text->setSelection(0, start_offset, end_offset));
+ waiter.WaitForNotification();
+
+ hr = list_item_text->get_selection(0, &start_offset, &end_offset);
+ EXPECT_EQ(S_OK, hr);
+ EXPECT_EQ(1, start_offset);
+ EXPECT_EQ(7, end_offset);
+
+ // Select both the list bullet and the word "Banana" in the ignored paragraph.
+ start_offset = 0;
+ end_offset = 7;
+ EXPECT_HRESULT_SUCCEEDED(
+ list_item_text->setSelection(0, start_offset, end_offset));
+ waiter.WaitForNotification();
+
+ hr = list_item_text->get_selection(0, &start_offset, &end_offset);
+ EXPECT_EQ(S_OK, hr);
+ EXPECT_EQ(0, start_offset);
+ EXPECT_EQ(7, end_offset);
+
+ // Select the joined word "Bananafruit." both in the ignored paragraph and in
+ // the unignored span.
+ start_offset = 1;
+ end_offset = n_characters;
+ EXPECT_HRESULT_SUCCEEDED(
+ list_item_text->setSelection(0, start_offset, end_offset));
+ waiter.WaitForNotification();
+
+ hr = list_item_text->get_selection(0, &start_offset, &end_offset);
+ EXPECT_EQ(S_OK, hr);
+ EXPECT_EQ(1, start_offset);
+ EXPECT_EQ(n_characters, end_offset);
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
+ SetSelectionRangesWithIgnoredObjects) {
+ LoadInitialAccessibilityTreeFromHtml(R"HTML(<!DOCTYPE html>
+ <html>
+ <body>
+ <ul>
+ <li>
+ <div role="presentation"></div>
+ <p role="presentation">
+ <span>Banana</span>
+ </p>
+ <span>fruit.</span>
+ </li>
+ </ul>
+ </body>
+ </html>)HTML");
+
+ BrowserAccessibility* list_item = FindNode(ax::mojom::Role::kListItem, "");
+ ASSERT_NE(nullptr, list_item);
+ BrowserAccessibility* list = list_item->PlatformGetParent();
+ ASSERT_NE(nullptr, list);
+
+ gfx::NativeViewAccessible list_item_win =
+ list_item->GetNativeViewAccessible();
+ ASSERT_NE(nullptr, list_item_win);
+ gfx::NativeViewAccessible list_win = list->GetNativeViewAccessible();
+ ASSERT_NE(nullptr, list_win);
+
+ Microsoft::WRL::ComPtr<IAccessible2_4> ax_list_item;
+ ASSERT_HRESULT_SUCCEEDED(
+ list_item_win->QueryInterface(IID_PPV_ARGS(&ax_list_item)));
+
+ Microsoft::WRL::ComPtr<IAccessible2_4> ax_list;
+ ASSERT_HRESULT_SUCCEEDED(list_win->QueryInterface(IID_PPV_ARGS(&ax_list)));
+
+ // The list item should contain the list bullet and two static text objects
+ // containing the word "Banana" and the word "fruit". The first static text's
+ // immediate parent, i.e. the paragraph object, is ignored.
+ LONG child_count = 0;
+ ASSERT_HRESULT_SUCCEEDED(ax_list_item->get_accChildCount(&child_count));
+ ASSERT_EQ(3, child_count);
+
+ AccessibilityNotificationWaiter waiter(
+ shell()->web_contents(), ui::kAXModeComplete,
+ ax::mojom::Event::kDocumentSelectionChanged);
+ LONG n_ranges = 1;
+ IA2Range* ranges =
+ reinterpret_cast<IA2Range*>(CoTaskMemAlloc(sizeof(IA2Range)));
+
+ // First select the whole of the list item.
+ ranges[0].anchor = ax_list_item.Get();
+ ranges[0].anchorOffset = 0;
+ ranges[0].active = ax_list_item.Get();
+ ranges[0].activeOffset = child_count;
+ EXPECT_HRESULT_SUCCEEDED(ax_list_item->setSelectionRanges(n_ranges, ranges));
+ waiter.WaitForNotification();
+
+ CoTaskMemFree(ranges);
+ ranges = nullptr;
+ n_ranges = 0;
+
+ HRESULT hr = ax_list->get_selectionRanges(&ranges, &n_ranges);
+ EXPECT_EQ(S_OK, hr);
+ EXPECT_EQ(1, n_ranges);
+ ASSERT_NE(nullptr, ranges);
+ ASSERT_NE(nullptr, ranges[0].anchor);
+ // The list bullet is not included in the DOM tree, so a DOM equivalent
+ // position at the beginning of the list (before the <li>) is computed by
+ // Blink.
+ EXPECT_EQ(ax_list.Get(), ranges[0].anchor);
+ EXPECT_EQ(0, ranges[0].anchorOffset);
+ ASSERT_NE(nullptr, ranges[0].active);
+ EXPECT_EQ(ax_list_item.Get(), ranges[0].active);
+ EXPECT_EQ(child_count, ranges[0].activeOffset);
+
+ ranges[0].anchor->Release();
+ ranges[0].active->Release();
+ n_ranges = 1;
+ ranges =
+ reinterpret_cast<IA2Range*>(CoTaskMemRealloc(ranges, sizeof(IA2Range)));
+
+ // Select only the list bullet.
+ ranges[0].anchor = ax_list_item.Get();
+ ranges[0].anchorOffset = 0;
+ ranges[0].active = ax_list_item.Get();
+ ranges[0].activeOffset = 1;
+ EXPECT_HRESULT_SUCCEEDED(ax_list_item->setSelectionRanges(n_ranges, ranges));
+ waiter.WaitForNotification();
+
+ CoTaskMemFree(ranges);
+ ranges = nullptr;
+ n_ranges = 0;
+
+ hr = ax_list->get_selectionRanges(&ranges, &n_ranges);
+ EXPECT_EQ(S_OK, hr);
+ EXPECT_EQ(1, n_ranges);
+ ASSERT_NE(nullptr, ranges);
+ ASSERT_NE(nullptr, ranges[0].anchor);
+ // The list bullet is not included in the DOM tree, so a DOM equivalent
+ // position at the beginning of the list (before the <li>) is computed by
+ // Blink.
+ EXPECT_EQ(ax_list.Get(), ranges[0].anchor);
+ EXPECT_EQ(0, ranges[0].anchorOffset);
+ ASSERT_NE(nullptr, ranges[0].active);
+ // Child 1 is the static text node with the word "Banana", so this is a
+ // "before text" position on that node.
+ //
+ // This is returned instead of an equivalent position anchored on the list
+ // item, in order to ensure that both a tree position before the first child
+ // and a "before text"position on the first child would always compare as
+ // equal.
+ EXPECT_EQ(list_item->PlatformGetChild(1)->GetNativeViewAccessible(),
+ ranges[0].active);
+ EXPECT_EQ(0, ranges[0].activeOffset);
+
+ ranges[0].anchor->Release();
+ ranges[0].active->Release();
+ n_ranges = 1;
+ ranges =
+ reinterpret_cast<IA2Range*>(CoTaskMemRealloc(ranges, sizeof(IA2Range)));
+
+ // Select the word "Banana" in the ignored paragraph.
+ ranges[0].anchor = ax_list_item.Get();
+ ranges[0].anchorOffset = 1;
+ ranges[0].active = ax_list_item.Get();
+ ranges[0].activeOffset = 2;
+ EXPECT_HRESULT_SUCCEEDED(ax_list_item->setSelectionRanges(n_ranges, ranges));
+ waiter.WaitForNotification();
+
+ CoTaskMemFree(ranges);
+ ranges = nullptr;
+ n_ranges = 0;
+
+ hr = ax_list->get_selectionRanges(&ranges, &n_ranges);
+ EXPECT_EQ(S_OK, hr);
+ EXPECT_EQ(1, n_ranges);
+ ASSERT_NE(nullptr, ranges);
+ ASSERT_NE(nullptr, ranges[0].anchor);
+ // Child 1 is the static text node with the word "Banana", so this is a
+ // "before text" position on that node.
+ EXPECT_EQ(list_item->PlatformGetChild(1)->GetNativeViewAccessible(),
+ ranges[0].anchor);
+ EXPECT_EQ(0, ranges[0].anchorOffset);
+ ASSERT_NE(nullptr, ranges[0].active);
+ // Child 2 is the static text node with the word "fruit.", so this is a
+ // "before text" position on that node.
+ //
+ // This is returned instead of an equivalent position anchored on the list
+ // item, in order to ensure that both a tree position before the second child
+ // and a "before text"position on the second child would always compare as
+ // equal.
+ EXPECT_EQ(list_item->PlatformGetChild(2)->GetNativeViewAccessible(),
+ ranges[0].active);
+ EXPECT_EQ(0, ranges[0].activeOffset);
+
+ ranges[0].anchor->Release();
+ ranges[0].active->Release();
+ n_ranges = 1;
+ ranges =
+ reinterpret_cast<IA2Range*>(CoTaskMemRealloc(ranges, sizeof(IA2Range)));
+
+ // Select the joined word "Bananafruit." both in the ignored paragraph and in
+ // the unignored span.
+ ranges[0].anchor = ax_list_item.Get();
+ ranges[0].anchorOffset = 1;
+ ranges[0].active = ax_list_item.Get();
+ ranges[0].activeOffset = child_count;
+ EXPECT_HRESULT_SUCCEEDED(ax_list_item->setSelectionRanges(n_ranges, ranges));
+ waiter.WaitForNotification();
+
+ CoTaskMemFree(ranges);
+ ranges = nullptr;
+ n_ranges = 0;
+
+ hr = ax_list->get_selectionRanges(&ranges, &n_ranges);
+ EXPECT_EQ(S_OK, hr);
+ EXPECT_EQ(1, n_ranges);
+ ASSERT_NE(nullptr, ranges);
+ ASSERT_NE(nullptr, ranges[0].anchor);
+ // Child 1 is the static text node with the word "Banana", so this is a
+ // "before text" position on that node.
+ EXPECT_EQ(list_item->PlatformGetChild(1)->GetNativeViewAccessible(),
+ ranges[0].anchor);
+ EXPECT_EQ(0, ranges[0].anchorOffset);
+ ASSERT_NE(nullptr, ranges[0].active);
+ EXPECT_EQ(ax_list_item.Get(), ranges[0].active);
+ EXPECT_EQ(child_count, ranges[0].activeOffset);
+
+ ranges[0].anchor->Release();
+ ranges[0].active->Release();
+ n_ranges = 1;
+ ranges =
+ reinterpret_cast<IA2Range*>(CoTaskMemRealloc(ranges, sizeof(IA2Range)));
+
+ // Select both the list bullet and the word "Banana" in the ignored paragraph.
+ ranges[0].anchor = ax_list_item.Get();
+ ranges[0].anchorOffset = 0;
+ ranges[0].active = ax_list_item.Get();
+ ranges[0].activeOffset = 2;
+ EXPECT_HRESULT_SUCCEEDED(ax_list_item->setSelectionRanges(n_ranges, ranges));
+ waiter.WaitForNotification();
+
+ CoTaskMemFree(ranges);
+ ranges = nullptr;
+ n_ranges = 0;
+
+ hr = ax_list->get_selectionRanges(&ranges, &n_ranges);
+ EXPECT_EQ(S_OK, hr);
+ EXPECT_EQ(1, n_ranges);
+ ASSERT_NE(nullptr, ranges);
+ ASSERT_NE(nullptr, ranges[0].anchor);
+ // The list bullet is not included in the DOM tree, so a DOM equivalent
+ // position at the beginning of the list (before the <li>) is computed by
+ // Blink.
+ EXPECT_EQ(ax_list.Get(), ranges[0].anchor);
+ EXPECT_EQ(0, ranges[0].anchorOffset);
+ ASSERT_NE(nullptr, ranges[0].active);
+ // Child 2 is the static text node with the word "fruit.", so this is a
+ // "before text" position on that node.
+ EXPECT_EQ(list_item->PlatformGetChild(2)->GetNativeViewAccessible(),
+ ranges[0].active);
EXPECT_EQ(0, ranges[0].activeOffset);
+
+ ranges[0].anchor->Release();
+ ranges[0].active->Release();
CoTaskMemFree(ranges);
ranges = nullptr;
}
@@ -3261,7 +3646,7 @@ IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
// "Before".
//
// The embedded object character representing the image is at offset 6.
- for (LONG i = 0; i <= 6; ++i) {
+ for (LONG i = 0; i < 6; ++i) {
CheckTextAtOffset(contenteditable_text, i, IA2_TEXT_BOUNDARY_CHAR, i,
(i + 1), std::wstring(1, expected_hypertext[i]));
}
@@ -3779,8 +4164,7 @@ IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, HasHWNDAfterNavigation) {
// At this point the root of the accessibility tree shouldn't have an HWND
// because we never gave a parent window to the RWHVA.
BrowserAccessibilityManagerWin* manager =
- static_cast<BrowserAccessibilityManagerWin*>(
- web_contents->GetRootBrowserAccessibilityManager());
+ static_cast<BrowserAccessibilityManagerWin*>(GetManager());
ASSERT_EQ(nullptr, manager->GetParentHWND());
// Now add the RWHVA's window to the root window and ensure that we have
@@ -4334,6 +4718,196 @@ IN_PROC_BROWSER_TEST_F(AccessibilityWinUIABrowserTest, TestGetFragmentRoot) {
ASSERT_NE(nullptr, fragment_root.Get());
}
+IN_PROC_BROWSER_TEST_F(AccessibilityWinUIABrowserTest, IA2ElementToUIAElement) {
+ // This test validates looking up an UIA element from an IA2 element.
+ // We start by retrieving an IA2 element then its corresponding unique id. We
+ // then use the unique id to retrieve the corresponding UIA element through
+ // IItemContainerProvider::FindItemByProperty().
+ LoadInitialAccessibilityTreeFromHtml(
+ R"HTML(
+ <html>
+ <div role="button">button</div>
+ <div role="listbox" aria-label="listbox"></div>
+ </html>
+ )HTML");
+
+ // Obtain the fragment root from the top-level HWND.
+ HWND hwnd = shell()->window()->GetHost()->GetAcceleratedWidget();
+ ASSERT_NE(gfx::kNullAcceleratedWidget, hwnd);
+ ui::AXFragmentRootWin* fragment_root =
+ ui::AXFragmentRootWin::GetForAcceleratedWidget(hwnd);
+ ASSERT_NE(nullptr, fragment_root);
+
+ Microsoft::WRL::ComPtr<IItemContainerProvider> item_container_provider;
+ ASSERT_HRESULT_SUCCEEDED(
+ fragment_root->GetNativeViewAccessible()->QueryInterface(
+ IID_PPV_ARGS(&item_container_provider)));
+
+ Microsoft::WRL::ComPtr<IAccessible> document(GetRendererAccessible());
+ std::vector<base::win::ScopedVariant> document_children =
+ GetAllAccessibleChildren(document.Get());
+
+ // Look up button's UIA element from its IA2 element.
+ {
+ // Retrieve button's IA2 element.
+ Microsoft::WRL::ComPtr<IAccessible2> button_ia2;
+ ASSERT_HRESULT_SUCCEEDED(QueryIAccessible2(
+ GetAccessibleFromVariant(document.Get(), document_children[0].AsInput())
+ .Get(),
+ &button_ia2));
+ LONG button_role = 0;
+ ASSERT_HRESULT_SUCCEEDED(button_ia2->role(&button_role));
+ ASSERT_EQ(ROLE_SYSTEM_PUSHBUTTON, button_role);
+
+ // Retrieve button's IA2 unique id.
+ LONG button_unique_id;
+ button_ia2->get_uniqueID(&button_unique_id);
+ base::win::ScopedVariant ia2_unique_id;
+ ia2_unique_id.Set(
+ SysAllocString(base::NumberToString16(button_unique_id).c_str()));
+
+ // Verify we can find the button's UIA element based on its unique id.
+ Microsoft::WRL::ComPtr<IRawElementProviderSimple> button_uia;
+ ASSERT_HRESULT_SUCCEEDED(item_container_provider->FindItemByProperty(
+ nullptr, ui::UiaRegistrarWin::GetInstance().GetUiaUniqueIdPropertyId(),
+ ia2_unique_id, &button_uia));
+
+ // UIA and IA2 elements should have the same unique id.
+ base::win::ScopedVariant uia_unique_id;
+ ASSERT_HRESULT_SUCCEEDED(button_uia->GetPropertyValue(
+ ui::UiaRegistrarWin::GetInstance().GetUiaUniqueIdPropertyId(),
+ uia_unique_id.Receive()));
+ ASSERT_STREQ(ia2_unique_id.ptr()->bstrVal, uia_unique_id.ptr()->bstrVal);
+
+ // Verify the retrieved UIA element is button through its name property.
+ base::win::ScopedVariant name_property;
+ ASSERT_HRESULT_SUCCEEDED(button_uia->GetPropertyValue(
+ UIA_NamePropertyId, name_property.Receive()));
+ ASSERT_EQ(name_property.type(), VT_BSTR);
+ BSTR name_bstr = name_property.ptr()->bstrVal;
+ base::string16 actual_name(name_bstr, ::SysStringLen(name_bstr));
+ ASSERT_EQ(L"button", actual_name);
+
+ // Verify that the button's IA2 element and UIA element are the same through
+ // comparing their IUnknown interfaces.
+ Microsoft::WRL::ComPtr<IUnknown> iunknown_button_from_uia;
+ ASSERT_HRESULT_SUCCEEDED(
+ button_uia->QueryInterface(IID_PPV_ARGS(&iunknown_button_from_uia)));
+
+ Microsoft::WRL::ComPtr<IUnknown> iunknown_button_from_ia2;
+ ASSERT_HRESULT_SUCCEEDED(
+ button_ia2->QueryInterface(IID_PPV_ARGS(&iunknown_button_from_ia2)));
+
+ ASSERT_EQ(iunknown_button_from_uia.Get(), iunknown_button_from_ia2.Get());
+ }
+
+ // Look up listbox's UIA element from its IA2 element.
+ {
+ // Retrieve listbox's IA2 element.
+ Microsoft::WRL::ComPtr<IAccessible2> listbox_ia2;
+ ASSERT_HRESULT_SUCCEEDED(QueryIAccessible2(
+ GetAccessibleFromVariant(document.Get(), document_children[1].AsInput())
+ .Get(),
+ &listbox_ia2));
+ LONG listbox_role = 0;
+ ASSERT_HRESULT_SUCCEEDED(listbox_ia2->role(&listbox_role));
+ ASSERT_EQ(ROLE_SYSTEM_LIST, listbox_role);
+
+ // Retrieve listbox's IA2 unique id.
+ LONG listbox_unique_id;
+ listbox_ia2->get_uniqueID(&listbox_unique_id);
+ base::win::ScopedVariant ia2_unique_id;
+ ia2_unique_id.Set(
+ SysAllocString(base::NumberToString16(listbox_unique_id).c_str()));
+
+ // Verify we can find the listbox's UIA element based on its unique id.
+ Microsoft::WRL::ComPtr<IRawElementProviderSimple> listbox_uia;
+ ASSERT_HRESULT_SUCCEEDED(item_container_provider->FindItemByProperty(
+ nullptr, ui::UiaRegistrarWin::GetInstance().GetUiaUniqueIdPropertyId(),
+ ia2_unique_id, &listbox_uia));
+
+ // UIA and IA2 elements should have the same unique id.
+ base::win::ScopedVariant uia_unique_id;
+ ASSERT_HRESULT_SUCCEEDED(listbox_uia->GetPropertyValue(
+ ui::UiaRegistrarWin::GetInstance().GetUiaUniqueIdPropertyId(),
+ uia_unique_id.Receive()));
+ ASSERT_STREQ(ia2_unique_id.ptr()->bstrVal, uia_unique_id.ptr()->bstrVal);
+
+ // Verify the retrieved UIA element is listbox through its name property.
+ base::win::ScopedVariant name_property;
+ ASSERT_HRESULT_SUCCEEDED(listbox_uia->GetPropertyValue(
+ UIA_NamePropertyId, name_property.Receive()));
+ ASSERT_EQ(name_property.type(), VT_BSTR);
+ BSTR name_bstr = name_property.ptr()->bstrVal;
+ base::string16 actual_name(name_bstr, ::SysStringLen(name_bstr));
+ ASSERT_EQ(L"listbox", actual_name);
+
+ // Verify that the listbox's IA2 element and UIA element are the same
+ // through comparing their IUnknown interfaces.
+ Microsoft::WRL::ComPtr<IUnknown> iunknown_listbox_from_uia;
+ ASSERT_HRESULT_SUCCEEDED(
+ listbox_uia->QueryInterface(IID_PPV_ARGS(&iunknown_listbox_from_uia)));
+
+ Microsoft::WRL::ComPtr<IUnknown> iunknown_listbox_from_ia2;
+ ASSERT_HRESULT_SUCCEEDED(
+ listbox_ia2->QueryInterface(IID_PPV_ARGS(&iunknown_listbox_from_ia2)));
+
+ ASSERT_EQ(iunknown_listbox_from_uia.Get(), iunknown_listbox_from_ia2.Get());
+ }
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityWinUIABrowserTest, UIAElementToIA2Element) {
+ // This test validates looking up an IA2 element from an UIA element.
+ // We start by retrieving an UIA element then its corresponding unique id. We
+ // then use the unique id to retrieve the corresponding IA2 element.
+ LoadInitialAccessibilityTreeFromHtml(
+ R"HTML(
+ <html>
+ <div role="button">button</div>
+ </html>
+ )HTML");
+ Microsoft::WRL::ComPtr<IRawElementProviderSimple> button_uia =
+ QueryInterfaceFromNode<IRawElementProviderSimple>(
+ FindNode(ax::mojom::Role::kButton, "button"));
+
+ // Retrieve the UIA element's unique id.
+ base::win::ScopedVariant uia_unique_id;
+ ASSERT_HRESULT_SUCCEEDED(button_uia->GetPropertyValue(
+ ui::UiaRegistrarWin::GetInstance().GetUiaUniqueIdPropertyId(),
+ uia_unique_id.Receive()));
+
+ int32_t unique_id_value;
+ ASSERT_EQ(VT_BSTR, uia_unique_id.type());
+ ASSERT_TRUE(
+ base::StringToInt(uia_unique_id.ptr()->bstrVal, &unique_id_value));
+
+ // Retrieve the corresponding IA2 element through the unique id.
+ Microsoft::WRL::ComPtr<IAccessible> document(GetRendererAccessible());
+ base::win::ScopedVariant ia2_unique_id(unique_id_value);
+ Microsoft::WRL::ComPtr<IAccessible2> button_ia2;
+ ASSERT_HRESULT_SUCCEEDED(QueryIAccessible2(
+ GetAccessibleFromVariant(document.Get(), ia2_unique_id.AsInput()).Get(),
+ &button_ia2));
+
+ // Verify that the retrieved IA2 element shares the same unique id as the UIA
+ // element.
+ LONG button_ia2_unique_id;
+ button_ia2->get_uniqueID(&button_ia2_unique_id);
+ ASSERT_EQ(unique_id_value, button_ia2_unique_id);
+
+ // Verify that the IA2 element and UIA element are the same through
+ // comparing their IUnknown interfaces.
+ Microsoft::WRL::ComPtr<IUnknown> iunknown_button_from_uia;
+ ASSERT_HRESULT_SUCCEEDED(
+ button_uia->QueryInterface(IID_PPV_ARGS(&iunknown_button_from_uia)));
+
+ Microsoft::WRL::ComPtr<IUnknown> iunknown_button_from_ia2;
+ ASSERT_HRESULT_SUCCEEDED(
+ button_ia2->QueryInterface(IID_PPV_ARGS(&iunknown_button_from_ia2)));
+
+ ASSERT_EQ(iunknown_button_from_uia.Get(), iunknown_button_from_ia2.Get());
+}
+
IN_PROC_BROWSER_TEST_F(AccessibilityWinUIABrowserTest,
RootElementPropertyValues) {
LoadInitialAccessibilityTreeFromHtml(
@@ -4511,6 +5085,56 @@ IN_PROC_BROWSER_TEST_F(AccessibilityWinUIABrowserTest,
->GetAccessibilityMode());
}
+IN_PROC_BROWSER_TEST_F(AccessibilityWinUIABrowserTest,
+ TabHeuristicForWindowsNarrator) {
+ // Windows Narrator uses certain heuristics to determine where in the UIA
+ // tree a "browser tab" begins and ends, in order to contain the search range
+ // for commands such as "move to next/previous text input field."
+ // This test is used to validate such heuristics.
+ // Specifically: The boundary of a browser tab is the element nearest the
+ // root with a ControlType of Document and an implementation of TextPattern.
+ // In this test, we validate that such an element exists.
+
+ // Load an empty HTML page.
+ LoadInitialAccessibilityTreeFromHtml(
+ R"HTML(<!DOCTYPE html>
+ <html>
+ </html>)HTML");
+
+ // The element exposed by the legacy window is a fragment root whose first
+ // child represents the document root (our tab boundary). First, get the
+ // fragment root using the legacy window's HWND.
+ RenderWidgetHostViewAura* render_widget_host_view_aura =
+ static_cast<RenderWidgetHostViewAura*>(
+ shell()->web_contents()->GetRenderWidgetHostView());
+ HWND hwnd = render_widget_host_view_aura->AccessibilityGetAcceleratedWidget();
+ ASSERT_NE(gfx::kNullAcceleratedWidget, hwnd);
+ Microsoft::WRL::ComPtr<IUIAutomation> uia;
+ ASSERT_HRESULT_SUCCEEDED(CoCreateInstance(CLSID_CUIAutomation, nullptr,
+ CLSCTX_INPROC_SERVER,
+ IID_IUIAutomation, &uia));
+ Microsoft::WRL::ComPtr<IUIAutomationElement> fragment_root;
+ ASSERT_HRESULT_SUCCEEDED(uia->ElementFromHandle(hwnd, &fragment_root));
+ ASSERT_NE(nullptr, fragment_root.Get());
+
+ // Now get the fragment root's first (only) child.
+ Microsoft::WRL::ComPtr<IUIAutomationTreeWalker> tree_walker;
+ uia->get_RawViewWalker(&tree_walker);
+ Microsoft::WRL::ComPtr<IUIAutomationElement> first_child;
+ tree_walker->GetFirstChildElement(fragment_root.Get(), &first_child);
+ ASSERT_NE(nullptr, first_child.Get());
+
+ // Validate the control type and presence of TextPattern.
+ CONTROLTYPEID control_type;
+ ASSERT_HRESULT_SUCCEEDED(first_child->get_CurrentControlType(&control_type));
+ EXPECT_EQ(control_type, UIA_DocumentControlTypeId);
+
+ Microsoft::WRL::ComPtr<IUnknown> text_pattern_unknown;
+ ASSERT_HRESULT_SUCCEEDED(
+ first_child->GetCurrentPattern(UIA_TextPatternId, &text_pattern_unknown));
+ EXPECT_NE(nullptr, text_pattern_unknown.Get());
+}
+
IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, TestOffsetsOfSelectionAll) {
LoadInitialAccessibilityTreeFromHtml(R"HTML(
<p>Hello world.</p>
diff --git a/chromium/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc b/chromium/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc
index b1772aa9847..164a92b2a15 100644
--- a/chromium/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc
+++ b/chromium/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc
@@ -526,10 +526,16 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
<input type='text' aria-label='input_text'><span
style="font-size: 12pt">Text1</span>
</div>
+ <div contenteditable="true">
+ <ul><li>item</li></ul>3.14
+ </div>
</body>
</html>
)HTML"));
+ // Case 1: Inside of a plain text field, NormalizeTextRange shouldn't modify
+ // the text range endpoints.
+ //
// In order for the test harness to effectively simulate typing in a text
// input, first change the value of the text input and then focus it. Only
// editing the value won't show the cursor and only focusing will put the
@@ -570,7 +576,7 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
/*expected_text*/ L"",
/*expected_count*/ 4);
- // Clone the original text range so we can keep track if NormalizeEndpoints
+ // Clone the original text range so we can keep track if NormalizeTextRange
// causes a change in position.
ComPtr<ITextRangeProvider> text_range_provider_clone;
text_range_provider->Clone(&text_range_provider_clone);
@@ -584,7 +590,8 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
ASSERT_EQ(0, result);
// Calling GetAttributeValue will call NormalizeTextRange, which shouldn't
- // change the result of CompareEndpoints below.
+ // change the result of CompareEndpoints below since the range is inside a
+ // plain text field.
base::win::ScopedVariant value;
EXPECT_HRESULT_SUCCEEDED(text_range_provider->GetAttributeValue(
UIA_IsReadOnlyAttributeId, value.Receive()));
@@ -592,16 +599,56 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
EXPECT_EQ(V_BOOL(value.ptr()), VARIANT_FALSE);
value.Reset();
- EXPECT_HRESULT_SUCCEEDED(text_range_provider_clone->GetAttributeValue(
+ EXPECT_HRESULT_SUCCEEDED(text_range_provider->CompareEndpoints(
+ TextPatternRangeEndpoint_End, text_range_provider_clone.Get(),
+ TextPatternRangeEndpoint_Start, &result));
+ ASSERT_EQ(0, result);
+
+ // Case 2: Inside of a rich text field, NormalizeTextRange should modify the
+ // text range endpoints.
+ auto* node = FindNode(ax::mojom::Role::kStaticText, "item");
+ ASSERT_NE(nullptr, node);
+ EXPECT_TRUE(node->PlatformIsLeaf());
+ EXPECT_EQ(0u, node->PlatformChildCount());
+
+ GetTextRangeProviderFromTextNode(*node, &text_range_provider);
+ ASSERT_NE(nullptr, text_range_provider.Get());
+ EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"item");
+
+ EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
+ text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
+ /*count*/ 4,
+ /*expected_text*/ L"",
+ /*expected_count*/ 4);
+ // Make the range degenerate.
+ EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
+ text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
+ /*count*/ 1,
+ /*expected_text*/ L"\n3",
+ /*expected_count*/ 1);
+
+ // The range should now span two nodes: start: "item<>", end: "<3>.14".
+ EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"\n3");
+
+ // Clone the original text range so we can keep track if NormalizeTextRange
+ // causes a change in position.
+ text_range_provider->Clone(&text_range_provider_clone);
+
+ // Calling GetAttributeValue will call NormalizeTextRange, which should
+ // change the result of CompareEndpoints below since we are in a rich text
+ // field.
+ EXPECT_HRESULT_SUCCEEDED(text_range_provider->GetAttributeValue(
UIA_IsReadOnlyAttributeId, value.Receive()));
EXPECT_EQ(value.type(), VT_BOOL);
EXPECT_EQ(V_BOOL(value.ptr()), VARIANT_FALSE);
value.Reset();
+ // Since text_range_provider has been modified by NormalizeTextRange, we
+ // expect a difference here.
EXPECT_HRESULT_SUCCEEDED(text_range_provider->CompareEndpoints(
TextPatternRangeEndpoint_End, text_range_provider_clone.Get(),
TextPatternRangeEndpoint_Start, &result));
- ASSERT_EQ(0, result);
+ ASSERT_EQ(1, result);
}
IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
diff --git a/chromium/content/browser/accessibility/ax_platform_node_win_browsertest.cc b/chromium/content/browser/accessibility/ax_platform_node_win_browsertest.cc
index e8774c8dc33..c83b531e584 100644
--- a/chromium/content/browser/accessibility/ax_platform_node_win_browsertest.cc
+++ b/chromium/content/browser/accessibility/ax_platform_node_win_browsertest.cc
@@ -21,6 +21,17 @@
using Microsoft::WRL::ComPtr;
namespace content {
+
+#define EXPECT_UIA_INT_EQ(node, property_id, expected) \
+ { \
+ base::win::ScopedVariant expectedVariant(expected); \
+ ASSERT_EQ(VT_I4, expectedVariant.type()); \
+ base::win::ScopedVariant actual; \
+ ASSERT_HRESULT_SUCCEEDED( \
+ node->GetPropertyValue(property_id, actual.Receive())); \
+ EXPECT_EQ(expectedVariant.ptr()->intVal, actual.ptr()->intVal); \
+ }
+
class AXPlatformNodeWinBrowserTest : public AccessibilityContentBrowserTest {
protected:
template <typename T>
@@ -48,6 +59,19 @@ class AXPlatformNodeWinBrowserTest : public AccessibilityContentBrowserTest {
return result;
}
+ BrowserAccessibility* FindNodeAfter(BrowserAccessibility* begin,
+ const std::string& name) {
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(shell()->web_contents());
+ BrowserAccessibilityManager* manager =
+ web_contents->GetRootBrowserAccessibilityManager();
+ BrowserAccessibility* node = begin;
+ while (node && (node->GetName() != name))
+ node = manager->NextInTreeOrder(node);
+
+ return node;
+ }
+
void UIAGetPropertyValueFlowsFromBrowserTestTemplate(
const BrowserAccessibility* target_browser_accessibility,
const std::vector<std::string>& expected_names) {
@@ -431,6 +455,77 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinBrowserTest,
}
IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinBrowserTest,
+ UIAGetPropertyValueCulture) {
+ LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML(
+ <!DOCTYPE html>
+ <html>
+ <body>
+ <div lang='en-us'>en-us</div>
+ <div lang='en-gb'>en-gb</div>
+ <div lang='ru-ru'>ru-ru</div>
+ <div lang='fake'>fake</div>
+ <div>no lang</div>
+ <div lang=''>empty lang</div>
+ </body>
+ </html>
+ )HTML"));
+
+ BrowserAccessibility* root_node = GetRootAndAssertNonNull();
+ BrowserAccessibility* body_node = root_node->PlatformGetFirstChild();
+ ASSERT_NE(nullptr, body_node);
+
+ BrowserAccessibility* node = FindNodeAfter(body_node, "en-us");
+ ASSERT_NE(nullptr, node);
+ BrowserAccessibilityComWin* en_us_node_com_win =
+ ToBrowserAccessibilityWin(node)->GetCOM();
+ ASSERT_NE(nullptr, en_us_node_com_win);
+ constexpr int en_us_lcid = 1033;
+ EXPECT_UIA_INT_EQ(en_us_node_com_win, UIA_CulturePropertyId, en_us_lcid);
+
+ node = FindNodeAfter(node, "en-gb");
+ ASSERT_NE(nullptr, node);
+ BrowserAccessibilityComWin* en_gb_node_com_win =
+ ToBrowserAccessibilityWin(node)->GetCOM();
+ ASSERT_NE(nullptr, en_gb_node_com_win);
+ constexpr int en_gb_lcid = 2057;
+ EXPECT_UIA_INT_EQ(en_gb_node_com_win, UIA_CulturePropertyId, en_gb_lcid);
+
+ node = FindNodeAfter(node, "ru-ru");
+ ASSERT_NE(nullptr, node);
+ BrowserAccessibilityComWin* ru_ru_node_com_win =
+ ToBrowserAccessibilityWin(node)->GetCOM();
+ ASSERT_NE(nullptr, ru_ru_node_com_win);
+ constexpr int ru_ru_lcid = 1049;
+ EXPECT_UIA_INT_EQ(ru_ru_node_com_win, UIA_CulturePropertyId, ru_ru_lcid);
+
+ // Setting to an invalid language should return a failed HRESULT.
+ node = FindNodeAfter(node, "fake");
+ ASSERT_NE(nullptr, node);
+ BrowserAccessibilityComWin* fake_lang_node_com_win =
+ ToBrowserAccessibilityWin(node)->GetCOM();
+ ASSERT_NE(nullptr, fake_lang_node_com_win);
+ base::win::ScopedVariant actual;
+ EXPECT_HRESULT_FAILED(fake_lang_node_com_win->GetPropertyValue(
+ UIA_CulturePropertyId, actual.Receive()));
+
+ // No lang should default to the page's default language (en-us).
+ node = FindNodeAfter(node, "no lang");
+ ASSERT_NE(nullptr, node);
+ BrowserAccessibilityComWin* no_lang_node_com_win =
+ ToBrowserAccessibilityWin(node)->GetCOM();
+ ASSERT_NE(nullptr, no_lang_node_com_win);
+ EXPECT_UIA_INT_EQ(no_lang_node_com_win, UIA_CulturePropertyId, en_us_lcid);
+
+ // Empty lang should default to the page's default language (en-us).
+ node = FindNodeAfter(node, "empty lang");
+ ASSERT_NE(nullptr, node);
+ BrowserAccessibilityComWin* empty_lang_node_com_win =
+ ToBrowserAccessibilityWin(node)->GetCOM();
+ ASSERT_NE(nullptr, empty_lang_node_com_win);
+ EXPECT_UIA_INT_EQ(empty_lang_node_com_win, UIA_CulturePropertyId, en_us_lcid);
+}
+
+IN_PROC_BROWSER_TEST_F(AXPlatformNodeWinBrowserTest,
HitTestOnAncestorOfWebRoot) {
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
diff --git a/chromium/content/browser/accessibility/browser_accessibility.cc b/chromium/content/browser/accessibility/browser_accessibility.cc
index 64000eb7dff..a84e5a94575 100644
--- a/chromium/content/browser/accessibility/browser_accessibility.cc
+++ b/chromium/content/browser/accessibility/browser_accessibility.cc
@@ -101,65 +101,24 @@ int GetBoundaryTextOffsetInsideBaseAnchor(
void BrowserAccessibility::Init(BrowserAccessibilityManager* manager,
ui::AXNode* node) {
+ DCHECK(manager);
+ DCHECK(node);
manager_ = manager;
node_ = node;
}
bool BrowserAccessibility::PlatformIsLeaf() const {
- if (InternalChildCount() == 0)
- return true;
-
- return PlatformIsLeafIncludingIgnored();
+ // TODO(nektar): Remove in favor of IsLeaf.
+ return IsLeaf();
}
bool BrowserAccessibility::PlatformIsLeafIncludingIgnored() const {
- if (node()->children().size() == 0)
- return true;
-
- // These types of objects may have children that we use as internal
- // implementation details, but we want to expose them as leaves to platform
- // accessibility APIs because screen readers might be confused if they find
- // any children.
- if (IsPlainTextField() || IsTextOnlyObject())
- return true;
-
- // Roles whose children are only presentational according to the ARIA and
- // HTML5 Specs should be hidden from screen readers.
- switch (GetRole()) {
- // According to the ARIA and Core-AAM specs:
- // https://w3c.github.io/aria/#button,
- // https://www.w3.org/TR/core-aam-1.1/#exclude_elements
- // button's children are presentational only and should be hidden from
- // screen readers. However, we cannot enforce the leafiness of buttons
- // because they may contain many rich, interactive descendants such as a day
- // in a calendar, and screen readers will need to interact with these
- // contents. See https://crbug.com/689204.
- // So we decided to not enforce the leafiness of buttons and expose all
- // children. The only exception to enforce leafiness is when the button has
- // a single text child and to prevent screen readers from double speak.
- case ax::mojom::Role::kButton: {
- if (InternalChildCount() == 1 &&
- InternalGetFirstChild()->IsTextOnlyObject())
- return true;
- return false;
- }
- case ax::mojom::Role::kDocCover:
- case ax::mojom::Role::kGraphicsSymbol:
- case ax::mojom::Role::kImage:
- case ax::mojom::Role::kMeter:
- case ax::mojom::Role::kScrollBar:
- case ax::mojom::Role::kSlider:
- case ax::mojom::Role::kSplitter:
- case ax::mojom::Role::kProgressIndicator:
- return true;
- default:
- return false;
- }
+ return node()->IsLeafIncludingIgnored();
}
bool BrowserAccessibility::CanFireEvents() const {
// Allow events unless this object would be trimmed away.
- return !PlatformIsChildOfLeafIncludingIgnored();
+ return !IsChildOfLeaf();
}
ui::AXPlatformNode* BrowserAccessibility::GetAXPlatformNode() const {
@@ -179,11 +138,11 @@ uint32_t BrowserAccessibility::PlatformChildCount() const {
}
BrowserAccessibility* BrowserAccessibility::PlatformGetParent() const {
- ui::AXNode* parent = node_->GetUnignoredParent();
+ ui::AXNode* parent = node()->GetUnignoredParent();
if (parent)
- return manager_->GetFromAXNode(parent);
+ return manager()->GetFromAXNode(parent);
- return manager_->GetParentNodeFromParentTree();
+ return manager()->GetParentNodeFromParentTree();
}
BrowserAccessibility* BrowserAccessibility::PlatformGetFirstChild() const {
@@ -249,11 +208,11 @@ bool BrowserAccessibility::IsIgnored() const {
}
bool BrowserAccessibility::IsTextOnlyObject() const {
- return node_ && node_->IsText();
+ return node()->IsText();
}
bool BrowserAccessibility::IsLineBreakObject() const {
- return node_ && node_->IsLineBreak();
+ return node()->IsLineBreak();
}
BrowserAccessibility* BrowserAccessibility::PlatformGetChild(
@@ -274,18 +233,6 @@ BrowserAccessibility* BrowserAccessibility::PlatformGetChild(
return result;
}
-bool BrowserAccessibility::PlatformIsChildOfLeafIncludingIgnored() const {
- BrowserAccessibility* ancestor = InternalGetParent();
-
- while (ancestor) {
- if (ancestor->PlatformIsLeafIncludingIgnored())
- return true;
- ancestor = ancestor->InternalGetParent();
- }
-
- return false;
-}
-
BrowserAccessibility* BrowserAccessibility::PlatformGetClosestPlatformObject()
const {
BrowserAccessibility* platform_object =
@@ -449,7 +396,7 @@ BrowserAccessibility::InternalChildrenEnd() const {
}
int32_t BrowserAccessibility::GetId() const {
- return node_ ? node_->id() : -1;
+ return node()->id();
}
gfx::RectF BrowserAccessibility::GetLocation() const {
@@ -1077,11 +1024,11 @@ bool BrowserAccessibility::IsClickable() const {
}
bool BrowserAccessibility::IsTextField() const {
- return IsPlainTextField() || IsRichTextField();
+ return GetData().IsTextField();
}
bool BrowserAccessibility::IsPasswordField() const {
- return IsTextField() && HasState(ax::mojom::State::kProtected);
+ return GetData().IsPasswordField();
}
bool BrowserAccessibility::IsPlainTextField() const {
@@ -1089,8 +1036,7 @@ bool BrowserAccessibility::IsPlainTextField() const {
}
bool BrowserAccessibility::IsRichTextField() const {
- return GetBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot) &&
- HasState(ax::mojom::State::kRichlyEditable);
+ return GetData().IsRichTextField();
}
bool BrowserAccessibility::HasExplicitlyEmptyName() const {
@@ -1199,18 +1145,7 @@ base::string16 BrowserAccessibility::GetHypertext() const {
}
base::string16 BrowserAccessibility::GetInnerText() const {
- if (!InternalChildCount()) {
- if (IsTextField())
- return GetString16Attribute(ax::mojom::StringAttribute::kValue);
- return GetString16Attribute(ax::mojom::StringAttribute::kName);
- }
-
- base::string16 text;
- for (InternalChildIterator it = InternalChildrenBegin();
- it != InternalChildrenEnd(); ++it) {
- text += (*it).GetInnerText();
- }
- return text;
+ return base::UTF8ToUTF16(node()->GetInnerText());
}
gfx::Rect BrowserAccessibility::RelativeToAbsoluteBounds(
@@ -1488,10 +1423,43 @@ const ui::AXTreeData& BrowserAccessibility::GetTreeData() const {
const ui::AXTree::Selection BrowserAccessibility::GetUnignoredSelection()
const {
- if (manager())
- return manager()->ax_tree()->GetUnignoredSelection();
- return ui::AXTree::Selection{-1, -1, -1,
- ax::mojom::TextAffinity::kDownstream};
+ DCHECK(manager());
+ ui::AXTree::Selection selection =
+ manager()->ax_tree()->GetUnignoredSelection();
+
+ // "selection.anchor_offset" and "selection.focus_ofset" might need to be
+ // adjusted if the anchor or the focus nodes include ignored children.
+ const BrowserAccessibility* anchor_object =
+ manager()->GetFromID(selection.anchor_object_id);
+ if (anchor_object && !anchor_object->PlatformIsLeaf()) {
+ DCHECK_GE(selection.anchor_offset, 0);
+ if (size_t{selection.anchor_offset} <
+ anchor_object->node()->children().size()) {
+ const ui::AXNode* anchor_child =
+ anchor_object->node()->children()[selection.anchor_offset];
+ DCHECK(anchor_child);
+ selection.anchor_offset = int{anchor_child->GetUnignoredIndexInParent()};
+ } else {
+ selection.anchor_offset = anchor_object->GetChildCount();
+ }
+ }
+
+ const BrowserAccessibility* focus_object =
+ manager()->GetFromID(selection.focus_object_id);
+ if (focus_object && !focus_object->PlatformIsLeaf()) {
+ DCHECK_GE(selection.focus_offset, 0);
+ if (size_t{selection.focus_offset} <
+ focus_object->node()->children().size()) {
+ const ui::AXNode* focus_child =
+ focus_object->node()->children()[selection.focus_offset];
+ DCHECK(focus_child);
+ selection.focus_offset = int{focus_child->GetUnignoredIndexInParent()};
+ } else {
+ selection.focus_offset = focus_object->GetChildCount();
+ }
+ }
+
+ return selection;
}
ui::AXNodePosition::AXPositionInstance
@@ -1521,7 +1489,7 @@ gfx::NativeViewAccessible BrowserAccessibility::GetParent() {
}
int BrowserAccessibility::GetChildCount() const {
- return PlatformChildCount();
+ return int{PlatformChildCount()};
}
gfx::NativeViewAccessible BrowserAccessibility::ChildAtIndex(int index) {
@@ -1565,15 +1533,31 @@ gfx::NativeViewAccessible BrowserAccessibility::GetPreviousSibling() {
}
bool BrowserAccessibility::IsChildOfLeaf() const {
- BrowserAccessibility* ancestor = InternalGetParent();
-
- while (ancestor) {
- if (ancestor->PlatformIsLeaf())
- return true;
- ancestor = ancestor->InternalGetParent();
+ return node()->IsChildOfLeaf();
+}
+
+bool BrowserAccessibility::IsLeaf() const {
+ // According to the ARIA and Core-AAM specs:
+ // https://w3c.github.io/aria/#button,
+ // https://www.w3.org/TR/core-aam-1.1/#exclude_elements
+ // button's children are presentational only and should be hidden from
+ // screen readers. However, we cannot enforce the leafiness of buttons
+ // because they may contain many rich, interactive descendants such as a day
+ // in a calendar, and screen readers will need to interact with these
+ // contents. See https://crbug.com/689204.
+ // So we decided to not enforce the leafiness of buttons and expose all
+ // children. The only exception to enforce leafiness is when the button has
+ // a single text child and to prevent screen readers from double speak.
+ if (GetRole() == ax::mojom::Role::kButton) {
+ return InternalChildCount() == 1 &&
+ InternalGetFirstChild()->IsTextOnlyObject();
}
+ return node()->IsLeaf();
+}
- return false;
+bool BrowserAccessibility::IsChildOfPlainTextField() const {
+ ui::AXNode* textfield_node = node()->GetTextFieldAncestor();
+ return textfield_node && textfield_node->data().IsPlainTextField();
}
gfx::NativeViewAccessible BrowserAccessibility::GetClosestPlatformObject()
@@ -1705,7 +1689,7 @@ int BrowserAccessibility::GetIndexInParent() {
// index at AXPlatformNodeBase.
return -1;
}
- return node_ ? node_->GetUnignoredIndexInParent() : -1;
+ return node()->GetUnignoredIndexInParent();
}
gfx::AcceleratedWidget
@@ -1746,30 +1730,24 @@ base::Optional<bool> BrowserAccessibility::GetTableHasColumnOrRowHeaderNode()
return node()->GetTableHasColumnOrRowHeaderNode();
}
-std::vector<int32_t> BrowserAccessibility::GetColHeaderNodeIds() const {
- std::vector<int32_t> result;
- node()->GetTableCellColHeaderNodeIds(&result);
- return result;
+std::vector<ui::AXNode::AXID> BrowserAccessibility::GetColHeaderNodeIds()
+ const {
+ return node()->GetTableColHeaderNodeIds();
}
-std::vector<int32_t> BrowserAccessibility::GetColHeaderNodeIds(
+std::vector<ui::AXNode::AXID> BrowserAccessibility::GetColHeaderNodeIds(
int col_index) const {
- std::vector<int32_t> result;
- node()->GetTableColHeaderNodeIds(col_index, &result);
- return result;
+ return node()->GetTableColHeaderNodeIds(col_index);
}
-std::vector<int32_t> BrowserAccessibility::GetRowHeaderNodeIds() const {
- std::vector<int32_t> result;
- node()->GetTableCellRowHeaderNodeIds(&result);
- return result;
+std::vector<ui::AXNode::AXID> BrowserAccessibility::GetRowHeaderNodeIds()
+ const {
+ return node()->GetTableCellRowHeaderNodeIds();
}
-std::vector<int32_t> BrowserAccessibility::GetRowHeaderNodeIds(
+std::vector<ui::AXNode::AXID> BrowserAccessibility::GetRowHeaderNodeIds(
int row_index) const {
- std::vector<int32_t> result;
- node()->GetTableRowHeaderNodeIds(row_index, &result);
- return result;
+ return node()->GetTableRowHeaderNodeIds(row_index);
}
ui::AXPlatformNode* BrowserAccessibility::GetTableCaption() const {
@@ -1871,9 +1849,49 @@ bool BrowserAccessibility::AccessibilityPerformAction(
case ax::mojom::Action::kSetScrollOffset:
manager_->SetScrollOffset(*this, data.target_point);
return true;
- case ax::mojom::Action::kSetSelection:
- manager_->SetSelection(data);
+ case ax::mojom::Action::kSetSelection: {
+ // "data.anchor_offset" and "data.focus_ofset" might need to be adjusted
+ // if the anchor or the focus nodes include ignored children.
+ ui::AXActionData selection = data;
+ const BrowserAccessibility* anchor_object =
+ manager()->GetFromID(selection.anchor_node_id);
+ DCHECK(anchor_object);
+ if (!anchor_object->PlatformIsLeaf()) {
+ DCHECK_GE(selection.anchor_offset, 0);
+ const BrowserAccessibility* anchor_child =
+ anchor_object->InternalGetChild(uint32_t{selection.anchor_offset});
+ if (anchor_child) {
+ selection.anchor_offset =
+ int{anchor_child->node()->index_in_parent()};
+ selection.anchor_node_id = anchor_child->node()->parent()->id();
+ } else {
+ // Since the child was not found, the only alternative is that this is
+ // an "after children" position.
+ selection.anchor_offset =
+ int{anchor_object->node()->children().size()};
+ }
+ }
+
+ const BrowserAccessibility* focus_object =
+ manager()->GetFromID(selection.focus_node_id);
+ DCHECK(focus_object);
+ if (!focus_object->PlatformIsLeaf()) {
+ DCHECK_GE(selection.focus_offset, 0);
+ const BrowserAccessibility* focus_child =
+ focus_object->InternalGetChild(uint32_t{selection.focus_offset});
+ if (focus_child) {
+ selection.focus_offset = int{focus_child->node()->index_in_parent()};
+ selection.focus_node_id = focus_child->node()->parent()->id();
+ } else {
+ // Since the child was not found, the only alternative is that this is
+ // an "after children" position.
+ selection.focus_offset = int{focus_object->node()->children().size()};
+ }
+ }
+
+ manager_->SetSelection(selection);
return true;
+ }
case ax::mojom::Action::kSetValue:
manager_->SetValue(*this, data.value);
return true;
diff --git a/chromium/content/browser/accessibility/browser_accessibility.h b/chromium/content/browser/accessibility/browser_accessibility.h
index c8184f66de2..df1b7977798 100644
--- a/chromium/content/browser/accessibility/browser_accessibility.h
+++ b/chromium/content/browser/accessibility/browser_accessibility.h
@@ -116,20 +116,11 @@ class CONTENT_EXPORT BrowserAccessibility : public ui::AXPlatformNodeDelegate {
bool IsLineBreakObject() const;
- // Returns true if this is a leaf node on this platform, meaning any
- // children should not be exposed to this platform's native accessibility
- // layer.
- // The definition of a leaf may vary depending on the platform,
- // but a leaf node should never have children that are focusable or
- // that might send notifications.
+ // See AXNode::IsLeaf().
bool PlatformIsLeaf() const;
- // Returns true if this is a leaf node on this platform, including
- // ignored nodes, meaning any children should not be exposed to this
- // platform's native accessibility layer, but a node shouldn't be
- // considered a leaf node solely because it has only ignored children.
- // Each platform subclass should implement this itself.
- virtual bool PlatformIsLeafIncludingIgnored() const;
+ // See AXNode::IsLeafIncludingIgnored().
+ bool PlatformIsLeafIncludingIgnored() const;
// Returns true if this object can fire events.
virtual bool CanFireEvents() const;
@@ -143,7 +134,7 @@ class CONTENT_EXPORT BrowserAccessibility : public ui::AXPlatformNodeDelegate {
virtual uint32_t PlatformChildCount() const;
// Return a pointer to the child at the given index, or NULL for an
- // invalid index. Returns NULL if PlatformIsLeaf() returns true.
+ // invalid index. Returns nullptr if PlatformIsLeaf() returns true.
virtual BrowserAccessibility* PlatformGetChild(uint32_t child_index) const;
BrowserAccessibility* PlatformGetParent() const;
@@ -186,12 +177,6 @@ class CONTENT_EXPORT BrowserAccessibility : public ui::AXPlatformNodeDelegate {
// Return a pointer to the first ancestor that is a selection container
BrowserAccessibility* PlatformGetSelectionContainer() const;
- // Returns true if an ancestor of this node (not including itself) is a
- // leaf node, including ignored nodes, meaning that this node is not
- // actually exposed to the platform, but a node shouldn't be
- // considered a leaf node solely because it has only ignored children.
- bool PlatformIsChildOfLeafIncludingIgnored() const;
-
// If this object is exposed to the platform, returns this object. Otherwise,
// returns the platform leaf under which this object is found.
BrowserAccessibility* PlatformGetClosestPlatformObject() const;
@@ -316,7 +301,7 @@ class CONTENT_EXPORT BrowserAccessibility : public ui::AXPlatformNodeDelegate {
InternalChildIterator InternalChildrenBegin() const;
InternalChildIterator InternalChildrenEnd() const;
- int32_t GetId() const;
+ ui::AXNode::AXID GetId() const;
gfx::RectF GetLocation() const;
ax::mojom::Role GetRole() const;
int32_t GetState() const;
@@ -388,30 +373,19 @@ class CONTENT_EXPORT BrowserAccessibility : public ui::AXPlatformNodeDelegate {
virtual bool IsClickable() const;
- // A text field is any widget in which the user should be able to enter and
- // edit text.
- //
- // Examples include <input type="text">, <input type="password">, <textarea>,
- // <div contenteditable="true">, <div role="textbox">, <div role="searchbox">
- // and <div role="combobox">. Note that when an ARIA role that indicates that
- // the widget is editable is used, such as "role=textbox", the element doesn't
- // need to be contenteditable for this method to return true, as in theory
- // JavaScript could be used to implement editing functionality. In practice,
- // this situation should be rare.
+ // See AXNodeData::IsTextField().
bool IsTextField() const;
- // A text field that is used for entering passwords.
+ // See AXNodeData::IsPasswordField().
bool IsPasswordField() const;
- // A text field that doesn't accept rich text content, such as text with
- // special formatting or styling.
+ // See AXNodeData::IsPlainTextField().
bool IsPlainTextField() const;
- // A text field that accepts rich text content, such as text with special
- // formatting or styling.
+ // See AXNodeData::IsRichTextField().
bool IsRichTextField() const;
- // Return true if the accessible name was explicitly set to "" by the author
+ // Returns true if the accessible name was explicitly set to "" by the author
bool HasExplicitlyEmptyName() const;
// TODO(nektar): Remove this method and replace with GetInnerText.
@@ -471,6 +445,8 @@ class CONTENT_EXPORT BrowserAccessibility : public ui::AXPlatformNodeDelegate {
gfx::NativeViewAccessible GetPreviousSibling() override;
bool IsChildOfLeaf() const override;
+ bool IsChildOfPlainTextField() const override;
+ bool IsLeaf() const override;
gfx::NativeViewAccessible GetClosestPlatformObject() const override;
std::unique_ptr<ChildIterator> ChildrenBegin() override;
@@ -523,10 +499,12 @@ class CONTENT_EXPORT BrowserAccessibility : public ui::AXPlatformNodeDelegate {
base::Optional<int> GetTableAriaRowCount() const override;
base::Optional<int> GetTableCellCount() const override;
base::Optional<bool> GetTableHasColumnOrRowHeaderNode() const override;
- std::vector<int32_t> GetColHeaderNodeIds() const override;
- std::vector<int32_t> GetColHeaderNodeIds(int col_index) const override;
- std::vector<int32_t> GetRowHeaderNodeIds() const override;
- std::vector<int32_t> GetRowHeaderNodeIds(int row_index) const override;
+ std::vector<ui::AXNode::AXID> GetColHeaderNodeIds() const override;
+ std::vector<ui::AXNode::AXID> GetColHeaderNodeIds(
+ int col_index) const override;
+ std::vector<ui::AXNode::AXID> GetRowHeaderNodeIds() const override;
+ std::vector<ui::AXNode::AXID> GetRowHeaderNodeIds(
+ int row_index) const override;
ui::AXPlatformNode* GetTableCaption() const override;
bool IsTableRow() const override;
@@ -574,6 +552,7 @@ class CONTENT_EXPORT BrowserAccessibility : public ui::AXPlatformNodeDelegate {
bool IsOrderedSet() const override;
base::Optional<int> GetPosInSet() const override;
base::Optional<int> GetSetSize() const override;
+
bool IsInListMarker() const;
bool IsCollapsedMenuListPopUpButton() const;
BrowserAccessibility* GetCollapsedMenuListPopUpButtonAncestor() const;
diff --git a/chromium/content/browser/accessibility/browser_accessibility_android.cc b/chromium/content/browser/accessibility/browser_accessibility_android.cc
index 87f3bd2ddcb..c219569e5e4 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_android.cc
+++ b/chromium/content/browser/accessibility/browser_accessibility_android.cc
@@ -116,61 +116,8 @@ base::string16 BrowserAccessibilityAndroid::GetValue() const {
return value;
}
-bool BrowserAccessibilityAndroid::PlatformIsLeafIncludingIgnored() const {
- if (BrowserAccessibility::PlatformIsLeafIncludingIgnored())
- return true;
-
- // Iframes are always allowed to contain children.
- if (IsIframe() || GetRole() == ax::mojom::Role::kRootWebArea ||
- GetRole() == ax::mojom::Role::kWebArea) {
- return false;
- }
-
- // Button, date and time controls should drop their children.
- switch (GetRole()) {
- case ax::mojom::Role::kButton:
- case ax::mojom::Role::kDate:
- case ax::mojom::Role::kDateTime:
- case ax::mojom::Role::kInputTime:
- return true;
- default:
- break;
- }
-
- // Links are never a leaf
- if (IsLink())
- return false;
-
- // If it has a focusable child, we definitely can't leave out children.
- if (HasFocusableNonOptionChild())
- return false;
-
- BrowserAccessibilityManagerAndroid* manager_android =
- static_cast<BrowserAccessibilityManagerAndroid*>(manager());
- if (manager_android->prune_tree_for_screen_reader()) {
- // Headings with text can drop their children.
- base::string16 name = GetInnerText();
- if (GetRole() == ax::mojom::Role::kHeading && !name.empty())
- return true;
-
- // Focusable nodes with text can drop their children.
- if (HasState(ax::mojom::State::kFocusable) && !name.empty())
- return true;
-
- // Nodes with only static text as children can drop their children.
- if (HasOnlyTextChildren())
- return true;
- }
-
- return false;
-}
-
-bool BrowserAccessibilityAndroid::CanFireEvents() const {
- return true;
-}
-
bool BrowserAccessibilityAndroid::IsCheckable() const {
- return HasIntAttribute(ax::mojom::IntAttribute::kCheckedState);
+ return GetData().HasCheckedState();
}
bool BrowserAccessibilityAndroid::IsChecked() const {
@@ -395,7 +342,7 @@ bool BrowserAccessibilityAndroid::IsInterestingOnAndroid() const {
}
// Otherwise, the interesting nodes are leaf nodes with non-whitespace text.
- return PlatformIsLeaf() &&
+ return IsLeaf() &&
!base::ContainsOnlyChars(GetInnerText(), base::kWhitespaceUTF16);
}
@@ -462,6 +409,67 @@ const char* BrowserAccessibilityAndroid::GetClassName() const {
return ui::AXRoleToAndroidClassName(role, PlatformGetParent() != nullptr);
}
+bool BrowserAccessibilityAndroid::IsChildOfLeaf() const {
+ BrowserAccessibility* ancestor = InternalGetParent();
+
+ while (ancestor) {
+ if (ancestor->IsLeaf())
+ return true;
+ ancestor = ancestor->InternalGetParent();
+ }
+
+ return false;
+}
+
+bool BrowserAccessibilityAndroid::IsLeaf() const {
+ if (BrowserAccessibility::IsLeaf())
+ return true;
+
+ // Iframes are always allowed to contain children.
+ if (IsIframe() || GetRole() == ax::mojom::Role::kRootWebArea ||
+ GetRole() == ax::mojom::Role::kWebArea) {
+ return false;
+ }
+
+ // Button, date and time controls should drop their children.
+ switch (GetRole()) {
+ case ax::mojom::Role::kButton:
+ case ax::mojom::Role::kDate:
+ case ax::mojom::Role::kDateTime:
+ case ax::mojom::Role::kInputTime:
+ return true;
+ default:
+ break;
+ }
+
+ // Links are never leaves.
+ if (IsLink())
+ return false;
+
+ // If it has a focusable child, we definitely can't leave out children.
+ if (HasFocusableNonOptionChild())
+ return false;
+
+ BrowserAccessibilityManagerAndroid* manager_android =
+ static_cast<BrowserAccessibilityManagerAndroid*>(manager());
+ if (manager_android->prune_tree_for_screen_reader()) {
+ // Headings with text can drop their children.
+ base::string16 name = GetInnerText();
+ if (GetRole() == ax::mojom::Role::kHeading && !name.empty())
+ return true;
+
+ // Focusable nodes with text can drop their children.
+ if (HasState(ax::mojom::State::kFocusable) && !name.empty())
+ return true;
+
+ // Nodes with only static text as children can drop their children.
+ if (HasOnlyTextChildren())
+ return true;
+ }
+
+ return false;
+}
+
base::string16 BrowserAccessibilityAndroid::GetInnerText() const {
if (IsIframe() || GetRole() == ax::mojom::Role::kWebArea) {
return base::string16();
@@ -496,7 +504,7 @@ base::string16 BrowserAccessibilityAndroid::GetInnerText() const {
if (GetRole() == ax::mojom::Role::kRootWebArea)
return text;
- // This is called from PlatformIsLeaf, so don't call PlatformChildCount
+ // This is called from IsLeaf, so don't call PlatformChildCount
// from within this!
if (text.empty() && (HasOnlyTextChildren() ||
(IsFocusable() && HasOnlyTextAndImageChildren()))) {
@@ -1678,7 +1686,7 @@ void BrowserAccessibilityAndroid::GetWordBoundaries(
}
bool BrowserAccessibilityAndroid::HasFocusableNonOptionChild() const {
- // This is called from PlatformIsLeaf, so don't call PlatformChildCount
+ // This is called from IsLeaf, so don't call PlatformChildCount
// from within this!
for (auto it = InternalChildrenBegin(); it != InternalChildrenEnd(); ++it) {
BrowserAccessibility* child = it.get();
@@ -1709,7 +1717,7 @@ void BrowserAccessibilityAndroid::GetSuggestions(
return;
// TODO(accessibility): using FindTextOnlyObjectsInRange or NextInTreeOrder
- // doesn't work because Android's PlatformIsLeafIncludingIgnored
+ // doesn't work because Android's IsLeaf
// implementation deliberately excludes a lot of nodes. We need a version of
// FindTextOnlyObjectsInRange and/or NextInTreeOrder that only walk
// the internal tree.
@@ -1790,7 +1798,7 @@ bool BrowserAccessibilityAndroid::HasImage() const {
}
bool BrowserAccessibilityAndroid::HasOnlyTextChildren() const {
- // This is called from PlatformIsLeaf, so don't call PlatformChildCount
+ // This is called from IsLeaf, so don't call PlatformChildCount
// from within this!
for (auto it = InternalChildrenBegin(); it != InternalChildrenEnd(); ++it) {
if (!it->IsTextOnlyObject())
@@ -1800,7 +1808,7 @@ bool BrowserAccessibilityAndroid::HasOnlyTextChildren() const {
}
bool BrowserAccessibilityAndroid::HasOnlyTextAndImageChildren() const {
- // This is called from PlatformIsLeaf, so don't call PlatformChildCount
+ // This is called from IsLeaf, so don't call PlatformChildCount
// from within this!
for (auto it = InternalChildrenBegin(); it != InternalChildrenEnd(); ++it) {
BrowserAccessibility* child = it.get();
diff --git a/chromium/content/browser/accessibility/browser_accessibility_android.h b/chromium/content/browser/accessibility/browser_accessibility_android.h
index ed8924e9461..ee55916ca43 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_android.h
+++ b/chromium/content/browser/accessibility/browser_accessibility_android.h
@@ -29,10 +29,6 @@ class CONTENT_EXPORT BrowserAccessibilityAndroid : public BrowserAccessibility {
void OnLocationChanged() override;
base::string16 GetValue() const override;
- bool PlatformIsLeafIncludingIgnored() const override;
- // Android needs events even on objects that are trimmed away.
- bool CanFireEvents() const override;
-
bool IsCheckable() const;
bool IsChecked() const;
bool IsClickable() const override;
@@ -84,6 +80,8 @@ class CONTENT_EXPORT BrowserAccessibilityAndroid : public BrowserAccessibility {
bool HasImage() const;
const char* GetClassName() const;
+ bool IsChildOfLeaf() const override;
+ bool IsLeaf() const override;
base::string16 GetInnerText() const override;
base::string16 GetHint() const;
diff --git a/chromium/content/browser/accessibility/browser_accessibility_android_unittest.cc b/chromium/content/browser/accessibility/browser_accessibility_android_unittest.cc
new file mode 100644
index 00000000000..00235ecfa19
--- /dev/null
+++ b/chromium/content/browser/accessibility/browser_accessibility_android_unittest.cc
@@ -0,0 +1,283 @@
+// Copyright (c) 2020 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/browser_accessibility_android.h"
+
+#include <memory>
+
+#include "base/test/task_environment.h"
+#include "build/build_config.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/browser/accessibility/test_browser_accessibility_delegate.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class BrowserAccessibilityAndroidTest : public testing::Test {
+ public:
+ BrowserAccessibilityAndroidTest();
+ ~BrowserAccessibilityAndroidTest() override;
+
+ protected:
+ std::unique_ptr<TestBrowserAccessibilityDelegate>
+ test_browser_accessibility_delegate_;
+
+ private:
+ void SetUp() override;
+ base::test::TaskEnvironment task_environment_;
+ DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityAndroidTest);
+};
+
+BrowserAccessibilityAndroidTest::BrowserAccessibilityAndroidTest() = default;
+
+BrowserAccessibilityAndroidTest::~BrowserAccessibilityAndroidTest() = default;
+
+void BrowserAccessibilityAndroidTest::SetUp() {
+ ui::AXPlatformNode::NotifyAddAXModeFlags(ui::kAXModeComplete);
+ test_browser_accessibility_delegate_ =
+ std::make_unique<TestBrowserAccessibilityDelegate>();
+}
+
+TEST_F(BrowserAccessibilityAndroidTest, TestRetargetTextOnly) {
+ ui::AXNodeData text1;
+ text1.id = 111;
+ text1.role = ax::mojom::Role::kStaticText;
+ text1.SetName("Hello, world");
+
+ ui::AXNodeData para1;
+ para1.id = 11;
+ para1.role = ax::mojom::Role::kParagraph;
+ para1.child_ids = {text1.id};
+
+ ui::AXNodeData root;
+ root.id = 1;
+ root.role = ax::mojom::Role::kRootWebArea;
+ root.child_ids = {para1.id};
+
+ std::unique_ptr<BrowserAccessibilityManager> manager(
+ BrowserAccessibilityManager::Create(
+ MakeAXTreeUpdate(root, para1, text1),
+ test_browser_accessibility_delegate_.get()));
+
+ BrowserAccessibility* root_obj = manager->GetRoot();
+ EXPECT_FALSE(root_obj->PlatformIsLeaf());
+ EXPECT_TRUE(root_obj->CanFireEvents());
+ BrowserAccessibility* para_obj = root_obj->PlatformGetChild(0);
+ EXPECT_TRUE(para_obj->PlatformIsLeaf());
+ EXPECT_TRUE(para_obj->CanFireEvents());
+ BrowserAccessibility* text_obj = manager->GetFromID(111);
+ EXPECT_TRUE(text_obj->PlatformIsLeaf());
+ EXPECT_FALSE(text_obj->CanFireEvents());
+ BrowserAccessibility* updated = manager->RetargetForEvents(
+ text_obj, BrowserAccessibilityManager::RetargetEventType::
+ RetargetEventTypeBlinkHover);
+ // |updated| should be the paragraph.
+ EXPECT_EQ(11, updated->GetId());
+ EXPECT_TRUE(updated->CanFireEvents());
+ manager.reset();
+}
+
+TEST_F(BrowserAccessibilityAndroidTest, TestRetargetHeading) {
+ ui::AXNodeData text1;
+ text1.id = 111;
+ text1.role = ax::mojom::Role::kStaticText;
+
+ ui::AXNodeData heading1;
+ heading1.id = 11;
+ heading1.role = ax::mojom::Role::kHeading;
+ heading1.SetName("heading");
+ heading1.child_ids = {text1.id};
+
+ ui::AXNodeData root;
+ root.id = 1;
+ root.role = ax::mojom::Role::kRootWebArea;
+ root.child_ids = {heading1.id};
+
+ std::unique_ptr<BrowserAccessibilityManager> manager(
+ BrowserAccessibilityManager::Create(
+ MakeAXTreeUpdate(root, heading1, text1),
+ test_browser_accessibility_delegate_.get()));
+
+ BrowserAccessibility* root_obj = manager->GetRoot();
+ EXPECT_FALSE(root_obj->PlatformIsLeaf());
+ EXPECT_TRUE(root_obj->CanFireEvents());
+ BrowserAccessibility* heading_obj = root_obj->PlatformGetChild(0);
+ EXPECT_TRUE(heading_obj->PlatformIsLeaf());
+ EXPECT_TRUE(heading_obj->CanFireEvents());
+ BrowserAccessibility* text_obj = manager->GetFromID(111);
+ EXPECT_TRUE(text_obj->PlatformIsLeaf());
+ EXPECT_FALSE(text_obj->CanFireEvents());
+ BrowserAccessibility* updated = manager->RetargetForEvents(
+ text_obj, BrowserAccessibilityManager::RetargetEventType::
+ RetargetEventTypeBlinkHover);
+ // |updated| should be the heading.
+ EXPECT_EQ(11, updated->GetId());
+ EXPECT_TRUE(updated->CanFireEvents());
+ manager.reset();
+}
+
+TEST_F(BrowserAccessibilityAndroidTest, TestRetargetFocusable) {
+ ui::AXNodeData text1;
+ text1.id = 111;
+ text1.role = ax::mojom::Role::kStaticText;
+
+ ui::AXNodeData para1;
+ para1.id = 11;
+ para1.role = ax::mojom::Role::kParagraph;
+ para1.AddState(ax::mojom::State::kFocusable);
+ para1.SetName("focusable");
+ para1.child_ids = {text1.id};
+
+ ui::AXNodeData root;
+ root.id = 1;
+ root.role = ax::mojom::Role::kRootWebArea;
+ root.child_ids = {para1.id};
+
+ std::unique_ptr<BrowserAccessibilityManager> manager(
+ BrowserAccessibilityManager::Create(
+ MakeAXTreeUpdate(root, para1, text1),
+ test_browser_accessibility_delegate_.get()));
+
+ BrowserAccessibility* root_obj = manager->GetRoot();
+ EXPECT_FALSE(root_obj->PlatformIsLeaf());
+ EXPECT_TRUE(root_obj->CanFireEvents());
+ BrowserAccessibility* para_obj = root_obj->PlatformGetChild(0);
+ EXPECT_TRUE(para_obj->PlatformIsLeaf());
+ EXPECT_TRUE(para_obj->CanFireEvents());
+ BrowserAccessibility* text_obj = manager->GetFromID(111);
+ EXPECT_TRUE(text_obj->PlatformIsLeaf());
+ EXPECT_FALSE(text_obj->CanFireEvents());
+ BrowserAccessibility* updated = manager->RetargetForEvents(
+ text_obj, BrowserAccessibilityManager::RetargetEventType::
+ RetargetEventTypeBlinkHover);
+ // |updated| should be the paragraph.
+ EXPECT_EQ(11, updated->GetId());
+ EXPECT_TRUE(updated->CanFireEvents());
+ manager.reset();
+}
+
+TEST_F(BrowserAccessibilityAndroidTest, TestRetargetInputControl) {
+ // Build the tree that has a form with input time.
+ // +rootWebArea
+ // ++genericContainer
+ // +++form
+ // ++++labelText
+ // +++++staticText
+ // ++++inputTime
+ // +++++genericContainer
+ // ++++++staticText
+ // ++++button
+ // +++++staticText
+ ui::AXNodeData label_text;
+ label_text.id = 11111;
+ label_text.role = ax::mojom::Role::kStaticText;
+ label_text.SetName("label");
+
+ ui::AXNodeData label;
+ label.id = 1111;
+ label.role = ax::mojom::Role::kLabelText;
+ label.child_ids = {label_text.id};
+
+ ui::AXNodeData input_text;
+ input_text.id = 111211;
+ input_text.role = ax::mojom::Role::kStaticText;
+ input_text.SetName("input_text");
+
+ ui::AXNodeData input_container;
+ input_container.id = 11121;
+ input_container.role = ax::mojom::Role::kGenericContainer;
+ input_container.child_ids = {input_text.id};
+
+ ui::AXNodeData input_time;
+ input_time.id = 1112;
+ input_time.role = ax::mojom::Role::kInputTime;
+ input_time.AddState(ax::mojom::State::kFocusable);
+ input_time.child_ids = {input_container.id};
+
+ ui::AXNodeData button_text;
+ button_text.id = 11131;
+ button_text.role = ax::mojom::Role::kStaticText;
+ button_text.AddState(ax::mojom::State::kFocusable);
+ button_text.SetName("button");
+
+ ui::AXNodeData button;
+ button.id = 1113;
+ button.role = ax::mojom::Role::kButton;
+ button.child_ids = {button_text.id};
+
+ ui::AXNodeData form;
+ form.id = 111;
+ form.role = ax::mojom::Role::kForm;
+ form.child_ids = {label.id, input_time.id, button.id};
+
+ ui::AXNodeData container;
+ container.id = 11;
+ container.role = ax::mojom::Role::kGenericContainer;
+ container.child_ids = {form.id};
+
+ ui::AXNodeData root;
+ root.id = 1;
+ root.role = ax::mojom::Role::kRootWebArea;
+ root.child_ids = {container.id};
+
+ std::unique_ptr<BrowserAccessibilityManager> manager(
+ BrowserAccessibilityManager::Create(
+ MakeAXTreeUpdate(root, container, form, label, label_text, input_time,
+ input_container, input_text, button, button_text),
+ test_browser_accessibility_delegate_.get()));
+
+ BrowserAccessibility* root_obj = manager->GetRoot();
+ EXPECT_FALSE(root_obj->PlatformIsLeaf());
+ EXPECT_TRUE(root_obj->CanFireEvents());
+ BrowserAccessibility* label_obj = manager->GetFromID(1111);
+ EXPECT_TRUE(label_obj->PlatformIsLeaf());
+ EXPECT_TRUE(label_obj->CanFireEvents());
+ BrowserAccessibility* label_text_obj = manager->GetFromID(11111);
+ EXPECT_TRUE(label_text_obj->PlatformIsLeaf());
+ EXPECT_FALSE(label_text_obj->CanFireEvents());
+ BrowserAccessibility* updated = manager->RetargetForEvents(
+ label_text_obj, BrowserAccessibilityManager::RetargetEventType::
+ RetargetEventTypeBlinkHover);
+ // |updated| should be the labelText.
+ EXPECT_EQ(1111, updated->GetId());
+ EXPECT_TRUE(updated->CanFireEvents());
+
+ BrowserAccessibility* input_time_obj = manager->GetFromID(1112);
+ EXPECT_TRUE(input_time_obj->PlatformIsLeaf());
+ EXPECT_TRUE(input_time_obj->CanFireEvents());
+ BrowserAccessibility* input_time_container_obj = manager->GetFromID(11121);
+ EXPECT_TRUE(input_time_container_obj->PlatformIsLeaf());
+ EXPECT_FALSE(input_time_container_obj->CanFireEvents());
+ updated = manager->RetargetForEvents(
+ input_time_container_obj, BrowserAccessibilityManager::RetargetEventType::
+ RetargetEventTypeBlinkHover);
+ // |updated| should be the inputTime.
+ EXPECT_EQ(1112, updated->GetId());
+ EXPECT_TRUE(updated->CanFireEvents());
+ BrowserAccessibility* input_text_obj = manager->GetFromID(111211);
+ EXPECT_TRUE(input_text_obj->PlatformIsLeaf());
+ EXPECT_FALSE(input_text_obj->CanFireEvents());
+ updated = manager->RetargetForEvents(
+ input_text_obj, BrowserAccessibilityManager::RetargetEventType::
+ RetargetEventTypeBlinkHover);
+ // |updated| should be the inputTime.
+ EXPECT_EQ(1112, updated->GetId());
+ EXPECT_TRUE(updated->CanFireEvents());
+
+ BrowserAccessibility* button_obj = manager->GetFromID(1113);
+ EXPECT_TRUE(button_obj->PlatformIsLeaf());
+ EXPECT_TRUE(button_obj->CanFireEvents());
+ BrowserAccessibility* button_text_obj = manager->GetFromID(11131);
+ EXPECT_TRUE(button_text_obj->PlatformIsLeaf());
+ EXPECT_FALSE(button_text_obj->CanFireEvents());
+ updated = manager->RetargetForEvents(
+ button_text_obj, BrowserAccessibilityManager::RetargetEventType::
+ RetargetEventTypeBlinkHover);
+ // |updated| should be the button.
+ EXPECT_EQ(1113, updated->GetId());
+ EXPECT_TRUE(updated->CanFireEvents());
+ manager.reset();
+}
+
+} // namespace content
diff --git a/chromium/content/browser/accessibility/browser_accessibility_auralinux_unittest.cc b/chromium/content/browser/accessibility/browser_accessibility_auralinux_unittest.cc
index 5e588edf716..e3f0043c295 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_auralinux_unittest.cc
+++ b/chromium/content/browser/accessibility/browser_accessibility_auralinux_unittest.cc
@@ -179,33 +179,31 @@ TEST_F(BrowserAccessibilityAuraLinuxTest, TestComplexHypertext) {
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_text.SetName(radio_button_text_name);
radio_button.role = ax::mojom::Role::kRadioButton;
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_text.SetName(link_text_name);
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.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(radio_button.id);
- root.child_ids.push_back(link.id);
+ 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(
@@ -355,6 +353,7 @@ TEST_F(BrowserAccessibilityAuraLinuxTest,
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;
diff --git a/chromium/content/browser/accessibility/browser_accessibility_cocoa.h b/chromium/content/browser/accessibility/browser_accessibility_cocoa.h
index 5124b17f020..947a5482780 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_cocoa.h
+++ b/chromium/content/browser/accessibility/browser_accessibility_cocoa.h
@@ -28,6 +28,23 @@ struct AXTextEdit {
base::string16 deleted_text;
};
+// Returns true if the given object is AXTextMarker object.
+bool IsAXTextMarker(id);
+
+// Returns true if the given object is AXTextMarkerRange object.
+bool IsAXTextMarkerRange(id);
+
+// Returns browser accessibility position for the given AXTextMarker.
+BrowserAccessibilityPosition::AXPositionInstance AXTextMarkerToPosition(id);
+
+// Returns browser accessibility range for the given AXTextMarkerRange.
+BrowserAccessibilityPosition::AXRangeType AXTextMarkerRangeToRange(id);
+
+// Returns AXTextMarker for the given browser accessibility position.
+id AXTextMarkerFrom(const BrowserAccessibilityCocoa* anchor,
+ int offset,
+ ax::mojom::TextAffinity affinity);
+
} // namespace content
// BrowserAccessibilityCocoa is a cocoa wrapper around the BrowserAccessibility
diff --git a/chromium/content/browser/accessibility/browser_accessibility_cocoa.mm b/chromium/content/browser/accessibility/browser_accessibility_cocoa.mm
index dc282c8b9a7..8e2592948d6 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_cocoa.mm
+++ b/chromium/content/browser/accessibility/browser_accessibility_cocoa.mm
@@ -301,11 +301,10 @@ id CreateTextMarkerRange(const AXPlatformRange range) {
BrowserAccessibilityPositionInstance CreatePositionFromTextMarker(
id text_marker) {
- AXTextMarkerRef cf_text_marker = static_cast<AXTextMarkerRef>(text_marker);
- DCHECK(cf_text_marker);
- if (CFGetTypeID(cf_text_marker) != AXTextMarkerGetTypeID())
+ if (!content::IsAXTextMarker(text_marker))
return BrowserAccessibilityPosition::CreateNullPosition();
+ AXTextMarkerRef cf_text_marker = static_cast<AXTextMarkerRef>(text_marker);
if (AXTextMarkerGetLength(cf_text_marker) != sizeof(SerializedPosition))
return BrowserAccessibilityPosition::CreateNullPosition();
@@ -318,11 +317,12 @@ BrowserAccessibilityPositionInstance CreatePositionFromTextMarker(
}
AXPlatformRange CreateRangeFromTextMarkerRange(id marker_range) {
+ if (!content::IsAXTextMarkerRange(marker_range)) {
+ return AXPlatformRange();
+ }
+
AXTextMarkerRangeRef cf_marker_range =
static_cast<AXTextMarkerRangeRef>(marker_range);
- DCHECK(cf_marker_range);
- if (CFGetTypeID(cf_marker_range) != AXTextMarkerRangeGetTypeID())
- return AXPlatformRange();
base::ScopedCFTypeRef<AXTextMarkerRef> start_marker(
AXTextMarkerRangeCopyStartMarker(cf_marker_range));
@@ -727,6 +727,44 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
#define NSAccessibilityLanguageAttribute @"AXLanguage"
#endif
+bool content::IsAXTextMarker(id object) {
+ if (object == nil)
+ return false;
+
+ AXTextMarkerRef cf_text_marker = static_cast<AXTextMarkerRef>(object);
+ DCHECK(cf_text_marker);
+ return CFGetTypeID(cf_text_marker) == AXTextMarkerGetTypeID();
+}
+
+bool content::IsAXTextMarkerRange(id object) {
+ if (object == nil)
+ return false;
+
+ AXTextMarkerRangeRef cf_marker_range =
+ static_cast<AXTextMarkerRangeRef>(object);
+ DCHECK(cf_marker_range);
+ return CFGetTypeID(cf_marker_range) == AXTextMarkerRangeGetTypeID();
+}
+
+BrowserAccessibilityPosition::AXPositionInstance
+content::AXTextMarkerToPosition(id text_marker) {
+ return CreatePositionFromTextMarker(text_marker);
+}
+
+BrowserAccessibilityPosition::AXRangeType
+content::AXTextMarkerRangeToRange(id text_marker_range) {
+ return CreateRangeFromTextMarkerRange(text_marker_range);
+}
+
+id content::AXTextMarkerFrom(const BrowserAccessibilityCocoa* anchor,
+ int offset,
+ ax::mojom::TextAffinity affinity) {
+ BrowserAccessibility* anchor_node = [anchor owner];
+ BrowserAccessibilityPositionInstance position =
+ CreateTextPosition(*anchor_node, offset, affinity);
+ return CreateTextMarker(std::move(position));
+}
+
@implementation BrowserAccessibilityCocoa
+ (void)initialize {
@@ -1032,13 +1070,7 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
if (is_table_like) {
// If this is a table, return all column headers.
- std::set<int32_t> headerIds;
- for (int i = 0; i < *_owner->GetTableColCount(); i++) {
- std::vector<int32_t> colHeaderIds = table->GetColHeaderNodeIds(i);
- std::copy(colHeaderIds.begin(), colHeaderIds.end(),
- std::inserter(headerIds, headerIds.end()));
- }
- for (int32_t id : headerIds) {
+ for (int32_t id : table->GetColHeaderNodeIds()) {
BrowserAccessibility* cell = _owner->manager()->GetFromID(id);
if (cell)
[ret addObject:ToBrowserAccessibilityCocoa(cell)];
@@ -1246,7 +1278,8 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
std::string classes;
- if (_owner->GetHtmlAttribute("class", &classes)) {
+ if (_owner->GetStringAttribute(ax::mojom::StringAttribute::kClassName,
+ &classes)) {
std::vector<std::string> split_classes = base::SplitString(
classes, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
for (const auto& className : split_classes)
@@ -1824,6 +1857,23 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
base::string16 deletedText = oldValue.substr(i, oldValue.length() - i - j);
base::string16 insertedText = newValue.substr(i, newValue.length() - i - j);
+
+ // Heuristic for editable combobox. If more than 1 character is inserted or
+ // deleted, and the caret is at the end of the field, assume the entire text
+ // field changed.
+ // TODO(nektar) Remove this once editing intents are implemented,
+ // and the actual inserted and deleted text is passed over from Blink.
+ if ([self internalRole] == ax::mojom::Role::kTextFieldWithComboBox &&
+ (deletedText.length() > 1 || insertedText.length() > 1)) {
+ int sel_start, sel_end;
+ _owner->GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart, &sel_start);
+ _owner->GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, &sel_end);
+ if (size_t{sel_start} == newValue.length() &&
+ size_t{sel_end} == newValue.length()) {
+ // Don't include oldValue as it would be announced -- very confusing.
+ return content::AXTextEdit(newValue, base::string16());
+ }
+ }
return content::AXTextEdit(insertedText, deletedText);
}
@@ -2055,9 +2105,7 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
}
} else {
// Otherwise this is a cell, return the row headers for this cell.
- std::vector<int32_t> rowHeaderIds;
- _owner->node()->GetTableCellRowHeaderNodeIds(&rowHeaderIds);
- for (int32_t id : rowHeaderIds) {
+ for (int32_t id : _owner->node()->GetTableCellRowHeaderNodeIds()) {
BrowserAccessibility* cell = _owner->manager()->GetFromID(id);
if (cell)
[ret addObject:ToBrowserAccessibilityCocoa(cell)];
@@ -2388,12 +2436,9 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
} else if ([role isEqualToString:NSAccessibilityButtonRole]) {
// AXValue does not make sense for pure buttons.
return @"";
- } else if (_owner->HasIntAttribute(ax::mojom::IntAttribute::kCheckedState) ||
- [role isEqualToString:NSAccessibilityRadioButtonRole]) {
- // On Mac, tabs are exposed as radio buttons, and are treated as checkable.
+ } else if ([self isCheckable]) {
int value;
- const auto checkedState = static_cast<ax::mojom::CheckedState>(
- _owner->GetIntAttribute(ax::mojom::IntAttribute::kCheckedState));
+ const auto checkedState = _owner->GetData().GetCheckedState();
switch (checkedState) {
case ax::mojom::CheckedState::kTrue:
value = 1;
@@ -2464,11 +2509,8 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
if (![self instanceActive])
return nil;
- std::vector<int32_t> unique_cell_ids;
- _owner->node()->GetTableUniqueCellIds(&unique_cell_ids);
NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
- for (size_t i = 0; i < unique_cell_ids.size(); ++i) {
- int id = unique_cell_ids[i];
+ for (int32_t id : _owner->node()->GetTableUniqueCellIds()) {
BrowserAccessibility* cell = _owner->manager()->GetFromID(id);
if (cell)
[ret addObject:ToBrowserAccessibilityCocoa(cell)];
@@ -3568,6 +3610,14 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
return [self isIgnored];
}
+- (BOOL)isCheckable {
+ if (![self instanceActive])
+ return NO;
+
+ return _owner->GetData().HasCheckedState() ||
+ _owner->GetData().role == ax::mojom::Role::kTab;
+}
+
// Performs the given accessibility action on the webkit accessibility object
// that backs this object.
- (void)accessibilityPerformAction:(NSString*)action {
@@ -3583,7 +3633,7 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
if ([action isEqualToString:NSAccessibilityPressAction]) {
manager->DoDefaultAction(*_owner);
if (_owner->GetData().GetRestriction() != ax::mojom::Restriction::kNone ||
- !_owner->HasIntAttribute(ax::mojom::IntAttribute::kCheckedState))
+ ![self isCheckable])
return;
// Hack: preemptively set the checked state to what it should become,
// otherwise VoiceOver will very likely report the old, incorrect state to
@@ -3679,13 +3729,15 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
if (![self instanceActive])
return nil;
+ // The point we receive is in frame coordinates.
+ // Convert to screen coordinates and then to physical pixel coordinates.
BrowserAccessibilityManager* manager = _owner->manager();
gfx::Point screen_point(point.x, point.y);
screen_point +=
manager->GetViewBoundsInScreenCoordinates().OffsetFromOrigin();
gfx::Point physical_pixel_point =
- content::IsUseZoomForDSFEnabled()
+ IsUseZoomForDSFEnabled()
? screen_point
: ScaleToRoundedPoint(screen_point, manager->device_scale_factor());
@@ -3711,9 +3763,8 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
}
- (BOOL)accessibilityNotifiesWhenDestroyed {
- TRACE_EVENT1("accessibility",
- "BrowserAccessibilityCocoa::accessibilityNotifiesWhenDestroyed",
- "role=", ui::ToString([self internalRole]));
+ TRACE_EVENT0("accessibility",
+ "BrowserAccessibilityCocoa::accessibilityNotifiesWhenDestroyed");
// Indicate that BrowserAccessibilityCocoa will post a notification when it's
// destroyed (see -detach). This allows VoiceOver to do some internal things
// more efficiently.
diff --git a/chromium/content/browser/accessibility/browser_accessibility_com_win.cc b/chromium/content/browser/accessibility/browser_accessibility_com_win.cc
index 89fd920e1cb..a8a043f82b4 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_com_win.cc
+++ b/chromium/content/browser/accessibility/browser_accessibility_com_win.cc
@@ -318,9 +318,8 @@ IFACEMETHODIMP BrowserAccessibilityComWin::get_newText(
if (!new_text)
return E_INVALIDARG;
- if (!old_win_attributes_ && !force_new_hypertext_)
+ if (!old_win_attributes_)
return E_FAIL;
- force_new_hypertext_ = false;
size_t start, old_len, new_len;
ComputeHypertextRemovedAndInserted(&start, &old_len, &new_len);
@@ -446,18 +445,20 @@ IFACEMETHODIMP BrowserAccessibilityComWin::get_attributes(
*start_offset = FindStartOfStyle(offset, ax::mojom::MoveDirection::kBackward);
*end_offset = FindStartOfStyle(offset, ax::mojom::MoveDirection::kForward);
- const ui::TextAttributeList& attributes =
- offset_to_text_attributes().find(*start_offset)->second;
-
std::ostringstream attributes_stream;
- for (const ui::TextAttribute& attribute : attributes) {
- // Don't expose the default language value of "en-US".
- // TODO(nektar): Determine if it's possible to check against the interface
- // language.
- if (attribute.first == "language" && attribute.second == "en-US")
- continue;
-
- attributes_stream << attribute.first << ":" << attribute.second << ";";
+ auto iter = offset_to_text_attributes().find(*start_offset);
+ if (iter != offset_to_text_attributes().end()) {
+ const ui::TextAttributeList& attributes = iter->second;
+
+ for (const ui::TextAttribute& attribute : attributes) {
+ // Don't expose the default language value of "en-US".
+ // TODO(nektar): Determine if it's possible to check against the interface
+ // language.
+ if (attribute.first == "language" && attribute.second == "en-US")
+ continue;
+
+ attributes_stream << attribute.first << ":" << attribute.second << ";";
+ }
}
base::string16 attributes_str = base::UTF8ToUTF16(attributes_stream.str());
@@ -1481,7 +1482,11 @@ void BrowserAccessibilityComWin::UpdateStep3FireEvents() {
}
// Fire hypertext-related events.
- if (ShouldFireHypertextEvents()) {
+ // Do not fire removed/inserted when a name change event will be fired by
+ // AXEventGenerator, as they are providing redundant information and will
+ // lead to duplicate announcements.
+ if (name() == old_win_attributes_->name ||
+ GetData().GetNameFrom() == ax::mojom::NameFrom::kContents) {
size_t start, old_len, new_len;
ComputeHypertextRemovedAndInserted(&start, &old_len, &new_len);
if (old_len > 0) {
@@ -1501,22 +1506,6 @@ void BrowserAccessibilityComWin::UpdateStep3FireEvents() {
old_hypertext_ = ui::AXHypertext();
}
-bool BrowserAccessibilityComWin::ShouldFireHypertextEvents() const {
- // Do not fire removed/inserted when a name change event will be fired by
- // AXEventGenerator, as they are providing redundant information and will
- // lead to duplicate announcements.
- if (name() != old_win_attributes_->name &&
- GetData().GetNameFrom() != ax::mojom::NameFrom::kContents)
- return false;
-
- // Similarly, for changes to live-regions we already fire an inserted event in
- // BrowserAccessibilityManagerWin, so we don't want an extra event here.
- if (GetData().IsContainedInActiveLiveRegion())
- return false;
-
- return true;
-}
-
BrowserAccessibilityManager* BrowserAccessibilityComWin::Manager() const {
DCHECK(owner());
diff --git a/chromium/content/browser/accessibility/browser_accessibility_com_win.h b/chromium/content/browser/accessibility/browser_accessibility_com_win.h
index f67b0f5aa29..f839748e4e5 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_com_win.h
+++ b/chromium/content/browser/accessibility/browser_accessibility_com_win.h
@@ -356,9 +356,6 @@ class __declspec(uuid("562072fe-3390-43b1-9e2c-dd4118f5ac79"))
BrowserAccessibilityManager* Manager() const;
- // Private helper methods.
- bool ShouldFireHypertextEvents() const;
-
//
// AXPlatformNode overrides
//
diff --git a/chromium/content/browser/accessibility/browser_accessibility_mac_unittest.mm b/chromium/content/browser/accessibility/browser_accessibility_mac_unittest.mm
index d9be35fdbda..4a417e2929f 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_mac_unittest.mm
+++ b/chromium/content/browser/accessibility/browser_accessibility_mac_unittest.mm
@@ -90,10 +90,10 @@ class BrowserAccessibilityMacTest : public ui::CocoaTest {
ui::AXNodeData child1;
child1.id = 1001;
+ child1.role = ax::mojom::Role::kButton;
child1.SetName("Child1");
child1.relative_bounds.bounds.set_width(250);
child1.relative_bounds.bounds.set_height(100);
- child1.role = ax::mojom::Role::kButton;
ui::AXNodeData child2;
child2.id = 1002;
diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager.cc b/chromium/content/browser/accessibility/browser_accessibility_manager.cc
index f22af5da7db..739a19d13eb 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_manager.cc
+++ b/chromium/content/browser/accessibility/browser_accessibility_manager.cc
@@ -284,6 +284,12 @@ bool BrowserAccessibilityManager::CanFireEvents() const {
return true;
}
+BrowserAccessibility* BrowserAccessibilityManager::RetargetForEvents(
+ BrowserAccessibility* node,
+ RetargetEventType type) const {
+ return node;
+}
+
void BrowserAccessibilityManager::FireFocusEvent(BrowserAccessibility* node) {
if (g_focus_change_callback_for_testing.Get())
g_focus_change_callback_for_testing.Get().Run();
@@ -324,7 +330,8 @@ BrowserAccessibility* BrowserAccessibilityManager::GetParentNodeFromParentTree()
ui::AXTreeID parent_tree_id = GetParentTreeID();
BrowserAccessibilityManager* parent_manager =
BrowserAccessibilityManager::FromID(parent_tree_id);
- return parent ? parent_manager->GetFromAXNode(parent) : nullptr;
+ return parent && parent_manager ? parent_manager->GetFromAXNode(parent)
+ : nullptr;
}
BrowserAccessibility* BrowserAccessibilityManager::GetPopupRoot() const {
@@ -438,6 +445,8 @@ bool BrowserAccessibilityManager::OnAccessibilityEvents(
if (!connected_to_parent_tree_node_) {
parent->OnDataChanged();
parent->UpdatePlatformAttributes();
+ parent = RetargetForEvents(parent,
+ RetargetEventType::RetargetEventTypeGenerated);
FireGeneratedEvent(ui::AXEventGenerator::Event::CHILDREN_CHANGED, parent);
connected_to_parent_tree_node_ = true;
}
@@ -463,6 +472,11 @@ bool BrowserAccessibilityManager::OnAccessibilityEvents(
// Fire any events related to changes to the tree.
for (const auto& targeted_event : event_generator()) {
BrowserAccessibility* event_target = GetFromAXNode(targeted_event.node);
+ if (!event_target)
+ continue;
+
+ event_target = RetargetForEvents(
+ event_target, RetargetEventType::RetargetEventTypeGenerated);
if (!event_target || !event_target->CanFireEvents())
continue;
@@ -479,13 +493,20 @@ bool BrowserAccessibilityManager::OnAccessibilityEvents(
for (const ui::AXEvent& event : details.events) {
// Fire the native event.
BrowserAccessibility* event_target = GetFromID(event.id);
- if (!event_target || !event_target->CanFireEvents())
+ if (!event_target)
+ continue;
+ RetargetEventType type =
+ event.event_type == ax::mojom::Event::kHover
+ ? RetargetEventType::RetargetEventTypeBlinkHover
+ : RetargetEventType::RetargetEventTypeBlinkGeneral;
+ BrowserAccessibility* retargeted = RetargetForEvents(event_target, type);
+ if (!retargeted || !retargeted->CanFireEvents())
continue;
if (root_manager && event.event_type == ax::mojom::Event::kHover)
root_manager->CacheHitTestResult(event_target);
- FireBlinkEvent(event.event_type, event_target);
+ FireBlinkEvent(event.event_type, retargeted);
}
if (received_load_complete_event) {
@@ -896,11 +917,7 @@ void BrowserAccessibilityManager::HitTest(const gfx::Point& frame_point) const {
if (!delegate_)
return;
- ui::AXActionData action_data;
- action_data.action = ax::mojom::Action::kHitTest;
- action_data.target_point = frame_point;
- action_data.hit_test_event_to_fire = ax::mojom::Event::kHover;
- delegate_->AccessibilityPerformAction(action_data);
+ delegate_->AccessibilityHitTest(frame_point, ax::mojom::Event::kHover, 0, {});
}
gfx::Rect BrowserAccessibilityManager::GetViewBoundsInScreenCoordinates()
@@ -1560,7 +1577,8 @@ void BrowserAccessibilityManager::CacheHitTestResult(
}
void BrowserAccessibilityManager::DidActivatePortal(
- WebContents* predecessor_contents) {
+ WebContents* predecessor_contents,
+ base::TimeTicks activation_time) {
if (GetTreeData().loaded) {
FireGeneratedEvent(ui::AXEventGenerator::Event::PORTAL_ACTIVATED,
GetRoot());
@@ -1595,7 +1613,8 @@ void BrowserAccessibilityManager::CollectChangedNodesAndParentsForAtomicUpdate(
if (!parent)
continue;
- if (ui::IsTextOrLineBreak(changed_node->data().role)) {
+ if (changed_node->IsText() &&
+ changed_node->data().role != ax::mojom::Role::kInlineTextBox) {
BrowserAccessibility* parent_obj = GetFromAXNode(parent);
if (parent_obj)
nodes_needing_update->insert(parent_obj->GetAXPlatformNode());
@@ -1618,6 +1637,7 @@ void BrowserAccessibilityManager::CollectChangedNodesAndParentsForAtomicUpdate(
bool BrowserAccessibilityManager::ShouldFireEventForNode(
BrowserAccessibility* node) const {
+ node = RetargetForEvents(node, RetargetEventType::RetargetEventTypeGenerated);
if (!node || !node->CanFireEvents())
return false;
diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager.h b/chromium/content/browser/accessibility/browser_accessibility_manager.h
index 8892004aaac..809ca3a2ad2 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_manager.h
+++ b/chromium/content/browser/accessibility/browser_accessibility_manager.h
@@ -96,6 +96,12 @@ class CONTENT_EXPORT BrowserAccessibilityDelegate {
virtual gfx::NativeViewAccessible
AccessibilityGetNativeViewAccessibleForWindow() = 0;
virtual WebContents* AccessibilityWebContents() = 0;
+ virtual void AccessibilityHitTest(
+ const gfx::Point& point_in_frame_pixels,
+ ax::mojom::Event opt_event_to_fire,
+ int opt_request_id,
+ base::OnceCallback<void(BrowserAccessibilityManager* hit_manager,
+ int hit_node_id)> opt_callback) = 0;
// Returns true if this delegate represents the main (topmost) frame in a
// tree of frames.
@@ -147,6 +153,17 @@ class CONTENT_EXPORT BrowserAccessibilityManager : public ui::AXTreeObserver,
static ui::AXTreeUpdate GetEmptyDocument();
+ enum RetargetEventType {
+ RetargetEventTypeGenerated = 0,
+ RetargetEventTypeBlinkGeneral,
+ RetargetEventTypeBlinkHover,
+ };
+
+ // Return |node| by default, but some platforms want to update the target node
+ // based on the event type.
+ virtual BrowserAccessibility* RetargetForEvents(BrowserAccessibility* node,
+ RetargetEventType type) const;
+
// Subclasses override these methods to send native event notifications.
virtual void FireFocusEvent(BrowserAccessibility* node);
virtual void FireBlinkEvent(ax::mojom::Event event_type,
@@ -202,7 +219,8 @@ class CONTENT_EXPORT BrowserAccessibilityManager : public ui::AXTreeObserver,
// WebContentsObserver overrides
void DidStopLoading() override;
- void DidActivatePortal(WebContents* predecessor_contents) override;
+ void DidActivatePortal(WebContents* predecessor_contents,
+ base::TimeTicks activation_time) override;
// Keep track of if this page is hidden by an interstitial, in which case
// we need to suppress all events.
diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_android.cc b/chromium/content/browser/accessibility/browser_accessibility_manager_android.cc
index 7ac1ed3294c..d52cec13ea1 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_manager_android.cc
+++ b/chromium/content/browser/accessibility/browser_accessibility_manager_android.cc
@@ -72,6 +72,55 @@ BrowserAccessibility* BrowserAccessibilityManagerAndroid::GetFocus() const {
return focus;
}
+BrowserAccessibility* BrowserAccessibilityManagerAndroid::RetargetForEvents(
+ BrowserAccessibility* node,
+ RetargetEventType type) const {
+ // Sometimes we get events on nodes in our internal accessibility tree
+ // that aren't exposed on Android. Get |updated| to point to the highest
+ // ancestor that's a leaf node.
+ BrowserAccessibility* updated = node->PlatformGetClosestPlatformObject();
+
+ switch (type) {
+ case RetargetEventType::RetargetEventTypeGenerated: {
+ // If the closest platform object is a password field, the event we're
+ // getting is doing something in the shadow dom, for example replacing a
+ // character with a dot after a short pause. On Android we don't want to
+ // fire an event for those changes, but we do want to make sure our
+ // internal state is correct, so we call OnDataChanged() and then return.
+ if (updated->IsPasswordField() && node != updated) {
+ updated->OnDataChanged();
+ return nullptr;
+ }
+ break;
+ }
+ case RetargetEventType::RetargetEventTypeBlinkGeneral:
+ break;
+ case RetargetEventType::RetargetEventTypeBlinkHover: {
+ // If this node is uninteresting and just a wrapper around a sole
+ // interesting descendant, prefer that descendant instead.
+ const BrowserAccessibilityAndroid* android_node =
+ static_cast<BrowserAccessibilityAndroid*>(updated);
+ const BrowserAccessibilityAndroid* sole_interesting_node =
+ android_node->GetSoleInterestingNodeFromSubtree();
+ if (sole_interesting_node)
+ android_node = sole_interesting_node;
+
+ // Finally, if this node is still uninteresting, try to walk up to
+ // find an interesting parent.
+ while (android_node && !android_node->IsInterestingOnAndroid()) {
+ android_node = static_cast<BrowserAccessibilityAndroid*>(
+ android_node->PlatformGetParent());
+ }
+ updated = const_cast<BrowserAccessibilityAndroid*>(android_node);
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+ return updated;
+}
+
void BrowserAccessibilityManagerAndroid::FireFocusEvent(
BrowserAccessibility* node) {
BrowserAccessibilityManager::FireFocusEvent(node);
@@ -103,10 +152,6 @@ void BrowserAccessibilityManagerAndroid::FireBlinkEvent(
if (!wcax)
return;
- // Sometimes we get events on nodes in our internal accessibility tree
- // that aren't exposed on Android. Update |node| to point to the highest
- // ancestor that's a leaf node.
- node = node->PlatformGetClosestPlatformObject();
BrowserAccessibilityAndroid* android_node =
static_cast<BrowserAccessibilityAndroid*>(node);
@@ -133,24 +178,9 @@ void BrowserAccessibilityManagerAndroid::FireGeneratedEvent(
if (!wcax)
return;
- // Sometimes we get events on nodes in our internal accessibility tree
- // that aren't exposed on Android. Update |node| to point to the highest
- // ancestor that's a leaf node.
- BrowserAccessibility* original_node = node;
- node = node->PlatformGetClosestPlatformObject();
BrowserAccessibilityAndroid* android_node =
static_cast<BrowserAccessibilityAndroid*>(node);
- // If the closest platform object is a password field, the event we're
- // getting is doing something in the shadow dom, for example replacing a
- // character with a dot after a short pause. On Android we don't want to
- // fire an event for those changes, but we do want to make sure our internal
- // state is correct, so we call OnDataChanged() and then return.
- if (android_node->IsPasswordField() && original_node != node) {
- android_node->OnDataChanged();
- return;
- }
-
// Always send AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED to notify
// the Android system that the accessibility hierarchy rooted at this
// node has changed.
@@ -390,25 +420,8 @@ void BrowserAccessibilityManagerAndroid::HandleHoverEvent(
if (!wcax)
return;
- // First walk up to the nearest platform node, in case this node isn't
- // even exposed on the platform.
- node = node->PlatformGetClosestPlatformObject();
-
- // If this node is uninteresting and just a wrapper around a sole
- // interesting descendant, prefer that descendant instead.
- const BrowserAccessibilityAndroid* android_node =
+ BrowserAccessibilityAndroid* android_node =
static_cast<BrowserAccessibilityAndroid*>(node);
- const BrowserAccessibilityAndroid* sole_interesting_node =
- android_node->GetSoleInterestingNodeFromSubtree();
- if (sole_interesting_node)
- android_node = sole_interesting_node;
-
- // Finally, if this node is still uninteresting, try to walk up to
- // find an interesting parent.
- while (android_node && !android_node->IsInterestingOnAndroid()) {
- android_node = static_cast<BrowserAccessibilityAndroid*>(
- android_node->PlatformGetParent());
- }
if (android_node)
wcax->HandleHover(android_node->unique_id());
diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_android.h b/chromium/content/browser/accessibility/browser_accessibility_manager_android.h
index bf675e03573..2a175634095 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_manager_android.h
+++ b/chromium/content/browser/accessibility/browser_accessibility_manager_android.h
@@ -76,6 +76,9 @@ class CONTENT_EXPORT BrowserAccessibilityManagerAndroid
BrowserAccessibility* GetFocus() const override;
void SendLocationChangeEvents(
const std::vector<mojom::LocationChangesPtr>& changes) override;
+ BrowserAccessibility* RetargetForEvents(
+ BrowserAccessibility* node,
+ RetargetEventType type) const override;
void FireFocusEvent(BrowserAccessibility* node) override;
void FireBlinkEvent(ax::mojom::Event event_type,
BrowserAccessibility* node) override;
diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_mac.mm b/chromium/content/browser/accessibility/browser_accessibility_manager_mac.mm
index 07645b4165b..9795a4ce389 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_manager_mac.mm
+++ b/chromium/content/browser/accessibility/browser_accessibility_manager_mac.mm
@@ -11,7 +11,6 @@
#import "base/mac/scoped_nsobject.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
-#include "base/task/post_task.h"
#include "base/time/time.h"
#import "content/browser/accessibility/browser_accessibility_cocoa.h"
#import "content/browser/accessibility/browser_accessibility_mac.h"
@@ -372,8 +371,8 @@ void BrowserAccessibilityManagerMac::FireGeneratedEvent(
// Use native VoiceOver support for live regions.
base::scoped_nsobject<BrowserAccessibilityCocoa> retained_node(
[native_node retain]);
- base::PostDelayedTask(
- FROM_HERE, {BrowserThread::UI},
+ GetUIThreadTaskRunner({})->PostDelayedTask(
+ FROM_HERE,
base::BindOnce(
[](base::scoped_nsobject<BrowserAccessibilityCocoa> node) {
if (node && [node instanceActive]) {
@@ -557,10 +556,12 @@ BrowserAccessibilityManagerMac::GetUserInfoForValueChangedNotification(
}];
}
if (!inserted_text.empty()) {
- // TODO(nektar): Figure out if this is a paste operation instead of typing.
- // Changes to Blink would be required.
+ // TODO(nektar): Figure out if this is a paste, insertion or typing.
+ // Changes to Blink would be required. A heuristic is currently used.
+ auto edit_type = inserted_text.length() > 1 ? @(AXTextEditTypeInsert)
+ : @(AXTextEditTypeTyping);
[changes addObject:@{
- NSAccessibilityTextEditType : @(AXTextEditTypeTyping),
+ NSAccessibilityTextEditType : edit_type,
NSAccessibilityTextChangeValueLength : @(inserted_text.length()),
NSAccessibilityTextChangeValue : base::SysUTF16ToNSString(inserted_text)
}];
diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_unittest.cc b/chromium/content/browser/accessibility/browser_accessibility_manager_unittest.cc
index 101b72a2ea8..971f4519665 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_manager_unittest.cc
+++ b/chromium/content/browser/accessibility/browser_accessibility_manager_unittest.cc
@@ -126,15 +126,15 @@ TEST_F(BrowserAccessibilityManagerTest, BoundsForRange) {
ui::AXNodeData static_text;
static_text.id = 2;
- static_text.SetName("Hello, world.");
static_text.role = ax::mojom::Role::kStaticText;
+ static_text.SetName("Hello, world.");
static_text.relative_bounds.bounds = gfx::RectF(100, 100, 29, 18);
root.child_ids.push_back(2);
ui::AXNodeData inline_text1;
inline_text1.id = 3;
- inline_text1.SetName("Hello, ");
inline_text1.role = ax::mojom::Role::kInlineTextBox;
+ inline_text1.SetName("Hello, ");
inline_text1.relative_bounds.bounds = gfx::RectF(100, 100, 29, 9);
inline_text1.SetTextDirection(ax::mojom::TextDirection::kLtr);
std::vector<int32_t> character_offsets1;
@@ -151,8 +151,8 @@ TEST_F(BrowserAccessibilityManagerTest, BoundsForRange) {
ui::AXNodeData inline_text2;
inline_text2.id = 4;
- inline_text2.SetName("world.");
inline_text2.role = ax::mojom::Role::kInlineTextBox;
+ inline_text2.SetName("world.");
inline_text2.relative_bounds.bounds = gfx::RectF(100, 109, 28, 9);
inline_text2.SetTextDirection(ax::mojom::TextDirection::kLtr);
std::vector<int32_t> character_offsets2;
@@ -232,15 +232,15 @@ TEST_F(BrowserAccessibilityManagerTest, BoundsForRangeMultiElement) {
ui::AXNodeData static_text;
static_text.id = 2;
- static_text.SetName("ABC");
static_text.role = ax::mojom::Role::kStaticText;
+ static_text.SetName("ABC");
static_text.relative_bounds.bounds = gfx::RectF(0, 20, 33, 9);
root.child_ids.push_back(2);
ui::AXNodeData inline_text1;
inline_text1.id = 3;
- inline_text1.SetName("ABC");
inline_text1.role = ax::mojom::Role::kInlineTextBox;
+ inline_text1.SetName("ABC");
inline_text1.relative_bounds.bounds = gfx::RectF(0, 20, 33, 9);
inline_text1.SetTextDirection(ax::mojom::TextDirection::kLtr);
std::vector<int32_t> character_offsets{10, 21, 33};
@@ -250,15 +250,15 @@ TEST_F(BrowserAccessibilityManagerTest, BoundsForRangeMultiElement) {
ui::AXNodeData static_text2;
static_text2.id = 4;
- static_text2.SetName("ABC");
static_text2.role = ax::mojom::Role::kStaticText;
+ static_text2.SetName("ABC");
static_text2.relative_bounds.bounds = gfx::RectF(10, 40, 33, 9);
root.child_ids.push_back(4);
ui::AXNodeData inline_text2;
inline_text2.id = 5;
- inline_text2.SetName("ABC");
inline_text2.role = ax::mojom::Role::kInlineTextBox;
+ inline_text2.SetName("ABC");
inline_text2.relative_bounds.bounds = gfx::RectF(10, 40, 33, 9);
inline_text2.SetTextDirection(ax::mojom::TextDirection::kLtr);
inline_text2.AddIntListAttribute(
@@ -350,15 +350,15 @@ TEST_F(BrowserAccessibilityManagerTest, BoundsForRangeBiDi) {
ui::AXNodeData static_text;
static_text.id = 2;
- static_text.SetName("123abc");
static_text.role = ax::mojom::Role::kStaticText;
+ static_text.SetName("123abc");
static_text.relative_bounds.bounds = gfx::RectF(100, 100, 60, 20);
root.child_ids.push_back(2);
ui::AXNodeData inline_text1;
inline_text1.id = 3;
- inline_text1.SetName("123");
inline_text1.role = ax::mojom::Role::kInlineTextBox;
+ inline_text1.SetName("123");
inline_text1.relative_bounds.bounds = gfx::RectF(100, 100, 30, 20);
inline_text1.SetTextDirection(ax::mojom::TextDirection::kLtr);
std::vector<int32_t> character_offsets1;
@@ -371,8 +371,8 @@ TEST_F(BrowserAccessibilityManagerTest, BoundsForRangeBiDi) {
ui::AXNodeData inline_text2;
inline_text2.id = 4;
- inline_text2.SetName("abc");
inline_text2.role = ax::mojom::Role::kInlineTextBox;
+ inline_text2.SetName("abc");
inline_text2.relative_bounds.bounds = gfx::RectF(130, 100, 30, 20);
inline_text2.SetTextDirection(ax::mojom::TextDirection::kRtl);
std::vector<int32_t> character_offsets2;
@@ -447,15 +447,15 @@ TEST_F(BrowserAccessibilityManagerTest, BoundsForRangeScrolledWindow) {
ui::AXNodeData static_text;
static_text.id = 2;
- static_text.SetName("ABC");
static_text.role = ax::mojom::Role::kStaticText;
+ static_text.SetName("ABC");
static_text.relative_bounds.bounds = gfx::RectF(100, 100, 16, 9);
root.child_ids.push_back(2);
ui::AXNodeData inline_text;
inline_text.id = 3;
- inline_text.SetName("ABC");
inline_text.role = ax::mojom::Role::kInlineTextBox;
+ inline_text.SetName("ABC");
inline_text.relative_bounds.bounds = gfx::RectF(100, 100, 16, 9);
inline_text.SetTextDirection(ax::mojom::TextDirection::kLtr);
std::vector<int32_t> character_offsets1;
@@ -513,28 +513,28 @@ TEST_F(BrowserAccessibilityManagerTest, BoundsForRangeOnParentElement) {
ui::AXNodeData static_text1;
static_text1.id = 3;
- static_text1.SetName("AB");
static_text1.role = ax::mojom::Role::kStaticText;
+ static_text1.SetName("AB");
static_text1.relative_bounds.bounds = gfx::RectF(100, 100, 40, 20);
static_text1.child_ids.push_back(6);
ui::AXNodeData img;
img.id = 4;
- img.SetName("Test image");
img.role = ax::mojom::Role::kImage;
+ img.SetName("Test image");
img.relative_bounds.bounds = gfx::RectF(140, 100, 20, 20);
ui::AXNodeData static_text2;
static_text2.id = 5;
- static_text2.SetName("CD");
static_text2.role = ax::mojom::Role::kStaticText;
+ static_text2.SetName("CD");
static_text2.relative_bounds.bounds = gfx::RectF(160, 100, 40, 20);
static_text2.child_ids.push_back(7);
ui::AXNodeData inline_text1;
inline_text1.id = 6;
- inline_text1.SetName("AB");
inline_text1.role = ax::mojom::Role::kInlineTextBox;
+ inline_text1.SetName("AB");
inline_text1.relative_bounds.bounds = gfx::RectF(100, 100, 40, 20);
inline_text1.SetTextDirection(ax::mojom::TextDirection::kLtr);
std::vector<int32_t> character_offsets1;
@@ -545,8 +545,8 @@ TEST_F(BrowserAccessibilityManagerTest, BoundsForRangeOnParentElement) {
ui::AXNodeData inline_text2;
inline_text2.id = 7;
- inline_text2.SetName("CD");
inline_text2.role = ax::mojom::Role::kInlineTextBox;
+ inline_text2.SetName("CD");
inline_text2.relative_bounds.bounds = gfx::RectF(160, 100, 40, 20);
inline_text2.SetTextDirection(ax::mojom::TextDirection::kLtr);
std::vector<int32_t> character_offsets2;
@@ -1342,6 +1342,7 @@ TEST_F(BrowserAccessibilityManagerTest, TestHitTestScaled) {
ui::AXNodeData child_child;
child_child.id = 2;
+ child_child.role = ax::mojom::Role::kGenericContainer;
child_child.SetName("child_child");
child_child.relative_bounds.bounds = gfx::RectF(0, 0, 100, 100);
@@ -1358,6 +1359,7 @@ TEST_F(BrowserAccessibilityManagerTest, TestHitTestScaled) {
ui::AXNodeData parent_child;
parent_child.id = 2;
+ parent_child.role = ax::mojom::Role::kGenericContainer;
parent_child.SetName("parent_child");
parent_child.relative_bounds.bounds = gfx::RectF(0, 0, 100, 100);
@@ -1408,10 +1410,12 @@ TEST_F(BrowserAccessibilityManagerTest, TestShouldFireEventForNode) {
ui::AXNodeData inline_text;
inline_text.id = 1111;
inline_text.role = ax::mojom::Role::kInlineTextBox;
+ inline_text.SetName("One two three.");
ui::AXNodeData text;
text.id = 111;
text.role = ax::mojom::Role::kStaticText;
+ text.SetName("One two three.");
text.child_ids = {inline_text.id};
ui::AXNodeData paragraph;
@@ -1432,7 +1436,13 @@ TEST_F(BrowserAccessibilityManagerTest, TestShouldFireEventForNode) {
EXPECT_TRUE(manager->ShouldFireEventForNode(manager->GetFromID(1)));
EXPECT_TRUE(manager->ShouldFireEventForNode(manager->GetFromID(11)));
EXPECT_TRUE(manager->ShouldFireEventForNode(manager->GetFromID(111)));
+#if defined(OS_ANDROID)
+ // On Android, ShouldFireEventForNode walks up the ancestor that's a leaf node
+ // node and the event is fired on the updated target.
+ EXPECT_TRUE(manager->ShouldFireEventForNode(manager->GetFromID(1111)));
+#else
EXPECT_FALSE(manager->ShouldFireEventForNode(manager->GetFromID(1111)));
+#endif
}
TEST_F(BrowserAccessibilityManagerTest, NestedChildRoot) {
diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_win.cc b/chromium/content/browser/accessibility/browser_accessibility_manager_win.cc
index cc3a7d1c522..579fafab388 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_manager_win.cc
+++ b/chromium/content/browser/accessibility/browser_accessibility_manager_win.cc
@@ -23,6 +23,7 @@
#include "ui/accessibility/ax_role_properties.h"
#include "ui/accessibility/platform/ax_fragment_root_win.h"
#include "ui/accessibility/platform/ax_platform_node_delegate_utils_win.h"
+#include "ui/accessibility/platform/uia_registrar_win.h"
#include "ui/base/win/atl_module.h"
namespace content {
@@ -95,24 +96,11 @@ void BrowserAccessibilityManagerWin::FireBlinkEvent(
if (node->GetData().IsInvocable())
FireUiaAccessibilityEvent(UIA_Invoke_InvokedEventId, node);
break;
- case ax::mojom::Event::kEndOfTest: {
- if (::switches::IsExperimentalAccessibilityPlatformUIAEnabled()) {
- // Event tests use kEndOfTest as a sentinel to mark the end of the test.
- Microsoft::WRL::ComPtr<IUIAutomationRegistrar> registrar;
- CoCreateInstance(CLSID_CUIAutomationRegistrar, NULL,
- CLSCTX_INPROC_SERVER, IID_IUIAutomationRegistrar,
- &registrar);
- CHECK(registrar.Get());
- UIAutomationEventInfo custom_event = {kUiaTestCompleteSentinelGuid,
- kUiaTestCompleteSentinel};
- EVENTID custom_event_id = 0;
- CHECK(SUCCEEDED(
- registrar->RegisterEvent(&custom_event, &custom_event_id)));
-
- FireUiaAccessibilityEvent(custom_event_id, node);
- }
+ case ax::mojom::Event::kEndOfTest:
+ // Event tests use kEndOfTest as a sentinel to mark the end of the test.
+ FireUiaAccessibilityEvent(
+ ui::UiaRegistrarWin::GetInstance().GetUiaTestCompleteEventId(), node);
break;
- }
case ax::mojom::Event::kLocationChanged:
FireWinAccessibilityEvent(IA2_EVENT_VISIBLE_DATA_CHANGED, node);
break;
@@ -171,8 +159,8 @@ void BrowserAccessibilityManagerWin::FireGeneratedEvent(
aria_properties_events_.insert(node);
break;
case ui::AXEventGenerator::Event::CHILDREN_CHANGED: {
- // If this node is ignored, notify from the platform parent if available,
- // since it will be unignored.
+ // If this node is ignored, fire the event on the platform parent since
+ // ignored nodes cannot raise events.
BrowserAccessibility* target_node =
node->IsIgnored() ? node->PlatformGetParent() : node;
if (target_node) {
@@ -264,17 +252,6 @@ void BrowserAccessibilityManagerWin::FireGeneratedEvent(
FireUiaAccessibilityEvent(UIA_LiveRegionChangedEventId, node);
break;
case ui::AXEventGenerator::Event::LIVE_REGION_CHANGED:
- // This will force ATs that synchronously call get_newText (e.g., NVDA) to
- // read the entire live region hypertext.
- ToBrowserAccessibilityWin(node)->GetCOM()->ForceNewHypertext();
- // TODO(accessibility) Technically this should only be fired if the new
- // text is non-empty. Also, IA2_EVENT_TEXT_REMOVED should be fired if
- // there was non-empty old text. However, this does not known to affect
- // any current screen reader behavior either way. It could affect
- // the aria-relevant="removals" case, but that in general is poorly
- // supported markup across browser-AT combinations, and not recommended.
- FireWinAccessibilityEvent(IA2_EVENT_TEXT_INSERTED, node);
-
// This event is redundant with the IA2_EVENT_TEXT_INSERTED events;
// however, JAWS 2018 and earlier do not process the text inserted
// events when "virtual cursor mode" is turned off (Insert+Z).
@@ -310,11 +287,9 @@ void BrowserAccessibilityManagerWin::FireGeneratedEvent(
break;
case ui::AXEventGenerator::Event::NAME_CHANGED:
FireUiaPropertyChangedEvent(UIA_NamePropertyId, node);
- // Only fire name changes when the name comes from an attribute, and is
- // not contained within an active live-region; otherwise name changes are
- // redundant with text removed/inserted events.
- if (node->GetData().GetNameFrom() != ax::mojom::NameFrom::kContents &&
- !node->GetData().IsContainedInActiveLiveRegion())
+ // Only fire name changes when the name comes from an attribute, otherwise
+ // name changes are redundant with text removed/inserted events.
+ if (node->GetData().GetNameFrom() != ax::mojom::NameFrom::kContents)
FireWinAccessibilityEvent(EVENT_OBJECT_NAMECHANGE, node);
break;
case ui::AXEventGenerator::Event::PLACEHOLDER_CHANGED:
@@ -424,10 +399,13 @@ void BrowserAccessibilityManagerWin::FireWinAccessibilityEvent(
// Suppress events when |IGNORED_CHANGED| except for related SHOW / HIDE.
// Also include MENUPOPUPSTART / MENUPOPUPEND since a change in the ignored
// state may show / hide a popup by exposing it to the tree or not.
+ // Also include focus events since a node may become visible at the same time
+ // it receives focus It's never good to suppress a po
if (base::Contains(ignored_changed_nodes_, node)) {
switch (win_event_type) {
case EVENT_OBJECT_HIDE:
case EVENT_OBJECT_SHOW:
+ case EVENT_OBJECT_FOCUS:
case EVENT_SYSTEM_MENUPOPUPEND:
case EVENT_SYSTEM_MENUPOPUPSTART:
break;
@@ -499,7 +477,7 @@ void BrowserAccessibilityManagerWin::FireUiaPropertyChangedEvent(
auto* provider = ToBrowserAccessibilityWin(node)->GetCOM();
base::win::ScopedVariant new_value;
if (SUCCEEDED(
- provider->GetPropertyValue(uia_property, new_value.Receive()))) {
+ provider->GetPropertyValueImpl(uia_property, new_value.Receive()))) {
::UiaRaiseAutomationPropertyChangedEvent(provider, uia_property, old_value,
new_value);
}
diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_win.h b/chromium/content/browser/accessibility/browser_accessibility_manager_win.h
index d1baa803849..20c72281451 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_manager_win.h
+++ b/chromium/content/browser/accessibility/browser_accessibility_manager_win.h
@@ -19,14 +19,6 @@
namespace content {
class BrowserAccessibilityWin;
-// {3761326A-34B2-465A-835D-7A3D8F4EFB92}
-static const GUID kUiaTestCompleteSentinelGuid = {
- 0x3761326a,
- 0x34b2,
- 0x465a,
- {0x83, 0x5d, 0x7a, 0x3d, 0x8f, 0x4e, 0xfb, 0x92}};
-static const wchar_t kUiaTestCompleteSentinel[] = L"kUiaTestCompleteSentinel";
-
// Manages a tree of BrowserAccessibilityWin objects.
class CONTENT_EXPORT BrowserAccessibilityManagerWin
: public BrowserAccessibilityManager {
diff --git a/chromium/content/browser/accessibility/browser_accessibility_state_impl.cc b/chromium/content/browser/accessibility/browser_accessibility_state_impl.cc
index 352063f1006..68243d5ec48 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_state_impl.cc
+++ b/chromium/content/browser/accessibility/browser_accessibility_state_impl.cc
@@ -11,7 +11,6 @@
#include "base/debug/crash_logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
-#include "base/task/post_task.h"
#include "base/task/thread_pool.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
@@ -89,8 +88,8 @@ BrowserAccessibilityStateImpl::BrowserAccessibilityStateImpl()
base::TimeDelta::FromSeconds(ACCESSIBILITY_HISTOGRAM_DELAY_SECS));
// Other things must be done on the UI thread (e.g. to access PrefService).
- base::PostDelayedTask(
- FROM_HERE, {BrowserThread::UI},
+ GetUIThreadTaskRunner({})->PostDelayedTask(
+ FROM_HERE,
base::BindOnce(&BrowserAccessibilityStateImpl::UpdateHistogramsOnUIThread,
this),
base::TimeDelta::FromSeconds(ACCESSIBILITY_HISTOGRAM_DELAY_SECS));
@@ -178,8 +177,9 @@ void BrowserAccessibilityStateImpl::UpdateHistogramsOnUIThread() {
#if defined(OS_WIN)
UMA_HISTOGRAM_ENUMERATION(
"Accessibility.WinHighContrastTheme",
- ui::NativeTheme::GetInstanceForNativeUi()->GetHighContrastColorScheme(),
- ui::NativeTheme::HighContrastColorScheme::kMaxValue);
+ ui::NativeTheme::GetInstanceForNativeUi()
+ ->GetPlatformHighContrastColorScheme(),
+ ui::NativeTheme::PlatformHighContrastColorScheme::kMaxValue);
#endif
}
diff --git a/chromium/content/browser/accessibility/browser_accessibility_state_impl_mac.mm b/chromium/content/browser/accessibility/browser_accessibility_state_impl_mac.mm
index 7297514e88a..7a59582ea63 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_state_impl_mac.mm
+++ b/chromium/content/browser/accessibility/browser_accessibility_state_impl_mac.mm
@@ -8,6 +8,7 @@
#include "base/metrics/histogram_macros.h"
#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_thread.h"
#include "ui/gfx/animation/animation.h"
@interface NSWorkspace (Partials)
@@ -52,8 +53,8 @@ void SetupAccessibilityDisplayOptionsNotifier() {
} // namespace
void BrowserAccessibilityStateImpl::PlatformInitialize() {
- base::PostTask(FROM_HERE, {BrowserThread::UI},
- base::BindOnce(&SetupAccessibilityDisplayOptionsNotifier));
+ GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE, base::BindOnce(&SetupAccessibilityDisplayOptionsNotifier));
}
void BrowserAccessibilityStateImpl::
diff --git a/chromium/content/browser/accessibility/browser_accessibility_unittest.cc b/chromium/content/browser/accessibility/browser_accessibility_unittest.cc
index e0d1eadd2b0..ed559c391b0 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_unittest.cc
+++ b/chromium/content/browser/accessibility/browser_accessibility_unittest.cc
@@ -73,7 +73,13 @@ TEST_F(BrowserAccessibilityTest, TestCanFireEvents) {
BrowserAccessibility* text_obj = manager->GetFromID(111);
EXPECT_TRUE(text_obj->PlatformIsLeaf());
+#if !defined(OS_ANDROID)
EXPECT_TRUE(text_obj->CanFireEvents());
+#endif
+ BrowserAccessibility* retarget = manager->RetargetForEvents(
+ text_obj, BrowserAccessibilityManager::RetargetEventType::
+ RetargetEventTypeBlinkHover);
+ EXPECT_TRUE(retarget->CanFireEvents());
manager.reset();
}
@@ -273,15 +279,15 @@ TEST_F(BrowserAccessibilityTest, GetInnerTextRangeBoundsRect) {
ui::AXNodeData static_text;
static_text.id = 2;
- static_text.SetName("Hello, world.");
static_text.role = ax::mojom::Role::kStaticText;
+ static_text.SetName("Hello, world.");
static_text.relative_bounds.bounds = gfx::RectF(100, 100, 29, 18);
root.child_ids.push_back(2);
ui::AXNodeData inline_text1;
inline_text1.id = 3;
- inline_text1.SetName("Hello, ");
inline_text1.role = ax::mojom::Role::kInlineTextBox;
+ inline_text1.SetName("Hello, ");
inline_text1.relative_bounds.bounds = gfx::RectF(100, 100, 29, 9);
inline_text1.SetTextDirection(ax::mojom::TextDirection::kLtr);
std::vector<int32_t> character_offsets1;
@@ -298,8 +304,8 @@ TEST_F(BrowserAccessibilityTest, GetInnerTextRangeBoundsRect) {
ui::AXNodeData inline_text2;
inline_text2.id = 4;
- inline_text2.SetName("world.");
inline_text2.role = ax::mojom::Role::kInlineTextBox;
+ inline_text2.SetName("world.");
inline_text2.relative_bounds.bounds = gfx::RectF(100, 109, 28, 9);
inline_text2.SetTextDirection(ax::mojom::TextDirection::kLtr);
std::vector<int32_t> character_offsets2;
@@ -394,15 +400,15 @@ TEST_F(BrowserAccessibilityTest, GetInnerTextRangeBoundsRectMultiElement) {
ui::AXNodeData static_text;
static_text.id = 2;
- static_text.SetName("ABC");
static_text.role = ax::mojom::Role::kStaticText;
+ static_text.SetName("ABC");
static_text.relative_bounds.bounds = gfx::RectF(0, 20, 33, 9);
root.child_ids.push_back(2);
ui::AXNodeData inline_text1;
inline_text1.id = 3;
- inline_text1.SetName("ABC");
inline_text1.role = ax::mojom::Role::kInlineTextBox;
+ inline_text1.SetName("ABC");
inline_text1.relative_bounds.bounds = gfx::RectF(0, 20, 33, 9);
inline_text1.SetTextDirection(ax::mojom::TextDirection::kLtr);
std::vector<int32_t> character_offsets{10, 21, 33};
@@ -412,15 +418,15 @@ TEST_F(BrowserAccessibilityTest, GetInnerTextRangeBoundsRectMultiElement) {
ui::AXNodeData static_text2;
static_text2.id = 4;
- static_text2.SetName("ABC");
static_text2.role = ax::mojom::Role::kStaticText;
+ static_text2.SetName("ABC");
static_text2.relative_bounds.bounds = gfx::RectF(10, 40, 33, 9);
root.child_ids.push_back(4);
ui::AXNodeData inline_text2;
inline_text2.id = 5;
- inline_text2.SetName("ABC");
inline_text2.role = ax::mojom::Role::kInlineTextBox;
+ inline_text2.SetName("ABC");
inline_text2.relative_bounds.bounds = gfx::RectF(10, 40, 33, 9);
inline_text2.SetTextDirection(ax::mojom::TextDirection::kLtr);
inline_text2.AddIntListAttribute(
@@ -521,15 +527,15 @@ TEST_F(BrowserAccessibilityTest, GetInnerTextRangeBoundsRectBiDi) {
ui::AXNodeData static_text;
static_text.id = 2;
- static_text.SetName("123abc");
static_text.role = ax::mojom::Role::kStaticText;
+ static_text.SetName("123abc");
static_text.relative_bounds.bounds = gfx::RectF(100, 100, 60, 20);
root.child_ids.push_back(2);
ui::AXNodeData inline_text1;
inline_text1.id = 3;
- inline_text1.SetName("123");
inline_text1.role = ax::mojom::Role::kInlineTextBox;
+ inline_text1.SetName("123");
inline_text1.relative_bounds.bounds = gfx::RectF(100, 100, 30, 20);
inline_text1.SetTextDirection(ax::mojom::TextDirection::kLtr);
std::vector<int32_t> character_offsets1;
@@ -542,8 +548,8 @@ TEST_F(BrowserAccessibilityTest, GetInnerTextRangeBoundsRectBiDi) {
ui::AXNodeData inline_text2;
inline_text2.id = 4;
- inline_text2.SetName("abc");
inline_text2.role = ax::mojom::Role::kInlineTextBox;
+ inline_text2.SetName("abc");
inline_text2.relative_bounds.bounds = gfx::RectF(130, 100, 30, 20);
inline_text2.SetTextDirection(ax::mojom::TextDirection::kRtl);
std::vector<int32_t> character_offsets2;
@@ -621,15 +627,15 @@ TEST_F(BrowserAccessibilityTest, GetInnerTextRangeBoundsRectScrolledWindow) {
ui::AXNodeData static_text;
static_text.id = 2;
- static_text.SetName("ABC");
static_text.role = ax::mojom::Role::kStaticText;
+ static_text.SetName("ABC");
static_text.relative_bounds.bounds = gfx::RectF(100, 100, 16, 9);
root.child_ids.push_back(2);
ui::AXNodeData inline_text;
inline_text.id = 3;
- inline_text.SetName("ABC");
inline_text.role = ax::mojom::Role::kInlineTextBox;
+ inline_text.SetName("ABC");
inline_text.relative_bounds.bounds = gfx::RectF(100, 100, 16, 9);
inline_text.SetTextDirection(ax::mojom::TextDirection::kLtr);
std::vector<int32_t> character_offsets1;
@@ -702,14 +708,14 @@ TEST_F(BrowserAccessibilityTest, NextWordPositionWithHypertext) {
ui::AXNodeData input;
input.id = 2;
input.role = ax::mojom::Role::kTextField;
- input.child_ids = {3};
input.SetName("Search the web");
+ input.child_ids = {3};
ui::AXNodeData static_text;
static_text.id = 3;
static_text.role = ax::mojom::Role::kStaticText;
- static_text.child_ids = {4};
static_text.SetName("Search the web");
+ static_text.child_ids = {4};
ui::AXNodeData inline_text;
inline_text.id = 4;
@@ -830,8 +836,8 @@ TEST_F(BrowserAccessibilityTest, GetIndexInParent) {
ui::AXNodeData static_text;
static_text.id = 2;
- static_text.SetName("ABC");
static_text.role = ax::mojom::Role::kStaticText;
+ static_text.SetName("ABC");
std::unique_ptr<BrowserAccessibilityManager> browser_accessibility_manager(
BrowserAccessibilityManager::Create(
diff --git a/chromium/content/browser/accessibility/browser_accessibility_win.cc b/chromium/content/browser/accessibility/browser_accessibility_win.cc
index e92df444a70..780a3f52232 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_win.cc
+++ b/chromium/content/browser/accessibility/browser_accessibility_win.cc
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "content/browser/accessibility/browser_accessibility_win.h"
+
#include "content/browser/accessibility/browser_accessibility_manager.h"
#include "content/browser/accessibility/browser_accessibility_state_impl.h"
@@ -40,18 +41,6 @@ void BrowserAccessibilityWin::UpdatePlatformAttributes() {
GetCOM()->UpdateStep3FireEvents();
}
-bool BrowserAccessibilityWin::PlatformIsLeafIncludingIgnored() const {
- // On Windows, we want to hide the subtree of a collapsed <select> element.
- // Otherwise, ATs are always going to announce its options whether it's
- // collapsed or expanded. In the AXTree, this element corresponds to a node
- // with role ax::mojom::Role::kPopUpButton parent of a node with role
- // ax::mojom::Role::kMenuListPopup.
- if (IsCollapsedMenuListPopUpButton())
- return true;
-
- return BrowserAccessibility::PlatformIsLeafIncludingIgnored();
-}
-
bool BrowserAccessibilityWin::CanFireEvents() const {
// On Windows, we want to hide the subtree of a collapsed <select> element but
// we still need to fire events on those hidden nodes.
diff --git a/chromium/content/browser/accessibility/browser_accessibility_win.h b/chromium/content/browser/accessibility/browser_accessibility_win.h
index 18bcad491c6..f7dcfaba976 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_win.h
+++ b/chromium/content/browser/accessibility/browser_accessibility_win.h
@@ -5,6 +5,8 @@
#ifndef CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_WIN_H_
#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_WIN_H_
+#include <vector>
+
#include "base/win/atl.h"
#include "content/browser/accessibility/browser_accessibility.h"
#include "content/browser/accessibility/browser_accessibility_com_win.h"
@@ -23,9 +25,9 @@ class CONTENT_EXPORT BrowserAccessibilityWin : public BrowserAccessibility {
void UpdatePlatformAttributes() override;
//
- // BrowserAccessibility methods.
+ // BrowserAccessibility overrides.
//
- bool PlatformIsLeafIncludingIgnored() const override;
+
bool CanFireEvents() const override;
ui::AXPlatformNode* GetAXPlatformNode() const override;
void OnLocationChanged() override;
diff --git a/chromium/content/browser/accessibility/browser_accessibility_win_unittest.cc b/chromium/content/browser/accessibility/browser_accessibility_win_unittest.cc
index 69cac2a162c..ca9831ee1e2 100644
--- a/chromium/content/browser/accessibility/browser_accessibility_win_unittest.cc
+++ b/chromium/content/browser/accessibility/browser_accessibility_win_unittest.cc
@@ -4,6 +4,9 @@
#include "content/browser/accessibility/browser_accessibility_win.h"
+#include <string>
+#include <vector>
+
#include <objbase.h>
#include <stdint.h>
#include <wrl/client.h>
@@ -129,18 +132,18 @@ TEST_F(BrowserAccessibilityWinTest, TestNoLeaks) {
// BrowserAccessibilityManager.
ui::AXNodeData button;
button.id = 2;
- button.SetName("Button");
button.role = ax::mojom::Role::kButton;
+ button.SetName("Button");
ui::AXNodeData checkbox;
checkbox.id = 3;
- checkbox.SetName("Checkbox");
checkbox.role = ax::mojom::Role::kCheckBox;
+ checkbox.SetName("Checkbox");
ui::AXNodeData root;
root.id = 1;
- root.SetName("Document");
root.role = ax::mojom::Role::kRootWebArea;
+ root.SetName("Document");
root.child_ids.push_back(2);
root.child_ids.push_back(3);
@@ -193,8 +196,8 @@ TEST_F(BrowserAccessibilityWinTest, TestChildrenChange) {
ui::AXNodeData root;
root.id = 1;
- root.SetName("Document");
root.role = ax::mojom::Role::kRootWebArea;
+ root.SetName("Document");
root.child_ids.push_back(2);
// Construct a BrowserAccessibilityManager with this
@@ -701,22 +704,28 @@ TEST_F(BrowserAccessibilityWinTest, TestComplexHypertext) {
check_box.role = ax::mojom::Role::kCheckBox;
check_box.SetCheckedState(ax::mojom::CheckedState::kTrue);
check_box.SetName(base::UTF16ToUTF8(check_box_name));
+ // ARIA checkbox where the name is derived from its inner text.
+ check_box.SetNameFrom(ax::mojom::NameFrom::kContents);
check_box.SetValue(base::UTF16ToUTF8(check_box_value));
ui::AXNodeData button, button_text;
button.id = 15;
button_text.id = 17;
- button_text.SetName(base::UTF16ToUTF8(button_text_name));
button.role = ax::mojom::Role::kButton;
+ button.SetName(base::UTF16ToUTF8(button_text_name));
+ button.SetNameFrom(ax::mojom::NameFrom::kContents);
+ // A single text child with the same name should be hidden from accessibility
+ // to prevent double speaking.
button_text.role = ax::mojom::Role::kStaticText;
+ button_text.SetName(base::UTF16ToUTF8(button_text_name));
button.child_ids.push_back(button_text.id);
ui::AXNodeData link, link_text;
link.id = 16;
link_text.id = 18;
- link_text.SetName(base::UTF16ToUTF8(link_text_name));
link.role = ax::mojom::Role::kLink;
link_text.role = ax::mojom::Role::kStaticText;
+ link_text.SetName(base::UTF16ToUTF8(link_text_name));
link.child_ids.push_back(link_text.id);
ui::AXNodeData root;
@@ -1068,15 +1077,15 @@ TEST_F(BrowserAccessibilityWinTest, TestIA2Attributes) {
ui::AXNodeData checkbox;
checkbox.id = 3;
- checkbox.SetName("Checkbox");
checkbox.role = ax::mojom::Role::kCheckBox;
checkbox.SetCheckedState(ax::mojom::CheckedState::kTrue);
+ checkbox.SetName("Checkbox");
ui::AXNodeData root;
root.id = 1;
- root.SetName("Document");
root.role = ax::mojom::Role::kRootWebArea;
root.AddState(ax::mojom::State::kFocusable);
+ root.SetName("Document");
root.child_ids.push_back(2);
root.child_ids.push_back(3);
@@ -1112,7 +1121,7 @@ TEST_F(BrowserAccessibilityWinTest, TestIA2Attributes) {
EXPECT_EQ(S_OK, hr);
EXPECT_NE(nullptr, attributes.Get());
attributes_str = std::wstring(attributes.Get(), attributes.Length());
- EXPECT_EQ(L"checkable:true;", attributes_str);
+ EXPECT_EQ(L"checkable:true;explicit-name:true;", attributes_str);
manager.reset();
}
@@ -1126,10 +1135,10 @@ TEST_F(BrowserAccessibilityWinTest, TestValueAttributeInTextControls) {
ui::AXNodeData combo_box, combo_box_text;
combo_box.id = 2;
combo_box_text.id = 3;
- combo_box.SetName("Combo box:");
- combo_box_text.SetName("Combo box text");
combo_box.role = ax::mojom::Role::kTextFieldWithComboBox;
combo_box_text.role = ax::mojom::Role::kStaticText;
+ combo_box.SetName("Combo box:");
+ combo_box_text.SetName("Combo box text");
combo_box.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot, true);
combo_box.AddState(ax::mojom::State::kEditable);
combo_box.AddState(ax::mojom::State::kRichlyEditable);
@@ -1142,12 +1151,12 @@ TEST_F(BrowserAccessibilityWinTest, TestValueAttributeInTextControls) {
search_box.id = 4;
search_box_text.id = 5;
new_line.id = 6;
- search_box.SetName("Search for:");
- search_box_text.SetName("Search box text");
- new_line.SetName("\n");
search_box.role = ax::mojom::Role::kSearchBox;
search_box_text.role = ax::mojom::Role::kStaticText;
new_line.role = ax::mojom::Role::kLineBreak;
+ search_box.SetName("Search for:");
+ search_box_text.SetName("Search box text");
+ new_line.SetName("\n");
search_box.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot, true);
search_box.AddState(ax::mojom::State::kEditable);
search_box.AddState(ax::mojom::State::kRichlyEditable);
@@ -1170,18 +1179,18 @@ TEST_F(BrowserAccessibilityWinTest, TestValueAttributeInTextControls) {
ui::AXNodeData link, link_text;
link.id = 8;
link_text.id = 9;
- link_text.SetName("Link text");
link.role = ax::mojom::Role::kLink;
link_text.role = ax::mojom::Role::kStaticText;
+ link_text.SetName("Link text");
link.child_ids.push_back(link_text.id);
ui::AXNodeData slider, slider_text;
slider.id = 10;
slider_text.id = 11;
- slider.AddFloatAttribute(ax::mojom::FloatAttribute::kValueForRange, 5.0F);
- slider_text.SetName("Slider text");
slider.role = ax::mojom::Role::kSlider;
slider_text.role = ax::mojom::Role::kStaticText;
+ slider.AddFloatAttribute(ax::mojom::FloatAttribute::kValueForRange, 5.0F);
+ slider_text.SetName("Slider text");
slider.child_ids.push_back(slider_text.id);
root.child_ids.push_back(2); // Combo box.
@@ -2227,11 +2236,11 @@ TEST_F(BrowserAccessibilityWinTest, TestIAccessibleHyperlink) {
link.AddState(ax::mojom::State::kFocusable);
link.AddState(ax::mojom::State::kLinked);
link.SetName("here");
+ link.SetNameFrom(ax::mojom::NameFrom::kContents);
link.AddStringAttribute(ax::mojom::StringAttribute::kUrl, "example.com");
- root.child_ids.push_back(2);
- div.child_ids.push_back(3);
- div.child_ids.push_back(4);
+ root.child_ids.push_back(div.id);
+ div.child_ids = {text.id, link.id};
std::unique_ptr<BrowserAccessibilityManager> manager(
BrowserAccessibilityManager::Create(
diff --git a/chromium/content/browser/accessibility/dump_accessibility_browsertest_base.cc b/chromium/content/browser/accessibility/dump_accessibility_browsertest_base.cc
index e90ddb72ebd..cd07323d855 100644
--- a/chromium/content/browser/accessibility/dump_accessibility_browsertest_base.cc
+++ b/chromium/content/browser/accessibility/dump_accessibility_browsertest_base.cc
@@ -157,6 +157,7 @@ DumpAccessibilityTestBase::DumpUnfilteredAccessibilityTreeAsString() {
void DumpAccessibilityTestBase::ParseHtmlForExtraDirectives(
const std::string& test_html,
+ std::vector<std::string>* no_load_expected,
std::vector<std::string>* wait_for,
std::vector<std::string>* execute,
std::vector<std::string>* run_until,
@@ -167,6 +168,7 @@ void DumpAccessibilityTestBase::ParseHtmlForExtraDirectives(
const std::string& allow_str = formatter_->GetAllowString();
const std::string& deny_str = formatter_->GetDenyString();
const std::string& deny_node_str = formatter_->GetDenyNodeString();
+ const std::string& no_load_expected_str = "@NO-LOAD-EXPECTED:";
const std::string& wait_str = "@WAIT-FOR:";
const std::string& execute_str = "@EXECUTE-AND-WAIT-FOR:";
const std::string& until_str = "@RUN-UNTIL-EVENT:";
@@ -194,6 +196,9 @@ void DumpAccessibilityTestBase::ParseHtmlForExtraDirectives(
node_filters_.push_back(
NodeFilter(parts[0], base::UTF8ToUTF16(parts[1])));
}
+ } else if (base::StartsWith(line, no_load_expected_str,
+ base::CompareCase::SENSITIVE)) {
+ no_load_expected->push_back(line.substr(no_load_expected_str.size()));
} else if (base::StartsWith(line, wait_str, base::CompareCase::SENSITIVE)) {
wait_for->push_back(line.substr(wait_str.size()));
} else if (base::StartsWith(line, execute_str,
@@ -284,6 +289,7 @@ void DumpAccessibilityTestBase::RunTestForPlatform(
}
// Parse filters and other directives in the test file.
+ std::vector<std::string> no_load_expected;
std::vector<std::string> wait_for;
std::vector<std::string> execute;
std::vector<std::string> run_until;
@@ -292,8 +298,8 @@ void DumpAccessibilityTestBase::RunTestForPlatform(
node_filters_.clear();
formatter_->AddDefaultFilters(&property_filters_);
AddDefaultFilters(&property_filters_);
- ParseHtmlForExtraDirectives(html_contents, &wait_for, &execute, &run_until,
- &default_action_on);
+ ParseHtmlForExtraDirectives(html_contents, &no_load_expected, &wait_for,
+ &execute, &run_until, &default_action_on);
// Get the test URL.
GURL url(embedded_test_server()->GetURL("/" + std::string(file_dir) + "/" +
@@ -343,7 +349,7 @@ void DumpAccessibilityTestBase::RunTestForPlatform(
waiter.WaitForNotification();
}
- WaitForAXTreeLoaded(web_contents, wait_for);
+ WaitForAXTreeLoaded(web_contents, no_load_expected, wait_for);
// Call the subclass to dump the output.
std::vector<std::string> actual_lines = Dump(run_until);
@@ -387,6 +393,7 @@ void DumpAccessibilityTestBase::RunTestForPlatform(
void DumpAccessibilityTestBase::WaitForAXTreeLoaded(
WebContentsImpl* web_contents,
+ const std::vector<std::string>& no_load_expected,
const std::vector<std::string>& wait_for) {
// Get the url of every frame in the frame tree.
FrameTree* frame_tree = web_contents->GetFrameTree();
@@ -401,8 +408,18 @@ void DumpAccessibilityTestBase::WaitForAXTreeLoaded(
//
// We also ignore frame tree nodes created for portals in the outer
// WebContents as the node doesn't have a url set.
+
std::string url = node->current_url().spec();
- if (url != url::kAboutBlankURL && !url.empty() &&
+
+ // sometimes we expect a url to never load, in these cases, don't wait.
+ bool skip_url = false;
+ for (std::string no_load_url : no_load_expected) {
+ if (url.find(no_load_url) != std::string::npos) {
+ skip_url = true;
+ break;
+ }
+ }
+ if (!skip_url && url != url::kAboutBlankURL && !url.empty() &&
node->frame_owner_element_type() !=
blink::mojom::FrameOwnerElementType::kPortal) {
all_frame_urls.push_back(url);
@@ -466,7 +483,7 @@ void DumpAccessibilityTestBase::WaitForAXTreeLoaded(
for (WebContents* inner_contents : web_contents->GetInnerWebContents()) {
WaitForAXTreeLoaded(static_cast<WebContentsImpl*>(inner_contents),
- std::vector<std::string>());
+ no_load_expected, std::vector<std::string>());
}
}
diff --git a/chromium/content/browser/accessibility/dump_accessibility_browsertest_base.h b/chromium/content/browser/accessibility/dump_accessibility_browsertest_base.h
index 259665069eb..e815a4bf97f 100644
--- a/chromium/content/browser/accessibility/dump_accessibility_browsertest_base.h
+++ b/chromium/content/browser/accessibility/dump_accessibility_browsertest_base.h
@@ -97,6 +97,7 @@ class DumpAccessibilityTestBase : public ContentBrowserTest,
// string to appear before comparing the results. There can be multiple
// @WAIT-FOR: directives.
void ParseHtmlForExtraDirectives(const std::string& test_html,
+ std::vector<std::string>* no_load_expected,
std::vector<std::string>* wait_for,
std::vector<std::string>* execute,
std::vector<std::string>* run_until,
@@ -139,6 +140,7 @@ class DumpAccessibilityTestBase : public ContentBrowserTest,
const std::string& name);
void WaitForAXTreeLoaded(WebContentsImpl* web_contents,
+ const std::vector<std::string>& no_load_expected,
const std::vector<std::string>& wait_for);
};
diff --git a/chromium/content/browser/accessibility/dump_accessibility_events_browsertest.cc b/chromium/content/browser/accessibility/dump_accessibility_events_browsertest.cc
index bdbeed607fa..25a997f2935 100644
--- a/chromium/content/browser/accessibility/dump_accessibility_events_browsertest.cc
+++ b/chromium/content/browser/accessibility/dump_accessibility_events_browsertest.cc
@@ -774,8 +774,9 @@ IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest,
RunEventTest(FILE_PATH_LITERAL("tabindex-removed-on-plain-div.html"));
}
-IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest,
- AccessibilityEventsTabindexRemovedOnAriaHidden) {
+IN_PROC_BROWSER_TEST_P(
+ DumpAccessibilityEventsTest,
+ DISABLED_AccessibilityEventsTabindexRemovedOnAriaHidden) {
RunEventTest(FILE_PATH_LITERAL("tabindex-removed-on-aria-hidden.html"));
}
diff --git a/chromium/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/chromium/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index c4bc659e60c..86b7113203c 100644
--- a/chromium/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/chromium/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -27,6 +27,7 @@
#include "content/public/test/browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
+#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/accessibility_switches.h"
#if defined(OS_MACOSX)
@@ -211,6 +212,8 @@ void DumpAccessibilityTreeTest::AddDefaultFilters(
AddPropertyFilter(property_filters, "layout-guess:*", PropertyFilter::ALLOW);
AddPropertyFilter(property_filters, "select*");
+ AddPropertyFilter(property_filters, "selectedFromFocus=*",
+ PropertyFilter::DENY);
AddPropertyFilter(property_filters, "descript*");
AddPropertyFilter(property_filters, "check*");
AddPropertyFilter(property_filters, "horizontal");
@@ -867,6 +870,26 @@ IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityAriaModal) {
RunAriaTest(FILE_PATH_LITERAL("aria-modal.html"));
}
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
+ AccessibilityAriaModalFocusableDialog) {
+ RunAriaTest(FILE_PATH_LITERAL("aria-modal-focusable-dialog.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
+ AccessibilityAriaModalLayered) {
+ RunAriaTest(FILE_PATH_LITERAL("aria-modal-layered.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
+ AccessibilityAriaModalMoveFocus) {
+ RunAriaTest(FILE_PATH_LITERAL("aria-modal-move-focus.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
+ AccessibilityAriaModalRemoveParentContainer) {
+ RunAriaTest(FILE_PATH_LITERAL("aria-modal-remove-parent-container.html"));
+}
+
IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityAriaMultiline) {
RunAriaTest(FILE_PATH_LITERAL("aria-multiline.html"));
}
@@ -2125,6 +2148,22 @@ IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilitySelect) {
RunHtmlTest(FILE_PATH_LITERAL("select.html"));
}
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
+ AccessibilitySelectFollowsFocus) {
+ RunHtmlTest(FILE_PATH_LITERAL("select-follows-focus.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
+ AccessibilitySelectFollowsFocusAriaSelectedFalse) {
+ RunHtmlTest(
+ FILE_PATH_LITERAL("select-follows-focus-aria-selected-false.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
+ AccessibilitySelectFollowsFocusMultiselect) {
+ RunHtmlTest(FILE_PATH_LITERAL("select-follows-focus-multiselect.html"));
+}
+
#if defined(OS_LINUX)
#define MAYBE_AccessibilitySource DISABLED_AccessibilitySource
#else
diff --git a/chromium/content/browser/accessibility/fullscreen_browsertest.cc b/chromium/content/browser/accessibility/fullscreen_browsertest.cc
index b32920ef5b5..acb1afe3ea7 100644
--- a/chromium/content/browser/accessibility/fullscreen_browsertest.cc
+++ b/chromium/content/browser/accessibility/fullscreen_browsertest.cc
@@ -52,8 +52,7 @@ class FakeFullscreenDelegate : public WebContentsDelegate {
~FakeFullscreenDelegate() override = default;
void EnterFullscreenModeForTab(
- WebContents*,
- const GURL&,
+ RenderFrameHost*,
const blink::mojom::FullscreenOptions&) override {
is_fullscreen_ = true;
}
diff --git a/chromium/content/browser/accessibility/hit_testing_browsertest.cc b/chromium/content/browser/accessibility/hit_testing_browsertest.cc
index 7c4afb60284..26cbd0452b3 100644
--- a/chromium/content/browser/accessibility/hit_testing_browsertest.cc
+++ b/chromium/content/browser/accessibility/hit_testing_browsertest.cc
@@ -5,6 +5,7 @@
#include "content/browser/accessibility/hit_testing_browsertest.h"
#include "base/check.h"
+#include "base/test/bind_test_util.h"
#include "build/build_config.h"
#include "build/chromecast_buildflags.h"
#include "content/browser/accessibility/accessibility_tree_formatter_blink.h"
@@ -138,7 +139,8 @@ AccessibilityHitTestingBrowserTest::HitTestAndWaitForResultWithEvent(
action_data.action = ax::mojom::Action::kHitTest;
action_data.target_point = CSSToFramePoint(point);
action_data.hit_test_event_to_fire = event_to_fire;
- manager->delegate()->AccessibilityPerformAction(action_data);
+ manager->delegate()->AccessibilityHitTest(CSSToFramePoint(point),
+ event_to_fire, 0, {});
event_waiter.WaitForNotification();
RenderFrameHostImpl* target_frame = event_waiter.event_render_frame_host();
@@ -156,6 +158,30 @@ AccessibilityHitTestingBrowserTest::HitTestAndWaitForResult(
}
BrowserAccessibility*
+AccessibilityHitTestingBrowserTest::AsyncHitTestAndWaitForCallback(
+ const gfx::Point& point) {
+ BrowserAccessibilityManager* manager = GetRootBrowserAccessibilityManager();
+
+ gfx::Point target_point = CSSToFramePoint(point);
+ base::RunLoop run_loop;
+ BrowserAccessibilityManager* hit_manager = nullptr;
+ int hit_node_id = 0;
+
+ auto callback = [&](BrowserAccessibilityManager* manager, int node_id) {
+ hit_manager = manager;
+ hit_node_id = node_id;
+ run_loop.QuitClosure().Run();
+ };
+ manager->delegate()->AccessibilityHitTest(
+ target_point, ax::mojom::Event::kNone, 0,
+ base::BindLambdaForTesting(callback));
+ run_loop.Run();
+
+ BrowserAccessibility* hit_node = hit_manager->GetFromID(hit_node_id);
+ return hit_node;
+}
+
+BrowserAccessibility*
AccessibilityHitTestingBrowserTest::CallCachingAsyncHitTest(
const gfx::Point& page_point) {
gfx::Point screen_point = CSSToPhysicalPixelPoint(page_point);
@@ -362,6 +388,10 @@ IN_PROC_BROWSER_TEST_P(AccessibilityHitTestingBrowserTest, HitTest) {
BrowserAccessibility* expected_node =
FindNode(ax::mojom::Role::kGenericContainer, "rect2");
EXPECT_ACCESSIBILITY_HIT_TEST_RESULT(rect_2_point, expected_node, hit_node);
+
+ // Try callback API.
+ hit_node = AsyncHitTestAndWaitForCallback(rect_2_point);
+ EXPECT_ACCESSIBILITY_HIT_TEST_RESULT(rect_2_point, expected_node, hit_node);
}
// Test a hit on a rect in the iframe.
@@ -372,6 +402,10 @@ IN_PROC_BROWSER_TEST_P(AccessibilityHitTestingBrowserTest, HitTest) {
FindNode(ax::mojom::Role::kGenericContainer, "rectB");
EXPECT_ACCESSIBILITY_HIT_TEST_RESULT(rect_b_point, expected_node, hit_node);
+ // Try callback API.
+ hit_node = AsyncHitTestAndWaitForCallback(rect_b_point);
+ EXPECT_ACCESSIBILITY_HIT_TEST_RESULT(rect_b_point, expected_node, hit_node);
+
// Test with a different event.
hit_node = HitTestAndWaitForResultWithEvent(rect_b_point,
ax::mojom::Event::kAlert);
@@ -400,7 +434,14 @@ IN_PROC_BROWSER_TEST_P(AccessibilityHitTestingBrowserTest,
EXPECT_TRUE(NavigateToURL(shell(), url));
waiter.WaitForNotification();
- BrowserAccessibility* hit_node = HitTestAndWaitForResult(gfx::Point(-1, -1));
+ gfx::Point out_of_bounds_point(-1, -1);
+
+ BrowserAccessibility* hit_node = HitTestAndWaitForResult(out_of_bounds_point);
+ ASSERT_TRUE(hit_node != nullptr);
+ ASSERT_EQ(ax::mojom::Role::kRootWebArea, hit_node->GetRole());
+
+ // Try callback API.
+ hit_node = AsyncHitTestAndWaitForCallback(out_of_bounds_point);
ASSERT_TRUE(hit_node != nullptr);
ASSERT_EQ(ax::mojom::Role::kRootWebArea, hit_node->GetRole());
}
@@ -448,6 +489,10 @@ IN_PROC_BROWSER_TEST_P(AccessibilityHitTestingCrossProcessBrowserTest,
BrowserAccessibility* expected_node =
FindNode(ax::mojom::Role::kGenericContainer, "rectB");
EXPECT_ACCESSIBILITY_HIT_TEST_RESULT(rect_b_point, expected_node, hit_node);
+
+ // Try callback API.
+ hit_node = AsyncHitTestAndWaitForCallback(rect_b_point);
+ EXPECT_ACCESSIBILITY_HIT_TEST_RESULT(rect_b_point, expected_node, hit_node);
}
// Scroll div up 100px.
@@ -468,6 +513,10 @@ IN_PROC_BROWSER_TEST_P(AccessibilityHitTestingCrossProcessBrowserTest,
BrowserAccessibility* expected_node =
FindNode(ax::mojom::Role::kGenericContainer, "rectG");
EXPECT_ACCESSIBILITY_HIT_TEST_RESULT(rect_g_point, expected_node, hit_node);
+
+ // Try callback API.
+ hit_node = AsyncHitTestAndWaitForCallback(rect_g_point);
+ EXPECT_ACCESSIBILITY_HIT_TEST_RESULT(rect_g_point, expected_node, hit_node);
}
}
@@ -595,6 +644,10 @@ IN_PROC_BROWSER_TEST_P(AccessibilityHitTestingBrowserTest,
BrowserAccessibility* expected_node =
FindNode(ax::mojom::Role::kGenericContainer, "rect2");
EXPECT_ACCESSIBILITY_HIT_TEST_RESULT(rect_2_point, expected_node, hit_node);
+
+ // Try callback API.
+ hit_node = AsyncHitTestAndWaitForCallback(rect_2_point);
+ EXPECT_ACCESSIBILITY_HIT_TEST_RESULT(rect_2_point, expected_node, hit_node);
}
// Test a hit on a rect in the iframe.
@@ -604,11 +657,23 @@ IN_PROC_BROWSER_TEST_P(AccessibilityHitTestingBrowserTest,
BrowserAccessibility* expected_node =
FindNode(ax::mojom::Role::kGenericContainer, "rectB");
EXPECT_ACCESSIBILITY_HIT_TEST_RESULT(rect_b_point, expected_node, hit_node);
+
+ // Try callback API.
+ hit_node = AsyncHitTestAndWaitForCallback(rect_b_point);
+ EXPECT_ACCESSIBILITY_HIT_TEST_RESULT(rect_b_point, expected_node, hit_node);
}
}
+// Timeouts on Linux. TODO(crbug.com/1083805): Enable this test.
+#if defined(OS_LINUX)
+#define MAYBE_CachingAsyncHitTestMissesElement_WithPinchZoom \
+ DISABLED_CachingAsyncHitTestMissesElement_WithPinchZoom
+#else
+#define MAYBE_CachingAsyncHitTestMissesElement_WithPinchZoom \
+ CachingAsyncHitTestMissesElement_WithPinchZoom
+#endif
IN_PROC_BROWSER_TEST_P(AccessibilityHitTestingBrowserTest,
- CachingAsyncHitTestMissesElement_WithPinchZoom) {
+ MAYBE_CachingAsyncHitTestMissesElement_WithPinchZoom) {
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
diff --git a/chromium/content/browser/accessibility/hit_testing_browsertest.h b/chromium/content/browser/accessibility/hit_testing_browsertest.h
index 05b294cef07..418d3f93dbd 100644
--- a/chromium/content/browser/accessibility/hit_testing_browsertest.h
+++ b/chromium/content/browser/accessibility/hit_testing_browsertest.h
@@ -35,11 +35,19 @@ class AccessibilityHitTestingBrowserTest
gfx::Rect GetViewBoundsInScreenCoordinates();
gfx::Point CSSToFramePoint(gfx::Point css_point);
gfx::Point CSSToPhysicalPixelPoint(gfx::Point css_point);
+
+ // Test the hit test action that fires an event.
BrowserAccessibility* HitTestAndWaitForResultWithEvent(
const gfx::Point& point,
ax::mojom::Event event_to_fire);
BrowserAccessibility* HitTestAndWaitForResult(const gfx::Point& point);
+
+ // Test the hit test mojo RPC that calls a callback function.
+ BrowserAccessibility* AsyncHitTestAndWaitForCallback(const gfx::Point& point);
+
+ // Test the caching async hit test.
BrowserAccessibility* CallCachingAsyncHitTest(const gfx::Point& page_point);
+
BrowserAccessibility* CallNearestLeafNode(const gfx::Point& page_point);
void SynchronizeThreads();
base::string16 FormatHitTestAccessibilityTree();
diff --git a/chromium/content/browser/accessibility/hit_testing_mac_browsertest.mm b/chromium/content/browser/accessibility/hit_testing_mac_browsertest.mm
new file mode 100644
index 00000000000..ba367164acb
--- /dev/null
+++ b/chromium/content/browser/accessibility/hit_testing_mac_browsertest.mm
@@ -0,0 +1,82 @@
+// Copyright 2020 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/browser_accessibility_cocoa.h"
+#include "content/browser/accessibility/browser_accessibility_mac.h"
+#include "content/browser/accessibility/hit_testing_browsertest.h"
+#include "content/public/test/accessibility_notification_waiter.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/shell/browser/shell.h"
+#include "ui/gfx/mac/coordinate_conversion.h"
+
+namespace content {
+
+#define EXPECT_ACCESSIBILITY_MAC_HIT_TEST_RESULT(css_point, expected_element, \
+ hit_element) \
+ SCOPED_TRACE(GetScopedTrace(css_point)); \
+ EXPECT_EQ([expected_element owner]->GetId(), [hit_element owner]->GetId());
+
+class AccessibilityHitTestingMacBrowserTest
+ : public AccessibilityHitTestingBrowserTest {
+ public:
+ BrowserAccessibilityCocoa* GetWebContentRoot() {
+ return ToBrowserAccessibilityCocoa(
+ GetRootBrowserAccessibilityManager()->GetRoot());
+ }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+ All,
+ AccessibilityHitTestingMacBrowserTest,
+ ::testing::Combine(::testing::Values(1, 2), ::testing::Bool()),
+ AccessibilityHitTestingBrowserTest::TestPassToString());
+
+IN_PROC_BROWSER_TEST_P(AccessibilityHitTestingMacBrowserTest,
+ AccessibilityHitTest) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
+
+ AccessibilityNotificationWaiter waiter(shell()->web_contents(),
+ ui::kAXModeComplete,
+ ax::mojom::Event::kLoadComplete);
+ GURL url(embedded_test_server()->GetURL(
+ "/accessibility/hit_testing/simple_rectangles.html"));
+ EXPECT_TRUE(NavigateToURL(shell(), url));
+ waiter.WaitForNotification();
+
+ WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(),
+ "rectA");
+
+ BrowserAccessibilityCocoa* root = GetWebContentRoot();
+
+ // Test a hit on a rect in the main frame.
+ {
+ gfx::Point rect_2_point(49, 20);
+ gfx::Point rect_2_point_frame = CSSToFramePoint(rect_2_point);
+ BrowserAccessibilityCocoa* hit_element =
+ [root accessibilityHitTest:NSMakePoint(rect_2_point_frame.x(),
+ rect_2_point_frame.y())];
+ BrowserAccessibilityCocoa* expected_element = ToBrowserAccessibilityCocoa(
+ FindNode(ax::mojom::Role::kGenericContainer, "rect2"));
+ EXPECT_ACCESSIBILITY_MAC_HIT_TEST_RESULT(rect_2_point, expected_element,
+ hit_element);
+ }
+
+ // Test a hit on a rect in the iframe.
+ {
+ gfx::Point rect_b_point(79, 79);
+ gfx::Point rect_b_point_frame = CSSToFramePoint(rect_b_point);
+ BrowserAccessibilityCocoa* hit_element =
+ [root accessibilityHitTest:NSMakePoint(rect_b_point_frame.x(),
+ rect_b_point_frame.y())];
+ BrowserAccessibilityCocoa* expected_element = ToBrowserAccessibilityCocoa(
+ FindNode(ax::mojom::Role::kGenericContainer, "rectB"));
+ EXPECT_ACCESSIBILITY_MAC_HIT_TEST_RESULT(rect_b_point, expected_element,
+ hit_element);
+ }
+}
+
+}
diff --git a/chromium/content/browser/accessibility/one_shot_accessibility_tree_search_unittest.cc b/chromium/content/browser/accessibility/one_shot_accessibility_tree_search_unittest.cc
index c920deb7adc..db4cc2f2520 100644
--- a/chromium/content/browser/accessibility/one_shot_accessibility_tree_search_unittest.cc
+++ b/chromium/content/browser/accessibility/one_shot_accessibility_tree_search_unittest.cc
@@ -23,7 +23,7 @@ namespace {
class TestBrowserAccessibilityManager
: public BrowserAccessibilityManagerAndroid {
public:
- TestBrowserAccessibilityManager(const ui::AXTreeUpdate& initial_tree)
+ explicit TestBrowserAccessibilityManager(const ui::AXTreeUpdate& initial_tree)
: BrowserAccessibilityManagerAndroid(initial_tree, nullptr, nullptr) {}
};
#else
@@ -55,15 +55,15 @@ class OneShotAccessibilityTreeSearchTest : public testing::TestWithParam<bool> {
void OneShotAccessibilityTreeSearchTest::SetUp() {
ui::AXNodeData root;
root.id = 1;
- root.SetName("Document");
root.role = ax::mojom::Role::kRootWebArea;
+ root.SetName("Document");
root.relative_bounds.bounds = gfx::RectF(0, 0, 800, 600);
root.AddBoolAttribute(ax::mojom::BoolAttribute::kClipsChildren, true);
ui::AXNodeData heading;
heading.id = 2;
- heading.SetName("Heading");
heading.role = ax::mojom::Role::kHeading;
+ heading.SetName("Heading");
heading.relative_bounds.bounds = gfx::RectF(0, 0, 800, 50);
ui::AXNodeData table;
@@ -102,20 +102,20 @@ void OneShotAccessibilityTreeSearchTest::SetUp() {
ui::AXNodeData list_item_1;
list_item_1.id = 8;
- list_item_1.SetName("Autobots");
list_item_1.role = ax::mojom::Role::kListItem;
+ list_item_1.SetName("Autobots");
list_item_1.relative_bounds.bounds = gfx::RectF(10, 10, 200, 30);
ui::AXNodeData list_item_2;
list_item_2.id = 9;
- list_item_2.SetName("Decepticons");
list_item_2.role = ax::mojom::Role::kListItem;
+ list_item_2.SetName("Decepticons");
list_item_2.relative_bounds.bounds = gfx::RectF(10, 40, 200, 60);
ui::AXNodeData footer;
footer.id = 10;
- footer.SetName("Footer");
footer.role = ax::mojom::Role::kFooter;
+ footer.SetName("Footer");
footer.relative_bounds.bounds = gfx::RectF(0, 650, 100, 800);
table_row.child_ids = {table_column_header_1.id, table_column_header_2.id};
diff --git a/chromium/content/browser/accessibility/site_per_process_accessibility_browsertest.cc b/chromium/content/browser/accessibility/site_per_process_accessibility_browsertest.cc
index 6c60abaf02d..6b73b0ed4fe 100644
--- a/chromium/content/browser/accessibility/site_per_process_accessibility_browsertest.cc
+++ b/chromium/content/browser/accessibility/site_per_process_accessibility_browsertest.cc
@@ -29,6 +29,7 @@
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
+#include "content/test/render_document_feature.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "url/gurl.h"
@@ -42,6 +43,13 @@
#define MAYBE_SitePerProcessAccessibilityBrowserTest \
SitePerProcessAccessibilityBrowserTest
#endif
+// "All/DISABLED_SitePerProcessAccessibilityBrowserTest" does not work. We need
+// "DISABLED_All/...". TODO(https://crbug.com/1096416) delete when fixed.
+#if defined(OS_ANDROID)
+#define MAYBE_All DISABLED_All
+#else
+#define MAYBE_All All
+#endif
namespace content {
@@ -70,7 +78,7 @@ class MAYBE_SitePerProcessAccessibilityBrowserTest
}
};
-IN_PROC_BROWSER_TEST_F(MAYBE_SitePerProcessAccessibilityBrowserTest,
+IN_PROC_BROWSER_TEST_P(MAYBE_SitePerProcessAccessibilityBrowserTest,
CrossSiteIframeAccessibility) {
// Enable full accessibility for all current and future WebContents.
BrowserAccessibilityState::GetInstance()->EnableAccessibility();
@@ -138,7 +146,7 @@ IN_PROC_BROWSER_TEST_F(MAYBE_SitePerProcessAccessibilityBrowserTest,
}
// TODO(aboxhall): Flaky test, discuss with dmazzoni
-IN_PROC_BROWSER_TEST_F(MAYBE_SitePerProcessAccessibilityBrowserTest,
+IN_PROC_BROWSER_TEST_P(MAYBE_SitePerProcessAccessibilityBrowserTest,
DISABLED_TwoCrossSiteNavigations) {
// Enable full accessibility for all current and future WebContents.
BrowserAccessibilityState::GetInstance()->EnableAccessibility();
@@ -168,7 +176,7 @@ IN_PROC_BROWSER_TEST_F(MAYBE_SitePerProcessAccessibilityBrowserTest,
// Ensure that enabling accessibility and doing a remote-to-local main frame
// navigation doesn't crash. See https://crbug.com/762824.
-IN_PROC_BROWSER_TEST_F(MAYBE_SitePerProcessAccessibilityBrowserTest,
+IN_PROC_BROWSER_TEST_P(MAYBE_SitePerProcessAccessibilityBrowserTest,
RemoteToLocalMainFrameNavigation) {
// Enable full accessibility for all current and future WebContents.
BrowserAccessibilityState::GetInstance()->EnableAccessibility();
@@ -192,4 +200,7 @@ IN_PROC_BROWSER_TEST_F(MAYBE_SitePerProcessAccessibilityBrowserTest,
"Title Of Awesomeness");
}
+INSTANTIATE_TEST_SUITE_P(MAYBE_All,
+ MAYBE_SitePerProcessAccessibilityBrowserTest,
+ testing::ValuesIn(RenderDocumentFeatureLevelValues()));
} // namespace content
diff --git a/chromium/content/browser/accessibility/snapshot_ax_tree_browsertest.cc b/chromium/content/browser/accessibility/snapshot_ax_tree_browsertest.cc
index 6d1a247582b..c807616350c 100644
--- a/chromium/content/browser/accessibility/snapshot_ax_tree_browsertest.cc
+++ b/chromium/content/browser/accessibility/snapshot_ax_tree_browsertest.cc
@@ -270,4 +270,102 @@ IN_PROC_BROWSER_TEST_F(SnapshotAXTreeBrowserTest,
total_attribute_count(complete_nodes[i]));
}
+IN_PROC_BROWSER_TEST_F(SnapshotAXTreeBrowserTest, SnapshotPDFMode) {
+ // The "PDF" accessibility mode is used when getting a snapshot of the
+ // accessibility tree in order to export a tagged PDF. Ensure that
+ // we're serializing the right set of attributes needed for a PDF and
+ // also ensure that we're *not* wasting time serializing attributes
+ // that are not needed for PDF export.
+ GURL url(R"HTML(data:text/html,<body>
+ <img src="" alt="Unicorns">
+ <ul>
+ <li aria-posinset="5">
+ <span style="color: red;">Red text</span>
+ </ul>
+ <table role="table">
+ <tr>
+ <td colspan="2">
+ </tr>
+ <tr>
+ <td>1</td><td>2</td>
+ </tr>
+ </table>
+ </body>)HTML");
+ EXPECT_TRUE(NavigateToURL(shell(), url));
+
+ auto* web_contents = static_cast<WebContentsImpl*>(shell()->web_contents());
+ AXTreeSnapshotWaiter waiter;
+ web_contents->RequestAXTreeSnapshot(
+ base::BindOnce(&AXTreeSnapshotWaiter::ReceiveSnapshot,
+ base::Unretained(&waiter)),
+ ui::AXMode::kPDF);
+ waiter.Wait();
+
+ // Dump the whole tree if one of the assertions below fails
+ // to aid in debugging why it failed.
+ SCOPED_TRACE(waiter.snapshot().ToString());
+
+ // Scan all of the nodes and make some general assertions.
+ int dom_node_id_count = 0;
+ for (const ui::AXNodeData& node_data : waiter.snapshot().nodes) {
+ // Every node should have a valid role, state, and ID.
+ EXPECT_NE(ax::mojom::Role::kUnknown, node_data.role);
+ EXPECT_NE(0, node_data.id);
+
+ if (node_data.GetIntAttribute(ax::mojom::IntAttribute::kDOMNodeId) != 0)
+ dom_node_id_count++;
+
+ // We don't need bounding boxes to make a tagged PDF. Ensure those are
+ // uninitialized.
+ EXPECT_TRUE(node_data.relative_bounds.bounds.IsEmpty());
+
+ // We shouldn't get any inline text box nodes. They aren't needed to
+ // make a tagged PDF and they make up a large fraction of nodes in the
+ // tree when present.
+ EXPECT_NE(ax::mojom::Role::kInlineTextBox, node_data.role);
+
+ // We shouldn't have any style information like color in the tree.
+ EXPECT_FALSE(node_data.HasIntAttribute(ax::mojom::IntAttribute::kColor));
+ }
+
+ // Many nodes should have a DOM node id. That's not normally included
+ // in the accessibility tree but it's needed for associating nodes with
+ // rendered text in the PDF file.
+ EXPECT_GT(dom_node_id_count, 5);
+
+ // Build an AXTree from the snapshot and make some specific assertions.
+ ui::AXTree tree(waiter.snapshot());
+ ui::AXNode* root = tree.root();
+ ASSERT_TRUE(root);
+ ASSERT_EQ(ax::mojom::Role::kRootWebArea, root->data().role);
+
+ // Img alt text should be present.
+ ui::AXNode* image = root->GetUnignoredChildAtIndex(0);
+ ASSERT_TRUE(image);
+ ASSERT_EQ(ax::mojom::Role::kImage, image->data().role);
+ ASSERT_EQ("Unicorns", image->data().GetStringAttribute(
+ ax::mojom::StringAttribute::kName));
+
+ // List attributes like posinset should be present.
+ ui::AXNode* ul = root->GetUnignoredChildAtIndex(1);
+ ASSERT_TRUE(ul);
+ ASSERT_EQ(ax::mojom::Role::kList, ul->data().role);
+ ui::AXNode* li = ul->GetUnignoredChildAtIndex(0);
+ ASSERT_TRUE(li);
+ ASSERT_EQ(ax::mojom::Role::kListItem, li->data().role);
+ EXPECT_EQ(5, *li->GetPosInSet());
+
+ // Table attributes like colspan should be present.
+ ui::AXNode* table = root->GetUnignoredChildAtIndex(2);
+ ASSERT_TRUE(table);
+ ASSERT_EQ(ax::mojom::Role::kTable, table->data().role);
+ ui::AXNode* tr = table->GetUnignoredChildAtIndex(0);
+ ASSERT_TRUE(tr);
+ ASSERT_EQ(ax::mojom::Role::kRow, tr->data().role);
+ ui::AXNode* td = tr->GetUnignoredChildAtIndex(0);
+ ASSERT_TRUE(td);
+ ASSERT_EQ(ax::mojom::Role::kCell, td->data().role);
+ EXPECT_EQ(2, *td->GetTableCellColSpan());
+}
+
} // namespace content
diff --git a/chromium/content/browser/accessibility/test_browser_accessibility_delegate.cc b/chromium/content/browser/accessibility/test_browser_accessibility_delegate.cc
index 9e3fe4f42ca..72d1d2610ad 100644
--- a/chromium/content/browser/accessibility/test_browser_accessibility_delegate.cc
+++ b/chromium/content/browser/accessibility/test_browser_accessibility_delegate.cc
@@ -55,6 +55,13 @@ bool TestBrowserAccessibilityDelegate::AccessibilityIsMainFrame() {
return is_root_frame_;
}
+void TestBrowserAccessibilityDelegate::AccessibilityHitTest(
+ const gfx::Point& point_in_frame_pixels,
+ ax::mojom::Event opt_event_to_fire,
+ int opt_request_id,
+ base::OnceCallback<void(BrowserAccessibilityManager* hit_manager,
+ int hit_node_id)> opt_callback) {}
+
bool TestBrowserAccessibilityDelegate::got_fatal_error() const {
return got_fatal_error_;
}
diff --git a/chromium/content/browser/accessibility/test_browser_accessibility_delegate.h b/chromium/content/browser/accessibility/test_browser_accessibility_delegate.h
index 818e827bd32..5b9b2607fe8 100644
--- a/chromium/content/browser/accessibility/test_browser_accessibility_delegate.h
+++ b/chromium/content/browser/accessibility/test_browser_accessibility_delegate.h
@@ -25,6 +25,12 @@ class TestBrowserAccessibilityDelegate : public BrowserAccessibilityDelegate {
override;
WebContents* AccessibilityWebContents() override;
bool AccessibilityIsMainFrame() override;
+ void AccessibilityHitTest(
+ const gfx::Point& point_in_frame_pixels,
+ ax::mojom::Event opt_event_to_fire,
+ int opt_request_id,
+ base::OnceCallback<void(BrowserAccessibilityManager* hit_manager,
+ int hit_node_id)> opt_callback) override;
bool got_fatal_error() const;
void reset_got_fatal_error();
diff --git a/chromium/content/browser/accessibility/web_contents_accessibility_android.cc b/chromium/content/browser/accessibility/web_contents_accessibility_android.cc
index 4633b8e274d..b53ca692ff8 100644
--- a/chromium/content/browser/accessibility/web_contents_accessibility_android.cc
+++ b/chromium/content/browser/accessibility/web_contents_accessibility_android.cc
@@ -697,13 +697,13 @@ void WebContentsAccessibilityAndroid::UpdateAccessibilityNodeInfoBoundsRect(
gfx::Rect absolute_rect = gfx::ScaleToEnclosingRect(
node->GetUnclippedRootFrameBoundsRect(), dip_scale, dip_scale);
gfx::Rect parent_relative_rect = absolute_rect;
- if (node->PlatformGetParent()) {
+ bool is_root = node->PlatformGetParent() == nullptr;
+ if (!is_root) {
gfx::Rect parent_rect = gfx::ScaleToEnclosingRect(
node->PlatformGetParent()->GetUnclippedRootFrameBoundsRect(), dip_scale,
dip_scale);
parent_relative_rect.Offset(-parent_rect.OffsetFromOrigin());
}
- bool is_root = node->PlatformGetParent() == NULL;
Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoLocation(
env, obj, info, unique_id, absolute_rect.x(), absolute_rect.y(),
parent_relative_rect.x(), parent_relative_rect.y(), absolute_rect.width(),
@@ -734,7 +734,8 @@ jboolean WebContentsAccessibilityAndroid::PopulateAccessibilityNodeInfo(
if (!node)
return false;
- if (node->PlatformGetParent()) {
+ bool is_root = node->PlatformGetParent() == nullptr;
+ if (!is_root) {
auto* android_node =
static_cast<BrowserAccessibilityAndroid*>(node->PlatformGetParent());
Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoParent(
@@ -749,9 +750,10 @@ jboolean WebContentsAccessibilityAndroid::PopulateAccessibilityNodeInfo(
}
Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoBooleanAttributes(
env, obj, info, unique_id, node->IsCheckable(), node->IsChecked(),
- node->IsClickable(), node->IsEnabled(), node->IsFocusable(),
- node->IsFocused(), node->IsPasswordField(), node->IsScrollable(),
- node->IsSelected(), node->IsVisibleToUser());
+ node->IsClickable(), node->IsContentInvalid(), node->IsEnabled(),
+ node->IsFocusable(), node->IsFocused(), node->HasImage(),
+ node->IsPasswordField(), node->IsScrollable(), node->IsSelected(),
+ node->IsVisibleToUser());
Java_WebContentsAccessibilityImpl_addAccessibilityNodeInfoActions(
env, obj, info, unique_id, node->CanScrollForward(),
node->CanScrollBackward(), node->CanScrollUp(), node->CanScrollDown(),
@@ -760,9 +762,14 @@ jboolean WebContentsAccessibilityAndroid::PopulateAccessibilityNodeInfo(
node->IsFocused(), node->IsCollapsed(), node->IsExpanded(),
node->HasNonEmptyValue(), !node->GetInnerText().empty(),
node->IsRangeType(), node->IsFormDescendant());
- Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoClassName(
- env, obj, info,
- base::android::ConvertUTF8ToJavaString(env, node->GetClassName()));
+
+ Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoBaseAttributes(
+ env, obj, info, is_root,
+ base::android::ConvertUTF8ToJavaString(env, node->GetClassName()),
+ base::android::ConvertUTF8ToJavaString(env, node->GetRoleString()),
+ base::android::ConvertUTF16ToJavaString(env, node->GetRoleDescription()),
+ base::android::ConvertUTF16ToJavaString(env, node->GetHint()),
+ base::android::ConvertUTF16ToJavaString(env, node->GetTargetUrl()));
ScopedJavaLocalRef<jintArray> suggestion_starts_java;
ScopedJavaLocalRef<jintArray> suggestion_ends_java;
@@ -801,18 +808,6 @@ jboolean WebContentsAccessibilityAndroid::PopulateAccessibilityNodeInfo(
UpdateAccessibilityNodeInfoBoundsRect(env, obj, info, unique_id, node);
- bool is_root = node->PlatformGetParent() == NULL;
-
- Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoKitKatAttributes(
- env, obj, info, is_root, node->IsTextField(),
- base::android::ConvertUTF8ToJavaString(env, node->GetRoleString()),
- base::android::ConvertUTF16ToJavaString(env, node->GetRoleDescription()),
- base::android::ConvertUTF16ToJavaString(env, node->GetHint()),
- node->GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart),
- node->GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd),
- node->HasImage(), node->IsContentInvalid(),
- base::android::ConvertUTF16ToJavaString(env, node->GetTargetUrl()));
-
Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoLollipopAttributes(
env, obj, info, node->CanOpenPopup(), node->IsContentInvalid(),
node->IsDismissable(), node->IsMultiLine(), node->AndroidInputType(),
@@ -820,9 +815,9 @@ jboolean WebContentsAccessibilityAndroid::PopulateAccessibilityNodeInfo(
base::android::ConvertUTF16ToJavaString(
env, node->GetContentInvalidErrorMessage()));
- bool has_character_locations = node->HasCharacterLocations();
Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoOAttributes(
- env, obj, info, has_character_locations);
+ env, obj, info, node->HasCharacterLocations(),
+ base::android::ConvertUTF16ToJavaString(env, node->GetHint()));
if (node->IsCollection()) {
Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoCollectionInfo(
@@ -846,6 +841,11 @@ jboolean WebContentsAccessibilityAndroid::PopulateAccessibilityNodeInfo(
base::android::ConvertUTF16ToJavaString(env, node->GetInnerText()));
}
+ if (node->IsTextField()) {
+ Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoSelectionAttrs(
+ env, obj, info, node->GetSelectionStart(), node->GetSelectionEnd());
+ }
+
return true;
}