blob: 90dee6a87d23969343f9ef91744c208a54adc9a7 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/accessibility/platform/browser_accessibility_manager.h"
#include <stddef.h>
#include <stdint.h>
#include <string>
#include "base/scoped_observation.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "ui/accessibility/ax_tree_observer.h"
#include "ui/accessibility/platform/browser_accessibility.h"
#if BUILDFLAG(IS_WIN)
#include "ui/accessibility/platform/browser_accessibility_win.h"
#endif
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_common.h"
#include "ui/accessibility/ax_tree.h"
#include "ui/accessibility/ax_updates_and_events.h"
#include "ui/accessibility/platform/test_ax_node_id_delegate.h"
#include "ui/accessibility/platform/test_ax_platform_tree_manager_delegate.h"
#include "ui/accessibility/test_ax_tree_update.h"
#if BUILDFLAG(IS_ANDROID)
#include "content/browser/accessibility/browser_accessibility_manager_android.h"
#endif
namespace content {
namespace {
class CountingAXTreeObserver : public ui::AXTreeObserver {
public:
CountingAXTreeObserver() = default;
~CountingAXTreeObserver() override = default;
CountingAXTreeObserver(const CountingAXTreeObserver&) = delete;
CountingAXTreeObserver& operator=(const CountingAXTreeObserver&) = delete;
int reparent_count() { return reparent_count_; }
int update_count() { return update_count_; }
int node_count() { return node_count_; }
private:
void OnNodeReparented(ui::AXTree* tree, ui::AXNode* node) override {
++reparent_count_;
}
void OnAtomicUpdateFinished(ui::AXTree* tree,
bool root_changed,
const std::vector<Change>& changes) override {
++update_count_;
node_count_ += static_cast<int>(changes.size());
}
int reparent_count_ = 0;
int update_count_ = 0;
int node_count_ = 0;
};
ui::BrowserAccessibilityManager* CreateBrowserAccessibilityManager(
const ui::AXTreeUpdate& initial_tree,
ui::AXNodeIdDelegate& node_id_delegate,
ui::AXPlatformTreeManagerDelegate* delegate) {
#if BUILDFLAG(IS_ANDROID)
return content::BrowserAccessibilityManagerAndroid::Create(
initial_tree, node_id_delegate, delegate);
#else
return ui::BrowserAccessibilityManager::Create(initial_tree, node_id_delegate,
delegate);
#endif
}
} // anonymous namespace
class BrowserAccessibilityManagerTest : public testing::Test {
public:
BrowserAccessibilityManagerTest() = default;
BrowserAccessibilityManagerTest(const BrowserAccessibilityManagerTest&) =
delete;
BrowserAccessibilityManagerTest& operator=(
const BrowserAccessibilityManagerTest&) = delete;
~BrowserAccessibilityManagerTest() override = default;
protected:
void SetUp() override;
std::unique_ptr<ui::TestAXPlatformTreeManagerDelegate>
test_browser_accessibility_delegate_;
ui::TestAXNodeIdDelegate node_id_delegate_;
const content::BrowserTaskEnvironment task_environment_;
};
void BrowserAccessibilityManagerTest::SetUp() {
testing::Test::SetUp();
test_browser_accessibility_delegate_ =
std::make_unique<ui::TestAXPlatformTreeManagerDelegate>();
}
TEST_F(BrowserAccessibilityManagerTest, TestErrorOnCreateIsFatal) {
// Test that BrowserAccessibilityManager raises a fatal error
// (which will crash the renderer) if the same id is used in
// two places in the tree.
ui::AXNodeData root;
root.id = 1;
root.role = ax::mojom::Role::kRootWebArea;
root.child_ids.push_back(2);
root.child_ids.push_back(2);
std::unique_ptr<ui::BrowserAccessibilityManager> manager;
EXPECT_DEATH_IF_SUPPORTED(
manager.reset(CreateBrowserAccessibilityManager(
MakeAXTreeUpdateForTesting(root), node_id_delegate_,
test_browser_accessibility_delegate_.get())),
"Node 1 has 1 duplicate child ids");
}
TEST_F(BrowserAccessibilityManagerTest, TestErrorOnUpdate) {
ui::AXNodeData root;
root.id = 1;
root.role = ax::mojom::Role::kRootWebArea;
ui::AXNodeData node2;
node2.id = 2;
root.child_ids.push_back(2);
ui::AXNodeData node3;
node3.id = 3;
root.child_ids.push_back(3);
ui::AXNodeData node4;
node4.id = 4;
node3.child_ids.push_back(4);
ui::AXNodeData node5;
node5.id = 5;
root.child_ids.push_back(5);
std::unique_ptr<ui::BrowserAccessibilityManager> manager(
CreateBrowserAccessibilityManager(
MakeAXTreeUpdateForTesting(root, node2, node3, node4, node5),
node_id_delegate_, test_browser_accessibility_delegate_.get()));
// node4 has two child ids now.
node4.child_ids.push_back(5);
node4.child_ids.push_back(5);
ui::AXTreeUpdate update = MakeAXTreeUpdateForTesting(node4, node5);
update.tree_data.tree_id = manager->GetTreeID();
ui::AXUpdatesAndEvents events;
events.updates = {update};
#if AX_FAIL_FAST_BUILD()
// Update errors are fatal in AX_FAIL_FAST_BUILD builds.
EXPECT_DEATH_IF_SUPPORTED(manager->OnAccessibilityEvents(events),
"Node 4 has 1 duplicate child ids");
#else
ASSERT_FALSE(manager->OnAccessibilityEvents(events));
#endif
}
// This test depends on hypertext, which is only used on
// Linux and Windows.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(USE_ATK)
TEST_F(BrowserAccessibilityManagerTest, BoundsForRange) {
ui::AXNodeData root;
root.id = 1;
root.role = ax::mojom::Role::kRootWebArea;
root.relative_bounds.bounds = gfx::RectF(0, 0, 800, 600);
ui::AXNodeData static_text;
static_text.id = 2;
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.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::WritingDirection::kLtr);
std::vector<int32_t> character_offsets1;
character_offsets1.push_back(6); // 0
character_offsets1.push_back(11); // 1
character_offsets1.push_back(16); // 2
character_offsets1.push_back(21); // 3
character_offsets1.push_back(26); // 4
character_offsets1.push_back(29); // 5
character_offsets1.push_back(29); // 6 (note that the space has no width)
inline_text1.AddIntListAttribute(
ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets1);
static_text.child_ids.push_back(3);
ui::AXNodeData inline_text2;
inline_text2.id = 4;
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::WritingDirection::kLtr);
std::vector<int32_t> character_offsets2;
character_offsets2.push_back(5);
character_offsets2.push_back(10);
character_offsets2.push_back(15);
character_offsets2.push_back(20);
character_offsets2.push_back(25);
character_offsets2.push_back(28);
inline_text2.AddIntListAttribute(
ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets2);
static_text.child_ids.push_back(4);
std::unique_ptr<ui::BrowserAccessibilityManager> manager(
CreateBrowserAccessibilityManager(
MakeAXTreeUpdateForTesting(root, static_text, inline_text1,
inline_text2),
node_id_delegate_, test_browser_accessibility_delegate_.get()));
ui::BrowserAccessibility* root_accessible =
manager->GetBrowserAccessibilityRoot();
ASSERT_NE(nullptr, root_accessible);
ui::BrowserAccessibility* static_text_accessible =
root_accessible->PlatformGetChild(0);
ASSERT_NE(nullptr, static_text_accessible);
EXPECT_EQ(gfx::Rect(100, 100, 6, 9).ToString(),
static_text_accessible
->GetRootFrameHypertextRangeBoundsRect(
0, 1, ui::AXClippingBehavior::kUnclipped)
.ToString());
EXPECT_EQ(gfx::Rect(100, 100, 26, 9).ToString(),
static_text_accessible
->GetRootFrameHypertextRangeBoundsRect(
0, 5, ui::AXClippingBehavior::kUnclipped)
.ToString());
EXPECT_EQ(gfx::Rect(100, 109, 5, 9).ToString(),
static_text_accessible
->GetRootFrameHypertextRangeBoundsRect(
7, 1, ui::AXClippingBehavior::kUnclipped)
.ToString());
EXPECT_EQ(gfx::Rect(100, 109, 25, 9).ToString(),
static_text_accessible
->GetRootFrameHypertextRangeBoundsRect(
7, 5, ui::AXClippingBehavior::kUnclipped)
.ToString());
EXPECT_EQ(gfx::Rect(100, 100, 29, 18).ToString(),
static_text_accessible
->GetRootFrameHypertextRangeBoundsRect(
5, 3, ui::AXClippingBehavior::kUnclipped)
.ToString());
EXPECT_EQ(gfx::Rect(100, 100, 29, 18).ToString(),
static_text_accessible
->GetRootFrameHypertextRangeBoundsRect(
0, 13, ui::AXClippingBehavior::kUnclipped)
.ToString());
// Note that each child in the parent element is represented by a single
// embedded object character and not by its text.
// TODO(nektar): Investigate failure on Linux.
EXPECT_EQ(gfx::Rect(100, 100, 29, 18).ToString(),
root_accessible
->GetRootFrameHypertextRangeBoundsRect(
0, 13, ui::AXClippingBehavior::kUnclipped)
.ToString());
}
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(USE_ATK)
TEST_F(BrowserAccessibilityManagerTest, BoundsForRangeMultiElement) {
ui::AXNodeData root;
root.id = 1;
root.role = ax::mojom::Role::kRootWebArea;
root.relative_bounds.bounds = gfx::RectF(0, 0, 800, 600);
ui::AXNodeData static_text;
static_text.id = 2;
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.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::WritingDirection::kLtr);
std::vector<int32_t> character_offsets{10, 21, 33};
inline_text1.AddIntListAttribute(
ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets);
static_text.child_ids.push_back(3);
ui::AXNodeData static_text2;
static_text2.id = 4;
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.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::WritingDirection::kLtr);
inline_text2.AddIntListAttribute(
ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets);
static_text2.child_ids.push_back(5);
std::unique_ptr<ui::BrowserAccessibilityManager> manager(
CreateBrowserAccessibilityManager(
MakeAXTreeUpdateForTesting(root, static_text, inline_text1,
static_text2, inline_text2),
node_id_delegate_, test_browser_accessibility_delegate_.get()));
ui::BrowserAccessibility* root_accessible =
manager->GetBrowserAccessibilityRoot();
ASSERT_NE(nullptr, root_accessible);
ui::BrowserAccessibility* static_text_accessible =
root_accessible->PlatformGetChild(0);
ASSERT_NE(nullptr, static_text_accessible);
ui::BrowserAccessibility* static_text_accessible2 =
root_accessible->PlatformGetChild(1);
ASSERT_NE(nullptr, static_text_accessible);
// The first line.
EXPECT_EQ(gfx::Rect(0, 20, 33, 9).ToString(),
manager
->GetRootFrameInnerTextRangeBoundsRect(
*static_text_accessible, 0, *static_text_accessible, 3)
.ToString());
// Part of the first line.
EXPECT_EQ(gfx::Rect(0, 20, 21, 9).ToString(),
manager
->GetRootFrameInnerTextRangeBoundsRect(
*static_text_accessible, 0, *static_text_accessible, 2)
.ToString());
// Part of the first line.
EXPECT_EQ(gfx::Rect(10, 20, 23, 9).ToString(),
manager
->GetRootFrameInnerTextRangeBoundsRect(
*static_text_accessible, 1, *static_text_accessible, 3)
.ToString());
// The second line.
EXPECT_EQ(gfx::Rect(10, 40, 33, 9).ToString(),
manager
->GetRootFrameInnerTextRangeBoundsRect(
*static_text_accessible2, 0, *static_text_accessible2, 3)
.ToString());
// All of both lines.
EXPECT_EQ(gfx::Rect(0, 20, 43, 29).ToString(),
manager
->GetRootFrameInnerTextRangeBoundsRect(
*static_text_accessible, 0, *static_text_accessible2, 3)
.ToString());
// Part of both lines.
EXPECT_EQ(gfx::Rect(10, 20, 23, 29).ToString(),
manager
->GetRootFrameInnerTextRangeBoundsRect(
*static_text_accessible, 2, *static_text_accessible2, 1)
.ToString());
// Part of both lines in reverse order.
EXPECT_EQ(gfx::Rect(10, 20, 23, 29).ToString(),
manager
->GetRootFrameInnerTextRangeBoundsRect(
*static_text_accessible2, 1, *static_text_accessible, 2)
.ToString());
}
// This test depends on hypertext, which is only used on
// Linux and Windows.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(USE_ATK)
TEST_F(BrowserAccessibilityManagerTest, BoundsForRangeBiDi) {
// In this example, we assume that the string "123abc" is rendered with
// "123" going left-to-right and "abc" going right-to-left. In other
// words, on-screen it would look like "123cba". This is possible to
// achieve if the source string had unicode control characters
// to switch directions. This test doesn't worry about how, though - it just
// tests that if something like that were to occur,
// GetRootFrameRangeBoundsRect returns the correct bounds for different
// ranges.
ui::AXNodeData root;
root.id = 1;
root.role = ax::mojom::Role::kRootWebArea;
root.relative_bounds.bounds = gfx::RectF(0, 0, 800, 600);
ui::AXNodeData static_text;
static_text.id = 2;
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.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::WritingDirection::kLtr);
std::vector<int32_t> character_offsets1;
character_offsets1.push_back(10); // 0
character_offsets1.push_back(20); // 1
character_offsets1.push_back(30); // 2
inline_text1.AddIntListAttribute(
ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets1);
static_text.child_ids.push_back(3);
ui::AXNodeData inline_text2;
inline_text2.id = 4;
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::WritingDirection::kRtl);
std::vector<int32_t> character_offsets2;
character_offsets2.push_back(10);
character_offsets2.push_back(20);
character_offsets2.push_back(30);
inline_text2.AddIntListAttribute(
ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets2);
static_text.child_ids.push_back(4);
std::unique_ptr<ui::BrowserAccessibilityManager> manager(
CreateBrowserAccessibilityManager(
MakeAXTreeUpdateForTesting(root, static_text, inline_text1,
inline_text2),
node_id_delegate_, test_browser_accessibility_delegate_.get()));
ui::BrowserAccessibility* root_accessible =
manager->GetBrowserAccessibilityRoot();
ASSERT_NE(nullptr, root_accessible);
ui::BrowserAccessibility* static_text_accessible =
root_accessible->PlatformGetChild(0);
ASSERT_NE(nullptr, static_text_accessible);
EXPECT_EQ(gfx::Rect(100, 100, 60, 20).ToString(),
static_text_accessible
->GetRootFrameHypertextRangeBoundsRect(
0, 6, ui::AXClippingBehavior::kUnclipped)
.ToString());
EXPECT_EQ(gfx::Rect(100, 100, 10, 20).ToString(),
static_text_accessible
->GetRootFrameHypertextRangeBoundsRect(
0, 1, ui::AXClippingBehavior::kUnclipped)
.ToString());
EXPECT_EQ(gfx::Rect(100, 100, 30, 20).ToString(),
static_text_accessible
->GetRootFrameHypertextRangeBoundsRect(
0, 3, ui::AXClippingBehavior::kUnclipped)
.ToString());
EXPECT_EQ(gfx::Rect(150, 100, 10, 20).ToString(),
static_text_accessible
->GetRootFrameHypertextRangeBoundsRect(
3, 1, ui::AXClippingBehavior::kUnclipped)
.ToString());
EXPECT_EQ(gfx::Rect(130, 100, 30, 20).ToString(),
static_text_accessible
->GetRootFrameHypertextRangeBoundsRect(
3, 3, ui::AXClippingBehavior::kUnclipped)
.ToString());
// This range is only two characters, but because of the direction switch
// the bounds are as wide as four characters.
EXPECT_EQ(gfx::Rect(120, 100, 40, 20).ToString(),
static_text_accessible
->GetRootFrameHypertextRangeBoundsRect(
2, 2, ui::AXClippingBehavior::kUnclipped)
.ToString());
}
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(USE_ATK)
// This test depends on hypertext, which is only used on
// Linux and Windows.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(USE_ATK)
TEST_F(BrowserAccessibilityManagerTest, BoundsForRangeScrolledWindow) {
ui::AXNodeData root;
root.id = 1;
root.role = ax::mojom::Role::kRootWebArea;
root.AddIntAttribute(ax::mojom::IntAttribute::kScrollX, 25);
root.AddIntAttribute(ax::mojom::IntAttribute::kScrollY, 50);
root.relative_bounds.bounds = gfx::RectF(0, 0, 800, 600);
ui::AXNodeData static_text;
static_text.id = 2;
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.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::WritingDirection::kLtr);
std::vector<int32_t> character_offsets1;
character_offsets1.push_back(6); // 0
character_offsets1.push_back(11); // 1
character_offsets1.push_back(16); // 2
inline_text.AddIntListAttribute(
ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets1);
static_text.child_ids.push_back(3);
std::unique_ptr<ui::BrowserAccessibilityManager> manager(
CreateBrowserAccessibilityManager(
MakeAXTreeUpdateForTesting(root, static_text, inline_text),
node_id_delegate_, test_browser_accessibility_delegate_.get()));
ui::BrowserAccessibility* root_accessible =
manager->GetBrowserAccessibilityRoot();
ASSERT_NE(nullptr, root_accessible);
ui::BrowserAccessibility* static_text_accessible =
root_accessible->PlatformGetChild(0);
ASSERT_NE(nullptr, static_text_accessible);
if (manager->UseRootScrollOffsetsWhenComputingBounds()) {
EXPECT_EQ(gfx::Rect(75, 50, 16, 9).ToString(),
static_text_accessible
->GetRootFrameHypertextRangeBoundsRect(
0, 3, ui::AXClippingBehavior::kUnclipped)
.ToString());
} else {
EXPECT_EQ(gfx::Rect(100, 100, 16, 9).ToString(),
static_text_accessible
->GetRootFrameHypertextRangeBoundsRect(
0, 3, ui::AXClippingBehavior::kUnclipped)
.ToString());
}
}
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(USE_ATK)
// This test depends on hypertext, which is only used on
// Linux and Windows.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(USE_ATK)
TEST_F(BrowserAccessibilityManagerTest, BoundsForRangeOnParentElement) {
ui::AXNodeData root;
root.id = 1;
root.role = ax::mojom::Role::kRootWebArea;
root.child_ids.push_back(2);
root.relative_bounds.bounds = gfx::RectF(0, 0, 800, 600);
ui::AXNodeData div;
div.id = 2;
div.role = ax::mojom::Role::kGenericContainer;
div.relative_bounds.bounds = gfx::RectF(100, 100, 100, 20);
div.child_ids.push_back(3);
div.child_ids.push_back(4);
div.child_ids.push_back(5);
ui::AXNodeData static_text1;
static_text1.id = 3;
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.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.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.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::WritingDirection::kLtr);
std::vector<int32_t> character_offsets1;
character_offsets1.push_back(20); // 0
character_offsets1.push_back(40); // 1
inline_text1.AddIntListAttribute(
ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets1);
ui::AXNodeData inline_text2;
inline_text2.id = 7;
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::WritingDirection::kLtr);
std::vector<int32_t> character_offsets2;
character_offsets2.push_back(20); // 0
character_offsets2.push_back(40); // 1
inline_text2.AddIntListAttribute(
ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets2);
std::unique_ptr<ui::BrowserAccessibilityManager> manager(
CreateBrowserAccessibilityManager(
MakeAXTreeUpdateForTesting(root, div, static_text1, img, static_text2,
inline_text1, inline_text2),
node_id_delegate_, test_browser_accessibility_delegate_.get()));
ui::BrowserAccessibility* root_accessible =
manager->GetBrowserAccessibilityRoot();
ASSERT_NE(nullptr, root_accessible);
ui::BrowserAccessibility* div_accessible =
root_accessible->PlatformGetChild(0);
ASSERT_NE(nullptr, div_accessible);
EXPECT_EQ(gfx::Rect(100, 100, 20, 20).ToString(),
div_accessible
->GetRootFrameHypertextRangeBoundsRect(
0, 1, ui::AXClippingBehavior::kUnclipped)
.ToString());
EXPECT_EQ(gfx::Rect(100, 100, 40, 20).ToString(),
div_accessible
->GetRootFrameHypertextRangeBoundsRect(
0, 2, ui::AXClippingBehavior::kUnclipped)
.ToString());
EXPECT_EQ(gfx::Rect(100, 100, 80, 20).ToString(),
div_accessible
->GetRootFrameHypertextRangeBoundsRect(
0, 4, ui::AXClippingBehavior::kUnclipped)
.ToString());
EXPECT_EQ(gfx::Rect(120, 100, 60, 20).ToString(),
div_accessible
->GetRootFrameHypertextRangeBoundsRect(
1, 3, ui::AXClippingBehavior::kUnclipped)
.ToString());
EXPECT_EQ(gfx::Rect(120, 100, 80, 20).ToString(),
div_accessible
->GetRootFrameHypertextRangeBoundsRect(
1, 4, ui::AXClippingBehavior::kUnclipped)
.ToString());
EXPECT_EQ(gfx::Rect(100, 100, 100, 20).ToString(),
div_accessible
->GetRootFrameHypertextRangeBoundsRect(
0, 5, ui::AXClippingBehavior::kUnclipped)
.ToString());
}
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(USE_ATK)
TEST_F(BrowserAccessibilityManagerTest, TestNextPreviousInTreeOrder) {
ui::TestAXTreeUpdate update(std::string(R"HTML(
++1 kRootWebArea
++++2 kUnknown
++++3 kUnknown
++++++4 kUnknown
++++5 kUnknown
)HTML"));
std::unique_ptr<ui::BrowserAccessibilityManager> manager(
CreateBrowserAccessibilityManager(
update, node_id_delegate_,
test_browser_accessibility_delegate_.get()));
ui::BrowserAccessibility* root_accessible =
manager->GetBrowserAccessibilityRoot();
ASSERT_NE(nullptr, root_accessible);
ASSERT_EQ(3U, root_accessible->PlatformChildCount());
ui::BrowserAccessibility* node2_accessible =
root_accessible->PlatformGetChild(0);
ASSERT_NE(nullptr, node2_accessible);
ui::BrowserAccessibility* node3_accessible =
root_accessible->PlatformGetChild(1);
ASSERT_NE(nullptr, node3_accessible);
ASSERT_EQ(1U, node3_accessible->PlatformChildCount());
ui::BrowserAccessibility* node4_accessible =
node3_accessible->PlatformGetChild(0);
ASSERT_NE(nullptr, node4_accessible);
ui::BrowserAccessibility* node5_accessible =
root_accessible->PlatformGetChild(2);
ASSERT_NE(nullptr, node5_accessible);
EXPECT_EQ(nullptr, manager->NextInTreeOrder(nullptr));
EXPECT_EQ(node2_accessible, manager->NextInTreeOrder(root_accessible));
EXPECT_EQ(node3_accessible, manager->NextInTreeOrder(node2_accessible));
EXPECT_EQ(node4_accessible, manager->NextInTreeOrder(node3_accessible));
EXPECT_EQ(node5_accessible, manager->NextInTreeOrder(node4_accessible));
EXPECT_EQ(nullptr, manager->NextInTreeOrder(node5_accessible));
EXPECT_EQ(nullptr, manager->PreviousInTreeOrder(nullptr, false));
EXPECT_EQ(node4_accessible,
manager->PreviousInTreeOrder(node5_accessible, false));
EXPECT_EQ(node3_accessible,
manager->PreviousInTreeOrder(node4_accessible, false));
EXPECT_EQ(node2_accessible,
manager->PreviousInTreeOrder(node3_accessible, false));
EXPECT_EQ(root_accessible,
manager->PreviousInTreeOrder(node2_accessible, false));
EXPECT_EQ(nullptr, manager->PreviousInTreeOrder(root_accessible, false));
EXPECT_EQ(nullptr, manager->PreviousInTreeOrder(nullptr, true));
EXPECT_EQ(node4_accessible,
manager->PreviousInTreeOrder(node5_accessible, true));
EXPECT_EQ(node3_accessible,
manager->PreviousInTreeOrder(node4_accessible, true));
EXPECT_EQ(node2_accessible,
manager->PreviousInTreeOrder(node3_accessible, true));
EXPECT_EQ(root_accessible,
manager->PreviousInTreeOrder(node2_accessible, true));
EXPECT_EQ(node5_accessible,
manager->PreviousInTreeOrder(root_accessible, true));
EXPECT_EQ(ax::mojom::TreeOrder::kEqual,
ui::BrowserAccessibilityManager::CompareNodes(*root_accessible,
*root_accessible));
EXPECT_EQ(ax::mojom::TreeOrder::kBefore,
ui::BrowserAccessibilityManager::CompareNodes(*node2_accessible,
*node3_accessible));
EXPECT_EQ(ax::mojom::TreeOrder::kAfter,
ui::BrowserAccessibilityManager::CompareNodes(*node3_accessible,
*node2_accessible));
EXPECT_EQ(ax::mojom::TreeOrder::kBefore,
ui::BrowserAccessibilityManager::CompareNodes(*node2_accessible,
*node4_accessible));
EXPECT_EQ(ax::mojom::TreeOrder::kAfter,
ui::BrowserAccessibilityManager::CompareNodes(*node4_accessible,
*node2_accessible));
EXPECT_EQ(ax::mojom::TreeOrder::kBefore,
ui::BrowserAccessibilityManager::CompareNodes(*node3_accessible,
*node4_accessible));
EXPECT_EQ(ax::mojom::TreeOrder::kAfter,
ui::BrowserAccessibilityManager::CompareNodes(*node4_accessible,
*node3_accessible));
EXPECT_EQ(ax::mojom::TreeOrder::kBefore,
ui::BrowserAccessibilityManager::CompareNodes(*root_accessible,
*node2_accessible));
EXPECT_EQ(ax::mojom::TreeOrder::kAfter,
ui::BrowserAccessibilityManager::CompareNodes(*node2_accessible,
*root_accessible));
}
TEST_F(BrowserAccessibilityManagerTest, TestNextNonDescendantInTreeOrder) {
ui::TestAXTreeUpdate update(std::string(R"HTML(
++1 kRootWebArea
++++2 kUnknown
++++3 kUnknown
++++++4 kUnknown
++++5 kUnknown
)HTML"));
std::unique_ptr<ui::BrowserAccessibilityManager> manager(
CreateBrowserAccessibilityManager(
update, node_id_delegate_,
test_browser_accessibility_delegate_.get()));
ui::BrowserAccessibility* root_accessible =
manager->GetBrowserAccessibilityRoot();
ASSERT_NE(nullptr, root_accessible);
ASSERT_EQ(3U, root_accessible->PlatformChildCount());
ui::BrowserAccessibility* node2_accessible =
root_accessible->PlatformGetChild(0);
ASSERT_NE(nullptr, node2_accessible);
ui::BrowserAccessibility* node3_accessible =
root_accessible->PlatformGetChild(1);
ASSERT_NE(nullptr, node3_accessible);
ASSERT_EQ(1U, node3_accessible->PlatformChildCount());
ui::BrowserAccessibility* node4_accessible =
node3_accessible->PlatformGetChild(0);
ASSERT_NE(nullptr, node4_accessible);
ui::BrowserAccessibility* node5_accessible =
root_accessible->PlatformGetChild(2);
ASSERT_NE(nullptr, node5_accessible);
EXPECT_EQ(nullptr, manager->NextNonDescendantInTreeOrder(nullptr));
EXPECT_EQ(node2_accessible, manager->NextInTreeOrder(root_accessible));
EXPECT_EQ(node3_accessible,
manager->NextNonDescendantInTreeOrder(node2_accessible));
EXPECT_EQ(node5_accessible,
manager->NextNonDescendantInTreeOrder(node3_accessible));
EXPECT_EQ(nullptr, manager->NextNonDescendantInTreeOrder(node5_accessible));
}
TEST_F(BrowserAccessibilityManagerTest, TestNextPreviousTextOnlyObject) {
ui::TestAXTreeUpdate update(std::string(R"HTML(
++1 kRootWebArea
++++2 kUnknown
++++3 kStaticText
++++4 kUnknown
++++++5 kStaticText
++++++6 kUnknown
++++++7 kStaticText
++++8 kGenericContainer
++++++9 kLineBreak
++++++10 kLink
)HTML"));
std::unique_ptr<ui::BrowserAccessibilityManager> manager(
CreateBrowserAccessibilityManager(
update, node_id_delegate_,
test_browser_accessibility_delegate_.get()));
ui::BrowserAccessibility* root_accessible =
manager->GetBrowserAccessibilityRoot();
ASSERT_NE(nullptr, root_accessible);
ASSERT_EQ(4U, root_accessible->PlatformChildCount());
ui::BrowserAccessibility* node2_accessible =
root_accessible->PlatformGetChild(0);
ASSERT_NE(nullptr, node2_accessible);
ui::BrowserAccessibility* text1_accessible =
root_accessible->PlatformGetChild(1);
ASSERT_NE(nullptr, text1_accessible);
ui::BrowserAccessibility* node3_accessible =
root_accessible->PlatformGetChild(2);
ASSERT_NE(nullptr, node3_accessible);
ASSERT_EQ(3U, node3_accessible->PlatformChildCount());
ui::BrowserAccessibility* text2_accessible =
node3_accessible->PlatformGetChild(0);
ASSERT_NE(nullptr, text2_accessible);
ui::BrowserAccessibility* node4_accessible =
node3_accessible->PlatformGetChild(1);
ASSERT_NE(nullptr, node4_accessible);
ui::BrowserAccessibility* text3_accessible =
node3_accessible->PlatformGetChild(2);
ASSERT_NE(nullptr, text3_accessible);
ui::BrowserAccessibility* node5_accessible =
root_accessible->PlatformGetChild(3);
ASSERT_NE(nullptr, node5_accessible);
ASSERT_EQ(2U, node5_accessible->PlatformChildCount());
ui::BrowserAccessibility* text4_accessible =
node5_accessible->PlatformGetChild(0);
ASSERT_NE(nullptr, text4_accessible);
EXPECT_EQ(nullptr, manager->NextTextOnlyObject(nullptr));
EXPECT_EQ(text1_accessible, manager->NextTextOnlyObject(root_accessible));
EXPECT_EQ(text1_accessible, manager->NextTextOnlyObject(node2_accessible));
EXPECT_EQ(text2_accessible, manager->NextTextOnlyObject(text1_accessible));
EXPECT_EQ(text2_accessible, manager->NextTextOnlyObject(node3_accessible));
EXPECT_EQ(text3_accessible, manager->NextTextOnlyObject(text2_accessible));
EXPECT_EQ(text3_accessible, manager->NextTextOnlyObject(node4_accessible));
EXPECT_EQ(text4_accessible, manager->NextTextOnlyObject(text3_accessible));
EXPECT_EQ(text4_accessible, manager->NextTextOnlyObject(node5_accessible));
EXPECT_EQ(nullptr, manager->NextTextOnlyObject(text4_accessible));
EXPECT_EQ(nullptr, manager->PreviousTextOnlyObject(nullptr));
EXPECT_EQ(text3_accessible,
manager->PreviousTextOnlyObject(text4_accessible));
EXPECT_EQ(text3_accessible,
manager->PreviousTextOnlyObject(node5_accessible));
EXPECT_EQ(text2_accessible,
manager->PreviousTextOnlyObject(text3_accessible));
EXPECT_EQ(text2_accessible,
manager->PreviousTextOnlyObject(node4_accessible));
EXPECT_EQ(text1_accessible,
manager->PreviousTextOnlyObject(text2_accessible));
EXPECT_EQ(text1_accessible,
manager->PreviousTextOnlyObject(node3_accessible));
EXPECT_EQ(nullptr, manager->PreviousTextOnlyObject(node2_accessible));
EXPECT_EQ(nullptr, manager->PreviousTextOnlyObject(root_accessible));
}
// This test depends on hypertext, which is only used on
// Linux and Windows.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(USE_ATK)
TEST_F(BrowserAccessibilityManagerTest, TestFindIndicesInCommonParent) {
ui::TestAXTreeUpdate update(std::string(R"HTML(
++1 kRootWebArea
++++2 kGenericContainer
++++++3 kButton
++++++++4 kStaticText name="Button"
++++++5 kLineBreak name="\n"
++++6 kParagraph
++++++7 kStaticText
++++++++8 kInlineTextBox name="Hello"
++++++++9 kInlineTextBox name="world."
)HTML"));
std::unique_ptr<ui::BrowserAccessibilityManager> manager(
CreateBrowserAccessibilityManager(
update, node_id_delegate_,
test_browser_accessibility_delegate_.get()));
ui::BrowserAccessibility* root_accessible =
manager->GetBrowserAccessibilityRoot();
ASSERT_NE(nullptr, root_accessible);
ASSERT_EQ(2U, root_accessible->PlatformChildCount());
ui::BrowserAccessibility* div_accessible =
root_accessible->PlatformGetChild(0);
ASSERT_NE(nullptr, div_accessible);
ASSERT_EQ(2U, div_accessible->PlatformChildCount());
ui::BrowserAccessibility* button_accessible =
div_accessible->PlatformGetChild(0);
ASSERT_NE(nullptr, button_accessible);
ASSERT_EQ(0U, button_accessible->PlatformChildCount());
ASSERT_EQ(1U, button_accessible->InternalChildCount());
ui::BrowserAccessibility* button_text_accessible =
button_accessible->InternalGetChild(0);
ASSERT_NE(nullptr, button_text_accessible);
ui::BrowserAccessibility* line_break_accessible =
div_accessible->PlatformGetChild(1);
ASSERT_NE(nullptr, line_break_accessible);
ui::BrowserAccessibility* paragraph_accessible =
root_accessible->PlatformGetChild(1);
ASSERT_NE(nullptr, paragraph_accessible);
ui::BrowserAccessibility* paragraph_text_accessible =
paragraph_accessible->PlatformGetChild(0);
ASSERT_NE(nullptr, paragraph_text_accessible);
ASSERT_EQ(2U, paragraph_text_accessible->InternalChildCount());
ui::BrowserAccessibility* paragraph_line1_accessible =
paragraph_text_accessible->InternalGetChild(0);
ASSERT_NE(nullptr, paragraph_line1_accessible);
ui::BrowserAccessibility* paragraph_line2_accessible =
paragraph_text_accessible->InternalGetChild(1);
ASSERT_NE(nullptr, paragraph_line2_accessible);
ui::BrowserAccessibility* common_parent = nullptr;
size_t child_index1, child_index2;
EXPECT_FALSE(ui::BrowserAccessibilityManager::FindIndicesInCommonParent(
*root_accessible, *root_accessible, &common_parent, &child_index1,
&child_index2));
EXPECT_TRUE(ui::BrowserAccessibilityManager::FindIndicesInCommonParent(
*div_accessible, *paragraph_accessible, &common_parent, &child_index1,
&child_index2));
EXPECT_EQ(root_accessible, common_parent);
EXPECT_EQ(0u, child_index1);
EXPECT_EQ(1u, child_index2);
EXPECT_TRUE(ui::BrowserAccessibilityManager::FindIndicesInCommonParent(
*div_accessible, *paragraph_line1_accessible, &common_parent,
&child_index1, &child_index2));
EXPECT_EQ(root_accessible, common_parent);
EXPECT_EQ(0u, child_index1);
EXPECT_EQ(1u, child_index2);
EXPECT_TRUE(ui::BrowserAccessibilityManager::FindIndicesInCommonParent(
*line_break_accessible, *paragraph_text_accessible, &common_parent,
&child_index1, &child_index2));
EXPECT_EQ(root_accessible, common_parent);
EXPECT_EQ(0u, child_index1);
EXPECT_EQ(1u, child_index2);
EXPECT_TRUE(ui::BrowserAccessibilityManager::FindIndicesInCommonParent(
*button_text_accessible, *line_break_accessible, &common_parent,
&child_index1, &child_index2));
EXPECT_EQ(div_accessible, common_parent);
EXPECT_EQ(0u, child_index1);
EXPECT_EQ(1u, child_index2);
EXPECT_TRUE(ui::BrowserAccessibilityManager::FindIndicesInCommonParent(
*paragraph_accessible, *paragraph_line2_accessible, &common_parent,
&child_index1, &child_index2));
EXPECT_EQ(root_accessible, common_parent);
EXPECT_EQ(1u, child_index1);
EXPECT_EQ(1u, child_index2);
EXPECT_TRUE(ui::BrowserAccessibilityManager::FindIndicesInCommonParent(
*paragraph_text_accessible, *paragraph_line1_accessible, &common_parent,
&child_index1, &child_index2));
EXPECT_EQ(paragraph_accessible, common_parent);
EXPECT_EQ(0u, child_index1);
EXPECT_EQ(0u, child_index2);
EXPECT_TRUE(ui::BrowserAccessibilityManager::FindIndicesInCommonParent(
*paragraph_line1_accessible, *paragraph_line2_accessible, &common_parent,
&child_index1, &child_index2));
EXPECT_EQ(paragraph_text_accessible, common_parent);
EXPECT_EQ(0u, child_index1);
EXPECT_EQ(1u, child_index2);
}
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(USE_ATK)
// This test depends on hypertext, which is only used on
// Linux and Windows.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(USE_ATK)
TEST_F(BrowserAccessibilityManagerTest, TestGetTextForRange) {
ui::AXNodeData root;
root.id = 1;
root.role = ax::mojom::Role::kRootWebArea;
ui::AXNodeData div;
div.id = 2;
div.role = ax::mojom::Role::kGenericContainer;
root.child_ids.push_back(div.id);
ui::AXNodeData button;
button.id = 3;
button.role = ax::mojom::Role::kButton;
div.child_ids.push_back(button.id);
ui::AXNodeData button_text;
button_text.id = 4;
button_text.role = ax::mojom::Role::kStaticText;
button_text.SetName("Button");
button.child_ids.push_back(button_text.id);
ui::AXNodeData container;
container.id = 5;
container.role = ax::mojom::Role::kGenericContainer;
div.child_ids.push_back(container.id);
ui::AXNodeData container_text;
container_text.id = 6;
container_text.role = ax::mojom::Role::kStaticText;
container_text.SetName("Text");
container.child_ids.push_back(container_text.id);
ui::AXNodeData line_break;
line_break.id = 7;
line_break.role = ax::mojom::Role::kLineBreak;
line_break.SetName("\n");
div.child_ids.push_back(line_break.id);
ui::AXNodeData paragraph;
paragraph.id = 8;
paragraph.role = ax::mojom::Role::kParagraph;
root.child_ids.push_back(paragraph.id);
ui::AXNodeData paragraph_text;
paragraph_text.id = 9;
paragraph_text.role = ax::mojom::Role::kStaticText;
paragraph_text.SetName("Hello world.");
paragraph.child_ids.push_back(paragraph_text.id);
ui::AXNodeData paragraph_line1;
paragraph_line1.id = 10;
paragraph_line1.role = ax::mojom::Role::kInlineTextBox;
paragraph_line1.SetName("Hello ");
paragraph_text.child_ids.push_back(paragraph_line1.id);
ui::AXNodeData paragraph_line2;
paragraph_line2.id = 11;
paragraph_line2.role = ax::mojom::Role::kInlineTextBox;
paragraph_line2.SetName("world.");
paragraph_text.child_ids.push_back(paragraph_line2.id);
std::unique_ptr<ui::BrowserAccessibilityManager> manager(
CreateBrowserAccessibilityManager(
MakeAXTreeUpdateForTesting(root, div, button, button_text, container,
container_text, line_break, paragraph,
paragraph_text, paragraph_line1,
paragraph_line2),
node_id_delegate_, test_browser_accessibility_delegate_.get()));
ui::BrowserAccessibility* root_accessible =
manager->GetBrowserAccessibilityRoot();
ASSERT_NE(nullptr, root_accessible);
ASSERT_EQ(2U, root_accessible->PlatformChildCount());
ui::BrowserAccessibility* div_accessible =
root_accessible->PlatformGetChild(0);
ASSERT_NE(nullptr, div_accessible);
ASSERT_EQ(3U, div_accessible->PlatformChildCount());
ui::BrowserAccessibility* button_accessible =
div_accessible->PlatformGetChild(0);
ASSERT_NE(nullptr, button_accessible);
ASSERT_EQ(0U, button_accessible->PlatformChildCount());
ASSERT_EQ(1U, button_accessible->InternalChildCount());
ui::BrowserAccessibility* button_text_accessible =
button_accessible->InternalGetChild(0);
ASSERT_NE(nullptr, button_text_accessible);
ui::BrowserAccessibility* container_accessible =
div_accessible->PlatformGetChild(1);
ASSERT_NE(nullptr, container_accessible);
ui::BrowserAccessibility* container_text_accessible =
container_accessible->PlatformGetChild(0);
ASSERT_NE(nullptr, container_text_accessible);
ui::BrowserAccessibility* line_break_accessible =
div_accessible->PlatformGetChild(2);
ASSERT_NE(nullptr, line_break_accessible);
ui::BrowserAccessibility* paragraph_accessible =
root_accessible->PlatformGetChild(1);
ASSERT_NE(nullptr, paragraph_accessible);
ui::BrowserAccessibility* paragraph_text_accessible =
paragraph_accessible->PlatformGetChild(0);
ASSERT_NE(nullptr, paragraph_text_accessible);
ASSERT_EQ(2U, paragraph_text_accessible->InternalChildCount());
ui::BrowserAccessibility* paragraph_line1_accessible =
paragraph_text_accessible->InternalGetChild(0);
ASSERT_NE(nullptr, paragraph_line1_accessible);
ui::BrowserAccessibility* paragraph_line2_accessible =
paragraph_text_accessible->InternalGetChild(1);
ASSERT_NE(nullptr, paragraph_line2_accessible);
std::vector<const ui::BrowserAccessibility*> text_only_objects =
ui::BrowserAccessibilityManager::FindTextOnlyObjectsInRange(
*root_accessible, *root_accessible);
EXPECT_EQ(3U, text_only_objects.size());
EXPECT_EQ(container_text_accessible, text_only_objects[0]);
EXPECT_EQ(line_break_accessible, text_only_objects[1]);
EXPECT_EQ(paragraph_text_accessible, text_only_objects[2]);
text_only_objects =
ui::BrowserAccessibilityManager::FindTextOnlyObjectsInRange(
*div_accessible, *paragraph_accessible);
EXPECT_EQ(3U, text_only_objects.size());
EXPECT_EQ(container_text_accessible, text_only_objects[0]);
EXPECT_EQ(line_break_accessible, text_only_objects[1]);
EXPECT_EQ(paragraph_text_accessible, text_only_objects[2]);
EXPECT_EQ(u"Text\nHello world.",
ui::BrowserAccessibilityManager::GetTextForRange(
*root_accessible, 0, *root_accessible, 16));
EXPECT_EQ(u"xt\nHello world.",
ui::BrowserAccessibilityManager::GetTextForRange(
*root_accessible, 2, *root_accessible, 12));
EXPECT_EQ(u"Text\nHello world.",
ui::BrowserAccessibilityManager::GetTextForRange(
*div_accessible, 0, *paragraph_accessible, 12));
EXPECT_EQ(u"xt\nHello world.",
ui::BrowserAccessibilityManager::GetTextForRange(
*div_accessible, 2, *paragraph_accessible, 12));
EXPECT_EQ(u"Text\n", ui::BrowserAccessibilityManager::GetTextForRange(
*div_accessible, 0, *div_accessible, 4));
EXPECT_EQ(u"Text\n", ui::BrowserAccessibilityManager::GetTextForRange(
*button_accessible, 0, *line_break_accessible, 4));
EXPECT_EQ(u"Hello world.",
ui::BrowserAccessibilityManager::GetTextForRange(
*paragraph_accessible, 0, *paragraph_accessible, 12));
EXPECT_EQ(u"Hello wor",
ui::BrowserAccessibilityManager::GetTextForRange(
*paragraph_accessible, 0, *paragraph_accessible, 9));
EXPECT_EQ(u"Hello world.",
ui::BrowserAccessibilityManager::GetTextForRange(
*paragraph_text_accessible, 0, *paragraph_text_accessible, 12));
EXPECT_EQ(u" world.",
ui::BrowserAccessibilityManager::GetTextForRange(
*paragraph_text_accessible, 5, *paragraph_text_accessible, 12));
EXPECT_EQ(u"Hello world.",
ui::BrowserAccessibilityManager::GetTextForRange(
*paragraph_accessible, 0, *paragraph_text_accessible, 12));
EXPECT_EQ(u"Hello ", ui::BrowserAccessibilityManager::GetTextForRange(
*paragraph_line1_accessible, 0,
*paragraph_line1_accessible, 6));
EXPECT_EQ(u"Hello", ui::BrowserAccessibilityManager::GetTextForRange(
*paragraph_line1_accessible, 0,
*paragraph_line1_accessible, 5));
EXPECT_EQ(u"ello ", ui::BrowserAccessibilityManager::GetTextForRange(
*paragraph_line1_accessible, 1,
*paragraph_line1_accessible, 6));
EXPECT_EQ(u"world.", ui::BrowserAccessibilityManager::GetTextForRange(
*paragraph_line2_accessible, 0,
*paragraph_line2_accessible, 6));
EXPECT_EQ(u"orld", ui::BrowserAccessibilityManager::GetTextForRange(
*paragraph_line2_accessible, 1,
*paragraph_line2_accessible, 5));
EXPECT_EQ(u"Hello world.", ui::BrowserAccessibilityManager::GetTextForRange(
*paragraph_line1_accessible, 0,
*paragraph_line2_accessible, 6));
// Start and end positions could be reversed.
EXPECT_EQ(u"Hello world.", ui::BrowserAccessibilityManager::GetTextForRange(
*paragraph_line2_accessible, 6,
*paragraph_line1_accessible, 0));
}
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(USE_ATK)
TEST_F(BrowserAccessibilityManagerTest, DeletingFocusedNodeDoesNotCrash) {
// Create a really simple tree with one root node and one focused child.
ui::AXNodeData root;
root.id = 1;
root.role = ax::mojom::Role::kRootWebArea;
root.child_ids.push_back(2);
ui::AXNodeData node2;
node2.id = 2;
ui::AXTreeUpdate initial_state = MakeAXTreeUpdateForTesting(root, node2);
initial_state.has_tree_data = true;
initial_state.tree_data.focus_id = 2;
std::unique_ptr<ui::BrowserAccessibilityManager> manager(
CreateBrowserAccessibilityManager(
initial_state, node_id_delegate_,
test_browser_accessibility_delegate_.get()));
EXPECT_EQ(1, manager->GetBrowserAccessibilityRoot()->GetId());
ASSERT_NE(nullptr, manager->GetFocus());
EXPECT_EQ(2, manager->GetFocus()->GetId());
// Now replace the tree with a new tree consisting of a single root.
ui::AXNodeData root2;
root2.id = 3;
root2.role = ax::mojom::Role::kRootWebArea;
ui::AXTreeUpdate update2 = MakeAXTreeUpdateForTesting(root2);
update2.tree_data.tree_id = initial_state.tree_data.tree_id;
update2.node_id_to_clear = root.id;
update2.root_id = root2.id;
ui::AXUpdatesAndEvents events2;
events2.updates = {update2};
ASSERT_TRUE(manager->OnAccessibilityEvents(events2));
// Make sure that the focused node was updated to the new root and
// that this doesn't crash.
EXPECT_EQ(3, manager->GetBrowserAccessibilityRoot()->GetId());
ASSERT_NE(nullptr, manager->GetFocus());
EXPECT_EQ(3, manager->GetFocus()->GetId());
}
TEST_F(BrowserAccessibilityManagerTest, DeletingFocusedNodeDoesNotCrash2) {
// Create a really simple tree with one root node and one focused child.
ui::AXNodeData root;
root.id = 1;
root.role = ax::mojom::Role::kRootWebArea;
root.child_ids.push_back(2);
root.child_ids.push_back(3);
root.child_ids.push_back(4);
ui::AXNodeData node2;
node2.id = 2;
ui::AXNodeData node3;
node3.id = 3;
ui::AXNodeData node4;
node4.id = 4;
ui::AXTreeUpdate initial_state =
MakeAXTreeUpdateForTesting(root, node2, node3, node4);
initial_state.has_tree_data = true;
initial_state.tree_data.focus_id = 2;
std::unique_ptr<ui::BrowserAccessibilityManager> manager(
CreateBrowserAccessibilityManager(
initial_state, node_id_delegate_,
test_browser_accessibility_delegate_.get()));
EXPECT_EQ(1, manager->GetBrowserAccessibilityRoot()->GetId());
ASSERT_NE(nullptr, manager->GetFocus());
EXPECT_EQ(2, manager->GetFocus()->GetId());
// Now replace the tree with a new tree consisting of a single root.
ui::AXNodeData root2;
root2.id = 3;
root2.role = ax::mojom::Role::kRootWebArea;
// Make an update that explicitly clears the previous root.
ui::AXTreeUpdate update2 = MakeAXTreeUpdateForTesting(root2);
update2.tree_data.tree_id = initial_state.tree_data.tree_id;
update2.node_id_to_clear = root.id;
update2.root_id = root2.id;
ui::AXUpdatesAndEvents events2;
events2.updates = {update2};
ASSERT_TRUE(manager->OnAccessibilityEvents(events2));
// Make sure that the focused node was updated to the new root and
// that this doesn't crash.
EXPECT_EQ(3, manager->GetBrowserAccessibilityRoot()->GetId());
ASSERT_NE(nullptr, manager->GetFocus());
EXPECT_EQ(3, manager->GetFocus()->GetId());
}
TEST_F(BrowserAccessibilityManagerTest, TreeUpdatesAreMergedWhenPossible) {
ui::AXTreeUpdate tree;
tree.root_id = 1;
tree.nodes.resize(4);
tree.nodes[0].id = 1;
tree.nodes[0].role = ax::mojom::Role::kMenu;
tree.nodes[0].child_ids = {2, 3, 4};
tree.nodes[1].id = 2;
tree.nodes[1].role = ax::mojom::Role::kMenuItem;
tree.nodes[2].id = 3;
tree.nodes[2].role = ax::mojom::Role::kMenuItemCheckBox;
tree.nodes[3].id = 4;
tree.nodes[3].role = ax::mojom::Role::kMenuItemRadio;
CountingAXTreeObserver observer;
std::unique_ptr<ui::BrowserAccessibilityManager> manager(
CreateBrowserAccessibilityManager(
tree, node_id_delegate_, test_browser_accessibility_delegate_.get()));
base::ScopedObservation<ui::AXTree, ui::AXTreeObserver> observation(
&observer);
observation.Observe(manager->ax_tree());
// Update each of the children using separate AXTreeUpdates.
ui::AXUpdatesAndEvents events;
events.updates.resize(3);
for (int i = 0; i < 3; i++) {
ui::AXTreeUpdate update;
update.root_id = 1;
update.nodes.resize(1);
update.nodes[0].id = 2 + i;
events.updates[i] = update;
}
events.updates[0].nodes[0].role = ax::mojom::Role::kMenuItemCheckBox;
events.updates[1].nodes[0].role = ax::mojom::Role::kMenuItemRadio;
events.updates[2].nodes[0].role = ax::mojom::Role::kMenuItem;
ASSERT_TRUE(manager->OnAccessibilityEvents(events));
// These should have been merged into a single tree update.
EXPECT_EQ(1, observer.update_count());
EXPECT_EQ(ax::mojom::Role::kMenuItemCheckBox,
manager->GetFromID(2)->GetRole());
EXPECT_EQ(ax::mojom::Role::kMenuItemRadio, manager->GetFromID(3)->GetRole());
EXPECT_EQ(ax::mojom::Role::kMenuItem, manager->GetFromID(4)->GetRole());
}
TEST_F(BrowserAccessibilityManagerTest, TestHitTestScaled) {
// We're creating two linked trees, each of which has two nodes; first create
// the child tree's nodes & tree-update.
ui::AXNodeData child_root;
child_root.id = 1;
child_root.role = ax::mojom::Role::kRootWebArea;
child_root.SetName("child_root");
child_root.relative_bounds.bounds = gfx::RectF(0, 0, 100, 100);
child_root.child_ids = {2};
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);
ui::AXTreeUpdate child_update =
MakeAXTreeUpdateForTesting(child_root, child_child);
// Next, create the parent tree's nodes and tree-update, with kChildTreeId
// pointing to the child tree.
ui::AXNodeData parent_root;
parent_root.id = 1;
parent_root.role = ax::mojom::Role::kRootWebArea;
parent_root.SetName("parent_root");
parent_root.relative_bounds.bounds = gfx::RectF(0, 0, 200, 200);
parent_root.child_ids = {2, 3};
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);
ui::AXNodeData parent_childtree;
parent_childtree.id = 3;
parent_childtree.AddChildTreeId(child_update.tree_data.tree_id);
parent_childtree.role = ax::mojom::Role::kGenericContainer;
parent_childtree.SetName("parent_childtree");
parent_childtree.relative_bounds.bounds = gfx::RectF(100, 100, 100, 100);
ui::AXTreeUpdate parent_update =
MakeAXTreeUpdateForTesting(parent_root, parent_child, parent_childtree);
// Link the child trees to their parent trees.
child_update.tree_data.parent_tree_id = parent_update.tree_data.tree_id;
// Create the two managers.
std::unique_ptr<ui::BrowserAccessibilityManager> parent_manager(
CreateBrowserAccessibilityManager(parent_update, node_id_delegate_,
nullptr));
std::unique_ptr<ui::BrowserAccessibilityManager> child_manager(
CreateBrowserAccessibilityManager(child_update, node_id_delegate_,
nullptr));
ASSERT_EQ(parent_manager.get(), child_manager->GetManagerForRootFrame());
// Set scaling factor for testing to be 200%
parent_manager->UseCustomDeviceScaleFactorForTesting(2.0f);
child_manager->UseCustomDeviceScaleFactorForTesting(2.0f);
// Run the hit-test; we should get the same result regardless of whether we
// start from the parent_manager or the child_manager.
auto* hittest1 = parent_manager->CachingAsyncHitTest(gfx::Point(75, 75));
ASSERT_NE(nullptr, hittest1);
ASSERT_EQ("parent_child",
hittest1->GetStringAttribute(ax::mojom::StringAttribute::kName));
auto* hittest2 = child_manager->CachingAsyncHitTest(gfx::Point(75, 75));
ASSERT_NE(nullptr, hittest2);
ASSERT_EQ("parent_child",
hittest2->GetStringAttribute(ax::mojom::StringAttribute::kName));
}
TEST_F(BrowserAccessibilityManagerTest, TestShouldFireEventForNode) {
ui::TestAXTreeUpdate update(std::string(R"HTML(
++1 kRootWebArea
++++11 kParagraph
++++++111 kStaticText
++++++++1111 kInlineTextBox
)HTML"));
update.nodes[2].SetName("One two three.");
update.nodes[3].SetName("One two three.");
std::unique_ptr<ui::BrowserAccessibilityManager> manager(
CreateBrowserAccessibilityManager(
update, node_id_delegate_,
test_browser_accessibility_delegate_.get()));
EXPECT_TRUE(manager->ShouldFireEventForNode(manager->GetFromID(1)));
EXPECT_TRUE(manager->ShouldFireEventForNode(manager->GetFromID(11)));
EXPECT_TRUE(manager->ShouldFireEventForNode(manager->GetFromID(111)));
#if BUILDFLAG(IS_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) {
ui::AXNodeData root;
root.id = 1;
root.role = ax::mojom::Role::kRootWebArea;
ui::AXNodeData popup_button;
popup_button.id = 2;
popup_button.role = ax::mojom::Role::kPopUpButton;
root.child_ids.push_back(2);
ui::AXNodeData child_tree_root;
child_tree_root.id = 3;
child_tree_root.role = ax::mojom::Role::kGroup;
child_tree_root.AddIntAttribute(ax::mojom::IntAttribute::kPopupForId, 2);
popup_button.child_ids.push_back(3);
std::unique_ptr<ui::BrowserAccessibilityManager> manager(
CreateBrowserAccessibilityManager(
MakeAXTreeUpdateForTesting(root, popup_button, child_tree_root),
node_id_delegate_, test_browser_accessibility_delegate_.get()));
ASSERT_NE(manager->GetPopupRoot(), nullptr);
EXPECT_EQ(manager->GetPopupRoot()->GetId(), 3);
// Test deleting child root.
// Now remove the child root from the tree.
popup_button.child_ids = {};
ui::AXTreeUpdate update = MakeAXTreeUpdateForTesting(popup_button);
update.tree_data.tree_id = manager->GetTreeID();
ui::AXUpdatesAndEvents events;
events.updates = {update};
ASSERT_TRUE(manager->OnAccessibilityEvents(events));
EXPECT_EQ(manager->GetPopupRoot(), nullptr);
}
TEST_F(BrowserAccessibilityManagerTest, TestApproximateHitTestCache) {
ui::AXNodeData root;
root.id = 1;
root.role = ax::mojom::Role::kRootWebArea;
root.SetName("root");
root.relative_bounds.bounds = gfx::RectF(0, 0, 200, 200);
root.child_ids = {2, 3};
ui::AXNodeData child1;
child1.id = 2;
child1.role = ax::mojom::Role::kGenericContainer;
child1.SetName("child1");
child1.relative_bounds.bounds = gfx::RectF(0, 0, 100, 100);
ui::AXNodeData child2;
child2.id = 3;
child2.role = ax::mojom::Role::kGenericContainer;
child2.SetName("child2");
child2.relative_bounds.bounds = gfx::RectF(50, 50, 50, 50);
ui::AXTreeUpdate update = MakeAXTreeUpdateForTesting(root, child1, child2);
// Create manager.
std::unique_ptr<ui::BrowserAccessibilityManager> manager(
CreateBrowserAccessibilityManager(
update, node_id_delegate_,
test_browser_accessibility_delegate_.get()));
manager->BuildAXTreeHitTestCache();
auto* hittest1 = manager->ApproximateHitTest(gfx::Point(1, 1));
ASSERT_NE(nullptr, hittest1);
ASSERT_EQ("child1",
hittest1->GetStringAttribute(ax::mojom::StringAttribute::kName));
auto* hittest2 = manager->CachingAsyncHitTest(gfx::Point(75, 75));
ASSERT_NE(nullptr, hittest2);
ASSERT_EQ("child2",
hittest2->GetStringAttribute(ax::mojom::StringAttribute::kName));
}
TEST_F(BrowserAccessibilityManagerTest, TestOnNodeReparented) {
ui::AXNodeData root;
root.id = 1;
root.role = ax::mojom::Role::kRootWebArea;
ui::AXNodeData child1;
child1.role = ax::mojom::Role::kGenericContainer;
child1.id = 2;
ui::AXNodeData child2;
child2.role = ax::mojom::Role::kGenericContainer;
child2.id = 3;
root.child_ids = {child1.id, child2.id};
const ui::AXTreeUpdate update1 =
MakeAXTreeUpdateForTesting(root, child1, child2);
CountingAXTreeObserver observer;
std::unique_ptr<ui::BrowserAccessibilityManager> manager(
CreateBrowserAccessibilityManager(
update1, node_id_delegate_,
test_browser_accessibility_delegate_.get()));
base::ScopedObservation<ui::AXTree, ui::AXTreeObserver> observation(
&observer);
observation.Observe(manager->ax_tree());
ASSERT_EQ(0, observer.reparent_count());
ASSERT_EQ(0, observer.node_count());
// Reparenting a child found in the tree should not crash.
root.child_ids = {child1.id};
child1.child_ids = {child2.id};
ui::AXTreeUpdate update2 = MakeAXTreeUpdateForTesting(root, child1, child2);
update2.tree_data.tree_id = update1.tree_data.tree_id;
manager->ax_tree()->Unserialize(update2);
EXPECT_EQ(1, observer.reparent_count());
EXPECT_EQ(3, observer.node_count());
// Reparenting a new child that is not found in the tree should trigger a
// DCHECK in AX_FAIL_FAST_BUILD builds, otherwise it should not crash.
ui::AXNode child3(manager->ax_tree(), /* parent */ nullptr, /* id */ 4,
/* index_in_parent */ 0u);
#if AX_FAIL_FAST_BUILD()
EXPECT_DEATH_IF_SUPPORTED(
manager->OnNodeReparented(manager->ax_tree(), &child3),
"Missing BrowserAccessibility");
#else
manager->OnNodeReparented(manager->ax_tree(), &child3);
#endif
// We avoid checking the observer on purpose, since reparenting a non-existent
// node should not trigger any tree observers. The node is not in the tree,
// hence the normal tree update process cannot be followed.
}
} // namespace content