Commit 9c2f1b4b authored by Nektarios Paisios's avatar Nektarios Paisios Committed by Commit Bot

Implements AXLineForTextMarker and corrects the implementation of...

Implements AXLineForTextMarker and corrects the implementation of AXTextMarkerRangeForLine and insertionPointLineNumber

1. NSAccessibilityLineForTextMarker should return the line number within the object
it is called on where the given text marker is located.

2. NSAccessibilityTextMarkerRangeForLineParameterizedAttribute was not implemented correctly.
It should return a text marker range spanning the given line within
the object it is called on.
It doesn't receive a text marker as a parameter but an integer representing the
line number.

3. Per Safari, "insertionPointLineNumber" should return nil when the selection is not collapsed
or when called on an object that does not contain the caret.

4. Took the opportunity to re-write the line calculation logic using C++ algorithms
and switch away from using the deprecated sel_start and sel_end attributes.

R=dmazzoni@chromium.org

Bug: 990931
Change-Id: I96d7aaf56c1603c13f58827bb58b8bf467d91994
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1812100Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Commit-Queue: Nektarios Paisios <nektar@chromium.org>
Cr-Commit-Position: refs/heads/master@{#699334}
parent 7ba14412
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
#include <algorithm>
#include <iterator>
#include <map> #include <map>
#include <memory> #include <memory>
#include <utility> #include <utility>
...@@ -370,6 +372,38 @@ AXPlatformRange CreateAXPlatformRange(const BrowserAccessibility& start_object, ...@@ -370,6 +372,38 @@ AXPlatformRange CreateAXPlatformRange(const BrowserAccessibility& start_object,
return AXPlatformRange(std::move(anchor), std::move(focus)); return AXPlatformRange(std::move(anchor), std::move(focus));
} }
AXPlatformRange GetSelectedRange(BrowserAccessibility& owner) {
const BrowserAccessibilityManager* manager = owner.manager();
if (!manager)
return {};
const ui::AXTree::Selection unignored_selection =
manager->ax_tree()->GetUnignoredSelection();
int32_t anchor_id = unignored_selection.anchor_object_id;
const BrowserAccessibility* anchor_object = manager->GetFromID(anchor_id);
if (!anchor_object)
return {};
int32_t focus_id = unignored_selection.focus_object_id;
const BrowserAccessibility* focus_object = manager->GetFromID(focus_id);
if (!focus_object)
return {};
// |anchor_offset| and / or |focus_offset| refer to a character offset if
// |anchor_object| / |focus_object| are text-only objects or native text
// fields. Otherwise, they should be treated as child indices.
int anchor_offset = unignored_selection.anchor_offset;
int focus_offset = unignored_selection.focus_offset;
DCHECK_GE(anchor_offset, 0);
DCHECK_GE(focus_offset, 0);
ax::mojom::TextAffinity anchor_affinity = unignored_selection.anchor_affinity;
ax::mojom::TextAffinity focus_affinity = unignored_selection.focus_affinity;
return CreateAXPlatformRange(*anchor_object, anchor_offset, anchor_affinity,
*focus_object, focus_offset, focus_affinity);
}
void AddMisspelledTextAttributes(const AXPlatformRange& ax_range, void AddMisspelledTextAttributes(const AXPlatformRange& ax_range,
NSMutableAttributedString* attributed_string) { NSMutableAttributedString* attributed_string) {
int anchor_start_offset = 0; int anchor_start_offset = 0;
...@@ -826,11 +860,10 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired"; ...@@ -826,11 +860,10 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
- (NSNumber*)ariaColumnIndex { - (NSNumber*)ariaColumnIndex {
if (![self instanceActive]) if (![self instanceActive])
return nil; return nil;
base::Optional<int> aria_col_index = base::Optional<int> ariaColIndex = owner_->node()->GetTableCellAriaColIndex();
owner_->node()->GetTableCellAriaColIndex(); if (!ariaColIndex)
if (!aria_col_index)
return nil; return nil;
return [NSNumber numberWithInt:*aria_col_index]; return [NSNumber numberWithInt:*ariaColIndex];
} }
- (NSString*)ariaLive { - (NSString*)ariaLive {
...@@ -843,10 +876,10 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired"; ...@@ -843,10 +876,10 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
- (NSNumber*)ariaPosInSet { - (NSNumber*)ariaPosInSet {
if (![self instanceActive]) if (![self instanceActive])
return nil; return nil;
base::Optional<int> pos_in_set = owner_->node()->GetPosInSet(); base::Optional<int> posInSet = owner_->node()->GetPosInSet();
if (!pos_in_set) if (!posInSet)
return nil; return nil;
return [NSNumber numberWithInt:*pos_in_set]; return [NSNumber numberWithInt:*posInSet];
} }
- (NSString*)ariaRelevant { - (NSString*)ariaRelevant {
...@@ -859,29 +892,28 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired"; ...@@ -859,29 +892,28 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
- (NSNumber*)ariaRowCount { - (NSNumber*)ariaRowCount {
if (![self instanceActive]) if (![self instanceActive])
return nil; return nil;
base::Optional<int> aria_row_count = owner_->node()->GetTableAriaRowCount(); base::Optional<int> ariaRowCount = owner_->node()->GetTableAriaRowCount();
if (!aria_row_count) if (!ariaRowCount)
return nil; return nil;
return [NSNumber numberWithInt:*aria_row_count]; return [NSNumber numberWithInt:*ariaRowCount];
} }
- (NSNumber*)ariaRowIndex { - (NSNumber*)ariaRowIndex {
if (![self instanceActive]) if (![self instanceActive])
return nil; return nil;
base::Optional<int> aria_row_index = base::Optional<int> ariaRowIndex = owner_->node()->GetTableCellAriaRowIndex();
owner_->node()->GetTableCellAriaRowIndex(); if (!ariaRowIndex)
if (!aria_row_index)
return nil; return nil;
return [NSNumber numberWithInt:*aria_row_index]; return [NSNumber numberWithInt:*ariaRowIndex];
} }
- (NSNumber*)ariaSetSize { - (NSNumber*)ariaSetSize {
if (![self instanceActive]) if (![self instanceActive])
return nil; return nil;
base::Optional<int> set_size = owner_->node()->GetSetSize(); base::Optional<int> setSize = owner_->node()->GetSetSize();
if (!set_size) if (!setSize)
return nil; return nil;
return [NSNumber numberWithInt:*set_size]; return [NSNumber numberWithInt:*setSize];
} }
- (NSString*)autocompleteValue { - (NSString*)autocompleteValue {
...@@ -1396,25 +1428,24 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired"; ...@@ -1396,25 +1428,24 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
- (NSNumber*)insertionPointLineNumber { - (NSNumber*)insertionPointLineNumber {
if (![self instanceActive]) if (![self instanceActive])
return nil; return nil;
if (!owner_->HasVisibleCaretOrSelection())
// TODO(nektar): Deprecate sel_start and sel_end attributes.
int selStart, selEnd;
if (!owner_->GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart,
&selStart) ||
!owner_->GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, &selEnd)) {
return nil; return nil;
}
if (selStart > selEnd)
std::swap(selStart, selEnd);
const std::vector<int> line_breaks = owner_->GetLineStartOffsets(); const AXPlatformRange range = GetSelectedRange(*owner_);
for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) { // If the selection is not collapsed, then there is no visible caret.
if (line_breaks[i] > selStart) if (!range.IsCollapsed())
return [NSNumber numberWithInt:i]; return nil;
}
return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())]; const BrowserAccessibilityPositionInstance caretPosition =
range.focus()->LowestCommonAncestor(*owner_->CreatePositionAt(0));
DCHECK(!caretPosition->IsNullPosition())
<< "Calling HasVisibleCaretOrSelection() should have ensured that there "
"is a valid selection focus inside the current object.";
const std::vector<int> lineBreaks = owner_->GetLineStartOffsets();
auto iterator =
std::upper_bound(lineBreaks.begin(), lineBreaks.end(),
caretPosition->AsTextPosition()->text_offset());
return @(std::distance(lineBreaks.begin(), iterator));
} }
// Returns whether or not this node should be ignored in the // Returns whether or not this node should be ignored in the
...@@ -2083,79 +2114,44 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired"; ...@@ -2083,79 +2114,44 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
- (NSString*)selectedText { - (NSString*)selectedText {
if (![self instanceActive]) if (![self instanceActive])
return nil; return nil;
if (!owner_->HasVisibleCaretOrSelection())
// TODO(nektar): Deprecate sel_start and sel_end attributes.
int selStart, selEnd;
if (!owner_->GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart,
&selStart) ||
!owner_->GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, &selEnd)) {
return nil; return nil;
}
if (selStart > selEnd)
std::swap(selStart, selEnd);
int selLength = selEnd - selStart; const AXPlatformRange range = GetSelectedRange(*owner_);
base::string16 value = owner_->GetValue(); if (range.IsNull())
return base::SysUTF16ToNSString(value.substr(selStart, selLength)); return nil;
return base::SysUTF16ToNSString(range.GetText());
} }
// Returns range of text under the current object that is selected.
//
// Example, caret at offset 5: // Example, caret at offset 5:
// AXSelectedTextRange: “pos=5 len=0” // NSRange: “pos=5 len=0”
- (NSValue*)selectedTextRange { - (NSValue*)selectedTextRange {
if (![self instanceActive]) if (![self instanceActive])
return nil; return nil;
if (!owner_->HasVisibleCaretOrSelection())
// TODO(nektar): Deprecate sel_start and sel_end attributes.
int selStart, selEnd;
if (!owner_->GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart,
&selStart) ||
!owner_->GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, &selEnd)) {
// TODO(accessibility) Incorrectly reaches this line in a rich text area.
return nil; return nil;
}
if (selStart > selEnd) const AXPlatformRange range = GetSelectedRange(*owner_).AsForwardRange();
std::swap(selStart, selEnd); if (range.IsNull())
return nil;
int selLength = selEnd - selStart; const BrowserAccessibilityPositionInstance startPosition =
range.anchor()->LowestCommonAncestor(*owner_->CreatePositionAt(0));
DCHECK(!startPosition->IsNullPosition())
<< "Calling HasVisibleCaretOrSelection() should have ensured that there "
"is a valid selection anchor inside the current object.";
int selStart = startPosition->AsTextPosition()->text_offset();
DCHECK_GE(selStart, 0);
int selLength = range.GetText().length();
return [NSValue valueWithRange:NSMakeRange(selStart, selLength)]; return [NSValue valueWithRange:NSMakeRange(selStart, selLength)];
} }
- (id)selectedTextMarkerRange { - (id)selectedTextMarkerRange {
if (![self instanceActive]) if (![self instanceActive])
return nil; return nil;
return CreateTextMarkerRange(GetSelectedRange(*owner_));
BrowserAccessibilityManager* manager = owner_->manager();
if (!manager)
return nil;
ui::AXTree::Selection unignored_selection =
manager->ax_tree()->GetUnignoredSelection();
int32_t anchorId = unignored_selection.anchor_object_id;
const BrowserAccessibility* anchorObject = manager->GetFromID(anchorId);
if (!anchorObject)
return nil;
int32_t focusId = unignored_selection.focus_object_id;
const BrowserAccessibility* focusObject = manager->GetFromID(focusId);
if (!focusObject)
return nil;
// |anchorOffset| and / or |focusOffset| refer to a character offset if
// |anchorObject| / |focusObject| are text-only objects. Otherwise, they
// should be treated as child indices.
int anchorOffset = unignored_selection.anchor_offset;
int focusOffset = unignored_selection.focus_offset;
if (anchorOffset < 0 || focusOffset < 0)
return nil;
ax::mojom::TextAffinity anchorAffinity = unignored_selection.anchor_affinity;
ax::mojom::TextAffinity focusAffinity = unignored_selection.focus_affinity;
return CreateTextMarkerRange(
CreateAXPlatformRange(*anchorObject, anchorOffset, anchorAffinity,
*focusObject, focusOffset, focusAffinity));
} }
- (NSValue*)size { - (NSValue*)size {
...@@ -2519,9 +2515,11 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired"; ...@@ -2519,9 +2515,11 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
if (![self instanceActive]) if (![self instanceActive])
return nil; return nil;
const std::vector<int> line_breaks = owner_->GetLineStartOffsets(); const std::vector<int> lineBreaks = owner_->GetLineStartOffsets();
base::string16 value = owner_->GetValue(); base::string16 value = owner_->GetValue();
int len = static_cast<int>(value.size()); if (owner_->IsTextOnlyObject() && value.empty())
value = owner_->GetText();
int valueLength = static_cast<int>(value.size());
if ([attribute isEqualToString: if ([attribute isEqualToString:
NSAccessibilityStringForRangeParameterizedAttribute]) { NSAccessibilityStringForRangeParameterizedAttribute]) {
...@@ -2536,22 +2534,21 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired"; ...@@ -2536,22 +2534,21 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
if ([attribute if ([attribute
isEqualToString:NSAccessibilityLineForIndexParameterizedAttribute]) { isEqualToString:NSAccessibilityLineForIndexParameterizedAttribute]) {
int index = [(NSNumber*)parameter intValue]; int lineIndex = [(NSNumber*)parameter intValue];
for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) { auto iterator =
if (line_breaks[i] > index) std::upper_bound(lineBreaks.begin(), lineBreaks.end(), lineIndex);
return [NSNumber numberWithInt:i]; return @(std::distance(lineBreaks.begin(), iterator));
}
return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())];
} }
if ([attribute if ([attribute
isEqualToString:NSAccessibilityRangeForLineParameterizedAttribute]) { isEqualToString:NSAccessibilityRangeForLineParameterizedAttribute]) {
int line_index = [(NSNumber*)parameter intValue]; int lineIndex = [(NSNumber*)parameter intValue];
int line_count = static_cast<int>(line_breaks.size()) + 1; int lineCount = static_cast<int>(lineBreaks.size()) + 1;
if (line_index < 0 || line_index >= line_count) if (lineIndex < 0 || lineIndex >= lineCount)
return nil; return nil;
int start = line_index > 0 ? line_breaks[line_index - 1] : 0; int start = (lineIndex > 0) ? lineBreaks[lineIndex - 1] : 0;
int end = line_index < line_count - 1 ? line_breaks[line_index] : len; int end =
(lineIndex < (lineCount - 1)) ? lineBreaks[lineIndex] : valueLength;
return [NSValue valueWithRange:NSMakeRange(start, end - start)]; return [NSValue valueWithRange:NSMakeRange(start, end - start)];
} }
...@@ -2568,11 +2565,11 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired"; ...@@ -2568,11 +2565,11 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
int column = [[array objectAtIndex:0] intValue]; int column = [[array objectAtIndex:0] intValue];
int row = [[array objectAtIndex:1] intValue]; int row = [[array objectAtIndex:1] intValue];
ui::AXNode* cell_node = owner_->node()->GetTableCellFromCoords(row, column); ui::AXNode* cellNode = owner_->node()->GetTableCellFromCoords(row, column);
if (!cell_node) if (!cellNode)
return nil; return nil;
BrowserAccessibility* cell = owner_->manager()->GetFromID(cell_node->id()); BrowserAccessibility* cell = owner_->manager()->GetFromID(cellNode->id());
if (cell) if (cell)
return ToBrowserAccessibilityCocoa(cell); return ToBrowserAccessibilityCocoa(cell);
} }
...@@ -2696,21 +2693,42 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired"; ...@@ -2696,21 +2693,42 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
ui::AXBoundaryBehavior::CrossBoundary)); ui::AXBoundaryBehavior::CrossBoundary));
} }
if ([attribute if ([attribute isEqualToString:
isEqualToString: NSAccessibilityLineForTextMarkerParameterizedAttribute]) {
NSAccessibilityTextMarkerRangeForLineParameterizedAttribute]) {
BrowserAccessibilityPositionInstance position = BrowserAccessibilityPositionInstance position =
CreatePositionFromTextMarker(parameter); CreatePositionFromTextMarker(parameter);
if (position->IsNullPosition()) if (position->IsNullPosition())
return nil; return nil;
BrowserAccessibilityPositionInstance startPosition = int textOffset = position->AsTextPosition()->text_offset();
position->CreatePreviousLineStartPosition( const auto iterator =
ui::AXBoundaryBehavior::StopIfAlreadyAtBoundary); std::upper_bound(lineBreaks.begin(), lineBreaks.end(), textOffset);
BrowserAccessibilityPositionInstance endPosition = return @(std::distance(lineBreaks.begin(), iterator));
position->CreateNextLineEndPosition( }
ui::AXBoundaryBehavior::StopIfAlreadyAtBoundary);
AXPlatformRange range(std::move(startPosition), std::move(endPosition)); if ([attribute
isEqualToString:
NSAccessibilityTextMarkerRangeForLineParameterizedAttribute]) {
int lineIndex = [(NSNumber*)parameter intValue];
int lineCount = static_cast<int>(lineBreaks.size()) + 1;
if (lineIndex < 0 || lineIndex >= lineCount)
return nil;
int lineStartOffset = (lineIndex > 0) ? lineBreaks[lineIndex - 1] : 0;
BrowserAccessibilityPositionInstance lineStartPosition = CreateTextPosition(
*owner_, lineStartOffset, ax::mojom::TextAffinity::kDownstream);
if (lineStartPosition->IsNullPosition())
return nil;
// Make sure that the line start position is really at the start of the
// current line.
lineStartPosition = lineStartPosition->CreatePreviousLineStartPosition(
ui::AXBoundaryBehavior::StopIfAlreadyAtBoundary);
BrowserAccessibilityPositionInstance lineEndPosition =
lineStartPosition->CreateNextLineEndPosition(
ui::AXBoundaryBehavior::StopAtAnchorBoundary);
AXPlatformRange range(std::move(lineStartPosition),
std::move(lineEndPosition));
return CreateTextMarkerRange(std::move(range)); return CreateTextMarkerRange(std::move(range));
} }
...@@ -2948,14 +2966,14 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired"; ...@@ -2948,14 +2966,14 @@ NSString* const NSAccessibilityRequiredAttributeChrome = @"AXRequired";
if (![parameter isKindOfClass:[NSArray class]]) if (![parameter isKindOfClass:[NSArray class]])
return nil; return nil;
NSArray* text_marker_array = parameter; NSArray* textMarkerArray = parameter;
if ([text_marker_array count] != 2) if ([textMarkerArray count] != 2)
return nil; return nil;
BrowserAccessibilityPositionInstance startPosition = BrowserAccessibilityPositionInstance startPosition =
CreatePositionFromTextMarker([text_marker_array objectAtIndex:0]); CreatePositionFromTextMarker([textMarkerArray objectAtIndex:0]);
BrowserAccessibilityPositionInstance endPosition = BrowserAccessibilityPositionInstance endPosition =
CreatePositionFromTextMarker([text_marker_array objectAtIndex:1]); CreatePositionFromTextMarker([textMarkerArray objectAtIndex:1]);
if (*startPosition <= *endPosition) { if (*startPosition <= *endPosition) {
return CreateTextMarkerRange( return CreateTextMarkerRange(
AXPlatformRange(std::move(startPosition), std::move(endPosition))); AXPlatformRange(std::move(startPosition), std::move(endPosition)));
......
...@@ -50,7 +50,7 @@ class AXRange { ...@@ -50,7 +50,7 @@ class AXRange {
focus_.swap(other.focus_); focus_.swap(other.focus_);
} }
virtual ~AXRange() {} virtual ~AXRange() = default;
AXPositionType* anchor() const { AXPositionType* anchor() const {
DCHECK(anchor_); DCHECK(anchor_);
...@@ -62,15 +62,10 @@ class AXRange { ...@@ -62,15 +62,10 @@ class AXRange {
return focus_.get(); return focus_.get();
} }
bool IsNull() const {
DCHECK(anchor_ && focus_);
return anchor_->IsNullPosition() || focus_->IsNullPosition();
}
AXRange& operator=(const AXRange& other) = delete; AXRange& operator=(const AXRange& other) = delete;
AXRange& operator=(const AXRange&& other) { AXRange& operator=(AXRange&& other) {
if (this != other) { if (this != &other) {
anchor_ = AXPositionType::CreateNullPosition(); anchor_ = AXPositionType::CreateNullPosition();
focus_ = AXPositionType::CreateNullPosition(); focus_ = AXPositionType::CreateNullPosition();
anchor_.swap(other.anchor_); anchor_.swap(other.anchor_);
...@@ -88,7 +83,7 @@ class AXRange { ...@@ -88,7 +83,7 @@ class AXRange {
bool operator!=(const AXRange& other) const { return !(*this == other); } bool operator!=(const AXRange& other) const { return !(*this == other); }
AXRange GetForwardRange() const { AXRange AsForwardRange() const {
// When we have a range with an empty text representation, its endpoints // When we have a range with an empty text representation, its endpoints
// would be considered equal as text positions, but they could be located in // would be considered equal as text positions, but they could be located in
// different anchors of the AXTree. Compare them as tree positions first to // different anchors of the AXTree. Compare them as tree positions first to
...@@ -102,6 +97,8 @@ class AXRange { ...@@ -102,6 +97,8 @@ class AXRange {
: AXRange(anchor_->Clone(), focus_->Clone()); : AXRange(anchor_->Clone(), focus_->Clone());
} }
bool IsCollapsed() const { return !IsNull() && *anchor_ == *focus_; }
// We define a "leaf text range" as an AXRange whose endpoints are leaf text // We define a "leaf text range" as an AXRange whose endpoints are leaf text
// positions located within the same anchor of the AXTree. // positions located within the same anchor of the AXTree.
bool IsLeafTextRange() const { bool IsLeafTextRange() const {
...@@ -109,6 +106,11 @@ class AXRange { ...@@ -109,6 +106,11 @@ class AXRange {
anchor_->IsLeafTextPosition() && focus_->IsLeafTextPosition(); anchor_->IsLeafTextPosition() && focus_->IsLeafTextPosition();
} }
bool IsNull() const {
DCHECK(anchor_ && focus_);
return anchor_->IsNullPosition() || focus_->IsNullPosition();
}
// We can decompose any given AXRange into multiple "leaf text ranges". // We can decompose any given AXRange into multiple "leaf text ranges".
// As an example, consider the following HTML code: // As an example, consider the following HTML code:
// //
...@@ -208,13 +210,13 @@ class AXRange { ...@@ -208,13 +210,13 @@ class AXRange {
}; };
Iterator begin() const { Iterator begin() const {
AXRange forward_range = GetForwardRange(); AXRange forward_range = AsForwardRange();
return Iterator(std::move(forward_range.anchor_), return Iterator(std::move(forward_range.anchor_),
std::move(forward_range.focus_)); std::move(forward_range.focus_));
} }
Iterator end() const { Iterator end() const {
AXRange forward_range = GetForwardRange(); AXRange forward_range = AsForwardRange();
return Iterator(nullptr, std::move(forward_range.focus_)); return Iterator(nullptr, std::move(forward_range.focus_));
} }
......
...@@ -377,6 +377,100 @@ TEST_F(AXRangeTest, EqualityOperators) { ...@@ -377,6 +377,100 @@ TEST_F(AXRangeTest, EqualityOperators) {
EXPECT_EQ(test_positions_1_and_2, test_positions_1_and_3); EXPECT_EQ(test_positions_1_and_2, test_positions_1_and_3);
} }
TEST_F(AXRangeTest, AsForwardRange) {
TestPositionRange null_range(AXNodePosition::CreateNullPosition(),
AXNodePosition::CreateNullPosition());
null_range = null_range.AsForwardRange();
EXPECT_TRUE(null_range.IsNull());
TestPositionInstance tree_position = AXNodePosition::CreateTreePosition(
tree_->data().tree_id, button_.id, 0 /* child_index */);
TestPositionInstance text_position1 = AXNodePosition::CreateTextPosition(
tree_->data().tree_id, line_break1_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionInstance text_position2 = AXNodePosition::CreateTextPosition(
tree_->data().tree_id, inline_box2_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionRange tree_to_text_range(text_position1->Clone(),
tree_position->Clone());
tree_to_text_range = tree_to_text_range.AsForwardRange();
EXPECT_EQ(*tree_position, *tree_to_text_range.anchor());
EXPECT_EQ(*text_position1, *tree_to_text_range.focus());
TestPositionRange text_to_text_range(text_position2->Clone(),
text_position1->Clone());
text_to_text_range = text_to_text_range.AsForwardRange();
EXPECT_EQ(*text_position1, *text_to_text_range.anchor());
EXPECT_EQ(*text_position2, *text_to_text_range.focus());
}
TEST_F(AXRangeTest, IsCollapsed) {
TestPositionRange null_range(AXNodePosition::CreateNullPosition(),
AXNodePosition::CreateNullPosition());
null_range = null_range.AsForwardRange();
EXPECT_FALSE(null_range.IsCollapsed());
TestPositionInstance tree_position1 = AXNodePosition::CreateTreePosition(
tree_->data().tree_id, text_field_.id, 0 /* child_index */);
// Since there are no children in inline_box1_, the following is essentially
// an "after text" position which should not compare as equivalent to the
// above tree position which is a "before text" position inside the text
// field.
TestPositionInstance tree_position2 = AXNodePosition::CreateTreePosition(
tree_->data().tree_id, inline_box1_.id, 0 /* child_index */);
TestPositionInstance text_position1 = AXNodePosition::CreateTextPosition(
tree_->data().tree_id, static_text1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionInstance text_position2 = AXNodePosition::CreateTextPosition(
tree_->data().tree_id, inline_box1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionInstance text_position3 = AXNodePosition::CreateTextPosition(
tree_->data().tree_id, inline_box2_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionRange tree_to_null_range(tree_position1->Clone(),
AXNodePosition::CreateNullPosition());
EXPECT_TRUE(tree_to_null_range.IsNull());
EXPECT_FALSE(tree_to_null_range.IsCollapsed());
TestPositionRange null_to_text_range(AXNodePosition::CreateNullPosition(),
text_position1->Clone());
EXPECT_TRUE(null_to_text_range.IsNull());
EXPECT_FALSE(null_to_text_range.IsCollapsed());
TestPositionRange tree_to_tree_range(tree_position2->Clone(),
tree_position1->Clone());
EXPECT_TRUE(tree_to_tree_range.IsCollapsed());
// A tree and a text position that essentially point to the same text offset
// are equivalent, even if they are anchored to a different node.
TestPositionRange tree_to_text_range(tree_position1->Clone(),
text_position1->Clone());
EXPECT_TRUE(tree_to_text_range.IsCollapsed());
// The following positions are not equivalent since tree_position2 is an
// "after text" position.
tree_to_text_range =
TestPositionRange(tree_position2->Clone(), text_position2->Clone());
EXPECT_FALSE(tree_to_text_range.IsCollapsed());
TestPositionRange text_to_text_range(text_position1->Clone(),
text_position1->Clone());
EXPECT_TRUE(text_to_text_range.IsCollapsed());
// Two text positions that essentially point to the same text offset are
// equivalent, even if they are anchored to a different node.
text_to_text_range =
TestPositionRange(text_position1->Clone(), text_position2->Clone());
EXPECT_TRUE(text_to_text_range.IsCollapsed());
text_to_text_range =
TestPositionRange(text_position1->Clone(), text_position3->Clone());
EXPECT_FALSE(text_to_text_range.IsCollapsed());
}
TEST_F(AXRangeTest, BeginAndEndIterators) { TEST_F(AXRangeTest, BeginAndEndIterators) {
TestPositionInstance null_position = AXNodePosition::CreateNullPosition(); TestPositionInstance null_position = AXNodePosition::CreateNullPosition();
TestPositionInstance test_position1 = AXNodePosition::CreateTextPosition( TestPositionInstance test_position1 = AXNodePosition::CreateTextPosition(
......
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