Bug 602757. Part 4: Let nsDisplayBackground::GetOpaqueRegion return opaque regions for arbitrary repeat modes, background-clip, border-radius, etc. r=tnikkel,sr=dbaron,a=blocking
authorRobert O'Callahan <robert@ocallahan.org>
Mon, 03 Jan 2011 14:48:09 +1300
changeset 59780 475fe8dd48a37963ce662e326a19fc815940c2f7
parent 59779 d6696d3206f83611614412489b08b34a069184e1
child 59781 b804550e79d746172c578ea169f752f36f75abab
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
reviewerstnikkel, dbaron, blocking
bugs602757
milestone2.0b9pre
Bug 602757. Part 4: Let nsDisplayBackground::GetOpaqueRegion return opaque regions for arbitrary repeat modes, background-clip, border-radius, etc. r=tnikkel,sr=dbaron,a=blocking
layout/base/nsCSSRendering.cpp
layout/base/nsCSSRendering.h
layout/base/nsDisplayList.cpp
layout/base/nsDisplayList.h
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -2129,16 +2129,61 @@ nsCSSRendering::PaintGradient(nsPresCont
       ctx->SetPattern(gradientPattern);
       ctx->Rectangle(fillRect - tileRect.pos, PR_TRUE);
       ctx->Fill();
       ctx->SetMatrix(ctm);
     }
   }
 }
 
+/**
+ * A struct representing all the information needed to paint a background
+ * image to some target, taking into account all CSS background-* properties.
+ * See PrepareBackgroundLayer.
+ */
+struct BackgroundLayerState {
+  /**
+   * @param aFlags some combination of nsCSSRendering::PAINTBG_* flags
+   */
+  BackgroundLayerState(nsIFrame* aForFrame, const nsStyleImage* aImage, PRUint32 aFlags)
+    : mImageRenderer(aForFrame, aImage, aFlags) {}
+
+  /**
+   * The ImageRenderer that will be used to draw the background.
+   */
+  ImageRenderer mImageRenderer;
+  /**
+   * A rectangle that one copy of the image tile is mapped onto. Same
+   * coordinate system as aBorderArea/aBGClipRect passed into
+   * PrepareBackgroundLayer.
+   */
+  nsRect mDestArea;
+  /**
+   * The actual rectangle that should be filled with (complete or partial)
+   * image tiles. Same coordinate system as aBorderArea/aBGClipRect passed into
+   * PrepareBackgroundLayer.
+   */
+  nsRect mFillArea;
+  /**
+   * The anchor point that should be snapped to a pixel corner. Same
+   * coordinate system as aBorderArea/aBGClipRect passed into
+   * PrepareBackgroundLayer.
+   */
+  nsPoint mAnchor;
+};
+
+static BackgroundLayerState
+PrepareBackgroundLayer(nsPresContext* aPresContext,
+                       nsIFrame* aForFrame,
+                       PRUint32 aFlags,
+                       const nsRect& aBorderArea,
+                       const nsRect& aBGClipRect,
+                       const nsStyleBackground& aBackground,
+                       const nsStyleBackground::Layer& aLayer);
+
 void
 nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext,
                                       nsIRenderingContext& aRenderingContext,
                                       nsIFrame* aForFrame,
                                       const nsRect& aDirtyRect,
                                       const nsRect& aBorderArea,
                                       nsStyleContext* aBackgroundSC,
                                       const nsStyleBorder& aBorder,
@@ -2299,19 +2344,23 @@ nsCSSRendering::PaintBackgroundWithSC(ns
           currentBackgroundClip = newBackgroundClip;
           SetupBackgroundClip(ctx, currentBackgroundClip, aForFrame,
                               aBorderArea, aDirtyRect, haveRoundedCorners,
                               bgRadii, appUnitsPerPixel, &autoSR,
                               &bgClipArea, &dirtyRect, &dirtyRectGfx);
         }
       }
       if (!dirtyRectGfx.IsEmpty()) {
-        PaintBackgroundLayer(aPresContext, aRenderingContext, aForFrame, aFlags,
-                             dirtyRect, aBorderArea, bgClipArea, *bg,
-                             layer);
+        BackgroundLayerState state = PrepareBackgroundLayer(aPresContext, aForFrame,
+            aFlags, aBorderArea, bgClipArea, *bg, layer);
+        if (!state.mFillArea.IsEmpty()) {
+          state.mImageRenderer.Draw(aPresContext, aRenderingContext,
+                                    state.mDestArea, state.mFillArea,
+                                    state.mAnchor + aBorderArea.TopLeft(), dirtyRect);
+        }
       }
     }
   }
 }
 
 static inline float
 ScaleDimension(const nsStyleBackground::Size::Dimension& aDimension,
                PRUint8 aType,
@@ -2328,26 +2377,24 @@ ScaleDimension(const nsStyleBackground::
       NS_ABORT_IF_FALSE(PR_FALSE, "bad aDimension.mType");
       return 1.0f;
     case nsStyleBackground::Size::eAuto:
       NS_ABORT_IF_FALSE(PR_FALSE, "aDimension.mType == eAuto isn't handled");
       return 1.0f;
   }
 }
 
