Merge backout, a=bustage-fix
authorEhsan Akhgari <ehsan@mozilla.com>
Tue, 07 Sep 2010 19:23:47 -0400
changeset 52146 75d2145d1bd3
parent 52145 daa97e0bc5f9 (current diff)
parent 52144 1633b8d8a184 (diff)
child 52147 459a07b6bdee
push id15554
push usereakhgari@mozilla.com
push date2010-09-07 23:24 +0000
treeherdermozilla-central@75d2145d1bd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbustage-fix
milestone2.0b6pre
Merge backout, a=bustage-fix
--- a/browser/themes/pinstripe/browser/browser.css
+++ b/browser/themes/pinstripe/browser/browser.css
@@ -1037,17 +1037,17 @@ richlistitem[type~="action"][actiontype=
 .editBookmarkPanelBottomButton:hover:active {
   background-color: #86888B;
 }
 
 .editBookmarkPanelHeaderButton:focus,
 .editBookmarkPanelBottomButton:focus {
   outline: 2px solid -moz-mac-focusring;
   outline-offset: -2px;
-  -moz-outline-radius: 100%;
+  -moz-outline-radius: 10000px;
 }
 
 .editBookmarkPanelBottomButton[default="true"] {
   background-color: #666;
 }
 
 #editBookmarkPanelHeader {
   margin-bottom: 6px;
--- a/extensions/cookie/test/unit/test_domain_eviction.js
+++ b/extensions/cookie/test/unit/test_domain_eviction.js
@@ -1,14 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 function run_test()
 {
+  // Set the base domain limit to 50 so we have a known value.
+  Services.prefs.setIntPref("network.cookie.maxPerHost", 50);
+
   var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
 
   cm.removeAll();
 
   // test eviction under the 50 cookies per base domain limit. this means
   // that cookies for foo.com and bar.foo.com should count toward this limit,
   // while cookies for baz.com should not. there are several tests we perform
   // to make sure the base domain logic is working correctly.
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -452,95 +452,28 @@ MakeBevelColor(mozilla::css::Side whichS
     break;
   }
   return theColor;
 }
 
 //----------------------------------------------------------------------
 // Thebes Border Rendering Code Start
 
-// helper function to convert a nsRect to a gfxRect
-static gfxRect
-RectToGfxRect(const nsRect& rect, nscoord twipsPerPixel)
-{
-  return gfxRect(gfxFloat(rect.x) / twipsPerPixel,
-                 gfxFloat(rect.y) / twipsPerPixel,
-                 gfxFloat(rect.width) / twipsPerPixel,
-                 gfxFloat(rect.height) / twipsPerPixel);
-}
-
 /*
  * Compute the float-pixel radii that should be used for drawing
  * this border/outline, given the various input bits.
- *
- * If a side is skipped via skipSides, its corners are forced to 0.
- * All corner radii are then adjusted so they do not require more
- * space than outerRect, according to the algorithm in css3-background.
  */
-static void
-ComputePixelRadii(const nscoord *aTwipsRadii,
-                  const nsRect& outerRect,
-                  PRIntn skipSides,
-                  nscoord twipsPerPixel,
-                  gfxCornerSizes *oBorderRadii)
+/* static */ void
+nsCSSRendering::ComputePixelRadii(const nscoord *aAppUnitsRadii,
+                                  nscoord aAppUnitsPerPixel,
+                                  gfxCornerSizes *oBorderRadii)
 {
-  nscoord twipsRadii[8];
-  memcpy(twipsRadii, aTwipsRadii, sizeof twipsRadii);
-
-  if (skipSides & SIDE_BIT_TOP) {
-    twipsRadii[NS_CORNER_TOP_LEFT_X] = 0;
-    twipsRadii[NS_CORNER_TOP_LEFT_Y] = 0;
-    twipsRadii[NS_CORNER_TOP_RIGHT_X] = 0;
-    twipsRadii[NS_CORNER_TOP_RIGHT_Y] = 0;
-  }
-
-  if (skipSides & SIDE_BIT_RIGHT) {
-    twipsRadii[NS_CORNER_TOP_RIGHT_X] = 0;
-    twipsRadii[NS_CORNER_TOP_RIGHT_Y] = 0;
-    twipsRadii[NS_CORNER_BOTTOM_RIGHT_X] = 0;
-    twipsRadii[NS_CORNER_BOTTOM_RIGHT_Y] = 0;
-  }
-
-  if (skipSides & SIDE_BIT_BOTTOM) {
-    twipsRadii[NS_CORNER_BOTTOM_RIGHT_X] = 0;
-    twipsRadii[NS_CORNER_BOTTOM_RIGHT_Y] = 0;
-    twipsRadii[NS_CORNER_BOTTOM_LEFT_X] = 0;
-    twipsRadii[NS_CORNER_BOTTOM_LEFT_Y] = 0;
-  }
-
-  if (skipSides & SIDE_BIT_LEFT) {
-    twipsRadii[NS_CORNER_BOTTOM_LEFT_X] = 0;
-    twipsRadii[NS_CORNER_BOTTOM_LEFT_Y] = 0;
-    twipsRadii[NS_CORNER_TOP_LEFT_X] = 0;
-    twipsRadii[NS_CORNER_TOP_LEFT_Y] = 0;
-  }
-
   gfxFloat radii[8];
   NS_FOR_CSS_HALF_CORNERS(corner)
-    radii[corner] = twipsRadii[corner] / twipsPerPixel;
-
-  // css3-background specifies this algorithm for reducing
-  // corner radii when they are too big.
-  gfxFloat maxWidth = outerRect.width / twipsPerPixel;
-  gfxFloat maxHeight = outerRect.height / twipsPerPixel;
-  gfxFloat f = 1.0f;
-  NS_FOR_CSS_SIDES(side) {
-    PRUint32 hc1 = NS_SIDE_TO_HALF_CORNER(side, PR_FALSE, PR_TRUE);
-    PRUint32 hc2 = NS_SIDE_TO_HALF_CORNER(side, PR_TRUE, PR_TRUE);
-    gfxFloat length = NS_SIDE_IS_VERTICAL(side) ? maxHeight : maxWidth;
-    gfxFloat sum = radii[hc1] + radii[hc2];
-    // avoid floating point division in the normal case
-    if (length < sum)
-      f = NS_MIN(f, length/sum);
-  }
-  if (f < 1.0) {
-    NS_FOR_CSS_HALF_CORNERS(corner) {
-      radii[corner] *= f;
-    }
-  }
+    radii[corner] = gfxFloat(aAppUnitsRadii[corner]) / aAppUnitsPerPixel;
 
   (*oBorderRadii)[C_TL] = gfxSize(radii[NS_CORNER_TOP_LEFT_X],
                                   radii[NS_CORNER_TOP_LEFT_Y]);
   (*oBorderRadii)[C_TR] = gfxSize(radii[NS_CORNER_TOP_RIGHT_X],
                                   radii[NS_CORNER_TOP_RIGHT_Y]);
   (*oBorderRadii)[C_BR] = gfxSize(radii[NS_CORNER_BOTTOM_RIGHT_X],
                                   radii[NS_CORNER_BOTTOM_RIGHT_Y]);
   (*oBorderRadii)[C_BL] = gfxSize(radii[NS_CORNER_BOTTOM_LEFT_X],
@@ -624,18 +557,23 @@ nsCSSRendering::PaintBorderWithStyleBord
   border = aStyleBorder.GetComputedBorder();
   if ((0 == border.left) && (0 == border.right) &&
       (0 == border.top) && (0 == border.bottom)) {
     // Empty border area
     return;
   }
 
   nsSize frameSize = aForFrame->GetSize();
-  GetBorderRadiusTwips(aStyleBorder.mBorderRadius, frameSize.width,
-                       frameSize.height, twipsRadii);
+  if (&aStyleBorder == aForFrame->GetStyleBorder() &&
+      frameSize == aBorderArea.Size()) {
+    aForFrame->GetBorderRadii(twipsRadii);
+  } else {
+    nsIFrame::ComputeBorderRadii(aStyleBorder.mBorderRadius, frameSize,
+                                 aBorderArea.Size(), aSkipSides, twipsRadii);
+  }
 
   // Turn off rendering for all of the zero sized sides
   if (aSkipSides & SIDE_BIT_TOP) border.top = 0;
   if (aSkipSides & SIDE_BIT_RIGHT) border.right = 0;
   if (aSkipSides & SIDE_BIT_BOTTOM) border.bottom = 0;
   if (aSkipSides & SIDE_BIT_LEFT) border.left = 0;
 
   // get the inside and outside parts of the border
@@ -644,28 +582,27 @@ nsCSSRendering::PaintBorderWithStyleBord
   SF(" outerRect: %d %d %d %d\n", outerRect.x, outerRect.y, outerRect.width, outerRect.height);
 
   // we can assume that we're already clipped to aDirtyRect -- I think? (!?)
 
   // Get our conversion values
   nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
 
   // convert outer and inner rects
-  gfxRect oRect(RectToGfxRect(outerRect, twipsPerPixel));
+  gfxRect oRect(nsLayoutUtils::RectToGfxRect(outerRect, twipsPerPixel));
 
   // convert the border widths
   gfxFloat borderWidths[4] = { gfxFloat(border.top / twipsPerPixel),
                                gfxFloat(border.right / twipsPerPixel),
                                gfxFloat(border.bottom / twipsPerPixel),
                                gfxFloat(border.left / twipsPerPixel) };
 
   // convert the radii
   gfxCornerSizes borderRadii;
-  ComputePixelRadii(twipsRadii, outerRect, aSkipSides, twipsPerPixel,
-                    &borderRadii);
+  ComputePixelRadii(twipsRadii, twipsPerPixel, &borderRadii);
 
   PRUint8 borderStyles[4];
   nscolor borderColors[4];
   nsBorderColors *compositeColors[4];
 
   // pull out styles, colors, composite colors
   NS_FOR_CSS_SIDES (i) {
     PRBool foreground;
@@ -744,20 +681,16 @@ nsCSSRendering::PaintOutline(nsPresConte
   }
 
   nsIFrame* bgFrame = nsCSSRendering::FindNonTransparentBackgroundFrame
     (aForFrame, PR_FALSE);
   nsStyleContext* bgContext = bgFrame->GetStyleContext();
   nscolor bgColor =
     bgContext->GetVisitedDependentColor(eCSSProperty_background_color);
 
-  // get the radius for our outline
-  GetBorderRadiusTwips(ourOutline->mOutlineRadius, aBorderArea.width,
-                       aBorderArea.height, twipsRadii);
-
   // When the outline property is set on :-moz-anonymous-block or
   // :-moz-anonyomus-positioned-block pseudo-elements, it inherited that
   // outline from the inline that was broken because it contained a
   // block.  In that case, we don't want a really wide outline if the
   // block inside the inline is narrow, so union the actual contents of
   // the anonymous blocks.
   nsIFrame *frameForArea = aForFrame;
   do {
@@ -793,27 +726,30 @@ nsCSSRendering::PaintOutline(nsPresConte
   // encroach into the content area.  A safer calculation would be to
   // shorten insideRect by the radius one each side before performing this test.
   if (innerRect.Contains(aDirtyRect))
     return;
 
   nsRect outerRect = innerRect;
   outerRect.Inflate(width, width);
 
+  // get the radius for our outline
+  nsIFrame::ComputeBorderRadii(ourOutline->mOutlineRadius, aBorderArea.Size(),
+                               outerRect.Size(), 0, twipsRadii);
+
   // Get our conversion values
   nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
 
   // get the outer rectangles
-  gfxRect oRect(RectToGfxRect(outerRect, twipsPerPixel));
+  gfxRect oRect(nsLayoutUtils::RectToGfxRect(outerRect, twipsPerPixel));
 
   // convert the radii
   nsMargin outlineMargin(width, width, width, width);
   gfxCornerSizes outlineRadii;
-  ComputePixelRadii(twipsRadii, outerRect, 0, twipsPerPixel,
-                    &outlineRadii);
+  ComputePixelRadii(twipsRadii, twipsPerPixel, &outlineRadii);
 
   PRUint8 outlineStyle = ourOutline->GetOutlineStyle();
   PRUint8 outlineStyles[4] = { outlineStyle,
                                outlineStyle,
                                outlineStyle,
                                outlineStyle };
 
   // This handles treating the initial color as 'currentColor'; if we
@@ -856,22 +792,22 @@ void
 nsCSSRendering::PaintFocus(nsPresContext* aPresContext,
                            nsIRenderingContext& aRenderingContext,
                            const nsRect& aFocusRect,
                            nscolor aColor)
 {
   nscoord oneCSSPixel = nsPresContext::CSSPixelsToAppUnits(1);
   nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
 
-  gfxRect focusRect(RectToGfxRect(aFocusRect, oneDevPixel));
+  gfxRect focusRect(nsLayoutUtils::RectToGfxRect(aFocusRect, oneDevPixel));
 
   gfxCornerSizes focusRadii;
   {
     nscoord twipsRadii[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
-    ComputePixelRadii(twipsRadii, aFocusRect, 0, oneDevPixel, &focusRadii);
+    ComputePixelRadii(twipsRadii, oneDevPixel, &focusRadii);
   }
   gfxFloat focusWidths[4] = { gfxFloat(oneCSSPixel / oneDevPixel),
                               gfxFloat(oneCSSPixel / oneDevPixel),
                               gfxFloat(oneCSSPixel / oneDevPixel),
                               gfxFloat(oneCSSPixel / oneDevPixel) };
 
   PRUint8 focusStyles[4] = { NS_STYLE_BORDER_STYLE_DOTTED,
                              NS_STYLE_BORDER_STYLE_DOTTED,
@@ -1133,50 +1069,16 @@ nsCSSRendering::FindBackground(nsPresCon
 }
 
 void
 nsCSSRendering::DidPaint()
 {
   gInlineBGData->Reset();
 }
 
-PRBool
-nsCSSRendering::GetBorderRadiusTwips(const nsStyleCorners& aBorderRadius,
-                                     const nscoord aFrameWidth,
-                                     const nscoord aFrameHeight,
-                                     nscoord aRadii[8])
-{
-  PRBool result = PR_FALSE;
-
-  // Percentages are relative to whichever side they're on.
-  NS_FOR_CSS_HALF_CORNERS(i) {
-    const nsStyleCoord c = aBorderRadius.Get(i);
-    nscoord axis = NS_HALF_CORNER_IS_X(i) ? aFrameWidth : aFrameHeight;
-
-    switch (c.GetUnit()) {
-      case eStyleUnit_Percent:
-        aRadii[i] = (nscoord)(c.GetPercentValue() * axis);
-        break;
-
-      case eStyleUnit_Coord:
-        aRadii[i] = c.GetCoordValue();
-        break;
-
-      default:
-        NS_NOTREACHED("GetBorderRadiusTwips: bad unit");
-        aRadii[i] = 0;
-        break;
-    }
-
-    if (aRadii[i])
-      result = PR_TRUE;
-  }
-  return result;
-}
-
 void
 nsCSSRendering::PaintBoxShadowOuter(nsPresContext* aPresContext,
                                     nsIRenderingContext& aRenderingContext,
                                     nsIFrame* aForFrame,
                                     const nsRect& aFrameArea,
                                     const nsRect& aDirtyRect)
 {
   const nsStyleBorder* styleBorder = aForFrame->GetStyleBorder();
@@ -1196,47 +1098,42 @@ nsCSSRendering::PaintBoxShadowOuter(nsPr
     // We don't respect border-radius for native-themed widgets
     hasBorderRadius = PR_FALSE;
     // For opaque (rectangular) theme widgets we can take the generic
     // border-box path with border-radius disabled.
     nativeTheme = transparency != nsITheme::eOpaque;
   } else {
     nativeTheme = PR_FALSE;
     nscoord twipsRadii[8];
-    hasBorderRadius =
-      GetBorderRadiusTwips(styleBorder->mBorderRadius,
-                           aFrameArea.width,
-                           aFrameArea.height,
-                           twipsRadii);
+    NS_ASSERTION(aFrameArea.Size() == aForFrame->GetSize(), "unexpected size");
+    hasBorderRadius = aForFrame->GetBorderRadii(twipsRadii);
     if (hasBorderRadius) {
-      PRIntn sidesToSkip = aForFrame->GetSkipSides();
-      ComputePixelRadii(twipsRadii, aFrameArea, sidesToSkip, twipsPerPixel,
-                        &borderRadii);
+      ComputePixelRadii(twipsRadii, twipsPerPixel, &borderRadii);
     }
   }
 
   nsRect frameRect =
     nativeTheme ? aForFrame->GetOverflowRectRelativeToSelf() + aFrameArea.TopLeft() : aFrameArea;
-  gfxRect frameGfxRect = RectToGfxRect(frameRect, twipsPerPixel);
+  gfxRect frameGfxRect(nsLayoutUtils::RectToGfxRect(frameRect, twipsPerPixel));
   frameGfxRect.Round();
 
   // We don't show anything that intersects with the frame we're blurring on. So tell the
   // blurrer not to do unnecessary work there.
   gfxRect skipGfxRect = frameGfxRect;
   PRBool useSkipGfxRect = PR_TRUE;
   if (nativeTheme) {
     // Optimize non-leaf native-themed frames by skipping computing pixels
     // in the padding-box. We assume the padding-box is going to be painted
     // opaquely for non-leaf frames.
     // XXX this may not be a safe assumption; we should make this go away
     // by optimizing box-shadow drawing more for the cases where we don't have a skip-rect.
     useSkipGfxRect = !aForFrame->IsLeaf();
     nsRect paddingRect =
       aForFrame->GetPaddingRect() - aForFrame->GetPosition() + aFrameArea.TopLeft();
-    skipGfxRect = RectToGfxRect(paddingRect, twipsPerPixel);
+    skipGfxRect = nsLayoutUtils::RectToGfxRect(paddingRect, twipsPerPixel);
   } else if (hasBorderRadius) {
     skipGfxRect.Inset(
         PR_MAX(borderRadii[C_TL].height, borderRadii[C_TR].height), 0,
         PR_MAX(borderRadii[C_BL].height, borderRadii[C_BR].height), 0);
   }
 
   for (PRUint32 i = shadows->Length(); i > 0; --i) {
     nsCSSShadowItem* shadowItem = shadows->ShadowAt(i - 1);
@@ -1254,18 +1151,20 @@ nsCSSRendering::PaintBoxShadowOuter(nsPr
     }
 
     // shadowRect won't include the blur, so make an extra rect here that includes the blur
     // for use in the even-odd rule below.
     nsRect shadowRectPlusBlur = shadowRect;
     nscoord blurRadius = shadowItem->mRadius;
     shadowRectPlusBlur.Inflate(blurRadius, blurRadius);
 
-    gfxRect shadowGfxRect = RectToGfxRect(shadowRect, twipsPerPixel);
-    gfxRect shadowGfxRectPlusBlur = RectToGfxRect(shadowRectPlusBlur, twipsPerPixel);
+    gfxRect shadowGfxRect =
+      nsLayoutUtils::RectToGfxRect(shadowRect, twipsPerPixel);
+    gfxRect shadowGfxRectPlusBlur =
+      nsLayoutUtils::RectToGfxRect(shadowRectPlusBlur, twipsPerPixel);
     shadowGfxRect.Round();
     shadowGfxRectPlusBlur.RoundOut();
 
     gfxContext* renderContext = aRenderingContext.ThebesContext();
     nsRefPtr<gfxContext> shadowContext;
     nsContextBoxBlur blurringArea;
 
     // When getting the widget shape from the native theme, we're going
@@ -1379,34 +1278,30 @@ nsCSSRendering::PaintBoxShadowInner(nsPr
     // "padding-box" for native-themed widgets, so just don't draw
     // inner box-shadows for them. But we allow chrome to paint inner
     // box shadows since chrome can be aware of the platform theme.
     return;
   }
 
   // Get any border radius, since box-shadow must also have rounded corners if the frame does
   nscoord twipsRadii[8];
-  PRBool hasBorderRadius = GetBorderRadiusTwips(styleBorder->mBorderRadius,
-                                                aFrameArea.width,
-                                                aFrameArea.height,
-                                                twipsRadii);
+  NS_ASSERTION(aFrameArea.Size() == aForFrame->GetSize(), "unexpected size");
+  PRBool hasBorderRadius = aForFrame->GetBorderRadii(twipsRadii);
   nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
 
   nsRect paddingRect = aFrameArea;
   nsMargin border = aForFrame->GetUsedBorder();
   aForFrame->ApplySkipSides(border);
   paddingRect.Deflate(border);
 
   gfxCornerSizes innerRadii;
   if (hasBorderRadius) {
     gfxCornerSizes borderRadii;
-    PRIntn sidesToSkip = aForFrame->GetSkipSides();
-
-    ComputePixelRadii(twipsRadii, aFrameArea, sidesToSkip,
-                      twipsPerPixel, &borderRadii);
+
+    ComputePixelRadii(twipsRadii, twipsPerPixel, &borderRadii);
     gfxFloat borderSizes[4] = {
       gfxFloat(border.top / twipsPerPixel),
       gfxFloat(border.right / twipsPerPixel),
       gfxFloat(border.bottom / twipsPerPixel),
       gfxFloat(border.left / twipsPerPixel)
     };
     nsCSSBorderRenderer::ComputeInnerRadii(borderRadii, borderSizes,
                                            &innerRadii);
@@ -1458,17 +1353,17 @@ nsCSSRendering::PaintBoxShadowInner(nsPr
       nsCSSBorderRenderer::ComputeInnerRadii(innerRadii, borderSizes,
                                              &clipRectRadii);
     }
 
     // Set the "skip rect" to the area within the frame that we don't paint in,
     // including after blurring. We also use this for clipping later on.
     nsRect skipRect = shadowClipRect;
     skipRect.Deflate(blurRadius, blurRadius);
-    gfxRect skipGfxRect = RectToGfxRect(skipRect, twipsPerPixel);
+    gfxRect skipGfxRect = nsLayoutUtils::RectToGfxRect(skipRect, twipsPerPixel);
     if (hasBorderRadius) {
       skipGfxRect.Inset(PR_MAX(clipRectRadii[C_TL].height, clipRectRadii[C_TR].height), 0,
                         PR_MAX(clipRectRadii[C_BL].height, clipRectRadii[C_BR].height), 0);
     }
 
     gfxContext* renderContext = aRenderingContext.ThebesContext();
     nsRefPtr<gfxContext> shadowContext;
     nsContextBoxBlur blurringArea;
@@ -1486,32 +1381,35 @@ nsCSSRendering::PaintBoxShadowInner(nsPr
       shadowColor = aForFrame->GetStyleColor()->mColor;
 
     renderContext->Save();
     renderContext->SetColor(gfxRGBA(shadowColor));
 
     // Clip the context to the area of the frame's padding rect, so no part of the
     // shadow is painted outside. Also cut out anything beyond where the inset shadow
     // will be.
-    gfxRect shadowGfxRect = RectToGfxRect(paddingRect, twipsPerPixel);
+    gfxRect shadowGfxRect =
+      nsLayoutUtils::RectToGfxRect(paddingRect, twipsPerPixel);
     shadowGfxRect.Round();
     renderContext->NewPath();
     if (hasBorderRadius)
       renderContext->RoundedRectangle(shadowGfxRect, innerRadii, PR_FALSE);
     else
       renderContext->Rectangle(shadowGfxRect);
     renderContext->Rectangle(skipGfxRect);
     renderContext->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD);
     renderContext->Clip();
 
     // Fill the temporary surface minus the area within the frame that we should
     // not paint in, and blur and apply it
-    gfxRect shadowPaintGfxRect = RectToGfxRect(shadowPaintRect, twipsPerPixel);
+    gfxRect shadowPaintGfxRect =
+      nsLayoutUtils::RectToGfxRect(shadowPaintRect, twipsPerPixel);
     shadowPaintGfxRect.RoundOut();
-    gfxRect shadowClipGfxRect = RectToGfxRect(shadowClipRect, twipsPerPixel);
+    gfxRect shadowClipGfxRect =
+      nsLayoutUtils::RectToGfxRect(shadowClipRect, twipsPerPixel);
     shadowClipGfxRect.Round();
     shadowContext->NewPath();
     shadowContext->Rectangle(shadowPaintGfxRect);
     if (hasBorderRadius)
       shadowContext->RoundedRectangle(shadowClipGfxRect, clipRectRadii, PR_FALSE);
     else
       shadowContext->Rectangle(shadowClipGfxRect);
     shadowContext->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD);
@@ -1613,17 +1511,17 @@ static inline void
 SetupDirtyRects(const nsRect& aBGClipArea, const nsRect& aCallerDirtyRect,
                 nscoord aAppUnitsPerPixel,
                 /* OUT: */
                 nsRect* aDirtyRect, gfxRect* aDirtyRectGfx)
 {
   aDirtyRect->IntersectRect(aBGClipArea, aCallerDirtyRect);
 
   // Compute the Thebes equivalent of the dirtyRect.
-  *aDirtyRectGfx = RectToGfxRect(*aDirtyRect, aAppUnitsPerPixel);
+  *aDirtyRectGfx = nsLayoutUtils::RectToGfxRect(*aDirtyRect, aAppUnitsPerPixel);
   NS_WARN_IF_FALSE(aDirtyRect->IsEmpty() || !aDirtyRectGfx->IsEmpty(),
                    "converted dirty rect should not be empty");
   NS_ABORT_IF_FALSE(!aDirtyRect->IsEmpty() || aDirtyRectGfx->IsEmpty(),
                     "second should be empty if first is");
 }
 
 static void
 SetupBackgroundClip(gfxContext *aCtx, PRUint8 aBackgroundClip,
@@ -1673,17 +1571,18 @@ SetupBackgroundClip(gfxContext *aCtx, PR
   // If we have rounded corners, clip all subsequent drawing to the
   // rounded rectangle defined by bgArea and bgRadii (we don't know
   // whether the rounded corners intrude on the dirtyRect or not).
   // Do not do this if we have a caller-provided clip rect --
   // as above with bgArea, arguably a bug, but table painting seems
   // to depend on it.
 
   if (aHaveRoundedCorners) {
-    gfxRect bgAreaGfx(RectToGfxRect(*aBGClipArea, aAppUnitsPerPixel));
+    gfxRect bgAreaGfx =
+      nsLayoutUtils::RectToGfxRect(*aBGClipArea, aAppUnitsPerPixel);
     bgAreaGfx.Round();
     bgAreaGfx.Condition();
 
     if (bgAreaGfx.IsEmpty()) {
       // I think it's become possible to hit this since
       // http://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
       NS_WARNING("converted background area should not be empty");
       // Make our caller not do anything.
@@ -1948,17 +1847,18 @@ nsCSSRendering::PaintGradient(nsPresCont
                               const nsRect& aOneCellArea,
                               const nsRect& aFillArea)
 {
   if (aOneCellArea.IsEmpty())
     return;
 
   gfxContext *ctx = aRenderingContext.ThebesContext();
   nscoord appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
-  gfxRect oneCellArea = RectToGfxRect(aOneCellArea, appUnitsPerPixel);
+  gfxRect oneCellArea =
+    nsLayoutUtils::RectToGfxRect(aOneCellArea, appUnitsPerPixel);
 
   // Compute "gradient line" start and end relative to oneCellArea
   gfxPoint lineStart, lineEnd;
   double radiusX = 0, radiusY = 0; // for radial gradients only
   if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
     ComputeLinearGradientLine(aPresContext, aGradient, oneCellArea.size,
                               &lineStart, &lineEnd);
   } else {
@@ -2177,30 +2077,31 @@ nsCSSRendering::PaintGradient(nsPresCont
   // Paint gradient tiles. This isn't terribly efficient, but doing it this
   // way is simple and sure to get pixel-snapping right. We could speed things
   // up by drawing tiles into temporary surfaces and copying those to the
   // destination, but after pixel-snapping tiles may not all be the same size.
   nsRect dirty;
   if (!dirty.IntersectRect(aDirtyRect, aFillArea))
     return;
 
-  gfxRect areaToFill = RectToGfxRect(aFillArea, appUnitsPerPixel);
+  gfxRect areaToFill =
+    nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerPixel);
   gfxMatrix ctm = ctx->CurrentMatrix();
 
   // xStart/yStart are the top-left corner of the top-left tile.
   nscoord xStart = FindTileStart(dirty.x, aOneCellArea.x, aOneCellArea.width);
   nscoord yStart = FindTileStart(dirty.y, aOneCellArea.y, aOneCellArea.height);
   nscoord xEnd = dirty.XMost();
   nscoord yEnd = dirty.YMost();
   // x and y are the top-left corner of the tile to draw
   for (nscoord y = yStart; y < yEnd; y += aOneCellArea.height) {
     for (nscoord x = xStart; x < xEnd; x += aOneCellArea.width) {
       // The coordinates of the tile
-      gfxRect tileRect =
-        RectToGfxRect(nsRect(x, y, aOneCellArea.width, aOneCellArea.height),
+      gfxRect tileRect = nsLayoutUtils::RectToGfxRect(
+                      nsRect(x, y, aOneCellArea.width, aOneCellArea.height),
                       appUnitsPerPixel);
       // The actual area to fill with this tile is the intersection of this
       // tile with the overall area we're supposed to be filling
       gfxRect fillRect = tileRect.Intersect(areaToFill);
       ctx->NewPath();
       ctx->Translate(tileRect.pos);
       ctx->SetPattern(gradientPattern);
       ctx->Rectangle(fillRect - tileRect.pos, PR_TRUE);
@@ -2274,22 +2175,26 @@ nsCSSRendering::PaintBackgroundWithSC(ns
   nscoord appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
 
   // Same coordinate space as aBorderArea & aBGClipRect
   gfxCornerSizes bgRadii;
   PRBool haveRoundedCorners;
   {
     nscoord radii[8];
     nsSize frameSize = aForFrame->GetSize();
-    haveRoundedCorners =
-      GetBorderRadiusTwips(aBorder.mBorderRadius, frameSize.width,
-                           frameSize.height, radii);
+    if (&aBorder == aForFrame->GetStyleBorder() &&
+        frameSize == aBorderArea.Size()) {
+      haveRoundedCorners = aForFrame->GetBorderRadii(radii);
+    } else {
+      haveRoundedCorners = nsIFrame::ComputeBorderRadii(aBorder.mBorderRadius,
+                                   frameSize, aBorderArea.Size(),
+                                   aForFrame->GetSkipSides(), radii);
+    }
     if (haveRoundedCorners)
-      ComputePixelRadii(radii, aBorderArea, aForFrame->GetSkipSides(),
-                        appUnitsPerPixel, &bgRadii);
+      ComputePixelRadii(radii, appUnitsPerPixel, &bgRadii);
   }
 
   // The 'bgClipArea' (used only by the image tiling logic, far below)
   // is the caller-provided aBGClipRect if any, or else the area
   // determined by the value of 'background-clip' in
   // SetupCurrentBackgroundClip.  (Arguably it should be the
   // intersection, but that breaks the table painter -- in particular,
   // taking the intersection breaks reftests/bugs/403249-1[ab].)
@@ -3930,19 +3835,20 @@ nsContextBoxBlur::Init(const nsRect& aRe
 
   // If not blurring, draw directly onto the destination device
   if (blurRadius <= 0 && spreadRadius <= 0 && !(aFlags & FORCE_MASK)) {
     mContext = aDestinationCtx;
     return mContext;
   }
 
   // Convert from app units to device pixels
-  gfxRect rect = RectToGfxRect(aRect, aAppUnitsPerDevPixel);
-
-  gfxRect dirtyRect = RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
+  gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel);
+
+  gfxRect dirtyRect =
+    nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
   dirtyRect.RoundOut();
 
   // Create the temporary surface for blurring
   mContext = blur.Init(rect, gfxIntSize(spreadRadius, spreadRadius),
                        gfxIntSize(blurRadius, blurRadius),
                        &dirtyRect, aSkipRect);
   return mContext;
 }
--- a/layout/base/nsCSSRendering.h
+++ b/layout/base/nsCSSRendering.h
@@ -68,25 +68,19 @@ struct nsCSSRendering {
                                   const nsRect& aDirtyRect);
 
   static void PaintBoxShadowOuter(nsPresContext* aPresContext,
                                   nsIRenderingContext& aRenderingContext,
                                   nsIFrame* aForFrame,
                                   const nsRect& aFrameArea,
                                   const nsRect& aDirtyRect);
 
-  /**
-   * Get the size, in app units, of the border radii. It returns FALSE iff all
-   * returned radii == 0 (so no border radii), TRUE otherwise.
-   * For the aRadii indexes, use the NS_CORNER_* constants in nsStyleConsts.h
-   */
-  static PRBool GetBorderRadiusTwips(const nsStyleCorners& aBorderRadius,
-                                     const nscoord aFrameWidth,
-                                     const nscoord aFrameHeight,
-                                     nscoord aRadii[8]);
+  static void ComputePixelRadii(const nscoord *aAppUnitsRadii,
+                                nscoord aAppUnitsPerPixel,
+                                gfxCornerSizes *oBorderRadii);
 
   /**
    * Render the border for an element using css rendering rules
    * for borders. aSkipSides is a bitmask of the sides to skip
    * when rendering. If 0 then no sides are skipped.
    */
   static void PaintBorder(nsPresContext* aPresContext,
                           nsIRenderingContext& aRenderingContext,
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -1004,21 +1004,17 @@ nsDisplayBoxShadowOuter::ComputeVisibili
   nsRect visibleBounds = aVisibleRegion->GetBounds();
   nsRect frameRect(origin, mFrame->GetSize());
   if (!frameRect.Contains(visibleBounds))
     return PR_TRUE;
 
   // the visible region is entirely inside the border-rect, and box shadows
   // never render within the border-rect (unless there's a border radius).
   nscoord twipsRadii[8];
-  PRBool hasBorderRadii =
-     nsCSSRendering::GetBorderRadiusTwips(mFrame->GetStyleBorder()->
-                                          mBorderRadius,
-                                          frameRect.width, frameRect.height,
-                                          twipsRadii);
+  PRBool hasBorderRadii = mFrame->GetBorderRadii(twipsRadii);
   if (!hasBorderRadii)
     return PR_FALSE;
 
   return !RoundedRectContainsRect(frameRect, twipsRadii, visibleBounds);
 }
 
 void
 nsDisplayBoxShadowInner::Paint(nsDisplayListBuilder* aBuilder,
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -2866,20 +2866,18 @@ MapToFloatImagePixels(const gfxSize& aSi
 static gfxPoint
 MapToFloatUserPixels(const gfxSize& aSize,
                      const gfxRect& aDest, const gfxPoint& aPt)
 {
   return gfxPoint(aPt.x*aDest.size.width/aSize.width + aDest.pos.x,
                   aPt.y*aDest.size.height/aSize.height + aDest.pos.y);
 }
 
-// helper function to convert a nsRect to a gfxRect
-// borrowed from nsCSSRendering.cpp
-static gfxRect
-RectToGfxRect(const nsRect& aRect, PRInt32 aAppUnitsPerDevPixel)
+/* static */ gfxRect
+nsLayoutUtils::RectToGfxRect(const nsRect& aRect, PRInt32 aAppUnitsPerDevPixel)
 {
   return gfxRect(gfxFloat(aRect.x) / aAppUnitsPerDevPixel,
                  gfxFloat(aRect.y) / aAppUnitsPerDevPixel,
                  gfxFloat(aRect.width) / aAppUnitsPerDevPixel,
                  gfxFloat(aRect.height) / aAppUnitsPerDevPixel);
 }
 
 struct SnappedImageDrawingParameters {
@@ -2929,19 +2927,22 @@ ComputeSnappedImageDrawingParameters(gfx
                                      const nsPoint   aAnchor,
                                      const nsRect    aDirty,
                                      const nsIntSize aImageSize)
 
 {
   if (aDest.IsEmpty() || aFill.IsEmpty())
     return SnappedImageDrawingParameters();
 
-  gfxRect devPixelDest = RectToGfxRect(aDest, aAppUnitsPerDevPixel);
-  gfxRect devPixelFill = RectToGfxRect(aFill, aAppUnitsPerDevPixel);
-  gfxRect devPixelDirty = RectToGfxRect(aDirty, aAppUnitsPerDevPixel);
+  gfxRect devPixelDest =
+    nsLayoutUtils::RectToGfxRect(aDest, aAppUnitsPerDevPixel);
+  gfxRect devPixelFill =
+    nsLayoutUtils::RectToGfxRect(aFill, aAppUnitsPerDevPixel);
+  gfxRect devPixelDirty =
+    nsLayoutUtils::RectToGfxRect(aDirty, aAppUnitsPerDevPixel);
 
   PRBool ignoreScale = PR_FALSE;
 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
   ignoreScale = PR_TRUE;
 #endif
   gfxRect fill = devPixelFill;
   PRBool didSnap = aCtx->UserToDevicePixelSnapped(fill, ignoreScale);
 
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -938,16 +938,22 @@ public:
                             gfxPattern::GraphicsFilter aGraphicsFilter,
                             const nsRect&        aDest,
                             const nsRect&        aFill,
                             const nsPoint&       aAnchor,
                             const nsRect&        aDirty,
                             PRUint32             aImageFlags);
 
   /**
+   * Convert an nsRect to a gfxRect.
+   */
+  static gfxRect RectToGfxRect(const nsRect& aRect,
+                               PRInt32 aAppUnitsPerDevPixel);
+
+  /**
    * Draw a drawable using the pixel snapping algorithm.
    * See https://wiki.mozilla.org/Gecko:Image_Snapping_and_Rendering
    *   @param aRenderingContext Where to draw the image, set up with an
    *                            appropriate scale and transform for drawing in
    *                            app units.
    *   @param aDrawable         The drawable we want to draw.
    *   @param aFilter           The graphics filter we should draw with.
    *   @param aDest             Where one copy of the image should mapped to.
--- a/layout/forms/nsFieldSetFrame.cpp
+++ b/layout/forms/nsFieldSetFrame.cpp
@@ -274,16 +274,18 @@ nsFieldSetFrame::PaintBorderBackground(n
   const nsStyleBorder* borderStyle = GetStyleBorder();
        
   nscoord topBorder = borderStyle->GetActualBorderWidth(NS_SIDE_TOP);
   nscoord yoff = 0;
   nsPresContext* presContext = PresContext();
      
   // if the border is smaller than the legend. Move the border down
   // to be centered on the legend. 
+  // FIXME: This means border-radius clamping is incorrect; we should
+  // override nsIFrame::GetBorderRadii.
   if (topBorder < mLegendRect.height)
     yoff = (mLegendRect.height - topBorder)/2;
       
   nsRect rect(aPt.x, aPt.y + yoff, mRect.width, mRect.height - yoff);
 
   nsCSSRendering::PaintBackground(presContext, aRenderingContext, this,
                                   aDirtyRect, rect, aBGFlags);
 
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -731,16 +731,159 @@ nsIFrame::GetContentRect() const
 {
   nsMargin bp(GetUsedBorderAndPadding());
   ApplySkipSides(bp);
   nsRect r(mRect);
   r.Deflate(bp);
   return r;
 }
 
+PRBool
+nsIFrame::ComputeBorderRadii(const nsStyleCorners& aBorderRadius,
+                             const nsSize& aFrameSize,
+                             const nsSize& aBorderArea,
+                             PRIntn aSkipSides,
+                             nscoord aRadii[8])
+{
+  // Percentages are relative to whichever side they're on.
+  NS_FOR_CSS_HALF_CORNERS(i) {
+    const nsStyleCoord c = aBorderRadius.Get(i);
+    nscoord axis =
+      NS_HALF_CORNER_IS_X(i) ? aFrameSize.width : aFrameSize.height;
+
+    switch (c.GetUnit()) {
+      case eStyleUnit_Percent:
+        aRadii[i] = (nscoord)(c.GetPercentValue() * axis);
+        break;
+
+      case eStyleUnit_Coord:
+        aRadii[i] = c.GetCoordValue();
+        break;
+
+      default:
+        NS_NOTREACHED("ComputeBorderRadii: bad unit");
+        aRadii[i] = 0;
+        break;
+    }
+  }
+
+  if (aSkipSides & (1 << NS_SIDE_TOP)) {
+    aRadii[NS_CORNER_TOP_LEFT_X] = 0;
+    aRadii[NS_CORNER_TOP_LEFT_Y] = 0;
+    aRadii[NS_CORNER_TOP_RIGHT_X] = 0;
+    aRadii[NS_CORNER_TOP_RIGHT_Y] = 0;
+  }
+
+  if (aSkipSides & (1 << NS_SIDE_RIGHT)) {
+    aRadii[NS_CORNER_TOP_RIGHT_X] = 0;
+    aRadii[NS_CORNER_TOP_RIGHT_Y] = 0;
+    aRadii[NS_CORNER_BOTTOM_RIGHT_X] = 0;
+    aRadii[NS_CORNER_BOTTOM_RIGHT_Y] = 0;
+  }
+
+  if (aSkipSides & (1 << NS_SIDE_BOTTOM)) {
+    aRadii[NS_CORNER_BOTTOM_RIGHT_X] = 0;
+    aRadii[NS_CORNER_BOTTOM_RIGHT_Y] = 0;
+    aRadii[NS_CORNER_BOTTOM_LEFT_X] = 0;
+    aRadii[NS_CORNER_BOTTOM_LEFT_Y] = 0;
+  }
+
+  if (aSkipSides & (1 << NS_SIDE_LEFT)) {
+    aRadii[NS_CORNER_BOTTOM_LEFT_X] = 0;
+    aRadii[NS_CORNER_BOTTOM_LEFT_Y] = 0;
+    aRadii[NS_CORNER_TOP_LEFT_X] = 0;
+    aRadii[NS_CORNER_TOP_LEFT_Y] = 0;
+  }
+
+  // css3-background specifies this algorithm for reducing
+  // corner radii when they are too big.
+  PRBool haveRadius = PR_FALSE;
+  double ratio = 1.0f;
+  NS_FOR_CSS_SIDES(side) {
+    PRUint32 hc1 = NS_SIDE_TO_HALF_CORNER(side, PR_FALSE, PR_TRUE);
+    PRUint32 hc2 = NS_SIDE_TO_HALF_CORNER(side, PR_TRUE, PR_TRUE);
+    nscoord length =
+      NS_SIDE_IS_VERTICAL(side) ? aBorderArea.height : aBorderArea.width;
+    nscoord sum = aRadii[hc1] + aRadii[hc2];
+    if (sum)
+      haveRadius = PR_TRUE;
+
+    // avoid floating point division in the normal case
+    if (length < sum)
+      ratio = NS_MIN(ratio, double(length)/sum);
+  }
+  if (ratio < 1.0) {
+    NS_FOR_CSS_HALF_CORNERS(corner) {
+      aRadii[corner] *= ratio;
+    }
+  }
+
+  return haveRadius;
+}
+
+/* static */ void
+nsIFrame::InsetBorderRadii(nscoord aRadii[8], const nsMargin &aOffsets)
+{
+  NS_FOR_CSS_SIDES(side) {
+    nscoord offset = aOffsets.side(side);
+    PRUint32 hc1 = NS_SIDE_TO_HALF_CORNER(side, PR_FALSE, PR_FALSE);
+    PRUint32 hc2 = NS_SIDE_TO_HALF_CORNER(side, PR_TRUE, PR_FALSE);
+    aRadii[hc1] = NS_MAX(0, aRadii[hc1] - offset);
+    aRadii[hc2] = NS_MAX(0, aRadii[hc2] - offset);
+  }
+}
+
+/* static */ void
+nsIFrame::OutsetBorderRadii(nscoord aRadii[8], const nsMargin &aOffsets)
+{
+  NS_FOR_CSS_SIDES(side) {
+    nscoord offset = aOffsets.side(side);
+    PRUint32 hc1 = NS_SIDE_TO_HALF_CORNER(side, PR_FALSE, PR_FALSE);
+    PRUint32 hc2 = NS_SIDE_TO_HALF_CORNER(side, PR_TRUE, PR_FALSE);
+    if (aRadii[hc1] > 0)
+      aRadii[hc1] += offset;
+    if (aRadii[hc2] > 0)
+      aRadii[hc2] += offset;
+  }
+}
+
+/* virtual */ PRBool
+nsIFrame::GetBorderRadii(nscoord aRadii[8]) const
+{
+  nsSize size = GetSize();
+  return ComputeBorderRadii(GetStyleBorder()->mBorderRadius, size, size,
+                            GetSkipSides(), aRadii);
+}
+
+PRBool
+nsIFrame::GetPaddingBoxBorderRadii(nscoord aRadii[8]) const
+{
+  if (!GetBorderRadii(aRadii))
+    return PR_FALSE;
+  InsetBorderRadii(aRadii, GetUsedBorder());
+  NS_FOR_CSS_HALF_CORNERS(corner) {
+    if (aRadii[corner])
+      return PR_TRUE;
+  }
+  return PR_FALSE;
+}
+
+PRBool
+nsIFrame::GetContentBoxBorderRadii(nscoord aRadii[8]) const
+{
+  if (!GetBorderRadii(aRadii))
+    return PR_FALSE;
+  InsetBorderRadii(aRadii, GetUsedBorderAndPadding());
+  NS_FOR_CSS_HALF_CORNERS(corner) {
+    if (aRadii[corner])
+      return PR_TRUE;
+  }
+  return PR_FALSE;
+}
+
 nsStyleContext*
 nsFrame::GetAdditionalStyleContext(PRInt32 aIndex) const
 {
   NS_PRECONDITION(aIndex >= 0, "invalid index number");
   return nsnull;
 }
 
 void
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -3136,16 +3136,79 @@ nsGfxScrollFrameInner::SetCoordAttribute
   newValue.AppendInt(aSize);
 
   if (aContent->AttrValueIs(kNameSpaceID_None, aAtom, newValue, eCaseMatters))
     return;
 
   aContent->SetAttr(kNameSpaceID_None, aAtom, newValue, PR_TRUE);
 }
 
+static void
+ReduceRadii(nscoord aXBorder, nscoord aYBorder,
+            nscoord& aXRadius, nscoord& aYRadius)
+{
+  // In order to ensure that the inside edge of the border has no
+  // curvature, we need at least one of its radii to be zero.
+  if (aXRadius <= aXBorder || aYRadius <= aYBorder)
+    return;
+
+  // For any corner where we reduce the radii, preserve the corner's shape.
+  double ratio = NS_MAX(double(aXBorder) / aXRadius,
+                        double(aYBorder) / aYRadius);
+  aXRadius *= ratio;
+  aYRadius *= ratio;
+}
+
+/**
+ * Implement an override for nsIFrame::GetBorderRadii to ensure that
+ * the clipping region for the border radius does not clip the scrollbars.
+ *
+ * In other words, we require that the border radius be reduced until the
+ * inner border radius at the inner edge of the border is 0 wherever we
+ * have scrollbars.
+ */
+PRBool
+nsGfxScrollFrameInner::GetBorderRadii(nscoord aRadii[8]) const
+{
+  if (!mOuter->nsContainerFrame::GetBorderRadii(aRadii))
+    return PR_FALSE;
+
+  // Since we can use GetActualScrollbarSizes (rather than
+  // GetDesiredScrollbarSizes) since this doesn't affect reflow, we
+  // probably should.
+  nsMargin sb = GetActualScrollbarSizes();
+  nsMargin border = mOuter->GetUsedBorder();
+
+  if (sb.left > 0 || sb.top > 0) {
+    ReduceRadii(border.left, border.top,
+                aRadii[NS_CORNER_TOP_LEFT_X],
+                aRadii[NS_CORNER_TOP_LEFT_Y]);
+  }
+
+  if (sb.top > 0 || sb.right > 0) {
+    ReduceRadii(border.right, border.top,
+                aRadii[NS_CORNER_TOP_RIGHT_X],
+                aRadii[NS_CORNER_TOP_RIGHT_Y]);
+  }
+
+  if (sb.right > 0 || sb.bottom > 0) {
+    ReduceRadii(border.right, border.bottom,
+                aRadii[NS_CORNER_BOTTOM_RIGHT_X],
+                aRadii[NS_CORNER_BOTTOM_RIGHT_Y]);
+  }
+
+  if (sb.bottom > 0 || sb.left > 0) {
+    ReduceRadii(border.left, border.bottom,
+                aRadii[NS_CORNER_BOTTOM_LEFT_X],
+                aRadii[NS_CORNER_BOTTOM_LEFT_Y]);
+  }
+
+  return PR_TRUE;
+}
+
 nsRect
 nsGfxScrollFrameInner::GetScrolledRect() const
 {
   nsRect result =
     GetScrolledRectInternal(mScrolledFrame->GetOverflowRect(),
                             mScrollPort.Size());
 
   NS_ASSERTION(result.width >= mScrollPort.width,
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -86,16 +86,18 @@ public:
   nsresult FireScrollPortEvent();
   void PostOverflowEvent();
   void Destroy();
 
   nsresult BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                             const nsRect&           aDirtyRect,
                             const nsDisplayListSet& aLists);
 
+  PRBool GetBorderRadii(nscoord aRadii[8]) const;
+
   // nsIReflowCallback
   virtual PRBool ReflowFinished();
   virtual void ReflowCallbackCanceled();
 
   // This gets called when the 'curpos' attribute on one of the scrollbars changes
   void CurPosAttributeChanged(nsIContent* aChild);
   void PostScrollEvent();
   void FireScrollEvent();
@@ -336,16 +338,20 @@ public:
                                nsHTMLReflowMetrics* aMetrics,
                                PRBool aFirstPass);
   nsresult ReflowContents(ScrollReflowState* aState,
                           const nsHTMLReflowMetrics& aDesiredSize);
   void PlaceScrollArea(const ScrollReflowState& aState,
                        const nsPoint& aScrollPosition);
   nscoord GetIntrinsicVScrollbarWidth(nsIRenderingContext *aRenderingContext);
 
+  virtual PRBool GetBorderRadii(nscoord aRadii[8]) const {
+    return mInner.GetBorderRadii(aRadii);
+  }
+
   virtual nscoord GetMinWidth(nsIRenderingContext *aRenderingContext);
   virtual nscoord GetPrefWidth(nsIRenderingContext *aRenderingContext);
   NS_IMETHOD GetPadding(nsMargin& aPadding);
   virtual PRBool IsCollapsed(nsBoxLayoutState& aBoxLayoutState);
   
   NS_IMETHOD Reflow(nsPresContext*          aPresContext,
                     nsHTMLReflowMetrics&     aDesiredSize,
                     const nsHTMLReflowState& aReflowState,
@@ -591,16 +597,20 @@ public:
   virtual nsSize GetMinSize(nsBoxLayoutState& aBoxLayoutState);
   virtual nsSize GetPrefSize(nsBoxLayoutState& aBoxLayoutState);
   virtual nsSize GetMaxSize(nsBoxLayoutState& aBoxLayoutState);
   virtual nscoord GetBoxAscent(nsBoxLayoutState& aBoxLayoutState);
 
   NS_IMETHOD DoLayout(nsBoxLayoutState& aBoxLayoutState);
   NS_IMETHOD GetPadding(nsMargin& aPadding);
 
+  virtual PRBool GetBorderRadii(nscoord aRadii[8]) const {
+    return mInner.GetBorderRadii(aRadii);
+  }
+
   nsresult Layout(nsBoxLayoutState& aState);
   void LayoutScrollArea(nsBoxLayoutState& aState, const nsPoint& aScrollPosition);
 
   static PRBool AddRemoveScrollbar(PRBool& aHasScrollbar, 
                                    nscoord& aXY, 
                                    nscoord& aSize, 
                                    nscoord aSbSize, 
                                    PRBool aOnRightOrBottom, 
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -914,16 +914,67 @@ public:
   /**
    * Like the frame's rect (see |GetRect|), which is the border rect,
    * other rectangles of the frame, in app units, relative to the parent.
    */
   nsRect GetPaddingRect() const;
   nsRect GetContentRect() const;
 
   /**
+   * Get the size, in app units, of the border radii. It returns FALSE iff all
+   * returned radii == 0 (so no border radii), TRUE otherwise.
+   * For the aRadii indexes, use the NS_CORNER_* constants in nsStyleConsts.h
+   * If a side is skipped via aSkipSides, its corners are forced to 0.
+   *
+   * All corner radii are then adjusted so they do not require more
+   * space than aBorderArea, according to the algorithm in css3-background.
+   *
+   * aFrameSize is used as the basis for percentage widths and heights.
+   * aBorderArea is used for the adjustment of radii that might be too
+   * large.
+   * FIXME: In the long run, we can probably get away with only one of
+   * these, especially if we change the way we handle outline-radius (by
+   * removing it and inflating the border radius)
+   *
+   * Return whether any radii are nonzero.
+   */
+  static PRBool ComputeBorderRadii(const nsStyleCorners& aBorderRadius,
+                                   const nsSize& aFrameSize,
+                                   const nsSize& aBorderArea,
+                                   PRIntn aSkipSides,
+                                   nscoord aRadii[8]);
+
+  /*
+   * Given a set of border radii for one box (e.g., border box), convert
+   * it to the equivalent set of radii for another box (e.g., in to
+   * padding box, out to outline box) by reducing radii or increasing
+   * nonzero radii as appropriate.
+   *
+   * Indices into aRadii are the NS_CORNER_* constants in nsStyleConsts.h
+   *
+   * Note that InsetBorderRadii is lossy, since it can turn nonzero
+   * radii into zero, and OutsetBorderRadii does not inflate zero radii.
+   * Therefore, callers should always inset or outset directly from the
+   * original value coming from style.
+   */
+  static void InsetBorderRadii(nscoord aRadii[8], const nsMargin &aOffsets);
+  static void OutsetBorderRadii(nscoord aRadii[8], const nsMargin &aOffsets);
+
+  /**
+   * Fill in border radii for this frame.  Return whether any are
+   * nonzero.
+   *
+   * Indices into aRadii are the NS_CORNER_* constants in nsStyleConsts.h
+   */
+  virtual PRBool GetBorderRadii(nscoord aRadii[8]) const;
+
+  PRBool GetPaddingBoxBorderRadii(nscoord aRadii[8]) const;
+  PRBool GetContentBoxBorderRadii(nscoord aRadii[8]) const;
+
+  /**
    * Get the position of the frame's baseline, relative to the top of
    * the frame (its top border edge).  Only valid when Reflow is not
    * needed and when the frame returned nsHTMLReflowMetrics::
    * ASK_FOR_BASELINE as ascent in its reflow metrics.
    */
   virtual nscoord GetBaseline() const = 0;
 
   /**
--- a/layout/reftests/border-radius/reftest.list
+++ b/layout/reftests/border-radius/reftest.list
@@ -43,8 +43,12 @@ fails == clipping-1.html clipping-1-ref.
 == inherit-1.html inherit-1-ref.html # border-radius shouldn't inherit
 
 # Table elements
 == table-collapse-1.html table-collapse-1-ref.html # border-radius is ignored on internal table elements
 # when border-collapse: collapse
 
 == invalidate-1a.html invalidate-1-ref.html
 == invalidate-1b.html invalidate-1-ref.html
+
+# test that border-radius is reduced for scrollbars
+== scrollbar-clamping-1.html scrollbar-clamping-1-ref.html
+== scrollbar-clamping-2.html scrollbar-clamping-2-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/border-radius/scrollbar-clamping-1-ref.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html dir="ltr">
+<title>Test of reduction of border-radius for scrollbars (border drawing)</title>
+<style>
+
+.contain { height: 130px; position: relative }
+
+.test {
+  position: absolute;
+  top: 0;
+  left: 0;
+  /* border-width: 2px 4px 8px 10px; */
+  height: 100px;
+  width: 200px;
+  /* -moz-border-radius: 12px / 15px; */
+  border: medium solid blue;
+  border-width: 2px 4px 8px 10px;
+}
+
+.cover {
+  position: absolute;
+  width: 200px;
+  height: 100px;
+  top: 2px;
+  left: 10px;
+  background: blue;
+}
+
+</style>
+
+<div class="contain">
+  <!-- scrollbar along bottom -->
+  <div class="test" id="x" style="-moz-border-radius: 12px 12px 6.4px 10px / 15px 15px 8px 12.5px"></div>
+  <div class="cover" style="-moz-border-radius-topright: 5px"></div>
+</div>
+
+<div class="contain">
+  <!-- scrollbar along right -->
+  <div class="test" id="y" style="-moz-border-radius: 12px 4px 6.4px 12px / 15px 5px 8px 15px"></div>
+  <div class="cover"></div>
+</div>
+
+<div class="contain">
+  <!-- scrollbar along bottom and right -->
+  <div class="test" id="xy" style="-moz-border-radius: 12px 4px 6.4px 10px / 15px 5px 8px 12.5px"></div>
+  <div class="cover"></div>
+</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/border-radius/scrollbar-clamping-1.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<title>Test of reduction of border-radius for scrollbars (border drawing)</title>
+<style>
+
+.contain { height: 130px; position: relative }
+
+.test {
+  position: absolute;
+  top: 0;
+  left: 0;
+  border: medium solid blue;
+  border-width: 2px 4px 8px 10px;
+  height: 100px;
+  width: 200px;
+  -moz-border-radius: 12px / 15px;
+}
+
+.cover {
+  position: absolute;
+  width: 200px;
+  height: 100px;
+  top: 2px;
+  left: 10px;
+  background: blue;
+}
+
+#x, #xy { overflow-x: scroll }
+#y, #xy { overflow-y: scroll }
+
+</style>
+
+<div class="contain">
+  <div class="test" id="x"></div>
+  <div class="cover" style="-moz-border-radius-topright: 5px"></div>
+</div>
+
+<div class="contain">
+  <div class="test" id="y"></div>
+  <div class="cover"></div>
+</div>
+
+<div class="contain">
+  <div class="test" id="xy"></div>
+  <div class="cover"></div>
+</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/border-radius/scrollbar-clamping-2-ref.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html dir="ltr">
+<title>Test of reduction of border-radius for scrollbars (background drawing)</title>
+<style>
+
+.contain { height: 130px; position: relative }
+
+.test {
+  position: absolute;
+  top: 0;
+  left: 0;
+  /* border-width: 2px 4px 8px 10px; */
+  height: 110px;
+  width: 214px;
+  /* -moz-border-radius: 12px / 15px; */
+  background: blue;
+}
+
+</style>
+
+<div class="contain">
+  <!-- scrollbar along bottom -->
+  <div class="test" id="x" style="-moz-border-radius: 12px 12px 6.4px 10px / 15px 15px 8px 12.5px"></div>
+</div>
+
+<div class="contain">
+  <!-- scrollbar along right -->
+  <div class="test" id="y" style="-moz-border-radius: 12px 4px 6.4px 12px / 15px 5px 8px 15px"></div>
+</div>
+
+<div class="contain">
+  <!-- scrollbar along bottom and right -->
+  <div class="test" id="xy" style="-moz-border-radius: 12px 4px 6.4px 10px / 15px 5px 8px 12.5px"></div>
+</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/border-radius/scrollbar-clamping-2.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<title>Test of reduction of border-radius for scrollbars (background drawing)</title>
+<style>
+
+.contain { height: 130px; position: relative }
+
+.test {
+  position: absolute;
+  top: 0;
+  left: 0;
+  border: medium solid transparent;
+  background: blue;
+  border-width: 2px 4px 8px 10px;
+  height: 100px;
+  width: 200px;
+  -moz-border-radius: 12px / 15px;
+}
+
+.cover {
+  position: absolute;
+  width: 200px;
+  height: 100px;
+  top: 2px;
+  left: 10px;
+  background: blue;
+}
+
+#x, #xy { overflow-x: scroll }
+#y, #xy { overflow-y: scroll }
+
+</style>
+
+<div class="contain">
+  <div class="test" id="x"></div>
+  <div class="cover" style="margin-top: 50px; height: 50px"></div>
+</div>
+
+<div class="contain">
+  <div class="test" id="y"></div>
+  <div class="cover" style="margin-left: 100px; width: 100px"></div>
+</div>
+
+<div class="contain">
+  <div class="test" id="xy"></div>
+  <div class="cover"></div>
+</div>
--- a/layout/tables/nsTableCellFrame.cpp
+++ b/layout/tables/nsTableCellFrame.cpp
@@ -1076,16 +1076,25 @@ nsBCTableCellFrame::GetType() const
 /* virtual */ nsMargin
 nsBCTableCellFrame::GetUsedBorder() const
 {
   nsMargin result;
   GetBorderWidth(result);
   return result;
 }
 
+/* virtual */ PRBool
+nsBCTableCellFrame::GetBorderRadii(nscoord aRadii[8]) const
+{
+  NS_FOR_CSS_HALF_CORNERS(corner) {
+    aRadii[corner] = 0;
+  }
+  return PR_FALSE;
+}
+
 #ifdef DEBUG
 NS_IMETHODIMP
 nsBCTableCellFrame::GetFrameName(nsAString& aResult) const
 {
   return MakeFrameName(NS_LITERAL_STRING("BCTableCell"), aResult);
 }
 #endif
 
--- a/layout/tables/nsTableCellFrame.h
+++ b/layout/tables/nsTableCellFrame.h
@@ -317,16 +317,17 @@ public:
 
   nsBCTableCellFrame(nsStyleContext* aContext);
 
   ~nsBCTableCellFrame();
 
   virtual nsIAtom* GetType() const;
 
   virtual nsMargin GetUsedBorder() const;
+  virtual PRBool GetBorderRadii(nscoord aRadii[8]) const;
 
   // Get the *inner half of the border only*, in twips.
   virtual nsMargin* GetBorderWidth(nsMargin& aBorder) const;
 
   // Get the *inner half of the border only*, in pixels.
   BCPixelSize GetBorderWidth(mozilla::css::Side aSide) const;
 
   // Set the full (both halves) width of the border
--- a/netwerk/cache/nsCacheEntry.cpp
+++ b/netwerk/cache/nsCacheEntry.cpp
@@ -57,16 +57,17 @@ nsCacheEntry::nsCacheEntry(nsCString *  
                            PRBool               streamBased,
                            nsCacheStoragePolicy storagePolicy)
     : mKey(key),
       mFetchCount(0),
       mLastFetched(0),
       mLastModified(0),
       mExpirationTime(nsICache::NO_EXPIRATION_TIME),
       mFlags(0),
+      mPredictedDataSize(-1),
       mDataSize(0),
       mCacheDevice(nsnull),
       mData(nsnull)
 {
     MOZ_COUNT_CTOR(nsCacheEntry);
     PR_INIT_CLIST(this);
     PR_INIT_CLIST(&mRequestQ);
     PR_INIT_CLIST(&mDescriptorQ);
--- a/netwerk/cache/nsCacheEntry.h
+++ b/netwerk/cache/nsCacheEntry.h
@@ -99,16 +99,19 @@ public:
     const char *    GetDeviceID();
 
     /**
      * Data accessors
      */
     nsISupports *Data()                           { return mData; }
     void         SetData( nsISupports * data);
 
+    PRInt64  PredictedDataSize()                  { return mPredictedDataSize; }
+    void     SetPredictedDataSize(PRInt64 size)   { mPredictedDataSize = size; }
+
     PRUint32 DataSize()                           { return mDataSize; }
     void     SetDataSize( PRUint32  size)         { mDataSize = size; }
 
     void     TouchData();
     
     /**
      * Meta data accessors
      */
@@ -234,16 +237,17 @@ private:
 
     nsCString *             mKey;            // 4  // XXX ask scc about const'ness
     PRUint32                mFetchCount;     // 4
     PRUint32                mLastFetched;    // 4
     PRUint32                mLastModified;   // 4
     PRUint32                mLastValidated;  // 4
     PRUint32                mExpirationTime; // 4
     PRUint32                mFlags;          // 4
+    PRInt64                 mPredictedDataSize;  // Size given by ContentLength.
     PRUint32                mDataSize;       // 4
     nsCacheDevice *         mCacheDevice;    // 4
     nsCOMPtr<nsISupports>   mSecurityInfo;   // 
     nsISupports *           mData;           // strong ref
     nsCOMPtr<nsIThread>     mThread;
     nsCacheMetaData         mMetaData;       // 4
     PRCList                 mRequestQ;       // 8
     PRCList                 mDescriptorQ;    // 8
--- a/netwerk/cache/nsCacheEntryDescriptor.cpp
+++ b/netwerk/cache/nsCacheEntryDescriptor.cpp
@@ -181,16 +181,35 @@ NS_IMETHODIMP nsCacheEntryDescriptor::Is
     NS_ENSURE_ARG_POINTER(result);
     nsCacheServiceAutoLock lock;
     if (!mCacheEntry)  return NS_ERROR_NOT_AVAILABLE;
 
     *result = mCacheEntry->IsStreamData();
     return NS_OK;
 }
 
+NS_IMETHODIMP nsCacheEntryDescriptor::GetPredictedDataSize(PRInt64 *result)
+{
+    NS_ENSURE_ARG_POINTER(result);
+    nsCacheServiceAutoLock lock;
+    if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+    *result = mCacheEntry->PredictedDataSize();
+    return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::SetPredictedDataSize(PRInt64
+                                                           predictedSize)
+{
+    nsCacheServiceAutoLock lock;
+    if (!mCacheEntry)  return NS_ERROR_NOT_AVAILABLE;
+
+    mCacheEntry->SetPredictedDataSize(predictedSize);
+    return NS_OK;
+}
 
 NS_IMETHODIMP nsCacheEntryDescriptor::GetDataSize(PRUint32 *result)
 {
     NS_ENSURE_ARG_POINTER(result);
     nsCacheServiceAutoLock lock;
     if (!mCacheEntry)  return NS_ERROR_NOT_AVAILABLE;
 
     *result = mCacheEntry->DataSize();
--- a/netwerk/cache/nsCacheService.cpp
+++ b/netwerk/cache/nsCacheService.cpp
@@ -1476,39 +1476,56 @@ nsCacheService::SearchCacheDevices(nsCSt
 
 
 nsCacheDevice *
 nsCacheService::EnsureEntryHasDevice(nsCacheEntry * entry)
 {
     nsCacheDevice * device = entry->CacheDevice();
     if (device)  return device;
 
+    PRInt64 predictedDataSize = entry->PredictedDataSize();
 #ifdef NECKO_DISK_CACHE
     if (entry->IsStreamData() && entry->IsAllowedOnDisk() && mEnableDiskDevice) {
         // this is the default
         if (!mDiskDevice) {
             (void)CreateDiskDevice();  // ignore the error (check for mDiskDevice instead)
         }
 
         if (mDiskDevice) {
+            // Bypass the cache if Content-Length says the entry will be too big
+            if (predictedDataSize != -1 &&
+                mDiskDevice->EntryIsTooBig(predictedDataSize)) {
+                nsresult rv = nsCacheService::DoomEntry(entry);
+                NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
+                return nsnull;
+            }
+
             entry->MarkBinding();  // enter state of binding
             nsresult rv = mDiskDevice->BindEntry(entry);
             entry->ClearBinding(); // exit state of binding
             if (NS_SUCCEEDED(rv))
                 device = mDiskDevice;
         }
     }
 #endif // !NECKO_DISK_CACHE
-     
+
     // if we can't use mDiskDevice, try mMemoryDevice
     if (!device && mEnableMemoryDevice && entry->IsAllowedInMemory()) {        
         if (!mMemoryDevice) {
             (void)CreateMemoryDevice();  // ignore the error (check for mMemoryDevice instead)
         }
         if (mMemoryDevice) {
+            // Bypass the cache if Content-Length says entry will be too big
+            if (predictedDataSize != -1 &&
+                mMemoryDevice->EntryIsTooBig(predictedDataSize)) {
+                nsresult rv = nsCacheService::DoomEntry(entry);
+                NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
+                return nsnull;
+            }
+
             entry->MarkBinding();  // enter state of binding
             nsresult rv = mMemoryDevice->BindEntry(entry);
             entry->ClearBinding(); // exit state of binding
             if (NS_SUCCEEDED(rv))
                 device = mMemoryDevice;
         }
     }
 
--- a/netwerk/cache/nsDiskCacheDevice.cpp
+++ b/netwerk/cache/nsDiskCacheDevice.cpp
@@ -770,18 +770,18 @@ nsDiskCacheDevice::OnDataSizeChange(nsCa
     if (!binding)  return NS_ERROR_UNEXPECTED;
 
     NS_ASSERTION(binding->mRecord.ValidRecord(), "bad record");
 
     PRUint32  newSize = entry->DataSize() + deltaSize;
     PRUint32  newSizeK =  ((newSize + 0x3FF) >> 10);
 
     // If the new size is larger than max. file size or larger than
-    // half the cache capacity (which is in KiB's), doom the entry and abort
-    if ((newSize > kMaxDataFileSize) || (newSizeK > mCacheCapacity/2)) {
+    // 1/8 the cache capacity (which is in KiB's), doom the entry and abort
+    if (EntryIsTooBig(newSize)) {
 #ifdef DEBUG
         nsresult rv =
 #endif
             nsCacheService::DoomEntry(entry);
         NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
         return NS_ERROR_ABORT;
     }
 
@@ -854,16 +854,23 @@ nsDiskCacheDevice::Visit(nsICacheVisitor
     if (keepGoing) {
         EntryInfoVisitor  infoVisitor(&mCacheMap, visitor);
         return mCacheMap.VisitRecords(&infoVisitor);
     }
 
     return NS_OK;
 }
 
+// Max allowed size for an entry is currently MIN(5MB, 1/8 CacheCapacity)
+bool
+nsDiskCacheDevice::EntryIsTooBig(PRInt64 entrySize)
+{
+    return entrySize > kMaxDataFileSize
+           || entrySize > (static_cast<PRInt64>(mCacheCapacity) * 1024 / 8);
+}
 
 nsresult
 nsDiskCacheDevice::EvictEntries(const char * clientID)
 {
     CACHE_LOG_DEBUG(("CACHE: disk EvictEntries [%s]\n", clientID));
 
     if (!Initialized())  return NS_ERROR_NOT_INITIALIZED;
     nsresult  rv;
--- a/netwerk/cache/nsDiskCacheDevice.h
+++ b/netwerk/cache/nsDiskCacheDevice.h
@@ -82,16 +82,18 @@ public:
                                             nsIFile **        result);
 
     virtual nsresult        OnDataSizeChange(nsCacheEntry * entry, PRInt32 deltaSize);
     
     virtual nsresult        Visit(nsICacheVisitor * visitor);
 
     virtual nsresult        EvictEntries(const char * clientID);
 
+    bool                    EntryIsTooBig(PRInt64 entrySize);
+
     /**
      * Preference accessors
      */
     void                    SetCacheParentDirectory(nsILocalFile * parentDir);
     void                    SetCapacity(PRUint32  capacity);
 
 /* private: */
 
--- a/netwerk/cache/nsDiskCacheDeviceSQL.h
+++ b/netwerk/cache/nsDiskCacheDeviceSQL.h
@@ -129,17 +129,16 @@ public:
                                           nsIFile **        result);
 
   virtual nsresult        OnDataSizeChange(nsCacheEntry * entry, PRInt32 deltaSize);
   
   virtual nsresult        Visit(nsICacheVisitor * visitor);
 
   virtual nsresult        EvictEntries(const char * clientID);
 
-
   /* Entry ownership */
   nsresult                GetOwnerDomains(const char *        clientID,
                                           PRUint32 *          count,
                                           char ***            domains);
   nsresult                GetOwnerURIs(const char *           clientID,
                                        const nsACString &     ownerDomain,
                                        PRUint32 *             count,
                                        char ***               uris);
--- a/netwerk/cache/nsDiskCacheMap.h
+++ b/netwerk/cache/nsDiskCacheMap.h
@@ -85,17 +85,18 @@ struct nsDiskCacheEntry;
  *****************************************************************************/
 
 #define BLOCK_SIZE_FOR_INDEX(index)  ((index) ? (256 << (2 * ((index) - 1))) : 0)
 
 // Min and max values for the number of records in the DiskCachemap
 #define kMinRecordCount    512
 
 #define kSeparateFile      0
-#define kMaxDataFileSize   0x3FFFC00   // 65535 KiB (see bug #443067)
+// #define must always  be <= 65535KB, or overflow. See bug 443067 Comment 8
+#define kMaxDataFileSize   5 * 1024 * 1024  // 5 MB (in bytes) 
 #define kBuckets           (1 << 5)    // must be a power of 2!
 
 class nsDiskCacheRecord {
 
 private:
     PRUint32    mHashNumber;
     PRUint32    mEvictionRank;
     PRUint32    mDataLocation;
--- a/netwerk/cache/nsICacheEntryDescriptor.idl
+++ b/netwerk/cache/nsICacheEntryDescriptor.idl
@@ -98,17 +98,25 @@ interface nsICacheEntryDescriptor : nsIC
     nsIOutputStream openOutputStream(in unsigned long offset);
 
     /**
      * Get/set the cache data element.  This will fail if the cache entry
      * IS stream based.  The cache entry holds a strong reference to this
      * object.  The object will be released when the cache entry is destroyed.
      */
     attribute nsISupports cacheElement;
-     
+    
+    /**
+      * Stores the Content-Length specified in the HTTP header for this
+      * entry. Checked before we write to the cache entry, to prevent ever
+      * taking up space in the cache for an entry that we know up front 
+      * is going to have to be evicted anyway. See bug 588507.
+      */
+    attribute PRInt64    predictedDataSize;
+
     /**
      * Get the access granted to this descriptor.  See nsICache.idl for the
      * definitions of the access modes and a thorough description of their
      * corresponding meanings.
      */
     readonly attribute nsCacheAccessMode accessGranted;
     
     /**
--- a/netwerk/cache/nsMemoryCacheDevice.cpp
+++ b/netwerk/cache/nsMemoryCacheDevice.cpp
@@ -289,24 +289,30 @@ nsMemoryCacheDevice::OpenOutputStreamFor
 
 nsresult
 nsMemoryCacheDevice::GetFileForEntry( nsCacheEntry *    entry,
                                       nsIFile **        result )
 {
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
+bool
+nsMemoryCacheDevice::EntryIsTooBig(PRInt64 entrySize)
+{
+    return entrySize > mSoftLimit;
+}
+
 
 nsresult
 nsMemoryCacheDevice::OnDataSizeChange( nsCacheEntry * entry, PRInt32 deltaSize)
 {
     if (entry->IsStreamData()) {
         // we have the right to refuse or pre-evict
         PRUint32  newSize = entry->DataSize() + deltaSize;
-        if ((PRInt32) newSize > mSoftLimit) {
+        if (EntryIsTooBig(newSize)) {
 #ifdef DEBUG
             nsresult rv =
 #endif
                 nsCacheService::DoomEntry(entry);
             NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
             return NS_ERROR_ABORT;
         }
     }
--- a/netwerk/cache/nsMemoryCacheDevice.h
+++ b/netwerk/cache/nsMemoryCacheDevice.h
@@ -83,17 +83,18 @@ public:
 
     virtual nsresult OnDataSizeChange( nsCacheEntry * entry, PRInt32 deltaSize );
 
     virtual nsresult Visit( nsICacheVisitor * visitor );
 
     virtual nsresult EvictEntries(const char * clientID);
     
     void             SetCapacity(PRInt32  capacity);
- 
+
+    bool             EntryIsTooBig(PRInt64 entrySize);
 private:
     friend class nsMemoryCacheDeviceInfo;
     enum      { DELETE_ENTRY        = PR_TRUE,
                 DO_NOT_DELETE_ENTRY = PR_FALSE };
 
     void      AdjustMemoryLimits( PRInt32  softLimit, PRInt32  hardLimit);
     void      EvictEntry( nsCacheEntry * entry , PRBool deleteEntry);
     void      EvictEntriesIfNecessary();
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -111,17 +111,17 @@ static const char kOldCookieFileName[] =
 #define LIMIT(x, low, high, default) ((x) >= (low) && (x) <= (high) ? (x) : (default))
 
 #undef  ADD_TEN_PERCENT
 #define ADD_TEN_PERCENT(i) ((i) + (i)/10)
 
 // default limits for the cookie list. these can be tuned by the
 // network.cookie.maxNumber and network.cookie.maxPerHost prefs respectively.
 static const PRUint32 kMaxNumberOfCookies = 3000;
-static const PRUint32 kMaxCookiesPerHost  = 50;
+static const PRUint32 kMaxCookiesPerHost  = 150;
 static const PRUint32 kMaxBytesPerCookie  = 4096;
 static const PRUint32 kMaxBytesPerPath    = 1024;
 
 // behavior pref constants
 static const PRUint32 BEHAVIOR_ACCEPT        = 0;
 static const PRUint32 BEHAVIOR_REJECTFOREIGN = 1;
 static const PRUint32 BEHAVIOR_REJECT        = 2;
 
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -128,17 +128,17 @@ HttpChannelParent::RecvAsyncOpen(const I
   if (NS_FAILED(rv))
     return SendCancelEarly(rv);
 
   rv = NS_NewChannel(getter_AddRefs(mChannel), uri, ios, nsnull, nsnull, loadFlags);
   if (NS_FAILED(rv))
     return SendCancelEarly(rv);
 
   nsHttpChannel *httpChan = static_cast<nsHttpChannel *>(mChannel.get());
-  httpChan->SetRemoteChannel(true);
+  httpChan->SetServicingRemoteChannel(PR_TRUE);
 
   if (doResumeAt)
     httpChan->ResumeAt(startPos, entityID);
 
   if (originalUri)
     httpChan->SetOriginalURI(originalUri);
   if (docUri)
     httpChan->SetDocumentURI(docUri);
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -750,20 +750,30 @@ nsHttpChannel::CallOnStartRequest()
                 }
             }
         }
     }
 
     if (mResponseHead && mResponseHead->ContentCharset().IsEmpty())
         mResponseHead->SetContentCharset(mContentCharsetHint);
 
-    if (mResponseHead)
+    if (mResponseHead) {
         SetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH,
                            mResponseHead->ContentLength());
-
+        // If we have a cache entry, set its predicted size to ContentLength to
+        // avoid caching an entry that will exceed the max size limit.
+        if (mCacheEntry) {
+            nsresult rv;
+            PRInt64 predictedDataSize = -1; // -1 in case GetAsInt64 fails.
+            GetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH, 
+                               &predictedDataSize);
+            rv = mCacheEntry->SetPredictedDataSize(predictedDataSize);
+            if (NS_FAILED(rv)) return rv;
+        }
+    }
     // Allow consumers to override our content type
     if ((mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) &&
         gIOService->GetContentSniffers().Count() != 0) {
         // NOTE: We can have both a txn pump and a cache pump when the cache
         // content is partial. In that case, we need to read from the cache,
         // because that's the one that has the initial contents. If that fails
         // then give the transaction pump a shot.
 
@@ -3057,35 +3067,37 @@ nsHttpChannel::SetupReplacementChannel(n
     nsresult rv = HttpBaseChannel::SetupReplacementChannel(newURI, newChannel, preserveMethod);
     if (NS_FAILED(rv))
         return rv;
 
     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
     if (!httpChannel)
         return NS_OK; // no other options to set
 
-    // transfer the remote flag
-    nsHttpChannel *httpChannelImpl = static_cast<nsHttpChannel*>(httpChannel.get());
-    httpChannelImpl->SetRemoteChannel(mRemoteChannel);
-
     // convey the mApplyConversion flag (bug 91862)
     nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
     if (encodedChannel)
         encodedChannel->SetApplyConversion(mApplyConversion);
 
     // transfer the resume information
     if (mResuming) {
         nsCOMPtr<nsIResumableChannel> resumableChannel(do_QueryInterface(newChannel));
         if (!resumableChannel) {
             NS_WARNING("Got asked to resume, but redirected to non-resumable channel!");
             return NS_ERROR_NOT_RESUMABLE;
         }
         resumableChannel->ResumeAt(mStartPos, mEntityID);
     }
 
+    // transfer the remote flag
+    nsCOMPtr<nsIHttpChannelParentInternal> httpInternal = 
+        do_QueryInterface(newChannel);
+    if (httpInternal)
+        httpInternal->SetServicingRemoteChannel(mRemoteChannel);
+
     return NS_OK;
 }
 
 nsresult
 nsHttpChannel::AsyncProcessRedirection(PRUint32 redirectType)
 {
     LOG(("nsHttpChannel::AsyncProcessRedirection [this=%p type=%u]\n",
         this, redirectType));
@@ -3526,16 +3538,33 @@ nsHttpChannel::SetupFallbackChannel(cons
 {
     LOG(("nsHttpChannel::SetupFallbackChannel [this=%x, key=%s]",
          this, aFallbackKey));
     mFallbackChannel = PR_TRUE;
     mFallbackKey = aFallbackKey;
 
     return NS_OK;
 }
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIHttpChannelParentInternal
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetServicingRemoteChannel(PRBool *value)
+{
+    *value = mRemoteChannel;
+    return NS_OK;
+}
+NS_IMETHODIMP
+nsHttpChannel::SetServicingRemoteChannel(PRBool value)
+{
+    mRemoteChannel = value;
+    return NS_OK;
+}
 //-----------------------------------------------------------------------------
 // nsHttpChannel::nsIEncodedChannel
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsHttpChannel::GetApplyConversion(PRBool *value)
 {
     NS_ENSURE_ARG_POINTER(value);
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -81,32 +81,34 @@ class nsHttpChannel : public HttpBaseCha
                     , public nsICacheListener
                     , public nsIEncodedChannel
                     , public nsITransportEventSink
                     , public nsIProtocolProxyCallback
                     , public nsIHttpAuthenticableChannel
                     , public nsITraceableChannel
                     , public nsIApplicationCacheChannel
                     , public nsIAsyncVerifyRedirectCallback
+                    , public nsIHttpChannelParentInternal
 {
 public:
     NS_DECL_ISUPPORTS_INHERITED
     NS_DECL_NSIREQUESTOBSERVER
     NS_DECL_NSISTREAMLISTENER
     NS_DECL_NSICACHEINFOCHANNEL
     NS_DECL_NSICACHINGCHANNEL
     NS_DECL_NSICACHELISTENER
     NS_DECL_NSIENCODEDCHANNEL
     NS_DECL_NSITRANSPORTEVENTSINK
     NS_DECL_NSIPROTOCOLPROXYCALLBACK
     NS_DECL_NSIPROXIEDCHANNEL
     NS_DECL_NSITRACEABLECHANNEL
     NS_DECL_NSIAPPLICATIONCACHECONTAINER
     NS_DECL_NSIAPPLICATIONCACHECHANNEL
     NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+    NS_DECL_NSIHTTPCHANNELPARENTINTERNAL
 
     // nsIHttpAuthenticableChannel. We can't use
     // NS_DECL_NSIHTTPAUTHENTICABLECHANNEL because it duplicates cancel() and
     // others.
     NS_IMETHOD GetIsSSL(PRBool *aIsSSL);
     NS_IMETHOD GetProxyMethodIsConnect(PRBool *aProxyMethodIsConnect);
     NS_IMETHOD GetServerResponseHeader(nsACString & aServerResponseHeader);
     NS_IMETHOD GetProxyChallenges(nsACString & aChallenges);
@@ -143,17 +145,16 @@ public:
     // nsISupportsPriority
     NS_IMETHOD SetPriority(PRInt32 value);
     // nsIResumableChannel
     NS_IMETHOD ResumeAt(PRUint64 startPos, const nsACString& entityID);
 
 public: /* internal necko use only */ 
     typedef void (nsHttpChannel:: *nsAsyncCallback)(void);
     nsHttpResponseHead * GetResponseHead() const { return mResponseHead; }
-    void SetRemoteChannel(bool aRemote) { mRemoteChannel = aRemote; }
     void InternalSetUploadStream(nsIInputStream *uploadStream) 
       { mUploadStream = uploadStream; }
     void SetUploadStreamHasHeaders(PRBool hasHeaders) 
       { mUploadStreamHasHeaders = hasHeaders; }
 
     nsresult SetReferrerInternal(nsIURI *referrer) {
         nsCAutoString spec;
         nsresult rv = referrer->GetAsciiSpec(spec);
--- a/netwerk/protocol/http/nsIHttpChannelInternal.idl
+++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl
@@ -84,8 +84,18 @@ interface nsIHttpChannelInternal : nsISu
      */
     attribute boolean forceAllowThirdPartyCookie;
 
     /**
      * Returns true iff the channel has been canceled.
      */
     readonly attribute boolean canceled;
 };
+
+[uuid(b18290f1-ff34-4e33-93a2-29aeb7b95425)]
+interface nsIHttpChannelParentInternal : nsISupports
+{
+    /**
+     * True for a chrome channel that is servicing a child channel (i.e. request
+     * originated in child process, not chrome).
+     */
+    [noscript] attribute boolean servicingRemoteChannel;
+};
--- a/netwerk/test/TestCookie.cpp
+++ b/netwerk/test/TestCookie.cpp
@@ -56,16 +56,17 @@ static NS_DEFINE_CID(kPrefServiceCID,   
 
 // various pref strings
 static const char kCookiesPermissions[] = "network.cookie.cookieBehavior";
 static const char kCookiesLifetimeEnabled[] = "network.cookie.lifetime.enabled";
 static const char kCookiesLifetimeDays[] = "network.cookie.lifetime.days";
 static const char kCookiesLifetimeCurrentSession[] = "network.cookie.lifetime.behavior";
 static const char kCookiesP3PString[] = "network.cookie.p3p";
 static const char kCookiesAskPermission[] = "network.cookie.warnAboutCookies";
+static const char kCookiesMaxPerHost[] = "network.cookie.maxPerHost";
 
 static char *sBuffer;
 
 nsresult
 SetACookie(nsICookieService *aCookieService, const char *aSpec1, const char *aSpec2, const char* aCookieString, const char *aServerTime)
 {
     nsCOMPtr<nsIURI> uri1, uri2;
     NS_NewURI(getter_AddRefs(uri1), aSpec1);
@@ -210,16 +211,18 @@ InitPrefs(nsIPrefBranch *aPrefBranch)
     // init some relevant prefs, so the tests don't go awry.
     // we use the most restrictive set of prefs we can;
     // however, we don't test third party blocking here.
     aPrefBranch->SetIntPref(kCookiesPermissions, 0); // accept all
     aPrefBranch->SetBoolPref(kCookiesLifetimeEnabled, PR_TRUE);
     aPrefBranch->SetIntPref(kCookiesLifetimeCurrentSession, 0);
     aPrefBranch->SetIntPref(kCookiesLifetimeDays, 1);
     aPrefBranch->SetBoolPref(kCookiesAskPermission, PR_FALSE);
+    // Set the base domain limit to 50 so we have a known value.
+    aPrefBranch->SetIntPref(kCookiesMaxPerHost, 50);
 }
 
 class ScopedXPCOM
 {
 public:
   ScopedXPCOM() : rv(NS_InitXPCOM2(nsnull, nsnull, nsnull)) { }
   ~ScopedXPCOM()
   {
--- a/toolkit/components/places/src/nsNavHistory.cpp
+++ b/toolkit/components/places/src/nsNavHistory.cpp
@@ -219,16 +219,47 @@ NS_IMPL_CI_INTERFACE_GETTER5(
 namespace {
 
 static PRInt64 GetSimpleBookmarksQueryFolder(
     const nsCOMArray<nsNavHistoryQuery>& aQueries,
     nsNavHistoryQueryOptions* aOptions);
 static void ParseSearchTermsFromQueries(const nsCOMArray<nsNavHistoryQuery>& aQueries,
                                         nsTArray<nsTArray<nsString>*>* aTerms);
 
+class VacuumDBListener : public AsyncStatementCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  VacuumDBListener(nsIPrefBranch* aBranch)
+    : mPrefBranch(aBranch)
+  {
+  }
+
+  NS_IMETHOD HandleResult(mozIStorageResultSet*)
+  {
+    // 'PRAGMA journal_mode' statements always return a result.  Ignore it.
+    return NS_OK;
+  }
+
+  NS_IMETHOD HandleCompletion(PRUint16 aReason)
+  {
+    if (aReason == REASON_FINISHED && mPrefBranch) {
+      (void)mPrefBranch->SetIntPref(PREF_LAST_VACUUM,
+                                    (PRInt32)(PR_Now() / PR_USEC_PER_SEC));
+    }
+    return NS_OK;
+  }
+
+private:
+  nsCOMPtr<nsIPrefBranch> mPrefBranch;
+};
+
+NS_IMPL_ISUPPORTS1(VacuumDBListener, mozIStorageStatementCallback)
+
 } // anonymous namespace
 
 namespace mozilla {
   namespace places {
 
     bool hasRecentCorruptDB()
     {
       nsCOMPtr<nsIFile> profDir;
@@ -5921,24 +5952,21 @@ nsNavHistory::VacuumDatabase()
     NS_ENSURE_SUCCESS(rv, rv);
 
     mozIStorageBaseStatement *stmts[] = {
       journalToMemory,
       vacuum,
       journalToDefault
     };
     nsCOMPtr<mozIStoragePendingStatement> ps;
-    rv = mDBConn->ExecuteAsync(stmts, NS_ARRAY_LENGTH(stmts), nsnull,
-                               getter_AddRefs(ps));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    if (mPrefBranch) {
-      (void)mPrefBranch->SetIntPref(PREF_LAST_VACUUM,
-                                    (PRInt32)(PR_Now() / PR_USEC_PER_SEC));
-    }
+    nsRefPtr<VacuumDBListener> vacuumDBListener =
+      new VacuumDBListener(mPrefBranch);
+    rv = mDBConn->ExecuteAsync(stmts, NS_ARRAY_LENGTH(stmts),
+                               vacuumDBListener, getter_AddRefs(ps));
+    NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 
 nsresult
 nsNavHistory::DecayFrecency()
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -507,16 +507,29 @@ function getExpectedEvent(aId) {
   if (gExpectedEvents[aId].length == 0)
     do_throw("Too many events for " + aId);
   let event = gExpectedEvents[aId].shift();
   if (event instanceof Array)
     return event;
   return [event, true];
 }
 
+function getExpectedInstall(aAddon) {
+  if (gExpectedInstalls instanceof Array)
+    return gExpectedInstalls.shift();
+  if (!aAddon || !aAddon.id)
+    return gExpectedInstalls["NO_ID"].shift();
+  let id = aAddon.id;
+  if (!(id in gExpectedInstalls) || !(gExpectedInstalls[id] instanceof Array))
+    do_throw("Wasn't expecting events for " + id);
+  if (gExpectedInstalls[id].length == 0)
+    do_throw("Too many events for " + id);
+  return gExpectedInstalls[id].shift();
+}
+
 const AddonListener = {
   onPropertyChanged: function(aAddon, aProperties) {
     let [event, properties] = getExpectedEvent(aAddon.id);
     do_check_eq("onPropertyChanged", event);
     do_check_eq(aProperties.length, properties.length);
     properties.forEach(function(aProperty) {
       // Only test that the expected properties are listed, having additional
       // properties listed is not necessary a problem
@@ -598,76 +611,76 @@ const AddonListener = {
 };
 
 const InstallListener = {
   onNewInstall: function(install) {
     if (install.state != AddonManager.STATE_DOWNLOADED &&
         install.state != AddonManager.STATE_AVAILABLE)
       do_throw("Bad install state " + install.state);
     do_check_eq(install.error, 0);
-    do_check_eq("onNewInstall", gExpectedInstalls.shift());
+    do_check_eq("onNewInstall", getExpectedInstall());
     return check_test_completed(arguments);
   },
 
   onDownloadStarted: function(install) {
     do_check_eq(install.state, AddonManager.STATE_DOWNLOADING);
     do_check_eq(install.error, 0);
-    do_check_eq("onDownloadStarted", gExpectedInstalls.shift());
+    do_check_eq("onDownloadStarted", getExpectedInstall());
     return check_test_completed(arguments);
   },
 
   onDownloadEnded: function(install) {
     do_check_eq(install.state, AddonManager.STATE_DOWNLOADED);
     do_check_eq(install.error, 0);
-    do_check_eq("onDownloadEnded", gExpectedInstalls.shift());
+    do_check_eq("onDownloadEnded", getExpectedInstall());
     return check_test_completed(arguments);
   },
 
   onDownloadFailed: function(install) {
     do_check_eq(install.state, AddonManager.STATE_DOWNLOAD_FAILED);
-    do_check_eq("onDownloadFailed", gExpectedInstalls.shift());
+    do_check_eq("onDownloadFailed", getExpectedInstall());
     return check_test_completed(arguments);
   },
 
   onDownloadCancelled: function(install) {
     do_check_eq(install.state, AddonManager.STATE_CANCELLED);
     do_check_eq(install.error, 0);
-    do_check_eq("onDownloadCancelled", gExpectedInstalls.shift());
+    do_check_eq("onDownloadCancelled", getExpectedInstall());
     return check_test_completed(arguments);
   },
 
   onInstallStarted: function(install) {
     do_check_eq(install.state, AddonManager.STATE_INSTALLING);
     do_check_eq(install.error, 0);
-    do_check_eq("onInstallStarted", gExpectedInstalls.shift());
+    do_check_eq("onInstallStarted", getExpectedInstall(install.addon));
     return check_test_completed(arguments);
   },
 
   onInstallEnded: function(install, newAddon) {
     do_check_eq(install.state, AddonManager.STATE_INSTALLED);
     do_check_eq(install.error, 0);
-    do_check_eq("onInstallEnded", gExpectedInstalls.shift());
+    do_check_eq("onInstallEnded", getExpectedInstall(install.addon));
     return check_test_completed(arguments);
   },
 
   onInstallFailed: function(install) {
     do_check_eq(install.state, AddonManager.STATE_INSTALL_FAILED);
-    do_check_eq("onInstallFailed", gExpectedInstalls.shift());
+    do_check_eq("onInstallFailed", getExpectedInstall(install.addon));
     return check_test_completed(arguments);
   },
 
   onInstallCancelled: function(install) {
     do_check_eq(install.state, AddonManager.STATE_CANCELLED);
     do_check_eq(install.error, 0);
-    do_check_eq("onInstallCancelled", gExpectedInstalls.shift());
+    do_check_eq("onInstallCancelled", getExpectedInstall(install.addon));
     return check_test_completed(arguments);
   },
 
   onExternalInstall: function(aAddon, existingAddon, aRequiresRestart) {
-    do_check_eq("onExternalInstall", gExpectedInstalls.shift());
+    do_check_eq("onExternalInstall", getExpectedInstall(aAddon));
     do_check_false(aRequiresRestart);
     return check_test_completed(arguments);
   }
 };
 
 function hasFlag(aBits, aFlag) {
   return (aBits & aFlag) != 0;
 }
@@ -682,18 +695,23 @@ function prepare_test(aExpectedEvents, a
   gNext = aNext;
 }
 
 // Checks if all expected events have been seen and if so calls the callback
 function check_test_completed(aArgs) {
   if (!gNext)
     return;
 
-  if (gExpectedInstalls.length > 0)
+  if (gExpectedInstalls instanceof Array &&
+      gExpectedInstalls.length > 0)
     return;
+  else for each (let installList in gExpectedInstalls) {
+    if (installList.length > 0)
+      return;
+  }
 
   for (let id in gExpectedEvents) {
     if (gExpectedEvents[id].length > 0)
       return;
   }
 
   return gNext.apply(null, aArgs);
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_install.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_install.js
@@ -659,26 +659,34 @@ function run_test_11() {
         ],
         "addon6@tests.mozilla.org": [
           ["onInstalling", false],
           "onInstalled"
         ],
         "addon7@tests.mozilla.org": [
           "onInstalling"
         ]
-      }, [
-        "onInstallStarted",
-        "onInstallEnded",
-        "onInstallStarted",
-        "onInstallEnded",
-        "onInstallStarted",
-        "onInstallEnded",
-        "onInstallStarted",
-        "onInstallEnded"
-      ], check_test_11);
+      }, {
+        "addon4@tests.mozilla.org": [
+          "onInstallStarted",
+          "onInstallEnded"
+        ],
+        "addon5@tests.mozilla.org": [
+          "onInstallStarted",
+          "onInstallEnded"
+        ],
+        "addon6@tests.mozilla.org": [
+          "onInstallStarted",
+          "onInstallEnded"
+        ],
+        "addon7@tests.mozilla.org": [
+          "onInstallStarted",
+          "onInstallEnded"
+        ]
+      }, check_test_11);
 
       installs[0].install();
       installs[1].install();
       installs[3].install();
 
       // Note that we install addon6 last. Since it doesn't need a restart to
       // install it completes asynchronously which would otherwise make the
       // onInstallStarted/onInstallEnded events go out of sequence unless this
@@ -736,31 +744,41 @@ function run_test_12() {
       ],
       "addon6@tests.mozilla.org": [
         ["onInstalling", false],
         "onInstalled"
       ],
       "addon7@tests.mozilla.org": [
         "onInstalling"
       ]
-    }, [
-      "onDownloadStarted",
-      "onNewInstall",
-      "onNewInstall",
-      "onNewInstall",
-      "onDownloadEnded",
-      "onInstallStarted",
-      "onInstallEnded",
-      "onInstallStarted",
-      "onInstallEnded",
-      "onInstallStarted",
-      "onInstallEnded",
-      "onInstallStarted",
-      "onInstallEnded"
-    ], check_test_12);
+    }, {
+      "NO_ID": [
+        "onDownloadStarted",
+        "onNewInstall",
+        "onNewInstall",
+        "onNewInstall",
+        "onDownloadEnded"
+      ],
+      "addon4@tests.mozilla.org": [
+        "onInstallStarted",
+        "onInstallEnded"
+      ],
+      "addon5@tests.mozilla.org": [
+        "onInstallStarted",
+        "onInstallEnded"
+      ],
+      "addon6@tests.mozilla.org": [
+        "onInstallStarted",
+        "onInstallEnded"
+      ],
+      "addon7@tests.mozilla.org": [
+        "onInstallStarted",
+        "onInstallEnded"
+      ]
+    }, check_test_12);
     install.install();
   }, "application/x-xpinstall", null, "Multi Test 4");
 }
 
 function check_test_12() {
   do_check_eq(gInstall.linkedInstalls.length, 3);
 
   // Might be in any order so sort them based on ID
--- a/toolkit/themes/pinstripe/global/button.css
+++ b/toolkit/themes/pinstripe/global/button.css
@@ -117,17 +117,17 @@ button[dlgtype="help"] {
 
 button[dlgtype="help"][disabled] {
   opacity: 0.5;
 }
 
 button[dlgtype="help"]:-moz-focusring {
   outline: 2px solid -moz-mac-focusring;
   outline-offset: -2px;
-  -moz-outline-radius: 100%;
+  -moz-outline-radius: 10000px;
 }
 
 button[dlgtype="help"] > .button-box > .button-icon {
   list-style-image: url("chrome://global/skin/icons/question-mark.png");
   -moz-image-region: rect(0 24px 24px 0);
   padding: 0;
   margin: 0;
 }
--- a/xpcom/io/nsInputStreamTee.cpp
+++ b/xpcom/io/nsInputStreamTee.cpp
@@ -41,85 +41,101 @@
 
 #include "nsIInputStreamTee.h"
 #include "nsIInputStream.h"
 #include "nsIOutputStream.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsIEventTarget.h"
 #include "nsThreadUtils.h"
+#include <prlock.h>
+#include "nsAutoLock.h"
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo* gInputStreamTeeLog = PR_NewLogModule("nsInputStreamTee");
 #define LOG(args) PR_LOG(gInputStreamTeeLog, PR_LOG_DEBUG, args)
 #else
 #define LOG(args)
 #endif
 
 class nsInputStreamTee : public nsIInputStreamTee
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIINPUTSTREAM
     NS_DECL_NSIINPUTSTREAMTEE
 
     nsInputStreamTee();
+    bool SinkIsValid();
+    void InvalidateSink();
 
 private:
-    ~nsInputStreamTee() {}
+    ~nsInputStreamTee() { if (mLock) PR_DestroyLock(mLock); }
 
     nsresult TeeSegment(const char *buf, PRUint32 count);
-
+    
     static NS_METHOD WriteSegmentFun(nsIInputStream *, void *, const char *,
                                      PRUint32, PRUint32, PRUint32 *);
 
 private:
     nsCOMPtr<nsIInputStream>  mSource;
     nsCOMPtr<nsIOutputStream> mSink;
     nsCOMPtr<nsIEventTarget>  mEventTarget;
     nsWriteSegmentFun         mWriter;  // for implementing ReadSegments
-    void                     *mClosure; // for implementing ReadSegments
+    void                      *mClosure; // for implementing ReadSegments
+    PRLock                    *mLock; // synchronize access to mSinkIsValid
+    bool                      mSinkIsValid; // False if TeeWriteEvent fails 
 };
 
 class nsInputStreamTeeWriteEvent : public nsRunnable {
 public:
+    // aTee's lock is held across construction of this object
     nsInputStreamTeeWriteEvent(const char *aBuf, PRUint32 aCount,
-                               nsIOutputStream *aSink)
+                               nsIOutputStream  *aSink, 
+                               nsInputStreamTee *aTee)
     {
         // copy the buffer - will be free'd by dtor
         mBuf = (char *)malloc(aCount);
         if (mBuf) memcpy(mBuf, (char *)aBuf, aCount);
         mCount = aCount;
         mSink = aSink;
         PRBool isNonBlocking;
         mSink->IsNonBlocking(&isNonBlocking);
         NS_ASSERTION(isNonBlocking == PR_FALSE, "mSink is nonblocking");
+        mTee = aTee;
     }
 
     NS_IMETHOD Run()
     {
         if (!mBuf) {
             NS_WARNING("nsInputStreamTeeWriteEvent::Run() "
-                    "memory not allocated\n");
+                       "memory not allocated\n");
             return NS_OK;
         }
         NS_ABORT_IF_FALSE(mSink, "mSink is null!");
 
+        //  The output stream could have been invalidated between when 
+        //  this event was dispatched and now, so check before writing.
+        if (!mTee->SinkIsValid()) {
+            return NS_OK; 
+        }
+
         LOG(("nsInputStreamTeeWriteEvent::Run() [%p]"
-                "will write %u bytes to %p\n",
-                this, mCount, mSink.get()));
+             "will write %u bytes to %p\n",
+              this, mCount, mSink.get()));
 
         PRUint32 totalBytesWritten = 0;
         while (mCount) {
             nsresult rv;
             PRUint32 bytesWritten = 0;
             rv = mSink->Write(mBuf + totalBytesWritten, mCount, &bytesWritten);
             if (NS_FAILED(rv)) {
                 LOG(("nsInputStreamTeeWriteEvent::Run[%p] error %x in writing",
                      this,rv));
+                mTee->InvalidateSink();
                 break;
             }
             totalBytesWritten += bytesWritten;
             NS_ASSERTION(bytesWritten <= mCount, "wrote too much");
             mCount -= bytesWritten;
         }
         return NS_OK;
     }
@@ -130,56 +146,76 @@ protected:
         if (mBuf) free(mBuf);
         mBuf = nsnull;
     }
     
 private:
     char *mBuf;
     PRUint32 mCount;
     nsCOMPtr<nsIOutputStream> mSink;
+    // back pointer to the tee that created this runnable
+    nsRefPtr<nsInputStreamTee> mTee;
 };
 
-nsInputStreamTee::nsInputStreamTee()
+nsInputStreamTee::nsInputStreamTee(): mLock(nsnull)
+                                    , mSinkIsValid(true)
 {
 }
 
+bool
+nsInputStreamTee::SinkIsValid()
+{
+    nsAutoLock lock(mLock); 
+    return mSinkIsValid; 
+}
+
+void
+nsInputStreamTee::InvalidateSink()
+{
+    nsAutoLock lock(mLock);
+    mSinkIsValid = false;
+}
+
 nsresult
 nsInputStreamTee::TeeSegment(const char *buf, PRUint32 count)
 {
-    if (!mSink)
-        return NS_OK; // nothing to do
-
-    if (mEventTarget) {
+    if (!mSink) return NS_OK; // nothing to do
+    if (mLock) { // asynchronous case
+        NS_ASSERTION(mEventTarget, "mEventTarget is null, mLock is not null.");
+        if (!SinkIsValid()) {
+            return NS_OK; // nothing to do
+        }
         nsRefPtr<nsIRunnable> event =
-            new nsInputStreamTeeWriteEvent(buf, count, mSink);
+            new nsInputStreamTeeWriteEvent(buf, count, mSink, this);
         NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
         LOG(("nsInputStreamTee::TeeSegment [%p] dispatching write %u bytes\n",
-                this, count));
+              this, count));
         return mEventTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+    } else { // synchronous case
+        NS_ASSERTION(!mEventTarget, "mEventTarget is not null, mLock is null.");
+        nsresult rv;
+        PRUint32 totalBytesWritten = 0;
+        while (count) {
+            PRUint32 bytesWritten = 0;
+            rv = mSink->Write(buf + totalBytesWritten, count, &bytesWritten);
+            if (NS_FAILED(rv)) {
+                // ok, this is not a fatal error... just drop our reference to mSink
+                // and continue on as if nothing happened.
+                NS_WARNING("Write failed (non-fatal)");
+                // catch possible misuse of the input stream tee
+                NS_ASSERTION(rv != NS_BASE_STREAM_WOULD_BLOCK, "sink must be a blocking stream");
+                mSink = 0;
+                break;
+            }
+            totalBytesWritten += bytesWritten;
+            NS_ASSERTION(bytesWritten <= count, "wrote too much");
+            count -= bytesWritten;
+        }
+        return NS_OK;
     }
-
-    nsresult rv;
-    PRUint32 totalBytesWritten = 0;
-    while (count) {
-        PRUint32 bytesWritten = 0;
-        rv = mSink->Write(buf + totalBytesWritten, count, &bytesWritten);
-        if (NS_FAILED(rv)) {
-            // ok, this is not a fatal error... just drop our reference to mSink
-            // and continue on as if nothing happened.
-            NS_WARNING("Write failed (non-fatal)");
-            // catch possible misuse of the input stream tee
-            NS_ASSERTION(rv != NS_BASE_STREAM_WOULD_BLOCK, "sink must be a blocking stream");
-            mSink = 0;
-            break;
-        }
-        totalBytesWritten += bytesWritten;
-        NS_ASSERTION(bytesWritten <= count, "wrote too much");
-        count -= bytesWritten;
-    }
-    return NS_OK;
 }
 
 NS_METHOD
 nsInputStreamTee::WriteSegmentFun(nsIInputStream *in, void *closure, const char *fromSegment,
                                   PRUint32 offset, PRUint32 count, PRUint32 *writeCount)
 {
     nsInputStreamTee *tee = reinterpret_cast<nsInputStreamTee *>(closure);
 
@@ -188,20 +224,19 @@ nsInputStreamTee::WriteSegmentFun(nsIInp
         NS_ASSERTION((NS_FAILED(rv) ? (*writeCount == 0) : PR_TRUE),
                 "writer returned an error with non-zero writeCount");
         return rv;
     }
 
     return tee->TeeSegment(fromSegment, *writeCount);
 }
 
-NS_IMPL_ISUPPORTS2(nsInputStreamTee,
-                   nsIInputStreamTee,
-                   nsIInputStream)
-
+NS_IMPL_THREADSAFE_ISUPPORTS2(nsInputStreamTee,
+                              nsIInputStreamTee,
+                              nsIInputStream)
 NS_IMETHODIMP
 nsInputStreamTee::Close()
 {
     NS_ENSURE_TRUE(mSource, NS_ERROR_NOT_INITIALIZED);
     nsresult rv = mSource->Close();
     mSource = 0;
     mSink = 0;
     return rv;
@@ -282,16 +317,24 @@ nsInputStreamTee::GetSink(nsIOutputStrea
     NS_IF_ADDREF(*sink = mSink);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsInputStreamTee::SetEventTarget(nsIEventTarget *anEventTarget)
 {
     mEventTarget = anEventTarget;
+    if (mEventTarget) {
+        // Only need synchronization if this is an async tee
+        mLock = PR_NewLock();
+        if (!mLock) {
+            NS_ERROR("Failed to allocate lock for nsInputStreamTee");
+            return NS_ERROR_OUT_OF_MEMORY;
+        }
+    }
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsInputStreamTee::GetEventTarget(nsIEventTarget **anEventTarget)
 {
     NS_IF_ADDREF(*anEventTarget = mEventTarget);
     return NS_OK;