Commit 44b452c2 authored by Victor Fei's avatar Victor Fei Committed by Commit Bot

NormalizeTextRange handles range that span ignored nodes

This change fixes an exception in NormalizeTextRange for the fringe case
when the range's |start_| and |end_| positions span ignored nodes.

Consider the following case:
   text1<start_>|IGNORED node|<end_>text2
Due to |start_| and |end_| positions spanning ignored nodes, after we
call AsLeafTextPosition{Before|After}Character() in NormalizeTextRange()
we will end up with |normalized_start| and |normalized_end| position
inverted:
   <normalized_end>text1|IGNORED node|<normalized_start>text2
For this scenario, we want to collapse the normlized range and set the
positions to |normalized_start|.
--- New expected behavior ---
Before NormalizeTextRange():
    text1<start_>|IGNORED node|<end_>text2

After NormalizeTextRange():
    text1|IGNORED node|<start_>text2
                       <end_>

Bug: 1021443
Change-Id: I8d9039fef3c47ce46d9dae1e3838655b317af439
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1918305Reviewed-by: default avatarNektarios Paisios <nektar@chromium.org>
Reviewed-by: default avatarAdam Ettenberger <adettenb@microsoft.com>
Commit-Queue: Victor Fei <vicfei@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#716853}
parent dbd38b34
......@@ -1144,24 +1144,33 @@ void AXPlatformNodeTextRangeProviderWin::NormalizeTextRange() {
// first snap them both to be unignored positions.
NormalizeAsUnignoredTextRange();
// Only normalize non-degenerate ranges.
if (*start_ != *end_) {
AXPositionInstance normalized_start =
start_->AsLeafTextPositionBeforeCharacter();
if (!normalized_start->IsNullPosition()) {
DCHECK_EQ(*start_, *normalized_start);
start_ = std::move(normalized_start);
}
if (*start_ == *end_)
return;
AXPositionInstance normalized_end =
end_->AsLeafTextPositionAfterCharacter();
if (!normalized_end->IsNullPosition()) {
DCHECK_EQ(*end_, *normalized_end);
end_ = std::move(normalized_end);
}
AXPositionInstance normalized_start =
start_->AsLeafTextPositionBeforeCharacter();
AXPositionInstance normalized_end = end_->AsLeafTextPositionAfterCharacter();
// Handle the fringe case when |normalized_start| and |normalized_end| end up
// inverted after AsLeafTextPosition{Before|After}Character() calls.
// Consider the following case:
// text1<start_>|IGNORED node|<end_>text2
// Due to |start_| and |end_| positions spanning ignored nodes, we end up
// with the following inverted normalized positions:
// <normalized_end>text1|IGNORED node|<normalized_start>text2
// So we want to create a collapsed range by setting |normalized_end| to
// |normalized_start|.
if (!normalized_start->IsNullPosition() &&
!normalized_end->IsNullPosition() && *normalized_end < *normalized_start)
normalized_end = normalized_start->Clone();
if (!normalized_start->IsNullPosition())
start_ = std::move(normalized_start);
if (!normalized_end->IsNullPosition())
end_ = std::move(normalized_end);
DCHECK_LE(*start_, *end_);
}
DCHECK_LE(*start_, *end_);
}
} // namespace ui
......@@ -4871,4 +4871,89 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
*GetEnd(normalized_range_win.Get()));
}
TEST_F(AXPlatformNodeTextRangeProviderTest,
TestNormalizeTextRangeSpanIgnoredNodes) {
ui::AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kRootWebArea;
ui::AXNodeData before_text;
before_text.id = 2;
before_text.role = ax::mojom::Role::kStaticText;
before_text.SetName("before");
root_data.child_ids.push_back(before_text.id);
ui::AXNodeData ignored_text1;
ignored_text1.id = 3;
ignored_text1.role = ax::mojom::Role::kStaticText;
ignored_text1.AddState(ax::mojom::State::kIgnored);
ignored_text1.SetName("ignored1");
root_data.child_ids.push_back(ignored_text1.id);
ui::AXNodeData ignored_text2;
ignored_text2.id = 4;
ignored_text2.role = ax::mojom::Role::kStaticText;
ignored_text2.AddState(ax::mojom::State::kIgnored);
ignored_text2.SetName("ignored2");
root_data.child_ids.push_back(ignored_text2.id);
ui::AXNodeData after_text;
after_text.id = 5;
after_text.role = ax::mojom::Role::kStaticText;
after_text.SetName("after");
root_data.child_ids.push_back(after_text.id);
ui::AXTreeUpdate update;
ui::AXTreeID tree_id = ui::AXTreeID::CreateNewAXTreeID();
update.root_id = root_data.id;
update.tree_data.tree_id = tree_id;
update.has_tree_data = true;
update.nodes = {root_data, before_text, ignored_text1, ignored_text2,
after_text};
Init(update);
AXNodePosition::SetTree(tree_.get());
// Making |owner| AXID:1 so that |TestAXNodeWrapper::BuildAllWrappers|
// will build the entire tree.
AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
AXPlatformNodeFromNode(GetNodeFromTree(tree_id, 1)));
// TextPosition, anchor_id=2, text_offset=6, annotated_text=before<>
AXNodePosition::AXPositionInstance range_start =
AXNodePosition::CreateTextPosition(tree_id, /*anchor_id=*/2,
/*text_offset=*/6,
ax::mojom::TextAffinity::kDownstream);
// TextPosition, anchor_id=5, text_offset=0, annotated_text=<a>fter
AXNodePosition::AXPositionInstance range_end =
AXNodePosition::CreateTextPosition(tree_id, /*anchor_id=*/5,
/*text_offset=*/0,
ax::mojom::TextAffinity::kDownstream);
ComPtr<AXPlatformNodeTextRangeProviderWin> range_span_ignored_nodes;
// Text range before NormalizeTextRange()
// |before<>||ignored1||ignored2||<a>fter|
// |-----------------------|
ComPtr<ITextRangeProvider> text_range_provider =
AXPlatformNodeTextRangeProviderWin::CreateTextRangeProvider(
owner, std::move(range_start), std::move(range_end));
text_range_provider->QueryInterface(IID_PPV_ARGS(&range_span_ignored_nodes));
// Text range after NormalizeTextRange()
// |before||ignored1||ignored2||<a>fter|
// |-|
NormalizeTextRange(range_span_ignored_nodes.Get());
EXPECT_EQ(*GetStart(range_span_ignored_nodes.Get()),
*GetEnd(range_span_ignored_nodes.Get()));
EXPECT_EQ(true, GetStart(range_span_ignored_nodes.Get())->IsTextPosition());
EXPECT_EQ(true, GetStart(range_span_ignored_nodes.Get())->AtStartOfAnchor());
EXPECT_EQ(5, GetStart(range_span_ignored_nodes.Get())->anchor_id());
EXPECT_EQ(0, GetStart(range_span_ignored_nodes.Get())->text_offset());
EXPECT_EQ(true, GetEnd(range_span_ignored_nodes.Get())->IsTextPosition());
EXPECT_EQ(true, GetEnd(range_span_ignored_nodes.Get())->AtStartOfAnchor());
EXPECT_EQ(5, GetEnd(range_span_ignored_nodes.Get())->anchor_id());
EXPECT_EQ(0, GetEnd(range_span_ignored_nodes.Get())->text_offset());
}
} // namespace ui
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