Bug 696253, patch 8: implement parsing/computation for CSS shorthand property 'flex'. r=dbaron
authorDaniel Holbert <dholbert@cs.stanford.edu>
Fri, 06 Jul 2012 17:06:23 -0700
changeset 98590 a112868224d10b3924a28e6752edf6336c6e62ba
parent 98589 57126745d4629a4be7049756fb649f59bd4de2d5
child 98591 b6488e90d566871ff1d86e19c90522ab35d20038
push id23064
push userryanvm@gmail.com
push dateSat, 07 Jul 2012 18:54:06 +0000
treeherdermozilla-central@9533b40ff28b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs696253
milestone16.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 696253, patch 8: implement parsing/computation for CSS shorthand property 'flex'. r=dbaron
dom/interfaces/css/nsIDOMCSS2Properties.idl
layout/style/Declaration.cpp
layout/style/nsCSSParser.cpp
layout/style/nsCSSPropList.h
layout/style/nsCSSProps.cpp
layout/style/test/Makefile.in
layout/style/test/property_database.js
layout/style/test/test_flexbox_flex_shorthand.html
--- a/dom/interfaces/css/nsIDOMCSS2Properties.idl
+++ b/dom/interfaces/css/nsIDOMCSS2Properties.idl
@@ -763,16 +763,19 @@ interface nsIDOMCSS2Properties : nsISupp
    block should be uncommented (and this interface's uuid should be revved).
    (This would be #ifdef MOZ_FLEXBOX, if that worked in IDL files.)
            attribute DOMString        MozAlignItems;
                                         // raises(DOMException) on setting
 
            attribute DOMString        MozAlignSelf;
                                         // raises(DOMException) on setting
 
+           attribute DOMString        MozFlex;
+                                        // raises(DOMException) on setting
+
            attribute DOMString        MozFlexBasis;
                                         // raises(DOMException) on setting
 
            attribute DOMString        MozFlexDirection;
                                         // raises(DOMException) on setting
 
            attribute DOMString        MozFlexGrow;
                                         // raises(DOMException) on setting
--- a/layout/style/Declaration.cpp
+++ b/layout/style/Declaration.cpp
@@ -795,16 +795,30 @@ Declaration::GetValue(nsCSSProperty aPro
       // Two values, column-count and column-width, separated by a space.
       const nsCSSProperty* subprops =
         nsCSSProps::SubpropertyEntryFor(aProperty);
       AppendValueToString(subprops[0], aValue);
       aValue.Append(PRUnichar(' '));
       AppendValueToString(subprops[1], aValue);
       break;
     }
+#ifdef MOZ_FLEXBOX
+    case eCSSProperty_flex: {
+      // flex-grow, flex-shrink, flex-basis, separated by single space
+      const nsCSSProperty* subprops =
+        nsCSSProps::SubpropertyEntryFor(aProperty);
+
+      AppendValueToString(subprops[0], aValue);
+      aValue.Append(PRUnichar(' '));
+      AppendValueToString(subprops[1], aValue);
+      aValue.Append(PRUnichar(' '));
+      AppendValueToString(subprops[2], aValue);
+      break;
+    }
+#endif // MOZ_FLEXBOX
     default:
       NS_ABORT_IF_FALSE(false, "no other shorthands");
       break;
   }
 }
 
 bool
 Declaration::GetValueIsImportant(const nsAString& aProperty) const
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -471,16 +471,21 @@ protected:
   bool ParseCalcAdditiveExpression(nsCSSValue& aValue,
                                      PRInt32& aVariantMask);
   bool ParseCalcMultiplicativeExpression(nsCSSValue& aValue,
                                            PRInt32& aVariantMask,
                                            bool *aHadFinalWS);
   bool ParseCalcTerm(nsCSSValue& aValue, PRInt32& aVariantMask);
   bool RequireWhitespace();
 
+#ifdef MOZ_FLEXBOX
+  // For "flex" shorthand property, defined in CSS3 Flexbox
+  bool ParseFlex();
+#endif
+
   // for 'clip' and '-moz-image-region'
   bool ParseRect(nsCSSProperty aPropID);
   bool ParseColumns();
   bool ParseContent();
   bool ParseCounterData(nsCSSProperty aPropID);
   bool ParseCursor();
   bool ParseFont();
   bool ParseFontWeight(nsCSSValue& aValue);
