Add support for calc() to the 'height', 'min-height', and 'max-height' properties. (Bug 585715) r=bzbarsky a2.0=blocking+
authorL. David Baron <dbaron@dbaron.org>
Wed, 25 Aug 2010 12:17:55 +0200
changeset 51406 fcc2aa4bd4519f51ced150cc460f714ee3dfc1c6
parent 51405 fb7ecc5f447c7df9ff59c68054367b50e1ce38bb
child 51407 4cc74816505e650952796f6e462a82beed02d036
push idunknown
push userunknown
push dateunknown
reviewersbzbarsky
bugs585715
milestone2.0b5pre
Add support for calc() to the 'height', 'min-height', and 'max-height' properties. (Bug 585715) r=bzbarsky a2.0=blocking+
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
layout/generic/nsBlockFrame.cpp
layout/generic/nsFrame.cpp
layout/generic/nsHTMLReflowMetrics.h
layout/generic/nsHTMLReflowState.cpp
layout/reftests/css-calc/height-block-1-ref.html
layout/reftests/css-calc/height-block-1.html
layout/reftests/css-calc/height-table-1-ref.html
layout/reftests/css-calc/height-table-1.html
layout/reftests/css-calc/max-height-block-1-ref.html
layout/reftests/css-calc/max-height-block-1.html
layout/reftests/css-calc/min-height-block-1.html
layout/reftests/css-calc/reftest.list
layout/style/nsCSSParser.cpp
layout/style/nsRuleNode.cpp
layout/style/nsStyleStruct.h
layout/style/test/property_database.js
layout/tables/nsTableFrame.cpp
layout/tables/nsTableRowFrame.cpp
layout/xul/base/src/nsBox.cpp
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1768,31 +1768,36 @@ static nscoord AddPercents(nsLayoutUtils
     if (aPercent >= 1.0f)
       result = nscoord_MAX;
     else
       result = NSToCoordRound(float(result) / (1.0f - aPercent));
   }
   return result;
 }
 
