Bug 1218257 - Use rust lengths for the SVG lengths. r=boris
authorEmilio Cobos Álvarez <emilio@crisal.io>
Sat, 02 Mar 2019 02:09:19 +0100
changeset 462168 0456bc2c98e2749e932d5d1b20e526a96797e4ca
parent 462167 0c76d78aca4851617a655ff34df969893342d801
child 462169 16ebe5e32775afa93eec29766332c8e1e04e62c6
push id35640
push userrgurzau@mozilla.com
push dateSun, 03 Mar 2019 21:56:15 +0000
treeherdermozilla-central@325cacd86079 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersboris
bugs1218257
milestone67.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 1218257 - Use rust lengths for the SVG lengths. r=boris As it turns out we need this to avoid losing precision both during painting and during serialization. This patch also changes to serialize `context-value` if it's the computed value. I could keep the previous behavior, but it makes no sense to serialize the initial value. We're the only ones to support this value anyway, and I couldn't find a definition or spec for this. Also update tests and expectations for: * New unexpected passes. * Always serializing the unit in getComputedStyle. * Calc and interpolation support. Chrome also always serializes the unit in getComputedStyle, so I'm pretty sure this is compatible with them. Chrome is inconsistent and keeps numbers in specified style, but that's inconsistent with itself and with other quirky lengths, so I updated the tests instead. Differential Revision: https://phabricator.services.mozilla.com/D21819
dom/smil/test/db_smilCSSPaced.js
dom/svg/SVGContentUtils.cpp
dom/svg/SVGContentUtils.h
dom/svg/test/test_scientific.html
layout/style/ServoCSSPropList.mako.py
layout/style/nsComputedDOMStyle.cpp
layout/style/nsComputedDOMStyle.h
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/test/property_database.js
layout/style/test/test_transitions_per_property.html
layout/style/test/test_value_computation.html
layout/svg/SVGTextFrame.cpp
layout/svg/nsSVGUtils.cpp
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/longhands/inherited_svg.mako.rs
testing/web-platform/meta/svg/painting/parsing/stroke-dasharray-computed.svg.ini
testing/web-platform/meta/svg/painting/parsing/stroke-dashoffset-computed.svg.ini
testing/web-platform/meta/svg/painting/parsing/stroke-width-computed.svg.ini
testing/web-platform/tests/svg/painting/parsing/stroke-dashoffset-valid.svg
testing/web-platform/tests/svg/painting/parsing/stroke-width-valid.svg
testing/web-platform/tests/web-animations/animation-model/animation-types/property-list.js
testing/web-platform/tests/web-animations/animation-model/animation-types/property-types.js
--- a/dom/smil/test/db_smilCSSPaced.js
+++ b/dom/smil/test/db_smilCSSPaced.js
@@ -282,27 +282,26 @@ var gPacedBundles =
                             comp1:   "monospace"
                           },
                           "need support for more font properties"),
   ]),
   new TestcaseBundle(gPropList.opacity, _pacedTestLists.opacity),
   new TestcaseBundle(gPropList.stroke_dasharray,
                      [].concat(_pacedTestLists.lengthPctSVG, [
     new AnimTestcasePaced("7, 7, 7; 7, 10, 3; 1, 2, 3",
-                          { comp0:   "7, 7, 7",
-                            comp1_6: "7, 8.5, 5",
-                            comp1_3: "7, 10, 3",
-                            comp2_3: "4, 6, 3",
-                            comp1:   "1, 2, 3"
+                          { comp0:   "7px, 7px, 7px",
+                            comp1_6: "7px, 8.5px, 5px",
+                            comp1_3: "7px, 10px, 3px",
+                            comp2_3: "4px, 6px, 3px",
+                            comp1:   "1px, 2px, 3px"
                           }),
   ])),
   new TestcaseBundle(gPropList.stroke_dashoffset,
                      [].concat(_pacedTestLists.lengthNoUnits,
                                _pacedTestLists.lengthPx,
                                _pacedTestLists.lengthPctSVG,
                                _pacedTestLists.lengthPxPctSVG)),
   new TestcaseBundle(gPropList.stroke_width,
                      [].concat(_pacedTestLists.lengthNoUnits,
                                _pacedTestLists.lengthPx,
                                _pacedTestLists.lengthPctSVG,
                                _pacedTestLists.lengthPxPctSVG)),
-  // XXXdholbert TODO: test 'stroke-dasharray' once we support animating it
 ];
--- a/dom/svg/SVGContentUtils.cpp
+++ b/dom/svg/SVGContentUtils.cpp
@@ -174,17 +174,17 @@ static DashState GetStrokeDashData(
     for (size_t i = 0; i < dashArrayLength; i++) {
       if (dashSrc[i] < 0.0) {
         return eContinuousStroke;  // invalid
       }
       dashPattern[i] = Float(dashSrc[i]);
       (i % 2 ? totalLengthOfGaps : totalLengthOfDashes) += dashSrc[i];
     }
   } else {
-    const nsTArray<nsStyleCoord>& dasharray = aStyleSVG->mStrokeDasharray;
+    const auto& dasharray = aStyleSVG->mStrokeDasharray;
     dashArrayLength = aStyleSVG->mStrokeDasharray.Length();
     if (dashArrayLength <= 0) {
       return eContinuousStroke;
     }
     if (aElement->IsNodeOfType(nsINode::eSHAPE)) {
       pathScale =
           static_cast<SVGGeometryElement*>(aElement)->GetPathLengthScale(
               SVGGeometryElement::eForStroking);
@@ -193,18 +193,17 @@ static DashState GetStrokeDashData(
       }
     }
     Float* dashPattern = aStrokeOptions->InitDashPattern(dashArrayLength);
     if (!dashPattern) {
       return eContinuousStroke;
     }
     for (uint32_t i = 0; i < dashArrayLength; i++) {
       Float dashLength =
-          SVGContentUtils::CoordToFloat(aElement, dasharray[i], true) *
-          pathScale;
+          SVGContentUtils::CoordToFloat(aElement, dasharray[i]) * pathScale;
       if (dashLength < 0.0) {
         return eContinuousStroke;  // invalid
       }
       dashPattern[i] = dashLength;
       (i % 2 ? totalLengthOfGaps : totalLengthOfDashes) += dashLength;
     }
   }
 
