Bug 615794. Part 3: Snap bounds of border and background display items to pixels if we're sure they will be snapped. r=tnikkel a=blocking
authorRobert O'Callahan <robert@ocallahan.org>
Tue, 04 Jan 2011 23:39:47 +1300
changeset 60027 1ca912846884b2c1d10344857284ca1233aed559
parent 60026 bf71941d1aeedd75180546c11b9cc838d822218c
child 60028 a61970a01de4fce2926c1c5b08e429bccf486458
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstnikkel, blocking
bugs615794
milestone2.0b9pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 615794. Part 3: Snap bounds of border and background display items to pixels if we're sure they will be snapped. r=tnikkel a=blocking
content/html/document/test/test_bug463104.html
dom/tests/mochitest/general/489127.html
layout/base/nsDisplayList.cpp
layout/base/nsDisplayList.h
layout/base/nsLayoutUtils.cpp
layout/base/nsPresShell.cpp
layout/generic/nsCanvasFrame.h
--- a/content/html/document/test/test_bug463104.html
+++ b/content/html/document/test/test_bug463104.html
@@ -8,18 +8,18 @@
 </head>
 <body>
 <div id="a" style="position: fixed; left: 5.5px; top: 5.5px; width: 100px; height: 100px; background: blue"></div>
 <p style="margin-top: 110px">
 <script>
 var a = document.getElementById("a");
 isnot(a, document.elementFromPoint(5, 5), "a shouldn't be found");
 isnot(a, document.elementFromPoint(5.25, 5.25), "a shouldn't be found");
-is(a, document.elementFromPoint(5.5, 5.5), "a should be found");
-is(a, document.elementFromPoint(5.75, 5.75), "a should be found");
+isnot(a, document.elementFromPoint(5.5, 5.5), "a shouldn't be found");
+isnot(a, document.elementFromPoint(5.75, 5.75), "a shouldn't be found");
 is(a, document.elementFromPoint(6, 6), "a should be found");
 is(a, document.elementFromPoint(105, 105), "a should be found");
 is(a, document.elementFromPoint(105.25, 105.25), "a should be found");
 isnot(a, document.elementFromPoint(105.5, 105.5), "a shouldn't be found");
 isnot(a, document.elementFromPoint(105.75, 105.75), "a shouldn't be found");
 isnot(a, document.elementFromPoint(106, 106), "a shouldn't be found");
 </script>
 </body>
