[LayoutNG] Guard |FirstLineBoxTopLeft()| when layout is dirty

Following r659675 (crrev.com/c/1612219), this patch guards
|FirstLineBoxTopLeft()| from when the layout tree is dirty.

Normally, all calls are limited to only when layout is clean.
However, scroll anchoring reads fragment data from dirty tree
after r660998 (crrev.com/c/1615680).

Ideally, we'd like to guard |FirstInlineFragment()| instead.
This aligns the concept of r659675, but ~10 tests in AX and
scroll anchoring depends on reading dirty trees. This
indicates that there are cases where we want to read dirty
trees. What to do more for reading dirty tree is for future
investigations.

Bug: 964619, 963103, 964854
Change-Id: I7ed4f9aac554f27ab8c2848a6d0b09a1e070215e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1619512
Commit-Queue: Koji Ishii <kojii@chromium.org>
Reviewed-by: Yoshifumi Inoue <yosin@chromium.org>
Reviewed-by: Morten Stenshorne <mstensho@chromium.org>
Cr-Commit-Position: refs/heads/master@{#661318}
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 300da2c..39ba4d2 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -825,6 +825,12 @@
   return block_flow->CurrentFragment();
 }
 
+bool LayoutObject::IsFirstInlineFragmentSafe() const {
+  DCHECK(IsInline());
+  LayoutBlockFlow* block_flow = ContainingNGBlockFlow();
+  return block_flow && !block_flow->NeedsLayout();
+}
+
 LayoutBox* LayoutObject::EnclosingScrollableBox() const {
   for (LayoutObject* ancestor = Parent(); ancestor;
        ancestor = ancestor->Parent()) {
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h
index c34647e6..06f1f8d 100644
--- a/third_party/blink/renderer/core/layout/layout_object.h
+++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -1332,6 +1332,10 @@
   // this is the root of an inline formatting context, laid out by LayoutNG.
   virtual NGPaintFragment* PaintFragment() const { return nullptr; }
 
+  // Paint/Physical fragments are not in sync with LayoutObject tree until it is
+  // laid out. For inline, it needs to check if the containing block is
+  // layout-clean. crbug.com/963103
+  bool IsFirstInlineFragmentSafe() const;
   void SetIsInLayoutNGInlineFormattingContext(bool);
   virtual NGPaintFragment* FirstInlineFragment() const { return nullptr; }
   virtual void SetFirstInlineFragment(NGPaintFragment*) {}
diff --git a/third_party/blink/renderer/core/layout/layout_text.cc b/third_party/blink/renderer/core/layout/layout_text.cc
index ba4fb32..5343744 100644
--- a/third_party/blink/renderer/core/layout/layout_text.cc
+++ b/third_party/blink/renderer/core/layout/layout_text.cc
@@ -1542,8 +1542,13 @@
 }
 
 PhysicalOffset LayoutText::FirstLineBoxTopLeft() const {
-  if (const NGPaintFragment* fragment = FirstInlineFragment())
+  if (const NGPaintFragment* fragment = FirstInlineFragment()) {
+    // TODO(kojii): Some clients call this against dirty-tree, but NG fragments
+    // are not safe to read for dirty-tree. crbug.com/963103
+    if (UNLIKELY(!IsFirstInlineFragmentSafe()))
+      return PhysicalOffset();
     return fragment->InlineOffsetToContainerBox();
+  }
   if (const auto* text_box = FirstTextBox()) {
     LayoutPoint location = text_box->Location();
     if (UNLIKELY(HasFlippedBlocksWritingMode())) {