Add support for calc() to the 'width' property. (Bug 585715) r=bzbarsky a2.0=blocking2.0+
authorL. David Baron <dbaron@dbaron.org>
Wed, 11 Aug 2010 12:32:53 -0700
changeset 49566 d6326ce2ea4ca7cc219a0b87552a6c910c40de7c
parent 49565 bffe7ef73b3a878bd3d47cd05a6564d0527d331e
child 49567 058caf257ad02e0b7bcb335af39182a4163427a9
push idunknown
push userunknown
push dateunknown
reviewersbzbarsky
bugs585715
milestone2.0b4pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Add support for calc() to the 'width' property. (Bug 585715) r=bzbarsky a2.0=blocking2.0+
layout/base/nsLayoutUtils.cpp
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsImageFrame.cpp
layout/generic/nsSpacerFrame.cpp
layout/reftests/css-calc/reftest.list
layout/reftests/css-calc/width-block-1-ref.html
layout/reftests/css-calc/width-block-1.html
layout/reftests/css-calc/width-block-intrinsic-1-ref.html
layout/reftests/css-calc/width-block-intrinsic-1.html
layout/reftests/css-calc/width-table-auto-1-ref.html
layout/reftests/css-calc/width-table-auto-1.html
layout/reftests/css-calc/width-table-fixed-1-ref.html
layout/reftests/css-calc/width-table-fixed-1.html
layout/reftests/reftest.list
layout/style/nsCSSParser.cpp
layout/style/nsRuleNode.cpp
layout/style/nsStyleCoord.cpp
layout/style/nsStyleCoord.h
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/test/property_database.js
layout/svg/base/src/nsSVGOuterSVGFrame.cpp
layout/tables/BasicTableLayoutStrategy.cpp
layout/tables/FixedTableLayoutStrategy.cpp
layout/xul/base/src/nsBox.cpp
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -104,16 +104,17 @@
 #endif
 
 #ifdef MOZ_XUL
 #include "nsXULPopupManager.h"
 #endif
 
 using namespace mozilla::layers;
 using namespace mozilla::dom;
+namespace css = mozilla::css;
 
 /**
  * A namespace class for static layout utilities.
  */
 
 nsIFrame*
 nsLayoutUtils::GetLastContinuationWithChild(nsIFrame* aFrame)
 {
@@ -1747,16 +1748,25 @@ static nscoord AddPercents(nsLayoutUtils
     else
       result = NSToCoordRound(float(result) / (1.0f - aPercent));
   }
   return result;
 }
 
 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);
+    return PR_TRUE;
+  }
+
   if (eStyleUnit_Coord != aStyle.GetUnit())
     return PR_FALSE;
 
   aResult = aStyle.GetCoordValue();
   return PR_TRUE;
 }
 
 static PRBool