+// Use only for widths/heights (or their min/max), since it clamps
+// negative calc() results to 0.
 static PRBool GetAbsoluteCoord(const nsStyleCoord& aStyle, nscoord& aResult)
 {
   if (aStyle.IsCalcUnit()) {
     if (aStyle.CalcHasPercent()) {
       return PR_FALSE;
     }
     // If it has no percents, we can pass 0 for the percentage basis.
     aResult = nsRuleNode::ComputeComputedCalc(aStyle, 0);
+    if (aResult < 0)
+      aResult = 0;
     return PR_TRUE;
   }
 
   if (eStyleUnit_Coord != aStyle.GetUnit())
     return PR_FALSE;
 
   aResult = aStyle.GetCoordValue();
+  NS_ASSERTION(aResult >= 0, "negative widths not allowed");
   return PR_TRUE;
 }
 
 static PRBool
 GetPercentHeight(const nsStyleCoord& aStyle,
                  nsIFrame* aFrame,
                  nscoord& aResult)
 {
@@ -1808,17 +1813,17 @@ GetPercentHeight(const nsStyleCoord& aSt
     return PR_FALSE;
   }
 
   const nsStylePosition *pos = f->GetStylePosition();
   nscoord h;
   if (!GetAbsoluteCoord(pos->mHeight, h) &&
       !GetPercentHeight(pos->mHeight, f, h)) {
     NS_ASSERTION(pos->mHeight.GetUnit() == eStyleUnit_Auto ||
-                 pos->mHeight.GetUnit() == eStyleUnit_Percent,
+                 pos->mHeight.HasPercent(),
                  "unknown height unit");
     nsIAtom* fType = f->GetType();
     if (fType != nsGkAtoms::viewportFrame && fType != nsGkAtoms::canvasFrame &&
         fType != nsGkAtoms::pageContentFrame) {
       // There's no basis for the percentage height, so it acts like auto.
       // Should we consider a max-height < min-height pair a basis for
       // percentage heights?  The spec is somewhat unclear, and not doing
       // so is simpler and avoids troubling discontinuities in behavior,
@@ -1839,27 +1844,27 @@ GetPercentHeight(const nsStyleCoord& aSt
 
   nscoord maxh;
   if (GetAbsoluteCoord(pos->mMaxHeight, maxh) ||
       GetPercentHeight(pos->mMaxHeight, f, maxh)) {
     if (maxh < h)
       h = maxh;
   } else {
     NS_ASSERTION(pos->mMaxHeight.GetUnit() == eStyleUnit_None ||
-                 pos->mMaxHeight.GetUnit() == eStyleUnit_Percent,
+                 pos->mMaxHeight.HasPercent(),
                  "unknown max-height unit");
   }
 
   nscoord minh;
   if (GetAbsoluteCoord(pos->mMinHeight, minh) ||
       GetPercentHeight(pos->mMinHeight, f, minh)) {
     if (minh > h)
       h = minh;
   } else {
-    NS_ASSERTION(pos->mMinHeight.GetUnit() == eStyleUnit_Percent,
+    NS_ASSERTION(pos->mMinHeight.HasPercent(),
                  "unknown min-height unit");
   }
 
   aResult = NSToCoordRound(aStyle.GetPercentValue() * h);
   return PR_TRUE;
 }
 
 // Handles only -moz-max-content and -moz-min-content, and
@@ -2243,18 +2248,17 @@ nsLayoutUtils::ComputeHeightDependentVal
 }
 
 inline PRBool
 IsAutoHeight(const nsStyleCoord &aCoord, nscoord aCBHeight)
 {
   nsStyleUnit unit = aCoord.GetUnit();
   return unit == eStyleUnit_Auto ||  // only for 'height'
          unit == eStyleUnit_None ||  // only for 'max-height'
-         (unit == eStyleUnit_Percent &&
-          aCBHeight == NS_AUTOHEIGHT);
+         (aCBHeight == NS_AUTOHEIGHT && aCoord.HasPercent());
 }
 
 #define MULDIV(a,b,c) (nscoord(PRInt64(a) * PRInt64(b) / PRInt64(c)))
 
 /* static */ nsSize
 nsLayoutUtils::ComputeSizeWithIntrinsicDimensions(
                    nsIRenderingContext* aRenderingContext, nsIFrame* aFrame,
                    const nsIFrame::IntrinsicSize& aIntrinsicSize,
@@ -2304,35 +2308,35 @@ nsLayoutUtils::ComputeSizeWithIntrinsicD
 
   minWidth = nsLayoutUtils::ComputeWidthValue(aRenderingContext,
                aFrame, aCBSize.width, boxSizingAdjust.width,
                boxSizingToMarginEdgeWidth, stylePos->mMinWidth);
   NS_ASSERTION(minWidth >= 0, "negative result from ComputeWidthValue");
 
   if (!isAutoHeight) {
     height = nsLayoutUtils::
-      ComputeHeightDependentValue(aCBSize.height, stylePos->mHeight) -
+      ComputeHeightValue(aCBSize.height, stylePos->mHeight) -
       boxSizingAdjust.height;
     if (height < 0)
       height = 0;
   }
 
   if (!IsAutoHeight(stylePos->mMaxHeight, aCBSize.height)) {
     maxHeight = nsLayoutUtils::
-      ComputeHeightDependentValue(aCBSize.height, stylePos->mMaxHeight) -
+      ComputeHeightValue(aCBSize.height, stylePos->mMaxHeight) -
       boxSizingAdjust.height;
     if (maxHeight < 0)
       maxHeight = 0;
   } else {
     maxHeight = nscoord_MAX;
   }
 
   if (!IsAutoHeight(stylePos->mMinHeight, aCBSize.height)) {
     minHeight = nsLayoutUtils::
-      ComputeHeightDependentValue(aCBSize.height, stylePos->mMinHeight) -
+      ComputeHeightValue(aCBSize.height, stylePos->mMinHeight) -
       boxSizingAdjust.height;
     if (minHeight < 0)
       minHeight = 0;
   } else {
     minHeight = 0;
   }
 
   // Resolve percentage intrinsic width/height as necessary:
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -772,16 +772,29 @@ public:
    * Convert nsStyleCoord to nscoord when percentages depend on the
    * containing block height.
    */
   static nscoord ComputeHeightDependentValue(
                    nscoord              aContainingBlockHeight,
                    const nsStyleCoord&  aCoord);
 
   /*
+   * Likewise, but for 'height', 'min-height', or 'max-height'.
+   */
+  static nscoord ComputeHeightValue(nscoord aContainingBlockHeight,
+                                    const nsStyleCoord& aCoord)
+  {
+    nscoord result =
+      ComputeHeightDependentValue(aContainingBlockHeight, aCoord);
+    if (result < 0)
+      result = 0; // clamp calc()
+    return result;
+  }
+
+  /*
    * Calculate the used values for 'width' and 'height' for a replaced element.
    *
    *   http://www.w3.org/TR/CSS21/visudet.html#min-max-widths
    */
   static nsSize ComputeSizeWithIntrinsicDimensions(
                     nsIRenderingContext* aRenderingContext, nsIFrame* aFrame,
                     const nsIFrame::IntrinsicSize& aIntrinsicSize,
                     nsSize aIntrinsicRatio, nsSize aCBSize,
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -2746,54 +2746,47 @@ nsBlockFrame::AttributeChanged(PRInt32  
 
 static inline PRBool
 IsPaddingZero(nsStyleUnit aUnit, const nsStyleCoord &aCoord)
 {
     return ((aUnit == eStyleUnit_Coord && aCoord.GetCoordValue() == 0) ||
             (aUnit == eStyleUnit_Percent && aCoord.GetPercentValue() == 0.0));
 }
 
+static inline PRBool
+IsNonAutoNonZeroHeight(const nsStyleCoord& aCoord)
+{
+  if (aCoord.GetUnit() == eStyleUnit_Auto)
+    return PR_FALSE;
+  if (aCoord.IsCoordPercentCalcUnit()) {
+    // If we evaluate the length/percent/calc at a percentage basis of
+    // both nscoord_MAX and 0, and it's zero both ways, then it's a zero
+    // length, percent, or combination thereof.  Test > 0 so we clamp
+    // negative calc() results to 0.
+    return nsRuleNode::ComputeCoordPercentCalc(aCoord, nscoord_MAX) > 0 ||
+           nsRuleNode::ComputeCoordPercentCalc(aCoord, 0) > 0;
+  }
+  NS_ABORT_IF_FALSE(PR_FALSE, "unexpected unit for height or min-height");
+  return PR_TRUE;
+}
+
 /* virtual */ PRBool
 nsBlockFrame::IsSelfEmpty()
 {
   // Blocks which are margin-roots (including inline-blocks) cannot be treated
   // as empty for margin-collapsing and other purposes. They're more like
   // replaced elements.
   if (GetStateBits() & NS_BLOCK_MARGIN_ROOT)
     return PR_FALSE;
 
   const nsStylePosition* position = GetStylePosition();
 
-  switch (position->mMinHeight.GetUnit()) {
-    case eStyleUnit_Coord:
-      if (position->mMinHeight.GetCoordValue() != 0)
-        return PR_FALSE;
-      break;
-    case eStyleUnit_Percent:
-      if (position->mMinHeight.GetPercentValue() != 0.0f)
-        return PR_FALSE;
-      break;
-    default:
-      return PR_FALSE;
-  }
-
-  switch (position->mHeight.GetUnit()) {
-    case eStyleUnit_Auto:
-      break;
-    case eStyleUnit_Coord:
-      if (position->mHeight.GetCoordValue() != 0)
-        return PR_FALSE;
-      break;
-    case eStyleUnit_Percent:
-      if (position->mHeight.GetPercentValue() != 0.0f)
-        return PR_FALSE;
-      break;
-    default:
-      return PR_FALSE;
-  }
+  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(),
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -3097,18 +3097,17 @@ nsFrame::GetIntrinsicRatio()
 }
 
 inline PRBool
 IsAutoHeight(const nsStyleCoord &aCoord, nscoord aCBHeight)
 {
   nsStyleUnit unit = aCoord.GetUnit();
   return unit == eStyleUnit_Auto ||  // only for 'height'
          unit == eStyleUnit_None ||  // only for 'max-height'
-         (unit == eStyleUnit_Percent && 
-          aCBHeight == NS_AUTOHEIGHT);
+         (aCBHeight == NS_AUTOHEIGHT && aCoord.HasPercent());
 }
 
 /* virtual */ nsSize
 nsFrame::ComputeSize(nsIRenderingContext *aRenderingContext,
                      nsSize aCBSize, nscoord aAvailableWidth,
                      nsSize aMargin, nsSize aBorder, nsSize aPadding,
                      PRBool aShrinkWrap)
 {
@@ -3151,35 +3150,32 @@ nsFrame::ComputeSize(nsIRenderingContext
       stylePos->mMinWidth);
   if (minWidth > result.width)
     result.width = minWidth;
 
   // Compute height
 
   if (!IsAutoHeight(stylePos->mHeight, aCBSize.height)) {
     result.height =
-      nsLayoutUtils::ComputeHeightDependentValue(aCBSize.height,
-                                                 stylePos->mHeight) -
+      nsLayoutUtils::ComputeHeightValue(aCBSize.height, stylePos->mHeight) -
       boxSizingAdjust.height;
   }
 
   if (result.height != NS_UNCONSTRAINEDSIZE) {
     if (!IsAutoHeight(stylePos->mMaxHeight, aCBSize.height)) {
       nscoord maxHeight =
-        nsLayoutUtils::ComputeHeightDependentValue(aCBSize.height,
-                                                   stylePos->mMaxHeight) -
+        nsLayoutUtils::ComputeHeightValue(aCBSize.height, stylePos->mMaxHeight) -
         boxSizingAdjust.height;
       if (maxHeight < result.height)
         result.height = maxHeight;
     }
 
     if (!IsAutoHeight(stylePos->mMinHeight, aCBSize.height)) {
       nscoord minHeight =
-        nsLayoutUtils::ComputeHeightDependentValue(aCBSize.height,
-                                                   stylePos->mMinHeight) -
+        nsLayoutUtils::ComputeHeightValue(aCBSize.height, stylePos->mMinHeight) -
         boxSizingAdjust.height;
       if (minHeight > result.height)
         result.height = minHeight;
     }
   }
 
   const nsStyleDisplay *disp = GetStyleDisplay();
   if (IsThemed(disp)) {
@@ -3308,20 +3304,19 @@ nsFrame::DidReflow(nsPresContext*       
                 NS_FRAME_HAS_DIRTY_CHILDREN);
   }
 
   // Notify the percent height observer if there is a percent height.
   // The observer may be able to initiate another reflow with a computed
   // height. This happens in the case where a table cell has no computed
   // height but can fabricate one when the cell height is known.
   if (aReflowState && aReflowState->mPercentHeightObserver &&
-      (eStyleUnit_Percent == aReflowState->mStylePosition->mHeight.GetUnit())) {
-
-    nsIFrame* prevInFlow = GetPrevInFlow();
-    if (!prevInFlow) { // 1st in flow
+      !GetPrevInFlow()) {
+    const nsStyleCoord &height = aReflowState->mStylePosition->mHeight;
+    if (height.HasPercent()) {
       aReflowState->mPercentHeightObserver->NotifyPercentHeight(*aReflowState);
     }
   }
 
   return NS_OK;
 }
 
 /* virtual */ PRBool
--- a/layout/generic/nsHTMLReflowMetrics.h
+++ b/layout/generic/nsHTMLReflowMetrics.h
@@ -160,17 +160,17 @@ struct nsHTMLReflowMetrics {
   
   // For frames that have content that overflow their content area
   // (HasOverflowRect() is true) this rectangle represents the total area
   // of the frame including visible overflow, i.e., don't include overflowing
   // content that is hidden.
   // The rect is in the local coordinate space of the frame, and should be at
   // least as big as the desired size. If there is no content that overflows,
   // then the overflow area is identical to the desired size and should be
-  // {0, 0, mWidth, mHeight}.
+  // {0, 0, width, height}.
   nsRect mOverflowArea;
 
   PRUint32 mFlags;
  
   // XXXldb Should |aFlags| generally be passed from parent to child?
   // Some places do it, and some don't.  |aFlags| should perhaps go away
   // entirely.
   nsHTMLReflowMetrics(PRUint32 aFlags = 0) {
--- a/layout/generic/nsHTMLReflowState.cpp
+++ b/layout/generic/nsHTMLReflowState.cpp
@@ -390,19 +390,25 @@ nsHTMLReflowState::InitResizeFlags(nsPre
     mFlags.mVResize = mFlags.mVResize || NS_SUBTREE_DIRTY(frame);
   } else {
     // not 'auto' height
     mFlags.mVResize = frame->GetSize().height !=
                         mComputedHeight + mComputedBorderPadding.TopBottom();
   }
 
   PRBool dependsOnCBHeight =
-    mStylePosition->mHeight.GetUnit() == eStyleUnit_Percent ||
-    mStylePosition->mMinHeight.GetUnit() == eStyleUnit_Percent ||
-    mStylePosition->mMaxHeight.GetUnit() == eStyleUnit_Percent ||
+    (mStylePosition->HeightDependsOnContainer() &&
+     // FIXME: condition this on not-abspos?
+     mStylePosition->mHeight.GetUnit() != eStyleUnit_Auto) ||
+    (mStylePosition->MinHeightDependsOnContainer() &&
+     // FIXME: condition this on not-abspos?
+     mStylePosition->mMinHeight.GetUnit() != eStyleUnit_Auto) ||
+    (mStylePosition->MaxHeightDependsOnContainer() &&
+     // FIXME: condition this on not-abspos?
+     mStylePosition->mMaxHeight.GetUnit() != eStyleUnit_Auto) ||
     mStylePosition->mOffset.GetTopUnit() == eStyleUnit_Percent ||
     mStylePosition->mOffset.GetBottomUnit() != eStyleUnit_Auto ||
     frame->IsBoxFrame() ||
     (mStylePosition->mHeight.GetUnit() == eStyleUnit_Auto &&
      frame->GetIntrinsicSize().height.GetUnit() == eStyleUnit_Percent);
 
   if (mStyleText->mLineHeight.GetUnit() == eStyleUnit_Enumerated) {
     NS_ASSERTION(mStyleText->mLineHeight.GetIntValue() ==
@@ -1574,16 +1580,17 @@ nsHTMLReflowState::ComputeContainingBloc
       // If the ancestor is block-level, the containing block is formed by the
       // padding edge of the ancestor
       aContainingBlockWidth += aContainingBlockRS->mComputedPadding.LeftRight();
       aContainingBlockHeight += aContainingBlockRS->mComputedPadding.TopBottom();
     }
   } else {
     // an element in quirks mode gets a containing block based on looking for a
     // parent with a non-auto height if the element has a percent height
+    // Note: We don't emulate this quirk for percents in calc().
     if (NS_AUTOHEIGHT == aContainingBlockHeight) {
       if (eCompatibility_NavQuirks == aPresContext->CompatibilityMode() &&
           mStylePosition->mHeight.GetUnit() == eStyleUnit_Percent) {
         aContainingBlockHeight = CalcQuirkContainingBlockHeight(aContainingBlockRS);
       }
     }
   }
 }
@@ -1703,22 +1710,24 @@ nsHTMLReflowState::InitConstraints(nsPre
           // use the cell's computed height 
           aContainingBlockHeight = cbrs->mComputedHeight;
         }
       }
     }
 
     InitOffsets(aContainingBlockWidth, aBorder, aPadding);
 
-    nsStyleUnit heightUnit = mStylePosition->mHeight.GetUnit();
+    const nsStyleCoord &height = mStylePosition->mHeight;
+    nsStyleUnit heightUnit = height.GetUnit();
 
     // Check for a percentage based height and a containing block height
     // that depends on the content height
     // XXX twiddling heightUnit doesn't help anymore
-    if (eStyleUnit_Percent == heightUnit) {
+    // FIXME Shouldn't we fix that?
+    if (height.HasPercent()) {
       if (NS_AUTOHEIGHT == aContainingBlockHeight) {
         // this if clause enables %-height on replaced inline frames,
         // such as images.  See bug 54119.  The else clause "heightUnit = eStyleUnit_Auto;"
         // used to be called exclusively.
         if (NS_FRAME_REPLACED(NS_CSS_FRAME_TYPE_INLINE) == mFrameType ||
             NS_FRAME_REPLACED_CONTAINS_BLOCK(
                 NS_CSS_FRAME_TYPE_INLINE) == mFrameType) {
           // Get the containing block reflow state
@@ -1767,25 +1776,27 @@ nsHTMLReflowState::InitConstraints(nsPre
     ComputeMinMaxValues(aContainingBlockWidth, aContainingBlockHeight, cbrs);
 
     // Calculate the computed width and height. This varies by frame type
 
     if (NS_CSS_FRAME_TYPE_INTERNAL_TABLE == mFrameType) {
       // Internal table elements. The rules vary depending on the type.
       // Calculate the computed width
       PRBool rowOrRowGroup = PR_FALSE;
-      nsStyleUnit widthUnit = mStylePosition->mWidth.GetUnit();
+      const nsStyleCoord &width = mStylePosition->mWidth;
+      nsStyleUnit widthUnit = width.GetUnit();
       if ((NS_STYLE_DISPLAY_TABLE_ROW == mStyleDisplay->mDisplay) ||
           (NS_STYLE_DISPLAY_TABLE_ROW_GROUP == mStyleDisplay->mDisplay)) {
         // 'width' property doesn't apply to table rows and row groups
         widthUnit = eStyleUnit_Auto;
         rowOrRowGroup = PR_TRUE;
       }
 
-      if (eStyleUnit_Auto == widthUnit) {
+      // calc() acts like auto on internal table elements
+      if (eStyleUnit_Auto == widthUnit || width.IsCalcUnit()) {
         mComputedWidth = availableWidth;
 
         if ((mComputedWidth != NS_UNCONSTRAINEDSIZE) && !rowOrRowGroup){
           // Internal table elements don't have margins. Only tables and
           // cells have border and padding
           mComputedWidth -= mComputedBorderPadding.left +
             mComputedBorderPadding.right;
           if (mComputedWidth < 0)
@@ -1802,24 +1813,24 @@ nsHTMLReflowState::InitConstraints(nsPre
       }
 
       // Calculate the computed height
       if ((NS_STYLE_DISPLAY_TABLE_COLUMN == mStyleDisplay->mDisplay) ||
           (NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP == mStyleDisplay->mDisplay)) {
         // 'height' property doesn't apply to table columns and column groups
         heightUnit = eStyleUnit_Auto;
       }
-      if (eStyleUnit_Auto == heightUnit) {
+      // calc() acts like 'auto' on internal table elements
+      if (eStyleUnit_Auto == heightUnit || height.IsCalcUnit()) {
         mComputedHeight = NS_AUTOHEIGHT;
       } else {
         NS_ASSERTION(heightUnit == mStylePosition->mHeight.GetUnit(),
                      "unexpected height unit change");
         mComputedHeight = nsLayoutUtils::
-          ComputeHeightDependentValue(aContainingBlockHeight,
-                                      mStylePosition->mHeight);
+          ComputeHeightValue(aContainingBlockHeight, mStylePosition->mHeight);
       }
 
       // Doesn't apply to table elements
       mComputedMinWidth = mComputedMinHeight = 0;
       mComputedMaxWidth = mComputedMaxHeight = NS_UNCONSTRAINEDSIZE;
 
     } else if (NS_FRAME_GET_TYPE(mFrameType) == NS_CSS_FRAME_TYPE_ABSOLUTE) {
       // XXX not sure if this belongs here or somewhere else - cwk
@@ -2272,38 +2283,46 @@ nsHTMLReflowState::ComputeMinMaxValues(n
   // If the computed value of 'min-width' is greater than the value of
   // 'max-width', 'max-width' is set to the value of 'min-width'
   if (mComputedMinWidth > mComputedMaxWidth) {
     mComputedMaxWidth = mComputedMinWidth;
   }
 
   // Check for percentage based values and a containing block height that
   // depends on the content height. Treat them like 'auto'
-  if ((NS_AUTOHEIGHT == aContainingBlockHeight) &&
-      (eStyleUnit_Percent == mStylePosition->mMinHeight.GetUnit())) {
+  // Likewise, check for calc() on internal table elements; calc() on
+  // such elements is unsupported.
+  const nsStyleCoord &minHeight = mStylePosition->mMinHeight;
+  if ((NS_AUTOHEIGHT == aContainingBlockHeight &&
+       minHeight.HasPercent()) ||
+      (mFrameType == NS_CSS_FRAME_TYPE_INTERNAL_TABLE &&
+       minHeight.IsCalcUnit())) {
     mComputedMinHeight = 0;
   } else {
     mComputedMinHeight = nsLayoutUtils::
-      ComputeHeightDependentValue(aContainingBlockHeight,
-                                  mStylePosition->mMinHeight);
+      ComputeHeightValue(aContainingBlockHeight, minHeight);
   }
-  nsStyleUnit maxHeightUnit = mStylePosition->mMaxHeight.GetUnit();
+  const nsStyleCoord &maxHeight = mStylePosition->mMaxHeight;
+  nsStyleUnit maxHeightUnit = maxHeight.GetUnit();
   if (eStyleUnit_None == maxHeightUnit) {
     // Specified value of 'none'
     mComputedMaxHeight = NS_UNCONSTRAINEDSIZE;  // no limit
   } else {
     // Check for percentage based values and a containing block height that
     // depends on the content height. Treat them like 'auto'
-    if ((NS_AUTOHEIGHT == aContainingBlockHeight) && 
-        (eStyleUnit_Percent == maxHeightUnit)) {
+    // Likewise, check for calc() on internal table elements; calc() on
+    // such elements is unsupported.
+    if ((NS_AUTOHEIGHT == aContainingBlockHeight && 
+         maxHeight.HasPercent()) ||
+        (mFrameType == NS_CSS_FRAME_TYPE_INTERNAL_TABLE &&
+         maxHeight.IsCalcUnit())) {
       mComputedMaxHeight = NS_UNCONSTRAINEDSIZE;
     } else {
       mComputedMaxHeight = nsLayoutUtils::
-        ComputeHeightDependentValue(aContainingBlockHeight,
-                                    mStylePosition->mMaxHeight);
+        ComputeHeightValue(aContainingBlockHeight, maxHeight);
     }
   }
 
   // If the computed value of 'min-height' is greater than the value of
   // 'max-height', 'max-height' is set to the value of 'min-height'
   if (mComputedMinHeight > mComputedMaxHeight) {
     mComputedMaxHeight = mComputedMinHeight;
   }
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/height-block-1-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<title>Test for height:calc() or min-height:calc() on blocks</title>
+<style>
+body { margin: 0 }
+body > div { float: left; width: 1px; background: blue; }
+</style>
+
+<!-- tests with a fixed-height container -->
+<div style="height: 50px"></div>
+<div style="height: 50px"></div>
+<div style="height: 75px"></div>
+<div style="height: 45px"></div>
+<div style="height: 40px"></div>
+<div style="height: 30px"></div>
+
+<!-- tests with an auto-height container -->
+<div style="height: 50px"></div>
+<div style="height: 10px"></div>
+<div style="height: 10px"></div>
+<div style="height: 10px"></div>
+<div style="height: 10px"></div>
+<div style="height: 10px"></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/height-block-1.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<title>Test for height:calc() on blocks</title>
+<style>
+body { margin: 0 }
+body > div { float: left; height: 100px; width: 1px; }
+body > div > div { background: blue }
+
+/* for auto-height tests */
+body > div > div > div > div { height: 10px }
+</style>
+
+<!-- tests with a fixed-height container -->
+<div><div style="height: -moz-calc(50px)"></div></div>
+<div><div style="height: -moz-calc(50%)"></div></div>
+<div><div style="height: -moz-calc(25px + 50%)"></div></div>
+<div><div style="height: -moz-calc(150% / 2 - 30px)"></div></div>
+<div><div style="height: -moz-calc(40px + 10% - 20% / 2)"></div></div>
+<div><div style="height: -moz-calc(40px - 10%)"></div></div>
+
+<!-- tests with an auto-height container -->
+<div><div><div style="height: -moz-calc(50px)"><div></div></div></div></div>
+<div><div><div style="height: -moz-calc(50%)"><div></div></div></div></div>
+<div><div><div style="height: -moz-calc(25px + 50%)"><div></div></div></div></div>
+<div><div><div style="height: -moz-calc(150% / 2 - 30px)"><div></div></div></div></div>
+<div><div><div style="height: -moz-calc(40px + 10% - 20% / 2)"><div></div></div></div></div>
+<div><div><div style="height: -moz-calc(40px - 10%)"><div></div></div></div></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/height-table-1-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<title>Test that height:calc() has no effect on inner table elements</title>
+<table border>
+  <tbody>
+    <tr>
+      <td>cell</td>
+      <td>cell</td>
+    </tr>
+    <tr>
+      <td>cell</td>
+      <td>cell</td>
+    </tr>
+  </tbody>
+</table>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/height-table-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<title>Test that height:calc() has no effect on inner table elements</title>
+<style>
+tbody, tr, td {
+  height: -moz-calc(500px);
+  min-height: -moz-calc(1000px);
+  max-height: -moz-calc(2px);
+}
+</style>
+<table border>
+  <tbody>
+    <tr>
+      <td>cell</td>
+      <td>cell</td>
+    </tr>
+    <tr>
+      <td>cell</td>
+      <td>cell</td>
+    </tr>
+  </tbody>
+</table>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/max-height-block-1-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<title>Test for max-height:calc() on blocks</title>
+<style>
+body { margin: 0 }
+body > div { float: left; width: 1px; background: blue; }
+</style>
+
+<!-- tests with a fixed-height container -->
+<div style="height: 50px"></div>
+<div style="height: 50px"></div>
+<div style="height: 75px"></div>
+<div style="height: 45px"></div>
+<div style="height: 40px"></div>
+<div style="height: 30px"></div>
+
+<!-- tests with an auto-height container -->
+<div style="height: 50px"></div>
+<div style="height: 300px"></div>
+<div style="height: 300px"></div>
+<div style="height: 300px"></div>
+<div style="height: 300px"></div>
+<div style="height: 300px"></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/max-height-block-1.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<title>Test for max-height:calc() on blocks</title>
+<style>
+body { margin: 0 }
+body > div { float: left; height: 100px; width: 1px; }
+body > div > div { background: blue }
+
+/* to give the max-height something to restrict */
+span { display: block; height: 300px }
+</style>
+
+<!-- tests with a fixed-height container -->
+<div><div style="max-height: -moz-calc(50px)"><span></span></div></div>
+<div><div style="max-height: -moz-calc(50%)"><span></span></div></div>
+<div><div style="max-height: -moz-calc(25px + 50%)"><span></span></div></div>
+<div><div style="max-height: -moz-calc(150% / 2 - 30px)"><span></span></div></div>
+<div><div style="max-height: -moz-calc(40px + 10% - 20% / 2)"><span></span></div></div>
+<div><div style="max-height: -moz-calc(40px - 10%)"><span></span></div></div>
+
+<!-- tests with an auto-height container -->
+<div><div><div style="max-height: -moz-calc(50px)"><span></span></div></div></div>
+<div><div><div style="max-height: -moz-calc(50%)"><span></span></div></div></div>
+<div><div><div style="max-height: -moz-calc(25px + 50%)"><span></span></div></div></div>
+<div><div><div style="max-height: -moz-calc(150% / 2 - 30px)"><span></span></div></div></div>
+<div><div><div style="max-height: -moz-calc(40px + 10% - 20% / 2)"><span></span></div></div></div>
+<div><div><div style="max-height: -moz-calc(40px - 10%)"><span></span></div></div></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/min-height-block-1.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<title>Test for min-height:calc() on blocks</title>
+<style>
+body { margin: 0 }
+body > div { float: left; height: 100px; width: 1px; }
+body > div > div { background: blue }
+
+/* for auto-height tests */
+body > div > div > div > div { height: 10px }
+</style>
+
+<!-- tests with a fixed-height container -->
+<div><div style="min-height: -moz-calc(50px)"></div></div>
+<div><div style="min-height: -moz-calc(50%)"></div></div>
+<div><div style="min-height: -moz-calc(25px + 50%)"></div></div>
+<div><div style="min-height: -moz-calc(150% / 2 - 30px)"></div></div>
+<div><div style="min-height: -moz-calc(40px + 10% - 20% / 2)"></div></div>
+<div><div style="min-height: -moz-calc(40px - 10%)"></div></div>
+
+<!-- tests with an auto-height container -->
+<div><div><div style="min-height: -moz-calc(50px)"><div></div></div></div></div>
+<div><div><div style="min-height: -moz-calc(50%)"><div></div></div></div></div>
+<div><div><div style="min-height: -moz-calc(25px + 50%)"><div></div></div></div></div>
+<div><div><div style="min-height: -moz-calc(150% / 2 - 30px)"><div></div></div></div></div>
+<div><div><div style="min-height: -moz-calc(40px + 10% - 20% / 2)"><div></div></div></div></div>
+<div><div><div style="min-height: -moz-calc(40px - 10%)"><div></div></div></div></div>
--- a/layout/reftests/css-calc/reftest.list
+++ b/layout/reftests/css-calc/reftest.list
@@ -1,4 +1,8 @@
+== height-block-1.html height-block-1-ref.html
+== height-table-1.html height-table-1-ref.html
+== max-height-block-1.html max-height-block-1-ref.html
+== min-height-block-1.html height-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
@@ -5726,17 +5726,18 @@ CSSParserImpl::ParseSingleValueProperty(
   case eCSSProperty_text_rendering:
     return ParseVariant(aValue, VARIANT_HK,
                         nsCSSProps::kTextRenderingKTable);
 #endif
   case eCSSProperty_box_sizing:
     return ParseVariant(aValue, VARIANT_HK,
                         nsCSSProps::kBoxSizingKTable);
   case eCSSProperty_height:
-    return ParseNonNegativeVariant(aValue, VARIANT_AHLP, nsnull);
+    return ParseNonNegativeVariant(aValue, VARIANT_AHLP | VARIANT_CALC,
+                                   nsnull);
   case eCSSProperty_width:
     return ParseNonNegativeVariant(aValue, VARIANT_AHKLP | VARIANT_CALC,
                                    nsCSSProps::kWidthKTable);
   case eCSSProperty_force_broken_image_icon:
     return ParseNonNegativeVariant(aValue, VARIANT_HI, nsnull);
   case eCSSProperty_caption_side:
     return ParseVariant(aValue, VARIANT_HK,
                         nsCSSProps::kCaptionSideKTable);
@@ -5825,22 +5826,24 @@ CSSParserImpl::ParseSingleValueProperty(
   case eCSSProperty_margin_start_value: // for internal use
   case eCSSProperty_margin_top:
     return ParseVariant(aValue, VARIANT_AHLP, 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, nsnull);
+    return ParseNonNegativeVariant(aValue, VARIANT_HLPO | VARIANT_CALC,
+                                   nsnull);
   case eCSSProperty_max_width:
     return ParseNonNegativeVariant(aValue, VARIANT_HKLPO,
                                    nsCSSProps::kWidthKTable);
   case eCSSProperty_min_height:
-    return ParseNonNegativeVariant(aValue, VARIANT_HLP, nsnull);
+    return ParseNonNegativeVariant(aValue, VARIANT_HLP | VARIANT_CALC,
+                                   nsnull);
   case eCSSProperty_min_width:
     return ParseNonNegativeVariant(aValue, VARIANT_HKLP,
                                    nsCSSProps::kWidthKTable);
   case eCSSProperty_opacity:
     return ParseVariant(aValue, VARIANT_HN, nsnull);
   case eCSSProperty_orphans:
   case eCSSProperty_widows:
     return ParsePositiveNonZeroVariant(aValue, VARIANT_HI, nsnull);
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -5543,24 +5543,24 @@ nsRuleNode::ComputePositionData(void* aS
   SetCoord(posData.mMinWidth, pos->mMinWidth, parentPos->mMinWidth,
            SETCOORD_LPEH | SETCOORD_INITIAL_ZERO, aContext,
            mPresContext, canStoreInRuleTree);
   SetCoord(posData.mMaxWidth, pos->mMaxWidth, parentPos->mMaxWidth,
            SETCOORD_LPOEH | SETCOORD_INITIAL_NONE, aContext,
            mPresContext, canStoreInRuleTree);
 
   SetCoord(posData.mHeight, pos->mHeight, parentPos->mHeight,
-           SETCOORD_LPAH | SETCOORD_INITIAL_AUTO, aContext,
-           mPresContext, canStoreInRuleTree);
+           SETCOORD_LPAH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC,
+           aContext, mPresContext, canStoreInRuleTree);
   SetCoord(posData.mMinHeight, pos->mMinHeight, parentPos->mMinHeight,
-           SETCOORD_LPH | SETCOORD_INITIAL_ZERO, aContext,
-           mPresContext, canStoreInRuleTree);
+           SETCOORD_LPH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC,
+           aContext, mPresContext, canStoreInRuleTree);
   SetCoord(posData.mMaxHeight, pos->mMaxHeight, parentPos->mMaxHeight,
-           SETCOORD_LPOH | SETCOORD_INITIAL_NONE, aContext,
-           mPresContext, canStoreInRuleTree);
+           SETCOORD_LPOH | SETCOORD_INITIAL_NONE | SETCOORD_STORE_CALC,
+           aContext, mPresContext, canStoreInRuleTree);
 
   // box-sizing: enum, inherit, initial
   SetDiscrete(posData.mBoxSizing, pos->mBoxSizing, canStoreInRuleTree,
               SETDSC_ENUMERATED, parentPos->mBoxSizing,
               NS_STYLE_BOX_SIZING_CONTENT, 0, 0, 0, 0);
 
   // z-index
   if (! SetCoord(posData.mZIndex, pos->mZIndex, parentPos->mZIndex,
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -1084,19 +1084,19 @@ struct nsStylePosition {
   static nsChangeHint MaxDifference();
 #endif
   static PRBool ForceCompare() { return PR_TRUE; }
 
   nsStyleSides  mOffset;                // [reset] coord, percent, auto
   nsStyleCoord  mWidth;                 // [reset] coord, percent, enum, auto
   nsStyleCoord  mMinWidth;              // [reset] coord, percent, enum
   nsStyleCoord  mMaxWidth;              // [reset] coord, percent, enum, none
-  nsStyleCoord  mHeight;                // [reset] coord, percent, auto
-  nsStyleCoord  mMinHeight;             // [reset] coord, percent
-  nsStyleCoord  mMaxHeight;             // [reset] coord, percent, none
+  nsStyleCoord  mHeight;                // [reset] coord, percent, calc, auto
+  nsStyleCoord  mMinHeight;             // [reset] coord, percent, calc
+  nsStyleCoord  mMaxHeight;             // [reset] coord, percent, calc, none
   PRUint8       mBoxSizing;             // [reset] see nsStyleConsts.h
   nsStyleCoord  mZIndex;                // [reset] integer, auto
 
   PRBool WidthDependsOnContainer() const
     { return WidthCoordDependsOnContainer(mWidth); }
   PRBool MinWidthDependsOnContainer() const
     { return WidthCoordDependsOnContainer(mMinWidth); }
   PRBool MaxWidthDependsOnContainer() const
@@ -1108,17 +1108,17 @@ struct nsStylePosition {
   PRBool MaxHeightDependsOnContainer() const
     { return HeightCoordDependsOnContainer(mMaxHeight); }
 
 private:
   static PRBool WidthCoordDependsOnContainer(const nsStyleCoord &aCoord);
   static PRBool HeightCoordDependsOnContainer(const nsStyleCoord &aCoord)
   {
     return aCoord.GetUnit() == eStyleUnit_Auto || // CSS 2.1, 10.6.4, item (5)
-           aCoord.GetUnit() == eStyleUnit_Percent;
+           aCoord.HasPercent();
   }
 };
 
 struct nsStyleTextReset {
   nsStyleTextReset(void);
   nsStyleTextReset(const nsStyleTextReset& aOther);
   ~nsStyleTextReset(void);
 
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -1579,17 +1579,24 @@ var gCSSProperties = {
 	},
 	"height": {
 		domProp: "height",
 		inherited: false,
 		type: CSS_TYPE_LONGHAND,
 		initial_values: [ " auto" ],
 		/* computed value tests for height test more with display:block */
 		prerequisites: { "display": "block" },
-		other_values: [ "15px", "3em", "15%" ],
+		other_values: [ "15px", "3em", "15%",
+			"-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: [ "none", "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available" ]
 	},
 	"ime-mode": {
 		domProp: "imeMode",
 		inherited: false,
 		type: CSS_TYPE_LONGHAND,
 		initial_values: [ "auto" ],
 		other_values: [ "normal", "disabled", "active", "inactive" ],
@@ -1754,17 +1761,24 @@ var gCSSProperties = {
 		invalid_values: [ "none none", "crop none", "none crop", "cross none", "none cross" ]
 	},
 	"max-height": {
 		domProp: "maxHeight",
 		inherited: false,
 		type: CSS_TYPE_LONGHAND,
 		prerequisites: { "display": "block" },
 		initial_values: [ "none" ],
-		other_values: [ "30px", "50%", "0" ],
+		other_values: [ "30px", "50%", "0",
+			"-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: [ "auto", "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available" ]
 	},
 	"max-width": {
 		domProp: "maxWidth",
 		inherited: false,
 		type: CSS_TYPE_LONGHAND,
 		prerequisites: { "display": "block" },
 		initial_values: [ "none" ],
@@ -1772,17 +1786,24 @@ var gCSSProperties = {
 		invalid_values: [ "auto" ]
 	},
 	"min-height": {
 		domProp: "minHeight",
 		inherited: false,
 		type: CSS_TYPE_LONGHAND,
 		prerequisites: { "display": "block" },
 		initial_values: [ "0" ],
-		other_values: [ "30px", "50%" ],
+		other_values: [ "30px", "50%",
+			"-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: [ "auto", "none", "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available" ]
 	},
 	"min-width": {
 		domProp: "minWidth",
 		inherited: false,
 		type: CSS_TYPE_LONGHAND,
 		prerequisites: { "display": "block" },
 		initial_values: [ "0" ],
--- a/layout/tables/nsTableFrame.cpp
+++ b/layout/tables/nsTableFrame.cpp
@@ -1584,17 +1584,19 @@ PRBool
 nsTableFrame::AncestorsHaveStyleHeight(const nsHTMLReflowState& aParentReflowState)
 {
   for (const nsHTMLReflowState* rs = &aParentReflowState;
        rs && rs->frame; rs = rs->parentReflowState) {
     nsIAtom* frameType = rs->frame->GetType();
     if (IS_TABLE_CELL(frameType)                     ||
         (nsGkAtoms::tableRowFrame      == frameType) ||
         (nsGkAtoms::tableRowGroupFrame == frameType)) {
-      if (rs->mStylePosition->mHeight.GetUnit() != eStyleUnit_Auto) {
+      const nsStyleCoord &height = rs->mStylePosition->mHeight;
+      // calc() treated like 'auto' on internal table elements
+      if (height.GetUnit() != eStyleUnit_Auto && !height.IsCalcUnit()) {
         return PR_TRUE;
       }
     }
     else if (nsGkAtoms::tableFrame == frameType) {
       // we reached the containing table, so always return
       if (rs->mStylePosition->mHeight.GetUnit() != eStyleUnit_Auto) {
         return PR_TRUE;
       }
@@ -1603,16 +1605,21 @@ nsTableFrame::AncestorsHaveStyleHeight(c
   }
   return PR_FALSE;
 }
 
 // See if a special height reflow needs to occur and if so, call RequestSpecialHeightReflow
 void
 nsTableFrame::CheckRequestSpecialHeightReflow(const nsHTMLReflowState& aReflowState)
 {
+  NS_ASSERTION(IS_TABLE_CELL(aReflowState.frame->GetType()) ||
+               aReflowState.frame->GetType() == nsGkAtoms::tableRowFrame ||
+               aReflowState.frame->GetType() == nsGkAtoms::tableRowGroupFrame ||
+               aReflowState.frame->GetType() == nsGkAtoms::tableFrame,
+               "unexpected frame type");
   if (!aReflowState.frame->GetPrevInFlow() &&  // 1st in flow
       (NS_UNCONSTRAINEDSIZE == aReflowState.ComputedHeight() ||  // no computed height
        0                    == aReflowState.ComputedHeight()) &&
       eStyleUnit_Percent == aReflowState.mStylePosition->mHeight.GetUnit() && // pct height
       nsTableFrame::AncestorsHaveStyleHeight(*aReflowState.parentReflowState)) {
     nsTableFrame::RequestSpecialHeightReflow(aReflowState);
   }
 }
@@ -3344,36 +3351,21 @@ nsTableFrame::GetTableFrame(nsIFrame* aS
   }
   NS_NOTREACHED("unable to find table parent");
   return nsnull;
 }
 
 PRBool
 nsTableFrame::IsAutoHeight()
 {
-  PRBool isAuto = PR_TRUE;  // the default
-
-  const nsStylePosition* position = GetStylePosition();
-
-  switch (position->mHeight.GetUnit()) {
-    case eStyleUnit_Auto:         // specified auto width
-      break;
-    case eStyleUnit_Coord:
-      isAuto = PR_FALSE;
-      break;
-    case eStyleUnit_Percent:
-      if (position->mHeight.GetPercentValue() > 0.0f) {
-        isAuto = PR_FALSE;
-      }
-      break;
-    default:
-      break;
-  }
-
-  return isAuto;
+  const nsStyleCoord &height = GetStylePosition()->mHeight;
+  // Don't consider calc() here like this quirk for percent.
+  return height.GetUnit() == eStyleUnit_Auto ||
+         (height.GetUnit() == eStyleUnit_Percent &&
+          height.GetPercentValue() <= 0.0f);
 }
 
 nscoord
 nsTableFrame::CalcBorderBoxHeight(const nsHTMLReflowState& aState)
 {
   nscoord height = aState.ComputedHeight();
   if (NS_AUTOHEIGHT != height) {
     nsMargin borderPadding = GetChildAreaOffset(&aState);
--- a/layout/tables/nsTableRowFrame.cpp
+++ b/layout/tables/nsTableRowFrame.cpp
@@ -542,16 +542,17 @@ nsTableRowFrame::CalcHeight(const nsHTML
 
   const nsStylePosition* position = GetStylePosition();
   if (eStyleUnit_Coord == position->mHeight.GetUnit()) {
     SetFixedHeight(position->mHeight.GetCoordValue());
   }
   else if (eStyleUnit_Percent == position->mHeight.GetUnit()) {
     SetPctHeight(position->mHeight.GetPercentValue());
   }
+  // calc() is treated like 'auto' on table rows.
 
   for (nsIFrame* kidFrame = mFrames.FirstChild(); kidFrame;
        kidFrame = kidFrame->GetNextSibling()) {
     nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame);
     if (cellFrame) {
       nsSize desSize = cellFrame->GetDesiredSize();
       if ((NS_UNCONSTRAINEDSIZE == aReflowState.availableHeight) && !GetPrevInFlow()) {
         CalculateCellActualHeight(cellFrame, desSize.height);
@@ -670,17 +671,17 @@ nsTableRowFrame::CalculateCellActualHeig
       break;
     case eStyleUnit_Percent: {
       if (1 == rowSpan) 
         SetPctHeight(position->mHeight.GetPercentValue());
       // pct heights are handled when all of the cells are finished, so don't set specifiedHeight 
       break;
     }
     case eStyleUnit_Auto:
-    default:
+    default: // includes calc(), which we treat like 'auto'
       break;
   }
 
   // If the specified height is greater than the desired height, then use the specified height
   if (specifiedHeight > aDesiredHeight)
     aDesiredHeight = specifiedHeight;
 
   return NS_OK;
@@ -1400,18 +1401,20 @@ void nsTableRowFrame::InitHasCellWithSty
 
   for (nsIFrame* kidFrame = iter.First(); kidFrame; kidFrame = iter.Next()) {
     nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame);
     if (!cellFrame) {
       NS_NOTREACHED("Table row has a non-cell child.");
       continue;
     }
     // Ignore row-spanning cells
+    const nsStyleCoord &cellHeight = cellFrame->GetStylePosition()->mHeight;
     if (aTableFrame->GetEffectiveRowSpan(*cellFrame) == 1 &&
-        cellFrame->GetStylePosition()->mHeight.GetUnit() != eStyleUnit_Auto) {
+        cellHeight.GetUnit() != eStyleUnit_Auto &&
+        !cellHeight.IsCalcUnit() /* calc() treated like 'auto' */) {
       AddStateBits(NS_ROW_HAS_CELL_WITH_STYLE_HEIGHT);
       return;
     }
   }
   RemoveStateBits(NS_ROW_HAS_CELL_WITH_STYLE_HEIGHT);
 }
 
 /* ----- global methods ----- */
--- a/layout/xul/base/src/nsBox.cpp
+++ b/layout/xul/base/src/nsBox.cpp
@@ -682,25 +682,36 @@ nsIBox::AddCSSPrefSize(nsIBox* aBox, nsS
     const nsStyleCoord &width = position->mWidth;
     if (width.GetUnit() == eStyleUnit_Coord) {
         aSize.width = width.GetCoordValue();
         aWidthSet = PR_TRUE;
     } else if (width.IsCalcUnit()) {
         if (!width.CalcHasPercent()) {
             // pass 0 for percentage basis since we know there are no %s
             aSize.width = nsRuleNode::ComputeComputedCalc(width, 0);
+            if (aSize.width < 0)
+                aSize.width = 0;
             aWidthSet = PR_TRUE;
         }
     }
 
-    if (position->mHeight.GetUnit() == eStyleUnit_Coord) {
-        aSize.height = position->mHeight.GetCoordValue();     
+    const nsStyleCoord &height = position->mHeight;
+    if (height.GetUnit() == eStyleUnit_Coord) {
+        aSize.height = height.GetCoordValue();
         aHeightSet = PR_TRUE;
+    } else if (height.IsCalcUnit()) {
+        if (!height.CalcHasPercent()) {
+            // pass 0 for percentage basis since we know there are no %s
+            aSize.height = nsRuleNode::ComputeComputedCalc(height, 0);
+            if (aSize.height < 0)
+                aSize.height = 0;
+            aHeightSet = PR_TRUE;
+        }
     }
-    
+
     nsIContent* content = aBox->GetContent();
     // ignore 'height' and 'width' attributes if the actual element is not XUL
     // For example, we might be magic XUL frames whose primary content is an HTML
     // <select>
     if (content && content->IsXUL()) {
         nsAutoString value;
         PRInt32 error;
 
@@ -775,28 +786,33 @@ nsIBox::AddCSSMinSize(nsBoxLayoutState& 
         aSize.width = 0;
         aWidthSet = PR_TRUE;
     }
     // XXX Handle eStyleUnit_Enumerated?
     // (Handling the eStyleUnit_Enumerated types requires
     // GetPrefSize/GetMinSize methods that don't consider
     // (min-/max-/)(width/height) properties.
 
-    if (position->mMinHeight.GetUnit() == eStyleUnit_Coord) {
-        nscoord min = position->mMinHeight.GetCoordValue();
-        if (min && (!aHeightSet || (min > aSize.height && canOverride))) {
+    const nsStyleCoord &minHeight = position->mMinHeight;
+    if ((minHeight.GetUnit() == eStyleUnit_Coord &&
+         minHeight.GetCoordValue() != 0) ||
+        (minHeight.IsCalcUnit() && !minHeight.CalcHasPercent())) {
+        nscoord min = nsRuleNode::ComputeCoordPercentCalc(minHeight, 0);
+        if (!aHeightSet || (min > aSize.height && canOverride)) {
            aSize.height = min;
            aHeightSet = PR_TRUE;
         }
-    } else if (position->mMinHeight.GetUnit() == eStyleUnit_Percent) {
+    } else if (minHeight.GetUnit() == eStyleUnit_Percent) {
         NS_ASSERTION(position->mMinHeight.GetPercentValue() == 0.0f,
           "Non-zero percentage values not currently supported");
         aSize.height = 0;
-        aHeightSet = PR_TRUE;
+        aHeightSet = PR_TRUE; // FIXME: should we really do this for
+                              // nonzero values?
     }
+    // calc() with percentage is treated like '0' (unset)
 
     nsIContent* content = aBox->GetContent();
     if (content) {
         nsAutoString value;
         PRInt32 error;
 
         content->GetAttr(kNameSpaceID_None, nsGkAtoms::minwidth, value);
         if (!value.IsEmpty())
@@ -842,20 +858,22 @@ nsIBox::AddCSSMaxSize(nsIBox* aBox, nsSi
     // (Handling the eStyleUnit_Enumerated types requires
     // GetPrefSize/GetMinSize methods that don't consider
     // (min-/max-/)(width/height) properties.)
     if (position->mMaxWidth.GetUnit() == eStyleUnit_Coord) {
         aSize.width = position->mMaxWidth.GetCoordValue();
         aWidthSet = PR_TRUE;
     }
 
-    if (position->mMaxHeight.GetUnit() == eStyleUnit_Coord) {
-        aSize.height = position->mMaxHeight.GetCoordValue();
+    const nsStyleCoord &maxHeight = position->mMaxHeight;
+    if (maxHeight.ConvertsToLength()) {
+        aSize.height = nsRuleNode::ComputeCoordPercentCalc(maxHeight, 0);
         aHeightSet = PR_TRUE;
     }
+    // percentages and calc() with percentages are treated like 'none'
 
     nsIContent* content = aBox->GetContent();
     if (content) {
         nsAutoString value;
         PRInt32 error;
 
         content->GetAttr(kNameSpaceID_None, nsGkAtoms::maxwidth, value);
         if (!value.IsEmpty()) {