--- a/dom/tests/mochitest/general/489127.html
+++ b/dom/tests/mochitest/general/489127.html
@@ -55,17 +55,17 @@
              "[" + top + "," + right + "," + bottom + "," + left + "]");
   }
 
   function doTest() {
 
     // Set up shortcut access to elements
     e['html'] = document.getElementsByTagName("html")[0];
     ['h1', 'd1', 'd2', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'span',
-     'a1', 'a2', 'a3', 'transf', 'iframe1', 'decimal', 'body'].forEach(function(a) {
+     'a1', 'a2', 'a3', 'transf', 'iframe1', 'body'].forEach(function(a) {
       e[a] = document.getElementById(a);
     });
 
     window.scrollTo(0, 0);
 
     // Top, Right, Bottom, Left directions:
     check(53, 71, 0,  0,  0,  0, []);
     check(53, 71, 10, 0,  0,  0, [e.h1]);
@@ -91,26 +91,16 @@
     // Expanding area checks:
     check(39, 212, 0,  0,  0,  0, []);
     check(39, 212, 10, 0,  0,  0, [e.d2]);
     check(39, 212, 0,  0,  10, 0, [e.p2]);
     check(39, 212, 10, 1,  30, 0, [e.d2, e.p2]);
     check(39, 212, 10, 5,  30, 0, [e.span, e.d2, e.p2]);
     check(39, 212, 10, 15, 30, 0, [e.p5, e.span, e.d2, e.p2]);
 
-    // Decimal CSS pixels
-    check(18,    330,   0, 0,    0,   0,   []);
-    check(18,    330,   0, 0.5,  0,   0,   []);
-    check(18,    330,   0, 0,    0.5, 0,   []);
-    check(18,    330,   0, 0.25, 0.5, 0,   []);
-    check(18,    330,   0, 0.5,  0.5, 0,   [e.decimal]);
-    check(144,   330.5, 0, 0,    0,   0,   [e.decimal]);
-    check(144.5, 330.5, 0, 0,    0,   0,   []);
-    check(144.5, 330.5, 0, 0,    0,   0.1, [e.decimal]);
-
     // Elements inside iframe shouldn't be returned:
     check(15, 410, 0, 30, 50, 0, [e.iframe1]);
 
     // Area with links and text nodes:
     let [x1, y1] = getCenterFor(e.a1);
     let [x2, y2] = getCenterFor(e.a2);
     let [x3, y3] = getCenterFor(e.a3);
     let [xt, yt] = [(x2 + x1) / 2, y1]; //text node between a1 and a2
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -79,17 +79,18 @@ nsDisplayListBuilder::nsDisplayListBuild
       mIgnoreSuppression(PR_FALSE),
       mHadToIgnoreSuppression(PR_FALSE),
       mIsAtRootOfPseudoStackingContext(PR_FALSE),
       mIncludeAllOutOfFlows(PR_FALSE),
       mSelectedFramesOnly(PR_FALSE),
       mAccurateVisibleRegions(PR_FALSE),
       mInTransform(PR_FALSE),
       mSyncDecodeImages(PR_FALSE),
-      mIsPaintingToWindow(PR_FALSE) {
+      mIsPaintingToWindow(PR_FALSE),
+      mSnappingEnabled(PR_TRUE) {
   MOZ_COUNT_CTOR(nsDisplayListBuilder);
   PL_InitArenaPool(&mPool, "displayListArena", 1024,
                    NS_MAX(NS_ALIGNMENT_OF(void*),NS_ALIGNMENT_OF(double))-1);
 
   nsPresContext* pc = aReferenceFrame->PresContext();
   nsIPresShell *shell = pc->PresShell();
   if (pc->IsRenderingOnlySelection()) {
     nsCOMPtr<nsISelectionController> selcon(do_QueryInterface(shell));
@@ -740,17 +741,18 @@ RegisterThemeWidgetGeometry(nsIFrame* aF
   nsRect borderBox(aFrame->GetOffsetTo(displayRoot), aFrame->GetSize());
   theme->RegisterWidgetGeometry(widget,
       aFrame->GetStyleDisplay()->mAppearance,
       borderBox.ToNearestPixels(presContext->AppUnitsPerDevPixel()));
 }
 
 nsDisplayBackground::nsDisplayBackground(nsDisplayListBuilder* aBuilder,
                                          nsIFrame* aFrame)
-  : nsDisplayItem(aBuilder, aFrame)
+  : nsDisplayItem(aBuilder, aFrame),
+    mSnappingEnabled(aBuilder->IsSnappingEnabled() && !aBuilder->IsInTransform())
 {
   MOZ_COUNT_CTOR(nsDisplayBackground);
   const nsStyleDisplay* disp = mFrame->GetStyleDisplay();
   mIsThemed = mFrame->IsThemed(disp, &mThemeTransparency);
 
   // Perform necessary RegisterWidgetGeometry
   if (mIsThemed &&
       (disp->mAppearance == NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR ||
@@ -881,18 +883,34 @@ nsDisplayBackground::ComputeVisibility(n
   // Return false if the background was propagated away from this
   // frame. We don't want this display item to show up and confuse
   // anything.
   nsStyleContext* bgSC;
   return mIsThemed ||
     nsCSSRendering::FindBackground(mFrame->PresContext(), mFrame, &bgSC);
 }
 
+// Note that even if the rectangle we draw and snap is smaller than aRect,
+// it's OK to call this to get a bounding rect for what we'll draw, because
+// snapping a rectangle which is contained in R always gives you a
+// rectangle which is contained in the snapped R.
+static nsRect
+SnapBounds(PRBool aSnappingEnabled, nsPresContext* aPresContext,
+           const nsRect& aRect) {
+  nsRect r = aRect;
+  if (aSnappingEnabled) {
+    nscoord appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
+    r = r.ToNearestPixels(appUnitsPerDevPixel).ToAppUnits(appUnitsPerDevPixel);
+  }
+  return r;
+}
+
 nsRegion
-nsDisplayBackground::GetInsideClipRegion(PRUint8 aClip, const nsRect& aRect)
+nsDisplayBackground::GetInsideClipRegion(nsPresContext* aPresContext,
+                                         PRUint8 aClip, const nsRect& aRect)
 {
   nsRegion result;
   if (aRect.IsEmpty())
     return result;
 
   nscoord radii[8];
   nsRect clipRect;
   PRBool haveRadii;
@@ -909,21 +927,24 @@ nsDisplayBackground::GetInsideClipRegion
     haveRadii = mFrame->GetContentBoxBorderRadii(radii);
     clipRect = mFrame->GetContentRect() - mFrame->GetPosition() + ToReferenceFrame();
     break;
   default:
     NS_NOTREACHED("Unknown clip type");
     return result;
   }
 
+  nsRect inputRect = SnapBounds(mSnappingEnabled, aPresContext, aRect);
+  clipRect = SnapBounds(mSnappingEnabled, aPresContext, clipRect);
+
   if (haveRadii) {
-    result = nsLayoutUtils::RoundedRectIntersectRect(clipRect, radii, aRect);
+    result = nsLayoutUtils::RoundedRectIntersectRect(clipRect, radii, inputRect);
   } else {
     nsRect r;
-    r.IntersectRect(clipRect, aRect);
+    r.IntersectRect(clipRect, inputRect);
     result = r;
   }
   return result;
 }
 
 nsRegion
 nsDisplayBackground::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
                                      PRBool* aForceTransparentSurface) {
@@ -949,32 +970,32 @@ nsDisplayBackground::GetOpaqueRegion(nsD
   if (!nsCSSRendering::FindBackground(mFrame->PresContext(), mFrame, &bgSC))
     return result;
   const nsStyleBackground* bg = bgSC->GetStyleBackground();
   const nsStyleBackground::Layer& bottomLayer = bg->BottomLayer();
 
   nsRect borderBox = nsRect(ToReferenceFrame(), mFrame->GetSize());
   if (NS_GET_A(bg->mBackgroundColor) == 255 &&
       !nsCSSRendering::IsCanvasFrame(mFrame)) {
-    result = GetInsideClipRegion(bottomLayer.mClip, borderBox);
+    result = GetInsideClipRegion(presContext, bottomLayer.mClip, borderBox);
   }
 
   // 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));
+        result.Or(result, GetInsideClipRegion(presContext, layer.mClip, r));
       }
     }
   }
 
   return result;
 }
 
 PRBool
@@ -1084,26 +1105,26 @@ nsDisplayBackground::Paint(nsDisplayList
   nsCSSRendering::PaintBackground(mFrame->PresContext(), *aCtx, mFrame,
                                   mVisibleRect,
                                   nsRect(offset, mFrame->GetSize()),
                                   flags);
 }
 
 nsRect
 nsDisplayBackground::GetBounds(nsDisplayListBuilder* aBuilder) {
+  nsRect r(nsPoint(0,0), mFrame->GetSize());
+  nsPresContext* presContext = mFrame->PresContext();
+
   if (mIsThemed) {
-    nsRect r(nsPoint(0,0), mFrame->GetSize());
-    nsPresContext* presContext = mFrame->PresContext();
     presContext->GetTheme()->
         GetWidgetOverflow(presContext->DeviceContext(), mFrame,
                           mFrame->GetStyleDisplay()->mAppearance, &r);
-    return r + ToReferenceFrame();
   }
 
-  return nsRect(ToReferenceFrame(), mFrame->GetSize());
+  return SnapBounds(mSnappingEnabled, presContext, r + ToReferenceFrame());
 }
 
 nsRect
 nsDisplayOutline::GetBounds(nsDisplayListBuilder* aBuilder) {
   return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
 }
 
 void
@@ -1189,16 +1210,23 @@ nsDisplayBorder::Paint(nsDisplayListBuil
   nsPoint offset = ToReferenceFrame();
   nsCSSRendering::PaintBorder(mFrame->PresContext(), *aCtx, mFrame,
                               mVisibleRect,
                               nsRect(offset, mFrame->GetSize()),
                               mFrame->GetStyleContext(),
                               mFrame->GetSkipSides());
 }
 
+nsRect
+nsDisplayBorder::GetBounds(nsDisplayListBuilder* aBuilder)
+{
+  return SnapBounds(mSnappingEnabled, mFrame->PresContext(),
+                    nsRect(ToReferenceFrame(), mFrame->GetSize()));
+}
+
 // Given a region, compute a conservative approximation to it as a list
 // of rectangles that aren't vertically adjacent (i.e., vertically
 // adjacent or overlapping rectangles are combined).
 // Right now this is only approximate, some vertically overlapping rectangles
 // aren't guaranteed to be combined.
 static void
 ComputeDisjointRectangles(const nsRegion& aRegion,
                           nsTArray<nsRect>* aRects) {
@@ -1562,27 +1590,29 @@ nsDisplayOwnLayer::BuildLayer(nsDisplayL
   nsRefPtr<Layer> layer = aBuilder->LayerBuilder()->
     BuildContainerLayerFor(aBuilder, aManager, mFrame, this, mList);
   return layer.forget();
 }
 
 nsDisplayClip::nsDisplayClip(nsDisplayListBuilder* aBuilder,
                              nsIFrame* aFrame, nsDisplayItem* aItem,
                              const nsRect& aRect)
-   : nsDisplayWrapList(aBuilder, aFrame, aItem),
-     mClip(aRect) {
+   : nsDisplayWrapList(aBuilder, aFrame, aItem) {
   MOZ_COUNT_CTOR(nsDisplayClip);
+  mClip = SnapBounds(aBuilder->IsSnappingEnabled() && !aBuilder->IsInTransform(),
+                     aBuilder->CurrentPresContext(), aRect);
 }
 
 nsDisplayClip::nsDisplayClip(nsDisplayListBuilder* aBuilder,
                              nsIFrame* aFrame, nsDisplayList* aList,
                              const nsRect& aRect)
-   : nsDisplayWrapList(aBuilder, aFrame, aList),
-     mClip(aRect) {
+   : nsDisplayWrapList(aBuilder, aFrame, aList) {
   MOZ_COUNT_CTOR(nsDisplayClip);
+  mClip = SnapBounds(aBuilder->IsSnappingEnabled() && !aBuilder->IsInTransform(),
+                     aBuilder->CurrentPresContext(), aRect);
 }
 
 nsRect nsDisplayClip::GetBounds(nsDisplayListBuilder* aBuilder) {
   nsRect r = nsDisplayWrapList::GetBounds(aBuilder);
   r.IntersectRect(mClip, r);
   return r;
 }
 
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -287,16 +287,26 @@ public:
   PRBool IsInTransform() { return mInTransform; }
   /**
    * Indicate whether or not we're directly or indirectly under and
    * nsDisplayTransform or SVG foreignObject.
    */
   void SetInTransform(PRBool aInTransform) { mInTransform = aInTransform; }
 
   /**
+   * Returns true if snapping is enabled for the final drawing context.
+   * The default is true.
+   */
+  PRBool IsSnappingEnabled() { return mSnappingEnabled; }
+  /**
+   * Set if snapping is enabled for the final drawing context.
+   */
+  void SetSnappingEnabled(PRBool aSnappingEnabled) { mSnappingEnabled = aSnappingEnabled; }
+
+  /**
    * @return PR_TRUE if images have been set to decode synchronously.
    */
   PRBool ShouldSyncDecodeImages() { return mSyncDecodeImages; }
 
   /**
    * Indicates whether we should synchronously decode images. If true, we decode
    * and draw whatever image data has been loaded. If false, we just draw
    * whatever has already been decoded.
@@ -409,16 +419,20 @@ public:
   };  
   
   // Helpers for tables
   nsDisplayTableItem* GetCurrentTableItem() { return mCurrentTableItem; }
   void SetCurrentTableItem(nsDisplayTableItem* aTableItem) { mCurrentTableItem = aTableItem; }
 
   NS_DECLARE_FRAME_PROPERTY(OutOfFlowDirtyRectProperty, nsIFrame::DestroyRect)
 
+  nsPresContext* CurrentPresContext() {
+    return CurrentPresShellState()->mPresShell->GetPresContext();
+  }
+
 private:
   struct PresShellState {
     nsIPresShell* mPresShell;
     nsIFrame*     mCaretFrame;
     PRUint32      mFirstFrameMarkedForDisplay;
     PRPackedBool  mIsBackgroundOnly;
   };
   PresShellState* CurrentPresShellState() {
@@ -444,16 +458,17 @@ private:
   PRPackedBool                   mIncludeAllOutOfFlows;
   PRPackedBool                   mSelectedFramesOnly;
   PRPackedBool                   mAccurateVisibleRegions;
   // True when we're building a display list that's directly or indirectly
   // under an nsDisplayTransform
   PRPackedBool                   mInTransform;
   PRPackedBool                   mSyncDecodeImages;
   PRPackedBool                   mIsPaintingToWindow;
+  PRPackedBool                   mSnappingEnabled;
 };
 
 class nsDisplayItem;
 class nsDisplayList;
 /**
  * nsDisplayItems are put in singly-linked lists rooted in an nsDisplayList.
  * nsDisplayItemLink holds the link. The lists are linked from lowest to
  * highest in z-order.
@@ -1314,29 +1329,34 @@ protected:
 };
 
 /**
  * The standard display item to paint the CSS borders of a frame.
  */
 class nsDisplayBorder : public nsDisplayItem {
 public:
   nsDisplayBorder(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) :
-    nsDisplayItem(aBuilder, aFrame) {
+    nsDisplayItem(aBuilder, aFrame),
+    mSnappingEnabled(aBuilder->IsSnappingEnabled() && !aBuilder->IsInTransform()) {
     MOZ_COUNT_CTOR(nsDisplayBorder);
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayBorder() {
     MOZ_COUNT_DTOR(nsDisplayBorder);
   }
 #endif
 
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder);
   virtual void Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx);
   virtual PRBool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                    nsRegion* aVisibleRegion);
   NS_DISPLAY_DECL_NAME("Border", TYPE_BORDER)
+
+protected:
+  PRPackedBool mSnappingEnabled;
 };
 
 /**
  * A simple display item that just renders a solid color across the
  * specified bounds. For canvas frames (in the CSS sense) we split off the
  * drawing of the background color into this class (from nsDisplayBackground
  * via nsDisplayCanvasBackground). This is done so that we can always draw a
  * background color to avoid ugly flashes of white when we can't draw a full
@@ -1409,20 +1429,22 @@ 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);
+  nsRegion GetInsideClipRegion(nsPresContext* aPresContext, PRUint8 aClip,
+                               const nsRect& aRect);
 
   /* Used to cache mFrame->IsThemed() since it isn't a cheap call */
   PRPackedBool mIsThemed;
+  PRPackedBool mSnappingEnabled;
   nsITheme::Transparency mThemeTransparency;
 };
 
 /**
  * The standard display item to paint the outer CSS box-shadows of a frame.
  */
 class nsDisplayBoxShadowOuter : public nsDisplayItem {
 public:
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1297,16 +1297,20 @@ nsLayoutUtils::PaintFrame(nsIRenderingCo
     builder.SetSyncDecodeImages(PR_TRUE);
   }
   if (aFlags & PAINT_WIDGET_LAYERS || aFlags & PAINT_TO_WINDOW) {
     builder.SetPaintingToWindow(PR_TRUE);
   }
   if (aFlags & PAINT_IGNORE_SUPPRESSION) {
     builder.IgnorePaintSuppression();
   }
+  if (aRenderingContext &&
+      aRenderingContext->ThebesContext()->GetFlags() & gfxContext::FLAG_DISABLE_SNAPPING) {
+    builder.SetSnappingEnabled(PR_FALSE);
+  }
   nsRect canvasArea(nsPoint(0, 0), aFrame->GetSize());
   if (ignoreViewportScrolling) {
     NS_ASSERTION(!aFrame->GetParent(), "must have root frame");
     nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
     if (rootScrollFrame) {
       nsIScrollableFrame* rootScrollableFrame =
         presShell->GetRootScrollFrameAsScrollable();
       if (aFlags & PAINT_DOCUMENT_RELATIVE) {
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -5349,16 +5349,19 @@ PresShell::RenderDocument(const nsRect& 
   AutoSaveRestoreRenderingState _(this);
 
   nsCOMPtr<nsIRenderingContext> rc;
   devCtx->CreateRenderingContextInstance(*getter_AddRefs(rc));
   rc->Init(devCtx, aThebesContext);
 
   PRBool wouldFlushRetainedLayers = PR_FALSE;
   PRUint32 flags = nsLayoutUtils::PAINT_IGNORE_SUPPRESSION;
+  if (aThebesContext->CurrentMatrix().HasNonIntegerTranslation()) {
+    flags |= nsLayoutUtils::PAINT_IN_TRANSFORM;
+  }
   if (!(aFlags & RENDER_ASYNC_DECODE_IMAGES)) {
     flags |= nsLayoutUtils::PAINT_SYNC_DECODE_IMAGES;
   }
   if (aFlags & RENDER_USE_WIDGET_LAYERS) {
     // We only support using widget layers on display root's with widgets.
     nsIView* view = rootFrame->GetView();
     if (view && view->GetWidget() &&
         nsLayoutUtils::GetDisplayRootFrame(rootFrame) == rootFrame) {
--- a/layout/generic/nsCanvasFrame.h
+++ b/layout/generic/nsCanvasFrame.h
@@ -196,17 +196,22 @@ public:
                  "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() + ToReferenceFrame();
+    nsRect r = frame->CanvasArea() + ToReferenceFrame();
+    if (mSnappingEnabled) {
+      nscoord appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
+      r = r.ToNearestPixels(appUnitsPerDevPixel).ToAppUnits(appUnitsPerDevPixel);
+    }
+    return r;
   }
   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                        HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
   {
     // We need to override so we don't consider border-radius.
     aOutFrames->AppendElement(mFrame);
   }