bug 1363349 (part 2) - accept calc expressions in webkit gradient r=dholbert draft
authorKevin Hsieh <kevin.hsieh@ucla.edu>
Wed, 12 Jul 2017 21:42:02 -0700
changeset 608069 7cbd0018efd49f15e5ab3b45f4af3ff32e0c0de1
parent 608068 28eded78186364632c9429e0aff2950945249fe1
child 637206 fa6c4752ce363fc2668fd2a98ba6963980b84ebd
push id68172
push userbmo:kevin.hsieh@ucla.edu
push dateThu, 13 Jul 2017 06:06:19 +0000
reviewersdholbert
bugs1363349
milestone56.0a1
bug 1363349 (part 2) - accept calc expressions in webkit gradient r=dholbert MozReview-Commit-ID: IhXyWoJI9Nb
layout/style/nsCSSParser.cpp
layout/style/test/property_database.js
layout/style/test/test_computed_style.html
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -10507,16 +10507,53 @@ CSSParserImpl::ParseGradientColorStops(n
 }
 
 // Parses the x or y component of a -webkit-gradient() <point> expression.
 // See ParseWebkitGradientPoint() documentation for more.
 bool
 CSSParserImpl::ParseWebkitGradientPointComponent(nsCSSValue& aComponent,
                                                  bool aIsHorizontal)
 {
+  // Attempts to use ParseVariant to process the token as a number (representing
+  // pixels), or a percent, or a calc expression of purely one or the other of
+  // those (we enforce this pureness via ComputeCalc below). If ParseVariant
+  // fails, the token may instead be a keyword or unknown token, in which case
+  // case we execute the rest of the function.
+  CSSParseResult status = ParseVariant(aComponent, VARIANT_PN | VARIANT_CALC,
+                                       nullptr);
+  if (status == CSSParseResult::Error) {
+    return false;
+  }
+  if (status == CSSParseResult::Ok) {
+    switch (aComponent.GetUnit()) {
+      case eCSSUnit_Number:
+        aComponent.SetFloatValue(aComponent.GetFloatValue(), eCSSUnit_Pixel);
+        return true;
+      case eCSSUnit_Calc: {
+        float result;
+        ReduceCalcOps<float, eCSSUnit_Number> opsNumber;
+        if (ComputeCalc(result, aComponent, opsNumber)) {
+          aComponent.SetFloatValue(result, eCSSUnit_Pixel);
+          return true;
+        }
+        ReduceCalcOps<float, eCSSUnit_Percent> opsPercent;
+        if (ComputeCalc(result, aComponent, opsPercent)) {
+          aComponent.SetPercentValue(result);
+          return true;
+        }
+        return false;
+      }
+      case eCSSUnit_Percent:
+        return true;
+      default:
+        MOZ_ASSERT(false, "ParseVariant returned value with unexpected unit");
+        return false;
+    }
+  }
+
   if (!GetToken(true)) {
     return false;
   }
 
   // Keyword tables to use for keyword-matching
   // (Keyword order is important; we assume the index can be multiplied by 50%
   // to convert to a percent-valued component.)
   static const nsCSSKeyword kHorizKeywords[] = {
@@ -10596,27 +10633,37 @@ CSSParserImpl::ParseWebkitGradientPoint(
 }
 
 // Parse the next token as a <number> (for a <radius> in a -webkit-gradient
 // expresison).  Returns true on success; returns false & puts back
 // whatever it parsed on failure.
 bool
 CSSParserImpl::ParseWebkitGradientRadius(float& aRadius)
 {
-  if (!GetToken(true)) {
-    return false;
-  }
-
-  if (mToken.mType != eCSSToken_Number) {
-    UngetToken();
-    return false;
-  }
-
-  aRadius = mToken.mNumber;
-  return true;
+  nsCSSValue parseResult;
+  CSSParseResult status = ParseVariant(parseResult,
+                                       VARIANT_NUMBER | VARIANT_CALC, nullptr);
+  if (status != CSSParseResult::Ok) {
+    return false;
+  }
+  switch (parseResult.GetUnit()) {
+    case eCSSUnit_Number:
+      aRadius = parseResult.GetFloatValue();
+      return true;
+    case eCSSUnit_Calc: {
+      ReduceCalcOps<float, eCSSUnit_Number> ops;
+      if (!ComputeCalc(aRadius, parseResult, ops)) {
+        MOZ_ASSERT_UNREACHABLE("unexpected unit");
+      }
+      return true;
+    }
+    default:
+      MOZ_ASSERT(false, "ParseVariant returned value with unexpected unit");
+      return false;
+  }
 }
 
 // Parse one of:
 //  color-stop(number|percent, color)
 //  from(color)
 //  to(color)
 //
 // Quoting https://www.webkit.org/blog/175/introducing-css-gradients/ :
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -403,16 +403,33 @@ if (IsCSSPropertyPrefEnabled("layout.css
     "-webkit-linear-gradient(135deg, red, blue)",
     "-webkit-linear-gradient( 135deg  , red  , blue )",
     "-webkit-linear-gradient(280deg, red 60%, blue)",
 
     // Linear-gradient with unitless-0 <angle> (normally invalid for <angle>
     // but accepted here for better webkit emulation):
     "-webkit-linear-gradient(0, red, blue)",
 
+    // Linear-gradient with calc expression (bug 1363349)
+    "-webkit-gradient(linear, calc(5 + 5) top, calc(10 + 10) top, from(blue), to(lime))",
+    "-webkit-gradient(linear, calc(5 - 5) top, calc(10 + 10) top, from(blue), to(lime))",
+    "-webkit-gradient(linear, calc(5 * 5) top, calc(10 + 10) top, from(blue), to(lime))",
+    "-webkit-gradient(linear, calc(5 / 5) top, calc(10 + 10) top, from(blue), to(lime))",
+    "-webkit-gradient(linear, left calc(25% - 10%), right calc(75% + 10%), from(blue), to(lime))",
+    "-webkit-gradient(linear, calc(1) 2, 3 4)",
+
+    // Radial-gradient with calc expression (bug 1363349)
+    "-webkit-gradient(radial, 1 2, 0, 3 4, calc(1 + 5), from(blue), to(lime))",
+    "-webkit-gradient(radial, 1 2, calc(1 + 2), 3 4, calc(1 + 5), from(blue), to(lime))",
+    "-webkit-gradient(radial, 1 2, calc(1 - 2), 3 4, calc(1 + 5), from(blue), to(lime))",
+    "-webkit-gradient(radial, 1 2, calc(1 * 2), 3 4, calc(1 + 5), from(blue), to(lime))",
+    "-webkit-gradient(radial, 1 2, calc(1 / 2), 3 4, calc(1 + 5), from(blue), to(lime))",
+    "-webkit-gradient(radial, calc(0 + 1) calc(1 + 1), calc(1 + 2), calc(1 + 2) 4, calc(1 + 5), from(blue), to(lime))",
+    "-webkit-gradient(radial, 1 2, calc(8), 3 4, 9)",
+
     // Basic radial-gradient syntax (valid when prefixed or unprefixed):
     "-webkit-radial-gradient(circle, white, black)",
     "-webkit-radial-gradient(circle, white, black)",
     "-webkit-radial-gradient(ellipse closest-side, white, black)",
     "-webkit-radial-gradient(circle farthest-corner, white, black)",
 
     // Contain/cover keywords (valid only for -moz/-webkit prefixed):
     "-webkit-radial-gradient(cover, red, blue)",
@@ -478,17 +495,16 @@ if (IsCSSPropertyPrefEnabled("layout.css
     "-webkit-gradient(linear, 1 2, 3, 4)", // Comma inside <point>
     "-webkit-gradient(linear, 1, 2, 3 4)", // Comma inside <point>
     "-webkit-gradient(linear, 1, 2, 3, 4)", // Comma inside <point>
 
     // linear w/ invalid units in <point> expression
     "-webkit-gradient(linear, 1px 2, 3 4)",
     "-webkit-gradient(linear, 1 2, 3 4px)",
     "-webkit-gradient(linear, 1px 2px, 3px 4px)",
-    "-webkit-gradient(linear, calc(1) 2, 3 4)",
     "-webkit-gradient(linear, 1 2em, 3 4)",
 
     // linear w/ <radius> (only valid for radial)
     "-webkit-gradient(linear, 1 2, 8, 3 4, 9)",
 
     // linear w/ out-of-order position keywords in <point> expression
     // (horizontal keyword is supposed to come first, for "x" coord)
     "-webkit-gradient(linear, 0 0, top right)",
@@ -528,17 +544,16 @@ if (IsCSSPropertyPrefEnabled("layout.css
     "-webkit-gradient(radial, 1 2, 8)", // Missing 2nd <point>
     "-webkit-gradient(radial, 1 2, 8, 3)", // Incomplete 2nd <point>
     "-webkit-gradient(radial, 1 2, 8, 3 4)", // Missing 2nd radius
     "-webkit-gradient(radial, 1 2, 3 4, 9)", // Missing 1st radius
 
     // radial w/ incorrect units on radius (invalid; expecting <number>)
     "-webkit-gradient(radial, 1 2, 8%,      3 4, 9)",
     "-webkit-gradient(radial, 1 2, 8px,     3 4, 9)",
-    "-webkit-gradient(radial, 1 2, calc(8), 3 4, 9)",
     "-webkit-gradient(radial, 1 2, 8em,     3 4, 9)",
     "-webkit-gradient(radial, 1 2, top,     3 4, 9)",
 
     // radial w/ trailing comma (which implies missing color-stops):
     "-webkit-gradient(linear, 1 2, 8, 3 4, 9,)",
 
     // radial w/ invalid color value (mostly leaning on 'linear' test above):
     "-webkit-gradient(radial, 1 2, 8, 3 4, 9, from(invalidcolorname))",
@@ -553,16 +568,29 @@ if (IsCSSPropertyPrefEnabled("layout.css
     "-webkit-repeating-radial-gradient()",
 
     // * missing comma between <legacy-gradient-line> and color list:
     "-webkit-linear-gradient(0 red, blue)",
     "-webkit-linear-gradient(30deg red, blue)",
     "-webkit-linear-gradient(top right red, blue)",
     "-webkit-linear-gradient(bottom red, blue)",
 
+    // Linear-gradient with calc expression containing mixed units or division
+    // by zero (bug 1363349)
+    "-webkit-gradient(linear, calc(5 + 5%) top, calc(10 + 10) top, from(blue), to(lime))",
+    "-webkit-gradient(linear, left calc(25 - 10%), right calc(75% + 10%), from(blue), to(lime))",
+    "-webkit-gradient(linear, calc(1 / 0) 2, 3 4)",
+
+    // Radial-gradient with calc expression containing mixed units, division
+    // by zero, or a percentage in the radius (bug 1363349)
+    "-webkit-gradient(radial, 1 2, 0, 3 4, calc(1% + 5%), from(blue), to(lime))",
+    "-webkit-gradient(radial, 1 2, calc(1 + 2), 3 4, calc(1 + 5%), from(blue), to(lime))",
+    "-webkit-gradient(radial, calc(0 + 1) calc(1 + 1), calc(1% + 2%), calc(1 + 2) 4, calc(1 + 5), from(blue), to(lime))",
+    "-webkit-gradient(radial, 1 2, calc(8 / 0), 3 4, 9)",
+
     // Linear syntax that's invalid for both -webkit & unprefixed, but valid
     // for -moz:
     // * initial <legacy-gradient-line> which includes a length:
     "-webkit-linear-gradient(10px, red, blue)",
     "-webkit-linear-gradient(10px top, red, blue)",
     // * initial <legacy-gradient-line> which includes a side *and* an angle:
     "-webkit-linear-gradient(bottom 30deg, red, blue)",
     "-webkit-linear-gradient(30deg bottom, red, blue)",
--- a/layout/style/test/test_computed_style.html
+++ b/layout/style/test/test_computed_style.html
@@ -334,16 +334,98 @@ var noframe_container = document.getElem
     for (var i = 0; i < subProp.length; i++) {
       p.style.mask = subProp[i];
       isnot(cs.mask, "", "computed value of " + subProp[i] + " mask");
     }
   }
   p.remove();
 })();
 
+(function test_bug_1363349_linear() {
+  const specPrefix = "-webkit-gradient(linear, ";
+  const specSuffix = ", from(blue), to(lime))";
+
+  const expPrefix = "linear-gradient(";
+  const expSuffix = "rgb(0, 0, 255) 0%, rgb(0, 255, 0) 100%)";
+
+  let testcases = [
+    [ "calc(5 + 5) top, calc(10 + 10) top",
+      "to right",
+      "calc(num+num) in position"
+    ],
+    [ "left calc(25% - 10%), right calc(75% + 10%)",
+      "to right bottom",
+      "calc(pct+pct) in position "
+    ]
+  ];
+
+  let p = document.createElement("p");
+  let cs = getComputedStyle(p, "");
+  frame_container.appendChild(p);
+
+  for (let test of testcases) {
+    let specifiedStyle = specPrefix + test[0] + specSuffix;
+    let expectedStyle = expPrefix;
+    if (test[1] != "") {
+      expectedStyle += test[1] + ", ";
+    }
+    expectedStyle += expSuffix;
+
+    p.style.backgroundImage = specifiedStyle;
+    is(cs.backgroundImage, expectedStyle,
+       "computed value of -webkit-gradient expression (" + test[2] + ")");
+    p.style.backgroundImage = "";
+  }
+
+  p.remove();
+})();
+
+(function test_bug_1363349_radial() {
+  const specPrefix = "-webkit-gradient(radial, ";
+  const specSuffix = ", from(blue), to(lime))";
+
+  const expPrefix = "radial-gradient(";
+  const expSuffix = "rgb(0, 0, 255) 0%, rgb(0, 255, 0) 100%)";
+
+  let testcases = [
+    [ "1 2, 0, 3 4, calc(1 + 5)",
+      "6px at 3px 4px",
+      "calc(num+num) in radius"
+    ],
+    [ "1 2, calc(1 + 2), 3 4, calc(1 + 5)",
+      "6px at 3px 4px",
+      "calc(num+num) in radius"
+    ],
+    [ "calc(0 + 1) calc(1 + 1), calc(1 + 2), calc(1 + 2) 4, calc(1 + 5)",
+      "6px at 3px 4px",
+      "calc(num+num) in position and radius"
+    ]
+  ];
+
+  let p = document.createElement("p");
+  let cs = getComputedStyle(p, "");
+  frame_container.appendChild(p);
+
+  for (let test of testcases) {
+    let specifiedStyle = specPrefix + test[0] + specSuffix;
+    let expectedStyle = expPrefix;
+    if (test[1] != "") {
+      expectedStyle += test[1] + ", ";
+    }
+    expectedStyle += expSuffix;
+
+    p.style.backgroundImage = specifiedStyle;
+    is(cs.backgroundImage, expectedStyle,
+       "computed value of -webkit-gradient expression (" + test[2] + ")");
+    p.style.backgroundImage = "";
+  }
+
+  p.remove();
+})();
+
 (function test_bug_1241623() {
   // Test that -webkit-gradient() styles are approximated the way we expect:
 
   // For compactness, we'll pull out the common prefix & suffix from all of the
   // specified & expected styles, and construct the full expression on the fly:
   const specPrefix = "-webkit-gradient(linear, ";
   const specSuffix = ", from(blue), to(lime))";