Bug 572613. Avoid creating a SolidColor display item when possible by poking the color into the canvas background display item. r=tnikkel
authorRobert O'Callahan <robert@ocallahan.org>
Fri, 16 Jul 2010 09:07:53 +1200
changeset 47741 34d7e0561e5bde247abf8af510a1f85a7cf8a189
parent 47740 d98f8a21727e27081c58a5db8c98dd8024bf45fe
child 47742 177b05c97d8490d1b7aef31655335b7ff2a52000
push idunknown
push userunknown
push dateunknown
reviewerstnikkel
bugs572613
milestone2.0b2pre
Bug 572613. Avoid creating a SolidColor display item when possible by poking the color into the canvas background display item. r=tnikkel
layout/base/nsCSSRendering.cpp
layout/base/nsLayoutUtils.cpp
layout/base/nsPresShell.cpp
layout/generic/nsCanvasFrame.cpp
layout/generic/nsCanvasFrame.h
layout/generic/nsFrameFrame.cpp
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -2155,18 +2155,21 @@ nsCSSRendering::PaintBackgroundWithSC(ns
                                   displayData->mAppearance, aBorderArea,
                                   drawing);
       return;
     }
   }
 
   // For canvas frames (in the CSS sense) we draw the background color using
   // a solid color item that gets added in nsLayoutUtils::PaintFrame,
-  // PresShell::RenderDocument, or nsSubDocumentFrame::BuildDisplayList
-  // (bug 488242).
+  // or nsSubDocumentFrame::BuildDisplayList (bug 488242). (The solid
+  // color may be moved into nsDisplayCanvasBackground by
+  // nsPresShell::AddCanvasBackgroundColorItem, and painted by
+  // nsDisplayCanvasBackground directly.) Either way we don't need to
+  // paint the background color here.
   PRBool isCanvasFrame = IsCanvasFrame(aForFrame);
 
   // Determine whether we are drawing background images and/or
   // background colors.
   PRBool drawBackgroundImage;
   PRBool drawBackgroundColor;
 
   nscolor bgColor = DetermineBackgroundColorInternal(aPresContext,
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1270,17 +1270,19 @@ nsLayoutUtils::PaintFrame(nsIRenderingCo
            aFrame, bounds, NS_RGB(115, 115, 115)));
   } else if (frameType != nsGkAtoms::pageFrame) {
     // For printing, this function is first called on an nsPageFrame, which
     // creates a display list with a PageContent item. The PageContent item's
     // paint function calls this function on the nsPageFrame's child which is
     // an nsPageContentFrame. We only want to add the canvas background color
     // item once, for the nsPageContentFrame.
 
-    // Add the canvas background color.
+    // Add the canvas background color to the bottom of the list. This
+    // happens after we've built the list so that AddCanvasBackgroundColorItem
+    // can monkey with the contents if necessary.
     rv = presShell->AddCanvasBackgroundColorItem(
            builder, list, aFrame, canvasArea, aBackstop);
   }
 
   builder.LeavePresShell(aFrame, dirtyRect);
   NS_ENSURE_SUCCESS(rv, rv);
 
 #ifdef DEBUG
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -5708,16 +5708,34 @@ PresShell::RenderSelection(nsISelection*
       return nsnull;
     }
   }
 
   return PaintRangePaintInfo(&rangeItems, aSelection, nsnull, area, aPoint,
                              aScreenRect);
 }
 
