Bug 1562086 - Add percentage for opacity (i.e. <alpha-value>). r=emilio
authorBoris Chiou <boris.chiou@gmail.com>
Fri, 12 Jul 2019 19:49:32 +0000
changeset 543190 abe58536c427f4a810df1274a6069b8a4832bcf2
parent 543189 6a357badc1904d9ead985087a791c1bb2d47f4e1
child 543191 bdbad45678f3de463e12cc9ab60fdec749abb7b5
push id11848
push userffxbld-merge
push dateMon, 26 Aug 2019 19:26:25 +0000
treeherdermozilla-beta@9b31bfdfac10 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
bugs1562086
milestone70.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 1562086 - Add percentage for opacity (i.e. <alpha-value>). r=emilio The following properties accept <alpha-value> [1], which is "<number> | <percentage>", so we update the parser, spec links, and their web-platform-tests. 1. opacity 2. flood-opacity 3. fill-opacity 4. stroke-opacity 5. stop-opacity 6. -moz-window-opacity Besides, shape-image-threshold [2] still only accepts <number>, so we need to support a different version of `Opacity::parse()`. [1] https://drafts.csswg.org/css-color/#typedef-alpha-value [2] https://drafts.csswg.org/css-shapes/#shape-image-threshold-property Differential Revision: https://phabricator.services.mozilla.com/D37493
layout/style/test/property_database.js
servo/components/style/properties/longhands/box.mako.rs
servo/components/style/properties/longhands/effects.mako.rs
servo/components/style/properties/longhands/inherited_svg.mako.rs
servo/components/style/properties/longhands/svg.mako.rs
servo/components/style/values/specified/mod.rs
testing/web-platform/meta/css/filter-effects/parsing/flood-opacity-valid.svg.ini
testing/web-platform/meta/svg/painting/parsing/fill-opacity-valid.svg.ini
testing/web-platform/meta/svg/painting/parsing/stroke-opacity-valid.svg.ini
testing/web-platform/meta/svg/pservers/parsing/stop-opacity-valid.svg.ini
testing/web-platform/tests/css/css-color/parsing/color-computed.html
testing/web-platform/tests/css/css-color/parsing/color-valid.html
testing/web-platform/tests/css/css-color/parsing/opacity-computed.html
testing/web-platform/tests/css/css-color/parsing/opacity-valid.html
testing/web-platform/tests/css/filter-effects/parsing/flood-opacity-computed.svg
testing/web-platform/tests/css/filter-effects/parsing/flood-opacity-valid.svg
testing/web-platform/tests/svg/painting/parsing/fill-opacity-computed.svg
testing/web-platform/tests/svg/painting/parsing/fill-opacity-valid.svg
testing/web-platform/tests/svg/painting/parsing/stroke-opacity-computed.svg
testing/web-platform/tests/svg/painting/parsing/stroke-opacity-valid.svg
testing/web-platform/tests/svg/pservers/parsing/stop-opacity-computed.svg
testing/web-platform/tests/svg/pservers/parsing/stop-opacity-valid.svg
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -6654,18 +6654,19 @@ var gCSSProperties = {
       "1",
       "17",
       "397.376",
       "3e1",
       "3e+1",
       "3e0",
       "3e+0",
       "3e-0",
-    ],
-    other_values: ["0", "0.4", "0.0000", "-3", "3e-1"],
+      "300%",
+    ],
+    other_values: ["0", "0.4", "0.0000", "-3", "3e-1", "-100%", "50%"],
     invalid_values: ["0px", "1px"],
   },
   "-moz-orient": {
     domProp: "MozOrient",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: ["inline"],
     other_values: ["horizontal", "vertical", "block"],
@@ -8187,21 +8188,23 @@ var gCSSProperties = {
       "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"],
+    initial_values: ["1", "2.8", "1.000", "300%"],
     other_values: [
       "0",
       "0.3",
       "-7.3",
+      "-100%",
+      "50%",
       "context-fill-opacity",
       "context-stroke-opacity",
     ],
     invalid_values: [],
   },
   "fill-rule": {
     domProp: "fillRule",
     inherited: true,
@@ -8458,18 +8461,18 @@ var gCSSProperties = {
       "000000",
       "ff00ff",
     ],
   },
   "flood-opacity": {
     domProp: "floodOpacity",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
-    initial_values: ["1", "2.8", "1.000"],
-    other_values: ["0", "0.3", "-7.3"],
+    initial_values: ["1", "2.8", "1.000", "300%"],
+    other_values: ["0", "0.3", "-7.3", "-100%", "50%"],
     invalid_values: [],
   },
   "image-orientation": {
     domProp: "imageOrientation",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
     initial_values: ["none"],
     other_values: ["from-image"],
@@ -8646,18 +8649,18 @@ var gCSSProperties = {
       "000000",
       "ff00ff",
     ],
   },
   "stop-opacity": {
     domProp: "stopOpacity",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
-    initial_values: ["1", "2.8", "1.000"],
-    other_values: ["0", "0.3", "-7.3"],
+    initial_values: ["1", "2.8", "1.000", "300%"],
+    other_values: ["0", "0.3", "-7.3", "-100%", "50%"],
     invalid_values: [],
   },
   stroke: {
     domProp: "stroke",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
     initial_values: ["none"],
     other_values: [
@@ -8725,21 +8728,23 @@ var gCSSProperties = {
     initial_values: ["4"],
     other_values: ["0", "0.9", "1", "7", "5000", "1.1"],
     invalid_values: ["-1", "3px", "-0.3"],
   },
   "stroke-opacity": {
     domProp: "strokeOpacity",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
-    initial_values: ["1", "2.8", "1.000"],
+    initial_values: ["1", "2.8", "1.000", "300%"],
     other_values: [
       "0",
       "0.3",
       "-7.3",
+      "-100%",
+      "50%",
       "context-fill-opacity",
       "context-stroke-opacity",
     ],
     invalid_values: [],
   },
   "stroke-width": {
     domProp: "strokeWidth",
     inherited: true,
@@ -12502,18 +12507,19 @@ if (false) {
       "1",
       "17",
       "397.376",
       "3e1",
       "3e+1",
       "3e0",
       "3e+0",
       "3e-0",
-    ],
-    other_values: ["0", "0.4", "0.0000", "-3", "3e-1"],
+      "300%",
+    ],
+    other_values: ["0", "0.4", "0.0000", "-3", "3e-1", "-100%", "50%"],
     invalid_values: ["0px", "1px", "20%", "default", "auto"],
   };
 
   gCSSProperties["-moz-window-transform"] = {
     // domProp: "MozWindowTransform",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     prerequisites: { width: "300px", height: "50px" },
--- a/servo/components/style/properties/longhands/box.mako.rs
+++ b/servo/components/style/properties/longhands/box.mako.rs
@@ -596,18 +596,20 @@
     "will-change",
     "WillChange",
     "computed::WillChange::auto()",
     products="gecko",
     animation_value_type="none",
     spec="https://drafts.csswg.org/css-will-change/#will-change",
 )}
 
