blob: 3101501baca1dbb5d0099c73fad21cf52fdf9c19 [file] [log] [blame]
// Copyright 2020 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 <sstream>
#include "base/strings/sys_string_conversions.h"
#include "content/browser/accessibility/accessibility_tools_utils_mac.h"
#include "content/browser/accessibility/accessibility_tree_formatter_utils_mac.h"
#include "content/browser/accessibility/browser_accessibility_mac.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/test/accessibility_notification_waiter.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/dump_accessibility_test_helper.h"
#include "content/shell/browser/shell.h"
#include "net/base/filename_util.h"
using content::a11y::LineIndexer;
using content::a11y::AttributeInvoker;
using content::a11y::OptionalNSObject;
using content::a11y::AttributeValueOf;
namespace content {
namespace {
class AccessibilityScriptsMacBrowserTest : public ContentBrowserTest {
public:
AccessibilityScriptsMacBrowserTest()
: test_file_path_(),
root_(nullptr),
line_indexer_(nullptr),
script_output_(),
formatter_(AXInspectFactory::CreatePlatformFormatter()),
helper_("mac") {
// Set property filters.
std::vector<ui::AXPropertyFilter> property_filters;
formatter_->SetPropertyFilters(property_filters,
ui::AXTreeFormatter::kFiltersDefaultSet);
}
void LoadFile(const std::string& file);
void AssertOutputMatchesExpectations();
protected:
AttributeInvoker GetInvokerAndAssertRole(
const std::string& line_index,
const std::string& expected_role) const {
return AttributeInvoker(
GetNativeNodeAndAssertRole(line_index, expected_role),
line_indexer_.get());
}
OptionalNSObject GetNodeAndAssertRole(
const std::string& line_index,
const std::string& expected_role) const {
return OptionalNSObject::NotNilOrError(
GetNativeNodeAndAssertRole(line_index, expected_role));
}
void Print(const OptionalNSObject& object) {
script_output_ << object.ToString() << "\n";
}
void WaitForEvent(ax::mojom::Event event) const {
AccessibilityNotificationWaiter waiter(shell()->web_contents(),
ui::kAXModeComplete, event);
waiter.WaitForNotification();
}
private:
BrowserAccessibilityManager* GetManager() const {
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
return web_contents->GetRootBrowserAccessibilityManager();
}
gfx::NativeViewAccessible GetNativeNodeAndAssertRole(
const std::string& line_index,
const std::string& expected_role) const;
base::FilePath test_file_path_;
BrowserAccessibility* root_;
std::unique_ptr<LineIndexer> line_indexer_;
std::ostringstream script_output_;
std::unique_ptr<ui::AXTreeFormatter> formatter_;
DumpAccessibilityTestHelper helper_;
};
gfx::NativeViewAccessible
AccessibilityScriptsMacBrowserTest::GetNativeNodeAndAssertRole(
const std::string& line_index,
const std::string& expected_role) const {
gfx::NativeViewAccessible node = line_indexer_->NodeBy(line_index);
NSString* role_nsstring =
AttributeValueOf(node, NSAccessibilityRoleAttribute);
std::string role_string = base::SysNSStringToUTF8(role_nsstring);
EXPECT_EQ(role_string, expected_role);
return node;
}
void AccessibilityScriptsMacBrowserTest::LoadFile(const std::string& file) {
ASSERT_FALSE(file.empty());
ASSERT_TRUE(test_file_path_.empty());
ASSERT_EQ(nullptr, root_);
ASSERT_EQ(nullptr, line_indexer_.get());
std::string dir = base::FilePath()
.AppendASCII("accessibility")
.AppendASCII("scripts")
.MaybeAsASCII();
test_file_path_ = GetTestFilePath(dir.c_str(), file.c_str());
ASSERT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
AccessibilityNotificationWaiter waiter(shell()->web_contents(),
ui::kAXModeComplete,
ax::mojom::Event::kLoadComplete);
ASSERT_TRUE(NavigateToURL(shell(), net::FilePathToFileURL(test_file_path_)));
waiter.WaitForNotification();
root_ = GetManager()->GetRoot();
ASSERT_NE(nullptr, root_);
BrowserAccessibilityCocoa* cocoa_root = ToBrowserAccessibilityCocoa(root_);
line_indexer_ = std::make_unique<LineIndexer>(cocoa_root);
}
void AccessibilityScriptsMacBrowserTest::AssertOutputMatchesExpectations() {
// Collect the tree dump and script output.
std::ostringstream output;
{
output << formatter_->Format(root_);
output << script_output_.str();
}
std::vector<std::string> lines = base::SplitString(
output.str(), "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
// Load expected output lines.
base::FilePath expectation_file =
helper_.GetExpectationFilePath(test_file_path_);
EXPECT_FALSE(expectation_file.empty());
absl::optional<std::vector<std::string>> expected_lines =
helper_.LoadExpectationFile(expectation_file);
EXPECT_TRUE(expected_lines.has_value());
bool matches_expectation = helper_.ValidateAgainstExpectation(
test_file_path_, expectation_file, lines, *expected_lines);
EXPECT_TRUE(matches_expectation);
}
} // namespace
IN_PROC_BROWSER_TEST_F(AccessibilityScriptsMacBrowserTest,
SetSelectedTextMarkerRange_ContentEditable) {
LoadFile("set-selection-contenteditable.html");
// Select the 1st word via text marker associated with container AXTextArea.
{
AttributeInvoker textarea = GetInvokerAndAssertRole(":2", "AXTextArea");
OptionalNSObject text_range =
textarea.GetValue("AXTextMarkerRangeForUIElement",
GetNodeAndAssertRole(":2", "AXTextArea"));
OptionalNSObject marker_0 = a11y::TextMarkerRangeGetStartMarker(text_range);
OptionalNSObject marker_1 =
textarea.GetValue("AXNextWordEndTextMarkerForTextMarker", marker_0);
OptionalNSObject target_selected_marker_range =
textarea.GetValue("AXTextMarkerRangeForUnorderedTextMarkers",
a11y::MakePairArray(marker_0, marker_1));
textarea.SetValue("AXSelectedTextMarkerRange",
target_selected_marker_range);
WaitForEvent(ax::mojom::Event::kTextSelectionChanged);
OptionalNSObject selected_text = textarea.GetValue("AXSelectedText");
Print(selected_text);
}
// Select the 2nd word via the marker associated with AXStaticText.
{
AttributeInvoker textarea = GetInvokerAndAssertRole(":2", "AXTextArea");
AttributeInvoker text = GetInvokerAndAssertRole(":4", "AXStaticText");
OptionalNSObject text_range =
text.GetValue("AXTextMarkerRangeForUIElement",
GetNodeAndAssertRole(":4", "AXStaticText"));
OptionalNSObject marker_0 = a11y::TextMarkerRangeGetStartMarker(text_range);
OptionalNSObject marker_1 =
text.GetValue("AXNextWordEndTextMarkerForTextMarker", marker_0);
OptionalNSObject marker_2 =
text.GetValue("AXNextWordEndTextMarkerForTextMarker", marker_1);
OptionalNSObject marker_3 =
text.GetValue("AXPreviousWordStartTextMarkerForTextMarker", marker_2);
OptionalNSObject target_selected_marker_range =
text.GetValue("AXTextMarkerRangeForUnorderedTextMarkers",
a11y::MakePairArray(marker_2, marker_3));
textarea.SetValue("AXSelectedTextMarkerRange",
target_selected_marker_range);
WaitForEvent(ax::mojom::Event::kTextSelectionChanged);
OptionalNSObject selected_text = textarea.GetValue("AXSelectedText");
Print(selected_text);
}
// Select text inside a span.
{
AttributeInvoker textarea = GetInvokerAndAssertRole(":2", "AXTextArea");
AttributeInvoker text = GetInvokerAndAssertRole(":8", "AXStaticText");
OptionalNSObject text_range =
text.GetValue("AXTextMarkerRangeForUIElement",
GetNodeAndAssertRole(":8", "AXStaticText"));
OptionalNSObject marker_0 = a11y::TextMarkerRangeGetStartMarker(text_range);
OptionalNSObject marker_1 = a11y::TextMarkerRangeGetEndMarker(text_range);
OptionalNSObject marker_2 =
text.GetValue("AXPreviousTextMarkerForTextMarker", marker_1);
OptionalNSObject target_selected_marker_range =
text.GetValue("AXTextMarkerRangeForUnorderedTextMarkers",
a11y::MakePairArray(marker_0, marker_2));
textarea.SetValue("AXSelectedTextMarkerRange",
target_selected_marker_range);
WaitForEvent(ax::mojom::Event::kTextSelectionChanged);
OptionalNSObject selected_text = textarea.GetValue("AXSelectedText");
Print(selected_text);
}
// Select the text inside the block with incorrect grammar.
{
AttributeInvoker textarea = GetInvokerAndAssertRole(":2", "AXTextArea");
AttributeInvoker text = GetInvokerAndAssertRole(":6", "AXStaticText");
OptionalNSObject text_range =
text.GetValue("AXTextMarkerRangeForUIElement",
GetNodeAndAssertRole(":6", "AXStaticText"));
OptionalNSObject target_selected_marker_range = text_range;
textarea.SetValue("AXSelectedTextMarkerRange",
target_selected_marker_range);
WaitForEvent(ax::mojom::Event::kTextSelectionChanged);
OptionalNSObject selected_text = textarea.GetValue("AXSelectedText");
Print(selected_text);
}
// Select the text after a forced break.
{
AttributeInvoker textarea = GetInvokerAndAssertRole(":10", "AXTextArea");
AttributeInvoker text = GetInvokerAndAssertRole(":14", "AXStaticText");
OptionalNSObject text_range =
text.GetValue("AXTextMarkerRangeForUIElement",
GetNodeAndAssertRole(":14", "AXStaticText"));
OptionalNSObject marker_0 = a11y::TextMarkerRangeGetStartMarker(text_range);
OptionalNSObject marker_1 =
text.GetValue("AXNextWordEndTextMarkerForTextMarker", marker_0);
OptionalNSObject target_selected_marker_range =
text.GetValue("AXTextMarkerRangeForUnorderedTextMarkers",
a11y::MakePairArray(marker_0, marker_1));
textarea.SetValue("AXSelectedTextMarkerRange",
target_selected_marker_range);
WaitForEvent(ax::mojom::Event::kTextSelectionChanged);
OptionalNSObject selected_text = textarea.GetValue("AXSelectedText");
Print(selected_text);
}
AssertOutputMatchesExpectations();
}
IN_PROC_BROWSER_TEST_F(AccessibilityScriptsMacBrowserTest,
SetSelectedTextMarkerRange_TextArea) {
LoadFile("set-selection-textarea.html");
// Select the 3rd word.
{
AttributeInvoker textarea = GetInvokerAndAssertRole(":3", "AXTextArea");
OptionalNSObject text_range =
textarea.GetValue("AXTextMarkerRangeForUIElement",
GetNodeAndAssertRole(":3", "AXTextArea"));
OptionalNSObject marker_0 = a11y::TextMarkerRangeGetStartMarker(text_range);
OptionalNSObject marker_1 =
textarea.GetValue("AXNextWordEndTextMarkerForTextMarker", marker_0);
OptionalNSObject marker_2 =
textarea.GetValue("AXNextWordEndTextMarkerForTextMarker", marker_1);
OptionalNSObject marker_3 =
textarea.GetValue("AXNextWordEndTextMarkerForTextMarker", marker_2);
OptionalNSObject marker_4 = textarea.GetValue(
"AXPreviousWordStartTextMarkerForTextMarker", marker_3);
OptionalNSObject target_selected_marker_range =
textarea.GetValue("AXTextMarkerRangeForUnorderedTextMarkers",
a11y::MakePairArray(marker_3, marker_4));
textarea.SetValue("AXSelectedTextMarkerRange",
target_selected_marker_range);
WaitForEvent(ax::mojom::Event::kTextSelectionChanged);
OptionalNSObject selected_text = textarea.GetValue("AXSelectedText");
Print(selected_text);
}
AssertOutputMatchesExpectations();
}
IN_PROC_BROWSER_TEST_F(AccessibilityScriptsMacBrowserTest,
SetSelectedTextRange_ContentEditable) {
LoadFile("set-selectedtextrange-contenteditable.html");
// AXValue='The quick brown foxes jumps over the lazy dog'
AttributeInvoker textarea = GetInvokerAndAssertRole(":2", "AXTextArea");
// select 1st word
{
OptionalNSObject range{[NSValue valueWithRange:NSMakeRange(0, 3)]};
textarea.SetValue("AXSelectedTextRange", range);
WaitForEvent(ax::mojom::Event::kTextSelectionChanged);
OptionalNSObject selected_text = textarea.GetValue("AXSelectedText");
Print(selected_text);
}
// select text inside span
{
OptionalNSObject range{[NSValue valueWithRange:NSMakeRange(22, 4)]};
textarea.SetValue("AXSelectedTextRange", range);
WaitForEvent(ax::mojom::Event::kTextSelectionChanged);
OptionalNSObject selected_text = textarea.GetValue("AXSelectedText");
Print(selected_text);
}
// select text across several elements
{
OptionalNSObject range{[NSValue valueWithRange:NSMakeRange(24, 15)]};
textarea.SetValue("AXSelectedTextRange", range);
WaitForEvent(ax::mojom::Event::kTextSelectionChanged);
OptionalNSObject selected_text = textarea.GetValue("AXSelectedText");
Print(selected_text);
}
AssertOutputMatchesExpectations();
}
} // namespace content