blob: 5a649c79d54801ba75302f38a3978699aeec4073 [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 {
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, DetachedIsIgnored) {
SetBodyInnerHTML(R"HTML(<button id="button">button</button>)HTML");
const AXObject* root = GetAXRootObject();
ASSERT_NE(nullptr, root);
AXObject* button = GetAXObjectByElementId("button");
ASSERT_NE(nullptr, button);
EXPECT_FALSE(button->IsDetached());
EXPECT_FALSE(button->AccessibilityIsIgnored());
GetAXObjectCache().Remove(button->GetNode());
EXPECT_TRUE(button->IsDetached());
EXPECT_TRUE(button->AccessibilityIsIgnored());
EXPECT_FALSE(button->AccessibilityIsIgnoredButIncludedInTree());
}
TEST_F(AccessibilityTest, UnignoredChildren) {
SetBodyInnerHTML(R"HTML(This is a test with
<p role="presentation">
ignored objects
</p>
<p>
which are at multiple
</p>
<p role="presentation">
<p role="presentation">
depth levels
</p>
in the accessibility tree.
</p>)HTML");
const AXObject* ax_body = GetAXRootObject()->FirstChildIncludingIgnored();
ASSERT_NE(nullptr, ax_body);
ASSERT_EQ(5, ax_body->UnignoredChildCount());
EXPECT_EQ(ax::mojom::blink::Role::kStaticText,
ax_body->UnignoredChildAt(0)->RoleValue());
EXPECT_EQ("This is a test with",
ax_body->UnignoredChildAt(0)->ComputedName());
EXPECT_EQ(ax::mojom::blink::Role::kStaticText,
ax_body->UnignoredChildAt(1)->RoleValue());
EXPECT_EQ("ignored objects", ax_body->UnignoredChildAt(1)->ComputedName());
EXPECT_EQ(ax::mojom::blink::Role::kParagraph,
ax_body->UnignoredChildAt(2)->RoleValue());
EXPECT_EQ(ax::mojom::blink::Role::kStaticText,
ax_body->UnignoredChildAt(3)->RoleValue());
EXPECT_EQ("depth levels", ax_body->UnignoredChildAt(3)->ComputedName());
EXPECT_EQ(ax::mojom::blink::Role::kStaticText,
ax_body->UnignoredChildAt(4)->RoleValue());
EXPECT_EQ("in the accessibility tree.",
ax_body->UnignoredChildAt(4)->ComputedName());
}
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 = GetAXBodyObject();
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->FirstChildIncludingIgnored());
EXPECT_EQ(button, body->LastChildIncludingIgnored());
ASSERT_NE(nullptr, paragraph->FirstChildIncludingIgnored());
EXPECT_EQ(ax::mojom::Role::kStaticText,
paragraph->FirstChildIncludingIgnored()->RoleValue());
ASSERT_NE(nullptr, paragraph->LastChildIncludingIgnored());
EXPECT_EQ(ax::mojom::Role::kStaticText,
paragraph->LastChildIncludingIgnored()->RoleValue());
ASSERT_NE(nullptr, paragraph->DeepestFirstChildIncludingIgnored());
EXPECT_EQ(ax::mojom::Role::kStaticText,
paragraph->DeepestFirstChildIncludingIgnored()->RoleValue());
ASSERT_NE(nullptr, paragraph->DeepestLastChildIncludingIgnored());
EXPECT_EQ(ax::mojom::Role::kStaticText,
paragraph->DeepestLastChildIncludingIgnored()->RoleValue());
EXPECT_EQ(paragraph->PreviousSiblingIncludingIgnored(),
GetAXObjectByElementId("ignored_a"));
EXPECT_EQ(GetAXObjectByElementId("ignored_a"),
input->NextSiblingIncludingIgnored());
ASSERT_NE(nullptr, br->NextSiblingIncludingIgnored());
EXPECT_EQ(ax::mojom::Role::kStaticText,
br->NextSiblingIncludingIgnored()->RoleValue());
ASSERT_NE(nullptr, br->PreviousSiblingIncludingIgnored());
EXPECT_EQ(ax::mojom::Role::kStaticText,
br->PreviousSiblingIncludingIgnored()->RoleValue());
EXPECT_EQ(paragraph->UnignoredPreviousSibling(), input);
EXPECT_EQ(paragraph, input->UnignoredNextSibling());
ASSERT_NE(nullptr, br->UnignoredNextSibling());
EXPECT_EQ(ax::mojom::Role::kStaticText,
br->UnignoredNextSibling()->RoleValue());
ASSERT_NE(nullptr, br->UnignoredPreviousSibling());
EXPECT_EQ(ax::mojom::Role::kStaticText,
br->UnignoredPreviousSibling()->RoleValue());
ASSERT_NE(nullptr, button->FirstChildIncludingIgnored());
EXPECT_EQ(ax::mojom::Role::kStaticText,
button->FirstChildIncludingIgnored()->RoleValue());
ASSERT_NE(nullptr, button->LastChildIncludingIgnored());
EXPECT_EQ(ax::mojom::Role::kStaticText,
button->LastChildIncludingIgnored()->RoleValue());
ASSERT_NE(nullptr, button->DeepestFirstChildIncludingIgnored());
EXPECT_EQ(ax::mojom::Role::kStaticText,
paragraph->DeepestFirstChildIncludingIgnored()->RoleValue());
}
TEST_F(AccessibilityTest, LangAttrInteresting) {
SetBodyInnerHTML(R"HTML(
<div id="A"><span>some text</span></div>
<div id="B"><span lang='en'>some text</span></div>
)HTML");
const AXObject* obj_a = GetAXObjectByElementId("A");
ASSERT_NE(nullptr, obj_a);
ASSERT_EQ(obj_a->ChildCountIncludingIgnored(), 1);
// A.span will be excluded from tree as it isn't semantically interesting.
// Instead its kStaticText child will be promoted.
const AXObject* span_1 = obj_a->ChildAtIncludingIgnored(0);
ASSERT_NE(nullptr, span_1);
EXPECT_EQ(ax::mojom::Role::kStaticText, span_1->RoleValue());
const AXObject* obj_b = GetAXObjectByElementId("B");
ASSERT_NE(nullptr, obj_b);
ASSERT_EQ(obj_b->ChildCountIncludingIgnored(), 1);
// B.span will be present as the lang attribute is semantically interesting.
const AXObject* span_2 = obj_b->ChildAtIncludingIgnored(0);
ASSERT_NE(nullptr, span_2);
EXPECT_EQ(ax::mojom::Role::kGenericContainer, span_2->RoleValue());
}
TEST_F(AccessibilityTest, LangAttrInterestingHidden) {
SetBodyInnerHTML(R"HTML(
<div id="A"><span lang='en' aria-hidden='true'>some text</span></div>
)HTML");
const AXObject* obj_a = GetAXObjectByElementId("A");
ASSERT_NE(nullptr, obj_a);
ASSERT_EQ(obj_a->ChildCountIncludingIgnored(), 1);
// A.span will be present as the lang attribute is semantically interesting.
const AXObject* span_1 = obj_a->ChildAtIncludingIgnored(0);
ASSERT_NE(nullptr, span_1);
EXPECT_EQ(ax::mojom::Role::kGenericContainer, span_1->RoleValue());
EXPECT_TRUE(span_1->AccessibilityIsIgnoredButIncludedInTree());
}
TEST_F(AccessibilityTest, TreeNavigationWithIgnoredContainer) {
// Setup the following tree :
// ++A
// ++IGNORED
// ++++B
// ++C
// So that nodes [A, B, C] are siblings
SetBodyInnerHTML(R"HTML(
<p id="A">some text</p>
<div>
<p id="B">nested text</p>
</div>
<p id="C">more text</p>
)HTML");
const AXObject* root = GetAXRootObject();
const AXObject* body = GetAXBodyObject();
ASSERT_EQ(3, body->ChildCountIncludingIgnored());
ASSERT_EQ(1, body->ChildAtIncludingIgnored(1)->ChildCountIncludingIgnored());
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->FirstChildIncludingIgnored();
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->FirstChildIncludingIgnored();
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->FirstChildIncludingIgnored();
ASSERT_NE(nullptr, obj_c_text);
EXPECT_EQ(ax::mojom::Role::kStaticText, obj_c_text->RoleValue());
const AXObject* obj_ignored = body->ChildAtIncludingIgnored(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->FirstChildIncludingIgnored());
EXPECT_EQ(nullptr, obj_a->PreviousSiblingIncludingIgnored());
EXPECT_EQ(nullptr, obj_a->UnignoredPreviousSibling());
EXPECT_EQ(obj_ignored, obj_a->NextSiblingIncludingIgnored());
EXPECT_EQ(obj_b, obj_a->UnignoredNextSibling());
EXPECT_EQ(body, obj_a->PreviousInPreOrderIncludingIgnored());
EXPECT_EQ(root, obj_a->UnignoredPreviousInPreOrder());
EXPECT_EQ(obj_a_text, obj_a->NextInPreOrderIncludingIgnored());
EXPECT_EQ(obj_a_text, obj_a->UnignoredNextInPreOrder());
EXPECT_EQ(nullptr, obj_b->PreviousSiblingIncludingIgnored());
EXPECT_EQ(obj_a, obj_b->UnignoredPreviousSibling());
EXPECT_EQ(nullptr, obj_b->NextSiblingIncludingIgnored());
EXPECT_EQ(obj_c, obj_b->UnignoredNextSibling());
EXPECT_EQ(obj_ignored, obj_b->PreviousInPreOrderIncludingIgnored());
EXPECT_EQ(obj_a_text, obj_b->UnignoredPreviousInPreOrder());
EXPECT_EQ(obj_b_text, obj_b->NextInPreOrderIncludingIgnored());
EXPECT_EQ(obj_b_text, obj_b->UnignoredNextInPreOrder());
EXPECT_EQ(obj_ignored, obj_c->PreviousSiblingIncludingIgnored());
EXPECT_EQ(obj_b, obj_c->UnignoredPreviousSibling());
EXPECT_EQ(nullptr, obj_c->NextSiblingIncludingIgnored());
EXPECT_EQ(nullptr, obj_c->UnignoredNextSibling());
EXPECT_EQ(obj_b_text, obj_c->PreviousInPreOrderIncludingIgnored());
EXPECT_EQ(obj_b_text, obj_c->UnignoredPreviousInPreOrder());
EXPECT_EQ(obj_c_text, obj_c->NextInPreOrderIncludingIgnored());
EXPECT_EQ(obj_c_text, obj_c->UnignoredNextInPreOrder());
}
TEST_F(AccessibilityTest, TreeNavigationWithContinuations) {
// Continuations found in the layout tree should not appear in the
// accessibility tree. For example, the following accessibility tree should
// result from the following HTML.
//
// WebArea
// ++HTMLElement
// ++++BodyElement
// ++++++Link
// ++++++++StaticText "Before block element."
// ++++++++GenericContainer
// ++++++++++Paragraph
// ++++++++++++StaticText "Inside block element."
// ++++++++StaticText "After block element."
SetBodyInnerHTML(R"HTML(
<a id="link" href="#">
Before block element.
<div id="div">
<p id="paragraph">
Inside block element.
</p>
</div>
After block element.
</a>
)HTML");
const AXObject* ax_root = GetAXRootObject();
ASSERT_NE(nullptr, ax_root);
const AXObject* ax_body = GetAXBodyObject();
ASSERT_NE(nullptr, ax_body);
const AXObject* ax_link = GetAXObjectByElementId("link");
ASSERT_NE(nullptr, ax_link);
const AXObject* ax_text_before = ax_link->FirstChildIncludingIgnored();
ASSERT_NE(nullptr, ax_text_before);
ASSERT_EQ(ax::mojom::Role::kStaticText, ax_text_before->RoleValue());
ASSERT_FALSE(ax_text_before->AccessibilityIsIgnored());
const AXObject* ax_div = GetAXObjectByElementId("div");
ASSERT_NE(nullptr, ax_div);
const AXObject* ax_paragraph = GetAXObjectByElementId("paragraph");
ASSERT_NE(nullptr, ax_paragraph);
const AXObject* ax_text_inside = ax_paragraph->FirstChildIncludingIgnored();
ASSERT_NE(nullptr, ax_text_inside);
ASSERT_EQ(ax::mojom::Role::kStaticText, ax_text_inside->RoleValue());
ASSERT_FALSE(ax_text_inside->AccessibilityIsIgnored());
const AXObject* ax_text_after = ax_link->LastChildIncludingIgnored();
ASSERT_NE(nullptr, ax_text_after);
ASSERT_EQ(ax::mojom::Role::kStaticText, ax_text_after->RoleValue());
ASSERT_FALSE(ax_text_after->AccessibilityIsIgnored());
//
// Test parent / child relationships individually. This is easier to debug
// than printing the whole accessibility tree as a string and comparing with
// an expected tree.
//
EXPECT_EQ(ax_root, ax_link->ParentObjectUnignored());
EXPECT_EQ(ax_body, ax_link->ParentObjectIncludedInTree());
EXPECT_EQ(ax_link, ax_text_before->ParentObjectUnignored());
EXPECT_EQ(ax_link, ax_text_before->ParentObjectIncludedInTree());
EXPECT_EQ(ax_link, ax_div->ParentObjectUnignored());
EXPECT_EQ(ax_link, ax_div->ParentObjectIncludedInTree());
EXPECT_EQ(ax_link, ax_text_after->ParentObjectUnignored());
EXPECT_EQ(ax_link, ax_text_after->ParentObjectIncludedInTree());
EXPECT_EQ(ax_div, ax_link->ChildAtIncludingIgnored(1));
EXPECT_EQ(ax_div, ax_link->UnignoredChildAt(1));
EXPECT_EQ(nullptr, ax_text_before->PreviousSiblingIncludingIgnored());
EXPECT_EQ(nullptr, ax_text_before->UnignoredPreviousSibling());
EXPECT_EQ(ax_div, ax_text_before->NextSiblingIncludingIgnored());
EXPECT_EQ(ax_div, ax_text_before->UnignoredNextSibling());
EXPECT_EQ(ax_div, ax_text_after->PreviousSiblingIncludingIgnored());
EXPECT_EQ(ax_div, ax_text_after->UnignoredPreviousSibling());
EXPECT_EQ(nullptr, ax_text_after->NextSiblingIncludingIgnored());
EXPECT_EQ(nullptr, ax_text_after->UnignoredNextSibling());
EXPECT_EQ(ax_paragraph, ax_div->ChildAtIncludingIgnored(0));
EXPECT_EQ(ax_paragraph, ax_div->UnignoredChildAt(0));
EXPECT_EQ(ax_div, ax_paragraph->ParentObjectUnignored());
EXPECT_EQ(ax_div, ax_paragraph->ParentObjectIncludedInTree());
EXPECT_EQ(ax_paragraph, ax_text_inside->ParentObjectUnignored());
EXPECT_EQ(ax_paragraph, ax_text_inside->ParentObjectIncludedInTree());
}
TEST_F(AccessibilityTest, TreeNavigationWithInlineTextBoxes) {
SetBodyInnerHTML(R"HTML(
Before paragraph element.
<p id="paragraph">
Inside paragraph element.
</p>
After paragraph element.
)HTML");
AXObject* ax_root = GetAXRootObject();
ASSERT_NE(nullptr, ax_root);
ax_root->LoadInlineTextBoxes();
const AXObject* ax_paragraph = GetAXObjectByElementId("paragraph");
ASSERT_NE(nullptr, ax_paragraph);
const AXObject* ax_text_inside = ax_paragraph->FirstChildIncludingIgnored();
ASSERT_NE(nullptr, ax_text_inside);
ASSERT_EQ(ax::mojom::Role::kStaticText, ax_text_inside->RoleValue());
const AXObject* ax_text_before = ax_paragraph->UnignoredPreviousSibling();
ASSERT_NE(nullptr, ax_text_before);
ASSERT_EQ(ax::mojom::blink::Role::kStaticText, ax_text_before->RoleValue());
const AXObject* ax_text_after = ax_paragraph->UnignoredNextSibling();
ASSERT_NE(nullptr, ax_text_after);
ASSERT_EQ(ax::mojom::blink::Role::kStaticText, ax_text_after->RoleValue());
//
// Verify parent / child relationships between static text and inline text
// boxes.
//
EXPECT_EQ(1, ax_text_before->ChildCountIncludingIgnored());
EXPECT_EQ(1, ax_text_before->UnignoredChildCount());
const AXObject* ax_inline_before =
ax_text_before->FirstChildIncludingIgnored();
EXPECT_EQ(ax::mojom::blink::Role::kInlineTextBox,
ax_inline_before->RoleValue());
EXPECT_EQ(ax_text_before, ax_inline_before->ParentObjectIncludedInTree());
EXPECT_EQ(ax_text_before, ax_inline_before->ParentObjectUnignored());
EXPECT_EQ(1, ax_text_inside->ChildCountIncludingIgnored());
EXPECT_EQ(1, ax_text_inside->UnignoredChildCount());
const AXObject* ax_inline_inside =
ax_text_inside->FirstChildIncludingIgnored();
EXPECT_EQ(ax::mojom::blink::Role::kInlineTextBox,
ax_inline_inside->RoleValue());
EXPECT_EQ(ax_text_inside, ax_inline_inside->ParentObjectIncludedInTree());
EXPECT_EQ(ax_text_inside, ax_inline_inside->ParentObjectUnignored());
EXPECT_EQ(1, ax_text_after->ChildCountIncludingIgnored());
EXPECT_EQ(1, ax_text_after->UnignoredChildCount());
const AXObject* ax_inline_after = ax_text_after->FirstChildIncludingIgnored();
EXPECT_EQ(ax::mojom::blink::Role::kInlineTextBox,
ax_inline_after->RoleValue());
EXPECT_EQ(ax_text_after, ax_inline_after->ParentObjectIncludedInTree());
EXPECT_EQ(ax_text_after, ax_inline_after->ParentObjectUnignored());
}
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, AXObjectUnignoredAncestorsIterator) {
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->UnignoredAncestorsBegin();
EXPECT_EQ(*paragraph, *iter);
EXPECT_EQ(ax::mojom::Role::kParagraph, iter->RoleValue());
EXPECT_EQ(*root, *++iter);
EXPECT_EQ(*root, *iter++);
EXPECT_EQ(br->UnignoredAncestorsEnd(), ++iter);
}
TEST_F(AccessibilityTest, AXObjectInOrderTraversalIterator) {
SetBodyInnerHTML(R"HTML(<input type="checkbox" id="checkbox">)HTML");
AXObject* root = GetAXRootObject();
ASSERT_NE(nullptr, root);
AXObject* body = GetAXBodyObject();
ASSERT_NE(nullptr, root);
AXObject* checkbox = GetAXObjectByElementId("checkbox");
ASSERT_NE(nullptr, checkbox);
AXObject::InOrderTraversalIterator iter = body->GetInOrderTraversalIterator();
EXPECT_EQ(*body, *iter);
EXPECT_NE(GetAXObjectCache().InOrderTraversalEnd(), iter);
EXPECT_EQ(*checkbox, *++iter);
EXPECT_EQ(ax::mojom::Role::kCheckBox, iter->RoleValue());
EXPECT_EQ(*checkbox, *iter++);
EXPECT_EQ(GetAXObjectCache().InOrderTraversalEnd(), iter);
EXPECT_EQ(*checkbox, *--iter);
EXPECT_EQ(*checkbox, *iter--);
--iter; // Skip the BODY element.
--iter; // Skip the HTML element.
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_F(AccessibilityTest, AxNodeObjectInPageLinkTargetNonAscii) {
GetDocument().SetURL(KURL("http://test.com"));
// ö is U+00F6 which URI encodes to %C3%B6
//
// This file is forced to be UTF-8 by the build system,
// the uR"" will create char16_t[] of UTF-16,
// WTF::String will wrap the char16_t* as UTF-16.
// All this is checked by ensuring a match against u"\u00F6".
//
// TODO(1117212): The escaped version currently takes precedence.
// <h1 id="%C3%B6">O2</h1>
SetBodyInnerHTML(
uR"HTML(
<a href="#ö" id="anchor">O</a>
<h1 id="ö">O</h1>"
<a href="#t%6Fp" id="top_test">top</a>"
<a href="#" id="empty_test">also top</a>");
)HTML");
{
// anchor
const AXObject* anchor = GetAXObjectByElementId("anchor");
ASSERT_NE(nullptr, anchor);
EXPECT_FALSE(anchor->Url().IsEmpty());
EXPECT_EQ(anchor->Url(), KURL(u"http://test.com/#\u00F6"));
const AXObject* target = anchor->InPageLinkTarget();
ASSERT_NE(nullptr, target);
auto* targetElement = DynamicTo<Element>(target->GetNode());
ASSERT_NE(nullptr, target);
ASSERT_TRUE(targetElement->HasID());
EXPECT_EQ(targetElement->IdForStyleResolution(), String(u"\u00F6"));
}
{
// top_test
const AXObject* anchor = GetAXObjectByElementId("top_test");
ASSERT_NE(nullptr, anchor);
EXPECT_FALSE(anchor->Url().IsEmpty());
EXPECT_EQ(anchor->Url(), KURL(u"http://test.com/#t%6Fp"));
const AXObject* target = anchor->InPageLinkTarget();
ASSERT_NE(nullptr, target);
EXPECT_EQ(&GetDocument(), target->GetNode());
}
{
// empty_test
const AXObject* anchor = GetAXObjectByElementId("empty_test");
ASSERT_NE(nullptr, anchor);
EXPECT_FALSE(anchor->Url().IsEmpty());
EXPECT_EQ(anchor->Url(), KURL(u"http://test.com/#"));
const AXObject* target = anchor->InPageLinkTarget();
ASSERT_NE(nullptr, target);
EXPECT_EQ(&GetDocument(), target->GetNode());
}
}
TEST_P(ParameterizedAccessibilityTest, 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->ChildCountIncludingIgnored());
EXPECT_FALSE(preserved_span->IsLineBreakingObject());
AXObject* preserved_text = preserved_span->FirstChildIncludingIgnored();
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->ChildCountIncludingIgnored());
bool all_children_are_inline_text_boxes = true;
for (const AXObject* child : preserved_text->ChildrenIncludingIgnored()) {
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->ChildAtIncludingIgnored(0)->ComputedName(), "\n");
EXPECT_TRUE(
preserved_text->ChildAtIncludingIgnored(0)->IsLineBreakingObject());
ASSERT_EQ(preserved_text->ChildAtIncludingIgnored(1)->ComputedName(),
"First Paragraph");
EXPECT_FALSE(
preserved_text->ChildAtIncludingIgnored(1)->IsLineBreakingObject());
ASSERT_EQ(preserved_text->ChildAtIncludingIgnored(2)->ComputedName(), "\n");
EXPECT_TRUE(
preserved_text->ChildAtIncludingIgnored(2)->IsLineBreakingObject());
ASSERT_EQ(preserved_text->ChildAtIncludingIgnored(3)->ComputedName(),
"Second Paragraph");
EXPECT_FALSE(
preserved_text->ChildAtIncludingIgnored(3)->IsLineBreakingObject());
ASSERT_EQ(preserved_text->ChildAtIncludingIgnored(4)->ComputedName(), "\n");
EXPECT_TRUE(
preserved_text->ChildAtIncludingIgnored(4)->IsLineBreakingObject());
ASSERT_EQ(preserved_text->ChildAtIncludingIgnored(5)->ComputedName(),
"Third Paragraph");
EXPECT_FALSE(
preserved_text->ChildAtIncludingIgnored(5)->IsLineBreakingObject());
ASSERT_EQ(preserved_text->ChildAtIncludingIgnored(6)->ComputedName(), "\n");
EXPECT_TRUE(
preserved_text->ChildAtIncludingIgnored(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->FirstChildIncludingIgnored()->ChildCountIncludingIgnored(), 1);
}
TEST_F(AccessibilityTest, InitRelationCacheLabelFor) {
// Most other tests already have accessibility initialized
// first, but we don't want to in this test.
//
// Get rid of the AXContext so the AXObjectCache is destroyed.
ax_context_.reset(nullptr);
SetBodyInnerHTML(R"HTML(
<label for="a"></label>
<input id="a">
<input id="b">
)HTML");
// Now recreate an AXContext, simulating what happens if accessibility
// is enabled after the document is loaded.
ax_context_.reset(new AXContext(GetDocument()));
const AXObject* root = GetAXRootObject();
ASSERT_NE(nullptr, root);
const AXObject* input_a = GetAXObjectByElementId("a");
ASSERT_NE(nullptr, input_a);
const AXObject* input_b = GetAXObjectByElementId("b");
ASSERT_NE(nullptr, input_b);
}
TEST_F(AccessibilityTest, InitRelationCacheAriaOwns) {
// Most other tests already have accessibility initialized
// first, but we don't want to in this test.
//
// Get rid of the AXContext so the AXObjectCache is destroyed.
ax_context_.reset(nullptr);
SetBodyInnerHTML(R"HTML(
<ul id="ul" aria-owns="li"></ul>
<div role="section" id="div">
<li id="li"></li>
</div>
)HTML");
// Now recreate an AXContext, simulating what happens if accessibility
// is enabled after the document is loaded.
ax_context_.reset(new AXContext(GetDocument()));
const AXObject* root = GetAXRootObject();
ASSERT_NE(nullptr, root);
// Note: retrieve the LI first and check that its parent is not
// the paragraph element. If we were to retrieve the UL element,
// that would trigger the aria-owns check and wouln't allow us to
// test whether the relation cache was initialized.
const AXObject* li = GetAXObjectByElementId("li");
ASSERT_NE(nullptr, li);
const AXObject* div = GetAXObjectByElementId("div");
ASSERT_NE(nullptr, div);
EXPECT_NE(li->ParentObjectUnignored(), div);
const AXObject* ul = GetAXObjectByElementId("ul");
ASSERT_NE(nullptr, ul);
EXPECT_EQ(li->ParentObjectUnignored(), ul);
}
} // namespace test
} // namespace blink