+// The spec issue for the parse_method: https://github.com/w3c/csswg-drafts/issues/4102.
 ${helpers.predefined_type(
     "shape-image-threshold", "Opacity", "0.0",
+    parse_method="parse_number",
     products="gecko",
     animation_value_type="ComputedValue",
     spec="https://drafts.csswg.org/css-shapes/#shape-image-threshold-property",
 )}
 
 ${helpers.predefined_type(
     "shape-margin",
     "NonNegativeLengthPercentage",
--- a/servo/components/style/properties/longhands/effects.mako.rs
+++ b/servo/components/style/properties/longhands/effects.mako.rs
@@ -8,17 +8,17 @@
 <% data.new_style_struct("Effects", inherited=False) %>
 
 ${helpers.predefined_type(
     "opacity",
     "Opacity",
     "1.0",
     animation_value_type="ComputedValue",
     flags="CREATES_STACKING_CONTEXT CAN_ANIMATE_ON_COMPOSITOR",
-    spec="https://drafts.csswg.org/css-color/#opacity",
+    spec="https://drafts.csswg.org/css-color/#transparency",
     servo_restyle_damage = "reflow_out_of_flow",
 )}
 
 ${helpers.predefined_type(
     "box-shadow",
     "BoxShadow",
     None,
     vector=True,
--- a/servo/components/style/properties/longhands/inherited_svg.mako.rs
+++ b/servo/components/style/properties/longhands/inherited_svg.mako.rs
@@ -47,17 +47,17 @@
 )}
 
 ${helpers.predefined_type(
     "fill-opacity",
     "SVGOpacity",
     "Default::default()",
     products="gecko",
     animation_value_type="ComputedValue",
-    spec="https://www.w3.org/TR/SVG11/painting.html#FillOpacityProperty",
+    spec="https://svgwg.org/svg2-draft/painting.html#FillOpacity",
 )}
 
 ${helpers.predefined_type(
     "fill-rule",
     "FillRule",
     "Default::default()",
     needs_context=False,
     products="gecko",
@@ -118,17 +118,17 @@
 )}
 
 ${helpers.predefined_type(
     "stroke-opacity",
     "SVGOpacity",
     "Default::default()",
     products="gecko",
     animation_value_type="ComputedValue",
-    spec="https://www.w3.org/TR/SVG11/painting.html#StrokeOpacityProperty",
+    spec="https://svgwg.org/svg2-draft/painting.html#StrokeOpacity",
 )}
 
 ${helpers.predefined_type(
     "stroke-dasharray",
     "SVGStrokeDashArray",
     "Default::default()",
     products="gecko",
     animation_value_type="crate::values::computed::SVGStrokeDashArray",
--- a/servo/components/style/properties/longhands/svg.mako.rs
+++ b/servo/components/style/properties/longhands/svg.mako.rs
@@ -35,17 +35,17 @@
 )}
 
 ${helpers.predefined_type(
     "stop-opacity",
     "Opacity",
     "1.0",
     products="gecko",
     animation_value_type="ComputedValue",
-    spec="https://www.w3.org/TR/SVGTiny12/painting.html#propdef-stop-opacity",
+    spec="https://svgwg.org/svg2-draft/pservers.html#StopOpacityProperty",
 )}
 
 // Section 15 - Filter Effects
 
 ${helpers.predefined_type(
     "flood-color",
     "Color",
     "RGBA::new(0, 0, 0, 255).into()",
@@ -55,17 +55,17 @@
 )}
 
 ${helpers.predefined_type(
     "flood-opacity",
     "Opacity",
     "1.0",
     products="gecko",
     animation_value_type="ComputedValue",
-    spec="https://www.w3.org/TR/SVG/filters.html#FloodOpacityProperty",
+    spec="https://drafts.fxtf.org/filter-effects/#FloodOpacityProperty",
 )}
 
 ${helpers.predefined_type(
     "lighting-color",
     "Color",
     "RGBA::new(255, 255, 255, 255).into()",
     products="gecko",
     animation_value_type="AnimatedColor",
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -137,18 +137,18 @@ fn parse_number_with_clamping_mode<'i, '
     let location = input.current_source_location();
     // FIXME: remove early returns when lifetimes are non-lexical
     match *input.next()? {
         Token::Number { value, .. } if clamping_mode.is_ok(context.parsing_mode, value) => {
             return Ok(Number {
                 value: value.min(f32::MAX).max(f32::MIN),
                 calc_clamping_mode: None,
             });
-        },
-        Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {},
+        }
+        Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {}
         ref t => return Err(location.new_unexpected_token_error(t.clone())),
     }
 
     let result = input.parse_nested_block(|i| CalcNode::parse_number(context, i))?;
 
     Ok(Number {
         value: result.min(f32::MAX).max(f32::MIN),
         calc_clamping_mode: Some(clamping_mode),
@@ -397,28 +397,55 @@ impl Parse for NonNegativeNumberOrPercen
         input: &mut Parser<'i, 't>,
     ) -> Result<Self, ParseError<'i>> {
         Ok(NonNegative(NumberOrPercentage::parse_non_negative(
             context, input,
         )?))
     }
 }
 
-#[allow(missing_docs)]
+/// The value of Opacity is <alpha-value>, which is "<number> | <percentage>".
+/// However, we serialize the specified value as number, so it's ok to store
+/// the Opacity as Number.
 #[derive(
     Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, SpecifiedValueInfo, ToCss, ToShmem,
 )]
 pub struct Opacity(Number);
 
