Bug 428156. ComputeRepaintRegionForCopy needs to handle non-moving frames that clip moving content. r+sr=dbaron,a=beltzner
authorroc+@cs.cmu.edu
Thu, 17 Apr 2008 01:15:14 -0700
changeset 14408 5e2ffdbabafdb87f9652c0a1e66931386c5ecbf8
parent 14407 0b2758ace99941bbd0be7358c5f51c7d5dfd268b
child 14409 4cb89907d5abffb91656479f72ed7ed90738ced8
push idunknown
push userunknown
push dateunknown
reviewersbeltzner
bugs428156
milestone1.9pre
Bug 428156. ComputeRepaintRegionForCopy needs to handle non-moving frames that clip moving content. r+sr=dbaron,a=beltzner
layout/base/nsDisplayList.cpp
layout/base/nsDisplayList.h
layout/base/nsLayoutUtils.cpp
layout/base/nsPresShell.cpp
layout/generic/nsFrame.cpp
layout/generic/nsFrameFrame.cpp
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -847,25 +847,27 @@ PRBool nsDisplayOpacity::TryMerge(nsDisp
   // compositing group
   // aItem->GetUnderlyingFrame() returns non-null because it's nsDisplayOpacity
   if (aItem->GetUnderlyingFrame()->GetContent() != mFrame->GetContent())
     return PR_FALSE;
   mList.AppendToBottom(&static_cast<nsDisplayOpacity*>(aItem)->mList);
   return PR_TRUE;
 }
 
-nsDisplayClip::nsDisplayClip(nsIFrame* aFrame, nsDisplayItem* aItem,
-    const nsRect& aRect)
-   : nsDisplayWrapList(aFrame, aItem), mClip(aRect) {
+nsDisplayClip::nsDisplayClip(nsIFrame* aFrame, nsIFrame* aClippingFrame,
+        nsDisplayItem* aItem, const nsRect& aRect)
+   : nsDisplayWrapList(aFrame, aItem),
+     mClippingFrame(aClippingFrame), mClip(aRect) {
   MOZ_COUNT_CTOR(nsDisplayClip);
 }
 
-nsDisplayClip::nsDisplayClip(nsIFrame* aFrame, nsDisplayList* aList,
-    const nsRect& aRect)
-   : nsDisplayWrapList(aFrame, aList), mClip(aRect) {
+nsDisplayClip::nsDisplayClip(nsIFrame* aFrame, nsIFrame* aClippingFrame,
+        nsDisplayList* aList, const nsRect& aRect)
+   : nsDisplayWrapList(aFrame, aList),
+     mClippingFrame(aClippingFrame), mClip(aRect) {
   MOZ_COUNT_CTOR(nsDisplayClip);
 }
 
 nsRect nsDisplayClip::GetBounds(nsDisplayListBuilder* aBuilder) {
   nsRect r = nsDisplayWrapList::GetBounds(aBuilder);
   r.IntersectRect(mClip, r);
   return r;
 }
@@ -898,18 +900,19 @@ PRBool nsDisplayClip::OptimizeVisibility
   return anyVisible;
 }
 
 PRBool nsDisplayClip::TryMerge(nsDisplayListBuilder* aBuilder,
                                nsDisplayItem* aItem) {
   if (aItem->GetType() != TYPE_CLIP)
     return PR_FALSE;
   nsDisplayClip* other = static_cast<nsDisplayClip*>(aItem);
-  if (other->mClip != mClip)
+  if (other->mClip != mClip || other->mClippingFrame != mClippingFrame)
     return PR_FALSE;
   mList.AppendToBottom(&other->mList);
   return PR_TRUE;
 }
 
 nsDisplayWrapList* nsDisplayClip::WrapWithClone(nsDisplayListBuilder* aBuilder,
                                                 nsDisplayItem* aItem) {
-  return new (aBuilder) nsDisplayClip(aItem->GetUnderlyingFrame(), aItem, mClip);
+  return new (aBuilder)
+    nsDisplayClip(aItem->GetUnderlyingFrame(), mClippingFrame, aItem, mClip);
 }
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -1185,34 +1185,47 @@ private:
 
 /**
  * nsDisplayClip can clip a list of items, but we take a single item
  * initially and then later merge other items into it when we merge
  * adjacent matching nsDisplayClips
  */
 class nsDisplayClip : public nsDisplayWrapList {
 public:
-  nsDisplayClip(nsIFrame* aFrame, nsDisplayItem* aItem, const nsRect& aRect);
-  nsDisplayClip(nsIFrame* aFrame, nsDisplayList* aList, const nsRect& aRect);
+  /**
+   * @param aFrame the frame that should be considered the underlying
+   * frame for this content, e.g. the frame whose z-index we have.
+   * @param aClippingFrame the frame that is inducing the clipping.
+   */
+  nsDisplayClip(nsIFrame* aFrame, nsIFrame* aClippingFrame, 
+                nsDisplayItem* aItem, const nsRect& aRect);
+  nsDisplayClip(nsIFrame* aFrame, nsIFrame* aClippingFrame,
+                nsDisplayList* aList, const nsRect& aRect);
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayClip();
 #endif
   
   virtual Type GetType() { return TYPE_CLIP; }
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder);
   virtual void Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx,
      const nsRect& aDirtyRect);
   virtual PRBool OptimizeVisibility(nsDisplayListBuilder* aBuilder,
                                     nsRegion* aVisibleRegion);
   virtual PRBool TryMerge(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem);
   NS_DISPLAY_DECL_NAME("Clip")
   
   nsRect GetClipRect() { return mClip; }
   void SetClipRect(const nsRect& aRect) { mClip = aRect; }
