Bug 564991. Part 7: Create Begin/EndDeferredInvalidates API so we can catch and defer invalidates on frames (and suppress certain areas completely). r=mats,sr=dbaron
authorRobert O'Callahan <robert@ocallahan.org>
Fri, 16 Jul 2010 09:07:50 +1200
changeset 47735 213c2dc6c88ac76fbc336ea79dd414742e533797
parent 47734 e4213cc1138f17b4b31b3d7212b1522fc99311e4
child 47736 e284964e5bcf5d1951d67d6a1f9386b9f023d583
push idunknown
push userunknown
push dateunknown
reviewersmats, dbaron
bugs564991
milestone2.0b2pre
Bug 564991. Part 7: Create Begin/EndDeferredInvalidates API so we can catch and defer invalidates on frames (and suppress certain areas completely). r=mats,sr=dbaron
layout/base/nsPresShell.cpp
layout/generic/nsFrame.cpp
layout/generic/nsIFrame.h
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -5815,17 +5815,16 @@ nscolor PresShell::ComputeBackstopColor(
   // Within an opaque widget (or no widget at all), so the backstop
   // color must be totally opaque. The user's default background
   // as reported by the prescontext is guaranteed to be opaque.
   return GetPresContext()->DefaultBackgroundColor();
 }
 
 struct PaintParams {
   nsIFrame* mFrame;
-  nsPoint mOffsetToRoot;
   nsPoint mOffsetToWidget;
   const nsRegion* mDirtyRegion;
   nscolor mBackgroundColor;
 };
 
 static void DrawThebesLayer(ThebesLayer* aLayer,
                             gfxContext* aContext,
                             const nsIntRegion& aRegionToDraw,
@@ -5836,21 +5835,19 @@ static void DrawThebesLayer(ThebesLayer*
   nsIFrame* frame = params->mFrame;
   if (frame) {
     // We're drawing into a child window.
     nsIDeviceContext* devCtx = frame->PresContext()->DeviceContext();
     nsCOMPtr<nsIRenderingContext> rc;
     nsresult rv = devCtx->CreateRenderingContextInstance(*getter_AddRefs(rc));
     if (NS_SUCCEEDED(rv)) {
       rc->Init(devCtx, aContext);
-      nsRegion dirtyRegion = *params->mDirtyRegion;
-      dirtyRegion.MoveBy(params->mOffsetToRoot);
       nsIRenderingContext::AutoPushTranslation
         push(rc, -params->mOffsetToWidget.x, -params->mOffsetToWidget.y);
-      nsLayoutUtils::PaintFrame(rc, frame, dirtyRegion,
+      nsLayoutUtils::PaintFrame(rc, frame, *params->mDirtyRegion,
                                 params->mBackgroundColor,
                                 nsLayoutUtils::PAINT_WIDGET_LAYERS);
     }
   } else {
     aContext->NewPath();
     aContext->SetColor(gfxRGBA(params->mBackgroundColor));
     nsIntRect dirtyRect = aRegionToDraw.GetBounds();
     aContext->Rectangle(
@@ -5886,48 +5883,67 @@ PresShell::Paint(nsIView*        aDispla
   NS_ASSERTION(aWidgetToPaint, "Can't paint without a widget");
 
   nscolor bgcolor = ComputeBackstopColor(aDisplayRoot);
 
   nsIFrame* frame = aPaintDefaultBackground
       ? nsnull : static_cast<nsIFrame*>(aDisplayRoot->GetClientData());
 
   if (frame && aViewToPaint == aDisplayRoot) {
+    // Defer invalidates that are triggered during painting, and discard
+    // invalidates of areas that are already being repainted.
+    // The layer system can trigger invalidates during painting
+    // (see FrameLayerBuilder).
+    frame->BeginDeferringInvalidatesForDisplayRoot(aDirtyRegion);
+
     // We can paint directly into the widget using its layer manager.
     // When we get rid of child widgets, this will be the only path we
     // need. (aPaintDefaultBackground will never be needed since the
     // chrome can always paint a default background.)
     nsLayoutUtils::PaintFrame(nsnull, frame, aDirtyRegion, bgcolor,
                               nsLayoutUtils::PAINT_WIDGET_LAYERS);
+
+    frame->EndDeferringInvalidatesForDisplayRoot();
     return NS_OK;
   }
 
+  nsPoint offsetToRoot = aViewToPaint->GetOffsetTo(aDisplayRoot);
+  nsRegion dirtyRegion = aDirtyRegion;
+  dirtyRegion.MoveBy(offsetToRoot);
+
+  if (frame) {
+    // Defer invalidates that are triggered during painting, and discard
+    // invalidates of areas that are already being repainted.
+    frame->BeginDeferringInvalidatesForDisplayRoot(dirtyRegion);
+  }
+
   LayerManager* layerManager = aWidgetToPaint->GetLayerManager();
   NS_ASSERTION(layerManager, "Must be in paint event");
 
   layerManager->BeginTransaction();
   nsRefPtr<ThebesLayer> root = layerManager->CreateThebesLayer();
   nsIntRect dirtyRect = aDirtyRegion.GetBounds().
     ToOutsidePixels(presContext->AppUnitsPerDevPixel());
   if (root) {
     root->SetVisibleRegion(dirtyRect);
     layerManager->SetRoot(root);
   }
   if (!frame) {
     bgcolor = NS_ComposeColors(bgcolor, mCanvasBackgroundColor);
   }
-  nsPoint offsetToRoot = aViewToPaint->GetOffsetTo(aDisplayRoot);
   PaintParams params =
     { frame,
-      offsetToRoot,
       offsetToRoot - aViewToPaint->ViewToWidgetOffset(),
-      &aDirtyRegion,
+      &dirtyRegion,
       bgcolor };
   layerManager->EndTransaction(DrawThebesLayer, &params);
 
+  if (frame) {
+    frame->EndDeferringInvalidatesForDisplayRoot();
+  }
   return NS_OK;
 }
 
 // static
 void
 nsIPresShell::SetCapturingContent(nsIContent* aContent, PRUint8 aFlags)
 {
   NS_IF_RELEASE(gCaptureInfo.mContent);
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -3851,29 +3851,63 @@ nsIFrame::InvalidateRectDifference(const
 }
 
 void
 nsIFrame::InvalidateOverflowRect()
 {
   Invalidate(GetOverflowRectRelativeToSelf());
 }
 
+NS_DECLARE_FRAME_PROPERTY(DeferInvalidatesProperty, nsIFrame::DestroyRegion)
+
 void
 nsIFrame::InvalidateRoot(const nsRect& aDamageRect, PRUint32 aFlags)
 {
+  nsRect rect;
+  rect.IntersectRect(aDamageRect, nsRect(nsPoint(0,0), GetSize()));
+
   if ((mState & NS_FRAME_HAS_CONTAINER_LAYER) &&
       !(aFlags & INVALIDATE_NO_THEBES_LAYERS)) {
-    FrameLayerBuilder::InvalidateThebesLayerContents(this, aDamageRect);
+    FrameLayerBuilder::InvalidateThebesLayerContents(this, rect);
   }
 
   PRUint32 flags =
     (aFlags & INVALIDATE_IMMEDIATE) ? NS_VMREFRESH_IMMEDIATE : NS_VMREFRESH_NO_SYNC;
+
+  nsRegion* excludeRegion = static_cast<nsRegion*>
+    (Properties().Get(DeferInvalidatesProperty()));
+  if (excludeRegion) {
+    flags = NS_VMREFRESH_DEFERRED;
+
+    nsRegion r;
+    r.Sub(rect, *excludeRegion);
+    if (r.IsEmpty())
+      return;
+    rect = r.GetBounds();
+  }
+
   nsIView* view = GetView();
   NS_ASSERTION(view, "This can only be called on frames with views");
-  view->GetViewManager()->UpdateView(view, aDamageRect, flags);
+  view->GetViewManager()->UpdateView(view, rect, flags);
+}
+
+void
+nsIFrame::BeginDeferringInvalidatesForDisplayRoot(const nsRegion& aExcludeRegion)
+{
+  NS_ASSERTION(nsLayoutUtils::GetDisplayRootFrame(this) == this,
+               "Can only call this on display roots");
+  Properties().Set(DeferInvalidatesProperty(), new nsRegion(aExcludeRegion));
+}
+
+void
+nsIFrame::EndDeferringInvalidatesForDisplayRoot()
+{
+  NS_ASSERTION(nsLayoutUtils::GetDisplayRootFrame(this) == this,
+               "Can only call this on display roots");
+  Properties().Delete(DeferInvalidatesProperty());
 }
 
 /**
  * @param aAnyOutlineOrEffects set to true if this frame has any
  * outline, SVG effects or box shadows that mean we need to invalidate
  * the whole overflow area if the frame's size changes.
  */
 static nsRect
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -800,16 +800,21 @@ public:
   virtual nsPoint GetPositionOfChildIgnoringScrolling(nsIFrame* aChild)
   { return aChild->GetPosition(); }
   
   nsPoint GetPositionIgnoringScrolling() {
     return mParent ? mParent->GetPositionOfChildIgnoringScrolling(this)
       : GetPosition();
   }
 
+  static void DestroyRegion(void* aPropertyValue)
+  {
+    delete static_cast<nsRegion*>(aPropertyValue);
+  }
+
   static void DestroyMargin(void* aPropertyValue)
   {
     delete static_cast<nsMargin*>(aPropertyValue);
   }
 
   static void DestroyRect(void* aPropertyValue)
   {
     delete static_cast<nsRect*>(aPropertyValue);
@@ -1791,16 +1796,33 @@ public:
    * have kids.  It could still have kids created via
    * nsIAnonymousContentCreator.  Returning true indicates that "normal"
    * (non-anonymous, XBL-bound, CSS generated content, etc) children should not
    * be constructed.
    */
   virtual PRBool IsLeaf() const;
 
   /**
+   * This must only be called on frames that are display roots (see
+   * nsLayoutUtils::GetDisplayRootFrame). This causes all invalidates
+   * reaching this frame to be performed asynchronously off an event,
+   * instead of being applied to the widget immediately. Also,
+   * invalidation of areas in aExcludeRegion is ignored completely.
+   * These can't be nested; two invocations of
+   * BeginDeferringInvalidatesForDisplayRoot for a frame must have a
+   * EndDeferringInvalidatesForDisplayRoot between them.
+   */
+  void BeginDeferringInvalidatesForDisplayRoot(const nsRegion& aExcludeRegion);
+
+  /**
+   * Cancel the most recent BeginDeferringInvalidatesForDisplayRoot.
+   */
+  void EndDeferringInvalidatesForDisplayRoot();
+
+  /**
    * @param aFlags see InvalidateInternal below
    */
   void InvalidateWithFlags(const nsRect& aDamageRect, PRUint32 aFlags);
 
   /**
    * Invalidate part of the frame by asking the view manager to repaint.
    * aDamageRect is allowed to extend outside the frame's bounds. We'll do the right
    * thing.