// Copyright 2019 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/ax_platform_node_win_unittest.h"
#include "base/win/scoped_bstr.h"
#include "ui/accessibility/platform/ax_fragment_root_win.h"
#include "ui/accessibility/platform/ax_platform_node_textchildprovider_win.h"
#include "ui/accessibility/platform/ax_platform_node_textprovider_win.h"
#include "ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h"
#include "ui/accessibility/test_ax_tree_update.h"
using Microsoft::WRL::ComPtr;
namespace ui {
class AXPlatformNodeTextChildProviderTest : public AXPlatformNodeWinTest {
protected:
// Construct an accessibility tree for testing ITextChildProvider resolution
// from various positions in the tree. The following tree configuration
// is constructed:
//
// root_________________________________________________
// | |
// nontext_child_of_root______ text_child_of_root
// | | |
// nontext_child_of_nontext text_child_of_nontext text_child_of_text
//
// nontext leaf elements are considered as embedded objects and expose a
// character to allow the text pattern navigation to work with them too.
// Because of that, a nontext leaf element is treated as a text element.
void SetUp() override {
TestAXTreeUpdate update(std::string(R"HTML(
++1 kRootWebArea
++++2 kGroup
++++++3 kGroup
++++++4 kStaticText
++++++5 kButton
++++6 kStaticText
++++++7 kInlineTextBox
)HTML"));
update.nodes[1].SetName("non text child of root.");
update.nodes[1].SetNameFrom(ax::mojom::NameFrom::kAttribute);
update.nodes[2].SetName("non text child of nontext.");
update.nodes[2].SetNameFrom(ax::mojom::NameFrom::kAttribute);
update.nodes[3].SetName("text child of nontext.");
update.nodes[3].SetNameFrom(ax::mojom::NameFrom::kContents);
update.nodes[5].SetName("text child of root.");
update.nodes[5].SetNameFrom(ax::mojom::NameFrom::kContents);
update.nodes[6].SetName("text child of text.");
update.nodes[6].SetNameFrom(ax::mojom::NameFrom::kContents);
Init(update);
AXNode* root_node = GetRoot();
AXNode* nontext_child_of_root_node = root_node->children()[0];
AXNode* text_child_of_root_node = root_node->children()[1];
AXNode* nontext_child_of_nontext_node =
nontext_child_of_root_node->children()[0];
AXNode* text_child_of_nontext_node =
nontext_child_of_root_node->children()[1];
AXNode* text_child_of_text_node = text_child_of_root_node->children()[0];
InitITextChildProvider(root_node, root_provider_raw_,
root_text_child_provider_);
InitITextChildProvider(nontext_child_of_root_node,
nontext_child_of_root_provider_raw_,
nontext_child_of_root_text_child_provider_);
InitITextChildProvider(text_child_of_root_node,
text_child_of_root_text_provider_raw_,
text_child_of_root_text_child_provider_);
InitITextChildProvider(nontext_child_of_nontext_node,
nontext_child_of_nontext_text_provider_raw_,
nontext_child_of_nontext_text_child_provider_);
InitITextChildProvider(text_child_of_nontext_node,
text_child_of_nontext_text_provider_raw_,
text_child_of_nontext_text_child_provider_);
InitITextChildProvider(text_child_of_text_node,
text_child_of_text_text_provider_raw_,
text_child_of_text_text_child_provider_);
AXPlatformNodeWinTest::SetUp();
}
void InitITextChildProvider(
AXNode* node,
ComPtr<IRawElementProviderSimple>& raw_element_provider,
ComPtr<ITextChildProvider>& text_child_provider) {
raw_element_provider =
QueryInterfaceFromNode<IRawElementProviderSimple>(node);
EXPECT_HRESULT_SUCCEEDED(raw_element_provider->GetPatternProvider(
UIA_TextChildPatternId, &text_child_provider));
// If the element does not support ITextChildProvider, create one anyways
// for testing purposes.
if (!text_child_provider) {
AXPlatformNodeWin* platform_node =
(AXPlatformNodeWin*)raw_element_provider.Get();
ComPtr<ITextChildProvider> new_child_provider =
AXPlatformNodeTextChildProviderWin::Create(platform_node);
new_child_provider->QueryInterface(IID_PPV_ARGS(&text_child_provider));
}
}
void SetOwner(AXPlatformNodeWin* owner,
ITextRangeProvider* destination_range) {
ComPtr<ITextRangeProvider> destination_provider = destination_range;
ComPtr<AXPlatformNodeTextRangeProviderWin> destination_provider_interal;
destination_provider->QueryInterface(
IID_PPV_ARGS(&destination_provider_interal));
destination_provider_interal->SetOwnerForTesting(owner);
}
ComPtr<IRawElementProviderSimple> root_provider_raw_;
ComPtr<IRawElementProviderSimple> nontext_child_of_root_provider_raw_;
ComPtr<IRawElementProviderSimple> text_child_of_root_text_provider_raw_;
ComPtr<IRawElementProviderSimple> nontext_child_of_nontext_text_provider_raw_;
ComPtr<IRawElementProviderSimple> text_child_of_nontext_text_provider_raw_;
ComPtr<IRawElementProviderSimple> text_child_of_text_text_provider_raw_;
ComPtr<ITextChildProvider> root_text_child_provider_;
ComPtr<ITextChildProvider> nontext_child_of_root_text_child_provider_;
ComPtr<ITextChildProvider> text_child_of_root_text_child_provider_;
ComPtr<ITextChildProvider> nontext_child_of_nontext_text_child_provider_;
ComPtr<ITextChildProvider> text_child_of_nontext_text_child_provider_;
ComPtr<ITextChildProvider> text_child_of_text_text_child_provider_;
};
// ITextChildProvider::TextContainer Tests
//
// For each possible position in the tree verify:
// 1) A text container can/cannot be retrieved if an ancestor does/doesn't
// support the UIA Text control pattern.
// 2) Any retrieved text container is the nearest ancestor text container.
// 3) A Text control can in fact be retrieved from any retrieved text
// container.
TEST_F(AXPlatformNodeTextChildProviderTest,
ITextChildProviderTextContainerFromRoot) {
ComPtr<IRawElementProviderSimple> text_container;
EXPECT_HRESULT_SUCCEEDED(
root_text_child_provider_->get_TextContainer(&text_container));
ASSERT_EQ(nullptr, text_container.Get());
}
TEST_F(AXPlatformNodeTextChildProviderTest,
ITextChildProviderTextContainerFromNontextChildOfRoot) {
ComPtr<IRawElementProviderSimple> text_container;
EXPECT_HRESULT_SUCCEEDED(
nontext_child_of_root_text_child_provider_->get_TextContainer(
&text_container));
ASSERT_NE(nullptr, text_container.Get());
EXPECT_EQ(root_provider_raw_.Get(), text_container.Get());
ComPtr<IUnknown> pattern_provider;
ComPtr<ITextProvider> text_container_text_provider;
text_container->GetPatternProvider(UIA_TextPatternId, &pattern_provider);
ASSERT_NE(nullptr, pattern_provider.Get());
EXPECT_HRESULT_SUCCEEDED(pattern_provider.As(&text_container_text_provider));
}
TEST_F(AXPlatformNodeTextChildProviderTest,
ITextChildProviderTextContainerFromTextChildOfRoot) {
ComPtr<IRawElementProviderSimple> text_container;
EXPECT_HRESULT_SUCCEEDED(
text_child_of_root_text_child_provider_->get_TextContainer(
&text_container));
ASSERT_NE(nullptr, text_container.Get());
EXPECT_EQ(root_provider_raw_.Get(), text_container.Get());
ComPtr<IUnknown> pattern_provider;
ComPtr<ITextProvider> text_container_text_provider;
text_container->GetPatternProvider(UIA_TextPatternId, &pattern_provider);
ASSERT_NE(nullptr, pattern_provider.Get());
EXPECT_HRESULT_SUCCEEDED(pattern_provider.As(&text_container_text_provider));
}
TEST_F(AXPlatformNodeTextChildProviderTest,
ITextChildProviderTextContainerFromNontextChildOfNontext) {
ComPtr<IRawElementProviderSimple> text_container;
EXPECT_HRESULT_SUCCEEDED(
nontext_child_of_nontext_text_child_provider_->get_TextContainer(
&text_container));
ASSERT_NE(nullptr, text_container.Get());
EXPECT_EQ(root_provider_raw_.Get(), text_container.Get());
ComPtr<IUnknown> pattern_provider;
ComPtr<ITextProvider> text_container_text_provider;
text_container->GetPatternProvider(UIA_TextPatternId, &pattern_provider);
ASSERT_NE(nullptr, pattern_provider.Get());
EXPECT_HRESULT_SUCCEEDED(pattern_provider.As(&text_container_text_provider));
}
TEST_F(AXPlatformNodeTextChildProviderTest,
ITextChildProviderTextContainerFromTextChildOfNontext) {
ComPtr<IRawElementProviderSimple> text_container;
EXPECT_HRESULT_SUCCEEDED(
text_child_of_nontext_text_child_provider_->get_TextContainer(
&text_container));
ASSERT_NE(nullptr, text_container.Get());
EXPECT_EQ(root_provider_raw_.Get(), text_container.Get());
ComPtr<IUnknown> pattern_provider;
ComPtr<ITextProvider> text_container_text_provider;
text_container->GetPatternProvider(UIA_TextPatternId, &pattern_provider);
ASSERT_NE(nullptr, pattern_provider.Get());
EXPECT_HRESULT_SUCCEEDED(pattern_provider.As(&text_container_text_provider));
}
TEST_F(AXPlatformNodeTextChildProviderTest,
ITextChildProviderTextContainerFromTextChildOfText) {
ComPtr<IRawElementProviderSimple> text_container;
EXPECT_HRESULT_SUCCEEDED(
text_child_of_text_text_child_provider_->get_TextContainer(
&text_container));
ASSERT_NE(nullptr, text_container.Get());
EXPECT_EQ(text_child_of_root_text_provider_raw_.Get(), text_container.Get());
ComPtr<IUnknown> pattern_provider;
ComPtr<ITextProvider> text_container_text_provider;
text_container->GetPatternProvider(UIA_TextPatternId, &pattern_provider);
ASSERT_NE(nullptr, pattern_provider.Get());
EXPECT_HRESULT_SUCCEEDED(pattern_provider.As(&text_container_text_provider));
}
// ITextChildProvider::TextRange Tests
//
// For each possible position in the tree verify:
// 1) A text range can/cannot be retrieved if an ancestor does/doesn't
// support the UIA Text control pattern.
// 2) Any retrieved text range encloses the child element.
TEST_F(AXPlatformNodeTextChildProviderTest,
ITextChildProviderTextRangeFromRoot) {
ComPtr<ITextRangeProvider> text_range_provider;
EXPECT_HRESULT_SUCCEEDED(
root_text_child_provider_->get_TextRange(&text_range_provider));
EXPECT_EQ(nullptr, text_range_provider.Get());
}
TEST_F(AXPlatformNodeTextChildProviderTest,
ITextChildProviderTextRangeFromNontextChildOfRoot) {
ComPtr<ITextRangeProvider> text_range_provider;
EXPECT_HRESULT_SUCCEEDED(
nontext_child_of_root_text_child_provider_->get_TextRange(
&text_range_provider));
ASSERT_NE(nullptr, text_range_provider.Get());
AXPlatformNodeWin* owner =
static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(GetRoot()));
ASSERT_NE(nullptr, owner);
SetOwner(owner, text_range_provider.Get());
base::win::ScopedBstr text_content;
// By design, empty objects, such as the unlabelled image in this case, are
// placed in their own paragraph for easier screen reader navigation.
EXPECT_HRESULT_SUCCEEDED(
text_range_provider->GetText(-1, text_content.Receive()));
EXPECT_EQ(base::WideToUTF16(text_content.Get()),
u"non text child of nontext.\ntext child of nontext.\n" +
kEmbeddedCharacterAsString);
ComPtr<IRawElementProviderSimple> enclosing_element;
text_range_provider->GetEnclosingElement(&enclosing_element);
EXPECT_EQ(nontext_child_of_root_provider_raw_.Get(), enclosing_element.Get());
}
TEST_F(AXPlatformNodeTextChildProviderTest,
ITextChildProviderTextRangeFromTextChildOfRoot) {
ComPtr<ITextRangeProvider> text_range_provider;
EXPECT_HRESULT_SUCCEEDED(
text_child_of_root_text_child_provider_->get_TextRange(
&text_range_provider));
ASSERT_NE(nullptr, text_range_provider.Get());
AXPlatformNodeWin* owner =
static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(GetRoot()));
ASSERT_NE(nullptr, owner);
SetOwner(owner, text_range_provider.Get());
base::win::ScopedBstr text_content;
EXPECT_HRESULT_SUCCEEDED(
text_range_provider->GetText(-1, text_content.Receive()));
EXPECT_EQ(0, wcscmp(text_content.Get(), L"text child of text."));
ComPtr<IRawElementProviderSimple> enclosing_element;
text_range_provider->GetEnclosingElement(&enclosing_element);
EXPECT_EQ(text_child_of_root_text_provider_raw_.Get(),
enclosing_element.Get());
}
TEST_F(AXPlatformNodeTextChildProviderTest,
ITextChildProviderTextRangeFromNontextChildOfNontext) {
ComPtr<ITextRangeProvider> text_range_provider;
EXPECT_HRESULT_SUCCEEDED(
nontext_child_of_nontext_text_child_provider_->get_TextRange(
&text_range_provider));
ASSERT_NE(nullptr, text_range_provider.Get());
AXPlatformNodeWin* owner =
static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(GetRoot()));
ASSERT_NE(nullptr, owner);
SetOwner(owner, text_range_provider.Get());
base::win::ScopedBstr text_content;
EXPECT_HRESULT_SUCCEEDED(
text_range_provider->GetText(-1, text_content.Receive()));
EXPECT_EQ(base::WideToUTF16(text_content.Get()),
u"non text child of nontext.");
ComPtr<IRawElementProviderSimple> enclosing_element;
text_range_provider->GetEnclosingElement(&enclosing_element);
EXPECT_EQ(nontext_child_of_nontext_text_provider_raw_.Get(),
enclosing_element.Get());
}
TEST_F(AXPlatformNodeTextChildProviderTest,
ITextChildProviderTextRangeFromTextChildOfNontext) {
ComPtr<ITextRangeProvider> text_range_provider;
EXPECT_HRESULT_SUCCEEDED(
text_child_of_nontext_text_child_provider_->get_TextRange(
&text_range_provider));
ASSERT_NE(nullptr, text_range_provider.Get());
AXPlatformNodeWin* owner =
static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(GetRoot()));
ASSERT_NE(nullptr, owner);
SetOwner(owner, text_range_provider.Get());
base::win::ScopedBstr text_content;
EXPECT_HRESULT_SUCCEEDED(
text_range_provider->GetText(-1, text_content.Receive()));
EXPECT_EQ(0, wcscmp(text_content.Get(), L"text child of nontext."));
ComPtr<IRawElementProviderSimple> enclosing_element;
text_range_provider->GetEnclosingElement(&enclosing_element);
EXPECT_EQ(text_child_of_nontext_text_provider_raw_.Get(),
enclosing_element.Get());
}
TEST_F(AXPlatformNodeTextChildProviderTest,
ITextChildProviderTextRangeFromTextChildOfText) {
ComPtr<ITextRangeProvider> text_range_provider;
EXPECT_HRESULT_SUCCEEDED(
text_child_of_text_text_child_provider_->get_TextRange(
&text_range_provider));
ASSERT_NE(nullptr, text_range_provider.Get());
AXPlatformNodeWin* owner =
static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(GetRoot()));
ASSERT_NE(nullptr, owner);
SetOwner(owner, text_range_provider.Get());
base::win::ScopedBstr text_content;
EXPECT_HRESULT_SUCCEEDED(
text_range_provider->GetText(-1, text_content.Receive()));
EXPECT_EQ(0, wcscmp(text_content.Get(), L"text child of text."));
ComPtr<IRawElementProviderSimple> enclosing_element;
text_range_provider->GetEnclosingElement(&enclosing_element);
EXPECT_EQ(text_child_of_root_text_provider_raw_.Get(),
enclosing_element.Get());
}
// ITextChildProvider Tests - Inactive AX Tree
//
// Test that both ITextChildProvider::GetTextContainer and
// ITextChildProvider::GetTextContainer fail under an inactive AX tree.
TEST_F(AXPlatformNodeTextChildProviderTest,
ITextChildProviderInactiveAccessibilityTree) {
DestroyTree();
// Test that GetTextContainer fails under an inactive tree.
ComPtr<IRawElementProviderSimple> text_container;
HRESULT hr = nontext_child_of_root_text_child_provider_->get_TextContainer(
&text_container);
EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE), hr);
// Test that GetTextRange fails under an inactive tree.
ComPtr<ITextRangeProvider> text_range_provider;
hr = nontext_child_of_root_text_child_provider_->get_TextRange(
&text_range_provider);
EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE), hr);
}
} // namespace ui