+static PRBool
+AddCanvasBackgroundColor(const nsDisplayList& aList, nsIFrame* aCanvasFrame,
+                         nscolor aColor)
+{
+  for (nsDisplayItem* i = aList.GetBottom(); i; i = i->GetAbove()) {
+    if (i->GetUnderlyingFrame() == aCanvasFrame &&
+        i->GetType() == nsDisplayItem::TYPE_CANVAS_BACKGROUND) {
+      nsDisplayCanvasBackground* bg = static_cast<nsDisplayCanvasBackground*>(i);
+      bg->SetExtraBackgroundColor(aColor);
+      return PR_TRUE;
+    }
+    nsDisplayList* sublist = i->GetList();
+    if (sublist && AddCanvasBackgroundColor(*sublist, aCanvasFrame, aColor))
+      return PR_TRUE;
+  }
+  return PR_FALSE;
+}
+
 nsresult PresShell::AddCanvasBackgroundColorItem(nsDisplayListBuilder& aBuilder,
                                                  nsDisplayList&        aList,
                                                  nsIFrame*             aFrame,
                                                  const nsRect&         aBounds,
                                                  nscolor               aBackstopColor,
                                                  PRBool                aForceDraw)
 {
   // We don't want to add an item for the canvas background color if the frame
@@ -5725,16 +5743,33 @@ nsresult PresShell::AddCanvasBackgroundC
   // an easy way to check this directly, but if we check if the root of the
   // (sub)tree we are painting is a canvas frame that should cover us in all
   // cases (it will usually be a viewport frame when we have a canvas frame in
   // the (sub)tree).
   if (!aForceDraw && !nsCSSRendering::IsCanvasFrame(aFrame))
     return NS_OK;
 
   nscolor bgcolor = NS_ComposeColors(aBackstopColor, mCanvasBackgroundColor);
+
+  // To make layers work better, we want to avoid having a big non-scrolled 
+  // color background behind a scrolled transparent background. Instead,
+  // we'll try to move the color background into the scrolled content
+  // by making nsDisplayCanvasBackground paint it.
+  if (!aFrame->GetParent()) {
+    nsIScrollableFrame* sf =
+      aFrame->PresContext()->PresShell()->GetRootScrollFrameAsScrollable();
+    if (sf) {
+      nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame());
+      if (canvasFrame && canvasFrame->IsVisibleForPainting(&aBuilder)) {
+        if (AddCanvasBackgroundColor(aList, canvasFrame, bgcolor))
+          return NS_OK;
+      }
+    }
+  }
+
   return aList.AppendNewToBottom(
       new (&aBuilder) nsDisplaySolidColor(aFrame, aBounds, bgcolor));
 }
 
 void PresShell::UpdateCanvasBackground()
 {
   // If we have a frame tree and it has style information that
   // specifies the background color of the canvas, update our local
--- a/layout/generic/nsCanvasFrame.cpp
+++ b/layout/generic/nsCanvasFrame.cpp
@@ -244,48 +244,35 @@ nsRect nsCanvasFrame::CanvasArea() const
   nsIScrollableFrame *scrollableFrame = do_QueryFrame(GetParent());
   if (scrollableFrame) {
     nsRect portRect = scrollableFrame->GetScrollPortRect();
     result.UnionRect(result, nsRect(nsPoint(0, 0), portRect.Size()));
   }
   return result;
 }
 
-/*
- * Override nsDisplayBackground methods so that we pass aBGClipRect to
- * PaintBackground, covering the whole overflow area.
- */
-class nsDisplayCanvasBackground : public nsDisplayBackground {
-public:
-  nsDisplayCanvasBackground(nsIFrame *aFrame)
-    : nsDisplayBackground(aFrame)
-  {
+void
+nsDisplayCanvasBackground::Paint(nsDisplayListBuilder* aBuilder,
+                                 nsIRenderingContext* aCtx)
+{
+  nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
+  nsPoint offset = aBuilder->ToReferenceFrame(mFrame);
+  nsRect bgClipRect = frame->CanvasArea() + offset;
+
+  if (NS_GET_A(mExtraBackgroundColor) > 0) {
+    aCtx->SetColor(mExtraBackgroundColor);
+    aCtx->FillRect(bgClipRect);
   }
 
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder)
-  {
-    nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
-    return frame->CanvasArea() + aBuilder->ToReferenceFrame(mFrame);
-  }
-
-  virtual void Paint(nsDisplayListBuilder* aBuilder,
-                     nsIRenderingContext* aCtx)
-  {
-    nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
-    nsPoint offset = aBuilder->ToReferenceFrame(mFrame);
-    nsRect bgClipRect = frame->CanvasArea() + offset;
-    nsCSSRendering::PaintBackground(mFrame->PresContext(), *aCtx, mFrame,
-                                    mVisibleRect,
-                                    nsRect(offset, mFrame->GetSize()),
-                                    aBuilder->GetBackgroundPaintFlags(),
-                                    &bgClipRect);
-  }
-
-  NS_DISPLAY_DECL_NAME("CanvasBackground", TYPE_CANVAS_BACKGROUND)
-};
+  nsCSSRendering::PaintBackground(mFrame->PresContext(), *aCtx, mFrame,
+                                  mVisibleRect,
+                                  nsRect(offset, mFrame->GetSize()),
+                                  aBuilder->GetBackgroundPaintFlags(),
+                                  &bgClipRect);
+}
 
 /**
  * A display item to paint the focus ring for the document.
  *
  * The only reason this can't use nsDisplayGeneric is overriding GetBounds.
  */
 class nsDisplayCanvasFocus : public nsDisplayItem {
 public:
--- a/layout/generic/nsCanvasFrame.h
+++ b/layout/generic/nsCanvasFrame.h
@@ -152,9 +152,60 @@ protected:
   virtual PRIntn GetSkipSides() const;
 
   // Data members
   PRPackedBool              mDoPaintFocus;
   PRPackedBool              mAddedScrollPositionListener;
   nsAbsoluteContainingBlock mAbsoluteContainer;
 };
 