+impl Opacity {
+    /// Parse number value only.
+    #[inline]
+    pub fn parse_number<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Self, ParseError<'i>> {
+        Number::parse(context, input).map(Opacity)
+    }
+}
+
 impl Parse for Opacity {
+    /// Opacity accepts <number> | <percentage>, so we parse it as NumberOrPercentage,
+    /// and then convert into an Number if it's a Percentage.
+    /// https://drafts.csswg.org/cssom/#serializing-css-values
     fn parse<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
     ) -> Result<Self, ParseError<'i>> {
-        Number::parse(context, input).map(Opacity)
+        let number = match NumberOrPercentage::parse(context, input)? {
+            NumberOrPercentage::Percentage(p) => Number {
+                value: p.get(),
+                calc_clamping_mode: if p.is_calc() {
+                    Some(AllowedNumericType::All)
+                } else {
+                    None
+                },
+            },
+            NumberOrPercentage::Number(n) => n,
+        };
+        Ok(Opacity(number))
     }
 }
 
 impl ToComputedValue for Opacity {
     type ComputedValue = CSSFloat;
 
     #[inline]
     fn to_computed_value(&self, context: &Context) -> CSSFloat {
@@ -505,17 +532,17 @@ impl Parse for Integer {
     ) -> Result<Self, ParseError<'i>> {
         let location = input.current_source_location();
 
         // FIXME: remove early returns when lifetimes are non-lexical
         match *input.next()? {
             Token::Number {
                 int_value: Some(v), ..
             } => return Ok(Integer::new(v)),
-            Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {},
+            Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {}
             ref t => return Err(location.new_unexpected_token_error(t.clone())),
         }
 
         let result = input.parse_nested_block(|i| CalcNode::parse_integer(context, i))?;
 
         Ok(Integer::from_calc(result))
     }
 }
