Skip to content

Commit

Permalink
Fix setting AXSelectedTextRange attribute on atomic text fields.
Browse files Browse the repository at this point in the history
It turned out that https://crrev.com/c/3059070 didn't fix the original
issue #1233944: that CL only covered the case of pre-populated <textarea>,
whereas if multi-line text is produced via user input, setting
AXSelectedTextRange accessibility attribute still has the wrong effect.

Bug: 1233944
Change-Id: Idf022abcd4beaf8007e6a3abc986d413f39bd1af
AX-Relnotes: n/a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3579339
Reviewed-by: Nektarios Paisios <nektar@chromium.org>
Commit-Queue: Nektarios Paisios <nektar@chromium.org>
Reviewed-by: Mark Schillaci <mschillaci@google.com>
Cr-Commit-Position: refs/heads/main@{#1002440}
  • Loading branch information
Ievgen Rysai authored and Chromium LUCI CQ committed May 12, 2022
1 parent f659c15 commit 0118ad2
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@
#include "content/browser/accessibility/dump_accessibility_browsertest_base.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
#include "ui/accessibility/platform/inspect/ax_api_type.h"
#include "ui/accessibility/platform/inspect/ax_script_instruction.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/keycodes/keyboard_code_conversion.h"

namespace content {

Expand Down Expand Up @@ -51,31 +54,29 @@ class DumpAccessibilityScriptTest : public DumpAccessibilityTestBase {
// from scenario directives.
test_helper_.OverrideExpectationType("content");
}
~DumpAccessibilityScriptTest() = default;
DumpAccessibilityScriptTest(const DumpAccessibilityScriptTest&) = delete;
DumpAccessibilityScriptTest& operator=(const DumpAccessibilityScriptTest&) =
delete;

protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
// Enable MathMLCore for some MathML tests.
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kEnableBlinkFeatures, "MathMLCore");
}

std::vector<ui::AXPropertyFilter> DefaultFilters() const override;
std::vector<ui::AXPropertyFilter> DefaultFilters() const override {
return {};
}

void AddPropertyFilter(
std::vector<AXPropertyFilter>* property_filters,
const std::string& filter,
AXPropertyFilter::Type type = AXPropertyFilter::ALLOW) {
property_filters->push_back(AXPropertyFilter(filter, type));
}

base::Value EvaluateScript(
AXTreeFormatter* formatter,
BrowserAccessibility* root,
const std::vector<AXScriptInstruction>& instructions,
size_t start_index,
size_t end_index) {
return base::Value(
formatter->EvaluateScript(root, instructions, start_index, end_index));
}