+/**
+ * Override nsDisplayBackground methods so that we pass aBGClipRect to
+ * PaintBackground, covering the whole overflow area.
+ * We can also paint an "extra background color" behind the normal
+ * background.
+ */
+class nsDisplayCanvasBackground : public nsDisplayBackground {
+public:
+  nsDisplayCanvasBackground(nsIFrame *aFrame)
+    : nsDisplayBackground(aFrame)
+  {
+    mExtraBackgroundColor = NS_RGBA(0,0,0,0);
+  }
+
+  virtual PRBool IsOpaque(nsDisplayListBuilder* aBuilder)
+  {
+    return NS_GET_A(mExtraBackgroundColor) == 255 ||
+           nsDisplayBackground::IsOpaque(aBuilder);
+  }
+  virtual PRBool IsUniform(nsDisplayListBuilder* aBuilder, nscolor* aColor)
+  {
+    nscolor background;
+    if (!nsDisplayBackground::IsUniform(aBuilder, &background))
+      return PR_FALSE;
+    NS_ASSERTION(background == NS_RGBA(0,0,0,0),
+                 "The nsDisplayBackground for a canvas frame doesn't paint "
+                 "its background color normally");
+    *aColor = mExtraBackgroundColor;
+    return PR_TRUE;
+  }
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder)
+  {
+    nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
+    return frame->CanvasArea() + aBuilder->ToReferenceFrame(mFrame);
+  }
+
+  virtual void Paint(nsDisplayListBuilder* aBuilder,
+                     nsIRenderingContext* aCtx);
+
+  void SetExtraBackgroundColor(nscolor aColor)
+  {
+    mExtraBackgroundColor = aColor;
+  }
+
+  NS_DISPLAY_DECL_NAME("CanvasBackground", TYPE_CANVAS_BACKGROUND)
+
+private:
+  nscolor mExtraBackgroundColor;
+};
+
+
 #endif /* nsCanvasFrame_h___ */
--- a/layout/generic/nsFrameFrame.cpp
+++ b/layout/generic/nsFrameFrame.cpp
@@ -422,27 +422,29 @@ nsSubDocumentFrame::BuildDisplayList(nsD
     aBuilder->EnterPresShell(f, dirty);
   }
 
   // Get the bounds of subdocView relative to the reference frame.
   nsRect shellBounds = subdocView->GetBounds() +
                        mInnerView->GetPosition() +
                        GetOffsetTo(aBuilder->ReferenceFrame());
 
+  if (f && NS_SUCCEEDED(rv)) {
+    rv = f->BuildDisplayListForStackingContext(aBuilder, dirty, &childItems);
+  }
+
   if (!aBuilder->IsForEventDelivery()) {
-    // Add the canvas background color.
+    // Add the canvas background color to the bottom of the list. This
+    // happens after we've built the list so that AddCanvasBackgroundColorItem
+    // can monkey with the contents if necessary.
     rv = presShell->AddCanvasBackgroundColorItem(
            *aBuilder, childItems, f ? f : this, shellBounds, NS_RGBA(0,0,0,0),
            PR_TRUE);
   }
 
-  if (f && NS_SUCCEEDED(rv)) {
-    rv = f->BuildDisplayListForStackingContext(aBuilder, dirty, &childItems);
-  }
-
   if (NS_SUCCEEDED(rv)) {
     // Clip children to the child root frame's rectangle
     rv = aLists.Content()->AppendNewToTop(
         new (aBuilder) nsDisplayClip(this, this, &childItems, shellBounds));
   }
   // delete childItems in case of OOM
   childItems.DeleteAll();