@@ -781,30 +808,30 @@ impl Attr {
 
                     let prefix_and_ns = if let Some(ns) = first {
                         let prefix = Prefix::from(ns.as_ref());
                         let ns = match get_namespace_for_prefix(&prefix, context) {
                             Some(ns) => ns,
                             None => {
                                 return Err(location
                                     .new_custom_error(StyleParseErrorKind::UnspecifiedError));
-                            },
+                            }
                         };
                         Some((prefix, ns))
                     } else {
                         None
                     };
                     return Ok(Attr {
                         namespace: prefix_and_ns,
                         attribute: Atom::from(second_token.as_ref()),
                     });
-                },
+                }
                 // In the case of attr(foobar    ) we don't want to error out
                 // because of the trailing whitespace
-                Token::WhiteSpace(..) => {},
+                Token::WhiteSpace(..) => {}
                 ref t => return Err(input.new_unexpected_token_error(t.clone())),
             }
         }
 
         if let Some(first) = first {
             Ok(Attr {
                 namespace: None,
                 attribute: Atom::from(first.as_ref()),
deleted file mode 100644
--- a/testing/web-platform/meta/css/filter-effects/parsing/flood-opacity-valid.svg.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[flood-opacity-valid.svg]
-  [e.style['flood-opacity'\] = "-100%" should set the property value]
-    expected: FAIL
-
-  [e.style['flood-opacity'\] = "50%" should set the property value]
-    expected: FAIL
-
-  [e.style['flood-opacity'\] = "300%" should set the property value]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/svg/painting/parsing/fill-opacity-valid.svg.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[fill-opacity-valid.svg]
-  [e.style['fill-opacity'\] = "300%" should set the property value]
-    expected: FAIL
-
-  [e.style['fill-opacity'\] = "50%" should set the property value]
-    expected: FAIL
-
-  [e.style['fill-opacity'\] = "-100%" should set the property value]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/svg/painting/parsing/stroke-opacity-valid.svg.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[stroke-opacity-valid.svg]
-  [e.style['stroke-opacity'\] = "50%" should set the property value]
-    expected: FAIL
-
-  [e.style['stroke-opacity'\] = "-100%" should set the property value]
-    expected: FAIL
-
-  [e.style['stroke-opacity'\] = "300%" should set the property value]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/svg/pservers/parsing/stop-opacity-valid.svg.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[stop-opacity-valid.svg]
-  [e.style['stop-opacity'\] = "300%" should set the property value]
-    expected: FAIL
-
-  [e.style['stop-opacity'\] = "50%" should set the property value]
-    expected: FAIL
-
-  [e.style['stop-opacity'\] = "-100%" should set the property value]
-    expected: FAIL
-
--- a/testing/web-platform/tests/css/css-color/parsing/color-computed.html
+++ b/testing/web-platform/tests/css/css-color/parsing/color-computed.html
@@ -22,16 +22,17 @@ test_computed_value("color", "currentcol
 test_computed_value("color", "transparent", "rgba(0, 0, 0, 0)");
 test_computed_value("color", "red", "rgb(255, 0, 0)");
 test_computed_value("color", "magenta", "rgb(255, 0, 255)");
 test_computed_value("color", "#234", "rgb(34, 51, 68)");
 test_computed_value("color", "#FEDCBA", "rgb(254, 220, 186)");
 test_computed_value("color", "rgb(2, 3, 4)");
 test_computed_value("color", "rgb(100%, 0%, 0%)", "rgb(255, 0, 0)");
 test_computed_value("color", "rgba(2, 3, 4, 0.5)");
+test_computed_value("color", "rgba(2, 3, 4, 50%)", "rgba(2, 3, 4, 0.5)");
 test_computed_value("color", "hsl(120, 100%, 50%)", "rgb(0, 255, 0)");
 test_computed_value("color", "hsla(120, 100%, 50%, 0.25)", "rgba(0, 255, 0, 0.25)");
 test_computed_value("color", "rgb(-2, 3, 4)", "rgb(0, 3, 4)");
 test_computed_value("color", "rgb(100, 200, 300)", "rgb(100, 200, 255)");
 test_computed_value("color", "rgb(20, 10, 0, -10)", "rgba(20, 10, 0, 0)");
 test_computed_value("color", "rgb(100%, 200%, 300%)", "rgb(255, 255, 255)");
 </script>
 </body>
--- a/testing/web-platform/tests/css/css-color/parsing/color-valid.html
+++ b/testing/web-platform/tests/css/css-color/parsing/color-valid.html
@@ -16,16 +16,17 @@ test_valid_value("color", "currentcolor"
 test_valid_value("color", "transparent");
 test_valid_value("color", "red");
 test_valid_value("color", "magenta");
 test_valid_value("color", "#234", "rgb(34, 51, 68)");
 test_valid_value("color", "#FEDCBA", "rgb(254, 220, 186)");
 test_valid_value("color", "rgb(2, 3, 4)");
 test_valid_value("color", "rgb(100%, 0%, 0%)", "rgb(255, 0, 0)");
 test_valid_value("color", "rgba(2, 3, 4, 0.5)"); // Safari serializes alpha-value 0.498039
+test_valid_value("color", "rgba(2, 3, 4, 50%)", "rgba(2, 3, 4, 0.5)"); // Safari serializes alpha-value 0.498039
 test_valid_value("color", "hsl(120, 100%, 50%)", ["rgb(0, 255, 0)", "hsl(120, 100%, 50%)"]);
 test_valid_value("color", "hsla(120, 100%, 50%, 0.25)", ["rgba(0, 255, 0, 0.25)", "hsla(120, 100%, 50%, 0.25)"]); // Safari serializes alpha-value 0.247059
 test_valid_value("color", "rgb(-2, 3, 4)", "rgb(0, 3, 4)");
 test_valid_value("color", "rgb(100, 200, 300)", "rgb(100, 200, 255)");
 test_valid_value("color", "rgb(20, 10, 0, -10)", "rgba(20, 10, 0, 0)"); // Not supported by Edge/Safari.
 test_valid_value("color", "rgb(100%, 200%, 300%)", "rgb(255, 255, 255)");
 </script>
 </body>
--- a/testing/web-platform/tests/css/css-color/parsing/opacity-computed.html
+++ b/testing/web-platform/tests/css/css-color/parsing/opacity-computed.html
@@ -11,11 +11,15 @@
 <body>
 <div id="target"></div>
 <script>
 test_computed_value("opacity", "1");
 test_computed_value("opacity", "0.5");
 test_computed_value("opacity", "0");
 test_computed_value("opacity", "-2", "0");
 test_computed_value("opacity", "3", "1");
+test_computed_value("opacity", "-100%", "0");
+test_computed_value("opacity", "50%", "0.5");
+test_computed_value("opacity", "300%", "1");
+
 </script>
 </body>
 </html>
--- a/testing/web-platform/tests/css/css-color/parsing/opacity-valid.html
+++ b/testing/web-platform/tests/css/css-color/parsing/opacity-valid.html
@@ -12,11 +12,15 @@
 </head>
 <body>
 <script>
 test_valid_value("opacity", "1");
 test_valid_value("opacity", "0.5");
 test_valid_value("opacity", "0");
 test_valid_value("opacity", "-2");
 test_valid_value("opacity", "3");
+test_valid_value("opacity", "-100%", "-1");
+test_valid_value("opacity", "50%", "0.5");
+test_valid_value("opacity", "300%", "3");
+
 </script>
 </body>
 </html>
--- a/testing/web-platform/tests/css/filter-effects/parsing/flood-opacity-computed.svg
+++ b/testing/web-platform/tests/css/filter-effects/parsing/flood-opacity-computed.svg
@@ -11,11 +11,14 @@
   <h:script src="/resources/testharness.js"/>
   <h:script src="/resources/testharnessreport.js"/>
   <h:script src="/css/support/computed-testcommon.js"/>
   <script><![CDATA[
 
 test_computed_value("flood-opacity", "-1", "0");
 test_computed_value("flood-opacity", "0.5");
 test_computed_value("flood-opacity", "3", "1");
+test_computed_value("flood-opacity", "-100%", "0");
+test_computed_value("flood-opacity", "50%", "0.5");
+test_computed_value("flood-opacity", "300%", "1");
 
   ]]></script>
 </svg>
--- a/testing/web-platform/tests/css/filter-effects/parsing/flood-opacity-valid.svg
+++ b/testing/web-platform/tests/css/filter-effects/parsing/flood-opacity-valid.svg
@@ -11,14 +11,14 @@
   <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("flood-opacity", "-1");
 test_valid_value("flood-opacity", "0.5");
 test_valid_value("flood-opacity", "3");
-test_valid_value("flood-opacity", "-100%");
-test_valid_value("flood-opacity", "50%");
-test_valid_value("flood-opacity", "300%");
+test_valid_value("flood-opacity", "-100%", "-1");
+test_valid_value("flood-opacity", "50%", "0.5");
+test_valid_value("flood-opacity", "300%", "3");
 
   ]]></script>
 </svg>
--- a/testing/web-platform/tests/svg/painting/parsing/fill-opacity-computed.svg
+++ b/testing/web-platform/tests/svg/painting/parsing/fill-opacity-computed.svg
@@ -11,11 +11,14 @@
   <h:script src="/resources/testharness.js"/>
   <h:script src="/resources/testharnessreport.js"/>
   <h:script src="/css/support/computed-testcommon.js"/>
   <script><![CDATA[
 
 test_computed_value("fill-opacity", "-1", "0");
 test_computed_value("fill-opacity", "0.5");
 test_computed_value("fill-opacity", "3", "1");
+test_computed_value("fill-opacity", "-100%", "0");
+test_computed_value("fill-opacity", "50%", "0.5");
+test_computed_value("fill-opacity", "300%", "1");
 
   ]]></script>
 </svg>
--- a/testing/web-platform/tests/svg/painting/parsing/fill-opacity-valid.svg
+++ b/testing/web-platform/tests/svg/painting/parsing/fill-opacity-valid.svg
@@ -11,14 +11,14 @@
   <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("fill-opacity", "-1");
 test_valid_value("fill-opacity", "0.5");
 test_valid_value("fill-opacity", "3");
-test_valid_value("fill-opacity", "-100%");
-test_valid_value("fill-opacity", "50%");
-test_valid_value("fill-opacity", "300%");
+test_valid_value("fill-opacity", "-100%", "-1");
+test_valid_value("fill-opacity", "50%", "0.5");
+test_valid_value("fill-opacity", "300%", "3");
 
   ]]></script>
 </svg>
--- a/testing/web-platform/tests/svg/painting/parsing/stroke-opacity-computed.svg
+++ b/testing/web-platform/tests/svg/painting/parsing/stroke-opacity-computed.svg
@@ -11,11 +11,14 @@
   <h:script src="/resources/testharness.js"/>
   <h:script src="/resources/testharnessreport.js"/>
   <h:script src="/css/support/computed-testcommon.js"/>
   <script><![CDATA[
 
 test_computed_value("stroke-opacity", "-1", "0");
 test_computed_value("stroke-opacity", "0.5");
 test_computed_value("stroke-opacity", "3", "1");
+test_computed_value("stroke-opacity", "-100%", "0");
+test_computed_value("stroke-opacity", "50%", "0.5");
+test_computed_value("stroke-opacity", "300%", "1");
 
   ]]></script>
 </svg>
--- a/testing/web-platform/tests/svg/painting/parsing/stroke-opacity-valid.svg
+++ b/testing/web-platform/tests/svg/painting/parsing/stroke-opacity-valid.svg
@@ -11,14 +11,14 @@
   <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-opacity", "-1");
 test_valid_value("stroke-opacity", "0.5");
 test_valid_value("stroke-opacity", "3");
-test_valid_value("stroke-opacity", "-100%");
-test_valid_value("stroke-opacity", "50%");
-test_valid_value("stroke-opacity", "300%");
+test_valid_value("stroke-opacity", "-100%", "-1");
+test_valid_value("stroke-opacity", "50%", "0.5");
+test_valid_value("stroke-opacity", "300%", "3");
 
   ]]></script>
 </svg>