std::vector<std::string> Dump() override {
std::vector<std::string> dump;
std::unique_ptr<AXTreeFormatter> formatter(CreateFormatter());
Expand All @@ -85,14 +86,21 @@ class DumpAccessibilityScriptTest : public DumpAccessibilityTestBase {
size_t length = scenario_.script_instructions.size();
while (start_index < length) {
std::string wait_for;
std::string dom_key_string;
bool printTree = false;
size_t index = start_index;
for (; index < length; index++) {
if (scenario_.script_instructions[index].IsEvent()) {
wait_for = scenario_.script_instructions[index].AsEvent();
const AXScriptInstruction& instruction =
scenario_.script_instructions[index];
if (instruction.IsEvent()) {
wait_for = instruction.AsEvent();
break;
}
if (scenario_.script_instructions[index].IsPrintTree()) {
if (instruction.IsKeyEvent()) {
dom_key_string = instruction.AsDomKeyString();
break;
}
if (instruction.IsPrintTree()) {
printTree = true;
break;
}
Expand All @@ -115,6 +123,20 @@ class DumpAccessibilityScriptTest : public DumpAccessibilityTestBase {
}
}

if (!dom_key_string.empty()) {
ui::DomKey dom_key =
ui::KeycodeConverter::KeyStringToDomKey(dom_key_string);
if (dom_key != ui::DomKey::NONE) {
ui::DomCode dom_code =
ui::KeycodeConverter::CodeStringToDomCode(dom_key_string);
SimulateKeyPress(GetWebContents(), dom_key, dom_code,
ui::DomCodeToUsLayoutKeyboardCode(dom_code),
/* control */ false, /* shift */ false,
/* alt */ false, /* command */ false);
}
actual_contents += "press " + dom_key_string + '\n';
RunUntilInputProcessed(GetWidgetHost());
}
if (printTree) {
actual_contents += DumpTreeAsString() + '\n';
}
Expand All @@ -128,12 +150,21 @@ class DumpAccessibilityScriptTest : public DumpAccessibilityTestBase {
}
return dump;
}
};

std::vector<ui::AXPropertyFilter> DumpAccessibilityScriptTest::DefaultFilters()
const {
return {};
}
base::Value EvaluateScript(
AXTreeFormatter* formatter,
BrowserAccessibility* root,
const std::vector<AXScriptInstruction>& instructions,
size_t start_index,
size_t end_index) {
return base::Value(
formatter->EvaluateScript(root, instructions, start_index, end_index));
}

RenderWidgetHost* GetWidgetHost() {
return GetWebContents()->GetMainFrame()->GetRenderViewHost()->GetWidget();
}
};

// Parameterize the tests so that each test-pass is run independently.
struct TestPassToString {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,12 @@ textarea.AXSelectedText='brown'
textarea.AXSelectedTextRange={loc: 20, len: 5}
AXSelectedTextChanged on AXTextArea AXValue='The quick brown fox jumps over the lazy dog<newline>' AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeUnknown
textarea.AXSelectedText='jumps'
// Put cursor after the 2nd word.
textarea.AXSelectedTextRange={loc: 10, len: 0}
AXSelectedTextChanged on AXTextArea AXValue='The quick brown fox jumps over the lazy dog<newline>' AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeUnknown
// Force line break.
press Enter
// Select text on the 2nd line (fox).
textarea.AXSelectedTextRange={loc: 17, len: 3}
AXSelectedTextChanged on AXTextArea AXValue='The quick <newline>brown fox jumps over the lazy dog<newline>' AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeUnknown
textarea.AXSelectedText='fox'
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@
textarea.AXSelectedTextRange = {loc: 20, len: 5}
wait for AXSelectedTextChanged on AXTextArea
textarea.AXSelectedText
@SCRIPT:
// Put cursor after the 2nd word.
textarea.AXSelectedTextRange = {loc: 10, len: 0}
wait for AXSelectedTextChanged on AXTextArea
// Force line break.
press Enter
// Select text on the 2nd line (fox).
textarea.AXSelectedTextRange = {loc: 17, len: 3}
wait for AXSelectedTextChanged on AXTextArea
textarea.AXSelectedText
-->
<!DOCTYPE html>
<html>
Expand Down
10 changes: 10 additions & 0 deletions content/test/data/accessibility/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,16 @@ will trigger `AXPress` action on a button and will wait for
and provide the event target. For example:
`wait for AXFocusedUIElementChanged on AXButton`

You can use `press` instruction to simulate key events.
The instruction accepts a single parameter which could be a character or
a key name (as specified in http://www.w3.org/TR/DOM-Level-3-Events-key/).
For example,
```
@SCRIPT:
press Enter
wait for AXValueChanged
```

You can use `print tree` to print a snapshot of an accessible tree. For example,
```
@SCRIPT:
Expand Down
8 changes: 1 addition & 7 deletions ui/accessibility/ax_position.h
Original file line number Diff line number Diff line change
Expand Up @@ -1644,14 +1644,8 @@ class AXPosition {
// When blink is asked to set selection, it expects a text position to be
// anchored to the text node (otherwise a generic tree position is assumed
// and the offset is interpreted as a child index).
//
// Using just AsLeafTextPosition() for sanitizing does not work on plain
// text-fields: an attempt to select the text beyond the first line results
// in a wrong selection which looks as if the text offset was counted through
// the first line only.
// TODO(nektar): Make this work in plain text fields too.
AXPositionInstance AsDomSelectionPosition() const {
if (IsNullPosition())
if (IsNullPosition() || GetAnchor()->data().IsAtomicTextField())
return Clone();

AXPositionInstance text_position = AsLeafTextPosition();
Expand Down
18 changes: 14 additions & 4 deletions ui/accessibility/platform/inspect/ax_script_instruction.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,23 @@

namespace ui {

const char kPrintTree[] = "print tree";
const char kWaitFor[] = "wait for ";
const size_t kWaitForLength = sizeof(kWaitFor) / sizeof(kWaitFor[0]) - 1;
constexpr char kPrintTree[] = "print tree";
constexpr char kWaitFor[] = "wait for ";
constexpr size_t kWaitForLength = sizeof(kWaitFor) / sizeof(kWaitFor[0]) - 1;
constexpr char kPress[] = "press ";
constexpr size_t kPressLength = sizeof(kPress) / sizeof(kPress[0]) - 1;

AXScriptInstruction::AXScriptInstruction(const std::string& instruction)
: instruction_(instruction) {}

bool AXScriptInstruction::IsEvent() const {
return !IsComment() && EventNameStartIndex() != std::string::npos;
}
bool AXScriptInstruction::IsKeyEvent() const {
return base::StartsWith(instruction_, kPress);
}
bool AXScriptInstruction::IsScript() const {
return !IsComment() && !IsEvent() && !IsPrintTree();
return !IsComment() && !IsEvent() && !IsKeyEvent() && !IsPrintTree();
}
bool AXScriptInstruction::IsComment() const {
return base::StartsWith(instruction_, "//");
Expand All @@ -43,6 +48,11 @@ std::string AXScriptInstruction::AsEvent() const {
return instruction_.substr(kWaitForLength);
}

std::string AXScriptInstruction::AsDomKeyString() const {
DCHECK(IsKeyEvent());
return instruction_.substr(kPressLength);
}

std::string AXScriptInstruction::AsComment() const {
DCHECK(IsComment());
return instruction_;
Expand Down
5 changes: 5 additions & 0 deletions ui/accessibility/platform/inspect/ax_script_instruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,16 @@ class AX_EXPORT AXScriptInstruction final {
explicit AXScriptInstruction(const std::string& instruction);

bool IsEvent() const;
bool IsKeyEvent() const;
bool IsScript() const;
bool IsComment() const;
bool IsPrintTree() const;

AXPropertyNode AsScript() const;
// Returns a character string containing either
// - a key name from http://www.w3.org/TR/DOM-Level-3-Events-key/, or
// - a single Unicode character (represented in UTF-8).
std::string AsDomKeyString() const;
std::string AsEvent() const;
std::string AsComment() const;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,58 @@ namespace ui {
TEST(AXScriptInstructionTest, Parse) {
AXScriptInstruction script("textbox.AXRole");
EXPECT_TRUE(script.IsScript());
EXPECT_FALSE(script.IsEvent());
EXPECT_FALSE(script.IsKeyEvent());
EXPECT_FALSE(script.IsKeyEvent());
EXPECT_FALSE(script.IsComment());
EXPECT_FALSE(script.IsPrintTree());
EXPECT_EQ(script.AsScript().ToString(), "textbox.AXRole");

AXScriptInstruction event("wait for AXTitleChange");
EXPECT_TRUE(event.IsEvent());
EXPECT_FALSE(event.IsKeyEvent());
EXPECT_FALSE(event.IsScript());
EXPECT_FALSE(event.IsComment());
EXPECT_FALSE(event.IsPrintTree());
EXPECT_EQ(event.AsEvent(), "AXTitleChange");

AXScriptInstruction event2("wait for AXTitleChange on AXButton");
EXPECT_TRUE(event2.IsEvent());
EXPECT_FALSE(event2.IsKeyEvent());
EXPECT_FALSE(event2.IsScript());
EXPECT_FALSE(event2.IsComment());
EXPECT_FALSE(event2.IsPrintTree());
EXPECT_EQ(event2.AsEvent(), "AXTitleChange on AXButton");

AXScriptInstruction printTree("print tree");
EXPECT_FALSE(printTree.IsEvent());
EXPECT_FALSE(printTree.IsKeyEvent());
EXPECT_FALSE(printTree.IsScript());
EXPECT_FALSE(printTree.IsComment());
EXPECT_TRUE(printTree.IsPrintTree());

AXScriptInstruction comment("// wait for AXTitleChange");
EXPECT_TRUE(comment.IsComment());
EXPECT_FALSE(comment.IsEvent());
EXPECT_FALSE(comment.IsKeyEvent());
EXPECT_FALSE(comment.IsScript());
EXPECT_FALSE(comment.IsPrintTree());
EXPECT_EQ(comment.AsComment(), "// wait for AXTitleChange");

AXScriptInstruction comment2("// press Enter");
EXPECT_TRUE(comment2.IsComment());
EXPECT_FALSE(comment2.IsEvent());
EXPECT_FALSE(comment2.IsKeyEvent());
EXPECT_FALSE(comment2.IsScript());
EXPECT_FALSE(comment2.IsPrintTree());
EXPECT_EQ(comment2.AsComment(), "// press Enter");

AXScriptInstruction keypress("press Enter");
EXPECT_TRUE(keypress.IsKeyEvent());
EXPECT_FALSE(keypress.IsComment());
EXPECT_FALSE(keypress.IsEvent());
EXPECT_FALSE(keypress.IsScript());
EXPECT_FALSE(keypress.IsPrintTree());
EXPECT_EQ(keypress.AsDomKeyString(), "Enter");
}

} // namespace ui

0 comments on commit 0118ad2

Please sign in to comment.