blob: ab4aad754ce296e1478c71a0826072587bee9ff6 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Chromium cannot upgrade to ATK 2.12 API as it still needs to run
// valid builds for Ubuntu Trusty.
// TODO(accessibility): Remove this when Chromium drops support for ATK
// older than 2.12.
#define ATK_DISABLE_DEPRECATION_WARNINGS
#include <atk/atk.h>
#include "base/bind_helpers.h"
#include "base/macros.h"
#include "content/browser/accessibility/accessibility_browsertest.h"
#include "content/browser/accessibility/browser_accessibility.h"
#include "content/browser/renderer_host/render_widget_host_view_aura.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/accessibility_browser_test_utils.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "ui/accessibility/platform/ax_platform_node_auralinux.h"
namespace content {
class AccessibilityAuraLinuxBrowserTest : public AccessibilityBrowserTest {
public:
AccessibilityAuraLinuxBrowserTest() = default;
~AccessibilityAuraLinuxBrowserTest() override = default;
protected:
static bool HasObjectWithAtkRoleFrameInAncestry(AtkObject* object) {
while (object) {
if (atk_object_get_role(object) == ATK_ROLE_FRAME)
return true;
object = atk_object_get_parent(object);
}
return false;
}
static void CheckTextAtOffset(AtkText* text_object,
int offset,
AtkTextBoundary boundary_type,
int expected_start_offset,
int expected_end_offset,
const char* expected_text);
AtkText* SetUpInputField();
AtkText* SetUpTextareaField();
AtkText* SetUpSampleParagraph();
AtkText* SetUpSampleParagraphInScrollableEditable();
AtkText* GetAtkTextForChild(AtkRole expected_role);
private:
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);
return atk_text;
}
// Loads a page with an input text field and places sample text in it.
AtkText* AccessibilityAuraLinuxBrowserTest::SetUpInputField() {
LoadInputField();
return GetAtkTextForChild(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);
}
// Loads a page with a paragraph of sample text.
AtkText* AccessibilityAuraLinuxBrowserTest::SetUpSampleParagraph() {
LoadSampleParagraph();
AtkObject* document = GetRendererAccessible();
EXPECT_EQ(1, atk_object_get_n_accessible_children(document));
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));
EXPECT_TRUE(ATK_IS_TEXT(input));
return ATK_TEXT(input);
}
AtkText*
AccessibilityAuraLinuxBrowserTest::SetUpSampleParagraphInScrollableEditable() {
LoadSampleParagraphInScrollableEditable();
AtkObject* document = GetRendererAccessible();
EXPECT_EQ(1, atk_object_get_n_accessible_children(document));
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));
EXPECT_TRUE(ATK_IS_TEXT(input));
return ATK_TEXT(input);
}
// 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 for \'" << expected_text << "\' at "
<< expected_start_offset << '-' << expected_end_offset << '.';
SCOPED_TRACE(message);
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);
}
IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
AuraLinuxBrowserAccessibleParent) {
AccessibilityNotificationWaiter waiter(shell()->web_contents(),
ui::kAXModeComplete,
ax::mojom::Event::kLoadComplete);
NavigateToURL(shell(), GURL("data:text/html,"));
waiter.WaitForNotification();
// Get the BrowserAccessibilityManager.
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
BrowserAccessibilityManager* manager =
web_contents->GetRootBrowserAccessibilityManager();
ASSERT_NE(nullptr, manager);
auto* host_view = static_cast<RenderWidgetHostViewAura*>(
web_contents->GetRenderWidgetHostView());
ASSERT_NE(nullptr, host_view->GetNativeViewAccessible());
AtkObject* host_view_parent =
host_view->AccessibilityGetNativeViewAccessible();
ASSERT_NE(nullptr, host_view_parent);
ASSERT_TRUE(
AccessibilityAuraLinuxBrowserTest::HasObjectWithAtkRoleFrameInAncestry(
host_view_parent));
}
IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
TestTextAtOffsetWithBoundaryLine) {
AtkText* atk_text = SetUpInputField();
// Single line text fields should return the whole text.
CheckTextAtOffset(atk_text, 0, ATK_TEXT_BOUNDARY_LINE_START, 0,
InputContentsString().size(),
InputContentsString().c_str());
}
IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
TestMultiLineTextAtOffsetWithBoundaryLine) {
AtkText* atk_text = SetUpTextareaField();
CheckTextAtOffset(atk_text, 0, ATK_TEXT_BOUNDARY_LINE_START, 0, 24,
"Moz/5.0 (ST 6.x; WWW33)\n");
// If the offset is at the newline, return the line preceding it.
CheckTextAtOffset(atk_text, 31, ATK_TEXT_BOUNDARY_LINE_START, 24, 32,
"WebKit \n");
// Last line does not have a trailing newline.
CheckTextAtOffset(atk_text, 32, ATK_TEXT_BOUNDARY_LINE_START, 32,
InputContentsString().size(), "\"KHTML, like\".");
}
IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
TestParagraphTextAtOffsetWithBoundaryLine) {
AtkText* atk_text = SetUpSampleParagraph();
// There should be two lines in this paragraph.
const int newline_offset = 46;
int n_characters = atk_text_get_character_count(atk_text);
ASSERT_LT(newline_offset, n_characters);
const base::string16 string16_embed(
1, ui::AXPlatformNodeAuraLinux::kEmbeddedCharacter);
std::string expected_string = "Game theory is \"the study of " +
base::UTF16ToUTF8(string16_embed) +
" of conflict and\n";
for (int i = 0; i <= newline_offset; ++i) {
CheckTextAtOffset(atk_text, i, ATK_TEXT_BOUNDARY_LINE_START, 0,
newline_offset + 1, expected_string.c_str());
}
for (int i = newline_offset + 1; i < n_characters; ++i) {
CheckTextAtOffset(
atk_text, i, ATK_TEXT_BOUNDARY_LINE_START, newline_offset + 1,
n_characters,
"cooperation between intelligent rational decision-makers.\"");
}
}
#if defined(ATK_CHECK_VERSION) && ATK_CHECK_VERSION(2, 30, 0)
#define ATK_230
#endif
IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
TestCharacterExtentsWithInvalidArguments) {
AtkText* atk_text = SetUpSampleParagraph();
int invalid_offset = -3;
int x = -1, y = -1;
int width = -1, height = -1;
atk_text_get_character_extents(atk_text, invalid_offset, &x, &y, &width,
&height, ATK_XY_SCREEN);
EXPECT_EQ(0, x);
EXPECT_EQ(0, y);
EXPECT_EQ(0, width);
EXPECT_EQ(0, height);
#ifdef ATK_230
atk_text_get_character_extents(atk_text, invalid_offset, &x, &y, &width,
&height, ATK_XY_PARENT);
EXPECT_EQ(0, x);
EXPECT_EQ(0, y);
EXPECT_EQ(0, width);
EXPECT_EQ(0, height);
#endif // ATK_230
atk_text_get_character_extents(atk_text, invalid_offset, &x, &y, &width,
&height, ATK_XY_WINDOW);
EXPECT_EQ(0, x);
EXPECT_EQ(0, y);
EXPECT_EQ(0, width);
EXPECT_EQ(0, height);
int n_characters = atk_text_get_character_count(atk_text);
ASSERT_LT(0, n_characters);
atk_text_get_character_extents(atk_text, invalid_offset, &x, &y, &width,
&height, ATK_XY_SCREEN);
EXPECT_EQ(0, x);
EXPECT_EQ(0, y);
EXPECT_EQ(0, width);
EXPECT_EQ(0, height);
#ifdef ATK_230
atk_text_get_character_extents(atk_text, invalid_offset, &x, &y, &width,
&height, ATK_XY_PARENT);
EXPECT_EQ(0, x);
EXPECT_EQ(0, y);
EXPECT_EQ(0, width);
EXPECT_EQ(0, height);
#endif // ATK_230
atk_text_get_character_extents(atk_text, invalid_offset, &x, &y, &width,
&height, ATK_XY_WINDOW);
EXPECT_EQ(0, x);
EXPECT_EQ(0, y);
EXPECT_EQ(0, width);
EXPECT_EQ(0, height);
}
AtkCoordType kCoordinateTypes[] = {
ATK_XY_SCREEN,
ATK_XY_WINDOW,
#ifdef ATK_230
ATK_XY_PARENT,
#endif // ATK_230
};
IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
TestCharacterExtentsInEditable) {
AtkText* atk_text = SetUpSampleParagraph();
constexpr int newline_offset = 46;
int n_characters = atk_text_get_character_count(atk_text);
ASSERT_EQ(105, n_characters);
int x, y, width, height;
int previous_x, previous_y, previous_height;
for (AtkCoordType coordinate_type : kCoordinateTypes) {
atk_text_get_character_extents(atk_text, 0, &x, &y, &width, &height,
coordinate_type);
EXPECT_LT(0, x) << "at offset 0";
EXPECT_LT(0, y) << "at offset 0";
EXPECT_LT(1, width) << "at offset 0";
EXPECT_LT(1, height) << "at offset 0";
gfx::Rect combined_extents(x, y, width, height);
for (int offset = 1; offset < newline_offset; ++offset) {
testing::Message message;
message << "While checking at offset " << offset;
SCOPED_TRACE(message);
previous_x = x;
previous_y = y;
previous_height = height;
atk_text_get_character_extents(atk_text, offset, &x, &y, &width, &height,
coordinate_type);
EXPECT_LT(previous_x, x);
EXPECT_EQ(previous_y, y);
EXPECT_LT(1, width);
EXPECT_EQ(previous_height, height);
combined_extents.Union(gfx::Rect(x, y, width, height));
atk_text_get_character_extents(atk_text, offset, &x, &y, &width, &height,
coordinate_type);
AtkTextRectangle atk_rect;
atk_text_get_range_extents(atk_text, 0, offset + 1, coordinate_type,
&atk_rect);
EXPECT_EQ(combined_extents.x(), atk_rect.x);
EXPECT_EQ(combined_extents.y(), atk_rect.y);
EXPECT_EQ(combined_extents.width(), atk_rect.width);
EXPECT_EQ(combined_extents.height(), atk_rect.height);
}
{
testing::Message message;
message << "While checking at offset " << newline_offset + 1;
SCOPED_TRACE(message);
atk_text_get_character_extents(atk_text, newline_offset + 1, &x, &y,
&width, &height, coordinate_type);
EXPECT_LE(0, x);
EXPECT_GT(previous_x, x);
EXPECT_LT(previous_y, y);
EXPECT_LT(1, width);
EXPECT_EQ(previous_height, height);
}
combined_extents = gfx::Rect(x, y, width, height);
for (int offset = newline_offset + 2; offset < n_characters; ++offset) {
testing::Message message;
message << "While checking at offset " << offset;
SCOPED_TRACE(message);
previous_x = x;
previous_y = y;
previous_height = height;
atk_text_get_character_extents(atk_text, offset, &x, &y, &width, &height,
coordinate_type);
EXPECT_LT(previous_x, x);
EXPECT_EQ(previous_y, y);
EXPECT_LT(1, width);
EXPECT_EQ(previous_height, height);
combined_extents.Union(gfx::Rect(x, y, width, height));
atk_text_get_character_extents(atk_text, offset, &x, &y, &width, &height,
coordinate_type);
AtkTextRectangle atk_rect;
atk_text_get_range_extents(atk_text, newline_offset + 1, offset + 1,
coordinate_type, &atk_rect);
EXPECT_EQ(combined_extents.x(), atk_rect.x);
EXPECT_EQ(combined_extents.y(), atk_rect.y);
EXPECT_EQ(combined_extents.width(), atk_rect.width);
EXPECT_EQ(combined_extents.height(), atk_rect.height);
}
}
}
IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
TestCharacterExtentsInScrollableEditable) {
// By construction, only the first line of the content editable is visible.
AtkText* atk_text = SetUpSampleParagraphInScrollableEditable();
constexpr int first_line_end = 5;
constexpr int last_line_start = 8;
int n_characters = atk_text_get_character_count(atk_text);
ASSERT_EQ(13, n_characters);
int x, y, width, height;
int previous_x, previous_y, previous_height;
for (AtkCoordType coordinate_type : kCoordinateTypes) {
// Test that non offscreen characters have increasing x coordinates and a
// height that is greater than 1px.
{
testing::Message message;
message << "While checking at offset 0";
SCOPED_TRACE(message);
atk_text_get_character_extents(atk_text, 0, &x, &y, &width, &height,
coordinate_type);
EXPECT_LT(0, x);
EXPECT_LT(0, y);
EXPECT_LT(1, width);
EXPECT_LT(1, height);
}
for (int offset = 1; offset < first_line_end; ++offset) {
testing::Message message;
message << "While checking at offset " << offset;
SCOPED_TRACE(message);
previous_x = x;
previous_y = y;
previous_height = height;
atk_text_get_character_extents(atk_text, offset, &x, &y, &width, &height,
coordinate_type);
EXPECT_LT(previous_x, x);
EXPECT_EQ(previous_y, y);
EXPECT_LT(1, width);
EXPECT_EQ(previous_height, height);
}
{
testing::Message message;
message << "While checking at offset " << last_line_start;
SCOPED_TRACE(message);
atk_text_get_character_extents(atk_text, last_line_start, &x, &y, &width,
&height, coordinate_type);
EXPECT_LT(0, x);
EXPECT_LT(previous_y, y);
EXPECT_LT(1, width);
EXPECT_EQ(previous_height, height);
}
for (int offset = last_line_start + 1; offset < n_characters; ++offset) {
testing::Message message;
message << "While checking at offset " << offset;
SCOPED_TRACE(message);
previous_x = x;
previous_y = y;
atk_text_get_character_extents(atk_text, offset, &x, &y, &width, &height,
coordinate_type);
EXPECT_LT(previous_x, x);
EXPECT_EQ(previous_y, y);
EXPECT_LT(1, width);
EXPECT_EQ(previous_height, height);
}
}
}
} // namespace content