[LayoutNG] Add IsAfterNonCollapsedCharacter(node, offset) API

This patch adds a new API to LayoutNG offset mapping, so that we can
easily check if a given DOM offset is right after a non-collapsed
character.

This patch is a preparation for adding an NG version of
LayoutText::ContainsCaretOffset().

Bug: 771398
Cq-Include-Trybots: master.tryserver.chromium.linux:linux_layout_tests_layout_ng
Change-Id: I93e8a89089661c6f17da6a7a847f75914cb36f75
Reviewed-on: https://chromium-review.googlesource.com/724230
Commit-Queue: Xiaocheng Hu <xiaochengh@chromium.org>
Reviewed-by: Emil A Eklund <eae@chromium.org>
Cr-Commit-Position: refs/heads/master@{#509915}
diff --git a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_node_offset_mapping_test.cc b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_node_offset_mapping_test.cc
index 4f99cb50..a13dd85 100644
--- a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_node_offset_mapping_test.cc
+++ b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_node_offset_mapping_test.cc
@@ -13,6 +13,8 @@
 
 namespace blink {
 
+// TODO(xiaochengh): Rename this test to NGOffsetMappingTest.
+
 class NGInlineNodeOffsetMappingTest : public RenderingTest {
  protected:
   void SetUp() override {
@@ -70,6 +72,10 @@
     return GetOffsetMapping().IsNonCollapsedCharacter(node, offset);
   }
 
+  bool IsAfterNonCollapsedCharacter(const Node& node, unsigned offset) const {
+    return GetOffsetMapping().IsAfterNonCollapsedCharacter(node, offset);
+  }
+
   RefPtr<const ComputedStyle> style_;
   LayoutNGBlockFlow* layout_block_flow_ = nullptr;
   LayoutObject* layout_object_ = nullptr;
@@ -151,6 +157,12 @@
   EXPECT_TRUE(IsNonCollapsedCharacter(*foo_node, 1));
   EXPECT_TRUE(IsNonCollapsedCharacter(*foo_node, 2));
   EXPECT_FALSE(IsNonCollapsedCharacter(*foo_node, 3));  // false at node end
+
+  // false at node start
+  EXPECT_FALSE(IsAfterNonCollapsedCharacter(*foo_node, 0));
+  EXPECT_TRUE(IsAfterNonCollapsedCharacter(*foo_node, 1));
+  EXPECT_TRUE(IsAfterNonCollapsedCharacter(*foo_node, 2));
+  EXPECT_TRUE(IsAfterNonCollapsedCharacter(*foo_node, 3));
 }
 
 TEST_F(NGInlineNodeOffsetMappingTest, TwoTextNodes) {
@@ -198,6 +210,18 @@
   EXPECT_TRUE(IsNonCollapsedCharacter(*bar_node, 1));
   EXPECT_TRUE(IsNonCollapsedCharacter(*bar_node, 2));
   EXPECT_FALSE(IsNonCollapsedCharacter(*bar_node, 3));  // false at node end
+
+  // false at node start
+  EXPECT_FALSE(IsAfterNonCollapsedCharacter(*foo_node, 0));
+  EXPECT_TRUE(IsAfterNonCollapsedCharacter(*foo_node, 1));
+  EXPECT_TRUE(IsAfterNonCollapsedCharacter(*foo_node, 2));
+  EXPECT_TRUE(IsAfterNonCollapsedCharacter(*foo_node, 3));
+
+  // false at node start
+  EXPECT_FALSE(IsAfterNonCollapsedCharacter(*bar_node, 0));
+  EXPECT_TRUE(IsAfterNonCollapsedCharacter(*bar_node, 1));
+  EXPECT_TRUE(IsAfterNonCollapsedCharacter(*bar_node, 2));
+  EXPECT_TRUE(IsAfterNonCollapsedCharacter(*bar_node, 3));
 }
 
 TEST_F(NGInlineNodeOffsetMappingTest, BRBetweenTextNodes) {
@@ -295,6 +319,16 @@
   EXPECT_TRUE(IsNonCollapsedCharacter(*node, 6));
   EXPECT_TRUE(IsNonCollapsedCharacter(*node, 7));
   EXPECT_FALSE(IsNonCollapsedCharacter(*node, 8));
+
+  EXPECT_FALSE(IsAfterNonCollapsedCharacter(*node, 0));
+  EXPECT_TRUE(IsAfterNonCollapsedCharacter(*node, 1));
+  EXPECT_TRUE(IsAfterNonCollapsedCharacter(*node, 2));
+  EXPECT_TRUE(IsAfterNonCollapsedCharacter(*node, 3));
+  EXPECT_TRUE(IsAfterNonCollapsedCharacter(*node, 4));
+  EXPECT_FALSE(IsAfterNonCollapsedCharacter(*node, 5));
+  EXPECT_TRUE(IsAfterNonCollapsedCharacter(*node, 6));
+  EXPECT_TRUE(IsAfterNonCollapsedCharacter(*node, 7));
+  EXPECT_TRUE(IsAfterNonCollapsedCharacter(*node, 8));
 }
 
 TEST_F(NGInlineNodeOffsetMappingTest, FullyCollapsedWhiteSpaceNode) {
diff --git a/third_party/WebKit/Source/core/layout/ng/inline/ng_offset_mapping_result.cc b/third_party/WebKit/Source/core/layout/ng/inline/ng_offset_mapping_result.cc
index 90b6fa8..ea1b7dd 100644
--- a/third_party/WebKit/Source/core/layout/ng/inline/ng_offset_mapping_result.cc
+++ b/third_party/WebKit/Source/core/layout/ng/inline/ng_offset_mapping_result.cc
@@ -170,4 +170,17 @@
          unit->GetType() != NGOffsetMappingUnitType::kCollapsed;
 }
 
+bool NGOffsetMappingResult::IsAfterNonCollapsedCharacter(
+    const Node& node,
+    unsigned offset) const {
+  if (!offset)
+    return false;
+  // In case we have one unit ending at |offset| and another starting at
+  // |offset|, we need to find the former. Hence, search with |offset - 1|.
+  const NGOffsetMappingUnit* unit =
+      GetMappingUnitForDOMOffset(node, offset - 1);
+  return unit && offset > unit->DOMStart() &&
+         unit->GetType() != NGOffsetMappingUnitType::kCollapsed;
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/layout/ng/inline/ng_offset_mapping_result.h b/third_party/WebKit/Source/core/layout/ng/inline/ng_offset_mapping_result.h
index 44fffb8..7738871 100644
--- a/third_party/WebKit/Source/core/layout/ng/inline/ng_offset_mapping_result.h
+++ b/third_party/WebKit/Source/core/layout/ng/inline/ng_offset_mapping_result.h
@@ -122,8 +122,13 @@
 
   // Returns true if the character at the position is non-collapsed. If the
   // offset is at the end of the node, returns false.
+  // TODO(xiaochengh): Rename to IsBeforeNonCollapsedCharacter().
   bool IsNonCollapsedCharacter(const Node&, unsigned offset) const;
 
+  // Returns true if the offset is right after a non-collapsed character. If the
+  // offset is at the beginning of the node, returns false.
+  bool IsAfterNonCollapsedCharacter(const Node&, unsigned offset) const;
+
   // TODO(xiaochengh): Add APIs for reverse mapping.
 
  private: