[LayoutNG] Fix crash when hit-testing flow control text fragments

Bug: 866556
Cq-Include-Trybots: luci.chromium.try:linux_layout_tests_layout_ng
Change-Id: Ie9977ebaa733e43d54fa6006e88adb3fce7cfb50
Reviewed-on: https://chromium-review.googlesource.com/1146882
Reviewed-by: Xiaocheng Hu <xiaochengh@chromium.org>
Commit-Queue: Koji Ishii <kojii@chromium.org>
Cr-Commit-Position: refs/heads/master@{#578372}
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc
index 7c70596..2246db2 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc
@@ -7,6 +7,7 @@
 #include "third_party/blink/renderer/core/dom/node.h"
 #include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
 #include "third_party/blink/renderer/core/layout/line/line_orientation_utils.h"
+#include "third_party/blink/renderer/core/layout/ng/geometry/ng_logical_size.h"
 #include "third_party/blink/renderer/core/layout/ng/geometry/ng_physical_offset_rect.h"
 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
@@ -187,15 +188,31 @@
 
 unsigned NGPhysicalTextFragment::TextOffsetForPoint(
     const NGPhysicalOffset& point) const {
-  if (IsLineBreak())
-    return StartOffset();
-  DCHECK(TextShapeResult());
+  const ComputedStyle& style = Style();
   const LayoutUnit& point_in_line_direction =
-      Style().IsHorizontalWritingMode() ? point.left : point.top;
-  return TextShapeResult()->OffsetForPosition(point_in_line_direction.ToFloat(),
-                                              IncludePartialGlyphs,
-                                              BreakGlyphs) +
-         StartOffset();
+      style.IsHorizontalWritingMode() ? point.left : point.top;
+  if (const ShapeResult* shape_result = TextShapeResult()) {
+    return shape_result->OffsetForPosition(point_in_line_direction.ToFloat(),
+                                           IncludePartialGlyphs, BreakGlyphs) +
+           StartOffset();
+  }
+
+  // Flow control fragments such as forced line break, tabulation, soft-wrap
+  // opportunities, etc. do not have ShapeResult.
+  DCHECK(IsFlowControl());
+
+  // Zero-inline-size objects such as newline always return the start offset.
+  NGLogicalSize size = Size().ConvertToLogical(style.GetWritingMode());
+  if (!size.inline_size)
+    return StartOffset();
+
+  // Sized objects such as tabulation returns the next offset if the given point
+  // is on the right half.
+  LayoutUnit inline_offset = IsLtr(ResolvedDirection())
+                                 ? point_in_line_direction
+                                 : size.inline_size - point_in_line_direction;
+  DCHECK_EQ(1u, Length());
+  return inline_offset <= size.inline_size / 2 ? StartOffset() : EndOffset();
 }
 
 UBiDiLevel NGPhysicalTextFragment::BidiLevel() const {
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc
index 8962d68..4419bfc 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment_test.cc
@@ -245,4 +245,48 @@
   EXPECT_TRUE(closed_quote.IsAnonymousText());
 }
 
+TEST_F(NGPhysicalTextFragmentTest, TextOffsetForPointForTabulation) {
+  LoadAhem();
+  SetBodyInnerHTML(R"HTML(
+    <style>
+    #container {
+      white-space: pre;
+      font-family: Ahem;
+      font-size: 10px;
+      tab-size: 8;
+    }
+    </style>
+    <div id="container">&#09;</div>
+  )HTML");
+  auto text_fragments = CollectTextFragmentsInContainer("container");
+  ASSERT_EQ(1u, text_fragments.size());
+  const NGPhysicalTextFragment& text = *text_fragments[0];
+  EXPECT_EQ(0u, text.TextOffsetForPoint({LayoutUnit(), LayoutUnit()}));
+  EXPECT_EQ(0u, text.TextOffsetForPoint({LayoutUnit(39), LayoutUnit()}));
+  EXPECT_EQ(1u, text.TextOffsetForPoint({LayoutUnit(41), LayoutUnit()}));
+  EXPECT_EQ(1u, text.TextOffsetForPoint({LayoutUnit(80), LayoutUnit()}));
+}
+
+TEST_F(NGPhysicalTextFragmentTest, TextOffsetForPointForTabulationRtl) {
+  LoadAhem();
+  SetBodyInnerHTML(R"HTML(
+    <style>
+    #container {
+      white-space: pre;
+      font-family: Ahem;
+      font-size: 10px;
+      tab-size: 8;
+    }
+    </style>
+    <div id="container" dir="rtl">&#09;</div>
+  )HTML");
+  auto text_fragments = CollectTextFragmentsInContainer("container");
+  ASSERT_EQ(1u, text_fragments.size());
+  const NGPhysicalTextFragment& text = *text_fragments[0];
+  EXPECT_EQ(1u, text.TextOffsetForPoint({LayoutUnit(), LayoutUnit()}));
+  EXPECT_EQ(1u, text.TextOffsetForPoint({LayoutUnit(39), LayoutUnit()}));
+  EXPECT_EQ(0u, text.TextOffsetForPoint({LayoutUnit(41), LayoutUnit()}));
+  EXPECT_EQ(0u, text.TextOffsetForPoint({LayoutUnit(80), LayoutUnit()}));
+}
+
 }  // namespace blink