Bug 1459937 - Mark pulled lines (from n-i-f or overflow) dirty - r=dbaron draft
authorGerald Squelart <gsquelart@mozilla.com>
Mon, 09 Jul 2018 11:42:47 +1000
changeset 820200 ee55a9ae7408e1f2603c1b2bc80ddcd8dbc837f0
parent 820131 5a8107262015714d2907a85abc24c847ad9b32d2
child 820201 ec99f380c978c6d28135490401beb0bb54c8e2b3
push id116747
push usergsquelart@mozilla.com
push dateThu, 19 Jul 2018 05:17:20 +0000
reviewersdbaron
bugs1459937
milestone63.0a1
Bug 1459937 - Mark pulled lines (from n-i-f or overflow) dirty - r=dbaron Lines pulled from next-in-flow or overflow frames have probably not been marked dirty (as ReflowInput hasn't dealt with them when it was constructed), so we need to mark them dirty for proper reflow. If we don't do that, and they don't fit in the current column, the next column will only mark its current children dirty, so when pulling back its first lines from the previous column they will not be reflowed as needed, which causes this bug. MozReview-Commit-ID: 8GFO1ZWuZ1b
layout/base/nsLayoutUtils.h
layout/generic/nsBlockFrame.cpp
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -129,16 +129,22 @@ enum class RelativeTo {
 
 // Flags to customize the behavior of nsLayoutUtils::DrawString.
 enum class DrawStringFlags {
   eDefault         = 0x0,
   eForceHorizontal = 0x1 // Forces the text to be drawn horizontally.
 };
 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DrawStringFlags)
 
+enum class ReparentingDirection {
+  Backwards,
+  Forwards,
+  Variable // Could be either of the above; take most pessimistic action.
+};
+
 /**
  * nsLayoutUtils is a namespace class used for various helper
  * functions that are useful in multiple places in layout.  The goal
  * is not to define multiple copies of the same static helper.
  */
 class nsLayoutUtils
 {
   typedef mozilla::ComputedStyle ComputedStyle;
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -601,35 +601,63 @@ nsBlockFrame::GetChildLists(nsTArray<Chi
 
 /* virtual */ bool
 nsBlockFrame::IsFloatContainingBlock() const
 {
   return true;
 }
 
 static void
-ReparentFrame(nsIFrame* aFrame, nsContainerFrame* aOldParent,
-              nsContainerFrame* aNewParent)
+ReparentFrameInternal(nsIFrame* aFrame, nsContainerFrame* aOldParent,
+                      nsContainerFrame* aNewParent, bool aMarkDirty)
 {
   NS_ASSERTION(aOldParent == aFrame->GetParent(),
                "Parent not consistent with expectations");
 
   aFrame->SetParent(aNewParent);
+  if (aMarkDirty) {
+    aFrame->AddStateBits(NS_FRAME_IS_DIRTY);
+  }
 
   // When pushing and pulling frames we need to check for whether any
   // views need to be reparented
   nsContainerFrame::ReparentFrameView(aFrame, aOldParent, aNewParent);
 }
 
+static bool
+ShouldMarkReparentedFramesDirty(nsIFrame* aNewParent,
+                                ReparentingDirection aDirection)
+{
+  // Frames going forward must have already been reflowed, or at least marked
+  // dirty. Otherwise frames going backwards (or direction is unknown) may not
+  // be marked dirty yet.
+  return (aDirection != ReparentingDirection::Forwards) &&
+         (aNewParent->GetStateBits() & NS_FRAME_IS_DIRTY);
+}
+
+// Because a frame with NS_FRAME_IS_DIRTY marks all of its children dirty at
+// the start of its reflow, when we move a frame from a later frame backwards to
+// an earlier frame, and the earlier frame has NS_FRAME_IS_DIRTY (though that
+// should corresponded with the later frame having NS_FRAME_IS_DIRTY), we need
+// to add NS_FRAME_IS_DIRTY to the reparented frame.
+static void
+ReparentFrame(nsIFrame* aFrame, nsContainerFrame* aOldParent,
+              nsContainerFrame* aNewParent, ReparentingDirection aDirection)
+{
+  const bool markDirty = ShouldMarkReparentedFramesDirty(aNewParent, aDirection);
+  ReparentFrameInternal(aFrame, aOldParent, aNewParent, markDirty);
+}
+
 static void
 ReparentFrames(nsFrameList& aFrameList, nsContainerFrame* aOldParent,
-               nsContainerFrame* aNewParent)
-{
+               nsContainerFrame* aNewParent, ReparentingDirection aDirection)
+{
+  const bool markDirty = ShouldMarkReparentedFramesDirty(aNewParent, aDirection);
   for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) {
-    ReparentFrame(e.get(), aOldParent, aNewParent);
+    ReparentFrameInternal(e.get(), aOldParent, aNewParent, markDirty);
   }
 }
 
 /**
  * Remove the first line from aFromLines and adjust the associated frame list
  * aFromFrames accordingly.  The removed line is assigned to *aOutLine and
  * a frame list with its frames is assigned to *aOutFrames, i.e. the frames
  * that were extracted from the head of aFromFrames.
@@ -2125,17 +2153,18 @@ void
 nsBlockFrame::ReparentFloats(nsIFrame* aFirstFrame, nsBlockFrame* aOldParent,
                              bool aReparentSiblings) {
   nsFrameList list;
   aOldParent->CollectFloats(aFirstFrame, list, aReparentSiblings);
   if (list.NotEmpty()) {
     for (nsIFrame* f : list) {
       MOZ_ASSERT(!(f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT),
                  "CollectFloats should've removed that bit");
-      ReparentFrame(f, aOldParent, this);
+      // XXX: "Backwards" to force NS_FRAME_IS_DIRTY for now, see next patch.
+      ReparentFrame(f, aOldParent, this, ReparentingDirection::Backwards);
     }
     mFloats.AppendFrames(nullptr, list);
   }
 }
 
 static void DumpLine(const BlockReflowInput& aState, nsLineBox* aLine,
                      nscoord aDeltaBCoord, int32_t aDeltaIndent) {
 #ifdef DEBUG
@@ -2622,17 +2651,18 @@ nsBlockFrame::ReflowDirtyLines(BlockRefl
                      !pulledLine->mFirstChild, "bad empty line");
         nextInFlow->FreeLineBox(pulledLine);
         continue;
       }
 
       if (pulledLine == nextInFlow->GetLineCursor()) {
         nextInFlow->ClearLineCursor();
       }
-      ReparentFrames(pulledFrames, nextInFlow, this);
+      ReparentFrames(pulledFrames, nextInFlow, this,
+                     ReparentingDirection::Backwards);
 
       NS_ASSERTION(pulledFrames.LastChild() == pulledLine->LastChild(),
                    "Unexpected last frame");
       NS_ASSERTION(aState.mPrevChild || mLines.empty(), "should have a prevchild here");
       NS_ASSERTION(aState.mPrevChild == mFrames.LastChild(),
                    "Incorrect aState.mPrevChild before inserting line at end");
 
       // Shift pulledLine's frames into our mFrames list.
@@ -2917,17 +2947,17 @@ nsBlockFrame::PullFrameFrom(nsLineBox*  
     // need to add it to our sibling list.
     MOZ_ASSERT(aLine == mLines.back());
     MOZ_ASSERT(aFromLine == aFromContainer->mLines.begin(),
                "should only pull from first line");
     aFromContainer->mFrames.RemoveFrame(frame);
 
     // When pushing and pulling frames we need to check for whether any
     // views need to be reparented.
-    ReparentFrame(frame, aFromContainer, this);
+    ReparentFrame(frame, aFromContainer, this, ReparentingDirection::Backwards);
     mFrames.AppendFrame(nullptr, frame);
 
     // The frame might have (or contain) floats that need to be brought
     // over too. (pass 'false' since there are no siblings to check)
     ReparentFloats(frame, aFromContainer, false);
   } else {
     MOZ_ASSERT(aLine == aFromLine.prev());
   }
@@ -3666,18 +3696,20 @@ nsBlockFrame::ReflowBlockFrame(BlockRefl
             if (!madeContinuation &&
                 (NS_FRAME_IS_OVERFLOW_CONTAINER & nextFrame->GetStateBits())) {
               nsOverflowContinuationTracker::AutoFinish fini(aState.mOverflowTracker, frame);
               nsContainerFrame* parent = nextFrame->GetParent();
               nsresult rv = parent->StealFrame(nextFrame);
               if (NS_FAILED(rv)) {
                 return;
               }
-              if (parent != this)
-                ReparentFrame(nextFrame, parent, this);
+              if (parent != this) {
+                ReparentFrame(nextFrame, parent, this,
+                              ReparentingDirection::Variable);
+              }
               mFrames.InsertFrame(nullptr, frame, nextFrame);
               madeContinuation = true; // needs to be added to mLines
               nextFrame->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
               frameReflowStatus.SetNextInFlowNeedsReflow();
             }
 
             // Push continuation to a new line, but only if we actually made one.
             if (madeContinuation) {
@@ -4326,17 +4358,17 @@ nsBlockFrame::SplitFloat(BlockReflowInpu
   MOZ_ASSERT(aState.mBlock == this);
 
   nsIFrame* nextInFlow = aFloat->GetNextInFlow();
   if (nextInFlow) {
     nsContainerFrame *oldParent = nextInFlow->GetParent();
     DebugOnly<nsresult> rv = oldParent->StealFrame(nextInFlow);
     NS_ASSERTION(NS_SUCCEEDED(rv), "StealFrame failed");
     if (oldParent != this) {
-      ReparentFrame(nextInFlow, oldParent, this);
+      ReparentFrame(nextInFlow, oldParent, this, ReparentingDirection::Backwards);
     }
     if (!aFloatStatus.IsOverflowIncomplete()) {
       nextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
     }
   } else {
     nextInFlow = aState.mPresContext->PresShell()->FrameConstructor()->
       CreateContinuingFrame(aState.mPresContext, aFloat, this);
   }
@@ -4845,31 +4877,33 @@ nsBlockFrame::DrainOverflowLines()
   // Steal the prev-in-flow's overflow lines and prepend them.
   bool didFindOverflow = false;
   nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow());
   if (prevBlock) {
     prevBlock->ClearLineCursor();
     FrameLines* overflowLines = prevBlock->RemoveOverflowLines();
     if (overflowLines) {
       // Make all the frames on the overflow line list mine.
-      ReparentFrames(overflowLines->mFrames, prevBlock, this);
+      ReparentFrames(overflowLines->mFrames, prevBlock, this,
+                     ReparentingDirection::Forwards);
 
       // Make the overflow out-of-flow frames mine too.
       nsAutoOOFFrameList oofs(prevBlock);
       if (oofs.mList.NotEmpty()) {
         // In case we own a next-in-flow of any of the drained frames, then
         // those are now not PUSHED_FLOATs anymore.
         for (nsFrameList::Enumerator e(oofs.mList); !e.AtEnd(); e.Next()) {
           nsIFrame* nif = e.get()->GetNextInFlow();
           for (; nif && nif->GetParent() == this; nif = nif->GetNextInFlow()) {
             MOZ_ASSERT(mFloats.ContainsFrame(nif));
             nif->RemoveStateBits(NS_FRAME_IS_PUSHED_FLOAT);
           }
         }
-        ReparentFrames(oofs.mList, prevBlock, this);
+        ReparentFrames(oofs.mList, prevBlock, this,
+                       ReparentingDirection::Forwards);
         mFloats.InsertFrames(nullptr, nullptr, oofs.mList);
       }
 
       if (!mLines.empty()) {
         // Remember to recompute the margins on the first line. This will
         // also recompute the correct deltaBCoord if necessary.
         mLines.front()->MarkPreviousMarginDirty();
       }