Commit 0190618c authored by Aaron Leventhal's avatar Aaron Leventhal Committed by Commit Bot

Character extents at end of text

For get_characterExtents() at the offset just past the last character,
return bounds equivalent to the caret's bounds if it
placed there, e.g. width of 1, but the same height as the current line.

Bug: 869985
Change-Id: Ib544b863c1fd7ab132dc5ff4ddbeebdde8ed342b
Reviewed-on: https://chromium-review.googlesource.com/1157228
Commit-Queue: Aaron Leventhal <aleventhal@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#595194}
parent f0f6d1ae
......@@ -76,6 +76,10 @@ class AccessibilityWinBrowserTest : public ContentBrowserTest {
void SetUpInputField(Microsoft::WRL::ComPtr<IAccessibleText>* input_text);
void SetUpScrollableInputField(
Microsoft::WRL::ComPtr<IAccessibleText>* input_text);
void SetUpSingleCharInputField(
Microsoft::WRL::ComPtr<IAccessibleText>* input_text);
void SetUpSingleCharRtlInputField(
Microsoft::WRL::ComPtr<IAccessibleText>* input_text);
void SetUpTextareaField(
Microsoft::WRL::ComPtr<IAccessibleText>* textarea_text);
void SetUpSampleParagraph(
......@@ -201,6 +205,39 @@ void AccessibilityWinBrowserTest::SetUpScrollableInputField(
SetUpInputFieldHelper(input_text);
}
// Loads a page with an input text field and places a single character in it.
void AccessibilityWinBrowserTest::SetUpSingleCharInputField(
Microsoft::WRL::ComPtr<IAccessibleText>* input_text) {
ASSERT_NE(nullptr, input_text);
LoadInitialAccessibilityTreeFromHtml(std::string(
R"HTML(<!DOCTYPE html>
<html>
<body>
<form>
<input type="text" id="textField" name="name" value="x">
</form>
</body>
</html>)HTML"));
SetUpInputFieldHelper(input_text);
}
// Loads a page with a right-to-left input text field and places a single
// character in it.
void AccessibilityWinBrowserTest::SetUpSingleCharRtlInputField(
Microsoft::WRL::ComPtr<IAccessibleText>* input_text) {
ASSERT_NE(nullptr, input_text);
LoadInitialAccessibilityTreeFromHtml(std::string(
R"HTML(<!DOCTYPE html>
<html>
<body>
<form>
<input type="text" id="textField" name="name" dir="rtl" value="x">
</form>
</body>
</html>)HTML"));
SetUpInputFieldHelper(input_text);
}
void AccessibilityWinBrowserTest::SetUpInputFieldHelper(
Microsoft::WRL::ComPtr<IAccessibleText>* input_text) {
Microsoft::WRL::ComPtr<IAccessible> document(GetRendererAccessible());
......@@ -1245,6 +1282,15 @@ IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
EXPECT_LT(1, width) << "at offset " << offset;
EXPECT_EQ(previous_height, height) << "at offset " << offset;
}
// Past end of text.
EXPECT_HRESULT_SUCCEEDED(paragraph_text->get_characterExtents(
n_characters, coordinate_type, &x, &y, &width, &height));
EXPECT_LT(previous_x, x) << "at final offset " << n_characters;
EXPECT_EQ(previous_y, y) << "at final offset " << n_characters;
// Last character width past end should be 1, the width of a caret.
EXPECT_EQ(1, width) << "at final offset " << n_characters;
EXPECT_EQ(previous_height, height) << "at final offset " << n_characters;
}
}
......@@ -1376,6 +1422,111 @@ IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
EXPECT_LT(1, width) << "at offset " << offset;
EXPECT_EQ(previous_height, height) << "at offset " << offset;
}
// Past end of text.
EXPECT_HRESULT_SUCCEEDED(input_text->get_characterExtents(
n_characters, coordinate_type, &x, &y, &width, &height));
EXPECT_LT(previous_x, x) << "at final offset " << n_characters;
EXPECT_EQ(previous_y, y) << "at final offset " << n_characters;
// Last character width past end should be 1, the width of a caret.
EXPECT_EQ(1, width) << "at final offset " << n_characters;
EXPECT_EQ(previous_height, height) << "at final offset " << n_characters;
}
}
// TODO(accessibility) Support bounds in an empty text field where there are
// no child inline text objects, such that the following test passes.
IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
DISABLED_TestCharacterExtentsInEmptyInputField) {
Microsoft::WRL::ComPtr<IAccessibleText> input_text;
SetUpSingleCharInputField(&input_text);
LONG n_characters;
ASSERT_HRESULT_SUCCEEDED(input_text->get_nCharacters(&n_characters));
ASSERT_EQ(1, n_characters);
// Get the rect for the only character.
LONG prev_x, prev_y, prev_width, prev_height;
EXPECT_HRESULT_SUCCEEDED(input_text->get_characterExtents(
0, IA2_COORDTYPE_SCREEN_RELATIVE, &prev_x, &prev_y, &prev_width,
&prev_height));
EXPECT_LT(1, prev_width);
EXPECT_LT(1, prev_height);
// Delete the character in the input field.
AccessibilityNotificationWaiter waiter(
shell()->web_contents(), ui::kAXModeComplete,
ax::mojom::Event::kTextSelectionChanged);
ExecuteScript(std::wstring(L"document.querySelector('input').value='';"));
waiter.WaitForNotification();
ASSERT_HRESULT_SUCCEEDED(input_text->get_nCharacters(&n_characters));
ASSERT_EQ(0, n_characters);
LONG caret_offset;
ASSERT_HRESULT_SUCCEEDED(input_text->get_caretOffset(&caret_offset));
ASSERT_EQ(0, caret_offset);
// Now that input is completely empty, the position of the caret should be
// returned for character 0. The x,y position and height should be the same as
// it was as when there was single character, but the width should now be 1.
LONG x, y, width, height;
for (int offset = IA2_TEXT_OFFSET_CARET; offset <= 0; ++offset) {
EXPECT_HRESULT_SUCCEEDED(input_text->get_characterExtents(
offset, IA2_COORDTYPE_SCREEN_RELATIVE, &x, &y, &width, &height));
EXPECT_EQ(prev_x, x);
EXPECT_EQ(prev_y, y);
EXPECT_EQ(1, width);
EXPECT_EQ(prev_height, height);
}
}
// TODO(accessibility) Support bounds in an empty text field where there are
// no child inline text objects, such that the following test passes.
IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
DISABLED_TestCharacterExtentsInEmptyRtlInputField) {
Microsoft::WRL::ComPtr<IAccessibleText> input_text;
SetUpSingleCharRtlInputField(&input_text);
LONG n_characters;
ASSERT_HRESULT_SUCCEEDED(input_text->get_nCharacters(&n_characters));
ASSERT_EQ(1, n_characters);
// Get the rect for the only character.
LONG prev_x, prev_y, prev_width, prev_height;
EXPECT_HRESULT_SUCCEEDED(input_text->get_characterExtents(
0, IA2_COORDTYPE_SCREEN_RELATIVE, &prev_x, &prev_y, &prev_width,
&prev_height));
EXPECT_LT(1, prev_width);
EXPECT_LT(1, prev_height);
// Delete the character in the input field.
AccessibilityNotificationWaiter waiter(
shell()->web_contents(), ui::kAXModeComplete,
ax::mojom::Event::kTextSelectionChanged);
ExecuteScript(
std::wstring(L"const input = document.querySelector('input');"
"input.value='';"));
waiter.WaitForNotification();
ASSERT_HRESULT_SUCCEEDED(input_text->get_nCharacters(&n_characters));
ASSERT_EQ(0, n_characters);
LONG caret_offset;
ASSERT_HRESULT_SUCCEEDED(input_text->get_caretOffset(&caret_offset));
ASSERT_EQ(0, caret_offset);
// Now that input is completely empty, the position of the caret should be
// returned for character 0. The x,y position and height should be the same as
// it was as when there was single character, but the width should now be 1.
LONG x, y, width, height;
for (int offset = IA2_TEXT_OFFSET_CARET; offset <= 0; ++offset) {
EXPECT_HRESULT_SUCCEEDED(input_text->get_characterExtents(
offset, IA2_COORDTYPE_SCREEN_RELATIVE, &x, &y, &width, &height));
// TODO(accessibility) Why do results keep changing on each run?
EXPECT_GE(prev_x + prev_width - 1, x);
EXPECT_EQ(prev_y, y);
EXPECT_EQ(1, width);
EXPECT_EQ(prev_height, height);
}
}
......
......@@ -380,7 +380,9 @@ gfx::Rect BrowserAccessibility::GetPageBoundsForRange(int start,
else
start = 0;
}
return bounds;
// When past the end of text, the area will be 0.
// In this case, use bounds provided for the caret.
return bounds.IsEmpty() ? GetPageBoundsPastEndOfText() : bounds;
}
int end = start + len;
......@@ -492,6 +494,54 @@ gfx::Rect BrowserAccessibility::GetScreenBoundsForRange(int start,
return bounds;
}
// Get a rect for a 1-width character past the end of text. This is what ATs
// expect when getting the character extents past the last character in a line,
// and equals what the caret bounds would be when past the end of the text.
gfx::Rect BrowserAccessibility::GetPageBoundsPastEndOfText() const {
// Step 1: get approximate caret bounds. The thickness may not yet be correct.
gfx::Rect bounds;
if (InternalChildCount() > 0) {
// When past the end of text, use bounds provided by a last child if
// available, and then correct for thickness of caret.
BrowserAccessibility* child = InternalGetChild(InternalChildCount() - 1);
int child_text_len = child->GetText().size();
bounds = child->GetPageBoundsForRange(child_text_len, child_text_len);
if (bounds.width() == 0 && bounds.height() == 0)
return bounds; // Inline text boxes info not yet available.
} else {
// TODO(accessibility) Support bounds in an empty text field where there are
// no child inline text objects.
return bounds;
}
// Step 2: correct for the thickness of the caret.
auto text_direction = static_cast<ax::mojom::TextDirection>(
GetIntAttribute(ax::mojom::IntAttribute::kTextDirection));
constexpr int kCaretThickness = 1;
switch (text_direction) {
case ax::mojom::TextDirection::kNone:
case ax::mojom::TextDirection::kLtr: {
bounds.set_width(kCaretThickness);
break;
}
case ax::mojom::TextDirection::kRtl: {
bounds.set_x(bounds.right() - kCaretThickness);
bounds.set_width(kCaretThickness);
break;
}
case ax::mojom::TextDirection::kTtb: {
bounds.set_height(kCaretThickness);
break;
}
case ax::mojom::TextDirection::kBtt: {
bounds.set_y(bounds.bottom() - kCaretThickness);
bounds.set_height(kCaretThickness);
break;
}
}
return bounds;
}
base::string16 BrowserAccessibility::GetValue() const {
base::string16 value =
GetString16Attribute(ax::mojom::StringAttribute::kValue);
......
......@@ -392,6 +392,8 @@ class CONTENT_EXPORT BrowserAccessibility : public ui::AXPlatformNodeDelegate {
// text, depending on the platform.
base::string16 GetInnerText() const;
gfx::Rect GetPageBoundsPastEndOfText() const;
// A unique ID, since node IDs are frame-local.
ui::AXUniqueId unique_id_;
......
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