@@ -4826,16 +4831,126 @@ CSSParserImpl::ParseElement(nsCSSValue& 
   }
 
   // If we detect a syntax error, we must match the opening parenthesis of the
   // function with the closing parenthesis and skip all the tokens in between.
   SkipUntil(')');
   return false;
 }
 
+#ifdef MOZ_FLEXBOX
+// flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
+bool
+CSSParserImpl::ParseFlex()
+{
+  // First check for inherit / initial
+  nsCSSValue tmpVal;
+  if (ParseVariant(tmpVal, VARIANT_INHERIT, nsnull)) {
+    AppendValue(eCSSProperty_flex_grow, tmpVal);
+    AppendValue(eCSSProperty_flex_shrink, tmpVal);
+    AppendValue(eCSSProperty_flex_basis, tmpVal);
+    return true;
+  }
+
+  // Next, check for 'none' == '0 0 auto'
+  if (ParseVariant(tmpVal, VARIANT_NONE, nsnull)) {
+    AppendValue(eCSSProperty_flex_grow, nsCSSValue(0.0f, eCSSUnit_Number));
+    AppendValue(eCSSProperty_flex_shrink, nsCSSValue(0.0f, eCSSUnit_Number));
+    AppendValue(eCSSProperty_flex_basis, nsCSSValue(eCSSUnit_Auto));
+    return true;
+  }
+
+  // OK, try parsing our value as individual per-subproperty components:
+  //   [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
+
+  // Each subproperty has a default value that it takes when it's omitted in a
+  // "flex" shorthand value. These default values are *only* for the shorthand
+  // syntax -- they're distinct from the subproperties' own initial values.  We
+  // start with each subproperty at its default, as if we had "flex: 1 1 0%".
+  nsCSSValue flexGrow(1.0f, eCSSUnit_Number);
+  nsCSSValue flexShrink(1.0f, eCSSUnit_Number);
+  nsCSSValue flexBasis(0.0f, eCSSUnit_Percent);
+
+  // OVERVIEW OF PARSING STRATEGY:
+  // =============================
+  // a) Parse the first component as either flex-basis or flex-grow.
+  // b) If it wasn't flex-grow, parse the _next_ component as flex-grow.
+  // c) Now we've just parsed flex-grow -- so try parsing the next thing as
+  //    flex-shrink.
+  // d) Finally: If we didn't get flex-basis at the beginning, try to parse
+  //    it now, at the end.
+  //
+  // More details in each section below.
+
+  // (a) Parse first component. It's either 'flex-basis' or 'flex-grow', so
+  // we allow anything that would be legal to specify for the 'flex-basis'
+  // property (except for "inherit") and we also allow VARIANT_NUMBER so that
+  // we'll accept 'flex-grow' values (and importantly, so that we'll treat
+  // unitless 0 as a number instead of a length, since the flexbox spec
+  // disallows unitless 0 as a flex-basis value in the shorthand).
+  PRUint32 variantMask = VARIANT_NUMBER |
+    (nsCSSProps::ParserVariant(eCSSProperty_flex_basis) & ~(VARIANT_INHERIT));
+
+  if (!ParseNonNegativeVariant(tmpVal, variantMask, nsCSSProps::kWidthKTable)) {
+    // First component was not a valid flex-basis or flex-grow value. Fail.
+    return false;
+  }
+
+  // Record what we just parsed as either flex-basis or flex-grow:
+  bool wasFirstComponentFlexBasis = (tmpVal.GetUnit() != eCSSUnit_Number);
+  (wasFirstComponentFlexBasis ? flexBasis : flexGrow) = tmpVal;
+
+  // (b) If we didn't get flex-grow yet, parse _next_ component as flex-grow.
+  bool doneParsing = false;
+  if (wasFirstComponentFlexBasis) {
+    if (ParseNonNegativeVariant(tmpVal, VARIANT_NUMBER, nsnull)) {
+      flexGrow = tmpVal;
+    } else {
+      // Failed to parse anything after our flex-basis -- that's fine. We can
+      // skip the remaining parsing.
+      doneParsing = true;
+    }
+  }
+
+  if (!doneParsing) {
+    // (c) OK -- the last thing we parsed was flex-grow, so look for a
+    //     flex-shrink in the next position.
+    if (ParseNonNegativeVariant(tmpVal, VARIANT_NUMBER, nsnull)) {
+      flexShrink = tmpVal;
+    }
+ 
+    // d) Finally: If we didn't get flex-basis at the beginning, try to parse
+    //    it now, at the end.
+    //
+    // NOTE: Even though we're looking for a (length-ish) flex-basis value, we
+    // *do* need to pass VARIANT_NUMBER as part of |variantMask| here.  This
+    // ensures that we'll parse unitless '0' as a number, rather than as a
+    // length, so that we can reject it (along with any other number) below.
+    // (The flexbox spec disallows unitless '0' as a flex-basis value in the
+    // 'flex' shorthand.)
+    if (!wasFirstComponentFlexBasis &&
+        ParseNonNegativeVariant(tmpVal, variantMask,
+                                nsCSSProps::kWidthKTable)) {
+      if (tmpVal.GetUnit() == eCSSUnit_Number) {
+        // This is where we reject "0 0 0" (which the spec says is invalid,
+        // because we must reject unitless 0 as a flex-basis value).
+        return false;
+      }
+      flexBasis = tmpVal;
+    }
+  }
+
+  AppendValue(eCSSProperty_flex_grow,   flexGrow);
+  AppendValue(eCSSProperty_flex_shrink, flexShrink);
+  AppendValue(eCSSProperty_flex_basis,  flexBasis);
+
+  return true;
+}
+#endif
+
 // <color-stop> : <color> [ <percentage> | <length> ]?
 bool
 CSSParserImpl::ParseColorStop(nsCSSValueGradient* aGradient)
 {
   nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement();
   if (!ParseVariant(stop->mColor, VARIANT_COLOR, nsnull)) {
     return false;
   }
@@ -5483,16 +5598,20 @@ CSSParserImpl::ParsePropertyByFunction(n
     return ParseBorderSide(kColumnRuleIDs, false);
   case eCSSProperty_content:
     return ParseContent();
   case eCSSProperty_counter_increment:
   case eCSSProperty_counter_reset:
     return ParseCounterData(aPropID);
   case eCSSProperty_cursor:
     return ParseCursor();
+#ifdef MOZ_FLEXBOX
+  case eCSSProperty_flex:
+    return ParseFlex();
+#endif // MOZ_FLEXBOX
   case eCSSProperty_font:
     return ParseFont();
   case eCSSProperty_image_region:
     return ParseRect(eCSSProperty_image_region);
   case eCSSProperty_list_style:
     return ParseListStyle();
   case eCSSProperty_margin:
     return ParseMargin();
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -1527,24 +1527,33 @@ CSS_PROP_POSITION(
     align_self,
     CSS_PROP_DOMPROP_PREFIXED(AlignSelf),
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kAlignSelfKTable,
     offsetof(nsStylePosition, mAlignSelf),
     eStyleAnimType_EnumU8)
+CSS_PROP_SHORTHAND(
+    -moz-flex,
+    flex,
+    CSS_PROP_DOMPROP_PREFIXED(Flex),
+    CSS_PROPERTY_PARSE_FUNCTION,
+    "")
 CSS_PROP_POSITION(
     -moz-flex-basis,
     flex_basis,
     CSS_PROP_DOMPROP_PREFIXED(FlexBasis),
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_NONNEGATIVE |
         CSS_PROPERTY_STORES_CALC,
     "",
+    // NOTE: The parsing implementation for the 'flex' shorthand property has
+    // its own code to parse each subproperty. It does not depend on the
+    // longhand parsing defined here.
     VARIANT_AHKLP | VARIANT_CALC,
     kWidthKTable,
     offsetof(nsStylePosition, mFlexBasis),
     eStyleAnimType_Coord)
 CSS_PROP_POSITION(
     -moz-flex-direction,
     flex_direction,
     CSS_PROP_DOMPROP_PREFIXED(FlexDirection),
@@ -1556,27 +1565,33 @@ CSS_PROP_POSITION(
     eStyleAnimType_EnumU8)
 CSS_PROP_POSITION(
     -moz-flex-grow,
     flex_grow,
     CSS_PROP_DOMPROP_PREFIXED(FlexGrow),
     CSS_PROPERTY_PARSE_VALUE |
       CSS_PROPERTY_VALUE_NONNEGATIVE,
     "",
+    // NOTE: The parsing implementation for the 'flex' shorthand property has
+    // its own code to parse each subproperty. It does not depend on the
+    // longhand parsing defined here.
     VARIANT_HN,
     nsnull,
     offsetof(nsStylePosition, mFlexGrow),
     eStyleAnimType_float) // float, except animations to/from 0 shouldn't work
 CSS_PROP_POSITION(
     -moz-flex-shrink,
     flex_shrink,
     CSS_PROP_DOMPROP_PREFIXED(FlexShrink),
     CSS_PROPERTY_PARSE_VALUE |
       CSS_PROPERTY_VALUE_NONNEGATIVE,
     "",
+    // NOTE: The parsing implementation for the 'flex' shorthand property has
+    // its own code to parse each subproperty. It does not depend on the
+    // longhand parsing defined here.
     VARIANT_HN,
     nsnull,
     offsetof(nsStylePosition, mFlexShrink),
     eStyleAnimType_float) // float, except animations to/from 0 shouldn't work
 CSS_PROP_POSITION(
     -moz-order,
     order,
     CSS_PROP_DOMPROP_PREFIXED(Order),
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -2063,16 +2063,25 @@ static const nsCSSProperty gColumnRuleSu
   // nsCSSDeclaration.cpp outputs the subproperties in this order.
   // It also depends on the color being third.
   eCSSProperty__moz_column_rule_width,
   eCSSProperty__moz_column_rule_style,
   eCSSProperty__moz_column_rule_color,
   eCSSProperty_UNKNOWN
 };
 
+#ifdef MOZ_FLEXBOX
+static const nsCSSProperty gFlexSubpropTable[] = {
+  eCSSProperty_flex_grow,
+  eCSSProperty_flex_shrink,
+  eCSSProperty_flex_basis,
+  eCSSProperty_UNKNOWN
+};
+#endif // MOZ_FLEXBOX
+
 static const nsCSSProperty gOverflowSubpropTable[] = {
   eCSSProperty_overflow_x,
   eCSSProperty_overflow_y,
   eCSSProperty_UNKNOWN
 };
 
 static const nsCSSProperty gPaddingSubpropTable[] = {
   // Code relies on these being in top-right-bottom-left order.
--- a/layout/style/test/Makefile.in
+++ b/layout/style/test/Makefile.in
@@ -201,16 +201,17 @@ GARBAGE += css_properties.js
 		test_load_events_on_stylesheets.html \
 		test_bug721136.html \
 		$(NULL)
 
 ifdef MOZ_FLEXBOX
 _TEST_FILES +=	\
 		test_flexbox_align_self_auto.html \
 		test_flexbox_flex_grow_and_shrink.html \
+		test_flexbox_flex_shorthand.html \
 		$(NULL)
 endif
 
 _VISITED_REFTEST_FILES = \
 		$(shell find $(topsrcdir)/layout/reftests/css-visited/ -name '*.html' -o -name '*.xhtml') \
 		$(topsrcdir)/layout/reftests/svg/pseudo-classes-02.svg \
 		$(topsrcdir)/layout/reftests/svg/pseudo-classes-02-ref.svg \
 		$(topsrcdir)/layout/reftests/svg/as-image/lime100x100.svg \
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -750,16 +750,56 @@ var gCSSProperties = {
 		domProp: "MozAlignSelf",
 		inherited: false,
 		type: CSS_TYPE_LONGHAND,
 		// (Assuming defaults on the parent, 'auto' will compute to 'stretch'.)
 		initial_values: [ "auto", "stretch" ],
 		other_values: [ "flex-start", "flex-end", "center", "baseline" ],
 		invalid_values: [ "space-between", "abc", "30px" ]
 	},
+	"-moz-flex": {
+		domProp: "MozFlex",
+		inherited: false,
+		type: CSS_TYPE_TRUE_SHORTHAND,
+		subproperties: [
+			"-moz-flex-grow",
+			"-moz-flex-shrink",
+			"-moz-flex-basis"
+		],
+		initial_values: [ "0 1 auto", "auto 0 1", "0 auto", "auto 0" ],
+		other_values: [
+			"none",
+			"1",
+			"0",
+			"0 1",
+			"0.5",
+			"1.2 3.4",
+			"0 0 0px",
+			"0px 0 0",
+			"5px 0 0",
+			"2 auto",
+			"auto 4",
+			"auto 5.6 7.8",
+			"-moz-max-content",
+			"1 -moz-max-content",
+			"1 2 -moz-max-content",
+			"-moz-max-content 1",
+			"-moz-max-content 1 2",
+			"-0"
+		],
+		invalid_values: [
+			"0 0 0",
+			"1 2px 3",
+			"1 auto 3",
+			"1px 2 3px",
+			"1px 2 3 4px",
+			"-1",
+			"1 -1"
+		]
+	},
 	"-moz-flex-basis": {
 		domProp: "MozFlexBasis",
 		inherited: false,
 		type: CSS_TYPE_LONGHAND,
 		initial_values: [ " auto" ],
         // NOTE: This is cribbed directly from the "width" chunk, since this
         // property takes the exact same values as width (albeit with
         // different semantics on 'auto').
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_flexbox_flex_shorthand.html
@@ -0,0 +1,260 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=696253
+-->
+<head>
+  <title>Test for Bug 696253</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="property_database.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=696253">Mozilla Bug 696253</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+
+/** Test for Bug 696253 **/
+/* (Testing the [-moz-]flex CSS shorthand property) */
+
+const gFlexPropName = "-moz-flex";
+const gFlexPropInfo = gCSSProperties[gFlexPropName];
+
+// Default values for shorthand subproperties, when they're not specified
+// explicitly in a testcase.  This lets the testcases be more concise.
+//
+// The values here are from the flexbox spec on the 'flex' shorthand:
+//   "When omitted, [flex-grow and flex-shrink] are set to '1'.
+//   "If omitted, the flex basis defaults to 0%"
+let gFlexShorthandDefaults = {
+    "-moz-flex-grow":   "1",
+    "-moz-flex-shrink": "1",
+    "-moz-flex-basis":  "0%"
+};
+
+let gFlexShorthandTestcases = [
+/*
+  {
+    "-moz-flex":        "SPECIFIED value for flex shorthand",
+
+    // Expected Computed Values of Subproperties
+    // Semi-optional -- if unspecified, the expected value is taken
+    // from gFlexShorthandDefaults.
+    "-moz-flex-grow":   "EXPECTED computed value for flex-grow property",
+    "-moz-flex-shrink": "EXPECTED computed value for flex-shrink property",
+    "-moz-flex-basis":  "EXPECTED computed value for flex-basis property",
+  }, 
+*/
+
+  // Initial values of subproperties:
+  // --------------------------------
+  // (checked by another test that uses property_database.js, too, but
+  // might as well check here, too, for thoroughness).
+  {
+    "-moz-flex":        "",
+    "-moz-flex-grow":   "0",
+    "-moz-flex-shrink": "1",
+    "-moz-flex-basis":  "auto",
+  },
+  {
+    "-moz-flex":        "-moz-initial",
+    "-moz-flex-grow":   "0",
+    "-moz-flex-shrink": "1",
+    "-moz-flex-basis":  "auto",
+  },
+
+  // Special keyword "none" --> "0 0 auto"
+  // -------------------------------------
+  {
+    "-moz-flex":        "none",
+    "-moz-flex-grow":   "0",
+    "-moz-flex-shrink": "0",
+    "-moz-flex-basis":  "auto",
+  },
+
+  // One Value (numeric) --> sets flex-grow
+  // --------------------------------------
+  {
+    "-moz-flex":        "0",
+    "-moz-flex-grow":   "0",
+  },
+  {
+    "-moz-flex":        "5",
+    "-moz-flex-grow":   "5",
+  },
+  {
+    "-moz-flex":        "1000",
+    "-moz-flex-grow":   "1000",
+  },
+  {
+    "-moz-flex":        "0.0000001",
+    "-moz-flex-grow":   "1e-7"
+  },
+  {
+    "-moz-flex":        "20000000",
+    "-moz-flex-grow":   "2e+7"
+  },
+
+  // One Value (length or other nonnumeric) --> sets flex-basis
+  // ----------------------------------------------------------
+  {
+    "-moz-flex":        "0px",
+    "-moz-flex-basis":  "0px",
+  },
+  {
+    "-moz-flex":        "0%",
+    "-moz-flex-basis":  "0%",
+  },
+  {
+    "-moz-flex":        "25px",
+    "-moz-flex-basis":  "25px",
+  },
+  {
+    "-moz-flex":        "5%",
+    "-moz-flex-basis":  "5%",
+  },
+  {
+    "-moz-flex":        "auto",
+    "-moz-flex-basis":  "auto",
+  },
+  {
+    "-moz-flex":        "-moz-fit-content",
+    "-moz-flex-basis":  "-moz-fit-content",
+  },
+  {
+    "-moz-flex":        "-moz-calc(5px + 6px)",
+    "-moz-flex-basis":  "11px",
+  },
+  {
+    "-moz-flex":        "-moz-calc(15% + 30px)",
+    "-moz-flex-basis":  "-moz-calc(30px + 15%)",
+  },
+
+  // Two Values (numeric) --> sets flex-grow, flex-shrink
+  // ----------------------------------------------------
+  {
+    "-moz-flex":        "0 0",
+    "-moz-flex-grow":   "0",
+    "-moz-flex-shrink": "0",
+  },
+  {
+    "-moz-flex":        "0 2",
+    "-moz-flex-grow":   "0",
+    "-moz-flex-shrink": "2",
+  },
+  {
+    "-moz-flex":        "3 0",
+    "-moz-flex-grow":   "3",
+    "-moz-flex-shrink": "0",
+  },
+  {
+    "-moz-flex":        "0.5000 2.03",
+    "-moz-flex-grow":   "0.5",
+    "-moz-flex-shrink": "2.03",
+  },
+  {
+    "-moz-flex":        "300.0 500.0",
+    "-moz-flex-grow":   "300",
+    "-moz-flex-shrink": "500",
+  },
+
+  // Two Values (numeric & length-ish) --> sets flex-grow, flex-basis
+  // ----------------------------------------------------------------
+  {
+    "-moz-flex":        "0 0px",
+    "-moz-flex-grow":   "0",
+    "-moz-flex-basis":  "0px",
+  },
+  {
+    "-moz-flex":        "0 0%",
+    "-moz-flex-grow":   "0",
+    "-moz-flex-basis":  "0%",
+  },
+  {
+    "-moz-flex":        "10 30px",
+    "-moz-flex-grow":   "10",
+    "-moz-flex-basis":  "30px",
+  },
+  {
+    "-moz-flex":        "99px 2.3",
+    "-moz-flex-grow":   "2.3",
+    "-moz-flex-basis":  "99px",
+  },
+  {
+    "-moz-flex":        "99% 6",
+    "-moz-flex-grow":   "6",
+    "-moz-flex-basis":  "99%",
+  },
+  {
+    "-moz-flex":        "auto 5",
+    "-moz-flex-grow":   "5",
+    "-moz-flex-basis":  "auto",
+  },
+  {
+    "-moz-flex":        "5 -moz-fit-content",
+    "-moz-flex-grow":   "5",
+    "-moz-flex-basis":  "-moz-fit-content",
+  },
+  {
+    "-moz-flex":        "-moz-calc(5% + 10px) 3",
+    "-moz-flex-grow":   "3",
+    "-moz-flex-basis":  "-moz-calc(10px + 5%)",
+  },
+
+  // Three Values --> Sets all three subproperties
+  // ---------------------------------------------
+  {
+    "-moz-flex":        "0.0 0.00 0px",
+    "-moz-flex-grow":   "0",
+    "-moz-flex-shrink": "0",
+    "-moz-flex-basis":  "0px",
+  },
+  {
+    "-moz-flex":        "0% 0 0",
+    "-moz-flex-grow":   "0",
+    "-moz-flex-shrink": "0",
+    "-moz-flex-basis":  "0%",
+  },
+  {
+    "-moz-flex":        "10px 3 2",
+    "-moz-flex-grow":   "3",
+    "-moz-flex-shrink": "2",
+    "-moz-flex-basis":  "10px",
+  },
+];
+
+function runFlexShorthandTest(aFlexShorthandTestcase)
+{
+  let content = document.getElementById("content");
+
+  let elem = document.createElement("div");
+
+  elem.style[gFlexPropInfo.domProp] = aFlexShorthandTestcase[gFlexPropName];
+  content.appendChild(elem); 
+
+  gFlexPropInfo.subproperties.forEach(function(aSubPropName) {
+    var expectedVal = aSubPropName in aFlexShorthandTestcase ?
+     aFlexShorthandTestcase[aSubPropName] :
+     gFlexShorthandDefaults[aSubPropName];
+
+    // Compare computed value against expected computed value (from testcase)
+    is(window.getComputedStyle(elem, null).getPropertyValue(aSubPropName),
+       expectedVal,
+       "Computed value of subproperty \"" + aSubPropName + "\" when we set \"" +
+       gFlexPropName + ": " + aFlexShorthandTestcase[gFlexPropName] + "\"");
+  });
+
+  // Clean up
+  content.removeChild(elem);
+}
+
+// Run the tests!
+gFlexShorthandTestcases.forEach(runFlexShorthandTest)
+
+</script>
+</pre>
+</body>
+</html>