blob: 2bf649ae2777ec0ebe0bb09f9e9fcf1a1ef2f3e1 [file] [log] [blame]
// Copyright 2018 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 "third_party/blink/renderer/modules/accessibility/ax_object.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h"
#include "third_party/blink/renderer/modules/accessibility/testing/accessibility_test.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
namespace blink {
namespace test {
class AccessibilityLayoutTest : public testing::WithParamInterface<bool>,
private ScopedLayoutNGForTest,
public AccessibilityTest {
public:
AccessibilityLayoutTest() : ScopedLayoutNGForTest(GetParam()) {}
protected:
bool LayoutNGEnabled() const { return GetParam(); }
};
INSTANTIATE_TEST_SUITE_P(AccessibilityTest,
AccessibilityLayoutTest,
testing::Bool());
TEST_F(AccessibilityTest, IsDescendantOf) {
SetBodyInnerHTML(R"HTML(<button id="button">button</button>)HTML");
const AXObject* root = GetAXRootObject();
ASSERT_NE(nullptr, root);
const AXObject* button = GetAXObjectByElementId("button");
ASSERT_NE(nullptr, button);
EXPECT_TRUE(button->IsDescendantOf(*root));
EXPECT_FALSE(root->IsDescendantOf(*root));
EXPECT_FALSE(button->IsDescendantOf(*button));
EXPECT_FALSE(root->IsDescendantOf(*button));
}
TEST_F(AccessibilityTest, IsAncestorOf) {
SetBodyInnerHTML(R"HTML(<button id="button">button</button>)HTML");
const AXObject* root = GetAXRootObject();
ASSERT_NE(nullptr, root);
const AXObject* button = GetAXObjectByElementId("button");
ASSERT_NE(nullptr, button);
EXPECT_TRUE(root->IsAncestorOf(*button));
EXPECT_FALSE(root->IsAncestorOf(*root));
EXPECT_FALSE(button->IsAncestorOf(*button));
EXPECT_FALSE(button->IsAncestorOf(*root));
}
TEST_F(AccessibilityTest, SimpleTreeNavigation) {
SetBodyInnerHTML(R"HTML(<input id="input" type="text" value="value">
<div id='ignored_a' aria-hidden='true'></div>
<p id="paragraph">hello<br id="br">there</p>
<span id='ignored_b' aria-hidden='true'></span>
<button id="button">button</button>)HTML");
const AXObject* body = GetAXRootObject()->FirstChild();
ASSERT_NE(nullptr, body);
const AXObject* input = GetAXObjectByElementId("input");
ASSERT_NE(nullptr, input);
ASSERT_NE(nullptr, GetAXObjectByElementId("ignored_a"));
ASSERT_TRUE(GetAXObjectByElementId("ignored_a")->AccessibilityIsIgnored());
const AXObject* paragraph = GetAXObjectByElementId("paragraph");
ASSERT_NE(nullptr, paragraph);
const AXObject* br = GetAXObjectByElementId("br");
ASSERT_NE(nullptr, br);
ASSERT_NE(nullptr, GetAXObjectByElementId("ignored_b"));
ASSERT_TRUE(GetAXObjectByElementId("ignored_b")->AccessibilityIsIgnored());
const AXObject* button = GetAXObjectByElementId("button");
ASSERT_NE(nullptr, button);
EXPECT_EQ(input, body->FirstChild());
EXPECT_EQ(button, body->LastChild());
EXPECT_EQ(button, body->DeepestLastChild());
ASSERT_NE(nullptr, paragraph->FirstChild());
EXPECT_EQ(ax::mojom::Role::kStaticText, paragraph->FirstChild()->RoleValue());
ASSERT_NE(nullptr, paragraph->LastChild());
EXPECT_EQ(ax::mojom::Role::kStaticText, paragraph->LastChild()->RoleValue());
ASSERT_NE(nullptr, paragraph->DeepestFirstChild());
EXPECT_EQ(ax::mojom::Role::kStaticText,
paragraph->DeepestFirstChild()->RoleValue());
ASSERT_NE(nullptr, paragraph->DeepestLastChild());
EXPECT_EQ(ax::mojom::Role::kStaticText,
paragraph->DeepestLastChild()->RoleValue());
EXPECT_EQ(paragraph->PreviousSibling(), input);
EXPECT_EQ(paragraph, input->NextSibling());
ASSERT_NE(nullptr, br->NextSibling());
EXPECT_EQ(ax::mojom::Role::kStaticText, br->NextSibling()->RoleValue());
ASSERT_NE(nullptr, br->PreviousSibling());
EXPECT_EQ(ax::mojom::Role::kStaticText, br->PreviousSibling()->RoleValue());
}
TEST_F(AccessibilityTest, TreeNavigationWithIgnoredContainer) {
// Setup the following tree :
// ++A
// ++IGNORED
// ++++B
// ++C
// So that nodes [A, B, C] are siblings
SetBodyInnerHTML(R"HTML(<body>
<p id="A">some text</p>
<div>
<p id="B">nested text</p>
</div>
<p id="C">more text</p>
</body>)HTML");
const AXObject* root = GetAXRootObject();
const AXObject* body = root->FirstChild();
ASSERT_EQ(3, body->ChildCount());
ASSERT_EQ(1, body->Children()[1]->ChildCount());
ASSERT_FALSE(root->AccessibilityIsIgnored());
ASSERT_TRUE(body->AccessibilityIsIgnored());
const AXObject* obj_a = GetAXObjectByElementId("A");
ASSERT_NE(nullptr, obj_a);
ASSERT_FALSE(obj_a->AccessibilityIsIgnored());
const AXObject* obj_a_text = obj_a->FirstChild();
ASSERT_NE(nullptr, obj_a_text);
EXPECT_EQ(ax::mojom::Role::kStaticText, obj_a_text->RoleValue());
const AXObject* obj_b = GetAXObjectByElementId("B");
ASSERT_NE(nullptr, obj_b);
ASSERT_FALSE(obj_b->AccessibilityIsIgnored());
const AXObject* obj_b_text = obj_b->FirstChild();
ASSERT_NE(nullptr, obj_b_text);
EXPECT_EQ(ax::mojom::Role::kStaticText, obj_b_text->RoleValue());
const AXObject* obj_c = GetAXObjectByElementId("C");
ASSERT_NE(nullptr, obj_c);
ASSERT_FALSE(obj_c->AccessibilityIsIgnored());
const AXObject* obj_c_text = obj_c->FirstChild();
ASSERT_NE(nullptr, obj_c_text);
EXPECT_EQ(ax::mojom::Role::kStaticText, obj_c_text->RoleValue());
const AXObject* obj_ignored = body->Children()[1];
ASSERT_NE(nullptr, obj_ignored);
ASSERT_TRUE(obj_ignored->AccessibilityIsIgnored());
EXPECT_EQ(root, obj_a->ParentObjectUnignored());
EXPECT_EQ(body, obj_a->ParentObjectIncludedInTree());
EXPECT_EQ(root, obj_b->ParentObjectUnignored());
EXPECT_EQ(obj_ignored, obj_b->ParentObjectIncludedInTree());
EXPECT_EQ(root, obj_c->ParentObjectUnignored());
EXPECT_EQ(body, obj_c->ParentObjectIncludedInTree());
EXPECT_EQ(obj_b, obj_ignored->FirstChild());
EXPECT_EQ(nullptr, obj_a->PreviousSibling());
EXPECT_EQ(obj_b, obj_a->NextSibling());
EXPECT_EQ(root, obj_a->PreviousInTreeObject());
EXPECT_EQ(obj_a_text, obj_a->NextInTreeObject());
EXPECT_EQ(obj_a, obj_b->PreviousSibling());
EXPECT_EQ(obj_c, obj_b->NextSibling());
EXPECT_EQ(obj_a_text, obj_b->PreviousInTreeObject());
EXPECT_EQ(obj_b_text, obj_b->NextInTreeObject());
EXPECT_EQ(obj_b, obj_c->PreviousSibling());
EXPECT_EQ(nullptr, obj_c->NextSibling());
EXPECT_EQ(obj_b_text, obj_c->PreviousInTreeObject());
EXPECT_EQ(obj_c_text, obj_c->NextInTreeObject());
}
TEST_F(AccessibilityTest, AXObjectComparisonOperators) {
SetBodyInnerHTML(R"HTML(<input id="input" type="text" value="value">
<p id="paragraph">hello<br id="br">there</p>
<button id="button">button</button>)HTML");
const AXObject* root = GetAXRootObject();
ASSERT_NE(nullptr, root);
const AXObject* input = GetAXObjectByElementId("input");
ASSERT_NE(nullptr, input);
const AXObject* paragraph = GetAXObjectByElementId("paragraph");
ASSERT_NE(nullptr, paragraph);
const AXObject* br = GetAXObjectByElementId("br");
ASSERT_NE(nullptr, br);
const AXObject* button = GetAXObjectByElementId("button");
ASSERT_NE(nullptr, button);
EXPECT_TRUE(*root == *root);
EXPECT_FALSE(*root != *root);
EXPECT_FALSE(*root < *root);
EXPECT_TRUE(*root <= *root);
EXPECT_FALSE(*root > *root);
EXPECT_TRUE(*root >= *root);
EXPECT_TRUE(*input > *root);
EXPECT_TRUE(*input >= *root);
EXPECT_FALSE(*input < *root);
EXPECT_FALSE(*input <= *root);
EXPECT_TRUE(*input != *root);
EXPECT_TRUE(*input < *paragraph);
EXPECT_TRUE(*br > *input);
EXPECT_TRUE(*paragraph < *br);
EXPECT_TRUE(*br >= *paragraph);
EXPECT_TRUE(*paragraph < *button);
EXPECT_TRUE(*button > *br);
EXPECT_FALSE(*button < *button);
EXPECT_TRUE(*button <= *button);
EXPECT_TRUE(*button >= *button);
EXPECT_FALSE(*button > *button);
}
TEST_F(AccessibilityTest, AXObjectAncestorsIterator) {
SetBodyInnerHTML(
R"HTML(<p id="paragraph"><b id="bold"><br id="br"></b></p>)HTML");
AXObject* root = GetAXRootObject();
ASSERT_NE(nullptr, root);
AXObject* paragraph = GetAXObjectByElementId("paragraph");
ASSERT_NE(nullptr, paragraph);
AXObject* bold = GetAXObjectByElementId("bold");
ASSERT_NE(nullptr, bold);
AXObject* br = GetAXObjectByElementId("br");
ASSERT_NE(nullptr, br);
ASSERT_EQ(ax::mojom::Role::kLineBreak, br->RoleValue());
AXObject::AncestorsIterator iter = br->AncestorsBegin();
EXPECT_EQ(*paragraph, *iter);
EXPECT_EQ(ax::mojom::Role::kParagraph, iter->RoleValue());
EXPECT_EQ(*root, *++iter);
EXPECT_EQ(*root, *iter++);
EXPECT_EQ(br->AncestorsEnd(), ++iter);
}
TEST_F(AccessibilityTest, AXObjectInOrderTraversalIterator) {
SetBodyInnerHTML(R"HTML(<button id="button">Button</button>)HTML");
AXObject* root = GetAXRootObject();
ASSERT_NE(nullptr, root);
AXObject* button = GetAXObjectByElementId("button");
ASSERT_NE(nullptr, button);
AXObject::InOrderTraversalIterator iter = root->GetInOrderTraversalIterator();
EXPECT_EQ(*root, *iter);
++iter; // Skip the generic container which is an ignored object.
EXPECT_NE(GetAXObjectCache().InOrderTraversalEnd(), iter);
EXPECT_EQ(*button, *++iter);
EXPECT_EQ(ax::mojom::Role::kButton, iter->RoleValue());
EXPECT_EQ(*button, *iter++);
EXPECT_EQ(GetAXObjectCache().InOrderTraversalEnd(), iter);
EXPECT_EQ(*button, *--iter);
EXPECT_EQ(*button, *iter--);
--iter; // Skip the generic container which is an ignored object.
EXPECT_EQ(ax::mojom::Role::kRootWebArea, iter->RoleValue());
EXPECT_EQ(GetAXObjectCache().InOrderTraversalBegin(), iter);
}
TEST_F(AccessibilityTest, AxNodeObjectContainsHtmlAnchorElementUrl) {
SetBodyInnerHTML(R"HTML(<a id="anchor" href="http://test.com">link</a>)HTML");
const AXObject* root = GetAXRootObject();
ASSERT_NE(nullptr, root);
const AXObject* anchor = GetAXObjectByElementId("anchor");
ASSERT_NE(nullptr, anchor);
// Passing a malformed string to KURL returns an empty URL, so verify the
// AXObject's URL is non-empty first to catch errors in the test itself.
EXPECT_FALSE(anchor->Url().IsEmpty());
EXPECT_EQ(anchor->Url(), KURL("http://test.com"));
}
TEST_F(AccessibilityTest, AxNodeObjectContainsSvgAnchorElementUrl) {
SetBodyInnerHTML(R"HTML(
<svg>
<a id="anchor" xlink:href="http://test.com"></a>
</svg>
)HTML");
const AXObject* root = GetAXRootObject();
ASSERT_NE(nullptr, root);
const AXObject* anchor = GetAXObjectByElementId("anchor");
ASSERT_NE(nullptr, anchor);
EXPECT_FALSE(anchor->Url().IsEmpty());
EXPECT_EQ(anchor->Url(), KURL("http://test.com"));
}
TEST_F(AccessibilityTest, AxNodeObjectContainsImageUrl) {
SetBodyInnerHTML(R"HTML(<img id="anchor" src="http://test.png" />)HTML");
const AXObject* root = GetAXRootObject();
ASSERT_NE(nullptr, root);
const AXObject* anchor = GetAXObjectByElementId("anchor");
ASSERT_NE(nullptr, anchor);
EXPECT_FALSE(anchor->Url().IsEmpty());
EXPECT_EQ(anchor->Url(), KURL("http://test.png"));
}
TEST_F(AccessibilityTest, AxNodeObjectContainsInPageLinkTarget) {
GetDocument().SetBaseURLOverride(KURL("http://test.com"));
SetBodyInnerHTML(R"HTML(<a id="anchor" href="#target">link</a>)HTML");
const AXObject* root = GetAXRootObject();
ASSERT_NE(nullptr, root);
const AXObject* anchor = GetAXObjectByElementId("anchor");
ASSERT_NE(nullptr, anchor);
EXPECT_FALSE(anchor->Url().IsEmpty());
EXPECT_EQ(anchor->Url(), KURL("http://test.com/#target"));
}
TEST_P(AccessibilityLayoutTest, NextOnLine) {
SetBodyInnerHTML(R"HTML(
<style>
html {
font-size: 10px;
}
/* TODO(kojii): |NextOnLine| doesn't work for culled-inline.
Ensure spans are not culled to avoid hitting the case. */
span {
background: gray;
}
</style>
<div><span id="span1">a</span><span>b</span></div>
)HTML");
const AXObject* span1 = GetAXObjectByElementId("span1");
ASSERT_NE(nullptr, span1);
const AXObject* next = span1->NextOnLine();
ASSERT_NE(nullptr, next);
EXPECT_EQ("b", next->GetNode()->textContent());
}
TEST_F(AccessibilityTest, AxObjectPreservedWhitespaceIsLineBreakingObjects) {
SetBodyInnerHTML(R"HTML(
<span style='white-space: pre-line' id="preserved">
First Paragraph
Second Paragraph
Third Paragraph
</span>)HTML");
const AXObject* root = GetAXRootObject();
ASSERT_NE(nullptr, root);
const AXObject* preserved_span = GetAXObjectByElementId("preserved");
ASSERT_NE(nullptr, preserved_span);
ASSERT_EQ(ax::mojom::Role::kGenericContainer, preserved_span->RoleValue());
ASSERT_EQ(1, preserved_span->ChildCount());
EXPECT_FALSE(preserved_span->IsLineBreakingObject());
AXObject* preserved_text = preserved_span->FirstChild();
ASSERT_NE(nullptr, preserved_text);
ASSERT_EQ(ax::mojom::Role::kStaticText, preserved_text->RoleValue());
EXPECT_FALSE(preserved_text->IsLineBreakingObject());
// Expect 7 kInlineTextBox children
// 3 lines of text, and 4 newlines
preserved_text->LoadInlineTextBoxes();
ASSERT_EQ(7, preserved_text->ChildCount());
bool all_children_are_inline_text_boxes = true;
for (const AXObject* child : preserved_text->Children()) {
if (child->RoleValue() != ax::mojom::Role::kInlineTextBox) {
all_children_are_inline_text_boxes = false;
break;
}
}
ASSERT_TRUE(all_children_are_inline_text_boxes);
ASSERT_EQ(preserved_text->Children()[0]->ComputedName(), "\n");
EXPECT_TRUE(preserved_text->Children()[0]->IsLineBreakingObject());
ASSERT_EQ(preserved_text->Children()[1]->ComputedName(), "First Paragraph");
EXPECT_FALSE(preserved_text->Children()[1]->IsLineBreakingObject());
ASSERT_EQ(preserved_text->Children()[2]->ComputedName(), "\n");
EXPECT_TRUE(preserved_text->Children()[2]->IsLineBreakingObject());
ASSERT_EQ(preserved_text->Children()[3]->ComputedName(), "Second Paragraph");
EXPECT_FALSE(preserved_text->Children()[3]->IsLineBreakingObject());
ASSERT_EQ(preserved_text->Children()[4]->ComputedName(), "\n");
EXPECT_TRUE(preserved_text->Children()[4]->IsLineBreakingObject());
ASSERT_EQ(preserved_text->Children()[5]->ComputedName(), "Third Paragraph");
EXPECT_FALSE(preserved_text->Children()[5]->IsLineBreakingObject());
ASSERT_EQ(preserved_text->Children()[6]->ComputedName(), "\n");
EXPECT_TRUE(preserved_text->Children()[6]->IsLineBreakingObject());
}
TEST_F(AccessibilityTest, CheckNoDuplicateChildren) {
GetPage().GetSettings().SetInlineTextBoxAccessibilityEnabled(false);
SetBodyInnerHTML(R"HTML(
<select id="sel"><option>1</option></select>
)HTML");
AXObject* ax_select = GetAXObjectByElementId("sel");
ax_select->SetNeedsToUpdateChildren();
ax_select->UpdateChildrenIfNecessary();
ASSERT_EQ(ax_select->FirstChild()->ChildCount(), 1);
}
} // namespace test
} // namespace blink