Abstract AXLayoutObject::CanIgnoreSpaceNextTo reading InlineTextBox

CL:988375 fixed AXLayoutObject::CanIgnoreSpaceNextTo by accessing
LayoutText::First/LastTextBox() directly. Since these functions
will be gone in LayoutNG, this patch abstracts what it does and
hide InlineTextBox inside of LayoutText.

Actual implementation of these functions for LayoutNG will be
done after CL:986982 adds retrieval of NGInlineItem. It's
possible with NGOffsetMapping or NGPhysicalFragment, but
NGInlineItem would be better fit.

Bug: 821906, 636993, 830546
Change-Id: If590e8c01e59d58b0f77ba6b3f57e35e9c29acb3
Reviewed-on: https://chromium-review.googlesource.com/997912
Commit-Queue: Koji Ishii <kojii@chromium.org>
Reviewed-by: Emil A Eklund <eae@chromium.org>
Reviewed-by: Dominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#549451}
diff --git a/third_party/blink/renderer/core/layout/layout_text.cc b/third_party/blink/renderer/core/layout/layout_text.cc
index e76a0698..a27c592b 100644
--- a/third_party/blink/renderer/core/layout/layout_text.cc
+++ b/third_party/blink/renderer/core/layout/layout_text.cc
@@ -1535,6 +1535,24 @@
   return curr_pos >= (from + len);
 }
 
+UChar32 LayoutText::FirstCharacterAfterWhitespaceCollapsing() const {
+  if (InlineTextBox* text_box = FirstTextBox()) {
+    String text = text_box->GetText();
+    return text.length() ? text.CharacterStartingAt(0) : 0;
+  }
+  // TODO(kojii): Support LayoutNG once we have NGInlineItem pointers.
+  return 0;
+}
+
+UChar32 LayoutText::LastCharacterAfterWhitespaceCollapsing() const {
+  if (InlineTextBox* text_box = LastTextBox()) {
+    String text = text_box->GetText();
+    return text.length() ? text.CharacterStartingAt(text.length() - 1) : 0;
+  }
+  // TODO(kojii): Support LayoutNG once we have NGInlineItem pointers.
+  return 0;
+}
+
 FloatPoint LayoutText::FirstRunOrigin() const {
   return IntPoint(FirstRunX(), FirstRunY());
 }
diff --git a/third_party/blink/renderer/core/layout/layout_text.h b/third_party/blink/renderer/core/layout/layout_text.h
index 99d8ca7..b90bafab 100644
--- a/third_party/blink/renderer/core/layout/layout_text.h
+++ b/third_party/blink/renderer/core/layout/layout_text.h
@@ -130,6 +130,13 @@
     return text_.length();
   }  // non virtual implementation of length()
   bool ContainsOnlyWhitespace(unsigned from, unsigned len) const;
+
+  // Get characters after whitespace collapsing was applied. Returns 0 if there
+  // were no characters left. If whitespace collapsing is disabled (i.e.
+  // white-space: pre), returns characters without whitespace collapsing.
+  UChar32 FirstCharacterAfterWhitespaceCollapsing() const;
+  UChar32 LastCharacterAfterWhitespaceCollapsing() const;
+
   void PositionLineBox(InlineBox*);
 
   virtual float Width(unsigned from,
diff --git a/third_party/blink/renderer/core/layout/layout_text_test.cc b/third_party/blink/renderer/core/layout/layout_text_test.cc
index 7225971..3cd39c3 100644
--- a/third_party/blink/renderer/core/layout/layout_text_test.cc
+++ b/third_party/blink/renderer/core/layout/layout_text_test.cc
@@ -201,6 +201,47 @@
   }
 }
 
+// TODO(kojii): Include LayoutNG tests by switching to
+// ParameterizedLayoutTextTest when these functions support LayoutNG.
+TEST_F(LayoutTextTest, CharacterAfterWhitespaceCollapsing) {
+  SetBodyInnerHTML("a<span id=target> b </span>");
+  LayoutText* layout_text = GetLayoutTextById("target");
+  EXPECT_EQ(' ', layout_text->FirstCharacterAfterWhitespaceCollapsing());
+  EXPECT_EQ('b', layout_text->LastCharacterAfterWhitespaceCollapsing());
+
+  SetBodyInnerHTML("a <span id=target> b </span>");
+  layout_text = GetLayoutTextById("target");
+  EXPECT_EQ('b', layout_text->FirstCharacterAfterWhitespaceCollapsing());
+  EXPECT_EQ('b', layout_text->LastCharacterAfterWhitespaceCollapsing());
+
+  SetBodyInnerHTML("a<span id=target> </span>b");
+  layout_text = GetLayoutTextById("target");
+  EXPECT_EQ(' ', layout_text->FirstCharacterAfterWhitespaceCollapsing());
+  EXPECT_EQ(' ', layout_text->LastCharacterAfterWhitespaceCollapsing());
+
+  SetBodyInnerHTML("a <span id=target> </span>b");
+  layout_text = GetLayoutTextById("target");
+  DCHECK(!layout_text->HasTextBoxes());
+  EXPECT_EQ(0, layout_text->FirstCharacterAfterWhitespaceCollapsing());
+  EXPECT_EQ(0, layout_text->LastCharacterAfterWhitespaceCollapsing());
+
+  SetBodyInnerHTML(
+      "<span style='white-space: pre'>a <span id=target> </span>b</span>");
+  layout_text = GetLayoutTextById("target");
+  EXPECT_EQ(' ', layout_text->FirstCharacterAfterWhitespaceCollapsing());
+  EXPECT_EQ(' ', layout_text->LastCharacterAfterWhitespaceCollapsing());
+
+  SetBodyInnerHTML("<span id=target>Hello </span> <span>world</span>");
+  layout_text = GetLayoutTextById("target");
+  EXPECT_EQ('H', layout_text->FirstCharacterAfterWhitespaceCollapsing());
+  EXPECT_EQ(' ', layout_text->LastCharacterAfterWhitespaceCollapsing());
+  layout_text =
+      ToLayoutText(GetLayoutObjectByElementId("target")->NextSibling());
+  DCHECK(!layout_text->HasTextBoxes());
+  EXPECT_EQ(0, layout_text->FirstCharacterAfterWhitespaceCollapsing());
+  EXPECT_EQ(0, layout_text->LastCharacterAfterWhitespaceCollapsing());
+}
+
 TEST_P(ParameterizedLayoutTextTest, CaretMinMaxOffset) {
   SetBasicBody("foo");
   EXPECT_EQ(0, GetBasicText()->CaretMinOffset());
diff --git a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
index 75c0e9d..c1d210d 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
@@ -768,12 +768,9 @@
       return false;
     if (layout_text->GetText().Impl()->ContainsOnlyWhitespace())
       return true;
-    DCHECK(layout_text->HasTextBoxes());
-    InlineTextBox* adjacent =
-        is_after ? layout_text->FirstTextBox() : layout_text->LastTextBox();
-    auto text = adjacent->GetText();
     auto adjacent_char =
-        text.CharacterStartingAt(is_after ? 0 : text.length() - 1);
+        is_after ? layout_text->FirstCharacterAfterWhitespaceCollapsing()
+                 : layout_text->LastCharacterAfterWhitespaceCollapsing();
     return adjacent_char == ' ' || adjacent_char == '\n' ||
            adjacent_char == '\t';
   }