Add support for calc() to the '{margin,padding}-{top,right,bottom,left}' and '-moz-margin-{start,end}' properties. (Bug 585715) r=bzbarsky a2.0=blocking:beta6+
authorL. David Baron <dbaron@dbaron.org>
Tue, 31 Aug 2010 12:05:12 -0400
changeset 51776 7bb992392d3ac38ff82766ea783f66eed0b091b1
parent 51775 253d994413d97700dbea2bd165146a81c2615812
child 51777 4744aeff506a2bd50ccf9f199fc008c5186a7c27
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersbzbarsky
bugs585715
milestone2.0b6pre
Add support for calc() to the '{margin,padding}-{top,right,bottom,left}' and '-moz-margin-{start,end}' properties. (Bug 585715) r=bzbarsky a2.0=blocking:beta6+
layout/generic/Makefile.in
layout/generic/nsAbsoluteContainingBlock.cpp
layout/generic/nsBlockFrame.cpp
layout/generic/nsContainerFrame.cpp
layout/generic/nsFrame.cpp
layout/generic/nsInlineFrame.cpp
layout/generic/nsLineLayout.cpp
layout/reftests/css-calc/margin-block-1-ref.html
layout/reftests/css-calc/margin-block-1.html
layout/reftests/css-calc/padding-block-1-ref.html
layout/reftests/css-calc/padding-block-1.html
layout/reftests/css-calc/reftest.list
layout/style/nsCSSParser.cpp
layout/style/nsRuleNode.cpp
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/test/property_database.js
--- a/layout/generic/Makefile.in
+++ b/layout/generic/Makefile.in
@@ -146,16 +146,17 @@ FORCE_STATIC_LIB = 1
 include $(topsrcdir)/config/config.mk
 include $(topsrcdir)/ipc/chromium/chromium-config.mk
 include $(topsrcdir)/config/rules.mk
 
 LOCAL_INCLUDES += \
 		-I$(srcdir) \
 		-I$(srcdir)/../base \
 		-I$(srcdir)/../forms \
+		-I$(srcdir)/../style \
 		-I$(srcdir)/../tables \
 		-I$(srcdir)/../xul/base/src \
 		-I$(srcdir)/../../content/xul/content/src \
 		-I$(srcdir)/../../content/base/src \
 		-I$(srcdir)/../../content/html/content/src \
 		-I$(srcdir)/../../dom/base \
 		$(MOZ_CAIRO_CFLAGS) \
 		$(NULL)
--- a/layout/generic/nsAbsoluteContainingBlock.cpp
+++ b/layout/generic/nsAbsoluteContainingBlock.cpp
@@ -215,25 +215,22 @@ nsAbsoluteContainingBlock::Reflow(nsCont
   // only overflow incomplete.
   if (NS_FRAME_IS_NOT_COMPLETE(reflowStatus))
     NS_FRAME_SET_OVERFLOW_INCOMPLETE(reflowStatus);
 
   NS_MergeReflowStatusInto(&aReflowStatus, reflowStatus);
   return NS_OK;
 }
 