--- a/testing/web-platform/tests/svg/pservers/parsing/stop-opacity-computed.svg
+++ b/testing/web-platform/tests/svg/pservers/parsing/stop-opacity-computed.svg
@@ -11,11 +11,14 @@
   <h:script src="/resources/testharness.js"/>
   <h:script src="/resources/testharnessreport.js"/>
   <h:script src="/css/support/computed-testcommon.js"/>
   <script><![CDATA[
 
 test_computed_value("stop-opacity", "-1", "0");
 test_computed_value("stop-opacity", "0.5");
 test_computed_value("stop-opacity", "3", "1");
+test_computed_value("stop-opacity", "-100%", "0");
+test_computed_value("stop-opacity", "50%", "0.5");
+test_computed_value("stop-opacity", "300%", "1");
 
   ]]></script>
 </svg>
--- a/testing/web-platform/tests/svg/pservers/parsing/stop-opacity-valid.svg
+++ b/testing/web-platform/tests/svg/pservers/parsing/stop-opacity-valid.svg
@@ -11,14 +11,14 @@
   <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("stop-opacity", "-1");
 test_valid_value("stop-opacity", "0.5");
 test_valid_value("stop-opacity", "3");
-test_valid_value("stop-opacity", "-100%");
-test_valid_value("stop-opacity", "50%");
-test_valid_value("stop-opacity", "300%");
+test_valid_value("stop-opacity", "-100%", "-1");
+test_valid_value("stop-opacity", "50%", "0.5");
+test_valid_value("stop-opacity", "300%", "3");
 
   ]]></script>
 </svg>