@@ -238,18 +237,17 @@ static DashState GetStrokeDashData(
       aStyleSVG->mStrokeLinecap == NS_STYLE_STROKE_LINECAP_BUTT) {
     return eNoStroke;
   }
 
   if (aContextPaint && aStyleSVG->StrokeDashoffsetFromObject()) {
     aStrokeOptions->mDashOffset = Float(aContextPaint->GetStrokeDashOffset());
   } else {
     aStrokeOptions->mDashOffset =
-        SVGContentUtils::CoordToFloat(aElement, aStyleSVG->mStrokeDashoffset,
-                                      false) *
+        SVGContentUtils::CoordToFloat(aElement, aStyleSVG->mStrokeDashoffset) *
         pathScale;
   }
 
   return eDashedStroke;
 }
 
 void SVGContentUtils::GetStrokeOptions(AutoStrokeOptions* aStrokeOptions,
                                        SVGElement* aElement,
@@ -340,17 +338,17 @@ Float SVGContentUtils::GetStrokeWidth(SV
   }
 
   const nsStyleSVG* styleSVG = computedStyle->StyleSVG();
 
   if (aContextPaint && styleSVG->StrokeWidthFromObject()) {
     return aContextPaint->GetStrokeWidth();
   }
 
-  return SVGContentUtils::CoordToFloat(aElement, styleSVG->mStrokeWidth, true);
+  return SVGContentUtils::CoordToFloat(aElement, styleSVG->mStrokeWidth);
 }
 
 float SVGContentUtils::GetFontSize(Element* aElement) {
   if (!aElement) {
     return 1.0f;
   }
 
   nsPresContext* pc = nsContentUtils::GetContextForContent(aElement);
@@ -761,46 +759,27 @@ bool SVGContentUtils::ParseInteger(Range
 bool SVGContentUtils::ParseInteger(const nsAString& aString, int32_t& aValue) {
   RangedPtr<const char16_t> iter = GetStartRangedPtr(aString);
   const RangedPtr<const char16_t> end = GetEndRangedPtr(aString);
 
   return ParseInteger(iter, end, aValue) && iter == end;
 }
 
 float SVGContentUtils::CoordToFloat(SVGElement* aContent,
-                                    const nsStyleCoord& aCoord,
-                                    bool aClampNegativeCalc) {
-  switch (aCoord.GetUnit()) {
-    case eStyleUnit_Coord:
-      return nsPresContext::AppUnitsToFloatCSSPixels(aCoord.GetCoordValue());
-
-    case eStyleUnit_Percent: {
-      SVGViewportElement* ctx = aContent->GetCtx();
-      if (!ctx) {
-        return 0.0f;
-      }
-      return aCoord.GetPercentValue() * ctx->GetLength(SVGContentUtils::XY);
-    }
-
-    case eStyleUnit_Calc: {
-      auto* calc = aCoord.GetCalcValue();
-      float result = nsPresContext::AppUnitsToFloatCSSPixels(calc->mLength);
-      if (calc->mHasPercent) {
-        SVGViewportElement* ctx = aContent->GetCtx();
-        if (!ctx) {
-          return 0.0f;
-        }
-        result += calc->mPercent * ctx->GetLength(SVGContentUtils::XY);
-      }
-      return aClampNegativeCalc ? std::max(result, 0.0f) : result;
-    }
-    default:
-      MOZ_ASSERT_UNREACHABLE("Unknown unit for SVG length");
-      return 0.0f;
+                                    const LengthPercentage& aLength) {
+  float result = aLength.ResolveToCSSPixelsWith([&] {
+    SVGViewportElement* ctx = aContent->GetCtx();
+    return CSSCoord(ctx ? ctx->GetLength(SVGContentUtils::XY) : 0.0f);
+  });
+  if (aLength.clamping_mode == StyleAllowedNumericType::NonNegative) {
+    result = std::max(result, 0.0f);
+  } else {
+    MOZ_ASSERT(aLength.clamping_mode == StyleAllowedNumericType::All);
   }
+  return result;
 }
 
 already_AddRefed<gfx::Path> SVGContentUtils::GetPath(
     const nsAString& aPathString) {
   SVGPathData pathData;
   SVGPathDataParser parser(aPathString, &pathData);
   if (!parser.Parse()) {
     return NULL;
--- a/dom/svg/SVGContentUtils.h
+++ b/dom/svg/SVGContentUtils.h
@@ -8,25 +8,25 @@
 #define MOZILLA_SVGCONTENTUTILS_H
 
 // include math.h to pick up definition of M_ maths defines e.g. M_PI
 #include <math.h>
 
 #include "mozilla/gfx/2D.h"  // for StrokeOptions
 #include "mozilla/gfx/Matrix.h"
 #include "mozilla/RangedPtr.h"
+#include "nsStyleCoord.h"
 #include "nsError.h"
 #include "nsStringFwd.h"
 #include "gfx2DGlue.h"
 
 class nsIContent;
 
 class nsIFrame;
 class nsPresContext;
-class nsStyleCoord;
 
 namespace mozilla {
 class ComputedStyle;
 class SVGAnimatedTransformList;
 class SVGAnimatedPreserveAspectRatio;
 class SVGContextPaint;
 class SVGPreserveAspectRatio;
 namespace dom {
@@ -316,19 +316,17 @@ class SVGContentUtils {
    * Parsing fails if there is anything left over after the number.
    */
   static bool ParseInteger(const nsAString& aString, int32_t& aValue);
 
   /**
    * Converts an nsStyleCoord into a userspace value, resolving percentage
    * values relative to aContent's SVG viewport.
    */
-  static float CoordToFloat(dom::SVGElement* aContent,
-                            const nsStyleCoord& aCoord,
-                            bool aClampNegativeCalc);
+  static float CoordToFloat(dom::SVGElement* aContent, const LengthPercentage&);
   /**
    * Parse the SVG path string
    * Returns a path
    * string formatted as an SVG path
    */
   static already_AddRefed<mozilla::gfx::Path> GetPath(
       const nsAString& aPathString);
 
--- a/dom/svg/test/test_scientific.html
+++ b/dom/svg/test/test_scientific.html
@@ -23,40 +23,40 @@ https://bugzilla.mozilla.org/show_bug.cg
 	{
 	var doc = $("svg").contentWindow.document;
 	var rect = doc.getElementById("rect");
 	var text = doc.getElementById("text");
 
 	// ordinary
 
 	rect.setAttribute("stroke-width", "5");
-	is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "5", "Ordinary");
+	is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "5px", "Ordinary");
 
 	// valid exponential notation
 
 	rect.setAttribute("stroke-width", "4E1");
-	is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "40", "Exponent");
+	is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "40px", "Exponent");
 
 	rect.setAttribute("stroke-width", "6e1");
-	is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "60", "Lower-case Exponent");
+	is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "60px", "Lower-case Exponent");
 
 	rect.setAttribute("stroke-width", "2E+1");
-	is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "20", "Positive Exponent");
+	is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "20px", "Positive Exponent");
 
 	rect.setAttribute("stroke-width", "100E-1");
-	is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "10", "Negative Exponent");
+	is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "10px", "Negative Exponent");
 
 	rect.setAttribute("stroke-width", "0.7E1");
-	is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "7", "Floating Point with Exponent");
+	is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "7px", "Floating Point with Exponent");
 
 	rect.setAttribute("stroke-width", "50.0E-1");