-static inline PRBool IsFixedPaddingSize(nsStyleUnit aUnit) {
-  return aUnit == eStyleUnit_Coord;
-}
-static inline PRBool IsFixedMarginSize(nsStyleUnit aUnit) {
-  return aUnit == eStyleUnit_Coord;
-}
-static inline PRBool IsFixedOffset(const nsStyleCoord& aCoord) {
-  return aCoord.ConvertsToLength();
-}
+static inline bool IsFixedPaddingSize(const nsStyleCoord& aCoord)
+  { return aCoord.ConvertsToLength(); }
+static inline bool IsFixedMarginSize(const nsStyleCoord& aCoord)
+  { return aCoord.ConvertsToLength(); }
+static inline bool IsFixedOffset(const nsStyleCoord& aCoord)
+  { return aCoord.ConvertsToLength(); }
 
 PRBool
 nsAbsoluteContainingBlock::FrameDependsOnContainer(nsIFrame* f,
                                                    PRBool aCBWidthChanged,
                                                    PRBool aCBHeightChanged)
 {
   const nsStylePosition* pos = f->GetStylePosition();
   // See if f's position might have changed because it depends on a
@@ -264,26 +261,26 @@ nsAbsoluteContainingBlock::FrameDependsO
     // If border-left, border-right, padding-left, padding-right,
     // width, min-width, and max-width are all lengths, 'none', or enumerated,
     // then our frame width does not depend on the parent width.
     // Note that borders never depend on the parent width
     // XXX All of the enumerated values except -moz-available are ok too.
     if (pos->WidthDependsOnContainer() ||
         pos->MinWidthDependsOnContainer() ||
         pos->MaxWidthDependsOnContainer() ||
-        !IsFixedPaddingSize(padding->mPadding.GetLeftUnit()) ||
-        !IsFixedPaddingSize(padding->mPadding.GetRightUnit())) {
+        !IsFixedPaddingSize(padding->mPadding.GetLeft()) ||
+        !IsFixedPaddingSize(padding->mPadding.GetRight())) {
       return PR_TRUE;
     }
 
     // See if f's position might have changed. If we're RTL then the
     // rules are slightly different. We'll assume percentage or auto
     // margins will always induce a dependency on the size
-    if (!IsFixedMarginSize(margin->mMargin.GetLeftUnit()) ||
-        !IsFixedMarginSize(margin->mMargin.GetRightUnit())) {
+    if (!IsFixedMarginSize(margin->mMargin.GetLeft()) ||
+        !IsFixedMarginSize(margin->mMargin.GetRight())) {
       return PR_TRUE;
     }
     if (f->GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
       // Note that even if 'left' is a length, our position can
       // still depend on the containing block width, because if
       // 'right' is also a length we will discard 'left' and be
       // positioned relative to the containing block right edge.
       // 'left' length and 'right' auto is the only combination
@@ -306,24 +303,24 @@ nsAbsoluteContainingBlock::FrameDependsO
     // then our frame height does not depend on the parent height.
     // Note that borders never depend on the parent height
     if ((pos->HeightDependsOnContainer() &&
          !(pos->mHeight.GetUnit() == eStyleUnit_Auto &&
            pos->mOffset.GetBottomUnit() == eStyleUnit_Auto &&
            pos->mOffset.GetTopUnit() != eStyleUnit_Auto)) ||
         pos->MinHeightDependsOnContainer() ||
         pos->MaxHeightDependsOnContainer() ||
-        !IsFixedPaddingSize(padding->mPadding.GetTopUnit()) ||
-        !IsFixedPaddingSize(padding->mPadding.GetBottomUnit())) { 
+        !IsFixedPaddingSize(padding->mPadding.GetTop()) ||
+        !IsFixedPaddingSize(padding->mPadding.GetBottom())) { 
       return PR_TRUE;
     }
       
     // See if f's position might have changed.
-    if (!IsFixedMarginSize(margin->mMargin.GetTopUnit()) ||
-        !IsFixedMarginSize(margin->mMargin.GetBottomUnit())) {
+    if (!IsFixedMarginSize(margin->mMargin.GetTop()) ||
+        !IsFixedMarginSize(margin->mMargin.GetBottom())) {
       return PR_TRUE;
     }
     if (!IsFixedOffset(pos->mOffset.GetTop())) {
       return PR_TRUE;
     }
   }
   return PR_FALSE;
 }
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -1525,17 +1525,17 @@ nsBlockFrame::PrepareResizeReflow(nsBloc
        (NS_STYLE_TEXT_ALIGN_DEFAULT == styleText->mTextAlign &&
         NS_STYLE_DIRECTION_LTR ==
           aState.mReflowState.mStyleVisibility->mDirection) ||
        (NS_STYLE_TEXT_ALIGN_END == styleText->mTextAlign &&
         NS_STYLE_DIRECTION_RTL ==
           aState.mReflowState.mStyleVisibility->mDirection)) &&
       // The left content-edge must be a constant distance from the left
       // border-edge.
-      GetStylePadding()->mPadding.GetLeftUnit() != eStyleUnit_Percent;
+      !GetStylePadding()->mPadding.GetLeft().HasPercent();
 
 #ifdef DEBUG
   if (gDisableResizeOpt) {
     tryAndSkipLines = PR_FALSE;
   }
   if (gNoisyReflow) {
     if (!tryAndSkipLines) {
       IndentBy(stdout, gNoiseIndent);
@@ -2497,17 +2497,17 @@ nsBlockFrame::ReflowLine(nsBlockReflowSt
     // We don't really know what changed in the line, so use the union
     // of the old and new combined areas
     nsRect dirtyRect;
     dirtyRect.UnionRect(oldCombinedArea, aLine->GetCombinedArea());
 #ifdef NOISY_BLOCK_INVALIDATE
     printf("%p invalidate (%d, %d, %d, %d)\n",
            this, dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height);
     if (aLine->IsForceInvalidate())
-      printf("  dirty line is %p\n", static_cast<void*>(aLine.get());
+      printf("  dirty line is %p\n", static_cast<void*>(aLine.get()));
 #endif
     Invalidate(dirtyRect);
     if (GetStateBits() & NS_FRAME_HAS_CONTAINER_LAYER_DESCENDANT) {
       PRInt32 childCount = aLine->GetChildCount();
       for (nsIFrame* f = aLine->mFirstChild; childCount;
            --childCount, f = f->GetNextSibling()) {
         FrameLayerBuilder::InvalidateThebesLayersInSubtree(f);
       }
@@ -2749,20 +2749,25 @@ nsBlockFrame::AttributeChanged(PRInt32  
       }
     }
   }
 
   return rv;
 }
 
 static inline PRBool
-IsPaddingZero(nsStyleUnit aUnit, const nsStyleCoord &aCoord)
+IsPaddingZero(const nsStyleCoord &aCoord)
 {
-    return ((aUnit == eStyleUnit_Coord && aCoord.GetCoordValue() == 0) ||
-            (aUnit == eStyleUnit_Percent && aCoord.GetPercentValue() == 0.0));
+  return (aCoord.GetUnit() == eStyleUnit_Coord &&
+          aCoord.GetCoordValue() == 0) ||
+         (aCoord.GetUnit() == eStyleUnit_Percent &&
+          aCoord.GetPercentValue() == 0.0) ||
+         (aCoord.IsCalcUnit() &&
+          nsRuleNode::ComputeCoordPercentCalc(aCoord, nscoord_MAX) == 0 &&
+          nsRuleNode::ComputeCoordPercentCalc(aCoord, 0) == 0);
 }
 
 static inline PRBool
 IsNonAutoNonZeroHeight(const nsStyleCoord& aCoord)
 {
   if (aCoord.GetUnit() == eStyleUnit_Auto)
     return PR_FALSE;
   if (aCoord.IsCoordPercentCalcUnit()) {
@@ -2791,20 +2796,18 @@ nsBlockFrame::IsSelfEmpty()
   if (IsNonAutoNonZeroHeight(position->mMinHeight) ||
       IsNonAutoNonZeroHeight(position->mHeight))
     return PR_FALSE;
 
   const nsStyleBorder* border = GetStyleBorder();
   const nsStylePadding* padding = GetStylePadding();
   if (border->GetActualBorderWidth(NS_SIDE_TOP) != 0 ||
       border->GetActualBorderWidth(NS_SIDE_BOTTOM) != 0 ||
-      !IsPaddingZero(padding->mPadding.GetTopUnit(),
-                     padding->mPadding.GetTop()) ||
-      !IsPaddingZero(padding->mPadding.GetBottomUnit(),
-                     padding->mPadding.GetBottom())) {
+      !IsPaddingZero(padding->mPadding.GetTop()) ||
+      !IsPaddingZero(padding->mPadding.GetBottom())) {
     return PR_FALSE;
   }
 
   if (HaveOutsideBullet() && !BulletIsEmpty()) {
     return PR_FALSE;
   }
 
   return PR_TRUE;
--- a/layout/generic/nsContainerFrame.cpp
+++ b/layout/generic/nsContainerFrame.cpp
@@ -585,19 +585,20 @@ nsContainerFrame::SyncFrameViewPropertie
     }
   }
 
   vm->SetViewZIndex(aView, autoZIndex, zIndex, isPositioned);
 }
 
 static nscoord GetCoord(const nsStyleCoord& aCoord, nscoord aIfNotCoord)
 {
-  return aCoord.GetUnit() == eStyleUnit_Coord
-           ? aCoord.GetCoordValue()
-           : aIfNotCoord;
+  if (aCoord.ConvertsToLength()) {
+    return nsRuleNode::ComputeCoordPercentCalc(aCoord, 0);
+  }
+  return aIfNotCoord;
 }
 
 void
 nsContainerFrame::DoInlineIntrinsicWidth(nsIRenderingContext *aRenderingContext,
                                          InlineIntrinsicWidthData *aData,
                                          nsLayoutUtils::IntrinsicWidthType aType)
 {
   if (GetPrevInFlow())
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -123,16 +123,17 @@
 #include "nsIObjectLoadingContent.h"
 #include "nsExpirationTracker.h"
 #ifdef MOZ_SVG
 #include "nsSVGIntegrationUtils.h"
 #include "nsSVGEffects.h"
 #endif
 
 #include "gfxContext.h"
+#include "CSSCalc.h"
 
 using namespace mozilla;
 
 static NS_DEFINE_CID(kLookAndFeelCID,  NS_LOOKANDFEEL_CID);
 
 // Struct containing cached metrics for box-wrapped frames.
 struct nsBoxLayoutMetrics
 {
@@ -3018,39 +3019,124 @@ nsIFrame::InlinePrefWidthData::ForceBrea
 
   currentLine =
     NSCoordSaturatingSubtract(currentLine, trailingWhitespace, nscoord_MAX);
   prevLines = NS_MAX(prevLines, currentLine);
   currentLine = trailingWhitespace = 0;
   skipWhitespace = PR_TRUE;
 }
 
+/**
+ * This class does calc() computation of lengths and percents separately,
+ * with support for min() and max().  However, its support for min() and
+ * max() is fundamentally broken in cases where percentages and lengths
+ * are mixed.
+ *
+ * It is used only for intrinsic width computation, where being an
+ * approximation is sometimes ok (although it would be good to fix at
+ * some point in the future).
+ */
+struct LengthPercentPairWithMinMaxCalcOps : public css::StyleCoordInputCalcOps
+{
+  struct result_type {
+    nscoord mLength;
+    float mPercent;
+
+    result_type(nscoord aLength, float aPercent)
+      : mLength(aLength), mPercent(aPercent) {}
+  };
+
+  result_type ComputeLeafValue(const nsStyleCoord& aValue)
+  {
+    if (aValue.GetUnit() == eStyleUnit_Percent) {
+      return result_type(0, aValue.GetPercentValue());
+    }
+    return result_type(aValue.GetCoordValue(), 0.0f);
+  }
+
+  result_type
+  MergeAdditive(nsCSSUnit aCalcFunction,
+                result_type aValue1, result_type aValue2)
+  {
+    if (aCalcFunction == eCSSUnit_Calc_Plus) {
+      return result_type(NSCoordSaturatingAdd(aValue1.mLength,
+                                              aValue2.mLength),
+                         aValue1.mPercent + aValue2.mPercent);
+    }
+    if (aCalcFunction == eCSSUnit_Calc_Minus) {
+      return result_type(NSCoordSaturatingSubtract(aValue1.mLength,
+                                                   aValue2.mLength, 0),
+                         aValue1.mPercent - aValue2.mPercent);
+    }
+    if (aCalcFunction == eCSSUnit_Calc_Minimum) {
+      // This is fundamentally incorrect; see the comment above the
+      // start of the class definition.
+      return result_type(NS_MIN(aValue1.mLength, aValue2.mLength),
+                         NS_MIN(aValue1.mPercent, aValue2.mPercent));
+    }
+    NS_ABORT_IF_FALSE(aCalcFunction == eCSSUnit_Calc_Maximum,
+                      "unexpected unit");
+    // This is fundamentally incorrect; see the comment above the
+    // start of the class definition.
+    return result_type(NS_MAX(aValue1.mLength, aValue2.mLength),
+                       NS_MAX(aValue1.mPercent, aValue2.mPercent));
+  }
+
+  result_type
+  MergeMultiplicativeL(nsCSSUnit aCalcFunction,
+                       float aValue1, result_type aValue2)
+  {
+    NS_ABORT_IF_FALSE(aCalcFunction == eCSSUnit_Calc_Times_L,
+                      "unexpected unit");
+    return result_type(NSCoordSaturatingMultiply(aValue2.mLength, aValue1),
+                       aValue1 * aValue2.mPercent);
+  }
+
+  result_type
+  MergeMultiplicativeR(nsCSSUnit aCalcFunction,
+                       result_type aValue1, float aValue2)
+  {
+    NS_ABORT_IF_FALSE(aCalcFunction == eCSSUnit_Calc_Times_R ||
+                      aCalcFunction == eCSSUnit_Calc_Divided,
+                      "unexpected unit");
+    if (aCalcFunction == eCSSUnit_Calc_Divided) {
+      aValue2 = 1.0f / aValue2;
+    }
+    return result_type(NSCoordSaturatingMultiply(aValue1.mLength, aValue2),
+                       aValue1.mPercent * aValue2);
+  }
+
+};
+
 static void
 AddCoord(const nsStyleCoord& aStyle,
          nsIRenderingContext* aRenderingContext,
          nsIFrame* aFrame,
          nscoord* aCoord, float* aPercent)
 {
-  switch (aStyle.GetUnit()) {
-    case eStyleUnit_Coord:
-      *aCoord += aStyle.GetCoordValue();
-      break;
-    case eStyleUnit_Percent:
-      *aPercent += aStyle.GetPercentValue();
-      break;
-    default:
-      break;
-  }
+  if (!aStyle.IsCoordPercentCalcUnit()) {
+    return;
+  }
+
+  LengthPercentPairWithMinMaxCalcOps ops;
+  LengthPercentPairWithMinMaxCalcOps::result_type pair =
+    css::ComputeCalc(aStyle, ops);
+  *aCoord += pair.mLength;
+  *aPercent += pair.mPercent;
 }
 
 /* virtual */ nsIFrame::IntrinsicWidthOffsetData
 nsFrame::IntrinsicWidthOffsets(nsIRenderingContext* aRenderingContext)
 {
   IntrinsicWidthOffsetData result;
 
+  // FIXME: The handling of calc() with min() and max() by AddCoord
+  // is a rough approximation.  It could be improved, but only by
+  // changing the IntrinsicWidthOffsets API substantially.  See the
+  // comment above LengthPercentPairWithMinMaxCalcOps.
   const nsStyleMargin *styleMargin = GetStyleMargin();
   AddCoord(styleMargin->mMargin.GetLeft(), aRenderingContext, this,
            &result.hMargin, &result.hPctMargin);
   AddCoord(styleMargin->mMargin.GetRight(), aRenderingContext, this,
            &result.hMargin, &result.hPctMargin);
 
   const nsStylePadding *stylePadding = GetStylePadding();
   AddCoord(stylePadding->mPadding.GetLeft(), aRenderingContext, this,
--- a/layout/generic/nsInlineFrame.cpp
+++ b/layout/generic/nsInlineFrame.cpp
@@ -89,28 +89,31 @@ nsInlineFrame::GetFrameName(nsAString& a
 
 nsIAtom*
 nsInlineFrame::GetType() const
 {
   return nsGkAtoms::inlineFrame;
 }
 
 static inline PRBool
-IsPaddingZero(nsStyleUnit aUnit, const nsStyleCoord &aCoord)
+IsPaddingZero(const nsStyleCoord &aCoord)
 {
-    return ((aUnit == eStyleUnit_Coord && aCoord.GetCoordValue() == 0) ||
-            (aUnit == eStyleUnit_Percent && aCoord.GetPercentValue() == 0.0));
+  return (aCoord.GetUnit() == eStyleUnit_Coord &&
+          aCoord.GetCoordValue() == 0) ||
+         (aCoord.GetUnit() == eStyleUnit_Percent &&
+          aCoord.GetPercentValue() == 0.0) ||
+         (aCoord.IsCalcUnit() &&
+          nsRuleNode::ComputeCoordPercentCalc(aCoord, nscoord_MAX) == 0 &&
+          nsRuleNode::ComputeCoordPercentCalc(aCoord, 0) == 0);
 }
 
 static inline PRBool
-IsMarginZero(nsStyleUnit aUnit, const nsStyleCoord &aCoord)
+IsMarginZero(const nsStyleCoord &aCoord)
 {
-    return (aUnit == eStyleUnit_Auto ||
-            (aUnit == eStyleUnit_Coord && aCoord.GetCoordValue() == 0) ||
-            (aUnit == eStyleUnit_Percent && aCoord.GetPercentValue() == 0.0));
+  return aCoord.GetUnit() == eStyleUnit_Auto || IsPaddingZero(aCoord);
 }
 
 /* virtual */ PRBool
 nsInlineFrame::IsSelfEmpty()
 {
 #if 0
   // I used to think inline frames worked this way, but it seems they
   // don't.  At least not in our codebase.
@@ -121,26 +124,22 @@ nsInlineFrame::IsSelfEmpty()
   const nsStyleMargin* margin = GetStyleMargin();
   const nsStyleBorder* border = GetStyleBorder();
   const nsStylePadding* padding = GetStylePadding();
   // XXX Top and bottom removed, since they shouldn't affect things, but this
   // doesn't really match with nsLineLayout.cpp's setting of
   // ZeroEffectiveSpanBox, anymore, so what should this really be?
   PRBool haveRight =
     border->GetActualBorderWidth(NS_SIDE_RIGHT) != 0 ||
-    !IsPaddingZero(padding->mPadding.GetRightUnit(),
-                   padding->mPadding.GetRight()) ||
-    !IsMarginZero(margin->mMargin.GetRightUnit(),
-                  margin->mMargin.GetRight());
+    !IsPaddingZero(padding->mPadding.GetRight()) ||
+    !IsMarginZero(margin->mMargin.GetRight());
   PRBool haveLeft =
     border->GetActualBorderWidth(NS_SIDE_LEFT) != 0 ||
-    !IsPaddingZero(padding->mPadding.GetLeftUnit(),
-                   padding->mPadding.GetLeft()) ||
-    !IsMarginZero(margin->mMargin.GetLeftUnit(),
-                  margin->mMargin.GetLeft());
+    !IsPaddingZero(padding->mPadding.GetLeft()) ||
+    !IsMarginZero(margin->mMargin.GetLeft());
   if (haveLeft || haveRight) {
     if (GetStateBits() & NS_FRAME_IS_SPECIAL) {
       PRBool haveStart, haveEnd;
       if (NS_STYLE_DIRECTION_LTR == GetStyleVisibility()->mDirection) {
         haveStart = haveLeft;
         haveEnd = haveRight;
       } else {
         haveStart = haveRight;
--- a/layout/generic/nsLineLayout.cpp
+++ b/layout/generic/nsLineLayout.cpp
@@ -643,17 +643,17 @@ nsLineLayout::LineIsBreakable() const
 // Checks all four sides for percentage units.  This means it should
 // only be used for things (margin, padding) where percentages on top
 // and bottom depend on the *width* just like percentages on left and
 // right.
 static PRBool
 HasPercentageUnitSide(const nsStyleSides& aSides)
 {
   NS_FOR_CSS_SIDES(side) {
-    if (eStyleUnit_Percent == aSides.GetUnit(side))
+    if (aSides.Get(side).HasPercent())
       return PR_TRUE;
   }
   return PR_FALSE;
 }
 
 static PRBool
 IsPercentageAware(const nsIFrame* aFrame)
 {
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/margin-block-1-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<title>Test of margin-*: calc()</title>
+<style>
+
+div { border: medium solid green; width: 500px }
+p { background: yellow }
+
+</style>
+
+<div><p style="margin: 15px 0 0 0">paragraph with margin</p></div>
+<div><p style="margin: 0 15px 0 0">paragraph with margin</p></div>
+<div><p style="margin: 0 0 15px 0">paragraph with margin</p></div>
+<div><p style="margin: 0 0 0 15px">paragraph with margin</p></div>
+<div><p style="margin: 25px 25px 25px 25px">paragraph with margin</p></div>
+<div><p style="margin: 15px 20px 20px 25px">paragraph with margin</p></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/margin-block-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<title>Test of margin-*: -moz-calc()</title>
+<style>
+
+div { border: medium solid green; width: 500px }
+p { background: yellow }
+
+</style>
+
+<div><p style="margin: -moz-calc(10px + 1%) 0 0 0">paragraph with margin</p></div>
+<div><p style="margin: 0 -moz-calc(10px + 1%) 0 0">paragraph with margin</p></div>
+<div><p style="margin: 0 0 -moz-calc(10px + 1%) 0">paragraph with margin</p></div>
+<div><p style="margin: 0 0 0 -moz-calc(10px + 1%)">paragraph with margin</p></div>
+<div><p style="margin: -moz-calc(30px - 1%)">paragraph with margin</p></div>
+<div><p style="margin: -moz-min(20px,3%) -moz-min(20px,5%) -moz-max(20px,3%) -moz-max(20px,5%)">paragraph with margin</p></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/padding-block-1-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<title>Test of padding-*: calc()</title>
+<style>
+
+body { width: 500px }
+div { border: medium solid green }
+p { background: yellow; margin: 0 }
+
+</style>
+
+<div style="padding: 15px 0 0 0"><p>paragraph with padding</p></div>
+<div style="padding: 0 15px 0 0"><p>paragraph with padding</p></div>
+<div style="padding: 0 0 15px 0"><p>paragraph with padding</p></div>
+<div style="padding: 0 0 0 15px"><p>paragraph with padding</p></div>
+<div style="padding: 25px 25px 25px 25px"><p>paragraph with padding</p></div>
+<div style="padding: 15px 20px 20px 25px"><p>paragraph with padding</p></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/padding-block-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<title>Test of padding-*: -moz-calc()</title>
+<style>
+
+body { width: 500px }
+div { border: medium solid green }
+p { background: yellow; margin: 0 }
+
+</style>
+
+<div style="padding: -moz-calc(10px + 1%) 0 0 0"><p>paragraph with padding</p></div>
+<div style="padding: 0 -moz-calc(10px + 1%) 0 0"><p>paragraph with padding</p></div>
+<div style="padding: 0 0 -moz-calc(10px + 1%) 0"><p>paragraph with padding</p></div>
+<div style="padding: 0 0 0 -moz-calc(10px + 1%)"><p>paragraph with padding</p></div>
+<div style="padding: -moz-calc(30px - 1%)"><p>paragraph with padding</p></div>
+<div style="padding: -moz-min(20px,3%) -moz-min(20px,5%) -moz-max(20px,3%) -moz-max(20px,5%)"><p>paragraph with padding</p></div>
--- a/layout/reftests/css-calc/reftest.list
+++ b/layout/reftests/css-calc/reftest.list
@@ -1,20 +1,22 @@
 == height-block-1.html height-block-1-ref.html
 == height-table-1.html height-table-1-ref.html
+== margin-block-1.html margin-block-1-ref.html
 == max-height-block-1.html max-height-block-1-ref.html
 == max-width-block-1.html width-block-1-ref.html
 == max-width-block-intrinsic-1.html max-width-block-intrinsic-1-ref.html
 == min-height-block-1.html height-block-1-ref.html
 == min-width-block-1.html width-block-1-ref.html
 == min-width-block-intrinsic-1.html min-width-block-intrinsic-1-ref.html
 == offsets-absolute-bottom-1.html offsets-absolute-top-1-ref.html
 == offsets-absolute-left-1.html offsets-relative-left-1-ref.html
 == offsets-absolute-right-1.html offsets-relative-left-1-ref.html
 == offsets-absolute-top-1.html offsets-absolute-top-1-ref.html
 == offsets-relative-bottom-1.html offsets-relative-top-1-ref.html
 == offsets-relative-left-1.html offsets-relative-left-1-ref.html
 == offsets-relative-right-1.html offsets-relative-left-1-ref.html
 == offsets-relative-top-1.html offsets-relative-top-1-ref.html
+== padding-block-1.html padding-block-1-ref.html
 == width-block-1.html width-block-1-ref.html
 == width-block-intrinsic-1.html width-block-intrinsic-1-ref.html
 == width-table-auto-1.html width-table-auto-1-ref.html
 == width-table-fixed-1.html width-table-fixed-1-ref.html
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -5823,17 +5823,17 @@ CSSParserImpl::ParseSingleValueProperty(
   case eCSSProperty_list_style_type:
     return ParseVariant(aValue, VARIANT_HK, nsCSSProps::kListStyleKTable);
   case eCSSProperty_margin_bottom:
   case eCSSProperty_margin_end_value: // for internal use
   case eCSSProperty_margin_left_value: // for internal use
   case eCSSProperty_margin_right_value: // for internal use
   case eCSSProperty_margin_start_value: // for internal use
   case eCSSProperty_margin_top:
-    return ParseVariant(aValue, VARIANT_AHLP, nsnull);
+    return ParseVariant(aValue, VARIANT_AHLP | VARIANT_CALC, nsnull);
   case eCSSProperty_marker_offset:
     return ParseVariant(aValue, VARIANT_AHL | VARIANT_CALC, nsnull);
   case eCSSProperty_marks:
     return ParseMarks(aValue);
   case eCSSProperty_max_height:
     return ParseNonNegativeVariant(aValue, VARIANT_HLPO | VARIANT_CALC,
                                    nsnull);
   case eCSSProperty_max_width:
@@ -5866,17 +5866,17 @@ CSSParserImpl::ParseSingleValueProperty(
     return ParseVariant(aValue, VARIANT_HK,
                         nsCSSProps::kOverflowSubKTable);
   case eCSSProperty_padding_bottom:
   case eCSSProperty_padding_end_value: // for internal use
   case eCSSProperty_padding_left_value: // for internal use
   case eCSSProperty_padding_right_value: // for internal use
   case eCSSProperty_padding_start_value: // for internal use
   case eCSSProperty_padding_top:
-    return ParseNonNegativeVariant(aValue, VARIANT_HLP, nsnull);
+    return ParseNonNegativeVariant(aValue, VARIANT_HLP | VARIANT_CALC, nsnull);
   case eCSSProperty_page:
     return ParseVariant(aValue, VARIANT_AUTO | VARIANT_IDENTIFIER, nsnull);
   case eCSSProperty_page_break_after:
   case eCSSProperty_page_break_before:
     return ParseVariant(aValue, VARIANT_HK,
                         nsCSSProps::kPageBreakKTable);
   case eCSSProperty_page_break_inside:
     return ParseVariant(aValue, VARIANT_HK,
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -4936,17 +4936,18 @@ nsRuleNode::ComputeMarginData(void* aSta
   AdjustLogicalBoxProp(aContext,
                        marginData.mMarginRightLTRSource,
                        marginData.mMarginRightRTLSource,
                        marginData.mMarginEnd, marginData.mMarginStart,
                        NS_SIDE_RIGHT, ourMargin, canStoreInRuleTree);
   NS_FOR_CSS_SIDES(side) {
     nsStyleCoord parentCoord = parentMargin->mMargin.Get(side);
     if (SetCoord(ourMargin.*(nsCSSRect::sides[side]),
-                 coord, parentCoord, SETCOORD_LPAH | SETCOORD_INITIAL_ZERO,
+                 coord, parentCoord,
+                 SETCOORD_LPAH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC,
                  aContext, mPresContext, canStoreInRuleTree)) {
       margin->mMargin.Set(side, coord);
     }
   }
 
   margin->RecalcData();
   COMPUTE_END_RESET(Margin, margin)
 }
@@ -5305,17 +5306,18 @@ nsRuleNode::ComputePaddingData(void* aSt
   AdjustLogicalBoxProp(aContext,
                        marginData.mPaddingRightLTRSource,
                        marginData.mPaddingRightRTLSource,
                        marginData.mPaddingEnd, marginData.mPaddingStart,
                        NS_SIDE_RIGHT, ourPadding, canStoreInRuleTree);
   NS_FOR_CSS_SIDES(side) {
     nsStyleCoord parentCoord = parentPadding->mPadding.Get(side);
     if (SetCoord(ourPadding.*(nsCSSRect::sides[side]),
-                 coord, parentCoord, SETCOORD_LPH | SETCOORD_INITIAL_ZERO,
+                 coord, parentCoord,
+                 SETCOORD_LPH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC,
                  aContext, mPresContext, canStoreInRuleTree)) {
       padding->mPadding.Set(side, coord);
     }
   }
 
   padding->RecalcData();
   COMPUTE_END_RESET(Padding, padding)
 }
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -279,16 +279,18 @@ void
 nsStyleMargin::Destroy(nsPresContext* aContext) {
   this->~nsStyleMargin();
   aContext->FreeToShell(sizeof(nsStyleMargin), this);
 }
 
 
 void nsStyleMargin::RecalcData()
 {
+  // FIXME: We could cache calc() with percents here if we changed
+  // IsFixedData and CalcCoord.
   if (IsFixedData(mMargin, PR_FALSE)) {
     NS_FOR_CSS_SIDES(side) {
       mCachedMargin.side(side) = CalcCoord(mMargin.Get(side), nsnull, 0);
     }
     mHasCachedMargin = PR_TRUE;
   }
   else
     mHasCachedMargin = PR_FALSE;
@@ -342,16 +344,18 @@ nsStylePadding::operator new(size_t sz, 
 void 
 nsStylePadding::Destroy(nsPresContext* aContext) {
   this->~nsStylePadding();
   aContext->FreeToShell(sizeof(nsStylePadding), this);
 }
 
 void nsStylePadding::RecalcData()
 {
+  // FIXME: We could cache calc() with percents here if we changed
+  // IsFixedData and CalcCoord.
   if (IsFixedData(mPadding, PR_FALSE)) {
     NS_FOR_CSS_SIDES(side) {
       mCachedPadding.side(side) = CalcCoord(mPadding.Get(side), nsnull, 0);
     }
     mHasCachedPadding = PR_TRUE;
   }
   else
     mHasCachedPadding = PR_FALSE;
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -553,17 +553,17 @@ struct nsStyleMargin {
 
   void RecalcData();
   nsChangeHint CalcDifference(const nsStyleMargin& aOther) const;
 #ifdef DEBUG
   static nsChangeHint MaxDifference();
 #endif
   static PRBool ForceCompare() { return PR_TRUE; }
 
-  nsStyleSides  mMargin;          // [reset] coord, percent, auto
+  nsStyleSides  mMargin;          // [reset] coord, percent, calc, auto
 
   PRBool GetMargin(nsMargin& aMargin) const
   {
     if (mHasCachedMargin) {
       aMargin = mCachedMargin;
       return PR_TRUE;
     }
     return PR_FALSE;
@@ -587,17 +587,17 @@ struct nsStylePadding {
 
   void RecalcData();
   nsChangeHint CalcDifference(const nsStylePadding& aOther) const;
 #ifdef DEBUG
   static nsChangeHint MaxDifference();
 #endif
   static PRBool ForceCompare() { return PR_TRUE; }
 
-  nsStyleSides  mPadding;         // [reset] coord, percent
+  nsStyleSides  mPadding;         // [reset] coord, percent, calc
 
   PRBool GetPadding(nsMargin& aPadding) const
   {
     if (mHasCachedPadding) {
       aPadding = mCachedPadding;
       return PR_TRUE;
     }
     return PR_FALSE;
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -567,28 +567,42 @@ var gCSSProperties = {
 	"-moz-margin-end": {
 		domProp: "MozMarginEnd",
 		inherited: false,
 		type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
 		get_computed: logical_box_prop_get_computed,
 		/* no subproperties */
 		/* auto may or may not be initial */
 		initial_values: [ "0", "0px", "0%", "0em", "0ex" ],
-		other_values: [ "1px", "3em" ],
+		other_values: [ "1px", "3em",
+			"-moz-calc(2px)",
+			"-moz-calc(50%)",
+			"-moz-calc(3*25px)",
+			"-moz-calc(25px*3)",
+			"-moz-calc(3*25px + 50%)",
+			"-moz-min(30%, 30em,200px, min(500px ,40em))",
+		],
 		invalid_values: []
 	},
 	"-moz-margin-start": {
 		domProp: "MozMarginStart",
 		inherited: false,
 		type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
 		get_computed: logical_box_prop_get_computed,
 		/* no subproperties */
 		/* auto may or may not be initial */
 		initial_values: [ "0", "0px", "0%", "0em", "0ex" ],
-		other_values: [ "1px", "3em" ],
+		other_values: [ "1px", "3em",
+			"-moz-calc(2px)",
+			"-moz-calc(50%)",
+			"-moz-calc(3*25px)",
+			"-moz-calc(25px*3)",
+			"-moz-calc(3*25px + 50%)",
+			"-moz-min(30%, 30em,200px, min(500px ,40em))",
+		],
 		invalid_values: []
 	},
 	"-moz-outline-radius": {
 		domProp: "MozOutlineRadius",
 		inherited: false,
 		type: CSS_TYPE_TRUE_SHORTHAND,
 		subproperties: [ "-moz-outline-radius-bottomleft", "-moz-outline-radius-bottomright", "-moz-outline-radius-topleft", "-moz-outline-radius-topright" ],
 		initial_values: [ "0", "0px", "0%" ],
@@ -639,27 +653,41 @@ var gCSSProperties = {
 	},
 	"-moz-padding-end": {
 		domProp: "MozPaddingEnd",
 		inherited: false,
 		type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
 		get_computed: logical_box_prop_get_computed,
 		/* no subproperties */
 		initial_values: [ "0", "0px", "0%", "0em", "0ex" ],
-		other_values: [ "1px", "3em" ],
+		other_values: [ "1px", "3em",
+			"-moz-calc(2px)",
+			"-moz-calc(50%)",
+			"-moz-calc(3*25px)",
+			"-moz-calc(25px*3)",
+			"-moz-calc(3*25px + 50%)",
+			"-moz-min(30%, 30em,200px, min(500px ,40em))",
+		],
 		invalid_values: []
 	},
 	"-moz-padding-start": {
 		domProp: "MozPaddingStart",
 		inherited: false,
 		type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
 		get_computed: logical_box_prop_get_computed,
 		/* no subproperties */
 		initial_values: [ "0", "0px", "0%", "0em", "0ex" ],
-		other_values: [ "1px", "3em" ],
+		other_values: [ "1px", "3em",
+			"-moz-calc(2px)",
+			"-moz-calc(50%)",
+			"-moz-calc(3*25px)",
+			"-moz-calc(25px*3)",
+			"-moz-calc(3*25px + 50%)",
+			"-moz-min(30%, 30em,200px, min(500px ,40em))",
+		],
 		invalid_values: []
 	},
 	"resize": {
 		domProp: "resize",
 		inherited: false,
 		type: CSS_TYPE_LONGHAND,
 		prerequisites: { "display": "block", "overflow": "auto" },
 		initial_values: [ "none" ],
@@ -1719,46 +1747,74 @@ var gCSSProperties = {
 		invalid_values: []
 	},
 	"margin-bottom": {
 		domProp: "marginBottom",
 		inherited: false,
 		type: CSS_TYPE_LONGHAND,
 		/* XXX testing auto has prerequisites */
 		initial_values: [ "0", "0px", "0%" ],
-		other_values: [ "1px", "2em", "5%" ],
+		other_values: [ "1px", "2em", "5%",
+			"-moz-calc(2px)",
+			"-moz-calc(50%)",
+			"-moz-calc(3*25px)",
+			"-moz-calc(25px*3)",
+			"-moz-calc(3*25px + 50%)",
+			"-moz-min(30%, 30em,200px, min(500px ,40em))",
+		],
 		invalid_values: [ ]
 	},
 	"margin-left": {
 		domProp: "marginLeft",
 		inherited: false,
 		type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
 		/* no subproperties */
 		/* XXX testing auto has prerequisites */
 		initial_values: [ "0", "0px", "0%" ],
-		other_values: [ "1px", "2em", "5%", ".5px", "+32px", "+.789px", "-.328px", "+0.56px", "-0.974px", "237px", "-289px", "-056px", "1987.45px", "-84.32px" ],
+		other_values: [ "1px", "2em", "5%", ".5px", "+32px", "+.789px", "-.328px", "+0.56px", "-0.974px", "237px", "-289px", "-056px", "1987.45px", "-84.32px",
+			"-moz-calc(2px)",
+			"-moz-calc(50%)",
+			"-moz-calc(3*25px)",
+			"-moz-calc(25px*3)",
+			"-moz-calc(3*25px + 50%)",
+			"-moz-min(30%, 30em,200px, min(500px ,40em))",
+		],
 		invalid_values: [ "..25px", ".+5px", ".px", "-.px", "++5px", "-+4px", "+-3px", "--7px", "+-.6px", "-+.5px", "++.7px", "--.4px" ]
 	},
 	"margin-right": {
 		domProp: "marginRight",
 		inherited: false,
 		type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
 		/* no subproperties */
 		/* XXX testing auto has prerequisites */
 		initial_values: [ "0", "0px", "0%" ],
-		other_values: [ "1px", "2em", "5%" ],
+		other_values: [ "1px", "2em", "5%",
+			"-moz-calc(2px)",
+			"-moz-calc(50%)",
+			"-moz-calc(3*25px)",
+			"-moz-calc(25px*3)",
+			"-moz-calc(3*25px + 50%)",
+			"-moz-min(30%, 30em,200px, min(500px ,40em))",
+		],
 		invalid_values: [ ]
 	},
 	"margin-top": {
 		domProp: "marginTop",
 		inherited: false,
 		type: CSS_TYPE_LONGHAND,
 		/* XXX testing auto has prerequisites */
 		initial_values: [ "0", "0px", "0%" ],
-		other_values: [ "1px", "2em", "5%" ],
+		other_values: [ "1px", "2em", "5%",
+			"-moz-calc(2px)",
+			"-moz-calc(50%)",
+			"-moz-calc(3*25px)",
+			"-moz-calc(25px*3)",
+			"-moz-calc(3*25px + 50%)",
+			"-moz-min(30%, 30em,200px, min(500px ,40em))",
+		],
 		invalid_values: [ ]
 	},
 	"marker-offset": {
 		domProp: "markerOffset",
 		inherited: false,
 		type: CSS_TYPE_LONGHAND,
 		initial_values: [ "auto" ],
 		other_values: [ "6em", "-1px", "-moz-calc(0px)", "-moz-calc(3em + 2px - 4px)", "-moz-calc(-2em)" ],
@@ -1942,43 +1998,71 @@ var gCSSProperties = {
 		other_values: [ "3px 0", "2em 4px 2pt", "1em 2em 3px 4px" ],
 		invalid_values: []
 	},
 	"padding-bottom": {
 		domProp: "paddingBottom",
 		inherited: false,
 		type: CSS_TYPE_LONGHAND,
 		initial_values: [ "0", "0px", "0%" ],
-		other_values: [ "1px", "2em", "5%" ],
+		other_values: [ "1px", "2em", "5%",
+			"-moz-calc(2px)",
+			"-moz-calc(50%)",
+			"-moz-calc(3*25px)",
+			"-moz-calc(25px*3)",
+			"-moz-calc(3*25px + 50%)",
+			"-moz-min(30%, 30em,200px, min(500px ,40em))",
+		],
 		invalid_values: [ ]
 	},
 	"padding-left": {
 		domProp: "paddingLeft",
 		inherited: false,
 		type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
 		/* no subproperties */
 		initial_values: [ "0", "0px", "0%" ],
-		other_values: [ "1px", "2em", "5%" ],
+		other_values: [ "1px", "2em", "5%",
+			"-moz-calc(2px)",
+			"-moz-calc(50%)",
+			"-moz-calc(3*25px)",
+			"-moz-calc(25px*3)",
+			"-moz-calc(3*25px + 50%)",
+			"-moz-min(30%, 30em,200px, min(500px ,40em))",
+		],
 		invalid_values: [ ]
 	},
 	"padding-right": {
 		domProp: "paddingRight",
 		inherited: false,
 		type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
 		/* no subproperties */
 		initial_values: [ "0", "0px", "0%" ],
-		other_values: [ "1px", "2em", "5%" ],
+		other_values: [ "1px", "2em", "5%",
+			"-moz-calc(2px)",
+			"-moz-calc(50%)",
+			"-moz-calc(3*25px)",
+			"-moz-calc(25px*3)",
+			"-moz-calc(3*25px + 50%)",
+			"-moz-min(30%, 30em,200px, min(500px ,40em))",
+		],
 		invalid_values: [ ]
 	},
 	"padding-top": {
 		domProp: "paddingTop",
 		inherited: false,
 		type: CSS_TYPE_LONGHAND,
 		initial_values: [ "0", "0px", "0%" ],
-		other_values: [ "1px", "2em", "5%" ],
+		other_values: [ "1px", "2em", "5%",
+			"-moz-calc(2px)",
+			"-moz-calc(50%)",
+			"-moz-calc(3*25px)",
+			"-moz-calc(25px*3)",
+			"-moz-calc(3*25px + 50%)",
+			"-moz-min(30%, 30em,200px, min(500px ,40em))",
+		],
 		invalid_values: [ ]
 	},
 	"page": {
 		domProp: "page",
 		inherited: true,
 		backend_only: true,
 		type: CSS_TYPE_LONGHAND,
 		initial_values: [ "auto" ],