@@ -2031,22 +2041,31 @@ nsLayoutUtils::IntrinsicForContainer(nsI
   pctTotal += pctOutsideWidth;
 
   nscoord w;
   if (GetAbsoluteCoord(styleWidth, w) ||
       GetIntrinsicCoord(styleWidth, aRenderingContext, aFrame,
                         PROP_WIDTH, w)) {
     result = AddPercents(aType, w + coordOutsideWidth, pctOutsideWidth);
   }
-  else if (aType == MIN_WIDTH && eStyleUnit_Percent == styleWidth.GetUnit() &&
+  else if (aType == MIN_WIDTH &&
+           // The only cases of coord-percent-calc() units that
+           // GetAbsoluteCoord didn't handle are percent and calc()s
+           // containing percent.
+           styleWidth.IsCoordPercentCalcUnit() &&
            aFrame->IsFrameOfType(nsIFrame::eReplaced)) {
     // A percentage width on replaced elements means they can shrink to 0.
     result = 0; // let |min| handle padding/border/margin
   }
   else {
+    // NOTE: We could really do a lot better for percents and for some
+    // cases of calc() containing percent (certainly including any where
+    // the coefficient on the percent is positive and there are no max()
+    // expressions).  However, doing better for percents wouldn't be
+    // backwards compatible.
     result = AddPercents(aType, result, pctTotal);
   }
 
   nscoord maxw;
   if (GetAbsoluteCoord(styleMaxWidth, maxw) ||
       GetIntrinsicCoord(styleMaxWidth, aRenderingContext, aFrame,
                         PROP_MAX_WIDTH, maxw)) {
     maxw = AddPercents(aType, maxw + coordOutsideWidth, pctOutsideWidth);
@@ -2133,25 +2152,22 @@ nsLayoutUtils::ComputeWidthValue(
   NS_WARN_IF_FALSE(aContainingBlockWidth != NS_UNCONSTRAINEDSIZE,
                    "have unconstrained width; this should only result from "
                    "very large sizes, not attempts at intrinsic width "
                    "calculation");
   NS_PRECONDITION(aContainingBlockWidth >= 0,
                   "width less than zero");
 
   nscoord result;
-  if (eStyleUnit_Coord == aCoord.GetUnit()) {
-    result = aCoord.GetCoordValue();
-    NS_ASSERTION(result >= 0, "width less than zero");
+  if (aCoord.IsCoordPercentCalcUnit()) {
+    result = nsRuleNode::ComputeCoordPercentCalc(aCoord, aContainingBlockWidth);
+    // The result of a calc() expression might be less than 0; we
+    // should clamp at runtime (below).  (Percentages and coords that
+    // are less than 0 have already been dropped by the parser.)
     result -= aContentEdgeToBoxSizing;
-  } else if (eStyleUnit_Percent == aCoord.GetUnit()) {
-    NS_ASSERTION(aCoord.GetPercentValue() >= 0.0f, "width less than zero");
-    result = NSToCoordFloorClamped(aContainingBlockWidth *
-                                   aCoord.GetPercentValue()) -
-             aContentEdgeToBoxSizing;
   } else if (eStyleUnit_Enumerated == aCoord.GetUnit()) {
     PRInt32 val = aCoord.GetIntValue();
     switch (val) {
       case NS_STYLE_WIDTH_MAX_CONTENT:
         result = aFrame->GetPrefWidth(aRenderingContext);
         NS_ASSERTION(result >= 0, "width less than zero");
         break;
       case NS_STYLE_WIDTH_MIN_CONTENT:
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -1152,39 +1152,16 @@ nsXULScrollFrame::GetMaxSize(nsBoxLayout
   nsSize maxSize(NS_INTRINSICSIZE, NS_INTRINSICSIZE);
 
   AddBorderAndPadding(maxSize);
   PRBool widthSet, heightSet;
   nsIBox::AddCSSMaxSize(this, maxSize, widthSet, heightSet);
   return maxSize;
 }
 
-#if 0 // XXXldb I don't think this is even needed
-/* virtual */ nscoord
-nsXULScrollFrame::GetMinWidth(nsIRenderingContext *aRenderingContext)
-{
-  nsStyleUnit widthUnit = GetStylePosition()->mWidth.GetUnit();
-  if (widthUnit == eStyleUnit_Percent || widthUnit == eStyleUnit_Auto) {
-    nsMargin border = aReflowState.mComputedBorderPadding;
-    aDesiredSize.mMaxElementWidth = border.right + border.left;
-    mMaxElementWidth = aDesiredSize.mMaxElementWidth;
-  } else {
-    NS_NOTYETIMPLEMENTED("Use the info from the scrolled frame");
-#if 0
-    // if not set then use the cached size. If set then set it.
-    if (aDesiredSize.mMaxElementWidth == -1)
-      aDesiredSize.mMaxElementWidth = mMaxElementWidth;
-    else
-      mMaxElementWidth = aDesiredSize.mMaxElementWidth;
-#endif
-  }
-  return 0;
-}
-#endif
-
 #ifdef NS_DEBUG
 NS_IMETHODIMP
 nsXULScrollFrame::GetFrameName(nsAString& aResult) const
 {
   return MakeFrameName(NS_LITERAL_STRING("XULScroll"), aResult);
 }
 #endif
 
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -120,42 +120,41 @@ nsImageFrame::IconLoad* nsImageFrame::gI
 nsIIOService* nsImageFrame::sIOService;
 
 // test if the width and height are fixed, looking at the style data
 static PRBool HaveFixedSize(const nsStylePosition* aStylePosition)
 {
   // check the width and height values in the reflow state's style struct
   // - if width and height are specified as either coord or percentage, then
   //   the size of the image frame is constrained
-  nsStyleUnit widthUnit = aStylePosition->mWidth.GetUnit();
-  nsStyleUnit heightUnit = aStylePosition->mHeight.GetUnit();
-
-  return ((widthUnit  == eStyleUnit_Coord ||
-           widthUnit  == eStyleUnit_Percent) &&
-          (heightUnit == eStyleUnit_Coord ||
-           heightUnit == eStyleUnit_Percent));
+  return aStylePosition->mWidth.IsCoordPercentCalcUnit() &&
+         aStylePosition->mHeight.IsCoordPercentCalcUnit();
 }
 // use the data in the reflow state to decide if the image has a constrained size
 // (i.e. width and height that are based on the containing block size and not the image size) 
 // so we can avoid animated GIF related reflows
 inline PRBool HaveFixedSize(const nsHTMLReflowState& aReflowState)
 { 
   NS_ASSERTION(aReflowState.mStylePosition, "crappy reflowState - null stylePosition");
   // when an image has percent css style height or width, but ComputedHeight() 
   // or ComputedWidth() of reflow state is  NS_UNCONSTRAINEDSIZE  
   // it needs to return PR_FALSE to cause an incremental reflow later
   // if an image is inside table like bug 156731 simple testcase III, 
   // during pass 1 reflow, ComputedWidth() is NS_UNCONSTRAINEDSIZE
   // in pass 2 reflow, ComputedWidth() is 0, it also needs to return PR_FALSE
   // see bug 156731
-  nsStyleUnit heightUnit = (*(aReflowState.mStylePosition)).mHeight.GetUnit();
-  nsStyleUnit widthUnit = (*(aReflowState.mStylePosition)).mWidth.GetUnit();
-  return ((eStyleUnit_Percent == heightUnit && NS_UNCONSTRAINEDSIZE == aReflowState.ComputedHeight()) ||
-          (eStyleUnit_Percent == widthUnit && (NS_UNCONSTRAINEDSIZE == aReflowState.ComputedWidth() ||
-           0 == aReflowState.ComputedWidth())))
+  const nsStyleCoord &height = aReflowState.mStylePosition->mHeight;
+  const nsStyleCoord &width = aReflowState.mStylePosition->mWidth;
+  return (((eStyleUnit_Percent == height.GetUnit() ||
+            (height.IsCalcUnit() && height.CalcHasPercent())) &&
+           NS_UNCONSTRAINEDSIZE == aReflowState.ComputedHeight()) ||
+          ((eStyleUnit_Percent == width.GetUnit() ||
+            (width.IsCalcUnit() && width.CalcHasPercent())) &&
+           (NS_UNCONSTRAINEDSIZE == aReflowState.ComputedWidth() ||
+            0 == aReflowState.ComputedWidth())))
           ? PR_FALSE
           : HaveFixedSize(aReflowState.mStylePosition); 
 }
 
 nsIFrame*
 NS_NewImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsImageFrame(aContext);
--- a/layout/generic/nsSpacerFrame.cpp
+++ b/layout/generic/nsSpacerFrame.cpp
@@ -167,16 +167,17 @@ SpacerFrame::GetDesiredSize(nsHTMLReflow
     if (eStyleUnit_Coord == unit) {
       aMetrics.width = position->mWidth.GetCoordValue();
     }
     else if (eStyleUnit_Percent == unit) 
     {
       float factor = position->mWidth.GetPercentValue();
       aMetrics.width = NSToCoordRound(factor * aPercentBase.width);
     }
+    // else treat enumerated values and calc() like 'auto'
 
     // height
     unit = position->mHeight.GetUnit();
     if (eStyleUnit_Coord == unit) {
       aMetrics.height = position->mHeight.GetCoordValue();
     }
     else if (eStyleUnit_Percent == unit) 
     {
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/reftest.list
@@ -0,0 +1,4 @@
+== 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
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/width-block-1-ref.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<title>width: calc() on blocks</title>
+<style>
+
+body { width: 500px }
+p { background: green; color: white; margin: 1px 0; font-size: 10px }
+
+</style>
+
+<p style="width: 247px">50% - 3px</p>
+<p style="width: 247px">25% - 3px + 25%</p>
+<p style="width: 247px">25% - 3px + 12.5% * 2</p>
+<p style="width: 247px">25% - 3px + 12.5%*2</p>
+<p style="width: 247px">25% - 3px + 2*12.5%</p>
+<p style="width: 247px">25% - 3px + 2 * 12.5%</p>
+<p style="width: 125px">min(25%, 150px)</p>
+<p style="width: 100px">min(25%, 100px)</p>
+<p style="width: 150px">max(25%, 150px)</p>
+<p style="width: 125px">max(25%, 100px)</p>
+<p style="width: 150px">min(25%, 150px) + 5%</p>
+<p style="width: 125px">min(25%, 100px) + 5%</p>
+<p style="width: 175px">max(25%, 150px) + 5%</p>
+<p style="width: 150px">max(25%, 100px) + 5%</p>
+<p style="width: 105px">min(25%, 150px) - 2em</p>
+<p style="width: 80px">min(25%, 100px) - 2em</p>
+<p style="width: 130px">max(25%, 150px) - 2em</p>
+<p style="width: 105px">max(25%, 100px) - 2em</p>
+<p style="width: 250px">30% + 20%</p>
+<p style="width: 250px">30% + max(20%, 1px)</p>
+<p style="width: 250px">max(25%, 50%)</p>
+<p style="width: 125px">max(25%, 50%)</p>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/width-block-1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<title>width: calc() on blocks</title>
+<style>
+
+body { width: 500px }
+p { background: green; color: white; margin: 1px 0; font-size: 10px }
+
+</style>
+
+<p style="width: -moz-calc(50% - 3px)">50% - 3px</p>
+<p style="width: -moz-calc(25% - 3px + 25%)">25% - 3px + 25%</p>
+<p style="width: -moz-calc(25% - 3px + 12.5% * 2)">25% - 3px + 12.5% * 2</p>
+<p style="width: -moz-calc(25% - 3px + 12.5%*2)">25% - 3px + 12.5%*2</p>
+<p style="width: -moz-calc(25% - 3px + 2*12.5%)">25% - 3px + 2*12.5%</p>
+<p style="width: -moz-calc(25% - 3px + 2 * 12.5%)">25% - 3px + 2 * 12.5%</p>
+<p style="width: -moz-min(25%, 150px)">min(25%, 150px)</p>
+<p style="width: -moz-calc(min(25%, 100px))">min(25%, 100px)</p>
+<p style="width: -moz-calc(max(25%, 150px))">max(25%, 150px)</p>
+<p style="width: -moz-max(25%, 100px)">max(25%, 100px)</p>
+<p style="width: -moz-calc(min(25%, 150px) + 5%)">min(25%, 150px) + 5%</p>
+<p style="width: -moz-calc(min(25%, 100px) + 5%)">min(25%, 100px) + 5%</p>
+<p style="width: -moz-calc(max(25%, 150px) + 5%)">max(25%, 150px) + 5%</p>
+<p style="width: -moz-calc(max(25%, 100px) + 5%)">max(25%, 100px) + 5%</p>
+<p style="width: -moz-calc(min(25%, 150px) - 2em)">min(25%, 150px) - 2em</p>
+<p style="width: -moz-calc(min(25%, 100px) - 2em)">min(25%, 100px) - 2em</p>
+<p style="width: -moz-calc(max(25%, 150px) - 2em)">max(25%, 150px) - 2em</p>
+<p style="width: -moz-calc(max(25%, 100px) - 2em)">max(25%, 100px) - 2em</p>
+<p style="width: -moz-calc(30% + 20%)">30% + 20%</p>
+<p style="width: -moz-calc(30% + max(20%, 1px))">30% + max(20%, 1px)</p>
+<p style="width: -moz-calc(max(25%, 50%))">max(25%, 50%)</p>
+<p style="width: -moz-calc(min(25%, 50%))">max(25%, 50%)</p>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/width-block-intrinsic-1-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<title>intrinsic width of width: calc() on blocks</title>
+<style>
+
+body > div { margin: 0 0 1px 0; background: blue; color: white; height: 5px }
+
+</style>
+
+<div style="width: 200px"></div>
+<div style="width: 47px"></div>
+<div style="width: 47px"></div>
+<div style="width: 200px"></div>
+<div style="width: 200px"></div>
+<div style="width: 50px"></div>
+<div style="width: 200px"></div>
+<div style="width: 200px"></div>
+<div style="width: 50px"></div>
+<div style="width: 200px"></div>
+<div style="width: 200px"></div>
+<div style="width: 200px"></div>
+<div style="width: 200px"></div>
+<div style="width: 200px"></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/width-block-intrinsic-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<title>intrinsic width of width: calc() on blocks</title>
+<style>
+
+body { font-size: 10px }
+body > div { float: left; clear: left;
+             margin: 0 0 1px 0; background: blue; color: white; height: 5px }
+body > div > div > div { width: 200px }
+
+</style>
+
+<div><div style="width: -moz-calc(50% - 3px)"><div></div></div></div>
+<div><div style="width: -moz-calc(5em - 3px)"><div></div></div></div>
+<div><div style="width: -moz-calc(max(5em, 0) - 3px)"><div></div></div></div>
+<div><div style="width: -moz-calc(max(5em, 0%) - 3px)"><div></div></div></div>
+<div><div style="width: -moz-calc(5em - min(3px, 0%))"><div></div></div></div>
+<div><div style="width: -moz-calc(5em - min(3px, 0))"><div></div></div></div>
+<div><div style="width: -moz-calc(5em - 0%)"><div></div></div></div>
+<div><div style="width: -moz-calc(50%)"><div></div></div></div>
+<div><div style="width: -moz-calc(50px)"><div></div></div></div>
+<div><div style="width: -moz-calc(25% + 25%)"><div></div></div></div>
+<div><div style="width: -moz-calc(min(25%, 50%))"><div></div></div></div>
+<div><div style="width: -moz-calc(max(25%, 50%))"><div></div></div></div>
+<div><div style="width: -moz-calc(min(25%, 100px))"><div></div></div></div>
+<div><div style="width: -moz-calc(max(25%, 100px))"><div></div></div></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/width-table-auto-1-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<title>width: calc() on table-layout: auto tables</title>
+<table border>
+  <tr>
+    <td>x</td>
+    <td style="width: 100px">y</td>
+</table>
+<table border>
+  <tr>
+    <td>x</td>
+    <td style="width: 100px">y</td>
+</table>
+<table border>
+  <tr>
+    <td>x</td>
+    <td style="width: 100px">y</td>
+</table>
+<table border>
+  <tr>
+    <td>x</td>
+    <td style="width: 100px">y</td>
+</table>
+<table border>
+  <tr>
+    <td>x</td>
+    <td style="width: 100px">y</td>
+</table>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/width-table-auto-1.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<title>width: calc() on table-layout: auto tables</title>
+<table border>
+  <tr>
+    <td style="width: -moz-calc(500px)">x</td>
+    <td style="width: 100px">y</td>
+</table>
+<table border>
+  <tr>
+    <td style="width: -moz-calc(50%)">x</td>
+    <td style="width: 100px">y</td>
+</table>
+<table border>
+  <tr>
+    <td style="width: -moz-calc(min(50%, 500px))">x</td>
+    <td style="width: 100px">y</td>
+</table>
+<table border>
+  <tr>
+    <td style="width: -moz-calc(max(50%, 500px))">x</td>
+    <td style="width: 100px">y</td>
+</table>
+<table border>
+  <tr>
+    <td style="width: -moz-calc(2 * 10% + 0.5 * 500px)">x</td>
+    <td style="width: 100px">y</td>
+</table>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/width-table-fixed-1-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<title>width: calc() on table-layout: auto tables</title>
+<style>
+table { table-layout: fixed; width: 500px; border-spacing: 0 }
+</style>
+<table border>
+  <tr>
+    <td>x</td>
+    <td style="width: 100px">y</td>
+</table>
+<table border>
+  <tr>
+    <td>x</td>
+    <td style="width: 100px">y</td>
+</table>
+<table border>
+  <tr>
+    <td>x</td>
+    <td style="width: 100px">y</td>
+</table>
+<table border>
+  <tr>
+    <td>x</td>
+    <td style="width: 100px">y</td>
+</table>
+<table border>
+  <tr>
+    <td>x</td>
+    <td style="width: 100px">y</td>
+</table>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/width-table-fixed-1.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<title>width: calc() on table-layout: auto tables</title>
+<style>
+table { table-layout: fixed; width: 500px; border-spacing: 0 }
+</style>
+<table border>
+  <tr>
+    <td style="width: -moz-calc(500px)">x</td>
+    <td style="width: 100px">y</td>
+</table>
+<table border>
+  <tr>
+    <td style="width: -moz-calc(50%)">x</td>
+    <td style="width: 100px">y</td>
+</table>
+<table border>
+  <tr>
+    <td style="width: -moz-calc(min(50%, 500px))">x</td>
+    <td style="width: 100px">y</td>
+</table>
+<table border>
+  <tr>
+    <td style="width: -moz-calc(max(50%, 500px))">x</td>
+    <td style="width: 100px">y</td>
+</table>
+<table border>
+  <tr>
+    <td style="width: -moz-calc(2 * 10% + 0.5 * 500px)">x</td>
+    <td style="width: 100px">y</td>
+</table>
--- a/layout/reftests/reftest.list
+++ b/layout/reftests/reftest.list
@@ -36,25 +36,28 @@ include box-properties/reftest.list
 include box-shadow/reftest.list
 
 # bugs/
 include bugs/reftest.list
 
 # canvas 2D
 include canvas/reftest.list
 
+# css calc() tests
+include css-calc/reftest.list
+
+# css character encoding tests
+include css-charset/reftest.list
+
 # css default pseudo class tests
 include css-default/reftest.list
 
 # css @import tests
 include css-import/reftest.list
 
-# css character encoding tests
-include css-charset/reftest.list
-
 # css gradients
 include css-gradients/reftest.list
 
 # css media queries (tests for print mode)
 include css-mediaqueries/reftest.list
 
 # css namespaces
 include css-namespace/reftest.list
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -5739,17 +5739,17 @@ CSSParserImpl::ParseSingleValueProperty(
                         nsCSSProps::kTextRenderingKTable);
 #endif
   case eCSSProperty_box_sizing:
     return ParseVariant(aValue, VARIANT_HK,
                         nsCSSProps::kBoxSizingKTable);
   case eCSSProperty_height:
     return ParseNonNegativeVariant(aValue, VARIANT_AHLP, nsnull);
   case eCSSProperty_width:
-    return ParseNonNegativeVariant(aValue, VARIANT_AHKLP,
+    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);
   case eCSSProperty_clear:
     return ParseVariant(aValue, VARIANT_HK,
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -5526,18 +5526,18 @@ nsRuleNode::ComputePositionData(void* aS
     if (SetCoord(posData.mOffset.*(nsCSSRect::sides[side]),
                  coord, parentCoord, SETCOORD_LPAH | SETCOORD_INITIAL_AUTO,
                  aContext, mPresContext, canStoreInRuleTree)) {
       pos->mOffset.Set(side, coord);
     }
   }
 
   SetCoord(posData.mWidth, pos->mWidth, parentPos->mWidth,
-           SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO, aContext,
-           mPresContext, canStoreInRuleTree);
+           SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC,
+           aContext, mPresContext, canStoreInRuleTree);
   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,
--- a/layout/style/nsStyleCoord.cpp
+++ b/layout/style/nsStyleCoord.cpp
@@ -206,16 +206,34 @@ nsStyleCoord::GetAngleValueInRadians() c
   case eStyleUnit_Grad:   return angle * M_PI / 200.0;
 
   default:
     NS_NOTREACHED("unrecognized angular unit");
     return 0.0;
   }
 }
 
+PRBool
+nsStyleCoord::CalcHasPercent() const
+{
+  NS_ABORT_IF_FALSE(IsCalcUnit(), "caller should check IsCalcUnit()");
+  nsStyleCoord::Array *a = GetArrayValue();
+  for (size_t i = 0, i_end = a->Count(); i < i_end; ++i) {
+    const nsStyleCoord &v = a->Item(i);
+    if (v.GetUnit() == eStyleUnit_Percent) {
+      return PR_TRUE;
+    }
+    if (v.IsCalcUnit() && v.CalcHasPercent()) {
+      return PR_TRUE;
+    }
+  }
+  return PR_FALSE;
+}
+
+
 inline void*
 nsStyleCoord::Array::operator new(size_t aSelfSize,
                                   nsStyleContext *aAllocationContext,
                                   size_t aItemCount) CPP_THROW_NEW
 {
   NS_ABORT_IF_FALSE(aItemCount > 0, "cannot have 0 item count");
   return aAllocationContext->Alloc(
            aSelfSize + sizeof(nsStyleCoord) * (aItemCount - 1));
--- a/layout/style/nsStyleCoord.h
+++ b/layout/style/nsStyleCoord.h
@@ -119,16 +119,26 @@ public:
   PRBool IsAngleValue() const {
     return eStyleUnit_Degree <= mUnit && mUnit <= eStyleUnit_Radian;
   }
 
   PRBool IsCalcUnit() const {
     return eStyleUnit_Calc <= mUnit && mUnit <= eStyleUnit_Calc_Maximum;
   }
 
+  PRBool IsCoordPercentCalcUnit() const {
+    return mUnit == eStyleUnit_Coord ||
+           mUnit == eStyleUnit_Percent ||
+           IsCalcUnit();
+  }
+
+  // Does this calc() expression have any percentages inside it?  Can be
+  // called only when IsCalcUnit() is true.
+  PRBool CalcHasPercent() const;
+
   PRBool IsArrayValue() const {
     return IsCalcUnit();
   }
 
   nscoord     GetCoordValue() const;
   PRInt32     GetIntValue() const;
   float       GetPercentValue() const;
   float       GetFactorValue() const;
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1141,16 +1141,27 @@ nsChangeHint nsStylePosition::CalcDiffer
 #ifdef DEBUG
 /* static */
 nsChangeHint nsStylePosition::MaxDifference()
 {
   return NS_STYLE_HINT_REFLOW;
 }
 #endif
 
+/* static */ PRBool
+nsStylePosition::WidthCoordDependsOnContainer(const nsStyleCoord &aCoord)
+{
+  return aCoord.GetUnit() == eStyleUnit_Auto ||
+         aCoord.GetUnit() == eStyleUnit_Percent ||
+         (aCoord.IsCalcUnit() && aCoord.CalcHasPercent()) ||
+         (aCoord.GetUnit() == eStyleUnit_Enumerated &&
+          (aCoord.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT ||
+           aCoord.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE));
+}
+
 // --------------------
 // nsStyleTable
 //
 
 nsStyleTable::nsStyleTable() 
 { 
   MOZ_COUNT_CTOR(nsStyleTable);
   // values not inherited
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -1070,24 +1070,17 @@ struct nsStylePosition {
   PRBool HeightDependsOnContainer() const
     { return HeightCoordDependsOnContainer(mHeight); }
   PRBool MinHeightDependsOnContainer() const
     { return HeightCoordDependsOnContainer(mMinHeight); }
   PRBool MaxHeightDependsOnContainer() const
     { return HeightCoordDependsOnContainer(mMaxHeight); }
 
 private:
-  static PRBool WidthCoordDependsOnContainer(const nsStyleCoord &aCoord)
-  {
-    return aCoord.GetUnit() == eStyleUnit_Auto ||
-           aCoord.GetUnit() == eStyleUnit_Percent ||
-           (aCoord.GetUnit() == eStyleUnit_Enumerated &&
-            (aCoord.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT ||
-             aCoord.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE));
-  }
+  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;
   }
 };
 
 struct nsStyleTextReset {
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -2280,18 +2280,73 @@ var gCSSProperties = {
 	"width": {
 		domProp: "width",
 		inherited: false,
 		type: CSS_TYPE_LONGHAND,
 		/* computed value tests for width test more with display:block */
 		prerequisites: { "display": "block" },
 		initial_values: [ " auto" ],
 		/* XXX these have prerequisites */
-		other_values: [ "15px", "3em", "15%", "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available" ],
-		invalid_values: [ "none", "-2px" ]
+		other_values: [ "15px", "3em", "15%", "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available",
+			/* valid calc() values */
+			"-moz-calc(-2px)",
+			"-moz-calc(2px)",
+			"-moz-calc(50%)",
+			"-moz-calc(50% + 2px)",
+			"-moz-calc( 50% + 2px)",
+			"-moz-calc(50% + 2px )",
+			"-moz-calc( 50% + 2px )",
+			"-moz-calc(50% - -2px)",
+			"-moz-calc(2px - -50%)",
+			"-moz-calc(3*25px)",
+			"-moz-calc(3 *25px)",
+			"-moz-calc(3 * 25px)",
+			"-moz-calc(3* 25px)",
+			"-moz-calc(25px*3)",
+			"-moz-calc(25px *3)",
+			"-moz-calc(25px* 3)",
+			"-moz-calc(25px * 3)",
+			"-moz-calc(3*25px + 50%)",
+			"-moz-calc(50% - 3em + 2px)",
+			"-moz-calc(50% - (3em + 2px))",
+			"-moz-calc((50% - 3em) + 2px)",
+			"-moz-min(50%, 30em)",
+			"-moz-calc(min(50%, 30em))",
+			"-moz-max(30em, 2px + 50%)",
+			"-moz-calc(max(30em, 2px + 50%))",
+			"-moz-min(30%, 30em,200px, min(500px ,40em))",
+			"-moz-calc(min(30%, 30em,200px, min(500px ,40em)))",
+			"-moz-min(50%)",
+			"-moz-max(20px)",
+			"-moz-calc(min(2em))",
+			"-moz-calc(max(50%))",
+			"-moz-calc(50px/2)",
+			"-moz-calc(50px/(2 - 1))"
+		],
+		invalid_values: [ "none", "-2px",
+			/* invalid calc() values */
+			"-moz-calc(50%+ 2px)",
+			"-moz-calc(50% +2px)",
+			"-moz-calc(50%+2px)",
+			"-moz-min()",
+			"-moz-calc(min())",
+			"-moz-max()",
+			"-moz-calc(max())",
+			"-moz-calc(50px/(2 - 2))",
+			/* If we ever support division by values, which is
+			 * complicated for the reasons described in
+			 * http://lists.w3.org/Archives/Public/www-style/2010Jan/0007.html
+			 * , we should support all 4 of these as described in
+			 * http://lists.w3.org/Archives/Public/www-style/2009Dec/0296.html
+			 */
+			"-moz-calc((3em / 100%) * 3em)",
+			"-moz-calc(3em / 100% * 3em)",
+			"-moz-calc(3em * (3em / 100%))",
+			"-moz-calc(3em * 3em / 100%)"
+		]
 	},
 	"word-spacing": {
 		domProp: "wordSpacing",
 		inherited: true,
 		type: CSS_TYPE_LONGHAND,
 		initial_values: [ "normal", "0", "0px", "-0em",
 			"-moz-calc(-0px)", "-moz-calc(0em)"
 		],
--- a/layout/svg/base/src/nsSVGOuterSVGFrame.cpp
+++ b/layout/svg/base/src/nsSVGOuterSVGFrame.cpp
@@ -465,23 +465,26 @@ nsDisplaySVG::Paint(nsDisplayListBuilder
     Paint(*aCtx, mVisibleRect, aBuilder->ToReferenceFrame(mFrame));
 }
 
 // helper
 static inline PRBool
 DependsOnIntrinsicSize(const nsIFrame* aEmbeddingFrame)
 {
   const nsStylePosition *pos = aEmbeddingFrame->GetStylePosition();
-  nsStyleUnit widthUnit  = pos->mWidth.GetUnit();
-  nsStyleUnit heightUnit = pos->mHeight.GetUnit();
+  const nsStyleCoord &width = pos->mWidth;
+  const nsStyleCoord &height = pos->mHeight;
 
   // XXX it would be nice to know if the size of aEmbeddingFrame's containing
   // block depends on aEmbeddingFrame, then we'd know if we can return false
   // for eStyleUnit_Percent too.
-  return (widthUnit != eStyleUnit_Coord) || (heightUnit != eStyleUnit_Coord);
+  return (width.GetUnit() != eStyleUnit_Coord &&
+          (!width.IsCalcUnit() || width.CalcHasPercent())) ||
+         (height.GetUnit() != eStyleUnit_Coord &&
+          (!height.IsCalcUnit() || height.CalcHasPercent()));
 }
 
 NS_IMETHODIMP
 nsSVGOuterSVGFrame::AttributeChanged(PRInt32  aNameSpaceID,
                                      nsIAtom* aAttribute,
                                      PRInt32  aModType)
 {
   if (aNameSpaceID == kNameSpaceID_None &&
--- a/layout/tables/BasicTableLayoutStrategy.cpp
+++ b/layout/tables/BasicTableLayoutStrategy.cpp
@@ -43,16 +43,18 @@
 
 #include "BasicTableLayoutStrategy.h"
 #include "nsTableFrame.h"
 #include "nsTableCellFrame.h"
 #include "nsLayoutUtils.h"
 #include "nsGkAtoms.h"
 #include "SpanningCellSorter.h"
 
+namespace css = mozilla::css;
+
 #undef  DEBUG_TABLE_STRATEGY 
 
 BasicTableLayoutStrategy::BasicTableLayoutStrategy(nsTableFrame *aTableFrame)
   : nsITableLayoutStrategy(nsITableLayoutStrategy::Auto)
   , mTableFrame(aTableFrame)
 {
     MarkIntrinsicWidthsDirty();
 }
@@ -115,38 +117,42 @@ GetWidthInfo(nsIRenderingContext *aRende
         prefCoord = 0;
     }
     float prefPercent = 0.0f;
     PRBool hasSpecifiedWidth = PR_FALSE;
 
     // XXXldb Should we consider -moz-box-sizing?
 
     const nsStylePosition *stylePos = aFrame->GetStylePosition();
-    nsStyleUnit unit = stylePos->mWidth.GetUnit();
+    const nsStyleCoord &width = stylePos->mWidth;
+    nsStyleUnit unit = width.GetUnit();
+    // NOTE: We're ignoring calc() units here, for lack of a sensible
+    // idea for what to do with them.  This means calc() is basically
+    // handled like 'auto' for table cells and columns.
     if (unit == eStyleUnit_Coord) {
         hasSpecifiedWidth = PR_TRUE;
         nscoord w = nsLayoutUtils::ComputeWidthValue(aRenderingContext,
-                      aFrame, 0, 0, 0, stylePos->mWidth);
+                                                     aFrame, 0, 0, 0, width);
         // Quirk: A cell with "nowrap" set and a coord value for the
         // width which is bigger than the intrinsic minimum width uses
         // that coord value as the minimum width.
         // This is kept up-to-date with dynamic changes to nowrap by code in
         // nsTableCellFrame::AttributeChanged
         if (aIsCell && w > minCoord &&
             aFrame->PresContext()->CompatibilityMode() ==
               eCompatibility_NavQuirks &&
             aFrame->GetContent()->HasAttr(kNameSpaceID_None,
                                           nsGkAtoms::nowrap)) {
             minCoord = w;
         }
         prefCoord = NS_MAX(w, minCoord);
     } else if (unit == eStyleUnit_Percent) {
-        prefPercent = stylePos->mWidth.GetPercentValue();
+        prefPercent = width.GetPercentValue();
     } else if (unit == eStyleUnit_Enumerated && aIsCell) {
-        switch (stylePos->mWidth.GetIntValue()) {
+        switch (width.GetIntValue()) {
             case NS_STYLE_WIDTH_MAX_CONTENT:
                 // 'width' only affects pref width, not min
                 // width, so don't change anything
                 break;
             case NS_STYLE_WIDTH_MIN_CONTENT:
                 prefCoord = minCoord;
                 break;
             case NS_STYLE_WIDTH_FIT_CONTENT:
--- a/layout/tables/FixedTableLayoutStrategy.cpp
+++ b/layout/tables/FixedTableLayoutStrategy.cpp
@@ -100,17 +100,18 @@ FixedTableLayoutStrategy::GetMinWidth(ns
             &colFrame->GetStylePosition()->mWidth;
         if (styleWidth->GetUnit() == eStyleUnit_Coord) {
             result += nsLayoutUtils::ComputeWidthValue(aRenderingContext,
                         colFrame, 0, 0, 0, *styleWidth);
         } else if (styleWidth->GetUnit() == eStyleUnit_Percent) {
             // do nothing
         } else {
             NS_ASSERTION(styleWidth->GetUnit() == eStyleUnit_Auto ||
-                         styleWidth->GetUnit() == eStyleUnit_Enumerated,
+                         styleWidth->GetUnit() == eStyleUnit_Enumerated ||
+                         styleWidth->IsCalcUnit(),
                          "bad width");
 
             // The 'table-layout: fixed' algorithm considers only cells
             // in the first row.
             PRBool originates;
             PRInt32 colSpan;
             nsTableCellFrame *cellFrame =
                 cellMap->GetCellInfoAt(0, col, &originates, &colSpan);
@@ -132,18 +133,18 @@ FixedTableLayoutStrategy::GetMinWidth(ns
                     result += cellWidth;
                 } else if (styleWidth->GetUnit() == eStyleUnit_Percent) {
                     if (colSpan > 1) {
                         // XXX Can this force columns to negative
                         // widths?
                         result -= spacing * (colSpan - 1);
                     }
                 }
-                // else, for 'auto', '-moz-available', and '-moz-fit-content'
-                // do nothing
+                // else, for 'auto', '-moz-available', '-moz-fit-content',
+                // and 'calc()', do nothing
             }
         }
     }
 
     return (mMinWidth = result);
 }
 
 /* virtual */ nscoord
@@ -246,17 +247,18 @@ FixedTableLayoutStrategy::ComputeColumnW
             specTotal += colWidth;
         } else if (styleWidth->GetUnit() == eStyleUnit_Percent) {
             float pct = styleWidth->GetPercentValue();
             colWidth = NSToCoordFloor(pct * float(tableWidth));
             colFrame->AddPrefPercent(pct);
             pctTotal += pct;
         } else {
             NS_ASSERTION(styleWidth->GetUnit() == eStyleUnit_Auto ||
-                         styleWidth->GetUnit() == eStyleUnit_Enumerated,
+                         styleWidth->GetUnit() == eStyleUnit_Enumerated ||
+                         styleWidth->IsCalcUnit(),
                          "bad width");
 
             // The 'table-layout: fixed' algorithm considers only cells
             // in the first row.
             PRBool originates;
             PRInt32 colSpan;
             nsTableCellFrame *cellFrame =
                 cellMap->GetCellInfoAt(0, col, &originates, &colSpan);
@@ -281,17 +283,18 @@ FixedTableLayoutStrategy::ComputeColumnW
                         cellFrame->IntrinsicWidthOffsets(aReflowState.rendContext);
                     float pct = styleWidth->GetPercentValue();
                     colWidth = NSToCoordFloor(pct * float(tableWidth)) +
                                offsets.hPadding + offsets.hBorder;
                     pct /= float(colSpan);
                     colFrame->AddPrefPercent(pct);
                     pctTotal += pct;
                 } else {
-                    // 'auto', '-moz-available', and '-moz-fit-content'
+                    // 'auto', '-moz-available', '-moz-fit-content', and
+                    // 'calc()'
                     colWidth = unassignedMarker;
                 }
                 if (colWidth != unassignedMarker) {
                     if (colSpan > 1) {
                         // If a column-spanning cell is in the first
                         // row, split up the space evenly.  (XXX This
                         // isn't quite right if some of the columns it's
                         // in have specified widths.  Should we care?)
--- a/layout/xul/base/src/nsBox.cpp
+++ b/layout/xul/base/src/nsBox.cpp
@@ -674,19 +674,26 @@ nsIBox::AddCSSPrefSize(nsIBox* aBox, nsS
     // add in the css min, max, pref
     const nsStylePosition* position = aBox->GetStylePosition();
 
     // see if the width or height was specifically set
     // XXX Handle eStyleUnit_Enumerated?
     // (Handling the eStyleUnit_Enumerated types requires
     // GetPrefSize/GetMinSize methods that don't consider
     // (min-/max-/)(width/height) properties.)
-    if (position->mWidth.GetUnit() == eStyleUnit_Coord) {
-        aSize.width = position->mWidth.GetCoordValue();
+    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);
+            aWidthSet = PR_TRUE;
+        }
     }
 
     if (position->mHeight.GetUnit() == eStyleUnit_Coord) {
         aSize.height = position->mHeight.GetCoordValue();     
         aHeightSet = PR_TRUE;
     }
     
     nsIContent* content = aBox->GetContent();