+  nsIFrame* GetClippingFrame() { return mClippingFrame; }
 
   virtual nsDisplayWrapList* WrapWithClone(nsDisplayListBuilder* aBuilder,
                                            nsDisplayItem* aItem);
 
 private:
-  nsRect mClip;
+  // The frame that is responsible for the clipping. This may be different
+  // from mFrame because mFrame represents the content that is being
+  // clipped, and for example may be used to obtain the z-index of the
+  // content.
+  nsIFrame* mClippingFrame;
+  nsRect    mClip;
 };
 
 #endif /*NSDISPLAYLIST_H_*/
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1012,18 +1012,35 @@ static void
 AddItemsToRegion(nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
                  const nsRect& aRect, const nsRect& aClipRect, nsPoint aDelta,
                  nsRegion* aRegion)
 {
   for (nsDisplayItem* item = aList->GetBottom(); item; item = item->GetAbove()) {
     nsDisplayList* sublist = item->GetList();
     if (sublist) {
       if (item->GetType() == nsDisplayItem::TYPE_CLIP) {
-        nsRect clip;
-        clip.IntersectRect(aClipRect, static_cast<nsDisplayClip*>(item)->GetClipRect());
+        nsDisplayClip* clipItem = static_cast<nsDisplayClip*>(item);
+        nsRect clip = aClipRect;
+        // If the clipping frame is moving, then it isn't clipping any
+        // non-moving content (see ApplyAbsPosClipping), so we don't need
+        // to do anything special, but we should not restrict aClipRect.
+        if (!aBuilder->IsMovingFrame(clipItem->GetClippingFrame())) {
+          clip.IntersectRect(clip, clipItem->GetClipRect());
+
+          // Invalidate the translation of the source area that was clipped out
+          nsRegion clippedOutSource;
+          clippedOutSource.Sub(aRect, clip);
+          clippedOutSource.MoveBy(aDelta);
+          aRegion->Or(*aRegion, clippedOutSource);
+
+          // Invalidate the destination area that is clipped out
+          nsRegion clippedOutDestination;
+          clippedOutDestination.Sub(aRect + aDelta, clip);
+          aRegion->Or(*aRegion, clippedOutDestination);
+        }
         AddItemsToRegion(aBuilder, sublist, aRect, clip, aDelta, aRegion);
       } else {
         // opacity, or a generic sublist
         AddItemsToRegion(aBuilder, sublist, aRect, aClipRect, aDelta, aRegion);
       }
     } else {
       nsRect r;
       if (r.IntersectRect(aClipRect, item->GetBounds(aBuilder))) {
@@ -1070,17 +1087,17 @@ nsLayoutUtils::ComputeRepaintRegionForCo
   // We need to ensure that the non-moving frame display items we get
   // are the same ones we would have gotten if we had constructed the
   // 'before' display list. So opaque moving items are only considered to
   // cover the intersection of their old and new bounds (see
   // nsDisplayItem::OptimizeVisibility). A moving clip item is not allowed
   // to clip non-moving items --- this is enforced by the code that sets
   // up nsDisplayClip items, in particular see ApplyAbsPosClipping.
   // XXX but currently a non-moving clip item can incorrectly clip
-  // moving moving items! See bug 428156.
+  // moving items! See bug 428156.
   nsRect rect;
   rect.UnionRect(aCopyRect, aCopyRect + aDelta);
   nsDisplayListBuilder builder(aRootFrame, PR_FALSE, PR_TRUE);
   builder.SetMovingFrame(aMovingFrame, aDelta);
   nsDisplayList list;
 
   builder.EnterPresShell(aRootFrame, rect);
 
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -5096,17 +5096,17 @@ PresShell::ClipListToRange(nsDisplayList
             nscoord x = PR_MIN(startPoint.x, endPoint.x);
             textRect.x += x;
             textRect.width = PR_MAX(startPoint.x, endPoint.x) - x;
             surfaceRect.UnionRect(surfaceRect, textRect);
 
             // wrap the item in an nsDisplayClip so that it can be clipped to
             // the selection. If the allocation fails, fall through and delete
             // the item below.
-            itemToInsert = new (aBuilder)nsDisplayClip(frame, i, textRect);
+            itemToInsert = new (aBuilder)nsDisplayClip(frame, frame, i, textRect);
           }
         }
         else {
           // if the node is within the range, append it to the temporary list
           PRBool before, after;
           nsRange::CompareNodeToRange(content, aRange, &before, &after);
           if (!before && !after) {
             itemToInsert = i;
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -1029,49 +1029,51 @@ public:
     : mContainer(aContainer), mRect(aRect),
       mClipBorderBackground(aClipBorderBackground), mClipAll(aClipAll) {}
   virtual PRBool WrapBorderBackground() { return mClipBorderBackground; }
   virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
                                   nsIFrame* aFrame, nsDisplayList* aList) {
     // We are not a stacking context root. There is no valid underlying
     // frame for the whole list. These items are all in-flow descendants so
     // we can safely just clip them.
-    return new (aBuilder) nsDisplayClip(nsnull, aList, mRect);
+    return new (aBuilder) nsDisplayClip(nsnull, mContainer, aList, mRect);
   }
   virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
                                   nsDisplayItem* aItem) {
     nsIFrame* f = aItem->GetUnderlyingFrame();
     if (mClipAll || nsLayoutUtils::IsProperAncestorFrame(mContainer, f, nsnull))
-      return new (aBuilder) nsDisplayClip(f, aItem, mRect);
+      return new (aBuilder) nsDisplayClip(f, mContainer, aItem, mRect);
     return aItem;
   }
 protected:
   nsIFrame*    mContainer;
   nsRect       mRect;
   PRPackedBool mClipBorderBackground;
   PRPackedBool mClipAll;
 };
 
 class nsAbsPosClipWrapper : public nsDisplayWrapper
 {
 public:
-  nsAbsPosClipWrapper(const nsRect& aRect)
-    : mRect(aRect) {}
+  nsAbsPosClipWrapper(nsIFrame* aContainer, const nsRect& aRect)
+    : mContainer(aContainer), mRect(aRect) {}
   virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
                                   nsIFrame* aFrame, nsDisplayList* aList) {
     // We are not a stacking context root. There is no valid underlying
     // frame for the whole list.
-    return new (aBuilder) nsDisplayClip(nsnull, aList, mRect);
+    return new (aBuilder) nsDisplayClip(nsnull, mContainer, aList, mRect);
   }
   virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
                                   nsDisplayItem* aItem) {
-    return new (aBuilder) nsDisplayClip(aItem->GetUnderlyingFrame(), aItem, mRect);
+    return new (aBuilder) nsDisplayClip(aItem->GetUnderlyingFrame(),
+            mContainer, aItem, mRect);
   }
 protected:
-  nsRect mRect;
+  nsIFrame* mContainer;
+  nsRect    mRect;
 };
 
 nsresult
 nsIFrame::OverflowClip(nsDisplayListBuilder*   aBuilder,
                        const nsDisplayListSet& aFromSet,
                        const nsDisplayListSet& aToSet,
                        const nsRect&           aClipRect,
                        PRBool                  aClipBorderBackground,
@@ -1082,17 +1084,17 @@ nsIFrame::OverflowClip(nsDisplayListBuil
 }
 
 nsresult
 nsIFrame::Clip(nsDisplayListBuilder*   aBuilder,
                const nsDisplayListSet& aFromSet,
                const nsDisplayListSet& aToSet,
                const nsRect&           aClipRect)
 {
-  nsAbsPosClipWrapper wrapper(aClipRect);
+  nsAbsPosClipWrapper wrapper(this, aClipRect);
   return wrapper.WrapLists(aBuilder, this, aFromSet, aToSet);
 }
 
 static nsresult
 BuildDisplayListWithOverflowClip(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
     const nsRect& aDirtyRect, const nsDisplayListSet& aSet,
     const nsRect& aClipRect)
 {
@@ -1231,33 +1233,61 @@ nsIFrame::BuildDisplayListForStackingCon
 #ifdef NS_DEBUG
   DisplayDebugBorders(aBuilder, this, set);
 #endif
   resultList.AppendToTop(set.Outlines());
   // 8, 9: non-negative z-index children
   resultList.AppendToTop(set.PositionedDescendants());
 
   if (applyAbsPosClipping) {
-    nsAbsPosClipWrapper wrapper(absPosClip);
+    nsAbsPosClipWrapper wrapper(this, absPosClip);
     nsDisplayItem* item = wrapper.WrapList(aBuilder, this, &resultList);
     if (!item)
       return NS_ERROR_OUT_OF_MEMORY;
     // resultList was emptied
     resultList.AppendToTop(item);
   }
  
   if (disp->mOpacity == 1.0f) {
     aList->AppendToTop(&resultList);
   } else {
     rv = aList->AppendNewToTop(new (aBuilder) nsDisplayOpacity(this, &resultList));
   }
   
   return rv;
 }
 
+class nsDisplaySummary : public nsDisplayItem
+{
+public:
+  nsDisplaySummary(nsIFrame* aFrame) : nsDisplayItem(aFrame) {
+    MOZ_COUNT_CTOR(nsDisplaySummary);
+  }
+#ifdef NS_BUILD_REFCNT_LOGGING
+  virtual ~nsDisplaySummary() {
+    MOZ_COUNT_DTOR(nsDisplaySummary);
+  }
+#endif
+
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder);
+  NS_DISPLAY_DECL_NAME("Summary")
+};
+
+nsRect
+nsDisplaySummary::GetBounds(nsDisplayListBuilder* aBuilder) {
+  return mFrame->GetOverflowRect() + aBuilder->ToReferenceFrame(mFrame);
+}
+
+static void
+AddSummaryFrameToList(nsDisplayListBuilder* aBuilder,
+                      nsIFrame* aFrame, nsDisplayList* aList)
+{
+  aList->AppendNewToTop(new (aBuilder) nsDisplaySummary(aFrame));
+}
+
 nsresult
 nsIFrame::BuildDisplayListForChild(nsDisplayListBuilder*   aBuilder,
                                    nsIFrame*               aChild,
                                    const nsRect&           aDirtyRect,
                                    const nsDisplayListSet& aLists,
                                    PRUint32                aFlags) {
   // If painting is restricted to just the background of the top level frame,
   // then we have nothing to do here.
@@ -1331,17 +1361,27 @@ nsIFrame::BuildDisplayListForChild(nsDis
 
     // Note that aBuilder->GetRootMovingFrame() is non-null only if we're doing
     // ComputeRepaintRegionForCopy.
     if (aBuilder->GetRootMovingFrame() == this &&
         !PresContext()->GetRenderedPositionVaryingContent()) {
       // No position-varying content has been rendered in this prescontext.
       // Therefore there is no need to descend into analyzing the moving frame's
       // descendants looking for such content, because any bitblit will
-      // not be copying position-varying graphics.
+      // not be copying position-varying graphics. However, to keep things
+      // sane we still need display items representing the frame subtree.
+      // We need to add these summaries to every list that the child could
+      // contribute to. This avoids display list optimizations optimizing
+      // away entire lists because they appear to be empty.
+      AddSummaryFrameToList(aBuilder, aChild, aLists.BlockBorderBackgrounds());
+      AddSummaryFrameToList(aBuilder, aChild, aLists.BorderBackground());
+      AddSummaryFrameToList(aBuilder, aChild, aLists.Content());
+      AddSummaryFrameToList(aBuilder, aChild, aLists.Floats());
+      AddSummaryFrameToList(aBuilder, aChild, aLists.PositionedDescendants());      
+      AddSummaryFrameToList(aBuilder, aChild, aLists.Outlines());
       return NS_OK;
     }
   }
 
   // XXX need to have inline-block and inline-table set pseudoStackingContext
   
   const nsStyleDisplay* ourDisp = GetStyleDisplay();
   // REVIEW: Taken from nsBoxFrame::Paint
@@ -1422,17 +1462,17 @@ nsIFrame::BuildDisplayListForChild(nsDis
       rv = aChild->BuildDisplayList(aBuilder, clippedDirtyRect, pseudoStack);
       if (NS_SUCCEEDED(rv)) {
         rv = aBuilder->DisplayCaret(aChild, dirty, aLists);
       }
     }
     
     if (NS_SUCCEEDED(rv)) {
       if (isPositioned && applyAbsPosClipping) {
-        nsAbsPosClipWrapper wrapper(clipRect);
+        nsAbsPosClipWrapper wrapper(aChild, clipRect);
         rv = wrapper.WrapListsInPlace(aBuilder, aChild, pseudoStack);
       }
     }
     list.AppendToTop(pseudoStack.BorderBackground());
     list.AppendToTop(pseudoStack.BlockBorderBackgrounds());
     list.AppendToTop(pseudoStack.Floats());
     list.AppendToTop(pseudoStack.Content());
     list.AppendToTop(pseudoStack.Outlines());
--- a/layout/generic/nsFrameFrame.cpp
+++ b/layout/generic/nsFrameFrame.cpp
@@ -347,17 +347,17 @@ nsSubDocumentFrame::BuildDisplayList(nsD
 
   aBuilder->EnterPresShell(f, dirty);
 
   // Clip children to the child root frame's rectangle
   nsDisplayList childItems;
   rv = f->BuildDisplayListForStackingContext(aBuilder, dirty, &childItems);
   if (NS_SUCCEEDED(rv)) {
     rv = aLists.Content()->AppendNewToTop(
-        new (aBuilder) nsDisplayClip(nsnull, &childItems,
+        new (aBuilder) nsDisplayClip(nsnull, this, &childItems,
               nsRect(aBuilder->ToReferenceFrame(f), f->GetSize())));
     // delete childItems in case of OOM
     childItems.DeleteAll();
   }
 
   aBuilder->LeavePresShell(f, dirty);
   return rv;
 }