-	is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "5", "Floating Point with Negative Exponent");
+	is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "5px", "Floating Point with Negative Exponent");
 
 	rect.setAttribute("stroke-width", "0.8E+1");
-	is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "8", "Floating Point with Positive Exponent");
+	is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "8px", "Floating Point with Positive Exponent");
 
 	rect.setAttribute("stroke-width", "4E1px");
 	is(doc.defaultView.getComputedStyle(rect).getPropertyValue("stroke-width"), "40px", "Units");
 
 	// check units that begin with the letter e
 
 	var font_size = doc.defaultView.getComputedStyle(rect).getPropertyValue("font-size");
 
--- a/layout/style/ServoCSSPropList.mako.py
+++ b/layout/style/ServoCSSPropList.mako.py
@@ -83,17 +83,16 @@ LONGHANDS_NOT_SERIALIZED_WITH_SERVO = [
     "color",
     "column-count",
     "column-gap",
     "column-rule-width",
     "column-width",
     "contain",
     "display",
     "fill",
-    "fill-opacity",
     "filter",
     "flex-basis",
     "flex-grow",
     "flex-shrink",
     "grid-auto-columns",
     "grid-auto-flow",
     "grid-auto-rows",
     "grid-column-end",
@@ -124,21 +123,16 @@ LONGHANDS_NOT_SERIALIZED_WITH_SERVO = [
     "-moz-outline-radius-topright",
     "outline-width",
     "paint-order",
     "row-gap",
     "scrollbar-color",
     "scroll-snap-points-x",
     "scroll-snap-points-y",
     "stroke",
-    "stroke-dasharray",
-    "stroke-dashoffset",
-    "stroke-miterlimit",
-    "stroke-opacity",
-    "stroke-width",
     "text-decoration-line",
     "text-emphasis-position",
     "text-emphasis-style",
     "text-overflow",
     "text-shadow",
     "touch-action",
     "transition-delay",
     "transition-duration",
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -3193,66 +3193,16 @@ already_AddRefed<CSSValue> nsComputedDOM
 
 already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetMarkerStart() {
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
   SetValueToURLValue(StyleSVG()->mMarkerStart, val);
 
   return val.forget();
 }
 
-already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetStrokeDasharray() {
-  const nsStyleSVG* svg = StyleSVG();
-
-  if (svg->mStrokeDasharray.IsEmpty()) {
-    RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
-    val->SetIdent(eCSSKeyword_none);
-    return val.forget();
-  }
-
-  RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
-
-  for (uint32_t i = 0; i < svg->mStrokeDasharray.Length(); i++) {
-    RefPtr<nsROCSSPrimitiveValue> dash = new nsROCSSPrimitiveValue;
-    SetValueToCoord(dash, svg->mStrokeDasharray[i], true);
-    valueList->AppendCSSValue(dash.forget());
-  }
-
-  return valueList.forget();
-}
-
-already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetStrokeDashoffset() {
-  RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
-  SetValueToCoord(val, StyleSVG()->mStrokeDashoffset, false);
-  return val.forget();
-}
-
-already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetStrokeWidth() {
-  RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
-  SetValueToCoord(val, StyleSVG()->mStrokeWidth, true);
-  return val.forget();
-}
-
-already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetFillOpacity() {
-  RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
-  val->SetNumber(StyleSVG()->mFillOpacity);
-  return val.forget();
-}
-
-already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetStrokeMiterlimit() {
-  RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
-  val->SetNumber(StyleSVG()->mStrokeMiterlimit);
-  return val.forget();
-}
-
-already_AddRefed<CSSValue> nsComputedDOMStyle::DoGetStrokeOpacity() {
-  RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
-  val->SetNumber(StyleSVG()->mStrokeOpacity);
-  return val.forget();
-}
-
 void nsComputedDOMStyle::BoxValuesToString(
     nsAString& aString, const nsTArray<nsStyleCoord>& aBoxValues,
     bool aClampNegativeCalc) {
   MOZ_ASSERT(aBoxValues.Length() == 4, "wrong number of box values");
   nsAutoString value1, value2, value3, value4;
   SetCssTextToCoord(value1, aBoxValues[0], aClampNegativeCalc);
   SetCssTextToCoord(value2, aBoxValues[1], aClampNegativeCalc);
   SetCssTextToCoord(value3, aBoxValues[2], aClampNegativeCalc);
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -373,24 +373,18 @@ class nsComputedDOMStyle final : public 
   already_AddRefed<CSSValue> DoGetRowGap();
 
   /* SVG properties */
   already_AddRefed<CSSValue> DoGetFill();
   already_AddRefed<CSSValue> DoGetStroke();
   already_AddRefed<CSSValue> DoGetMarkerEnd();
   already_AddRefed<CSSValue> DoGetMarkerMid();
   already_AddRefed<CSSValue> DoGetMarkerStart();
-  already_AddRefed<CSSValue> DoGetStrokeDasharray();
 
-  already_AddRefed<CSSValue> DoGetStrokeDashoffset();
-  already_AddRefed<CSSValue> DoGetStrokeWidth();
 
-  already_AddRefed<CSSValue> DoGetFillOpacity();
-  already_AddRefed<CSSValue> DoGetStrokeMiterlimit();
-  already_AddRefed<CSSValue> DoGetStrokeOpacity();
 
   already_AddRefed<CSSValue> DoGetFilter();
   already_AddRefed<CSSValue> DoGetPaintOrder();
 
   // For working around a MSVC bug. See related comment in
   // GenerateComputedDOMStyleGenerated.py.
   already_AddRefed<CSSValue> DummyGetter();
 
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -637,19 +637,18 @@ nsChangeHint nsStyleColumn::CalcDifferen
 }
 
 // --------------------
 // nsStyleSVG
 //
 nsStyleSVG::nsStyleSVG(const Document& aDocument)
     : mFill(eStyleSVGPaintType_Color),  // Will be initialized to NS_RGB(0,0,0)
       mStroke(eStyleSVGPaintType_None),
-      mStrokeDashoffset(0, nsStyleCoord::CoordConstructor),
-      mStrokeWidth(nsPresContext::CSSPixelsToAppUnits(1),
-                   nsStyleCoord::CoordConstructor),
+      mStrokeDashoffset(LengthPercentage::Zero()),
+      mStrokeWidth(LengthPercentage::FromPixels(1.0f)),
       mFillOpacity(1.0f),
       mStrokeMiterlimit(4.0f),
       mStrokeOpacity(1.0f),
       mClipRule(StyleFillRule::Nonzero),
       mColorInterpolation(NS_STYLE_COLOR_INTERPOLATION_SRGB),
       mColorInterpolationFilters(NS_STYLE_COLOR_INTERPOLATION_LINEARRGB),
       mFillRule(StyleFillRule::Nonzero),
       mPaintOrder(NS_STYLE_PAINT_ORDER_NORMAL),
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -2703,21 +2703,21 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
 
   nsChangeHint CalcDifference(const nsStyleSVG& aNewData) const;
 
   nsStyleSVGPaint mFill;
   nsStyleSVGPaint mStroke;
   RefPtr<mozilla::css::URLValue> mMarkerEnd;
   RefPtr<mozilla::css::URLValue> mMarkerMid;
   RefPtr<mozilla::css::URLValue> mMarkerStart;
-  nsTArray<nsStyleCoord> mStrokeDasharray;  // coord, percent, factor
+  nsTArray<mozilla::NonNegativeLengthPercentage> mStrokeDasharray;
   nsTArray<RefPtr<nsAtom>> mContextProps;
 
-  nsStyleCoord mStrokeDashoffset;  // coord, percent, factor
-  nsStyleCoord mStrokeWidth;       // coord, percent, factor
+  mozilla::LengthPercentage mStrokeDashoffset;
+  mozilla::NonNegativeLengthPercentage mStrokeWidth;
 
   float mFillOpacity;
   float mStrokeMiterlimit;
   float mStrokeOpacity;
 
   mozilla::StyleFillRule mClipRule;
   uint8_t mColorInterpolation;         // NS_STYLE_COLOR_INTERPOLATION_*
   uint8_t mColorInterpolationFilters;  // NS_STYLE_COLOR_INTERPOLATION_*
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -5139,18 +5139,18 @@ var gCSSProperties = {
     initial_values: [ "black", "#000", "#000000", "rgb(0,0,0)", "rgba(0,0,0,1)" ],
     other_values: [ "green", "#fc3", "url('#myserver')", "url(foo.svg#myserver)", 'url("#myserver") green', "none", "currentColor", "context-fill", "context-stroke" ],
     invalid_values: [ "000000", "ff00ff", "url('#myserver') rgb(0, rubbish, 0)" ]
   },
   "fill-opacity": {
     domProp: "fillOpacity",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
-    initial_values: [ "1", "2.8", "1.000", "context-fill-opacity", "context-stroke-opacity" ],
-    other_values: [ "0", "0.3", "-7.3" ],
+    initial_values: [ "1", "2.8", "1.000", ],
+    other_values: [ "0", "0.3", "-7.3", "context-fill-opacity", "context-stroke-opacity" ],
     invalid_values: []
   },
   "fill-rule": {
     domProp: "fillRule",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "nonzero" ],
     other_values: [ "evenodd" ],
@@ -5554,26 +5554,26 @@ var gCSSProperties = {
     initial_values: [ "none" ],
     other_values: [ "black", "#000", "#000000", "rgb(0,0,0)", "rgba(0,0,0,1)", "green", "#fc3", "url('#myserver')", "url(foo.svg#myserver)", 'url("#myserver") green', "currentColor", "context-fill", "context-stroke" ],
     invalid_values: [ "000000", "ff00ff" ]
   },
   "stroke-dasharray": {
     domProp: "strokeDasharray",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
-    initial_values: [ "none", "context-value" ],
-    other_values: [ "5px,3px,2px", "5px 3px 2px", "  5px ,3px\t, 2px ", "1px", "5%", "3em", "0.0002" ],
+    initial_values: [ "none" ],
+    other_values: [ "5px,3px,2px", "5px 3px 2px", "  5px ,3px\t, 2px ", "1px", "5%", "3em", "0.0002", "context-value"],
     invalid_values: [ "-5px,3px,2px", "5px,3px,-2px" ]
   },
   "stroke-dashoffset": {
     domProp: "strokeDashoffset",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
-    initial_values: [ "0", "-0px", "0em", "context-value" ],
-    other_values: [ "3px", "3%", "1em", "0.0002" ],
+    initial_values: [ "0", "-0px", "0em" ],
+    other_values: [ "3px", "3%", "1em", "0.0002", "context-value" ],
     invalid_values: []
   },
   "stroke-linecap": {
     domProp: "strokeLinecap",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "butt" ],
     other_values: [ "round", "square" ],
@@ -5594,26 +5594,26 @@ var gCSSProperties = {
     initial_values: [ "4" ],
     other_values: [ "1", "7", "5000", "1.1" ],
     invalid_values: [ "0.9", "0", "-1", "3px", "-0.3" ]
   },
   "stroke-opacity": {
     domProp: "strokeOpacity",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
-    initial_values: [ "1", "2.8", "1.000", "context-fill-opacity", "context-stroke-opacity" ],
-    other_values: [ "0", "0.3", "-7.3" ],
+    initial_values: [ "1", "2.8", "1.000" ],
+    other_values: [ "0", "0.3", "-7.3", "context-fill-opacity", "context-stroke-opacity" ],
     invalid_values: []
   },
   "stroke-width": {
     domProp: "strokeWidth",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
-    initial_values: [ "1px", "context-value" ],
-    other_values: [ "0", "0px", "-0em", "17px", "0.2em", "0.0002" ],
+    initial_values: [ "1px" ],
+    other_values: [ "0", "0px", "-0em", "17px", "0.2em", "0.0002", "context-value" ],
     invalid_values: [ "-0.1px", "-3px" ]
   },
   "text-anchor": {
     domProp: "textAnchor",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "start" ],
     other_values: [ "middle", "end" ],
--- a/layout/style/test/test_transitions_per_property.html
+++ b/layout/style/test/test_transitions_per_property.html
@@ -256,30 +256,26 @@ var supported_properties = {
                     test_currentcolor_transition ],
     "stop-opacity" : [ test_float_zeroToOne_transition,
                        // opacity is clamped in computed style
                        // (not parsing/interpolation)
                        test_float_zeroToOne_clamped ],
     "stroke": [ test_color_transition,
                 test_currentcolor_transition ],
     "stroke-dasharray": [ test_dasharray_transition ],
-    // NOTE: when calc() is supported on 'stroke-dashoffset', we should
-    // add test_length_percent_calc_transition.
-    "stroke-dashoffset": [ test_length_transition_svg, test_percent_transition,
-                           test_length_unclamped_svg, test_percent_unclamped ],
+    "stroke-dashoffset": [ test_length_transition, test_percent_transition,
+                           test_length_unclamped, test_percent_unclamped, ],
     "stroke-miterlimit": [ test_float_aboveOne_transition,
                            test_float_aboveOne_clamped ],
     "stroke-opacity" : [ test_float_zeroToOne_transition,
                          // opacity is clamped in computed style
                          // (not parsing/interpolation)
                          test_float_zeroToOne_clamped ],
-    // NOTE: when calc() is supported on 'stroke-width', we should add
-    // test_length_percent_calc_transition.
-    "stroke-width": [ test_length_transition_svg, test_percent_transition,
-                      test_length_clamped_svg, test_percent_clamped ],
+    "stroke-width": [ test_length_transition, test_percent_transition,
+                      test_length_clamped, test_percent_clamped, ],
     "-moz-tab-size": [ test_float_zeroToOne_transition,
                        test_float_aboveOne_transition, test_length_clamped ],
     "text-decoration": [ test_color_shorthand_transition,
                          test_currentcolor_shorthand_transition ],
     "text-decoration-color": [ test_color_transition,
                                test_currentcolor_transition ],
     "text-emphasis-color": [ test_color_transition,
                              test_currentcolor_transition ],
@@ -1259,62 +1255,45 @@ function check_distance(prop, start, qua
   var sq = get_distance(prop, start, quarter);
   var se = get_distance(prop, start, end);
   var qe = get_distance(prop, quarter, end);
 
   ok(Math.abs((sq * 4 - se) / se) < 0.0001, "property '" + prop + "': distance " + sq + " from start '" + start + "' to quarter '" + quarter + "' should be quarter distance " + se + " from start '" + start + "' to end '" + end + "'");
   ok(Math.abs((qe * 4 - se * 3) / se) < 0.0001, "property '" + prop + "': distance " + qe + " from quarter '" + quarter + "' to end '" + end + "' should be three quarters distance " + se + " from start '" + start + "' to end '" + end + "'");
 }
 
-function test_length_transition_svg_or_units(prop, numbers_are_pixels) {
+function test_length_transition(prop) {
   div.style.setProperty("transition-property", "none", "");
   div.style.setProperty(prop, "4px", "");
   is(cs.getPropertyValue(prop), "4px",
      "length-valued property " + prop + ": computed value before transition");
   div.style.setProperty("transition-property", prop, "");
   div.style.setProperty(prop, "12px", "");
-  is(cs.getPropertyValue(prop), numbers_are_pixels ? "6" : "6px",
+  is(cs.getPropertyValue(prop), "6px",
      "length-valued property " + prop + ": interpolation of lengths");
   check_distance(prop, "4px", "6px", "12px");
 }
 
-function test_length_transition(prop) {
-  test_length_transition_svg_or_units(prop, false);
-}
-
-function test_length_transition_svg(prop) {
-  test_length_transition_svg_or_units(prop, true);
-}
-
 function test_length_clamped(prop) {
-  test_length_clamped_or_unclamped(prop, true, false);
+  test_length_clamped_or_unclamped(prop, true);
 }
 
 function test_length_unclamped(prop) {
-  test_length_clamped_or_unclamped(prop, false, false);
+  test_length_clamped_or_unclamped(prop, false);
 }
 
-function test_length_clamped_svg(prop) {
-  test_length_clamped_or_unclamped(prop, true, true);
-}
-
-function test_length_unclamped_svg(prop) {
-  test_length_clamped_or_unclamped(prop, false, true);
-}
-
-function test_length_clamped_or_unclamped(prop, is_clamped, numbers_are_pixels) {
+function test_length_clamped_or_unclamped(prop, is_clamped) {
   div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
   div.style.setProperty("transition-property", "none", "");
   div.style.setProperty(prop, "0px", "");
   is(cs.getPropertyValue(prop), "0px",
      "length-valued property " + prop + ": flush before clamping test");
   div.style.setProperty("transition-property", prop, "");
   div.style.setProperty(prop, "100px", "");
-  (is_clamped ? is : isnot)(cs.getPropertyValue(prop),
-     numbers_are_pixels ? "0" : "0px",
+  (is_clamped ? is : isnot)(cs.getPropertyValue(prop), "0px",
      "length-valued property " + prop + ": clamping of negatives");
   div.style.setProperty("transition-timing-function", "linear", "");
 }
 
 // Test transition to/from the special 'flex-basis: content' keyword.
 function test_flex_basis_content_transition(prop) {
   is(prop, "flex-basis", "this test function should only be called for 'flex-basis'");
 
@@ -1434,16 +1413,18 @@ function test_percent_clamped_or_unclamp
   var zero_val = cs.getPropertyValue(prop); // flushes too
   div.style.setProperty("transition-property", prop, "");
   div.style.setProperty(prop, "150%", "");
   (is_clamped ? is : isnot)(cs.getPropertyValue(prop), zero_val,
      "percent-valued property " + prop + ": clamping of negatives");
   div.style.setProperty("transition-timing-function", "linear", "");
 }
 
+// FIXME: This doesn't deal well with properties for which the resolved value
+// is not the used value, like stroke-dashoffset or stroke-width.
 function test_length_percent_calc_transition(prop) {
   div.style.setProperty("transition-property", "none", "");
   div.style.setProperty(prop, "0%", "");
   var av = cs.getPropertyValue(prop);
   var a = any_unit_to_num(av);
   div.style.setProperty(prop, "100%", "");
   var bv = cs.getPropertyValue(prop);
   var b = any_unit_to_num(bv);
@@ -2041,63 +2022,70 @@ function test_shadow_transition(prop) {
      "timing function which produces values greater than 1.0");
 
   div.style.setProperty("transition-timing-function", origTimingFunc, "");
 }
 
 function test_dasharray_transition(prop) {
   div.style.setProperty("transition-property", "none", "");
   div.style.setProperty(prop, "3", "");
-  is(cs.getPropertyValue(prop), "3",
+  is(cs.getPropertyValue(prop), "3px",
      "dasharray-valued property " + prop +
      ": computed value before transition");
   div.style.setProperty("transition-property", prop, "");
   div.style.setProperty(prop, "15px", "");
-  is(cs.getPropertyValue(prop), "6",
+  is(cs.getPropertyValue(prop), "6px",
      "dasharray-valued property " + prop + ": interpolation of dasharray");
   check_distance(prop, "3", "6", "15px");
   div.style.setProperty(prop, "none", "");
   is(cs.getPropertyValue(prop), "none",
      "dasharray-valued property " + prop + ": non-interpolability of none");
   div.style.setProperty(prop, "6,8px,4,4", "");
-  is(cs.getPropertyValue(prop), "6, 8px, 4, 4",
+  is(cs.getPropertyValue(prop), "6px, 8px, 4px, 4px",
      "dasharray-valued property " + prop +
      ": computed value before transition");
   div.style.setProperty(prop, "14, 12,16,16px", "");
-  is(cs.getPropertyValue(prop), "8, 9, 7, 7",
+  is(cs.getPropertyValue(prop), "8px, 9px, 7px, 7px",
      "dasharray-valued property " + prop + ": interpolation of dasharray");
   check_distance(prop, "6,8px,4,4", "8,9,7,7", "14, 12,16,16px");
   div.style.setProperty(prop, "none", "");
   is(cs.getPropertyValue(prop), "none",
      "dasharray-valued property " + prop + ": non-interpolability of none");
   div.style.setProperty(prop, "8,16,4", "");
-  is(cs.getPropertyValue(prop), "8, 16, 4",
+  is(cs.getPropertyValue(prop), "8px, 16px, 4px",
      "dasharray-valued property " + prop +
      ": computed value before transition");
   div.style.setProperty(prop, "4,8,12,16", "");
-  is(cs.getPropertyValue(prop), "7, 14, 6, 10, 13, 5, 9, 16, 4, 8, 15, 7",
+  is(cs.getPropertyValue(prop), "7px, 14px, 6px, 10px, 13px, 5px, 9px, 16px, 4px, 8px, 15px, 7px",
      "dasharray-valued property " + prop + ": interpolation of dasharray");
   check_distance(prop, "8,16,4", "7, 14, 6, 10, 13, 5, 9, 16, 4, 8, 15, 7",
                        "4,8,12,16");
   div.style.setProperty(prop, "2,50%,6,10", "");
-  is(cs.getPropertyValue(prop), "2, 50%, 6, 10",
-     "dasharray-valued property " + prop + ": non-interpolability of mixed units");
+  is(cs.getPropertyValue(prop),
+     "5.75px, calc(12.5% + 10.5px), 6px, 10px, 10.25px, calc(12.5% + 3.75px), 8.25px, 14.5px, 3.5px, calc(12.5% + 6px), 12.75px, 7.75px",
+     "dasharray-valued property " + prop + ": interpolability of mixed units");
+  div.style.setProperty(prop, "none", "");
+  is(cs.getPropertyValue(prop), "none",
+     "dasharray-valued property " + prop + ": non-interpolability of none");
+  div.style.setProperty(prop, "2,50%,6,10", "");
+  is(cs.getPropertyValue(prop), "2px, 50%, 6px, 10px",
+     "dasharray-valued property " + prop + ": non-interpolability of none");
   div.style.setProperty(prop, "6,30%,2,2", "");
-  is(cs.getPropertyValue(prop), "3, 45%, 5, 8",
+  is(cs.getPropertyValue(prop), "3px, 45%, 5px, 8px",
      "dasharray-valued property " + prop + ": interpolation of dasharray");
   check_distance(prop, "2,50%,6,10", "3, 45%, 5, 8", "6,30%,2,2");
 
   div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
   div.style.setProperty("transition-property", "none", "");
   div.style.setProperty(prop, "0,0%", "");
-  is(cs.getPropertyValue(prop), "0, 0%",
+  is(cs.getPropertyValue(prop), "0px, 0%",
      "dasharray-valued property " + prop + ": flush before clamping test");
   div.style.setProperty("transition-property", prop, "");
-  div.style.setProperty(prop, "5, 25%", "");
-  is(cs.getPropertyValue(prop), "0, 0%",
+  div.style.setProperty(prop, "5px, 25%", "");
+  is(cs.getPropertyValue(prop), "0px, 0%",
      "dasharray-valued property " + prop + ": clamping of negatives");
   div.style.setProperty("transition-timing-function", "linear", "");
 }
 
 function test_radius_transition(prop) {
   div.style.setProperty("transition-property", "none", "");
 
   // FIXME: Test a square for now, since we haven't updated to the spec
--- a/layout/style/test/test_value_computation.html
+++ b/layout/style/test/test_value_computation.html
@@ -39,22 +39,16 @@
 
   
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for computation of values in property database **/
 
-var gBadComputed = {
-  // This is the only SVG-length property (i.e., length allowing
-  // unitless lengths) whose initial value is zero.
-  "stroke-dashoffset": [ "0", "-moz-objectValue" ],
-};
-
 var gBadComputedNoFrame = {
   // These are probably bogus tests...
   "-moz-margin-end": [ "0%", "calc(0% + 0px)" ],
   "-moz-margin-start": [ "0%", "calc(0% + 0px)" ],
   "-moz-padding-end": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
   "-moz-padding-start": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
   "margin": [ "0% 0px 0em 0pt" ],
   "margin-block-end": [ "0%", "calc(0% + 0px)" ],
@@ -72,20 +66,16 @@ var gBadComputedNoFrame = {
   "padding-inline-end": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
   "padding-inline-start": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
   "padding-left": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
   "padding-right": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
   "padding-top": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
 };
 
 function xfail_value(property, value, is_initial, has_frame) {
-  if ((property in gBadComputed) &&
-      gBadComputed[property].includes(value))
-    return true;
-
   if (!has_frame && (property in gBadComputedNoFrame) &&
       gBadComputedNoFrame[property].includes(value))
     return true;
 
   return false;
 }
 
 var gSwapInitialWhenHaveFrame = {
--- a/layout/svg/SVGTextFrame.cpp
+++ b/layout/svg/SVGTextFrame.cpp
@@ -5001,17 +5001,17 @@ bool SVGTextFrame::ShouldRenderAsPath(ns
         (style->mFill.Type() == eStyleSVGPaintType_Color &&
          style->mFillOpacity == 1))) {
     return true;
   }
 
   // Text has a stroke.
   if (style->HasStroke() &&
       SVGContentUtils::CoordToFloat(static_cast<SVGElement*>(GetContent()),
-                                    style->mStrokeWidth, true) > 0) {
+                                    style->mStrokeWidth) > 0) {
     return true;
   }
 
   return false;
 }
 
 void SVGTextFrame::ScheduleReflowSVG() {
   if (mState & NS_FRAME_IS_NONDISPLAY) {
--- a/layout/svg/nsSVGUtils.cpp
+++ b/layout/svg/nsSVGUtils.cpp
@@ -1537,17 +1537,17 @@ float nsSVGUtils::GetStrokeWidth(nsIFram
   }
 
   nsIContent* content = aFrame->GetContent();
   if (content->IsText()) {
     content = content->GetParent();
   }
 
   SVGElement* ctx = static_cast<SVGElement*>(content);
-  return SVGContentUtils::CoordToFloat(ctx, style->mStrokeWidth, true);
+  return SVGContentUtils::CoordToFloat(ctx, style->mStrokeWidth);
 }
 
 void nsSVGUtils::SetupStrokeGeometry(nsIFrame* aFrame, gfxContext* aContext,
                                      SVGContextPaint* aContextPaint) {
   SVGContentUtils::AutoStrokeOptions strokeOptions;
   SVGContentUtils::GetStrokeOptions(
       &strokeOptions, static_cast<SVGElement*>(aFrame->GetContent()),
       aFrame->Style(), aContextPaint);
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -473,42 +473,38 @@ def set_gecko_property(ffi_name, expr):
             SVGLength::ContextValue => {
                 self.gecko.mContextFlags |= CONTEXT_VALUE;
                 match longhands::${ident}::get_initial_value() {
                     SVGLength::LengthPercentage(length) => length,
                     _ => unreachable!("Initial value should not be context-value"),
                 }
             }
         };
-        self.gecko.${gecko_ffi_name}.set(length)
+        self.gecko.${gecko_ffi_name} = length;
     }
 
     pub fn copy_${ident}_from(&mut self, other: &Self) {
         use crate::gecko_bindings::structs::nsStyleSVG_${ident.upper()}_CONTEXT as CONTEXT_VALUE;
-        self.gecko.${gecko_ffi_name}.copy_from(&other.gecko.${gecko_ffi_name});
+        self.gecko.${gecko_ffi_name} = other.gecko.${gecko_ffi_name};
         self.gecko.mContextFlags =
             (self.gecko.mContextFlags & !CONTEXT_VALUE) |
             (other.gecko.mContextFlags & CONTEXT_VALUE);
     }
 
     pub fn reset_${ident}(&mut self, other: &Self) {
         self.copy_${ident}_from(other)
     }
 
     pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
         use crate::values::generics::svg::SVGLength;
-        use crate::values::computed::LengthPercentage;
         use crate::gecko_bindings::structs::nsStyleSVG_${ident.upper()}_CONTEXT as CONTEXT_VALUE;
         if (self.gecko.mContextFlags & CONTEXT_VALUE) != 0 {
             return SVGLength::ContextValue;
         }
-        // TODO(emilio): Use the Rust representation instead.
-        let length =
-            LengthPercentage::from_gecko_style_coord(&self.gecko.${gecko_ffi_name}).unwrap();
-        SVGLength::LengthPercentage(length.into())
+        SVGLength::LengthPercentage(self.gecko.${gecko_ffi_name})
     }
 </%def>
 
 <%def name="impl_svg_opacity(ident, gecko_ffi_name)">
     <% source_prefix = ident.split("_")[0].upper() + "_OPACITY_SOURCE" %>
 
     pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
         use crate::gecko_bindings::structs::nsStyleSVG_${source_prefix}_MASK as MASK;
@@ -4702,17 +4698,17 @@ clip-path
         match v {
             SVGStrokeDashArray::Values(v) => {
                 let v = v.into_iter();
                 self.gecko.mContextFlags &= !CONTEXT_VALUE;
                 unsafe {
                     bindings::Gecko_nsStyleSVG_SetDashArrayLength(&mut self.gecko, v.len() as u32);
                 }
                 for (gecko, servo) in self.gecko.mStrokeDasharray.iter_mut().zip(v) {
-                    gecko.set(servo)
+                    *gecko = servo;
                 }
             }
             SVGStrokeDashArray::ContextValue => {
                 self.gecko.mContextFlags |= CONTEXT_VALUE;
                 unsafe {
                     bindings::Gecko_nsStyleSVG_SetDashArrayLength(&mut self.gecko, 0);
                 }
             }
@@ -4730,29 +4726,23 @@ clip-path
     }
 
     pub fn reset_stroke_dasharray(&mut self, other: &Self) {
         self.copy_stroke_dasharray_from(other)
     }
 
     pub fn clone_stroke_dasharray(&self) -> longhands::stroke_dasharray::computed_value::T {
         use crate::gecko_bindings::structs::nsStyleSVG_STROKE_DASHARRAY_CONTEXT as CONTEXT_VALUE;
-        use crate::values::computed::NonNegativeLengthPercentage;
         use crate::values::generics::svg::SVGStrokeDashArray;
 
         if self.gecko.mContextFlags & CONTEXT_VALUE != 0 {
             debug_assert_eq!(self.gecko.mStrokeDasharray.len(), 0);
             return SVGStrokeDashArray::ContextValue;
         }
-        // TODO(emilio): Use the rust representation instead.
-        let mut vec = vec![];
-        for gecko in self.gecko.mStrokeDasharray.iter() {
-            vec.push(NonNegativeLengthPercentage::from_gecko_style_coord(gecko).unwrap());
-        }
-        SVGStrokeDashArray::Values(vec)
+        SVGStrokeDashArray::Values(self.gecko.mStrokeDasharray.iter().cloned().collect())
     }
 
     #[allow(non_snake_case)]
     pub fn _moz_context_properties_count(&self) -> usize {
         self.gecko.mContextProps.len()
     }
 
     #[allow(non_snake_case)]
--- a/servo/components/style/properties/longhands/inherited_svg.mako.rs
+++ b/servo/components/style/properties/longhands/inherited_svg.mako.rs
@@ -79,17 +79,18 @@
     "Default::default()",
     products="gecko",
     animation_value_type="IntermediateSVGPaint",
     boxed=True,
     spec="https://www.w3.org/TR/SVG2/painting.html#SpecifyingStrokePaint",
 )}
 
 ${helpers.predefined_type(
-    "stroke-width", "SVGWidth",
+    "stroke-width",
+    "SVGWidth",
     "computed::SVGWidth::one()",
     products="gecko",
     animation_value_type="crate::values::computed::SVGWidth",
     spec="https://www.w3.org/TR/SVG2/painting.html#StrokeWidth",
 )}
 
 ${helpers.single_keyword(
     "stroke-linecap",
deleted file mode 100644
--- a/testing/web-platform/meta/svg/painting/parsing/stroke-dasharray-computed.svg.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[stroke-dasharray-computed.svg]
-  [Property stroke-dasharray value '0, 5' computes to '0px, 5px']
-    expected: FAIL
-
-  [Property stroke-dasharray value '10' computes to '10px']
-    expected: FAIL
-
-  [Property stroke-dasharray value 'calc(50% + 60px)' computes to 'calc(50% + 60px)']
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/svg/painting/parsing/stroke-dashoffset-computed.svg.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[stroke-dashoffset-computed.svg]
-  [Property stroke-dashoffset value 'calc(50% + 60px)' computes to 'calc(50% + 60px)']
-    expected: FAIL
-
-  [Property stroke-dashoffset value '10' computes to '10px']
-    expected: FAIL
-
--- a/testing/web-platform/meta/svg/painting/parsing/stroke-width-computed.svg.ini
+++ b/testing/web-platform/meta/svg/painting/parsing/stroke-width-computed.svg.ini
@@ -1,7 +1,7 @@
 [stroke-width-computed.svg]
-  [Property stroke-width value 'calc(50% + 60px)' computes to 'calc(50% + 60px)']
+  [stroke-width computes mm lengths]
     expected: FAIL
-
-  [Property stroke-width value '10' computes to '10px']
+    bug: Precision difference with non-Servo serialization.
+  [stroke-width computes Q lengths]
     expected: FAIL
-
+    bug: Precision difference with non-Servo serialization.
--- a/testing/web-platform/tests/svg/painting/parsing/stroke-dashoffset-valid.svg
+++ b/testing/web-platform/tests/svg/painting/parsing/stroke-dashoffset-valid.svg
@@ -8,17 +8,17 @@
     <h:meta name="assert" content="stroke-dashoffset supports the full grammar 'length-percentage'"/>
   </metadata>
   <g id="target"></g>
   <h:script src="/resources/testharness.js"/>
   <h:script src="/resources/testharnessreport.js"/>
   <h:script src="/css/support/parsing-testcommon.js"/>
   <script><![CDATA[
 
-test_valid_value("stroke-dashoffset", "0");
+test_valid_value("stroke-dashoffset", "0", "0px");
 test_valid_value("stroke-dashoffset", "10px");
 test_valid_value("stroke-dashoffset", "-20%");
-test_valid_value("stroke-dashoffset", "30");
+test_valid_value("stroke-dashoffset", "30", "30px");
 test_valid_value("stroke-dashoffset", "40Q", "40q");
 test_valid_value("stroke-dashoffset", "calc(2em + 3ex)");
 
   ]]></script>
 </svg>
--- a/testing/web-platform/tests/svg/painting/parsing/stroke-width-valid.svg
+++ b/testing/web-platform/tests/svg/painting/parsing/stroke-width-valid.svg
@@ -8,18 +8,18 @@
     <h:meta name="assert" content="stroke-width supports the full grammar '&lt;length-percentage&gt;' and unitless."/>
   </metadata>
   <g id="target"></g>
   <h:script src="/resources/testharness.js"/>
   <h:script src="/resources/testharnessreport.js"/>
   <h:script src="/css/support/parsing-testcommon.js"/>
   <script><![CDATA[
 
-test_valid_value("stroke-width", "0");
-test_valid_value("stroke-width", "10");
+test_valid_value("stroke-width", "0", "0px");
+test_valid_value("stroke-width", "10", "10px");
 test_valid_value("stroke-width", "1px");
 test_valid_value("stroke-width", "calc(2em + 3ex)");
 test_valid_value("stroke-width", "4%");
 test_valid_value("stroke-width", "5vmin");
 test_valid_value("stroke-width", "calc(50% + 60px)");
 
   ]]></script>
 </svg>
--- a/testing/web-platform/tests/web-animations/animation-model/animation-types/property-list.js
+++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/property-list.js
@@ -1213,17 +1213,17 @@ const gCSSProperties = {
     // https://svgwg.org/svg2-draft/painting.html#StrokeProperty
     types: [
     ]
   },
   'stroke-dasharray': {
     // https://svgwg.org/svg2-draft/painting.html#StrokeDasharrayProperty
     types: [
       'dasharray',
-      { type: 'discrete', options: [ [ 'none', '10, 20' ] ] }
+      { type: 'discrete', options: [ [ 'none', '10px, 20px' ] ] }
     ]
   },
   'stroke-dashoffset': {
     // https://svgwg.org/svg2-draft/painting.html#StrokeDashoffsetProperty
     types: [
     ]
   },
   'stroke-linecap': {
--- a/testing/web-platform/tests/web-animations/animation-model/animation-types/property-types.js
+++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/property-types.js
@@ -468,24 +468,24 @@ const lengthPercentageOrCalcType = {
   },
 
   testAccumulation: function(property, setup) {
     this.testAdditionOrAccumulation(property, setup, 'accumulate');
   },
 };
 
 const positiveNumberType = {
-  testInterpolation: (property, setup) => {
+  testInterpolation: (property, setup, expectedUnit='') => {
     test(t => {
       const idlName = propertyToIDL(property);
       const target = createTestElement(t, setup);
       const animation = target.animate({ [idlName]: [1.1, 1.5] },
                                        { duration: 1000, fill: 'both' });
       testAnimationSamples(animation, idlName,
-                           [{ time: 500,  expected: '1.3' }]);
+                           [{ time: 500,  expected: '1.3' + expectedUnit }]);
     }, `${property} supports animating as a positive number`);
   },
 
   testAdditionOrAccumulation: (property, setup, composite) => {
     test(t => {
       const idlName = propertyToIDL(property);
       const target = createTestElement(t, setup);
       target.style[idlName] = 1.1;
@@ -2617,41 +2617,41 @@ const rectType = {
     this.testAdditionOrAccumulation(property, setup, 'accumulate');
   },
 }
 
 // stroke-dasharray: none | [ <length> | <percentage> | <number> ]*
 const dasharrayType = {
   testInterpolation: (property, setup) => {
     percentageType.testInterpolation(property, setup);
-    positiveNumberType.testInterpolation(property, setup);
+    positiveNumberType.testInterpolation(property, setup, 'px');
 
     test(t => {
       const idlName = propertyToIDL(property);
       const target = createTestElement(t, setup);
       const animation = target.animate({ [idlName]:
                                            ['8, 16, 4',
                                             '4, 8, 12, 16'] },
                                        { duration: 1000, fill: 'both' });
       testAnimationSamples(
           animation, idlName,
-          [{ time: 500,  expected: '6, 12, 8, 12, 10, 6, 10, 16, 4, 8, 14, 10' }]);
+          [{ time: 500,  expected: '6px, 12px, 8px, 12px, 10px, 6px, 10px, 16px, 4px, 8px, 14px, 10px' }]);
     }, `${property} supports animating as a dasharray (mismatched length)`);
 
     test(t => {
       const idlName = propertyToIDL(property);
       const target = createTestElement(t, setup);
       const animation = target.animate({ [idlName]:
                                            ['2, 50%, 6, 10',
                                             '6, 30%, 2, 2'] },
                                        { duration: 1000, fill: 'both' });
       testAnimationSamples(
           animation, idlName,
-          [{ time: 500,  expected: '4, 40%, 4, 6' }]);
-    }, `${property} supports animating as a dasharray (mixed number and percentage)`);
+          [{ time: 500,  expected: '4px, 40%, 4px, 6px' }]);
+    }, `${property} supports animating as a dasharray (mixed lengths and percentages)`);
 
   },
 
   // Note that stroke-dasharray is neither additive nor cumulative, so we should
   // write this additive test case that animating value replaces underlying
   // values.
   // See https://www.w3.org/TR/SVG2/painting.html#StrokeDashing.
   testAdditionOrAccumulation: (property, setup, composite) => {
@@ -2660,17 +2660,17 @@ const dasharrayType = {
       const target = createTestElement(t, setup);
       target.style[idlName] = '6, 30%, 2px';
       const animation = target.animate({ [idlName]:
                                            ['1, 2, 3, 4, 5',
                                             '6, 7, 8, 9, 10'] },
                                        { duration: 1000, composite });
       testAnimationSamples(
           animation, idlName,
-          [{ time: 0, expected: '1, 2, 3, 4, 5' }]);
+          [{ time: 0, expected: '1px, 2px, 3px, 4px, 5px' }]);
     }, `${property}: dasharray`);
   },
 
   testAddition: function(property, setup) {
     this.testAdditionOrAccumulation(property, setup, 'add');
   },
 
   testAccumulation: function(property, setup) {