-static void
-PaintBackgroundLayer(nsPresContext* aPresContext,
-                     nsIRenderingContext& aRenderingContext,
-                     nsIFrame* aForFrame,
-                     PRUint32 aFlags,
-                     const nsRect& aDirtyRect, // intersected with aBGClipRect
-                     const nsRect& aBorderArea,
-                     const nsRect& aBGClipRect,
-                     const nsStyleBackground& aBackground,
-                     const nsStyleBackground::Layer& aLayer)
+static BackgroundLayerState
+PrepareBackgroundLayer(nsPresContext* aPresContext,
+                       nsIFrame* aForFrame,
+                       PRUint32 aFlags,
+                       const nsRect& aBorderArea,
+                       const nsRect& aBGClipRect,
+                       const nsStyleBackground& aBackground,
+                       const nsStyleBackground::Layer& aLayer)
 {
   /*
    * The background properties we need to keep in mind when drawing background
    * layers are:
    *
    *   background-image
    *   background-repeat
    *   background-attachment
@@ -2395,22 +2442,24 @@ PaintBackgroundLayer(nsPresContext* aPre
    *   background-break, background-origin
    *   background-attachment (postfix for background-{origin,break} if 'fixed')
    *   background-size
    *   background-position
    *   background-repeat
    */
 
   PRUint32 irFlags = 0;
-  if (aFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES)
+  if (aFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) {
     irFlags |= ImageRenderer::FLAG_SYNC_DECODE_IMAGES;
-  ImageRenderer imageRenderer(aForFrame, &aLayer.mImage, irFlags);
-  if (!imageRenderer.PrepareImage()) {
+  }
+
+  BackgroundLayerState state(aForFrame, &aLayer.mImage, irFlags);
+  if (!state.mImageRenderer.PrepareImage()) {
     // There's no image or it's not ready to be painted.
-    return;
+    return state;
   }
 
   // Compute background origin area relative to aBorderArea now as we may need
   // it to compute the effective image size for a CSS gradient.
   nsRect bgPositioningArea(0, 0, 0, 0);
 
   nsIAtom* frameType = aForFrame->GetType();
   nsIFrame* geometryFrame = aForFrame;
@@ -2464,17 +2513,17 @@ PaintBackgroundLayer(nsPresContext* aPre
   // For background-attachment:fixed backgrounds, we'll limit the area
   // where the background can be drawn to the viewport.
   nsRect bgClipRect = aBGClipRect;
 
   // Compute the anchor point.
   //
   // relative to aBorderArea.TopLeft() (which is where the top-left
   // of aForFrame's border-box will be rendered)
-  nsPoint imageTopLeft, anchor;
+  nsPoint imageTopLeft;
   if (NS_STYLE_BG_ATTACHMENT_FIXED == aLayer.mAttachment) {
     aPresContext->SetHasFixedBackgroundFrame();
 
     // If it's a fixed background attachment, then the image is placed
     // relative to the viewport, which is the area of the root frame
     // in a screen context or the page content frame in a print context.
     nsIFrame* topFrame =
       aPresContext->PresShell()->FrameManager()->GetRootFrame();
@@ -2509,19 +2558,19 @@ PaintBackgroundLayer(nsPresContext* aPre
       // without affecting output since drawing is always clipped to the viewport
       // when we draw to the screen. (But it's not a pure optimization since it
       // can affect the values of pixels at the edge of the viewport ---
       // whether they're sampled from a putative "next tile" or not.)
       bgClipRect.IntersectRect(bgClipRect, bgPositioningArea + aBorderArea.TopLeft());
     }
   }
 
-  nsSize imageSize = imageRenderer.ComputeSize(bgPositioningArea.Size());
+  nsSize imageSize = state.mImageRenderer.ComputeSize(bgPositioningArea.Size());
   if (imageSize.width <= 0 || imageSize.height <= 0)
-    return;
+    return state;
 
   // Scale the image as specified for background-size and as required for
   // proper background positioning when background-position is defined with
   // percentages.
   float scaleX, scaleY;
   switch (aLayer.mSize.mWidthType) {
     case nsStyleBackground::Size::eContain:
     case nsStyleBackground::Size::eCover: {
@@ -2559,37 +2608,48 @@ PaintBackgroundLayer(nsPresContext* aPre
     }
   }
   imageSize.width = NSCoordSaturatingNonnegativeMultiply(imageSize.width, scaleX);
   imageSize.height = NSCoordSaturatingNonnegativeMultiply(imageSize.height, scaleY);
 
   // Compute the position of the background now that the background's size is
   // determined.
   ComputeBackgroundAnchorPoint(aLayer, bgPositioningArea.Size(), imageSize,
-                               &imageTopLeft, &anchor);
+                               &imageTopLeft, &state.mAnchor);
   imageTopLeft += bgPositioningArea.TopLeft();
-  anchor += bgPositioningArea.TopLeft();
-
-  nsRect destArea(imageTopLeft + aBorderArea.TopLeft(), imageSize);
-  nsRect fillArea = destArea;
+  state.mAnchor += bgPositioningArea.TopLeft();
+
+  state.mDestArea = nsRect(imageTopLeft + aBorderArea.TopLeft(), imageSize);
+  state.mFillArea = state.mDestArea;
   PRIntn repeat = aLayer.mRepeat;
   PR_STATIC_ASSERT(NS_STYLE_BG_REPEAT_XY ==
                    (NS_STYLE_BG_REPEAT_X | NS_STYLE_BG_REPEAT_Y));
   if (repeat & NS_STYLE_BG_REPEAT_X) {
-    fillArea.x = bgClipRect.x;
-    fillArea.width = bgClipRect.width;
+    state.mFillArea.x = bgClipRect.x;
+    state.mFillArea.width = bgClipRect.width;
   }
   if (repeat & NS_STYLE_BG_REPEAT_Y) {
-    fillArea.y = bgClipRect.y;
-    fillArea.height = bgClipRect.height;
+    state.mFillArea.y = bgClipRect.y;
+    state.mFillArea.height = bgClipRect.height;
   }
-  fillArea.IntersectRect(fillArea, bgClipRect);
-
-  imageRenderer.Draw(aPresContext, aRenderingContext, destArea, fillArea,
-                     anchor + aBorderArea.TopLeft(), aDirtyRect);
+  state.mFillArea.IntersectRect(state.mFillArea, bgClipRect);
+  return state;
+}
+
+nsRect
+nsCSSRendering::GetBackgroundLayerRect(nsPresContext* aPresContext,
+                                       nsIFrame* aForFrame,
+                                       const nsRect& aBorderArea,
+                                       const nsStyleBackground& aBackground,
+                                       const nsStyleBackground::Layer& aLayer)
+{
+  BackgroundLayerState state =
+      PrepareBackgroundLayer(aPresContext, aForFrame, 0, aBorderArea,
+                             aBorderArea, aBackground, aLayer);
+  return state.mFillArea;
 }
 
 static void
 DrawBorderImage(nsPresContext*       aPresContext,
                 nsIRenderingContext& aRenderingContext,
                 nsIFrame*            aForFrame,
                 const nsRect&        aBorderArea,
                 const nsStyleBorder& aStyleBorder,
--- a/layout/base/nsCSSRendering.h
+++ b/layout/base/nsCSSRendering.h
@@ -252,16 +252,27 @@ struct nsCSSRendering {
                                     const nsRect& aDirtyRect,
                                     const nsRect& aBorderArea,
                                     nsStyleContext *aStyleContext,
                                     const nsStyleBorder& aBorder,
                                     PRUint32 aFlags,
                                     nsRect* aBGClipRect = nsnull);
 
   /**
+   * Returns the rectangle covered by the given background layer image, taking
+   * into account background positioning, sizing, and repetition, but not
+   * clipping.
+   */
+  static nsRect GetBackgroundLayerRect(nsPresContext* aPresContext,
+                                       nsIFrame* aForFrame,
+                                       const nsRect& aBorderArea,
+                                       const nsStyleBackground& aBackground,
+                                       const nsStyleBackground::Layer& aLayer);
+
+  /**
    * Called by the presShell when painting is finished, so we can clear our
    * inline background data cache.
    */
   static void DidPaint();
 
   // Draw a border segment in the table collapsing border model without
   // beveling corners
   static void DrawTableBorderSegment(nsIRenderingContext& aContext,
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -868,16 +868,54 @@ nsDisplayBackground::ComputeVisibility(n
   // frame. We don't want this display item to show up and confuse
   // anything.
   nsStyleContext* bgSC;
   return mIsThemed ||
     nsCSSRendering::FindBackground(mFrame->PresContext(), mFrame, &bgSC);
 }
 
 nsRegion
+nsDisplayBackground::GetInsideClipRegion(PRUint8 aClip, const nsRect& aRect)
+{
+  nsRegion result;
+  if (aRect.IsEmpty())
+    return result;
+
+  nscoord radii[8];
+  nsRect clipRect;
+  PRBool haveRadii;
+  switch (aClip) {
+  case NS_STYLE_BG_CLIP_BORDER:
+    haveRadii = mFrame->GetBorderRadii(radii);
+    clipRect = nsRect(ToReferenceFrame(), mFrame->GetSize());
+    break;
+  case NS_STYLE_BG_CLIP_PADDING:
+    haveRadii = mFrame->GetPaddingBoxBorderRadii(radii);
+    clipRect = mFrame->GetPaddingRect() - mFrame->GetPosition() + ToReferenceFrame();
+    break;
+  case NS_STYLE_BG_CLIP_CONTENT:
+    haveRadii = mFrame->GetContentBoxBorderRadii(radii);
+    clipRect = mFrame->GetContentRect() - mFrame->GetPosition() + ToReferenceFrame();
+    break;
+  default:
+    NS_NOTREACHED("Unknown clip type");
+    return result;
+  }
+
+  if (haveRadii) {
+    result = nsLayoutUtils::RoundedRectIntersectRect(clipRect, radii, aRect);
+  } else {
+    nsRect r;
+    r.IntersectRect(clipRect, aRect);
+    result = r;
+  }
+  return result;
+}
+
+nsRegion
 nsDisplayBackground::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
                                      PRBool* aForceTransparentSurface) {
   nsRegion result;
   if (aForceTransparentSurface) {
     *aForceTransparentSurface = PR_FALSE;
   }
   // theme background overrides any other background
   if (mIsThemed) {
@@ -888,36 +926,45 @@ nsDisplayBackground::GetOpaqueRegion(nsD
     }
     if (mThemeTransparency == nsITheme::eOpaque) {
       result = GetBounds(aBuilder);
     }
     return result;
   }
 
   nsStyleContext* bgSC;
+  nsPresContext* presContext = mFrame->PresContext();
   if (!nsCSSRendering::FindBackground(mFrame->PresContext(), mFrame, &bgSC))
     return result;
   const nsStyleBackground* bg = bgSC->GetStyleBackground();
   const nsStyleBackground::Layer& bottomLayer = bg->BottomLayer();
 
-  // bottom layer's clip is used for the color
-  if (bottomLayer.mClip != NS_STYLE_BG_CLIP_BORDER ||
-      nsLayoutUtils::HasNonZeroCorner(mFrame->GetStyleBorder()->mBorderRadius))
-    return result;
-
+  nsRect borderBox = nsRect(ToReferenceFrame(), mFrame->GetSize());
   if (NS_GET_A(bg->mBackgroundColor) == 255 &&
       !nsCSSRendering::IsCanvasFrame(mFrame)) {
-    result = GetBounds(aBuilder);
-    return result;
+    result = GetInsideClipRegion(bottomLayer.mClip, borderBox);
   }
 
-  if (bottomLayer.mRepeat == NS_STYLE_BG_REPEAT_XY &&
-      bottomLayer.mImage.IsOpaque()) {
-    result = GetBounds(aBuilder);
+  // For policies other than EACH_BOX, don't try to optimize here, since
+  // this could easily lead to O(N^2) behavior inside InlineBackgroundData,
+  // which expects frames to be sent to it in content order, not reverse
+  // content order which we'll produce here.
+  // Of course, if there's only one frame in the flow, it doesn't matter.
+  if (bg->mBackgroundInlinePolicy == NS_STYLE_BG_INLINE_POLICY_EACH_BOX ||
+      (!mFrame->GetPrevContinuation() && !mFrame->GetNextContinuation())) {
+    NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT(i, bg) {
+      const nsStyleBackground::Layer& layer = bg->mLayers[i];
+      if (layer.mImage.IsOpaque()) {
+        nsRect r = nsCSSRendering::GetBackgroundLayerRect(presContext, mFrame,
+            borderBox, *bg, layer);
+        result.Or(result, GetInsideClipRegion(layer.mClip, r));
+      }
+    }
   }
+
   return result;
 }
 
 PRBool
 nsDisplayBackground::IsUniform(nsDisplayListBuilder* aBuilder, nscolor* aColor) {
   // theme background overrides any other background
   if (mIsThemed)
     return PR_FALSE;
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -1408,16 +1408,18 @@ public:
   virtual PRBool IsVaryingRelativeToMovingFrame(nsDisplayListBuilder* aBuilder,
                                                 nsIFrame* aFrame);
   virtual PRBool IsUniform(nsDisplayListBuilder* aBuilder, nscolor* aColor);
   virtual PRBool IsFixedAndCoveringViewport(nsDisplayListBuilder* aBuilder);
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder);
   virtual void Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx);
   NS_DISPLAY_DECL_NAME("Background", TYPE_BACKGROUND)
 protected:
+  nsRegion GetInsideClipRegion(PRUint8 aClip, const nsRect& aRect);
+
   /* Used to cache mFrame->IsThemed() since it isn't a cheap call */
   PRPackedBool mIsThemed;
   nsITheme::Transparency mThemeTransparency;
 };
 
 /**
  * The standard display item to paint the outer CSS box-shadows of a frame.
  */