Commit 85cff993 authored by Kurt Catti-Schmidt (SCHMIDT)'s avatar Kurt Catti-Schmidt (SCHMIDT) Committed by Commit Bot

Add test for UIA GetEnclosingElement with trailing newlines

This change originally excluded newlines from the start and end of all
TextPattern API's. However, upon rebase, I discovered that this issue
was fixed via this change:

https://chromium-review.googlesource.com/c/chromium/src/+/2346653

...so that is no longer necessary. Instead, this change adds a test
to verify this important editing scenario as well as some minor
refactoring/cleanup.

Ax-Relnotes: N/A

Bug: 1049682
Change-Id: Ic20f36793484daa5b7043b1197697969e20244f0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2363369
Commit-Queue: Kurt Catti-Schmidt <kschmi@microsoft.com>
Reviewed-by: default avatarKevin Babbitt <kbabbitt@microsoft.com>
Reviewed-by: default avatarEthan Jimenez <ethavar@microsoft.com>
Reviewed-by: default avatarBenjamin Beaudry <benjamin.beaudry@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#804319}
parent 07223901
...@@ -651,6 +651,84 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, ...@@ -651,6 +651,84 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
ASSERT_EQ(1, result); ASSERT_EQ(1, result);
} }
IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
TextInputWithNewline) {
LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML(
<!DOCTYPE html>
<html>
<body>
<div aria-value='wrapper'>
<input type='text' aria-label='input_text'><br>
</div>
</body>
</html>
)HTML"));
// This test validates an important scenario for editing. UIA clients such as
// Narrator expect newlines to be contained within their adjacent nodes.
// This test validates this scenario for GetEnclosingElement and
// GetAttributeValue, both of which are essential for text editing scenarios.
//
// In order for the test harness to effectively simulate typing in a text
// input, first change the value of the text input and then focus it. Only
// editing the value won't show the cursor and only focusing will put the
// cursor at the beginning of the text input, so both steps are necessary.
auto* input_text_node = FindNode(ax::mojom::Role::kTextField, "input_text");
ASSERT_NE(nullptr, input_text_node);
EXPECT_TRUE(input_text_node->PlatformIsLeaf());
EXPECT_EQ(0u, input_text_node->PlatformChildCount());
AccessibilityNotificationWaiter edit_waiter(shell()->web_contents(),
ui::kAXModeComplete,
ax::mojom::Event::kValueChanged);
ui::AXActionData edit_data;
edit_data.target_node_id = input_text_node->GetId();
edit_data.action = ax::mojom::Action::kSetValue;
edit_data.value = "test";
input_text_node->AccessibilityPerformAction(edit_data);
edit_waiter.WaitForNotification();
AccessibilityNotificationWaiter focus_waiter(
shell()->web_contents(), ui::kAXModeComplete, ax::mojom::Event::kFocus);
ui::AXActionData focus_data;
focus_data.target_node_id = input_text_node->GetId();
focus_data.action = ax::mojom::Action::kFocus;
input_text_node->AccessibilityPerformAction(focus_data);
focus_waiter.WaitForNotification();
ComPtr<ITextRangeProvider> text_range_provider;
GetTextRangeProviderFromTextNode(*input_text_node, &text_range_provider);
ASSERT_NE(nullptr, text_range_provider.Get());
EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"test");
// Move the first position so that both endpoints are at the end of the text
// input.
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
/*count*/ 4,
/*expected_text*/ L"",
/*expected_count*/ 4);
ComPtr<IRawElementProviderSimple> text_input_provider =
QueryInterfaceFromNode<IRawElementProviderSimple>(input_text_node);
// Validate that the enclosing element is the text input node and not the
// parent node that includes the newline.
ComPtr<IRawElementProviderSimple> enclosing_element;
ASSERT_HRESULT_SUCCEEDED(
text_range_provider->GetEnclosingElement(&enclosing_element));
EXPECT_EQ(text_input_provider.Get(), enclosing_element.Get());
// Calling GetAttributeValue on the editable text input should return false,
// verifying that the read-only newline is not interfering.
base::win::ScopedVariant value;
EXPECT_HRESULT_SUCCEEDED(text_range_provider->GetAttributeValue(
UIA_IsReadOnlyAttributeId, value.Receive()));
EXPECT_EQ(value.type(), VT_BOOL);
EXPECT_EQ(V_BOOL(value.ptr()), VARIANT_FALSE);
value.Reset();
}
IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest,
GetBoundingRectangles) { GetBoundingRectangles) {
LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML( LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML(
......
...@@ -850,13 +850,15 @@ HRESULT AXPlatformNodeTextRangeProviderWin::Select() { ...@@ -850,13 +850,15 @@ HRESULT AXPlatformNodeTextRangeProviderWin::Select() {
HRESULT AXPlatformNodeTextRangeProviderWin::AddToSelection() { HRESULT AXPlatformNodeTextRangeProviderWin::AddToSelection() {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_ADDTOSELECTION); WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_ADDTOSELECTION);
return UIA_E_INVALIDOPERATION; // not supporting disjoint text selections // Blink does not support disjoint text selections.
return UIA_E_INVALIDOPERATION;
} }
HRESULT HRESULT
AXPlatformNodeTextRangeProviderWin::RemoveFromSelection() { AXPlatformNodeTextRangeProviderWin::RemoveFromSelection() {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_REMOVEFROMSELECTION); WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_REMOVEFROMSELECTION);
return UIA_E_INVALIDOPERATION; // not supporting disjoint text selections // Blink does not support disjoint text selections.
return UIA_E_INVALIDOPERATION;
} }
HRESULT AXPlatformNodeTextRangeProviderWin::ScrollIntoView(BOOL align_to_top) { HRESULT AXPlatformNodeTextRangeProviderWin::ScrollIntoView(BOOL align_to_top) {
...@@ -1185,21 +1187,12 @@ void AXPlatformNodeTextRangeProviderWin::NormalizeTextRange() { ...@@ -1185,21 +1187,12 @@ void AXPlatformNodeTextRangeProviderWin::NormalizeTextRange() {
// first snap them both to be unignored positions. // first snap them both to be unignored positions.
NormalizeAsUnignoredTextRange(); NormalizeAsUnignoredTextRange();
// Do not normalize text ranges when a cursor or selection is visible. ATs // When carets are visible or selections are occurring, the precise state of
// may depend on the specific position that the caret or selection is at. This // the TextPattern must be preserved so that the UIA client can handle
// condition fixes issues when the caret is inside a plain text field, but // scenarios such as determining which characters were deleted. So
// causes more issues when used inside of a rich text field. For this reason, // normalization must be bypassed.
// if we have a caret or a selection inside of an editable node, restrict this if (HasCaretOrSelectionInPlainTextField(start_) ||
// to a plain text field as we gain nothing from using it in a rich text HasCaretOrSelectionInPlainTextField(end_)) {
// field.
AXPlatformNodeDelegate* start_delegate = GetDelegate(start_.get());
AXPlatformNodeDelegate* end_delegate = GetDelegate(end_.get());
if ((start_delegate && start_delegate->HasVisibleCaretOrSelection() &&
(!start_delegate->GetData().HasState(ax::mojom::State::kEditable) ||
start_delegate->IsChildOfPlainTextField())) ||
(end_delegate && end_delegate->HasVisibleCaretOrSelection() &&
(!end_delegate->GetData().HasState(ax::mojom::State::kEditable) ||
end_delegate->IsChildOfPlainTextField()))) {
return; return;
} }
...@@ -1339,6 +1332,24 @@ AXPlatformNodeTextRangeProviderWin::GetLowestAccessibleCommonPlatformNode() ...@@ -1339,6 +1332,24 @@ AXPlatformNodeTextRangeProviderWin::GetLowestAccessibleCommonPlatformNode()
return platform_node->GetLowestAccessibleElement(); return platform_node->GetLowestAccessibleElement();
} }
bool AXPlatformNodeTextRangeProviderWin::HasCaretOrSelectionInPlainTextField(
const AXPositionInstance& position) const {
// This condition fixes issues when the caret is inside a plain text field,
// but causes more issues when used inside of a rich text field. For this
// reason, if we have a caret or a selection inside of an editable node,
// restrict this to a plain text field as we gain nothing from using it in a
// rich text field.
AXPlatformNodeDelegate* delegate = GetDelegate(position.get());
if (delegate && delegate->HasVisibleCaretOrSelection()) {
if (!delegate->GetData().HasState(ax::mojom::State::kEditable) ||
(delegate->GetData().IsPlainTextField() ||
delegate->IsChildOfPlainTextField())) {
return true;
}
}
return false;
}
// static // static
bool AXPlatformNodeTextRangeProviderWin::TextAttributeIsArrayType( bool AXPlatformNodeTextRangeProviderWin::TextAttributeIsArrayType(
TEXTATTRIBUTEID attribute_id) { TEXTATTRIBUTEID attribute_id) {
......
...@@ -170,6 +170,8 @@ class AX_EXPORT __declspec(uuid("3071e40d-a10d-45ff-a59f-6e8e1138e2c1")) ...@@ -170,6 +170,8 @@ class AX_EXPORT __declspec(uuid("3071e40d-a10d-45ff-a59f-6e8e1138e2c1"))
void RemoveFocusFromPreviousSelectionIfNeeded( void RemoveFocusFromPreviousSelectionIfNeeded(
const AXNodeRange& new_selection); const AXNodeRange& new_selection);
AXPlatformNodeWin* GetLowestAccessibleCommonPlatformNode() const; AXPlatformNodeWin* GetLowestAccessibleCommonPlatformNode() const;
bool HasCaretOrSelectionInPlainTextField(
const AXPositionInstance& position) const;
static bool TextAttributeIsArrayType(TEXTATTRIBUTEID attribute_id); static bool TextAttributeIsArrayType(TEXTATTRIBUTEID attribute_id);
static bool TextAttributeIsUiaReservedValue( static bool TextAttributeIsUiaReservedValue(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment