Bug 539356 - Add an option for frames to invalid just a rect instead of the frame bounds. r=roc
authorMatt Woodrow <mwoodrow@mozilla.com>
Wed, 29 Aug 2012 17:48:45 +1200
changeset 108568 8fa21e95b22dfa47bd5a7b011fcaba52dcba138b
parent 108567 ba21437b71135fd28f4dc30b7f185a88c0e8886c
child 108569 b55adbbe23bcb8e4851c4a3bcc5c8f2c524f0777
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewersroc
bugs539356
milestone18.0a1
Bug 539356 - Add an option for frames to invalid just a rect instead of the frame bounds. r=roc
layout/base/FrameLayerBuilder.cpp
layout/base/nsDisplayList.h
layout/generic/nsFrame.cpp
layout/generic/nsIFrame.h
layout/generic/nsImageFrame.cpp
layout/svg/nsSVGOuterSVGFrame.h
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -2109,17 +2109,18 @@ ContainerState::ProcessDisplayItems(cons
 
       // Just use its layer.
       nsRefPtr<Layer> ownLayer = item->BuildLayer(mBuilder, mManager, mParameters);
       if (!ownLayer) {
         InvalidateForLayerChange(item, ownLayer, aClip, topLeft);
         continue;
       }
 
-      if (item->IsInvalid()) {
+      nsRect invalid;
+      if (item->IsInvalid(invalid)) {
         ownLayer->SetInvalidRectToVisibleRegion();
       }
 
       // If it's not a ContainerLayer, we need to apply the scale transform
       // ourselves.
       if (!ownLayer->AsContainerLayer()) {
         ownLayer->SetPostScale(mParameters.mXScale,
                                mParameters.mYScale);
@@ -2247,41 +2248,46 @@ ContainerState::InvalidateForLayerChange
 
   ThebesLayer* newThebesLayer = aNewLayer->AsThebesLayer();
   if (!newThebesLayer) {
     return;
   }
 
   ThebesDisplayItemLayerUserData* data =
     static_cast<ThebesDisplayItemLayerUserData*>(newThebesLayer->GetUserData(&gThebesDisplayItemLayerUserData));
-  // If the frame is marked as invalidated then we want to invalidate both the old and new bounds,
-  // otherwise we only want to invalidate the changed areas.
+  // If the frame is marked as invalidated, and didn't specify a rect to invalidate  then we want to 
+  // invalidate both the old and new bounds, otherwise we only want to invalidate the changed areas.
+  // If we do get an invalid rect, then we want to add this on top of the change areas.
+  nsRect invalid;
   nsRegion combined;
   if (!oldLayer) {
     // This item is being added for the first time, invalidate its entire area.
     //TODO: We call GetGeometry again in AddThebesDisplayItem, we should reuse this.
     combined = geometry->ComputeInvalidationRegion();
 #ifdef DEBUG_INVALIDATIONS
     printf("Display item type %s(%p) added to layer %p!\n", aItem->Name(), f, aNewLayer);
 #endif
-  } else if (aItem->IsInvalid()) {
+  } else if (aItem->IsInvalid(invalid) && invalid.IsEmpty()) {
     // Either layout marked item as needing repainting, invalidate the entire old and new areas.
     combined.Or(geometry->ComputeInvalidationRegion(), oldGeometry->ComputeInvalidationRegion());
 #ifdef DEBUG_INVALIDATIONS
     printf("Display item type %s(%p) (in layer %p) belongs to an invalidated frame!\n", aItem->Name(), f, aNewLayer);
 #endif
   } else {
     // Let the display item check for geometry changes and decide what needs to be
     // repainted.
     nsPoint shift = aTopLeft - data->mLastActiveScrolledRootOrigin;
     oldGeometry->MoveBy(shift);
     aItem->ComputeInvalidationRegion(mBuilder, oldGeometry, &combined);
     oldClip->AddOffsetAndComputeDifference(shift, oldGeometry->ComputeInvalidationRegion(),
                                            aClip, geometry->ComputeInvalidationRegion(),
                                            &combined);
+
+    // Add in any rect that the frame specified
+    combined = combined.Or(combined, invalid);
 #ifdef DEBUG_INVALIDATIONS
     if (!combined.IsEmpty()) {
       printf("Display item type %s(%p) (in layer %p) changed geometry!\n", aItem->Name(), f, aNewLayer);
     }
 #endif
   }
   if (!combined.IsEmpty()) {
     InvalidatePostTransformRegion(newThebesLayer,
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -769,17 +769,21 @@ public:
   nsRect GetContentRect() {
     return GetUnderlyingFrame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
   }
 
   /**
    * Checks if the frame(s) owning this display item have been marked as invalid,
    * and needing repainting.
    */
-  virtual bool IsInvalid() { return mFrame ? mFrame->IsInvalid() : false; }
+  virtual bool IsInvalid(nsRect& aRect) { 
+    bool result = mFrame ? mFrame->IsInvalid(aRect) : false;
+    aRect += ToReferenceFrame();
+    return result;
+  }
 
   /**
    * Creates and initializes an nsDisplayItemGeometry object that retains the current
    * areas covered by this display item. These need to retain enough information
    * such that they can be compared against a future nsDisplayItem of the same type, 
    * and determine if repainting needs to happen.
    *
    * Subclasses wishing to store more information need to override both this
@@ -2000,27 +2004,31 @@ public:
   virtual bool TryMerge(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem) MOZ_OVERRIDE {
     NS_WARNING("This list should already have been flattened!!!");
     return false;
   }
   virtual void GetMergedFrames(nsTArray<nsIFrame*>* aFrames) MOZ_OVERRIDE
   {
     aFrames->AppendElements(mMergedFrames);
   }
-  virtual bool IsInvalid()
+  virtual bool IsInvalid(nsRect& aRect)
   {
-    if (mFrame->IsInvalid()) {
+    if (mFrame->IsInvalid(aRect) && aRect.IsEmpty()) {
       return true;
     }
+    nsRect temp;
     for (uint32_t i = 0; i < mMergedFrames.Length(); i++) {
-      if (mMergedFrames[i]->IsInvalid()) {
+      if (mMergedFrames[i]->IsInvalid(temp) && temp.IsEmpty()) {
+        aRect.SetEmpty();
         return true;
       }
+      aRect = aRect.Union(temp);
     }
-    return false;
+    aRect += ToReferenceFrame();
+    return !aRect.IsEmpty();
   }
   NS_DISPLAY_DECL_NAME("WrapList", TYPE_WRAP_LIST)
 
   virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder);
                                     
   virtual nsDisplayList* GetList() MOZ_OVERRIDE { return &mList; }
   
   /**
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -4801,22 +4801,60 @@ nsIFrame::InvalidateFrame()
   nsIFrame *parent = nsLayoutUtils::GetCrossDocParentFrame(this);
   while (parent && !parent->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) {
     parent->AddStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT);
     parent = nsLayoutUtils::GetCrossDocParentFrame(parent);
   }
   if (!parent) {
     SchedulePaint();
   }
+  if (HasAnyStateBits(NS_FRAME_HAS_INVALID_RECT)) {
+    Properties().Delete(InvalidationRect());
+    RemoveStateBits(NS_FRAME_HAS_INVALID_RECT);
+  }
+}
+
+void
+nsIFrame::InvalidateFrameWithRect(const nsRect& aRect)
+{
+  bool alreadyInvalid = false;
+  if (!HasAnyStateBits(NS_FRAME_NEEDS_PAINT)) {
+    InvalidateFrame();
+  } else {
+    alreadyInvalid = true;
+  } 
+
+  nsRect *rect = static_cast<nsRect*>(Properties().Get(InvalidationRect()));
+  if (!rect) {
+    if (alreadyInvalid) {
+      return;
+    }
+    rect = new nsRect();
+    Properties().Set(InvalidationRect(), rect);
+    AddStateBits(NS_FRAME_HAS_INVALID_RECT);
+  }
+
+  *rect = rect->Union(aRect);
 }
   
 bool 
-nsIFrame::IsInvalid() 
-{
-  return HasAnyStateBits(NS_FRAME_NEEDS_PAINT);
+nsIFrame::IsInvalid(nsRect& aRect)
+{
+  if (!HasAnyStateBits(NS_FRAME_NEEDS_PAINT)) {
+    return false;
+  }
+  
+  if (HasAnyStateBits(NS_FRAME_HAS_INVALID_RECT)) {
+    nsRect *rect = static_cast<nsRect*>(Properties().Get(InvalidationRect()));
+    NS_ASSERTION(rect, "Must have an invalid rect if NS_FRAME_HAS_INVALID_RECT is set!");
+    aRect = *rect;
+  } else {
+    aRect.SetEmpty();
+  }
+  return true;
 }
 
 void
 nsIFrame::SchedulePaint(uint32_t aFlags)
 {
   nsIFrame *displayRoot = nsLayoutUtils::GetDisplayRootFrame(this);
   nsPresContext *pres = displayRoot->PresContext()->GetRootPresContext();
 
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -314,16 +314,20 @@ typedef uint64_t nsFrameState;
 #define NS_FRAME_IN_POPUP                           NS_FRAME_STATE_BIT(50)
 
 // Frame has only descendant frames that needs painting - This includes
 // cross-doc children. This guarantees that all descendents have 
 // NS_FRAME_NEEDS_PAINT and NS_FRAME_ALL_DESCENDANTS_NEED_PAINT, or they 
 // have no display items.
 #define NS_FRAME_ALL_DESCENDANTS_NEED_PAINT         NS_FRAME_STATE_BIT(51)
 
+// Frame is marked as NS_FRAME_NEEDS_PAINT and also has an explicit
+// rect stored to invalidate.
+#define NS_FRAME_HAS_INVALID_RECT                   NS_FRAME_STATE_BIT(52)
+
 // Box layout bits
 #define NS_STATE_IS_HORIZONTAL                      NS_FRAME_STATE_BIT(22)
 #define NS_STATE_IS_DIRECTION_NORMAL                NS_FRAME_STATE_BIT(31)
 
 // Helper macros
 #define NS_SUBTREE_DIRTY(_frame)  \
   (((_frame)->GetStateBits() &      \
     (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN)) != 0)
@@ -950,16 +954,18 @@ public:
   NS_DECLARE_FRAME_PROPERTY(UsedBorderProperty, DestroyMargin)
 
   NS_DECLARE_FRAME_PROPERTY(ScrollLayerCount, nullptr)
 
   NS_DECLARE_FRAME_PROPERTY(LineBaselineOffset, nullptr)
 
   NS_DECLARE_FRAME_PROPERTY(CachedBackgroundImage, DestroySurface)
 
+  NS_DECLARE_FRAME_PROPERTY(InvalidationRect, DestroyRect)
+
   /**
    * Return the distance between the border edge of the frame and the
    * margin edge of the frame.  Like GetRect(), returns the dimensions
    * as of the most recent reflow.
    *
    * This doesn't include any margin collapsing that may have occurred.
    *
    * It also treats 'auto' margins as zero, and treats any margins that
@@ -2174,31 +2180,45 @@ public:
   /**
    * Marks all display items created by this frame as needing a repaint,
    * and calls SchedulePaint() if requested and one is not already pending.
    *
    * This includes all display items created by this frame, including
    * container types.
    */
   virtual void InvalidateFrame();
+
+  /**
+   * Same as InvalidateFrame(), but only mark a fixed rect as needing
+   * repainting.
+   *
+   * @param aRect The rect to invalidate, relative to the TopLeft of the
+   * frame's border box.
+   */
+  virtual void InvalidateFrameWithRect(const nsRect& aRect);
   
   /**
    * Calls InvalidateFrame() on all frames descendant frames (including
    * this one).
    * 
    * This function doesn't walk through placeholder frames to invalidate
    * the out-of-flow frames.
    */
   void InvalidateFrameSubtree();
   
   /**
    * Checks if a frame has had InvalidateFrame() called on it since the
    * last paint.
+   *
+   * If true, then the invalid rect is returned in aRect, with an
+   * empty rect meaning all pixels drawn by this frame should be
+   * invalidated.
+   * If false, aRect is left unchanged.
    */
-  bool IsInvalid();
+  bool IsInvalid(nsRect& aRect);
  
   /**
    * Check if any frame within the frame subtree (including this frame) 
    * returns true for IsInvalid().
    */
   bool HasInvalidFrameInSubtree()
   {
     return HasAnyStateBits(NS_FRAME_NEEDS_PAINT | NS_FRAME_DESCENDANT_NEEDS_PAINT);
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -582,22 +582,26 @@ nsImageFrame::OnDataAvailable(imgIReques
     // We don't care
     return NS_OK;
   }
 
   // Don't invalidate if the current visible frame isn't the one the data is
   // from
   if (!aCurrentFrame)
     return NS_OK;
-
+  
 #ifdef DEBUG_decode
   printf("Source rect (%d,%d,%d,%d)\n",
          aRect->x, aRect->y, aRect->width, aRect->height);
 #endif
-  InvalidateFrame();
+  if (aRect->IsEqualInterior(nsIntRect::GetMaxSizedIntRect())) {
+    InvalidateFrame();
+  } else {
+    InvalidateFrameWithRect(SourceRectToDest(*aRect));
+  }
   
   return NS_OK;
 }
 
 nsresult
 nsImageFrame::OnStopDecode(imgIRequest *aRequest,
                            nsresult aStatus,
                            const PRUnichar *aStatusArg)
@@ -666,18 +670,21 @@ nsImageFrame::FrameChanged(imgIRequest *
     return NS_OK;
   }
 
   if (IsPendingLoad(aContainer)) {
     // We don't care about it
     return NS_OK;
   }
 
-  // Update border+content to account for image change
-  InvalidateFrame();
+  if (aDirtyRect->IsEqualInterior(nsIntRect::GetMaxSizedIntRect())) {
+    InvalidateFrame();
+  } else {
+    InvalidateFrameWithRect(SourceRectToDest(*aDirtyRect));
+  }
   return NS_OK;
 }
 
 void
 nsImageFrame::EnsureIntrinsicSizeAndRatio(nsPresContext* aPresContext)
 {
   // if mIntrinsicSize.width and height are 0, then we should
   // check to see if the size is already known by the image container.
--- a/layout/svg/nsSVGOuterSVGFrame.h
+++ b/layout/svg/nsSVGOuterSVGFrame.h
@@ -150,17 +150,18 @@ public:
       mInvalidRegion.Or(mInvalidRegion, aRegion);
       InvalidateFrame();
     }
   }
   
   void ClearInvalidRegion() { mInvalidRegion.SetEmpty(); }
 
   const nsRegion& GetInvalidRegion() {
-    if (!IsInvalid()) {
+    nsRect rect;
+    if (!IsInvalid(rect)) {
       mInvalidRegion.SetEmpty();
     }
     return mInvalidRegion;
   }
 
   nsRegion FindInvalidatedForeignObjectFrameChildren(nsIFrame* aFrame);
 
 protected: