Merge mozilla-central to autoland. a=merge CLOSED TREE
authorNoemi Erli <nerli@mozilla.com>
Wed, 22 Aug 2018 12:53:37 +0300
changeset 432780 2ee4398514ac09b08a48a737d7e22cd63184901d
parent 432779 db66903f54cc0b27224255efab79cef50ca16540 (current diff)
parent 432775 3b5452b3778153f6f223bb2177235835b2314eb6 (diff)
child 432781 325f0f4b0f11616b8b7268521a2b6e58715684fd
push id34487
push usernerli@mozilla.com
push dateWed, 22 Aug 2018 16:28:03 +0000
treeherdermozilla-central@61396b1eaa14 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone63.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
Merge mozilla-central to autoland. a=merge CLOSED TREE
testing/web-platform/meta/css/motion/offset-path-string.html.ini
testing/web-platform/tests/css/motion/offset-path-string.html
--- a/browser/installer/Makefile.in
+++ b/browser/installer/Makefile.in
@@ -136,19 +136,16 @@ DEFINES += -DRESPATH='$(RESPATH)'
 LPROJ_ROOT = $(firstword $(subst -, ,$(AB_CD)))
 ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 ifeq (zh-TW,$(AB_CD))
 LPROJ_ROOT := $(subst -,_,$(AB_CD))
 endif
 endif
 DEFINES += -DLPROJ_ROOT=$(LPROJ_ROOT)
 
-ifdef MOZ_SYSTEM_ICU
-DEFINES += -DMOZ_SYSTEM_ICU
-endif
 ifdef CLANG_CXX
 DEFINES += -DCLANG_CXX
 endif
 ifdef CLANG_CL
 DEFINES += -DCLANG_CL
 endif
 
 ifdef LLVM_SYMBOLIZER
--- a/build/autoconf/icu.m4
+++ b/build/autoconf/icu.m4
@@ -12,16 +12,17 @@ MOZ_ARG_WITH_BOOL(system-icu,
 [  --with-system-icu
                           Use system ICU (located with pkgconfig)],
     MOZ_SYSTEM_ICU=1)
 
 if test -n "$MOZ_SYSTEM_ICU"; then
     PKG_CHECK_MODULES(MOZ_ICU, icu-i18n >= 59.1)
     CFLAGS="$CFLAGS $MOZ_ICU_CFLAGS"
     CXXFLAGS="$CXXFLAGS $MOZ_ICU_CFLAGS"
+    AC_DEFINE(MOZ_SYSTEM_ICU)
 fi
 
 AC_SUBST(MOZ_SYSTEM_ICU)
 
 MOZ_ARG_WITH_STRING(intl-api,
 [  --with-intl-api, --with-intl-api=build, --without-intl-api
     Determine the status of the ECMAScript Internationalization API.  The first
     (or lack of any of these) builds and exposes the API.  The second builds it
--- a/build/valgrind/mach_commands.py
+++ b/build/valgrind/mach_commands.py
@@ -109,16 +109,17 @@ class MachCommands(MachCommandBase):
             kp_kwargs = {'processOutputLine': [outputHandler]}
 
             valgrind = 'valgrind'
             if not os.path.exists(valgrind):
                 valgrind = findInPath(valgrind)
 
             valgrind_args = [
                 valgrind,
+                '--sym-offsets=yes',
                 '--smc-check=all-non-file',
                 '--vex-iropt-register-updates=allregs-at-mem-access',
                 '--gen-suppressions=all',
                 '--num-callers=36',
                 '--leak-check=full',
                 '--show-possibly-lost=no',
                 '--track-origins=yes',
                 '--trace-children=yes',
--- a/config/moz.build
+++ b/config/moz.build
@@ -21,19 +21,16 @@ CONFIGURE_SUBST_FILES += [
 
 if CONFIG['HOST_OS_ARCH'] != 'WINNT':
     HOST_SOURCES += [
         'nsinstall.c',
         'pathsub.c',
     ]
     HostProgram('nsinstall_real')
 
-if CONFIG['MOZ_SYSTEM_ICU']:
-    DEFINES['MOZ_SYSTEM_ICU'] = True
-
 PYTHON_UNITTEST_MANIFESTS += [
     'tests/python.ini',
 ]
 
 if CONFIG['CC_TYPE'] in ('clang', 'gcc') and CONFIG['MOZ_OPTIMIZE']:
     CFLAGS += ['-O3']
 
 HOST_DEFINES = {
--- a/devtools/client/inspector/animation/test/browser.ini
+++ b/devtools/client/inspector/animation/test/browser.ini
@@ -48,16 +48,17 @@ support-files =
 [browser_animation_empty_on_invalid_nodes.js]
 [browser_animation_indication-bar.js]
 [browser_animation_infinity-duration_current-time-scrubber.js]
 [browser_animation_infinity-duration_summary-graph.js]
 [browser_animation_infinity-duration_tick-label.js]
 [browser_animation_inspector_exists.js]
 [browser_animation_keyframes-graph_computed-value-path-01.js]
 [browser_animation_keyframes-graph_computed-value-path-02.js]
+[browser_animation_keyframes-graph_computed-value-path-03.js]
 [browser_animation_keyframes-graph_computed-value-path_easing-hint.js]
 skip-if = (verify && !debug)
 [browser_animation_keyframes-graph_keyframe-marker.js]
 [browser_animation_keyframes-graph_keyframe-marker-rtl.js]
 [browser_animation_keyframes-progress-bar.js]
 [browser_animation_keyframes-progress-bar_after-resuming.js]
 [browser_animation_logic_adjust-time.js]
 [browser_animation_logic_adjust-time-with-playback-rate.js]
--- a/devtools/client/inspector/animation/test/browser_animation_keyframes-graph_computed-value-path-01.js
+++ b/devtools/client/inspector/animation/test/browser_animation_keyframes-graph_computed-value-path-01.js
@@ -1,18 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Test for following ComputedValuePath component:
-// * element existence
-// * path segments
-// * fill color by animation type
-// * stop color if the animation type is color
+// Test for ComputedValuePath of animations that consist by multi types of animated
+// properties.
 
 requestLongerTimeout(2);
 
 const TEST_DATA = [
   {
     targetClass: "multi-types",
     properties: [
       {
@@ -155,187 +152,13 @@ const TEST_DATA = [
         expectedPathSegments: [
           { x: 0, y: 100 },
           { x: 500, y: 50 },
           { x: 1000, y: 0 },
         ],
       },
     ],
   },
-  {
-    targetClass: "middle-keyframe",
-    properties: [
-      {
-        name: "background-color",
-        computedValuePathClass: "color-path",
-        expectedPathSegments: [
-          { x: 0, y: 0 },
-          { x: 0, y: 100 },
-          { x: 500, y: 100 },
-          { x: 1000, y: 100 },
-        ],
-        expectedStopColors: [
-          { offset: 0, color: "rgb(255, 0, 0)" },
-          { offset: 0.5, color: "rgb(0, 0, 255)" },
-          { offset: 1, color: "rgb(0, 255, 0)" },
-        ]
-      },
-      {
-        name: "background-repeat",
-        computedValuePathClass: "discrete-path",
-        expectedPathSegments: [
-          { x: 0, y: 0 },
-          { x: 249.999, y: 0 },
-          { x: 250, y: 100 },
-          { x: 749.999, y: 100 },
-          { x: 750, y: 0 },
-          { x: 1000, y: 0 },
-        ],
-      },
-      {
-        name: "font-size",
-        computedValuePathClass: "distance-path",
-        expectedPathSegments: [
-          { x: 0, y: 0 },
-          { x: 250, y: 50 },
-          { x: 500, y: 100 },
-          { x: 750, y: 50 },
-          { x: 1000, y: 0 },
-        ],
-      },
-      {
-        name: "margin-left",
-        computedValuePathClass: "distance-path",
-        expectedPathSegments: [
-          { x: 0, y: 0 },
-          { x: 250, y: 50 },
-          { x: 500, y: 100 },
-          { x: 750, y: 50 },
-          { x: 1000, y: 0 },
-        ],
-      },
-      {
-        name: "opacity",
-        computedValuePathClass: "distance-path",
-        expectedPathSegments: [
-          { x: 0, y: 0 },
-          { x: 250, y: 50 },
-          { x: 500, y: 100 },
-          { x: 750, y: 50 },
-          { x: 1000, y: 0 },
-        ],
-      },
-      {
-        name: "text-align",
-        computedValuePathClass: "discrete-path",
-        expectedPathSegments: [
-          { x: 0, y: 0 },
-          { x: 249.999, y: 0 },
-          { x: 250, y: 100 },
-          { x: 749.999, y: 100 },
-          { x: 750, y: 0 },
-          { x: 1000, y: 0 },
-        ],
-      },
-      {
-        name: "transform",
-        computedValuePathClass: "distance-path",
-        expectedPathSegments: [
-          { x: 0, y: 0 },
-          { x: 250, y: 50 },
-          { x: 500, y: 100 },
-          { x: 750, y: 50 },
-          { x: 1000, y: 0 },
-        ],
-      },
-    ],
-  },
-  {
-    targetClass: "steps-keyframe",
-    properties: [
-      {
-        name: "background-color",
-        computedValuePathClass: "color-path",
-        expectedPathSegments: [
-          { x: 0, y: 0 },
-          { x: 0, y: 100 },
-          { x: 500, y: 100 },
-          { x: 1000, y: 100 },
-        ],
-        expectedStopColors: [
-          { offset: 0, color: "rgb(255, 0, 0)" },
-          { offset: 0.499, color: "rgb(255, 0, 0)" },
-          { offset: 0.5, color: "rgb(128, 128, 0)" },
-          { offset: 0.999, color: "rgb(128, 128, 0)" },
-          { offset: 1, color: "rgb(0, 255, 0)" },
-        ]
-      },
-      {
-        name: "background-repeat",
-        computedValuePathClass: "discrete-path",
-        expectedPathSegments: [
-          { x: 0, y: 0 },
-          { x: 499.999, y: 0 },
-          { x: 500, y: 100 },
-          { x: 1000, y: 100 },
-        ],
-      },
-      {
-        name: "font-size",
-        computedValuePathClass: "distance-path",
-        expectedPathSegments: [
-          { x: 0, y: 0 },
-          { x: 500, y: 0 },
-          { x: 500, y: 50 },
-          { x: 1000, y: 50 },
-          { x: 1000, y: 100 },
-        ],
-      },
-      {
-        name: "margin-left",
-        computedValuePathClass: "distance-path",
-        expectedPathSegments: [
-          { x: 0, y: 0 },
-          { x: 499.999, y: 0 },
-          { x: 500, y: 50 },
-          { x: 999.999, y: 50 },
-          { x: 1000, y: 100 },
-        ],
-      },
-      {
-        name: "opacity",
-        computedValuePathClass: "distance-path",
-        expectedPathSegments: [
-          { x: 0, y: 0 },
-          { x: 499.999, y: 0 },
-          { x: 500, y: 50 },
-          { x: 999.999, y: 50 },
-          { x: 1000, y: 100 },
-        ],
-      },
-      {
-        name: "text-align",
-        computedValuePathClass: "discrete-path",
-        expectedPathSegments: [
-          { x: 0, y: 0 },
-          { x: 499.999, y: 0 },
-          { x: 500, y: 100 },
-          { x: 1000, y: 100 },
-        ],
-      },
-      {
-        name: "transform",
-        computedValuePathClass: "distance-path",
-        expectedPathSegments: [
-          { x: 0, y: 0 },
-          { x: 500, y: 0 },
-          { x: 500, y: 50 },
-          { x: 1000, y: 50 },
-          { x: 1000, y: 100 },
-        ],
-      },
-    ],
-  },
 ];
 
 add_task(async function() {
   await testKeyframesGraphComputedValuePath(TEST_DATA);
 });
--- a/devtools/client/inspector/animation/test/browser_animation_keyframes-graph_computed-value-path-02.js
+++ b/devtools/client/inspector/animation/test/browser_animation_keyframes-graph_computed-value-path-02.js
@@ -1,16 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Test for following ComputedValuePath component:
-// * element existence
-// * path segments
+// Test for ComputedValuePath of animations that consist by one animated property
+// on complexed keyframes.
 
 requestLongerTimeout(2);
 
 const TEST_DATA = [
   {
     targetClass: "steps-effect",
     properties: [
       {
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_keyframes-graph_computed-value-path-03.js
@@ -0,0 +1,190 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for ComputedValuePath of animations that consist by multi types of animated
+// properties on complexed keyframes.
+
+requestLongerTimeout(2);
+
+const TEST_DATA = [
+  {
+    targetClass: "middle-keyframe",
+    properties: [
+      {
+        name: "background-color",
+        computedValuePathClass: "color-path",
+        expectedPathSegments: [
+          { x: 0, y: 0 },
+          { x: 0, y: 100 },
+          { x: 500, y: 100 },
+          { x: 1000, y: 100 },
+        ],
+        expectedStopColors: [
+          { offset: 0, color: "rgb(255, 0, 0)" },
+          { offset: 0.5, color: "rgb(0, 0, 255)" },
+          { offset: 1, color: "rgb(0, 255, 0)" },
+        ]
+      },
+      {
+        name: "background-repeat",
+        computedValuePathClass: "discrete-path",
+        expectedPathSegments: [
+          { x: 0, y: 0 },
+          { x: 249.999, y: 0 },
+          { x: 250, y: 100 },
+          { x: 749.999, y: 100 },
+          { x: 750, y: 0 },
+          { x: 1000, y: 0 },
+        ],
+      },
+      {
+        name: "font-size",
+        computedValuePathClass: "distance-path",
+        expectedPathSegments: [
+          { x: 0, y: 0 },
+          { x: 250, y: 50 },
+          { x: 500, y: 100 },
+          { x: 750, y: 50 },
+          { x: 1000, y: 0 },
+        ],
+      },
+      {
+        name: "margin-left",
+        computedValuePathClass: "distance-path",
+        expectedPathSegments: [
+          { x: 0, y: 0 },
+          { x: 250, y: 50 },
+          { x: 500, y: 100 },
+          { x: 750, y: 50 },
+          { x: 1000, y: 0 },
+        ],
+      },
+      {
+        name: "opacity",
+        computedValuePathClass: "distance-path",
+        expectedPathSegments: [
+          { x: 0, y: 0 },
+          { x: 250, y: 50 },
+          { x: 500, y: 100 },
+          { x: 750, y: 50 },
+          { x: 1000, y: 0 },
+        ],
+      },
+      {
+        name: "text-align",
+        computedValuePathClass: "discrete-path",
+        expectedPathSegments: [
+          { x: 0, y: 0 },
+          { x: 249.999, y: 0 },
+          { x: 250, y: 100 },
+          { x: 749.999, y: 100 },
+          { x: 750, y: 0 },
+          { x: 1000, y: 0 },
+        ],
+      },
+      {
+        name: "transform",
+        computedValuePathClass: "distance-path",
+        expectedPathSegments: [
+          { x: 0, y: 0 },
+          { x: 250, y: 50 },
+          { x: 500, y: 100 },
+          { x: 750, y: 50 },
+          { x: 1000, y: 0 },
+        ],
+      },
+    ],
+  },
+  {
+    targetClass: "steps-keyframe",
+    properties: [
+      {
+        name: "background-color",
+        computedValuePathClass: "color-path",
+        expectedPathSegments: [
+          { x: 0, y: 0 },
+          { x: 0, y: 100 },
+          { x: 500, y: 100 },
+          { x: 1000, y: 100 },
+        ],
+        expectedStopColors: [
+          { offset: 0, color: "rgb(255, 0, 0)" },
+          { offset: 0.499, color: "rgb(255, 0, 0)" },
+          { offset: 0.5, color: "rgb(128, 128, 0)" },
+          { offset: 0.999, color: "rgb(128, 128, 0)" },
+          { offset: 1, color: "rgb(0, 255, 0)" },
+        ]
+      },
+      {
+        name: "background-repeat",
+        computedValuePathClass: "discrete-path",
+        expectedPathSegments: [
+          { x: 0, y: 0 },
+          { x: 499.999, y: 0 },
+          { x: 500, y: 100 },
+          { x: 1000, y: 100 },
+        ],
+      },
+      {
+        name: "font-size",
+        computedValuePathClass: "distance-path",
+        expectedPathSegments: [
+          { x: 0, y: 0 },
+          { x: 500, y: 0 },
+          { x: 500, y: 50 },
+          { x: 1000, y: 50 },
+          { x: 1000, y: 100 },
+        ],
+      },
+      {
+        name: "margin-left",
+        computedValuePathClass: "distance-path",
+        expectedPathSegments: [
+          { x: 0, y: 0 },
+          { x: 499.999, y: 0 },
+          { x: 500, y: 50 },
+          { x: 999.999, y: 50 },
+          { x: 1000, y: 100 },
+        ],
+      },
+      {
+        name: "opacity",
+        computedValuePathClass: "distance-path",
+        expectedPathSegments: [
+          { x: 0, y: 0 },
+          { x: 499.999, y: 0 },
+          { x: 500, y: 50 },
+          { x: 999.999, y: 50 },
+          { x: 1000, y: 100 },
+        ],
+      },
+      {
+        name: "text-align",
+        computedValuePathClass: "discrete-path",
+        expectedPathSegments: [
+          { x: 0, y: 0 },
+          { x: 499.999, y: 0 },
+          { x: 500, y: 100 },
+          { x: 1000, y: 100 },
+        ],
+      },
+      {
+        name: "transform",
+        computedValuePathClass: "distance-path",
+        expectedPathSegments: [
+          { x: 0, y: 0 },
+          { x: 500, y: 0 },
+          { x: 500, y: 50 },
+          { x: 1000, y: 50 },
+          { x: 1000, y: 100 },
+        ],
+      },
+    ],
+  },
+];
+
+add_task(async function() {
+  await testKeyframesGraphComputedValuePath(TEST_DATA);
+});
--- a/devtools/client/responsive.html/components/App.js
+++ b/devtools/client/responsive.html/components/App.js
@@ -6,20 +6,22 @@
 
 "use strict";
 
 const { Component, createFactory } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
-const DeviceModal = createFactory(require("./DeviceModal"));
 const Toolbar = createFactory(require("./Toolbar"));
 const Viewports = createFactory(require("./Viewports"));
 
+loader.lazyGetter(this, "DeviceModal",
+  () => createFactory(require("./DeviceModal")));
+
 const {
   addCustomDevice,
   removeCustomDevice,
   updateDeviceDisplayed,
   updateDeviceModal,
   updatePreferredDevices,
 } = require("../actions/devices");
 const { changeNetworkThrottling } = require("devtools/client/shared/components/throttling/actions");
@@ -250,22 +252,25 @@ class App extends Component {
       Viewports({
         screenshot,
         viewports,
         onBrowserMounted,
         onContentResize,
         onRemoveDeviceAssociation,
         onResizeViewport,
       }),
-      DeviceModal({
-        deviceAdderViewportTemplate,
-        devices,
-        onAddCustomDevice,
-        onDeviceListUpdate,
-        onRemoveCustomDevice,
-        onUpdateDeviceDisplayed,
-        onUpdateDeviceModal,
-      })
+      devices.isModalOpen ?
+        DeviceModal({
+          deviceAdderViewportTemplate,
+          devices,
+          onAddCustomDevice,
+          onDeviceListUpdate,
+          onRemoveCustomDevice,
+          onUpdateDeviceDisplayed,
+          onUpdateDeviceModal,
+        })
+        :
+        null
     );
   }
 }
 
 module.exports = connect(state => state)(App);
--- a/devtools/client/responsive.html/components/DeviceAdder.js
+++ b/devtools/client/responsive.html/components/DeviceAdder.js
@@ -22,35 +22,32 @@ class DeviceAdder extends PureComponent 
       viewportTemplate: PropTypes.shape(Types.viewport).isRequired,
       onAddCustomDevice: PropTypes.func.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
-    this.state = {};
+    const {
+      height,
+      width,
+    } = this.props.viewportTemplate;
+
+    this.state = {
+      deviceAdderDisplayed: false,
+      height,
+      width,
+    };
 
     this.onChangeSize = this.onChangeSize.bind(this);
     this.onDeviceAdderShow = this.onDeviceAdderShow.bind(this);
     this.onDeviceAdderSave = this.onDeviceAdderSave.bind(this);
   }
 
-  componentWillReceiveProps(nextProps) {
-    const {
-      width,
-      height,
-    } = nextProps.viewportTemplate;
-
-    this.setState({
-      width,
-      height,
-    });
-  }
-
   onChangeSize(_, width, height) {
     this.setState({
       width,
       height,
     });
   }
 
   onDeviceAdderShow() {
@@ -63,24 +60,26 @@ class DeviceAdder extends PureComponent 
     const {
       devices,
       onAddCustomDevice,
     } = this.props;
 
     if (!this.pixelRatioInput.checkValidity()) {
       return;
     }
+
     if (devices.custom.find(device => device.name == this.nameInput.value)) {
       this.nameInput.setCustomValidity("Device name already in use");
       return;
     }
 
     this.setState({
       deviceAdderDisplayed: false,
     });
+
     onAddCustomDevice({
       name: this.nameInput.value,
       width: this.state.width,
       height: this.state.height,
       pixelRatio: parseFloat(this.pixelRatioInput.value),
       userAgent: this.userAgentInput.value,
       touch: this.touchInput.checked,
     });
--- a/devtools/client/responsive.html/components/DeviceModal.js
+++ b/devtools/client/responsive.html/components/DeviceModal.js
@@ -27,47 +27,32 @@ class DeviceModal extends PureComponent 
       onUpdateDeviceModal: PropTypes.func.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
     this.state = {};
+    for (const type of this.props.devices.types) {
+      for (const device of this.props.devices[type]) {
+        this.state[device.name] = device.displayed;
+      }
+    }
 
     this.onAddCustomDevice = this.onAddCustomDevice.bind(this);
     this.onDeviceCheckboxChange = this.onDeviceCheckboxChange.bind(this);
     this.onDeviceModalSubmit = this.onDeviceModalSubmit.bind(this);
     this.onKeyDown = this.onKeyDown.bind(this);
   }
 
   componentDidMount() {
     window.addEventListener("keydown", this.onKeyDown, true);
   }
 
-  componentWillReceiveProps(nextProps) {
-    const {
-      devices: oldDevices,
-    } = this.props;
-    const {
-      devices,
-    } = nextProps;
-
-    // Refresh component state only when model transitions from closed to open
-    if (!oldDevices.isModalOpen && devices.isModalOpen) {
-      for (const type of devices.types) {
-        for (const device of devices[type]) {
-          this.setState({
-            [device.name]: device.displayed,
-          });
-        }
-      }
-    }
-  }
-
   componentWillUnmount() {
     window.removeEventListener("keydown", this.onKeyDown, true);
   }
 
   onAddCustomDevice(device) {
     this.props.onAddCustomDevice(device);
     // Default custom devices to enabled
     this.setState({
--- a/devtools/client/responsive.html/test/browser/browser_device_modal_exit.js
+++ b/devtools/client/responsive.html/test/browser/browser_device_modal_exit.js
@@ -4,42 +4,37 @@ http://creativecommons.org/publicdomain/
 "use strict";
 
 // Test submitting display device changes on the device modal
 
 const TEST_URL = "data:text/html;charset=utf-8,";
 const Types = require("devtools/client/responsive.html/types");
 
 addRDMTask(TEST_URL, async function({ ui }) {
-  const { store, document } = ui.toolWindow;
-  const modal = document.querySelector("#device-modal-wrapper");
-  const closeButton = document.querySelector("#device-close-button");
+  const { document, store } = ui.toolWindow;
 
   // Wait until the viewport has been added and the device list has been loaded
   await waitUntilState(store, state => state.viewports.length == 1
     && state.devices.listState == Types.loadableState.LOADED);
 
   await openDeviceModal(ui);
 
   const preferredDevicesBefore = _loadPreferredDevices();
 
   info("Check the first unchecked device and exit the modal.");
   const uncheckedCb = [...document.querySelectorAll(".device-input-checkbox")]
     .filter(cb => !cb.checked)[0];
   const value = uncheckedCb.value;
   uncheckedCb.click();
-  closeButton.click();
+  document.getElementById("device-close-button").click();
 
-  ok(modal.classList.contains("closed") && !modal.classList.contains("opened"),
-    "The device modal is closed on exit.");
+  ok(!store.getState().devices.isModalOpen, "The device modal is closed on exit.");
 
   info("Check that the device list remains unchanged after exitting.");
   const preferredDevicesAfter = _loadPreferredDevices();
 
   is(preferredDevicesBefore.added.size, preferredDevicesAfter.added.size,
     "Got expected number of added devices.");
-
   is(preferredDevicesBefore.removed.size, preferredDevicesAfter.removed.size,
     "Got expected number of removed devices.");
-
   ok(!preferredDevicesAfter.removed.has(value),
     value + " was not added to removed device list.");
 });
--- a/devtools/client/responsive.html/test/browser/browser_device_modal_submit.js
+++ b/devtools/client/responsive.html/test/browser/browser_device_modal_submit.js
@@ -18,20 +18,18 @@ const addedDevice = {
   "featured": true,
 };
 
 const TEST_URL = "data:text/html;charset=utf-8,";
 const Types = require("devtools/client/responsive.html/types");
 
 addRDMTask(TEST_URL, async function({ ui }) {
   const { toolWindow } = ui;
-  const { store, document } = toolWindow;
+  const { document, store } = toolWindow;
   const deviceSelector = document.getElementById("device-selector");
-  const modal = document.getElementById("device-modal-wrapper");
-  const submitButton = document.getElementById("device-submit-button");
 
   // Wait until the viewport has been added and the device list has been loaded
   await waitUntilState(store, state => state.viewports.length == 1
     && state.devices.listState == Types.loadableState.LOADED);
 
   await openDeviceModal(ui);
 
   info("Checking displayed device checkboxes are checked in the device modal.");
@@ -55,47 +53,47 @@ addRDMTask(TEST_URL, async function({ ui
   }
 
   // Tests where the user adds a non-featured device
   info("Check the first unchecked device and submit new device list.");
   const uncheckedCb = [...document.querySelectorAll(".device-input-checkbox")]
     .filter(cb => !cb.checked)[0];
   const value = uncheckedCb.value;
   uncheckedCb.click();
-  submitButton.click();
+  document.getElementById("device-submit-button").click();
 
-  ok(modal.classList.contains("closed") && !modal.classList.contains("opened"),
-    "The device modal is closed on submit.");
+  ok(!store.getState().devices.isModalOpen, "The device modal is closed on submit.");
 
   info("Checking that the new device is added to the user preference list.");
   let preferredDevices = _loadPreferredDevices();
   ok(preferredDevices.added.has(value), value + " in user added list.");
 
   info("Checking new device is added to the device selector.");
   await testMenuItems(toolWindow, deviceSelector, menuItems => {
     is(menuItems.length - 2, featuredCount + 1,
       "Got expected number of devices in device selector.");
 
     const menuItem = menuItems.find(item => item.getAttribute("label") === name);
     ok(menuItem, value + " added to the device selector.");
   });
 
   info("Reopen device modal and check new device is correctly checked");
   await openDeviceModal(ui);
+
   ok([...document.querySelectorAll(".device-input-checkbox")]
     .filter(cb => cb.checked && cb.value === value)[0],
     value + " is checked in the device modal.");
 
   // Tests where the user removes a featured device
   info("Uncheck the first checked device different than the previous one");
   const checkedCb = [...document.querySelectorAll(".device-input-checkbox")]
     .filter(cb => cb.checked && cb.value != value)[0];
   const checkedVal = checkedCb.value;
   checkedCb.click();
-  submitButton.click();
+  document.getElementById("device-submit-button").click();
 
   info("Checking that the device is removed from the user preference list.");
   preferredDevices = _loadPreferredDevices();
   ok(preferredDevices.removed.has(checkedVal), checkedVal + " in removed list");
 
   info("Checking that the device is not in the device selector.");
   await testMenuItems(toolWindow, deviceSelector, menuItems => {
     is(menuItems.length - 2, featuredCount,
@@ -112,17 +110,17 @@ addRDMTask(TEST_URL, async function({ ui
     checkedVal + " is unchecked in the device modal.");
 
   // Let's add a dummy device to simulate featured flag changes for next test
   addDeviceForTest(addedDevice);
 });
 
 addRDMTask(TEST_URL, async function({ ui }) {
   const { toolWindow } = ui;
-  const { store, document } = toolWindow;
+  const { document, store } = toolWindow;
 
   // Wait until the viewport has been added and the device list has been loaded
   await waitUntilState(store, state => state.viewports.length == 1
     && state.devices.listState == Types.loadableState.LOADED);
 
   await openDeviceModal(ui);
 
   const remoteList = await getDevices();
--- a/devtools/client/responsive.html/test/browser/head.js
+++ b/devtools/client/responsive.html/test/browser/head.js
@@ -223,26 +223,24 @@ async function testViewportResize(ui, se
   const endRect = getElRect(selector, win);
   is(endRect.left - startRect.left, expectedHandleMove[0],
     `The x move of ${selector} is as expected`);
   is(endRect.top - startRect.top, expectedHandleMove[1],
     `The y move of ${selector} is as expected`);
 }
 
 async function openDeviceModal(ui) {
-  const { document } = ui.toolWindow;
-  const modal = document.getElementById("device-modal-wrapper");
-
-  info("Checking initial device modal state");
-  ok(modal.classList.contains("closed") && !modal.classList.contains("opened"),
-    "The device modal is closed by default.");
+  const { document, store } = ui.toolWindow;
 
   info("Opening device modal through device selector.");
+  const onModalOpen = waitUntilState(store, state => state.devices.isModalOpen);
   await selectMenuItem(ui, "#device-selector", getStr("responsive.editDeviceList2"));
+  await onModalOpen;
 
+  const modal = document.getElementById("device-modal-wrapper");
   ok(modal.classList.contains("opened") && !modal.classList.contains("closed"),
     "The device modal is displayed.");
 }
 
 async function selectMenuItem({ toolWindow }, selector, value) {
   const { document } = toolWindow;
 
   const button = document.querySelector(selector);
@@ -409,17 +407,17 @@ async function testUserAgentFromBrowser(
 
 /**
  * Assuming the device modal is open and the device adder form is shown, this helper
  * function adds `device` via the form, saves it, and waits for it to appear in the store.
  */
 function addDeviceInModal(ui, device) {
   const { Simulate } =
     ui.toolWindow.require("devtools/client/shared/vendor/react-dom-test-utils");
-  const { store, document } = ui.toolWindow;
+  const { document, store } = ui.toolWindow;
 
   const nameInput = document.querySelector("#device-adder-name input");
   const [ widthInput, heightInput ] =
     document.querySelectorAll("#device-adder-size input");
   const pixelRatioInput = document.querySelector("#device-adder-pixel-ratio input");
   const userAgentInput = document.querySelector("#device-adder-user-agent input");
   const touchInput = document.querySelector("#device-adder-touch input");
 
--- a/devtools/server/actors/animation-type-longhand.js
+++ b/devtools/server/actors/animation-type-longhand.js
@@ -205,16 +205,17 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [
     "margin-inline-end",
     "margin-inline-start",
     "-moz-math-display",
     "max-block-size",
     "max-inline-size",
     "min-block-size",
     "-moz-min-font-size-ratio",
     "min-inline-size",
+    "offset-path",
     "padding-block-end",
     "padding-block-start",
     "padding-inline-end",
     "padding-inline-start",
     "rotate",
     "scale",
     "-moz-script-level",
     "-moz-top-layer",
--- a/devtools/shared/css/generated/properties-db.js
+++ b/devtools/shared/css/generated/properties-db.js
@@ -2944,16 +2944,17 @@ exports.CSS_PROPERTIES = {
       "scroll-snap-points-x",
       "scroll-snap-points-y",
       "scroll-snap-destination",
       "scroll-snap-coordinate",
       "transform",
       "rotate",
       "scale",
       "translate",
+      "offset-path",
       "scroll-behavior",
       "scroll-snap-type-x",
       "scroll-snap-type-y",
       "overscroll-behavior-x",
       "overscroll-behavior-y",
       "isolation",
       "page-break-after",
       "page-break-before",
@@ -7410,16 +7411,30 @@ exports.CSS_PROPERTIES = {
       "inherit",
       "initial",
       "left",
       "right",
       "top",
       "unset"
     ]
   },
+  "offset-path": {
+    "isInherited": false,
+    "subproperties": [
+      "offset-path"
+    ],
+    "supports": [],
+    "values": [
+      "inherit",
+      "initial",
+      "none",
+      "path",
+      "unset"
+    ]
+  },
   "opacity": {
     "isInherited": false,
     "subproperties": [
       "opacity"
     ],
     "supports": [],
     "values": [
       "inherit",
@@ -9327,16 +9342,20 @@ exports.PREFERENCES = [
     "background-blend-mode",
     "layout.css.background-blend-mode.enabled"
   ],
   [
     "font-variation-settings",
     "layout.css.font-variations.enabled"
   ],
   [
+    "offset-path",
+    "layout.css.motion-path.enabled"
+  ],
+  [
     "rotate",
     "layout.css.individual-transform.enabled"
   ],
   [
     "scale",
     "layout.css.individual-transform.enabled"
   ],
   [
--- a/dom/payments/test/CurrencyAmountValidationChromeScript.js
+++ b/dom/payments/test/CurrencyAmountValidationChromeScript.js
@@ -1,60 +1,66 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
-const { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+const { XPCOMUtils } = ChromeUtils.import(
+  "resource://gre/modules/XPCOMUtils.jsm"
+);
 
-const paymentSrv = Cc["@mozilla.org/dom/payments/payment-request-service;1"].getService(Ci.nsIPaymentRequestService);
+const paymentSrv = Cc[
+  "@mozilla.org/dom/payments/payment-request-service;1"
+].getService(Ci.nsIPaymentRequestService);
 
 const InvalidDetailsUIService = {
-  showPayment: function(requestId) {
+  showPayment(requestId) {
     paymentSrv.changeShippingOption(requestId, "");
   },
-  abortPayment: function(requestId) {
-    let abortResponse = Cc["@mozilla.org/dom/payments/payment-abort-action-response;1"].
-                           createInstance(Ci.nsIPaymentAbortActionResponse);
+  abortPayment(requestId) {
+    const abortResponse = Cc[
+      "@mozilla.org/dom/payments/payment-abort-action-response;1"
+    ].createInstance(Ci.nsIPaymentAbortActionResponse);
     abortResponse.init(requestId, Ci.nsIPaymentActionResponse.ABORT_SUCCEEDED);
-    paymentSrv.respondPayment(abortResponse.QueryInterface(Ci.nsIPaymentActionResponse));
-  },
-  completePayment: function(requestId) {
+    paymentSrv.respondPayment(
+      abortResponse.QueryInterface(Ci.nsIPaymentActionResponse)
+    );
   },
-  updatePayment: function(requestId) {
-  },
+  completePayment(requestId) {},
+  updatePayment(requestId) {},
   QueryInterface: ChromeUtils.generateQI([Ci.nsIPaymentUIService]),
-
 };
 
-function emitTestFail(message) {
-  sendAsyncMessage("test-fail", message);
-}
-
 function checkLowerCaseCurrency() {
   const paymentEnum = paymentSrv.enumerate();
   if (!paymentEnum.hasMoreElements()) {
-    emitTestFail("PaymentRequestService should have at least one payment request.");
+    const msg =
+      "PaymentRequestService should have at least one payment request.";
+    sendAsyncMessage("test-fail", msg);
   }
   while (paymentEnum.hasMoreElements()) {
-    let payRequest = paymentEnum.getNext().QueryInterface(Ci.nsIPaymentRequest);
+    const payRequest = paymentEnum
+      .getNext()
+      .QueryInterface(Ci.nsIPaymentRequest);
     if (!payRequest) {
-      emitTestFail("Fail to get existing payment request.");
+      sendAsyncMessage("test-fail", "Fail to get existing payment request.");
       break;
     }
-    if (payRequest.paymentDetails.totalItem.amount.currency != "USD") {
-      emitTestFail("Currency of PaymentItem total should be 'USD', but got " +
-                   payRequest.paymentDetails.totalItem.amount.currency + ".");
+    const { currency } = payRequest.paymentDetails.totalItem.amount;
+    if (currency != "USD") {
+      const msg =
+        "Currency of PaymentItem total should be 'USD', but got ${currency}";
+      sendAsyncMessage("check-complete");
     }
   }
   paymentSrv.cleanup();
   sendAsyncMessage("check-complete");
 }
 
 addMessageListener("check-lower-case-currency", checkLowerCaseCurrency);
 
-addMessageListener("set-update-with-invalid-details-ui-service", function() {
-  paymentSrv.setTestingUIService(InvalidDetailsUIService.QueryInterface(Ci.nsIPaymentUIService));
+addMessageListener("set-update-with-invalid-details-ui-service", () => {
+  paymentSrv.setTestingUIService(
+    InvalidDetailsUIService.QueryInterface(Ci.nsIPaymentUIService)
+  );
 });
 
-addMessageListener("teardown", function() {
-  sendAsyncMessage("teardown-complete");
-});
+addMessageListener("teardown", () => sendAsyncMessage("teardown-complete"));
--- a/dom/payments/test/test_currency_amount_validation.html
+++ b/dom/payments/test/test_currency_amount_validation.html
@@ -1,376 +1,353 @@
 <!DOCTYPE HTML>
-<html>
+<meta charset="utf-8">
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=1367669
 https://bugzilla.mozilla.org/show_bug.cgi?id=1388661
 -->
-<head>
-  <meta charset="utf-8">
-  <title>Test for PaymentRequest API currency amount validation</title>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript">
-
-  "use strict";
-  SimpleTest.waitForExplicitFinish();
+<title>Test for PaymentRequest API currency amount validation</title>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+"use strict";
+SimpleTest.waitForExplicitFinish();
 
-  var gUrl = SimpleTest.getTestFileURL('CurrencyAmountValidationChromeScript.js');
-  var gScript = SpecialPowers.loadChromeScript(gUrl);
+const gUrl = SimpleTest.getTestFileURL(
+  "CurrencyAmountValidationChromeScript.js"
+);
+const gScript = SpecialPowers.loadChromeScript(gUrl);
 
-  function testFailHandler(message) {
-    ok(false, message);
-  }
-  gScript.addMessageListener("test-fail", testFailHandler);
+function testFailHandler(message) {
+  ok(false, message);
+}
+gScript.addMessageListener("test-fail", testFailHandler);
 
-  const defaultMethods = [{
+const defaultMethods = [
+  {
     supportedMethods: "basic-card",
-  }];
-  const defaultDetails = {
-    total: {
-      label: "total",
-      amount: {
-        currency: "usd",
-        value: "1.00",
-      },
+  },
+];
+const defaultDetails = {
+  total: {
+    label: "total",
+    amount: {
+      currency: "usd",
+      value: "1.00",
     },
-  };
+  },
+};
 
-  const specialAmountDetails = {
-    total: {
-      label: "total",
-      amount: {
-        currency: "usd",
-        value: {
-          toString() {
-            throw "42";
-          },
+const specialAmountDetails = {
+  total: {
+    label: "total",
+    amount: {
+      currency: "usd",
+      value: {
+        toString() {
+          throw "42";
         },
       },
     },
-  };
+  },
+};
 
-  const wellFormedCurrencyCodes = [
-    "BOB",
-    "EUR",
-    "usd", // currency codes are case-insensitive
-    "XdR",
-    "xTs",
-  ];
+const wellFormedCurrencyCodes = [
+  "BOB",
+  "EUR",
+  "usd", // currency codes are case-insensitive
+  "XdR",
+  "xTs",
+];
 
-  const invalidCurrencyCodes = [
-    "",
-    "€",
-    "$",
-    "SFr.",
-    "DM",
-    "KR₩",
-    "702",
-    "ßP",
-    "ınr",
-    "invalid",
-    "in",
-    "123",
-  ];
+const invalidCurrencyCodes = [
+  "",
+  "€",
+  "$",
+  "SFr.",
+  "DM",
+  "KR₩",
+  "702",
+  "ßP",
+  "ınr",
+  "invalid",
+  "in",
+  "123",
+];
 
-  const updatedInvalidCurrencyDetails = {
-    total: {
-      label: "Total",
-      amount: {
-        currency: "Invalid",
-        value: "1.00"
-      }
+const updatedInvalidCurrencyDetails = {
+  total: {
+    label: "Total",
+    amount: {
+      currency: "Invalid",
+      value: "1.00",
     },
-  };
+  },
+};
 
-  const updatedInvalidAmountDetails = {
-    total: {
-      label: "Total",
-      amount: {
-        currency: "USD",
-        value: "-1.00",
-      },
+const updatedInvalidAmountDetails = {
+  total: {
+    label: "Total",
+    amount: {
+      currency: "USD",
+      value: "-1.00",
     },
-  }
+  },
+};
 
-  const invalidAmounts = [
-    "-",
-    "notdigits",
-    "ALSONOTDIGITS",
-    "10.",
-    ".99",
-    "-10.",
-    "-.99",
-    "10-",
-    "1-0",
-    "1.0.0",
-    "1/3",
-    "",
-    null,
-    " 1.0  ",
-    " 1.0 ",
-    "1.0 ",
-    "USD$1.0",
-    "$1.0",
-    {
-      toString() {
-        return " 1.0";
-      },
+const invalidAmounts = [
+  "-",
+  "notdigits",
+  "ALSONOTDIGITS",
+  "10.",
+  ".99",
+  "-10.",
+  "-.99",
+  "10-",
+  "1-0",
+  "1.0.0",
+  "1/3",
+  "",
+  null,
+  " 1.0  ",
+  " 1.0 ",
+  "1.0 ",
+  "USD$1.0",
+  "$1.0",
+  {
+    toString() {
+      return " 1.0";
     },
-    undefined,
-  ];
-  const invalidTotalAmounts = invalidAmounts.concat([
-    "-1",
-    "-1.0",
-    "-1.00",
-    "-1000.000",
-  ]);
+  },
+  undefined,
+];
+const invalidTotalAmounts = invalidAmounts.concat([
+  "-1",
+  "-1.0",
+  "-1.00",
+  "-1000.000",
+]);
 
-  function updateWithInvalidCurrency() {
-    return new Promise((resolve, reject) => {
-      resolve(updatedInvalidCurrencyDetails);
-    });
-  }
+function updateWithInvalidAmount() {
+  return new Promise((resolve, reject) => {
+    resolve(updatedInvalidAmountDetails);
+  });
+}
 
-  function updateWithInvalidAmount() {
-    return new Promise((resolve, reject) => {
-      resolve(updatedInvalidAmountDetails);
-    });
-  }
-
-  function testWithLowerCaseCurrency() {
-    return new Promise((resolve, reject) => {
-      const payRequest = new PaymentRequest(defaultMethods, defaultDetails);
-      ok(payRequest, "PaymentRequest should be created");
-      gScript.addMessageListener("check-complete", function checkCompleteHandler() {
+async function testWithLowerCaseCurrency() {
+  const payRequest = new PaymentRequest(defaultMethods, defaultDetails);
+  return new Promise(resolve => {
+    gScript.addMessageListener(
+      "check-complete",
+      function checkCompleteHandler() {
         gScript.removeMessageListener("check-complete", checkCompleteHandler);
         resolve();
-      });
-      gScript.sendAsyncMessage("check-lower-case-currency");
-    });
+      }
+    );
+    gScript.sendAsyncMessage("check-lower-case-currency");
+  });
+}
+
+function testWithWellFormedCurrencyCodes() {
+  for (const currency of wellFormedCurrencyCodes) {
+    const details = {
+      total: {
+        label: "Well Formed Currency",
+        amount: {
+          currency: currency,
+          value: "1.00",
+        },
+      },
+    };
+    try {
+      const payRequest = new PaymentRequest(defaultMethods, details);
+    } catch (e) {
+      const msg = `Unexpected error while creating payment request with well-formed currency (${currency}) ${
+        e.name
+      }`;
+      ok(false, msg);
+    }
   }
+}
+
+function testWithInvalidCurrencyCodes() {
+  for (const invalidCurrency of invalidCurrencyCodes) {
+    const invalidDetails = {
+      total: {
+        label: "Invalid Currency",
+        amount: {
+          currency: invalidCurrency,
+          value: "1.00",
+        },
+      },
+    };
+    try {
+      const payRequest = new PaymentRequest(defaultMethods, invalidDetails);
+      ok(
+        false,
+        `Creating a Payment Request with invalid currency (${invalidCurrency}) must throw.`
+      );
+    } catch (e) {
+      is(
+        e.name,
+        "RangeError",
+        `Expected rejected with 'RangeError', but got '${e.name}'.`
+      );
+    }
+  }
+}
 
-  function testWithWellFormedCurrencyCodes() {
-    return new Promise((resolve, reject) => {
-      for (const currency of wellFormedCurrencyCodes) {
-        let details = {
-          total: {
-            label: "Well Formed Currency",
-            amount: {
-              currency: currency,
-              value: "1.00",
-            },
+async function testUpdateWithInvalidCurrency() {
+  const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(
+    true
+  );
+  gScript.sendAsyncMessage("set-update-with-invalid-details-ui-service");
+  const payRequest = new PaymentRequest(defaultMethods, defaultDetails);
+  payRequest.addEventListener("shippingaddresschange", event => {
+    event.updateWith(Promise.resolve(updatedInvalidCurrencyDetails));
+  });
+  payRequest.addEventListener("shippingoptionchange", event => {
+    event.updateWith(updatedInvalidCurrencyDetails);
+  });
+  try {
+    await payRequest.show();
+    ok(false, "Should have rejected with 'RangeError'");
+  } catch (err) {
+    is(
+      err.name,
+      "RangeError",
+      `Should be rejected with 'RangeError', but got '${err.name}'.`
+    );
+  }
+  handler.destruct();
+}
+
+async function testUpdateWithInvalidAmount() {
+  const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(
+    true
+  );
+  gScript.sendAsyncMessage("set-update-with-invalid-details-ui-service");
+  const payRequest = new PaymentRequest(defaultMethods, defaultDetails);
+  payRequest.addEventListener("shippingaddresschange", event => {
+    event.updateWith(updateWithInvalidAmount());
+  });
+  payRequest.addEventListener("shippingoptionchange", event => {
+    event.updateWith(updateWithInvalidAmount());
+  });
+  try {
+    await payRequest.show();
+    ok(false, "Should be rejected with 'TypeError'");
+  } catch (err) {
+    is(
+      err.name,
+      "TypeError",
+      `Should be rejected with 'TypeError', but got ${err.name}.`
+    );
+  }
+  handler.destruct();
+}
+
+function testSpecialAmount() {
+  try {
+    new PaymentRequest(defaultMethods, specialAmountDetails);
+    ok(false, "Should throw '42', but got resolved.");
+  } catch (e) {
+    is(e, "42", "Expected throw '42'. but got " + e);
+  }
+}
+
+function testInvalidTotalAmounts() {
+  for (const invalidAmount of invalidTotalAmounts) {
+    try {
+      const invalidDetails = {
+        total: {
+          label: "",
+          amount: {
+            currency: "USD",
+            value: invalidAmount,
           },
-        };
-        try {
-          const payRequest = new PaymentRequest(defaultMethods, details);
-        } catch (e) {
-          ok(false, "Unexpected error while creating payment request with well formed currency("
-                    + currency + ") " + e.name + ".");
-        }
-      }
-      resolve();
-    });
+        },
+      };
+      new PaymentRequest(defaultMethods, invalidDetails);
+      ok(false, "Should throw 'TypeError', but got resolved.");
+    } catch (err) {
+      is(err.name, "TypeError", `Expected 'TypeError', but got '${err.name}'`);
+    }
   }
+}
 
-  function testWithInvalidCurrencyCodes() {
-    return new Promise((resolve, reject) => {
-      for (const invalidCurrency of invalidCurrencyCodes) {
-        let invalidDetails = {
-          total: {
-            label: "Invalid Currency",
+function testInvalidAmounts() {
+  for (const invalidAmount of invalidAmounts) {
+    try {
+      new PaymentRequest(defaultMethods, {
+        total: {
+          label: "",
+          amount: {
+            currency: "USD",
+            value: "1.00",
+          },
+        },
+        displayItems: [
+          {
+            label: "",
             amount: {
-              currency: invalidCurrency,
-              value: "1.00",
+              currency: "USD",
+              value: invalidAmount,
             },
           },
-        };
-        try {
-          const payRequest = new PaymentRequest(defaultMethods, invalidDetails);
-          ok(false, "Expected fail to create PaymentRequest with invalid currency(" + invalidCurrency + ").");
-        } catch (e) {
-          is(e.name, "RangeError", "Expected rejected with 'RangeError', but got " + e.name + ".");
-        }
-      }
-      resolve();
-    });
-  }
-
-  function testUpdateWithInvalidCurrency() {
-    const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true);
-
-    gScript.sendAsyncMessage("set-update-with-invalid-details-ui-service");
-    return new Promise((resolve, reject) => {
-      const payRequest = new PaymentRequest(defaultMethods, defaultDetails);
-      payRequest.addEventListener("shippingaddresschange", event => {
-        event.updateWith(updateWithInvalidCurrency());
+        ],
       });
-      payRequest.addEventListener("shippingoptionchange", event => {
-        event.updateWith(updateWithInvalidCurrency());
-      });
-      payRequest.show().then((result) => {
-        ok(false, "Should be rejected with 'RangeError', but got resolved");
-        resolve();
-      }, (result) => {
-        is(result.name, "RangeError", "Should be rejected with 'RangeError', but got " + result.name + ".");
-        resolve();
-      }).catch(e => {
-        ok(false, "Unexpected error: " + e.name);
-        resolve();
-      }).finally(handler.destruct);
-    });
+      ok(false, "Should throw 'TypeError', but got resolved.");
+    } catch (err) {
+      is(err.name, "TypeError", `Expected 'TypeError', but got '${err.name}'.`);
+    }
   }
-
-  function testUpdateWithInvalidAmount() {
-    const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(true);
+}
 
-    gScript.sendAsyncMessage("set-update-with-invalid-details-ui-service");
-    return new Promise((resolve, reject) => {
-      const payRequest = new PaymentRequest(defaultMethods, defaultDetails);
-      payRequest.addEventListener("shippingaddresschange", event => {
-        event.updateWith(updateWithInvalidAmount());
-      });
-      payRequest.addEventListener("shippingoptionchange", event => {
-        event.updateWith(updateWithInvalidAmount());
-      });
-      payRequest.show().then((result) => {
-        ok(false, "Should be rejected with 'TypeError', but got resolved");
-        resolve();
-      }, (result) => {
-        is(result.name, "TypeError", "Should be rejected with 'TypeError', but got " + result.name + ".");
-        resolve();
-      }).catch(e => {
-        ok(false, "Unexpected error: " + e.name);
-        resolve();
-      }).finally(handler.destruct);
-    });
-  }
-
-  function testSpecialAmount() {
-    return new Promise((resolve, reject) => {
-      try {
-        new PaymentRequest([{supportedMethods: "basic-card"}],
-                           specialAmountDetails);
-        ok(false, "Should throw '42', but got resolved.");
-        resolve();
-      } catch (e) {
-        is(e, "42", "Expected throw '42'. but got " + e);
+function teardown() {
+  return new Promise(resolve => {
+    gScript.addMessageListener(
+      "teardown-complete",
+      function teardownCompleteHandler() {
+        gScript.removeMessageListener(
+          "teardown-complete",
+          teardownCompleteHandler
+        );
+        gScript.removeMessageListener("test-fail", testFailHandler);
+        gScript.destroy();
+        SimpleTest.finish();
         resolve();
       }
-    });
-  }
-
-  function testInvalidTotalAmounts() {
-    return new Promise((resolve, reject) => {
-      for (const amount of invalidTotalAmounts) {
-        try {
-          new PaymentRequest(
-            [
-              {
-                supportedMethods: "basic-card",
-              },
-            ],
-            {
-              total: {
-                label: "",
-                amount: {
-                  currency: "USD",
-                  value: amount,
-                },
-              },
-            }
-          );
-
-          ok(false, "Should throw 'TypeError', but got resolved.");
-          resolve();
-        }
-        catch (err) {
-          is(err.name, "TypeError",
-            "Expected 'TypeError', but got '" + err.name + "'");
-          resolve();
-        };
-      }
-    });
-  }
+    );
+    gScript.sendAsyncMessage("teardown");
+  });
+}
 
-  function testInvalidAmounts() {
-     return new Promise((resolve, reject) => {
-      for (const amount of invalidAmounts) {
-        try {
-          new PaymentRequest(
-            [
-              {
-                supportedMethods: "basic-card",
-              },
-            ],
-            {
-              total: {
-                label: "",
-                amount: {
-                  currency: "USD",
-                  value: "1.00",
-                },
-              },
-              displayItems: [
-                {
-                  label: "",
-                  amount: {
-                    currency: "USD",
-                    value: amount,
-                  },
-                },
-              ],
-            }
-          );
-          ok(false, "Should throw 'TypeError', but got resolved.");
-          resolve();
-        }
-        catch (err) {
-          is(err.name, "TypeError",
-            "Expected 'TypeError', but got '" + err.name + "'");
-          resolve();
-        };
-      }
-    });
+async function runTests() {
+  try {
+    testInvalidTotalAmounts();
+    testSpecialAmount();
+    testInvalidAmounts();
+    testWithWellFormedCurrencyCodes();
+    testWithInvalidCurrencyCodes();
+    await testUpdateWithInvalidAmount();
+    await testUpdateWithInvalidCurrency();
+    await testWithLowerCaseCurrency();
+    await teardown();
+  } catch (e) {
+    console.error(e);
+    ok(false, "Unexpected error: " + e.name);
+    SimpleTest.finish();
   }
-
-  function teardown() {
-    gScript.addMessageListener("teardown-complete", function teardownCompleteHandler() {
-      gScript.removeMessageListener("teardown-complete", teardownCompleteHandler);
-      gScript.removeMessageListener("test-fail", testFailHandler)
-      gScript.destroy();
-      SimpleTest.finish();
-    });
-    gScript.sendAsyncMessage("teardown");
-  }
+}
 
-  function runTests() {
-    testInvalidTotalAmounts()
-    .then(testSpecialAmount)
-    .then(testInvalidAmounts)
-    .then(testWithLowerCaseCurrency)
-    .then(testWithWellFormedCurrencyCodes)
-    .then(testWithInvalidCurrencyCodes)
-    .then(testUpdateWithInvalidAmount)
-    .then(testUpdateWithInvalidCurrency)
-    .then(teardown)
-    .catch( e => {
-      ok(false, "Unexpected error: " + e.name);
-      SimpleTest.finish();
-    });
-  }
-
-  window.addEventListener('load', function() {
-    SpecialPowers.pushPrefEnv({
-      'set': [
-        ['dom.payments.request.enabled', true],
-      ]
-    }, runTests);
-  });
-
-  </script>
-</head>
+window.addEventListener("load", () => {
+  SpecialPowers.pushPrefEnv(
+    {
+      set: [["dom.payments.request.enabled", true]],
+    },
+    runTests
+  );
+});
+</script>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1367669">Mozilla Bug 1367669</a>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1388661">Mozilla Bug 1388661</a>
-</body>
-</html>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1388661">Mozilla Bug 1388661</a>
\ No newline at end of file
--- a/dom/svg/SVGPathData.cpp
+++ b/dom/svg/SVGPathData.cpp
@@ -21,22 +21,51 @@
 #include "SVGGeometryElement.h" // for nsSVGMark
 #include "SVGPathSegUtils.h"
 #include <algorithm>
 
 using namespace mozilla;
 using namespace mozilla::dom::SVGPathSeg_Binding;
 using namespace mozilla::gfx;
 
-static bool IsMoveto(uint16_t aSegType)
+static inline bool IsMoveto(uint16_t aSegType)
 {
   return aSegType == PATHSEG_MOVETO_ABS ||
          aSegType == PATHSEG_MOVETO_REL;
 }
 
+static inline bool
+IsMoveto(StylePathCommand::Tag aSegType)
+{
+  return aSegType == StylePathCommand::Tag::MoveTo;
+}
+
+static inline bool
+IsValidType(uint16_t aSegType)
+{
+  return SVGPathSegUtils::IsValidType(aSegType);
+}
+
+static inline bool
+IsValidType(StylePathCommand::Tag aSegType)
+{
+  return aSegType != StylePathCommand::Tag::Unknown;
+}
+
+static inline bool
+IsClosePath(uint16_t aSegType) {
+  return aSegType == PATHSEG_CLOSEPATH;
+}
+
+static inline bool
+IsClosePath(StylePathCommand::Tag aSegType)
+{
+  return aSegType == StylePathCommand::Tag::ClosePath;
+}
+
 nsresult
 SVGPathData::CopyFrom(const SVGPathData& rhs)
 {
   if (!mData.Assign(rhs.mData, fallible)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   return NS_OK;
 }
@@ -257,28 +286,28 @@ ApproximateZeroLengthSubpathSquareCaps(P
   // described in the comment above.
 
   Float tinyLength = aStrokeWidth / SVG_ZERO_LENGTH_PATH_FIX_FACTOR;
 
   aPB->LineTo(aPoint + Point(tinyLength, 0));
   aPB->MoveTo(aPoint);
 }
 
-#define MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT               \
-  do {                                                                        \
-    if (!subpathHasLength && hasLineCaps && aStrokeWidth > 0 &&               \
-        subpathContainsNonMoveTo &&                                           \
-        SVGPathSegUtils::IsValidType(prevSegType) &&                          \
-        (!IsMoveto(prevSegType) || segType == PATHSEG_CLOSEPATH)) {           \
-      ApproximateZeroLengthSubpathSquareCaps(builder, segStart, aStrokeWidth);\
-    }                                                                         \
+#define MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT                \
+  do {                                                                         \
+    if (!subpathHasLength && hasLineCaps && aStrokeWidth > 0 &&                \
+        subpathContainsNonMoveTo &&                                            \
+        IsValidType(prevSegType) &&                                            \
+        (!IsMoveto(prevSegType) || IsClosePath(segType))) {                    \
+      ApproximateZeroLengthSubpathSquareCaps(aBuilder, segStart, aStrokeWidth);\
+    }                                                                          \
   } while(0)
 
 already_AddRefed<Path>
-SVGPathData::BuildPath(PathBuilder* builder,
+SVGPathData::BuildPath(PathBuilder* aBuilder,
                        uint8_t aStrokeLineCap,
                        Float aStrokeWidth) const
 {
   if (mData.IsEmpty() || !IsMoveto(SVGPathSegUtils::DecodeType(mData[0]))) {
     return nullptr; // paths without an initial moveto are invalid
   }
 
   bool hasLineCaps = aStrokeLineCap != NS_STYLE_STROKE_LINECAP_BUTT;
@@ -304,189 +333,189 @@ SVGPathData::BuildPath(PathBuilder* buil
 
     switch (segType)
     {
     case PATHSEG_CLOSEPATH:
       // set this early to allow drawing of square caps for "M{x},{y} Z":
       subpathContainsNonMoveTo = true;
       MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
       segEnd = pathStart;
-      builder->Close();
+      aBuilder->Close();
       break;
 
     case PATHSEG_MOVETO_ABS:
       MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
       pathStart = segEnd = Point(mData[i], mData[i+1]);
-      builder->MoveTo(segEnd);
+      aBuilder->MoveTo(segEnd);
       subpathHasLength = false;
       break;
 
     case PATHSEG_MOVETO_REL:
       MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
       pathStart = segEnd = segStart + Point(mData[i], mData[i+1]);
-      builder->MoveTo(segEnd);
+      aBuilder->MoveTo(segEnd);
       subpathHasLength = false;
       break;
 
     case PATHSEG_LINETO_ABS:
       segEnd = Point(mData[i], mData[i+1]);
       if (segEnd != segStart) {
         subpathHasLength = true;
-        builder->LineTo(segEnd);
+        aBuilder->LineTo(segEnd);
       }
       break;
 
     case PATHSEG_LINETO_REL:
       segEnd = segStart + Point(mData[i], mData[i+1]);
       if (segEnd != segStart) {
         subpathHasLength = true;
-        builder->LineTo(segEnd);
+        aBuilder->LineTo(segEnd);
       }
       break;
 
     case PATHSEG_CURVETO_CUBIC_ABS:
       cp1 = Point(mData[i], mData[i+1]);
       cp2 = Point(mData[i+2], mData[i+3]);
       segEnd = Point(mData[i+4], mData[i+5]);
       if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
         subpathHasLength = true;
-        builder->BezierTo(cp1, cp2, segEnd);
+        aBuilder->BezierTo(cp1, cp2, segEnd);
       }
       break;
 
     case PATHSEG_CURVETO_CUBIC_REL:
       cp1 = segStart + Point(mData[i], mData[i+1]);
       cp2 = segStart + Point(mData[i+2], mData[i+3]);
       segEnd = segStart + Point(mData[i+4], mData[i+5]);
       if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
         subpathHasLength = true;
-        builder->BezierTo(cp1, cp2, segEnd);
+        aBuilder->BezierTo(cp1, cp2, segEnd);
       }
       break;
 
     case PATHSEG_CURVETO_QUADRATIC_ABS:
       cp1 = Point(mData[i], mData[i+1]);
       // Convert quadratic curve to cubic curve:
       tcp1 = segStart + (cp1 - segStart) * 2 / 3;
       segEnd = Point(mData[i+2], mData[i+3]); // set before setting tcp2!
       tcp2 = cp1 + (segEnd - cp1) / 3;
       if (segEnd != segStart || segEnd != cp1) {
         subpathHasLength = true;
-        builder->BezierTo(tcp1, tcp2, segEnd);
+        aBuilder->BezierTo(tcp1, tcp2, segEnd);
       }
       break;
 
     case PATHSEG_CURVETO_QUADRATIC_REL:
       cp1 = segStart + Point(mData[i], mData[i+1]);
       // Convert quadratic curve to cubic curve:
       tcp1 = segStart + (cp1 - segStart) * 2 / 3;
       segEnd = segStart + Point(mData[i+2], mData[i+3]); // set before setting tcp2!
       tcp2 = cp1 + (segEnd - cp1) / 3;
       if (segEnd != segStart || segEnd != cp1) {
         subpathHasLength = true;
-        builder->BezierTo(tcp1, tcp2, segEnd);
+        aBuilder->BezierTo(tcp1, tcp2, segEnd);
       }
       break;
 
     case PATHSEG_ARC_ABS:
     case PATHSEG_ARC_REL:
     {
       Point radii(mData[i], mData[i+1]);
       segEnd = Point(mData[i+5], mData[i+6]);
       if (segType == PATHSEG_ARC_REL) {
         segEnd += segStart;
       }
       if (segEnd != segStart) {
         subpathHasLength = true;
         if (radii.x == 0.0f || radii.y == 0.0f) {
-          builder->LineTo(segEnd);
+          aBuilder->LineTo(segEnd);
         } else {
           nsSVGArcConverter converter(segStart, segEnd, radii, mData[i+2],
                                       mData[i+3] != 0, mData[i+4] != 0);
           while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) {
-            builder->BezierTo(cp1, cp2, segEnd);
+            aBuilder->BezierTo(cp1, cp2, segEnd);
           }
         }
       }
       break;
     }
 
     case PATHSEG_LINETO_HORIZONTAL_ABS:
       segEnd = Point(mData[i], segStart.y);
       if (segEnd != segStart) {
         subpathHasLength = true;
-        builder->LineTo(segEnd);
+        aBuilder->LineTo(segEnd);
       }
       break;
 
     case PATHSEG_LINETO_HORIZONTAL_REL:
       segEnd = segStart + Point(mData[i], 0.0f);
       if (segEnd != segStart) {
         subpathHasLength = true;
-        builder->LineTo(segEnd);
+        aBuilder->LineTo(segEnd);
       }
       break;
 
     case PATHSEG_LINETO_VERTICAL_ABS:
       segEnd = Point(segStart.x, mData[i]);
       if (segEnd != segStart) {
         subpathHasLength = true;
-        builder->LineTo(segEnd);
+        aBuilder->LineTo(segEnd);
       }
       break;
 
     case PATHSEG_LINETO_VERTICAL_REL:
       segEnd = segStart + Point(0.0f, mData[i]);
       if (segEnd != segStart) {
         subpathHasLength = true;
-        builder->LineTo(segEnd);
+        aBuilder->LineTo(segEnd);
       }
       break;
 
     case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
       cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segStart * 2 - cp2 : segStart;
       cp2 = Point(mData[i],   mData[i+1]);
       segEnd = Point(mData[i+2], mData[i+3]);
       if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
         subpathHasLength = true;
-        builder->BezierTo(cp1, cp2, segEnd);
+        aBuilder->BezierTo(cp1, cp2, segEnd);
       }
       break;
 
     case PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
       cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segStart * 2 - cp2 : segStart;
       cp2 = segStart + Point(mData[i], mData[i+1]);
       segEnd = segStart + Point(mData[i+2], mData[i+3]);
       if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
         subpathHasLength = true;
-        builder->BezierTo(cp1, cp2, segEnd);
+        aBuilder->BezierTo(cp1, cp2, segEnd);
       }
       break;
 
     case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
       cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segStart * 2 - cp1 : segStart;
       // Convert quadratic curve to cubic curve:
       tcp1 = segStart + (cp1 - segStart) * 2 / 3;
       segEnd = Point(mData[i], mData[i+1]); // set before setting tcp2!
       tcp2 = cp1 + (segEnd - cp1) / 3;
       if (segEnd != segStart || segEnd != cp1) {
         subpathHasLength = true;
-        builder->BezierTo(tcp1, tcp2, segEnd);
+        aBuilder->BezierTo(tcp1, tcp2, segEnd);
       }
       break;
 
     case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
       cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segStart * 2 - cp1 : segStart;
       // Convert quadratic curve to cubic curve:
       tcp1 = segStart + (cp1 - segStart) * 2 / 3;
       segEnd = segStart + Point(mData[i], mData[i+1]); // changed before setting tcp2!
       tcp2 = cp1 + (segEnd - cp1) / 3;
       if (segEnd != segStart || segEnd != cp1) {
         subpathHasLength = true;
-        builder->BezierTo(tcp1, tcp2, segEnd);
+        aBuilder->BezierTo(tcp1, tcp2, segEnd);
       }
       break;
 
     default:
       MOZ_ASSERT_UNREACHABLE("Bad path segment type");
       return nullptr; // according to spec we'd use everything up to the bad seg anyway
     }
 
@@ -498,17 +527,17 @@ SVGPathData::BuildPath(PathBuilder* buil
   }
 
   MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
   MOZ_ASSERT(prevSegType == segType,
              "prevSegType should be left at the final segType");
 
   MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
 
-  return builder->Finish();
+  return aBuilder->Finish();
 }
 
 already_AddRefed<Path>
 SVGPathData::BuildPathForMeasuring() const
 {
   // Since the path that we return will not be used for painting it doesn't
   // matter what we pass to CreatePathBuilder as aFillRule. Hawever, we do want
   // to pass something other than NS_STYLE_STROKE_LINECAP_SQUARE as
@@ -519,16 +548,219 @@ SVGPathData::BuildPathForMeasuring() con
 
   RefPtr<DrawTarget> drawTarget =
     gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
   RefPtr<PathBuilder> builder =
     drawTarget->CreatePathBuilder(FillRule::FILL_WINDING);
   return BuildPath(builder, NS_STYLE_STROKE_LINECAP_BUTT, 0);
 }
 
+// We could simplify this function because this is only used by CSS motion path
+// and clip-path, which don't render the SVG Path. i.e. The returned path is
+// used as a reference.
+/* static */ already_AddRefed<Path>
+SVGPathData::BuildPath(const nsTArray<StylePathCommand>& aPath,
+                       PathBuilder* aBuilder,
+                       uint8_t aStrokeLineCap,
+                       Float aStrokeWidth)
+{
+  if (aPath.IsEmpty() || !aPath[0].IsMoveTo()) {
+    return nullptr; // paths without an initial moveto are invalid
+  }
+
+  auto toGfxPoint = [](const StyleCoordPair& aPair) {
+    return Point(aPair._0, aPair._1);
+  };
+
+  auto isCubicType = [](StylePathCommand::Tag aType) {
+    return aType == StylePathCommand::Tag::CurveTo ||
+           aType == StylePathCommand::Tag::SmoothCurveTo;
+  };
+
+  auto isQuadraticType = [](StylePathCommand::Tag aType) {
+    return aType == StylePathCommand::Tag::QuadBezierCurveTo ||
+           aType == StylePathCommand::Tag::SmoothQuadBezierCurveTo;
+  };
+
+  bool hasLineCaps = aStrokeLineCap != NS_STYLE_STROKE_LINECAP_BUTT;
+  bool subpathHasLength = false;  // visual length
+  bool subpathContainsNonMoveTo = false;
+
+  StylePathCommand::Tag segType = StylePathCommand::Tag::Unknown;
+  StylePathCommand::Tag prevSegType = StylePathCommand::Tag::Unknown;
+  Point pathStart(0.0, 0.0); // start point of [sub]path
+  Point segStart(0.0, 0.0);
+  Point segEnd;
+  Point cp1, cp2;            // previous bezier's control points
+  Point tcp1, tcp2;          // temporaries
+
+  // Regarding cp1 and cp2: If the previous segment was a cubic bezier curve,
+  // then cp2 is its second control point. If the previous segment was a
+  // quadratic curve, then cp1 is its (only) control point.
+
+  for (const StylePathCommand& cmd: aPath) {
+    segType = cmd.tag;
+    switch (segType) {
+      case StylePathCommand::Tag::ClosePath:
+        // set this early to allow drawing of square caps for "M{x},{y} Z":
+        subpathContainsNonMoveTo = true;
+        MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
+        segEnd = pathStart;
+        aBuilder->Close();
+        break;
+      case StylePathCommand::Tag::MoveTo: {
+        MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
+        const Point& p = toGfxPoint(cmd.move_to.point);
+        pathStart = segEnd = cmd.move_to.absolute ? p : segStart + p;
+        aBuilder->MoveTo(segEnd);
+        subpathHasLength = false;
+        break;
+      }
+      case StylePathCommand::Tag::LineTo: {
+        const Point& p = toGfxPoint(cmd.line_to.point);
+        segEnd = cmd.line_to.absolute ? p : segStart + p;
+        if (segEnd != segStart) {
+          subpathHasLength = true;
+          aBuilder->LineTo(segEnd);
+        }
+        break;
+      }
+      case StylePathCommand::Tag::CurveTo:
+        cp1 = toGfxPoint(cmd.curve_to.control1);
+        cp2 = toGfxPoint(cmd.curve_to.control2);
+        segEnd = toGfxPoint(cmd.curve_to.point);
+
+        if (!cmd.curve_to.absolute) {
+          cp1 += segStart;
+          cp2 += segStart;
+          segEnd += segStart;
+        }
+
+        if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
+          subpathHasLength = true;
+          aBuilder->BezierTo(cp1, cp2, segEnd);
+        }
+        break;
+
+      case StylePathCommand::Tag::QuadBezierCurveTo:
+        cp1 = toGfxPoint(cmd.quad_bezier_curve_to.control1);
+        segEnd = toGfxPoint(cmd.quad_bezier_curve_to.point);
+
+        if (!cmd.quad_bezier_curve_to.absolute) {
+          cp1 += segStart;
+          segEnd += segStart; // set before setting tcp2!
+        }
+
+        // Convert quadratic curve to cubic curve:
+        tcp1 = segStart + (cp1 - segStart) * 2 / 3;
+        tcp2 = cp1 + (segEnd - cp1) / 3;
+
+        if (segEnd != segStart || segEnd != cp1) {
+          subpathHasLength = true;
+          aBuilder->BezierTo(tcp1, tcp2, segEnd);
+        }
+        break;
+
+      case StylePathCommand::Tag::EllipticalArc: {
+        const auto& arc = cmd.elliptical_arc;
+        Point radii(arc.rx, arc.ry);
+        segEnd = toGfxPoint(arc.point);
+        if (!arc.absolute) {
+          segEnd += segStart;
+        }
+        if (segEnd != segStart) {
+          subpathHasLength = true;
+          if (radii.x == 0.0f || radii.y == 0.0f) {
+            aBuilder->LineTo(segEnd);
+          } else {
+            nsSVGArcConverter converter(segStart, segEnd, radii, arc.angle,
+                                        arc.large_arc_flag, arc.sweep_flag);
+            while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) {
+              aBuilder->BezierTo(cp1, cp2, segEnd);
+            }
+          }
+        }
+        break;
+      }
+      case StylePathCommand::Tag::HorizontalLineTo:
+        if (cmd.horizontal_line_to.absolute) {
+          segEnd = Point(cmd.horizontal_line_to.x, segStart.y);
+        } else {
+          segEnd = segStart + Point(cmd.horizontal_line_to.x, 0.0f);
+        }
+
+        if (segEnd != segStart) {
+          subpathHasLength = true;
+          aBuilder->LineTo(segEnd);
+        }
+        break;
+
+      case StylePathCommand::Tag::VerticalLineTo:
+        if (cmd.vertical_line_to.absolute) {
+          segEnd = Point(segStart.x, cmd.vertical_line_to.y);
+        } else {
+          segEnd = segStart + Point(0.0f, cmd.vertical_line_to.y);
+        }
+
+        if (segEnd != segStart) {
+          subpathHasLength = true;
+          aBuilder->LineTo(segEnd);
+        }
+        break;
+
+      case StylePathCommand::Tag::SmoothCurveTo:
+        cp1 = isCubicType(prevSegType) ? segStart * 2 - cp2 : segStart;
+        cp2 = toGfxPoint(cmd.smooth_curve_to.control2);
+        segEnd = toGfxPoint(cmd.smooth_curve_to.point);
+
+        if (!cmd.smooth_curve_to.absolute) {
+          cp2 += segStart;
+          segEnd += segStart;
+        }
+
+        if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
+          subpathHasLength = true;
+          aBuilder->BezierTo(cp1, cp2, segEnd);
+        }
+        break;
+
+      case StylePathCommand::Tag::SmoothQuadBezierCurveTo: {
+        cp1 = isQuadraticType(prevSegType) ? segStart * 2 - cp1 : segStart;
+        // Convert quadratic curve to cubic curve:
+        tcp1 = segStart + (cp1 - segStart) * 2 / 3;
+
+        const Point& p = toGfxPoint(cmd.smooth_quad_bezier_curve_to.point);
+        // set before setting tcp2!
+        segEnd = cmd.smooth_quad_bezier_curve_to.absolute ? p : segStart + p;
+        tcp2 = cp1 + (segEnd - cp1) / 3;
+
+        if (segEnd != segStart || segEnd != cp1) {
+          subpathHasLength = true;
+          aBuilder->BezierTo(tcp1, tcp2, segEnd);
+        }
+        break;
+      }
+      case StylePathCommand::Tag::Unknown:
+        MOZ_ASSERT_UNREACHABLE("Unacceptable path segment type");
+        return nullptr;
+    }
+
+    subpathContainsNonMoveTo = !IsMoveto(segType);
+    prevSegType = segType;
+    segStart = segEnd;
+  }
+
+  MOZ_ASSERT(prevSegType == segType,
+             "prevSegType should be left at the final segType");
+
+  MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
+
+  return aBuilder->Finish();
+}
+
 static double
 AngleOfVector(const Point& aVector)
 {
   // C99 says about atan2 "A domain error may occur if both arguments are
   // zero" and "On a domain error, the function returns an implementation-
   // defined value". In the case of atan2 the implementation-defined value
   // seems to commonly be zero, but it could just as easily be a NaN value.
   // We specifically want zero in this case, hence the check:
--- a/dom/svg/SVGPathData.h
+++ b/dom/svg/SVGPathData.h
@@ -164,16 +164,26 @@ public:
    * ApproximateZeroLengthSubpathSquareCaps can insert if we have square-caps.
    * See the comment for that function for more info on that.
    */
   already_AddRefed<Path> BuildPathForMeasuring() const;
 
   already_AddRefed<Path> BuildPath(PathBuilder* aBuilder,
                                uint8_t aCapStyle,
                                Float aStrokeWidth) const;
+  /**
+   * This function tries to build the path by an array of StylePathCommand,
+   * which is generated by cbindgen from Rust (see ServoStyleConsts.h).
+   * Basically, this is a variant of the above BuildPath() functions.
+   */
+  static already_AddRefed<Path>
+  BuildPath(const nsTArray<StylePathCommand>& aPath,
+            PathBuilder* aBuilder,
+            uint8_t aCapStyle,
+            Float aStrokeWidth);
 
   const_iterator begin() const { return mData.Elements(); }
   const_iterator end() const { return mData.Elements() + mData.Length(); }
 
   // memory reporting methods
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
--- a/dom/svg/moz.build
+++ b/dom/svg/moz.build
@@ -76,16 +76,17 @@ EXPORTS.mozilla.dom += [
     'SVGImageElement.h',
     'SVGIRect.h',
     'SVGLineElement.h',
     'SVGMarkerElement.h',
     'SVGMaskElement.h',
     'SVGMatrix.h',
     'SVGMetadataElement.h',
     'SVGMPathElement.h',
+    'SVGPathData.h',
     'SVGPathElement.h',
     'SVGPatternElement.h',
     'SVGPolygonElement.h',
     'SVGPolylineElement.h',
     'SVGRect.h',
     'SVGRectElement.h',
     'SVGScriptElement.h',
     'SVGSetElement.h',
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -2557,20 +2557,21 @@ CommonPerformPromiseAllRace(JSContext *c
                     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
                     return false;
                 }
                 ar.emplace(cx, nextPromiseObj);
                 if (!cx->compartment()->wrap(cx, &blockedPromise))
                     return false;
             }
 
-            // If either the object to depend on or the object that gets
-            // blocked isn't a, maybe-wrapped, Promise instance, we ignore it.
-            // All this does is lose some small amount of debug information in
-            // scenarios that are highly unlikely to occur in useful code.
+            // If either the object to depend on (`nextPromiseObj`) or the
+            // object that gets blocked (`resultPromise`) isn't a,
+            // maybe-wrapped, Promise instance, we ignore it. All this does is
+            // lose some small amount of debug information in scenarios that
+            // are highly unlikely to occur in useful code.
             if (nextPromiseObj->is<PromiseObject>() && resultPromise->is<PromiseObject>()) {
                 Handle<PromiseObject*> promise = nextPromiseObj.as<PromiseObject>();
                 if (!AddDummyPromiseReactionForDebugger(cx, promise, blockedPromise))
                     return false;
             }
         }
     }
 }
@@ -2994,34 +2995,68 @@ Promise_static_species(JSContext* cx, un
     // Step 1: Return the this value.
     args.rval().set(args.thisv());
     return true;
 }
 
 // ES2016, 25.4.5.1, implemented in Promise.js.
 
 enum class IncumbentGlobalObject {
-    Yes, No
+    // Do not use the incumbent global, this is a special case used by the
+    // debugger.
+    No,
+
+    // Use incumbent global, this is the normal operation.
+    Yes
 };
 
 static PromiseReactionRecord*
 NewReactionRecord(JSContext* cx, Handle<PromiseCapability> resultCapability,
                   HandleValue onFulfilled, HandleValue onRejected,
                   IncumbentGlobalObject incumbentGlobalObjectOption)
 {
-    // Either of the following conditions must be met:
-    //   * resultCapability.promise is a PromiseObject
-    //   * resultCapability.resolve and resultCapability.resolve are callable
-    // except for Async Generator, there resultPromise can be nullptr.
 #ifdef DEBUG
-    if (resultCapability.promise() && !resultCapability.promise()->is<PromiseObject>()) {
-        MOZ_ASSERT(resultCapability.resolve());
-        MOZ_ASSERT(IsCallable(resultCapability.resolve()));
-        MOZ_ASSERT(resultCapability.reject());
-        MOZ_ASSERT(IsCallable(resultCapability.reject()));
+    if (resultCapability.promise()) {
+        if (incumbentGlobalObjectOption == IncumbentGlobalObject::Yes) {
+            if (resultCapability.promise()->is<PromiseObject>()) {
+                // If `resultCapability.promise` is a Promise object,
+                // `resultCapability.{resolve,reject}` may be optimized out,
+                // but if they're not, they should be callable.
+                MOZ_ASSERT_IF(resultCapability.resolve(),
+                              IsCallable(resultCapability.resolve()));
+                MOZ_ASSERT_IF(resultCapability.reject(),
+                              IsCallable(resultCapability.reject()));
+            } else {
+                // If `resultCapability.promise` is a non-Promise object
+                // (including wrapped Promise object),
+                // `resultCapability.{resolve,reject}` should be callable.
+                MOZ_ASSERT(resultCapability.resolve());
+                MOZ_ASSERT(IsCallable(resultCapability.resolve()));
+                MOZ_ASSERT(resultCapability.reject());
+                MOZ_ASSERT(IsCallable(resultCapability.reject()));
+            }
+        } else {
+            // For debugger usage, `resultCapability.promise` should be a
+            // maybe-wrapped Promise object. The other fields are not used.
+            //
+            // This is the only case where we allow `resolve` and `reject` to
+            // be null when the `promise` field is not a PromiseObject.
+            JSObject* unwrappedPromise = UncheckedUnwrap(resultCapability.promise());
+            MOZ_ASSERT(unwrappedPromise->is<PromiseObject>());
+            MOZ_ASSERT(!resultCapability.resolve());
+            MOZ_ASSERT(!resultCapability.reject());
+        }
+    } else {
+        // `resultCapability.promise` is null for the following cases:
+        //   * resulting Promise is known to be unused
+        //   * Async Generator
+        // In any case, other fields are also not used.
+        MOZ_ASSERT(!resultCapability.resolve());
+        MOZ_ASSERT(!resultCapability.reject());
+        MOZ_ASSERT(incumbentGlobalObjectOption == IncumbentGlobalObject::Yes);
     }
 #endif
 
     // Ensure the onFulfilled handler has the expected type.
     MOZ_ASSERT(onFulfilled.isInt32() || onFulfilled.isObjectOrNull());
     MOZ_ASSERT_IF(onFulfilled.isObject(), IsCallable(onFulfilled));
     MOZ_ASSERT_IF(onFulfilled.isInt32(),
                   0 <= onFulfilled.toInt32() && onFulfilled.toInt32() < PromiseHandlerLimit);
@@ -4013,16 +4048,19 @@ AddPromiseReaction(JSContext* cx, Handle
 
 static MOZ_MUST_USE bool
 AddDummyPromiseReactionForDebugger(JSContext* cx, Handle<PromiseObject*> promise,
                                    HandleObject dependentPromise)
 {
     if (promise->state() != JS::PromiseState::Pending)
         return true;
 
+    // `dependentPromise` should be a maybe-wrapped Promise.
+    MOZ_ASSERT(UncheckedUnwrap(dependentPromise)->is<PromiseObject>());
+
     // Leave resolve and reject as null.
     Rooted<PromiseCapability> capability(cx);
     capability.promise().set(dependentPromise);
 
     Rooted<PromiseReactionRecord*> reaction(cx, NewReactionRecord(cx, capability,
                                                                   NullHandleValue, NullHandleValue,
                                                                   IncumbentGlobalObject::No));
     if (!reaction)
--- a/js/src/ds/Bitmap.cpp
+++ b/js/src/ds/Bitmap.cpp
@@ -23,17 +23,17 @@ SparseBitmap::sizeOfExcludingThis(mozill
     for (Data::Range r(data.all()); !r.empty(); r.popFront())
         size += mallocSizeOf(r.front().value());
     return size;
 }
 
 SparseBitmap::BitBlock&
 SparseBitmap::createBlock(Data::AddPtr p, size_t blockId, AutoEnterOOMUnsafeRegion& oomUnsafe)
 {
-    MOZ_ASSERT(!p && p.isValid());
+    MOZ_ASSERT(!p);
     BitBlock* block = js_new<BitBlock>();
     if (!block || !data.add(p, blockId, block))
         oomUnsafe.crash("Bitmap OOM");
     std::fill(block->begin(), block->end(), 0);
     return *block;
 }
 
 bool
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/auto-regress/bug1483188.js
@@ -0,0 +1,13 @@
+var P = newGlobal().eval(`
+(class extends Promise {
+    static resolve(o) {
+        return o;
+    }
+});
+`);
+var alwaysPending = new Promise(() => {});
+function neverCalled() {
+    assertEq(true, false);
+}
+P.race([alwaysPending]).then(neverCalled, neverCalled);
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1483182.js
@@ -0,0 +1,15 @@
+var lfLogBuffer = `
+  function testOuterForInVar() {
+    return eval("for (var x in {}); (function() { return delete x; })");
+  }
+  testOuterForInVar();
+`;
+loadFile(lfLogBuffer);
+loadFile(lfLogBuffer);
+function loadFile(lfVarx) {
+    try {
+        oomTest(function() {
+            eval(lfVarx);
+        });
+    } catch (lfVare) {}
+}
--- a/js/src/jsapi-tests/testHashTable.cpp
+++ b/js/src/jsapi-tests/testHashTable.cpp
@@ -443,18 +443,44 @@ BEGIN_TEST(testHashLazyStorage)
 
     CHECK(set.putNew(1));
     CHECK(set.capacity() == minCap);
 
     set.clear();
     set.compact();
     CHECK(set.capacity() == 0);
 
-    // lookupForAdd() instantiates, even if not followed by add().
-    set.lookupForAdd(1);
+    auto p = set.lookupForAdd(1);
+    CHECK(set.capacity() == 0);
+    CHECK(set.add(p, 1));
+    CHECK(set.capacity() == minCap);
+    CHECK(set.has(1));
+
+    set.clear();
+    set.compact();
+    CHECK(set.capacity() == 0);
+
+    p = set.lookupForAdd(1);
+    CHECK(set.putNew(2));
+    CHECK(set.capacity() == minCap);
+    CHECK(set.relookupOrAdd(p, 1, 1));
+    CHECK(set.capacity() == minCap);
+    CHECK(set.has(1));
+
+    set.clear();
+    set.compact();
+    CHECK(set.capacity() == 0);
+
+    CHECK(set.putNew(1));
+    p = set.lookupForAdd(1);
+    set.clear();
+    set.compact();
+    CHECK(set.count() == 0);
+    CHECK(set.relookupOrAdd(p, 1, 1));
+    CHECK(set.count() == 1);
     CHECK(set.capacity() == minCap);
 
     set.clear();
     set.compact();
     CHECK(set.capacity() == 0);
 
     CHECK(set.reserve(0));          // a no-op
     CHECK(set.capacity() == 0);
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -71,16 +71,17 @@
 #include "mozilla/dom/AnonymousContent.h"
 #include "mozilla/dom/HTMLBodyElement.h"
 #include "mozilla/dom/HTMLMediaElementBinding.h"
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "mozilla/dom/HTMLImageElement.h"
 #include "mozilla/dom/DOMRect.h"
 #include "mozilla/dom/DOMStringList.h"
 #include "mozilla/dom/KeyframeEffect.h"
+#include "mozilla/dom/SVGPathData.h"
 #include "mozilla/layers/APZCCallbackHelper.h"
 #include "imgIRequest.h"
 #include "nsIImageLoadingContent.h"
 #include "nsCOMPtr.h"
 #include "nsCSSProps.h"
 #include "nsListControlFrame.h"
 #include "mozilla/dom/Element.h"
 #include "nsCanvasFrame.h"
@@ -10261,8 +10262,99 @@ nsLayoutUtils::StyleForScrollbar(nsIFram
              "Root element is the only case for this fallback "
              "path to be triggered");
   RefPtr<ComputedStyle> style =
     pc->StyleSet()->ResolveServoStyle(*content->AsElement());
   // Dropping the strong reference is fine because the style should be
   // held strongly by the element.
   return style.get();
 }
+
+static float
+ResolveTransformOrigin(const nsStyleCoord& aCoord,
+                       TransformReferenceBox& aRefBox,
+                       TransformReferenceBox::DimensionGetter aGetter)
+{
+  float result = 0.0;
+  const float scale = mozilla::AppUnitsPerCSSPixel();
+  if (aCoord.GetUnit() == eStyleUnit_Calc) {
+    const nsStyleCoord::Calc *calc = aCoord.GetCalcValue();
+    result = NSAppUnitsToFloatPixels((aRefBox.*aGetter)(), scale) *
+               calc->mPercent +
+               NSAppUnitsToFloatPixels(calc->mLength, scale);
+  } else if (aCoord.GetUnit() == eStyleUnit_Percent) {
+    result = NSAppUnitsToFloatPixels((aRefBox.*aGetter)(), scale) *
+               aCoord.GetPercentValue();
+  } else {
+    MOZ_ASSERT(aCoord.GetUnit() == eStyleUnit_Coord, "unexpected unit");
+    result = NSAppUnitsToFloatPixels(aCoord.GetCoordValue(), scale);
+  }
+  return result;
+}
+
+/* static */ Maybe<MotionPathData>
+nsLayoutUtils::ResolveMotionPath(const nsIFrame* aFrame)
+{
+  MOZ_ASSERT(aFrame);
+
+  const nsStyleDisplay* display = aFrame->StyleDisplay();
+  if (!display->mMotion || !display->mMotion->HasPath()) {
+    return Nothing();
+  }
+
+  const UniquePtr<StyleMotion>& motion = display->mMotion;
+  // Bug 1429299 - Implement offset-distance for motion path. For now, we use
+  // the default value, i.e. 0%.
+  float distance = 0.0;
+  float angle = 0.0;
+  Point point;
+  if (motion->OffsetPath().GetType() == StyleShapeSourceType::Path) {
+    // Build the path and compute the point and angle for creating the
+    // equivalent translate and rotate.
+    // Here we only need to build a valid path for motion path, so
+    // using the default values of stroke-width, stoke-linecap, and fill-rule
+    // is fine for now because what we want is get the point and its normal
+    // vector along the path, instead of rendering it.
+    // FIXME: Bug 1484780, we should cache the path to avoid rebuilding it here
+    // at every restyle. (Caching the path avoids the cost of flattening it
+    // again each time.)
+    RefPtr<DrawTarget> drawTarget =
+      gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+    RefPtr<PathBuilder> builder =
+      drawTarget->CreatePathBuilder(FillRule::FILL_WINDING);
+    RefPtr<gfx::Path> gfxPath =
+      SVGPathData::BuildPath(motion->OffsetPath().GetPath()->Path(),
+                             builder,
+                             NS_STYLE_STROKE_LINECAP_BUTT,
+                             0.0);
+    if (!gfxPath) {
+      return Nothing();
+    }
+    float pathLength = gfxPath->ComputeLength();
+    float computedDistance = distance * pathLength;
+    Point tangent;
+    point = gfxPath->ComputePointAtLength(computedDistance, &tangent);
+    // Bug 1429301 - Implement offset-rotate for motion path.
+    // After implement offset-rotate, |angle| will be adjusted more.
+    // For now, the default value of offset-rotate is "auto", so we use the
+    // directional tangent vector.
+    angle = atan2(tangent.y, tangent.x);
+  } else {
+    // Bug 1480665: Implement ray() function.
+    NS_WARNING("Unsupported offset-path value");
+  }
+
+  // Compute the offset for motion path translate.
+  // We need to resolve transform-origin here to calculate the correct path
+  // translate. (i.e. Center transform-origin on the path.)
+  TransformReferenceBox refBox(aFrame);
+  Point origin(
+    ResolveTransformOrigin(display->mTransformOrigin[0],
+                           refBox,
+                           &TransformReferenceBox::Width),
+    ResolveTransformOrigin(display->mTransformOrigin[1],
+                           refBox,
+                           &TransformReferenceBox::Height)
+  );
+  // Bug 1186329: the translate parameters will be adjusted more after we
+  // implement offset-position and offset-anchor.
+  return Some(MotionPathData { point - origin, angle });
+}
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -114,16 +114,21 @@ struct DisplayPortMarginsPropertyData {
                                  uint32_t aPriority)
     : mMargins(aMargins)
     , mPriority(aPriority)
   {}
   ScreenMargin mMargins;
   uint32_t mPriority;
 };
 
+struct MotionPathData {
+  gfx::Point mTranslate;
+  float mRotate;
+};
+
 } // namespace mozilla
 
 // For GetDisplayPort
 enum class RelativeTo {
   ScrollPort,
   ScrollFrame
 };
 
@@ -3108,16 +3113,22 @@ public:
   }
 
   /**
    * Get the computed style from which the scrollbar style should be
    * used for the given scrollbar part frame.
    */
   static ComputedStyle* StyleForScrollbar(nsIFrame* aScrollbarPart);
 
+  /**
+   * Generate the motion path transform result.
+   **/
+  static mozilla::Maybe<mozilla::MotionPathData>
+  ResolveMotionPath(const nsIFrame* aFrame);
+
 private:
   static uint32_t sFontSizeInflationEmPerLine;
   static uint32_t sFontSizeInflationMinTwips;
   static uint32_t sFontSizeInflationLineThreshold;
   static int32_t  sFontSizeInflationMappingIntercept;
   static uint32_t sFontSizeInflationMaxRatio;
   static bool sFontSizeInflationForceEnabled;
   static bool sFontSizeInflationDisabledInMasterProcess;
--- a/layout/generic/nsFloatManager.cpp
+++ b/layout/generic/nsFloatManager.cpp
@@ -2404,16 +2404,20 @@ nsFloatManager::FloatInfo::FloatInfo(nsI
     case StyleShapeSourceType::None:
       // No need to create shape info.
       return;
 
     case StyleShapeSourceType::URL:
       MOZ_ASSERT_UNREACHABLE("shape-outside doesn't have URL source type!");
       return;
 
+    case StyleShapeSourceType::Path:
+      MOZ_ASSERT_UNREACHABLE("shape-outside doesn't have Path source type!");
+      return;
+
     case StyleShapeSourceType::Image: {
       float shapeImageThreshold = styleDisplay->mShapeImageThreshold;
       mShapeInfo = ShapeInfo::CreateImageShape(shapeOutside.GetShapeImage(),
                                                shapeImageThreshold,
                                                shapeMargin,
                                                mFrame,
                                                aMarginRect,
                                                aWM,
--- a/layout/painting/ActiveLayerTracker.cpp
+++ b/layout/painting/ActiveLayerTracker.cpp
@@ -244,32 +244,40 @@ ActiveLayerTracker::TransferActivityToFr
   aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
   aFrame->SetProperty(LayerActivityProperty(), layerActivity);
 }
 
 static void
 IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame, LayerActivity* aActivity)
 {
   const nsStyleDisplay* display = aFrame->StyleDisplay();
-  RefPtr<nsCSSValueSharedList> transformList = display->GetCombinedTransform();
-  if (!transformList) {
+  if (!display->mSpecifiedTransform &&
+      !display->HasIndividualTransform() &&
+      !(display->mMotion && display->mMotion->HasPath())) {
     // The transform was removed.
     aActivity->mPreviousTransformScale = Nothing();
-    IncrementMutationCount(&aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
+    IncrementMutationCount(
+      &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
     return;
   }
 
   // Compute the new scale due to the CSS transform property.
   bool dummyBool;
   nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame);
-  Matrix4x4 transform =
-    nsStyleTransformMatrix::ReadTransforms(transformList->mHead,
-                                           refBox,
-                                           AppUnitsPerCSSPixel(),
-                                           &dummyBool);
+  Matrix4x4 transform = nsStyleTransformMatrix::ReadTransforms(
+      display->mIndividualTransform
+        ? display->mIndividualTransform->mHead
+        : nullptr,
+      nsLayoutUtils::ResolveMotionPath(aFrame),
+      display->mSpecifiedTransform
+        ? display->mSpecifiedTransform->mHead
+        : nullptr,
+      refBox,
+      AppUnitsPerCSSPixel(),
+      &dummyBool);
   Matrix transform2D;
   if (!transform.Is2D(&transform2D)) {
     // We don't attempt to handle 3D transforms; just assume the scale changed.
     aActivity->mPreviousTransformScale = Nothing();
     IncrementMutationCount(&aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
     return;
   }
 
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -7844,21 +7844,24 @@ nsDisplayTransform::ComputePerspectiveMa
 
   aOutMatrix._34 =
     -1.0 / NSAppUnitsToFloatPixels(perspective, aAppUnitsPerPixel);
 
   aOutMatrix.ChangeBasis(Point3D(perspectiveOrigin.x, perspectiveOrigin.y, 0));
   return true;
 }
 
-nsDisplayTransform::FrameTransformProperties::FrameTransformProperties(const nsIFrame* aFrame,
-                                                                       float aAppUnitsPerPixel,
-                                                                       const nsRect* aBoundsOverride)
+nsDisplayTransform::FrameTransformProperties::FrameTransformProperties(
+  const nsIFrame* aFrame,
+  float aAppUnitsPerPixel,
+  const nsRect* aBoundsOverride)
   : mFrame(aFrame)
-  , mTransformList(aFrame->StyleDisplay()->GetCombinedTransform())
+  , mIndividualTransformList(aFrame->StyleDisplay()->mIndividualTransform)
+  , mMotion(nsLayoutUtils::ResolveMotionPath(aFrame))
+  , mTransformList(aFrame->StyleDisplay()->mSpecifiedTransform)
   , mToTransformOrigin(GetDeltaToTransformOrigin(aFrame, aAppUnitsPerPixel, aBoundsOverride))
 {
 }
 
 /* Wraps up the transform matrix in a change-of-basis matrix pair that
  * translates from local coordinate space to transform coordinate space, then
  * hands it back.
  */
@@ -7917,20 +7920,26 @@ nsDisplayTransform::GetResultingTransfor
   // Call IsSVGTransformed() regardless of the value of
   // disp->mSpecifiedTransform, since we still need any
   // parentsChildrenOnlyTransform.
   Matrix svgTransform, parentsChildrenOnlyTransform;
   bool hasSVGTransforms =
     frame && frame->IsSVGTransformed(&svgTransform,
                                      &parentsChildrenOnlyTransform);
   /* Transformed frames always have a transform, or are preserving 3d (and might still have perspective!) */
-  if (aProperties.mTransformList) {
-    result = nsStyleTransformMatrix::ReadTransforms(aProperties.mTransformList->mHead,
-                                                    refBox, aAppUnitsPerPixel,
-                                                    &dummyBool);
+  if (aProperties.HasTransform()) {
+    result = nsStyleTransformMatrix::ReadTransforms(
+        aProperties.mIndividualTransformList
+          ? aProperties.mIndividualTransformList->mHead
+          : nullptr,
+        aProperties.mMotion,
+        aProperties.mTransformList
+          ? aProperties.mTransformList->mHead
+          : nullptr,
+        refBox, aAppUnitsPerPixel, &dummyBool);
   } else if (hasSVGTransforms) {
     // Correct the translation components for zoom:
     float pixelsPerCSSPx = AppUnitsPerCSSPixel() /
                              aAppUnitsPerPixel;
     svgTransform._31 *= pixelsPerCSSPx;
     svgTransform._32 *= pixelsPerCSSPx;
     result = Matrix4x4::From2D(svgTransform);
   }
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -63,16 +63,17 @@ class nsIScrollableFrame;
 class nsSubDocumentFrame;
 class nsDisplayCompositorHitTestInfo;
 class nsDisplayScrollInfoLayer;
 class nsCaret;
 enum class nsDisplayOwnLayerFlags;
 
 namespace mozilla {
 class FrameLayerBuilder;
+struct MotionPathData;
 namespace layers {
 class Layer;
 class ImageLayer;
 class ImageContainer;
 class StackingContextHelper;
 class WebRenderCommand;
 class WebRenderScrollData;
 class WebRenderLayerScrollData;
@@ -6572,25 +6573,37 @@ public:
                                        float aAppUnitsPerPixel,
                                        Matrix4x4& aOutMatrix);
 
   struct FrameTransformProperties
   {
     FrameTransformProperties(const nsIFrame* aFrame,
                              float aAppUnitsPerPixel,
                              const nsRect* aBoundsOverride);
+    // This constructor is used on the compositor (for animations).
+    // Bug 1186329, Bug 1425837, If we want to support compositor animationsf
+    // or individual transforms and motion path, we may need to update this.
+    // For now, let mIndividualTransformList and mMotion as nullptr and
+    // Nothing().
     FrameTransformProperties(RefPtr<const nsCSSValueSharedList>&&
                                aTransformList,
                              const Point3D& aToTransformOrigin)
       : mFrame(nullptr)
       , mTransformList(std::move(aTransformList))
       , mToTransformOrigin(aToTransformOrigin)
     {}
 
+    bool HasTransform() const
+    {
+      return mIndividualTransformList || mTransformList || mMotion.isSome();
+    }
+
     const nsIFrame* mFrame;
+    const RefPtr<const nsCSSValueSharedList> mIndividualTransformList;
+    const mozilla::Maybe<mozilla::MotionPathData> mMotion;
     const RefPtr<const nsCSSValueSharedList> mTransformList;
     const Point3D mToTransformOrigin;
   };
 
   /**
    * Given a frame with the -moz-transform property or an SVG transform,
    * returns the transformation matrix for that frame.
    *
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -1946,16 +1946,45 @@ Gecko_NewBasicShape(mozilla::StyleShapeS
 
 void
 Gecko_NewShapeImage(mozilla::StyleShapeSource* aShape)
 {
   aShape->SetShapeImage(MakeUnique<nsStyleImage>());
 }
 
 void
+Gecko_NewStyleSVGPath(mozilla::StyleShapeSource* aShape)
+{
+  MOZ_ASSERT(aShape);
+  aShape->SetPath(MakeUnique<mozilla::StyleSVGPath>());
+}
+
+void
+Gecko_SetStyleMotion(UniquePtr<mozilla::StyleMotion>* aMotion,
+                     mozilla::StyleMotion* aValue)
+{
+  MOZ_ASSERT(aMotion);
+  aMotion->reset(aValue);
+}
+
+mozilla::StyleMotion*
+Gecko_NewStyleMotion()
+{
+  return new StyleMotion();
+}
+
+void
+Gecko_CopyStyleMotions(mozilla::UniquePtr<mozilla::StyleMotion>* aMotion,
+                       const mozilla::StyleMotion* aOther)
+{
+  MOZ_ASSERT(aMotion);
+  *aMotion = aOther ? MakeUnique<StyleMotion>(*aOther) : nullptr;
+}
+
+void
 Gecko_ResetFilters(nsStyleEffects* effects, size_t new_len)
 {
   effects->mFilters.Clear();
   effects->mFilters.SetLength(new_len);
 }
 
 void
 Gecko_CopyFiltersFrom(nsStyleEffects* aSrc, nsStyleEffects* aDest)
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -519,16 +519,22 @@ void Gecko_SetStyleCoordCalcValue(nsStyl
 
 void Gecko_CopyShapeSourceFrom(mozilla::StyleShapeSource* dst, const mozilla::StyleShapeSource* src);
 
 void Gecko_DestroyShapeSource(mozilla::StyleShapeSource* shape);
 void Gecko_NewBasicShape(mozilla::StyleShapeSource* shape,
                          mozilla::StyleBasicShapeType type);
 void Gecko_NewShapeImage(mozilla::StyleShapeSource* shape);
 void Gecko_StyleShapeSource_SetURLValue(mozilla::StyleShapeSource* shape, mozilla::css::URLValue* uri);
+void Gecko_NewStyleSVGPath(mozilla::StyleShapeSource* shape);
+void Gecko_SetStyleMotion(mozilla::UniquePtr<mozilla::StyleMotion>* aMotion,
+                          mozilla::StyleMotion* aValue);
+mozilla::StyleMotion* Gecko_NewStyleMotion();
+void Gecko_CopyStyleMotions(mozilla::UniquePtr<mozilla::StyleMotion>* motion,
+                            const mozilla::StyleMotion* other);
 
 void Gecko_ResetFilters(nsStyleEffects* effects, size_t new_len);
 void Gecko_CopyFiltersFrom(nsStyleEffects* aSrc, nsStyleEffects* aDest);
 void Gecko_nsStyleFilter_SetURLValue(nsStyleFilter* effects, mozilla::css::URLValue* uri);
 
 void Gecko_nsStyleSVGPaint_CopyFrom(nsStyleSVGPaint* dest, const nsStyleSVGPaint* src);
 void Gecko_nsStyleSVGPaint_SetURLValue(nsStyleSVGPaint* paint, mozilla::css::URLValue* uri);
 void Gecko_nsStyleSVGPaint_Reset(nsStyleSVGPaint* paint);
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -468,16 +468,17 @@ structs-types = [
     "mozilla::dom::ShadowRoot",
     "mozilla::AnonymousCounterStyle",
     "mozilla::AtomArray",
     "mozilla::FontStretch",
     "mozilla::FontSlantStyle",
     "mozilla::FontWeight",
     "mozilla::MallocSizeOf",
     "mozilla::OriginFlags",
+    "mozilla::StyleMotion",
     "mozilla::UniquePtr",
     "mozilla::StyleDisplayMode",
     "ServoRawOffsetArc",
     "DeclarationBlockMutationClosure",
     "nsAttrValue",
     "nsIContent",
     "nsINode",
     "nsIDocument",
--- a/layout/style/ServoCSSPropList.mako.py
+++ b/layout/style/ServoCSSPropList.mako.py
@@ -89,16 +89,17 @@ SERIALIZED_PREDEFINED_TYPES = [
     "FontVariationSettings",
     "FontWeight",
     "Integer",
     "Length",
     "LengthOrPercentage",
     "NonNegativeLength",
     "NonNegativeLengthOrPercentage",
     "ListStyleType",
+    "OffsetPath",
     "Opacity",
     "Resize",
     "url::ImageUrlOrNone",
 ]
 
 def serialized_by_servo(prop):
     # If the property requires layout information, no such luck.
     if "GETCS_NEEDS_LAYOUT_FLUSH" in prop.flags:
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -5043,16 +5043,21 @@ nsComputedDOMStyle::GetShapeSource(
       val->SetIdent(eCSSKeyword_none);
       return val.forget();
     }
     case StyleShapeSourceType::Image: {
       RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
       SetValueToStyleImage(*aShapeSource.GetShapeImage(), val);
       return val.forget();
     }
+    case StyleShapeSourceType::Path: {
+      // Bug 1246764: we have to support this for clip-path. For now, no one
+      // uses this.
+      MOZ_ASSERT_UNREACHABLE("Unexpected SVG Path type.");
+    }
   }
   return nullptr;
 }
 
 already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetClipPath()
 {
   return GetShapeSource(StyleSVGReset()->mClipPath,
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -193,16 +193,17 @@ enum class StyleShapeRadius : uint8_t {
 
 // Shape source type
 enum class StyleShapeSourceType : uint8_t {
   None,
   URL,   // clip-path only
   Image, // shape-outside only
   Shape,
   Box,
+  Path,  // SVG path function
 };
 
 // -moz-stack-sizing
 enum class StyleStackSizing : uint8_t {
   Ignore,
   StretchToFit,
   IgnoreHorizontal,
   IgnoreVertical,
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1034,16 +1034,19 @@ StyleShapeSource::operator==(const Style
       return *mShapeImage == *aOther.mShapeImage;
 
     case StyleShapeSourceType::Shape:
       return *mBasicShape == *aOther.mBasicShape &&
         mReferenceBox == aOther.mReferenceBox;
 
     case StyleShapeSourceType::Box:
       return mReferenceBox == aOther.mReferenceBox;
+
+    case StyleShapeSourceType::Path:
+      return *mSVGPath == *aOther.mSVGPath;
   }
 
   MOZ_ASSERT_UNREACHABLE("Unexpected shape source type!");
   return true;
 }
 
 void
 StyleShapeSource::SetURL(css::URLValue* aValue)
@@ -1086,16 +1089,25 @@ StyleShapeSource::SetBasicShape(UniquePt
   MOZ_ASSERT(aBasicShape);
   DoDestroy();
   new (&mBasicShape) UniquePtr<StyleBasicShape>(std::move(aBasicShape));
   mReferenceBox = aReferenceBox;
   mType = StyleShapeSourceType::Shape;
 }
 
 void
+StyleShapeSource::SetPath(UniquePtr<StyleSVGPath> aPath)
+{
+  MOZ_ASSERT(aPath);
+  DoDestroy();
+  new (&mSVGPath) UniquePtr<StyleSVGPath>(std::move(aPath));
+  mType = StyleShapeSourceType::Path;
+}
+
+void
 StyleShapeSource::SetReferenceBox(StyleGeometryBox aReferenceBox)
 {
   DoDestroy();
   mReferenceBox = aReferenceBox;
   mType = StyleShapeSourceType::Box;
 }
 
 void
@@ -1118,30 +1130,37 @@ StyleShapeSource::DoCopy(const StyleShap
     case StyleShapeSourceType::Shape:
       SetBasicShape(MakeUnique<StyleBasicShape>(*aOther.GetBasicShape()),
                     aOther.GetReferenceBox());
       break;
 
     case StyleShapeSourceType::Box:
       SetReferenceBox(aOther.GetReferenceBox());
       break;
+
+    case StyleShapeSourceType::Path:
+      SetPath(MakeUnique<StyleSVGPath>(*aOther.GetPath()));
+      break;
   }
 }
 
 void
 StyleShapeSource::DoDestroy()
 {
   switch (mType) {
     case StyleShapeSourceType::Shape:
       mBasicShape.~UniquePtr<StyleBasicShape>();
       break;
     case StyleShapeSourceType::Image:
     case StyleShapeSourceType::URL:
       mShapeImage.~UniquePtr<nsStyleImage>();
       break;
+    case StyleShapeSourceType::Path:
+      mSVGPath.~UniquePtr<StyleSVGPath>();
+      break;
     case StyleShapeSourceType::None:
     case StyleShapeSourceType::Box:
       // Not a union type, so do nothing.
       break;
   }
   mType = StyleShapeSourceType::None;
 }
 
@@ -3612,17 +3631,20 @@ nsStyleDisplay::nsStyleDisplay(const nsS
   , mScrollSnapCoordinate(aSource.mScrollSnapCoordinate)
   , mBackfaceVisibility(aSource.mBackfaceVisibility)
   , mTransformStyle(aSource.mTransformStyle)
   , mTransformBox(aSource.mTransformBox)
   , mSpecifiedTransform(aSource.mSpecifiedTransform)
   , mSpecifiedRotate(aSource.mSpecifiedRotate)
   , mSpecifiedTranslate(aSource.mSpecifiedTranslate)
   , mSpecifiedScale(aSource.mSpecifiedScale)
-  , mCombinedTransform(aSource.mCombinedTransform)
+  , mIndividualTransform(aSource.mIndividualTransform)
+  , mMotion(aSource.mMotion
+            ? MakeUnique<StyleMotion>(*aSource.mMotion)
+            : nullptr)
   , mTransformOrigin{ aSource.mTransformOrigin[0],
                       aSource.mTransformOrigin[1],
                       aSource.mTransformOrigin[2] }
   , mChildPerspective(aSource.mChildPerspective)
   , mPerspectiveOrigin{ aSource.mPerspectiveOrigin[0],
                         aSource.mPerspectiveOrigin[1] }
   , mVerticalAlign(aSource.mVerticalAlign)
   , mTransitions(aSource.mTransitions)
@@ -3677,19 +3699,18 @@ nsStyleDisplay::~nsStyleDisplay()
   ReleaseSharedListOnMainThread("nsStyleDisplay::mSpecifiedTransform",
                                 mSpecifiedTransform);
   ReleaseSharedListOnMainThread("nsStyleDisplay::mSpecifiedRotate",
                                 mSpecifiedRotate);
   ReleaseSharedListOnMainThread("nsStyleDisplay::mSpecifiedTranslate",
                                 mSpecifiedTranslate);
   ReleaseSharedListOnMainThread("nsStyleDisplay::mSpecifiedScale",
                                 mSpecifiedScale);
-  ReleaseSharedListOnMainThread("nsStyleDisplay::mCombinedTransform",
-                                mCombinedTransform);
-
+  ReleaseSharedListOnMainThread("nsStyleDisplay::mIndividualTransform",
+                                mIndividualTransform);
   MOZ_COUNT_DTOR(nsStyleDisplay);
 }
 
 void
 nsStyleDisplay::FinishStyle(
     nsPresContext* aPresContext, const nsStyleDisplay* aOldStyle)
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -3707,17 +3728,17 @@ nsStyleDisplay::FinishStyle(
       const nsStyleImage* oldShapeImage =
         (aOldStyle &&
          aOldStyle->mShapeOutside.GetType() == StyleShapeSourceType::Image)
           ?  &*aOldStyle->mShapeOutside.GetShapeImage() : nullptr;
       shapeImage->ResolveImage(aPresContext, oldShapeImage);
     }
   }
 
-  GenerateCombinedTransform();
+  GenerateCombinedIndividualTransform();
 }
 
 static inline nsChangeHint
 CompareTransformValues(const RefPtr<nsCSSValueSharedList>& aList,
                        const RefPtr<nsCSSValueSharedList>& aNewList)
 {
   nsChangeHint result = nsChangeHint(0);
 
@@ -3730,16 +3751,39 @@ CompareTransformValues(const RefPtr<nsCS
     } else {
       result |= nsChangeHint_UpdateOverflow;
     }
   }
 
   return result;
 }
 
+static inline nsChangeHint
+CompareMotionValues(const StyleMotion* aMotion,
+                    const StyleMotion* aNewMotion)
+{
+  nsChangeHint result = nsChangeHint(0);
+
+  // TODO: Bug 1482737: This probably doesn't need to UpdateOverflow
+  // (or UpdateTransformLayer) if there's already a transform.
+  if (!aMotion != !aNewMotion ||
+      (aMotion && *aMotion != *aNewMotion)) {
+    // Set the same hints as what we use for transform because motion path is
+    // a kind of transform and will be combined with other transforms.
+    result |= nsChangeHint_UpdateTransformLayer;
+    if ((aMotion && aMotion->HasPath()) &&
+        (aNewMotion && aNewMotion->HasPath())) {
+      result |= nsChangeHint_UpdatePostTransformOverflow;
+    } else {
+      result |= nsChangeHint_UpdateOverflow;
+    }
+  }
+  return result;
+}
+
 nsChangeHint
 nsStyleDisplay::CalcDifference(const nsStyleDisplay& aNewData) const
 {
   nsChangeHint hint = nsChangeHint(0);
 
   if (!DefinitelyEqualURIsAndPrincipal(mBinding, aNewData.mBinding)
       || mPosition != aNewData.mPosition
       || mDisplay != aNewData.mDisplay
@@ -3861,16 +3905,17 @@ nsStyleDisplay::CalcDifference(const nsS
     transformHint |= CompareTransformValues(mSpecifiedTransform,
                                             aNewData.mSpecifiedTransform);
     transformHint |= CompareTransformValues(mSpecifiedRotate, aNewData.
                                             mSpecifiedRotate);
     transformHint |= CompareTransformValues(mSpecifiedTranslate,
                                             aNewData.mSpecifiedTranslate);
     transformHint |= CompareTransformValues(mSpecifiedScale,
                                             aNewData.mSpecifiedScale);
+    transformHint |= CompareMotionValues(mMotion.get(), aNewData.mMotion.get());
 
     const nsChangeHint kUpdateOverflowAndRepaintHint =
       nsChangeHint_UpdateOverflow | nsChangeHint_RepaintFrame;
     for (uint8_t index = 0; index < 3; ++index) {
       if (mTransformOrigin[index] != aNewData.mTransformOrigin[index]) {
         transformHint |= nsChangeHint_UpdateTransformLayer |
                          nsChangeHint_UpdatePostTransformOverflow;
         break;
@@ -3980,70 +4025,67 @@ nsStyleDisplay::CalcDifference(const nsS
        mScrollSnapCoordinate != aNewData.mScrollSnapCoordinate)) {
     hint |= nsChangeHint_NeutralChange;
   }
 
   return hint;
 }
 
 void
-nsStyleDisplay::GenerateCombinedTransform()
+nsStyleDisplay::GenerateCombinedIndividualTransform()
 {
   // FIXME(emilio): This should probably be called from somewhere like what we
   // do for image layers, instead of FinishStyle.
   //
   // This does and undoes the work a ton of times in Stylo.
-  mCombinedTransform = nullptr;
+  mIndividualTransform = nullptr;
 
   // Follow the order defined in the spec to append transform functions.
   // https://drafts.csswg.org/css-transforms-2/#ctm
-  AutoTArray<nsCSSValueSharedList*, 4> shareLists;
+  AutoTArray<nsCSSValueSharedList*, 3> shareLists;
   if (mSpecifiedTranslate) {
     shareLists.AppendElement(mSpecifiedTranslate.get());
   }
   if (mSpecifiedRotate) {
     shareLists.AppendElement(mSpecifiedRotate.get());
   }
   if (mSpecifiedScale) {
     shareLists.AppendElement(mSpecifiedScale.get());
   }
-  if (mSpecifiedTransform) {
-    shareLists.AppendElement(mSpecifiedTransform.get());
-  }
 
   if (shareLists.Length() == 0) {
     return;
   }
-
   if (shareLists.Length() == 1) {
-    mCombinedTransform = shareLists[0];
+    mIndividualTransform = shareLists[0];
     return;
   }
 
-  // In common, we may have 3 transform functions(for rotate, translate and
-  // scale) in mSpecifiedTransform, one rotate function in mSpecifiedRotate,
-  // one translate function in mSpecifiedTranslate, and one scale function in
-  // mSpecifiedScale. So 6 slots are enough for the most cases.
-  AutoTArray<nsCSSValueList*, 6> valueLists;
+  // In common, we may have 3 transform functions:
+  // 1. one rotate function in mSpecifiedRotate,
+  // 2. one translate function in mSpecifiedTranslate,
+  // 3. one scale function in mSpecifiedScale.
+  AutoTArray<nsCSSValueList*, 3> valueLists;
   for (auto list: shareLists) {
     if (list) {
       valueLists.AppendElement(list->mHead->Clone());
     }
   }
 
   // Check we have at least one list or else valueLists.Length() - 1 below will
   // underflow.
   MOZ_ASSERT(valueLists.Length());
 
   for (uint32_t i = 0; i < valueLists.Length() - 1; i++) {
     valueLists[i]->mNext = valueLists[i + 1];
   }
 
-  mCombinedTransform = new nsCSSValueSharedList(valueLists[0]);
-}
+  mIndividualTransform = new nsCSSValueSharedList(valueLists[0]);
+}
+
 // --------------------
 // nsStyleVisibility
 //
 
 nsStyleVisibility::nsStyleVisibility(const nsPresContext* aContext)
   : mDirection(aContext->GetBidi() == IBMBIDI_TEXTDIRECTION_RTL
                  ? NS_STYLE_DIRECTION_RTL
                  : NS_STYLE_DIRECTION_LTR)
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -1966,16 +1966,37 @@ private:
   // (top, right, bottom, left) for inset
   nsTArray<nsStyleCoord> mCoordinates;
   // position of center for ellipse or circle
   mozilla::Position mPosition;
   // corner radii for inset (0 if not set)
   nsStyleCorners mRadius;
 };
 
+struct StyleSVGPath final
+{
+  const nsTArray<StylePathCommand>& Path() const
+  {
+    return mPath;
+  }
+
+  bool operator==(const StyleSVGPath& aOther) const
+  {
+    return mPath == aOther.mPath;
+  }
+
+  bool operator!=(const StyleSVGPath& aOther) const
+  {
+    return !(*this == aOther);
+  }
+
+private:
+  nsTArray<StylePathCommand> mPath;
+};
+
 struct StyleShapeSource final
 {
   StyleShapeSource();
 
   StyleShapeSource(const StyleShapeSource& aSource);
 
   ~StyleShapeSource();
 
@@ -2030,32 +2051,67 @@ struct StyleShapeSource final
     MOZ_ASSERT(mType == StyleShapeSourceType::Box ||
                mType == StyleShapeSourceType::Shape,
                "Wrong shape source type!");
     return mReferenceBox;
   }
 
   void SetReferenceBox(StyleGeometryBox aReferenceBox);
 
+  const StyleSVGPath* GetPath() const
+  {
+    MOZ_ASSERT(mType == StyleShapeSourceType::Path, "Wrong shape source type!");
+    return mSVGPath.get();
+  }
+  void SetPath(UniquePtr<StyleSVGPath> aPath);
+
 private:
   void* operator new(size_t) = delete;
 
   void DoCopy(const StyleShapeSource& aOther);
   void DoDestroy();
 
   union {
     mozilla::UniquePtr<StyleBasicShape> mBasicShape;
     mozilla::UniquePtr<nsStyleImage> mShapeImage;
-    // TODO: Bug 1429298, implement SVG Path function.
+    mozilla::UniquePtr<StyleSVGPath> mSVGPath;
     // TODO: Bug 1480665, implement ray() function.
   };
   StyleShapeSourceType mType = StyleShapeSourceType::None;
   StyleGeometryBox mReferenceBox = StyleGeometryBox::NoBox;
 };
 
+struct StyleMotion final
+{
+  bool operator==(const StyleMotion& aOther) const
+  {
+    return mOffsetPath == aOther.mOffsetPath;
+  }
+
+  bool operator!=(const StyleMotion& aOther) const
+  {
+    return !(*this == aOther);
+  }
+
+  const StyleShapeSource& OffsetPath() const
+  {
+    return mOffsetPath;
+  }
+
+  bool HasPath() const
+  {
+    // Bug 1186329: We have to check other acceptable types after supporting
+    // different values of offset-path. e.g. basic-shapes, ray.
+    return mOffsetPath.GetType() == StyleShapeSourceType::Path;
+  }
+
+private:
+  StyleShapeSource mOffsetPath;
+};
+
 } // namespace mozilla
 
 struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleDisplay
 {
   typedef mozilla::StyleGeometryBox StyleGeometryBox;
 
   explicit nsStyleDisplay(const nsPresContext* aContext);
   nsStyleDisplay(const nsStyleDisplay& aOther);
@@ -2120,22 +2176,20 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
   // null, as appropriate.)
   uint8_t mBackfaceVisibility;
   uint8_t mTransformStyle;
   StyleGeometryBox mTransformBox;
   RefPtr<nsCSSValueSharedList> mSpecifiedTransform;
   RefPtr<nsCSSValueSharedList> mSpecifiedRotate;
   RefPtr<nsCSSValueSharedList> mSpecifiedTranslate;
   RefPtr<nsCSSValueSharedList> mSpecifiedScale;
-
-  // Used to store the final combination of mSpecifiedTranslate,
-  // mSpecifiedRotate, mSpecifiedScale and mSpecifiedTransform.
-  // Use GetCombinedTransform() to get the final transform, instead of
-  // accessing mCombinedTransform directly.
-  RefPtr<nsCSSValueSharedList> mCombinedTransform;
+  // Used to store the final combination of mSpecifiedRotate,
+  // mSpecifiedTranslate, and mSpecifiedScale.
+  RefPtr<nsCSSValueSharedList> mIndividualTransform;
+  mozilla::UniquePtr<mozilla::StyleMotion> mMotion;
 
   nsStyleCoord mTransformOrigin[3]; // percent, coord, calc, 3rd param is coord, calc only
   nsStyleCoord mChildPerspective; // none, coord
   nsStyleCoord mPerspectiveOrigin[2]; // percent, coord, calc
 
   nsStyleCoord mVerticalAlign;  // coord, percent, calc, enum (NS_STYLE_VERTICAL_ALIGN_*)
 
   nsStyleAutoArray<mozilla::StyleTransition> mTransitions;
@@ -2375,17 +2429,18 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
   }
 
   /* Returns whether the element has the -moz-transform property
    * or a related property. */
   bool HasTransformStyle() const {
     return mSpecifiedTransform || mSpecifiedRotate || mSpecifiedTranslate ||
            mSpecifiedScale ||
            mTransformStyle == NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D ||
-           (mWillChangeBitField & NS_STYLE_WILL_CHANGE_TRANSFORM);
+           (mWillChangeBitField & NS_STYLE_WILL_CHANGE_TRANSFORM) ||
+           (mMotion && mMotion->HasPath());
   }
 
   bool HasIndividualTransform() const {
     return mSpecifiedRotate || mSpecifiedTranslate || mSpecifiedScale;
   }
 
   bool HasPerspectiveStyle() const {
     return mChildPerspective.GetUnit() == eStyleUnit_Coord;
@@ -2465,31 +2520,27 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
    */
   inline bool IsFixedPosContainingBlockForNonSVGTextFrames(
     mozilla::ComputedStyle&) const;
   inline bool
     IsFixedPosContainingBlockForContainLayoutAndPaintSupportingFrames() const;
   inline bool IsFixedPosContainingBlockForTransformSupportingFrames() const;
 
   /**
-   * Returns the final combined transform.
+   * Returns the final combined individual transform.
    **/
-  already_AddRefed<nsCSSValueSharedList> GetCombinedTransform() const {
-    if (mCombinedTransform) {
-      return do_AddRef(mCombinedTransform);
-    }
-
-    // backward compatible to gecko-backed style system.
-    return mSpecifiedTransform ? do_AddRef(mSpecifiedTransform) : nullptr;
+  already_AddRefed<nsCSSValueSharedList> GetCombinedTransform() const
+  {
+    return mIndividualTransform ? do_AddRef(mIndividualTransform) : nullptr;
   }
 
 private:
   // Helpers for above functions, which do some but not all of the tests
   // for them (since transform must be tested separately for each).
-  void GenerateCombinedTransform();
+  void GenerateCombinedIndividualTransform();
 };
 
 struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleTable
 {
   explicit nsStyleTable(const nsPresContext* aContext);
   nsStyleTable(const nsStyleTable& aOther);
   ~nsStyleTable();
   void FinishStyle(nsPresContext*, const nsStyleTable*) {}
--- a/layout/style/nsStyleTransformMatrix.cpp
+++ b/layout/style/nsStyleTransformMatrix.cpp
@@ -921,40 +921,84 @@ SetIdentityMatrix(nsCSSValue::Array* aMa
 
   MOZ_ASSERT(aMatrix->Count() == 17, "Invalid matrix3d");
   Matrix4x4 m;
   for (size_t i = 0; i < 16; ++i) {
     aMatrix->Item(i + 1).SetFloatValue(m.components[i], eCSSUnit_Number);
   }
 }
 
-Matrix4x4
-ReadTransforms(const nsCSSValueList* aList,
-               TransformReferenceBox& aRefBox,
-               float aAppUnitsPerMatrixUnit,
-               bool* aContains3dTransform)
+static void
+ReadTransformsImpl(Matrix4x4& aMatrix,
+                   const nsCSSValueList* aList,
+                   TransformReferenceBox& aRefBox,
+                   bool* aContains3dTransform)
 {
-  Matrix4x4 result;
-
   for (const nsCSSValueList* curr = aList; curr != nullptr; curr = curr->mNext) {
     const nsCSSValue &currElem = curr->mValue;
     if (currElem.GetUnit() != eCSSUnit_Function) {
       NS_ASSERTION(currElem.GetUnit() == eCSSUnit_None &&
                    !aList->mNext,
                    "stream should either be a list of functions or a "
                    "lone None");
       continue;
     }
     NS_ASSERTION(currElem.GetArrayValue()->Count() >= 1,
                  "Incoming function is too short!");
 
     /* Read in a single transform matrix. */
-    MatrixForTransformFunction(result, currElem.GetArrayValue(), aRefBox,
+    MatrixForTransformFunction(aMatrix, currElem.GetArrayValue(), aRefBox,
                                aContains3dTransform);
   }
+}
+
+Matrix4x4
+ReadTransforms(const nsCSSValueList* aList,
+               TransformReferenceBox& aRefBox,
+               float aAppUnitsPerMatrixUnit,
+               bool* aContains3dTransform)
+{
+  Matrix4x4 result;
+  ReadTransformsImpl(result, aList, aRefBox, aContains3dTransform);
+
+  float scale = float(AppUnitsPerCSSPixel()) / aAppUnitsPerMatrixUnit;
+  result.PreScale(1/scale, 1/scale, 1/scale);
+  result.PostScale(scale, scale, scale);
+
+  return result;
+}
+
+Matrix4x4
+ReadTransforms(const nsCSSValueList* aIndividualTransforms,
+               const Maybe<MotionPathData>& aMotion,
+               const nsCSSValueList* aTransform,
+               TransformReferenceBox& aRefBox,
+               float aAppUnitsPerMatrixUnit,
+               bool* aContains3dTransform)
+{
+  Matrix4x4 result;
+
+  if (aIndividualTransforms) {
+    ReadTransformsImpl(result, aIndividualTransforms, aRefBox,
+                       aContains3dTransform);
+  }
+
+  if (aMotion.isSome()) {
+    // Create the equivalent translate and rotate function, according to the
+    // order in spec. We combine the translate and then the rotate.
+    // https://drafts.fxtf.org/motion-1/#calculating-path-transform
+    result.PreTranslate(aMotion->mTranslate.x, aMotion->mTranslate.y, 0.0);
+    if (aMotion->mRotate != 0.0) {
+      result.RotateZ(aMotion->mRotate);
+    }
+  }
+
+  if (aTransform) {
+    ReadTransformsImpl(result, aTransform, aRefBox, aContains3dTransform);
+  }
 
   float scale = float(AppUnitsPerCSSPixel()) / aAppUnitsPerMatrixUnit;
   result.PreScale(1/scale, 1/scale, 1/scale);
   result.PostScale(scale, scale, scale);
 
   return result;
 }
 
--- a/layout/style/nsStyleTransformMatrix.h
+++ b/layout/style/nsStyleTransformMatrix.h
@@ -19,16 +19,20 @@
 
 #include <limits>
 
 class nsIFrame;
 class nsPresContext;
 struct gfxQuaternion;
 struct nsRect;
 
+namespace mozilla {
+struct MotionPathData;
+}
+
 /**
  * A helper to generate gfxMatrixes from css transform functions.
  */
 namespace nsStyleTransformMatrix {
   // The operator passed to Servo backend.
   enum class MatrixTransformOperator: uint8_t {
     Interpolate,
     Accumulate
@@ -195,16 +199,26 @@ namespace nsStyleTransformMatrix {
    *
    * eCSSUnit_Pixel (as they are in an StyleAnimationValue)
    */
   mozilla::gfx::Matrix4x4 ReadTransforms(const nsCSSValueList* aList,
                                          TransformReferenceBox& aBounds,
                                          float aAppUnitsPerMatrixUnit,
                                          bool* aContains3dTransform);
 
+  // Generate the gfx::Matrix for CSS Transform Module Level 2.
+  // https://drafts.csswg.org/css-transforms-2/#ctm
+  mozilla::gfx::Matrix4x4
+  ReadTransforms(const nsCSSValueList* aIndividualTransforms,
+                 const mozilla::Maybe<mozilla::MotionPathData>& aMotion,
+                 const nsCSSValueList* aTransform,
+                 TransformReferenceBox& aRefBox,
+                 float aAppUnitsPerMatrixUnit,
+                 bool* aContains3dTransform);
+
   /**
    * Given two nsStyleCoord values, compute the 2d position with respect to the
    * given TransformReferenceBox that these values describe, in device pixels.
    */
   mozilla::gfx::Point Convert2DPosition(nsStyleCoord const (&aValue)[2],
                                         TransformReferenceBox& aRefBox,
                                         int32_t aAppUnitsPerDevPixel);
 
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -8168,16 +8168,38 @@ if (IsCSSPropertyPrefEnabled("layout.css
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "auto" ],
     other_values: [ "none", "thin" ],
     invalid_values: [ "1px" ]
   };
 }
 
+if (IsCSSPropertyPrefEnabled("layout.css.motion-path.enabled")) {
+  gCSSProperties["offset-path"] = {
+    domProp: "offsetPath",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "none" ],
+    other_values: [
+      "path('M 10 10 20 20 H 90 V 90 Z')",
+      "path('M10 10 20,20H90V90Z')",
+      "path('M 10 10 C 20 20, 40 20, 50 10')",
+      "path('M 10 80 C 40 10, 65 10, 95 80 S 1.5e2 150, 180 80')",
+      "path('M 10 80 Q 95 10 180 80')",
+      "path('M 10 80 Q 52.5 10, 95 80 T 180 80')",
+      "path('M 80 80 A 45 45, 0, 0, 0, 1.25e2 1.25e2 L 125 80 Z')",
+      "path('M100-200h20z')",
+      "path('M10,10L20.6.5z')"
+    ],
+    invalid_values: [ "path('')", "path()", "path(a)", "path('M 10 Z')" ,
+                      "path('M 10-10 20')", "path('M 10 10 C 20 20 40 20')" ]
+  };
+}
+
 const OVERFLOW_MOZKWS = [
   "-moz-scrollbars-none",
   "-moz-scrollbars-horizontal",
   "-moz-scrollbars-vertical",
 ];
 if (IsCSSPropertyPrefEnabled("layout.css.overflow.moz-scrollbars.enabled")) {
   gCSSProperties["overflow"].other_values.push(...OVERFLOW_MOZKWS);
 } else {
--- a/mfbt/HashTable.h
+++ b/mfbt/HashTable.h
@@ -1204,28 +1204,38 @@ public:
       : mEntry(&aEntry)
 #ifdef DEBUG
       , mTable(&aTable)
       , mGeneration(aTable.generation())
 #endif
     {
     }
 
+    // This constructor is used only by AddPtr() within lookupForAdd().
+    explicit Ptr(const HashTable& aTable)
+      : mEntry(nullptr)
+#ifdef DEBUG
+      , mTable(&aTable)
+      , mGeneration(aTable.generation())
+#endif
+    {
+    }
+
+    bool isValid() const { return !!mEntry; }
+
   public:
     Ptr()
       : mEntry(nullptr)
 #ifdef DEBUG
       , mTable(nullptr)
       , mGeneration(0)
 #endif
     {
     }
 
-    bool isValid() const { return !!mEntry; }
-
     bool found() const
     {
       if (!isValid()) {
         return false;
       }
 #ifdef DEBUG
       MOZ_ASSERT(mGeneration == mTable->generation());
 #endif
@@ -1281,16 +1291,31 @@ public:
       : Ptr(aEntry, aTable)
       , mKeyHash(aHashNumber)
 #ifdef DEBUG
       , mMutationCount(aTable.mMutationCount)
 #endif
     {
     }
 
+    // This constructor is used when lookupForAdd() is performed on a table
+    // lacking entry storage; it leaves mEntry null but initializes everything
+    // else.
+    AddPtr(const HashTable& aTable, HashNumber aHashNumber)
+      : Ptr(aTable)
+      , mKeyHash(aHashNumber)
+#ifdef DEBUG
+      , mMutationCount(aTable.mMutationCount)
+#endif
+    {
+      MOZ_ASSERT(isLive());
+    }
+
+    bool isLive() const { return isLiveHash(mKeyHash); }
+
   public:
     AddPtr()
       : mKeyHash(0)
     {
     }
   };
 
   // A hash table iterator that (mostly) doesn't allow table modifications.
@@ -2025,17 +2050,17 @@ public:
       return true;
     }
 
     uint32_t bestCapacity = this->bestCapacity(aLen);
     if (bestCapacity <= capacity()) {
       return true;  // Capacity is already sufficient.
     }
 
-    RebuildStatus status = changeTableSize(bestCapacity, DontReportFailure);
+    RebuildStatus status = changeTableSize(bestCapacity, ReportFailure);
     MOZ_ASSERT(status != NotOverloaded);
     return status != RehashFailed;
   }
 
   Iterator iter() const
   {
     return Iterator(*this);
   }
@@ -2102,59 +2127,66 @@ public:
 
   MOZ_ALWAYS_INLINE AddPtr lookupForAdd(const Lookup& aLookup)
   {
     ReentrancyGuard g(*this);
     if (!EnsureHash<HashPolicy>(aLookup)) {
       return AddPtr();
     }
 
+    HashNumber keyHash = prepareHash(aLookup);
+
     if (!mTable) {
-      uint32_t newCapacity = rawCapacity();
-      RebuildStatus status = changeTableSize(newCapacity, ReportFailure);
-      MOZ_ASSERT(status != NotOverloaded);
-      if (status == RehashFailed) {
-        return AddPtr();
-      }
+      return AddPtr(*this, keyHash);
     }
 
-    HashNumber keyHash = prepareHash(aLookup);
     // Directly call the constructor in the return statement to avoid
     // excess copying when building with Visual Studio 2017.
     // See bug 1385181.
     return AddPtr(lookup<ForAdd>(aLookup, keyHash), *this, keyHash);
   }
 
   template<typename... Args>
   MOZ_MUST_USE bool add(AddPtr& aPtr, Args&&... aArgs)
   {
     ReentrancyGuard g(*this);
     MOZ_ASSERT_IF(aPtr.isValid(), mTable);
     MOZ_ASSERT_IF(aPtr.isValid(), aPtr.mTable == this);
     MOZ_ASSERT(!aPtr.found());
     MOZ_ASSERT(!(aPtr.mKeyHash & sCollisionBit));
 
     // Check for error from ensureHash() here.
-    if (!aPtr.isValid()) {
+    if (!aPtr.isLive()) {
       return false;
     }
 
     MOZ_ASSERT(aPtr.mGeneration == generation());
 #ifdef DEBUG
     MOZ_ASSERT(aPtr.mMutationCount == mMutationCount);
 #endif
 
-    // Changing an entry from removed to live does not affect whether we
-    // are overloaded and can be handled separately.
-    if (aPtr.mEntry->isRemoved()) {
+    if (!aPtr.isValid()) {
+      MOZ_ASSERT(!mTable && mEntryCount == 0);
+      uint32_t newCapacity = rawCapacity();
+      RebuildStatus status = changeTableSize(newCapacity, ReportFailure);
+      MOZ_ASSERT(status != NotOverloaded);
+      if (status == RehashFailed) {
+        return false;
+      }
+      aPtr.mEntry = &findNonLiveEntry(aPtr.mKeyHash);
+
+    } else if (aPtr.mEntry->isRemoved()) {
+      // Changing an entry from removed to live does not affect whether we are
+      // overloaded and can be handled separately.
       if (!this->checkSimulatedOOM()) {
         return false;
       }
       mRemovedCount--;
       aPtr.mKeyHash |= sCollisionBit;
+
     } else {
       // Preserve the validity of |aPtr.mEntry|.
       RebuildStatus status = rehashIfOverloaded();
       if (status == RehashFailed) {
         return false;
       }
       if (status == NotOverloaded && !this->checkSimulatedOOM()) {
         return false;
@@ -2205,30 +2237,37 @@ public:
   // Note: |aLookup| may be a reference to a piece of |u|, so this function
   // must take care not to use |aLookup| after moving |u|.
   template<typename... Args>
   MOZ_MUST_USE bool relookupOrAdd(AddPtr& aPtr,
                                   const Lookup& aLookup,
                                   Args&&... aArgs)
   {
     // Check for error from ensureHash() here.
-    if (!aPtr.isValid()) {
+    if (!aPtr.isLive()) {
       return false;
     }
 #ifdef DEBUG
     aPtr.mGeneration = generation();
     aPtr.mMutationCount = mMutationCount;
 #endif
-    {
+    if (mTable) {
       ReentrancyGuard g(*this);
       // Check that aLookup has not been destroyed.
       MOZ_ASSERT(prepareHash(aLookup) == aPtr.mKeyHash);
       aPtr.mEntry = &lookup<ForAdd>(aLookup, aPtr.mKeyHash);
+      if (aPtr.found()) {
+        return true;
+      }
+    } else {
+      // Clear aPtr so it's invalid; add() will allocate storage and redo the
+      // lookup.
+      aPtr.mEntry = nullptr;
     }
-    return aPtr.found() || add(aPtr, std::forward<Args>(aArgs)...);
+    return add(aPtr, std::forward<Args>(aArgs)...);
   }
 
   void remove(Ptr aPtr)
   {
     MOZ_ASSERT(mTable);
     ReentrancyGuard g(*this);
     MOZ_ASSERT(aPtr.found());
     MOZ_ASSERT(aPtr.mGeneration == generation());
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3068,16 +3068,23 @@ pref("layout.css.font-loading-api.enable
 pref("layout.css.column-span.enabled", false);
 
 // Are inter-character ruby annotations enabled?
 pref("layout.css.ruby.intercharacter.enabled", false);
 
 // Is support for overscroll-behavior enabled?
 pref("layout.css.overscroll-behavior.enabled", true);
 
+// Is support for motion-path enabled?
+#ifdef RELEASE_OR_BETA
+pref("layout.css.motion-path.enabled", false);
+#else
+pref("layout.css.motion-path.enabled", true);
+#endif
+
 // pref for which side vertical scrollbars should be on
 // 0 = end-side in UI direction
 // 1 = end-side in document/content direction
 // 2 = right
 // 3 = left
 pref("layout.scrollbar.side", 0);
 
 // pref to stop overlay scrollbars from fading out, for testing purposes
--- a/servo/components/style/cbindgen.toml
+++ b/servo/components/style/cbindgen.toml
@@ -2,25 +2,26 @@ header = """/* This Source Code Form is 
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */"""
 autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen.
  * To generate this file:
  *   1. Get the latest cbindgen using `cargo install --force cbindgen`
  *      a. Alternatively, you can clone `https://github.com/eqrion/cbindgen` and use a tagged release
  *   2. Run `rustup run nightly cbindgen toolkit/library/rust/ --lockfile Cargo.lock --crate style -o layout/style/ServoStyleConsts.h`
  */"""
+include_guard = "mozilla_ServoStyleConsts_h"
 include_version = true
 braces = "SameLine"
 line_length = 80
 tab_width = 2
 language = "C++"
 namespaces = ["mozilla"]
 
 [struct]
 derive_eq = true
 
 [enum]
 derive_helper_methods = true
 
 [export]
 prefix = "Style"
-include = ["StyleDisplay", "StyleAppearance", "StyleDisplayMode"]
-item_types = ["enums"]
+include = ["StyleDisplay", "StyleAppearance", "StyleDisplayMode", "StylePathCommand"]
+item_types = ["enums", "structs", "typedefs"]
--- a/servo/components/style/gecko/conversions.rs
+++ b/servo/components/style/gecko/conversions.rs
@@ -633,16 +633,17 @@ pub mod basic_shape {
     use gecko_bindings::structs::{StyleGeometryBox, StyleShapeSource, StyleShapeSourceType};
     use gecko_bindings::structs::{nsStyleCoord, nsStyleCorners};
     use gecko_bindings::sugar::ns_style_coord::{CoordDataMut, CoordDataValue};
     use gecko_bindings::sugar::refptr::RefPtr;
     use std::borrow::Borrow;
     use values::computed::basic_shape::{BasicShape, ClippingShape, FloatAreaShape, ShapeRadius};
     use values::computed::border::{BorderCornerRadius, BorderRadius};
     use values::computed::length::LengthOrPercentage;
+    use values::computed::motion::OffsetPath;
     use values::computed::position;
     use values::computed::url::ComputedUrl;
     use values::generics::basic_shape::{BasicShape as GenericBasicShape, InsetRect, Polygon};
     use values::generics::basic_shape::{Circle, Ellipse, FillRule};
     use values::generics::basic_shape::{GeometryBox, ShapeBox, ShapeSource};
     use values::generics::border::BorderRadius as GenericBorderRadius;
     use values::generics::rect::Rect;
 
@@ -664,16 +665,17 @@ pub mod basic_shape {
                     let reference_box = if self.mReferenceBox == StyleGeometryBox::NoBox {
                         None
                     } else {
                         Some(self.mReferenceBox.into())
                     };
                     Some(ShapeSource::Shape(shape, reference_box))
                 },
                 StyleShapeSourceType::URL | StyleShapeSourceType::Image => None,
+                StyleShapeSourceType::Path => None,
             }
         }
     }
 
     impl<'a> From<&'a StyleShapeSource> for ClippingShape {
         fn from(other: &'a StyleShapeSource) -> Self {
             match other.mType {
                 StyleShapeSourceType::URL => unsafe {
@@ -705,16 +707,39 @@ pub mod basic_shape {
                 },
                 _ => other
                     .into_shape_source()
                     .expect("Couldn't convert to StyleSource!"),
             }
         }
     }
 
+    impl<'a> From<&'a StyleShapeSource> for OffsetPath {
+        fn from(other: &'a StyleShapeSource) -> Self {
+            use gecko_bindings::structs::StylePathCommand;
+            use values::specified::motion::{SVGPathData, PathCommand};
+            match other.mType {
+                StyleShapeSourceType::Path => {
+                    let gecko_path = unsafe { &*other.__bindgen_anon_1.mSVGPath.as_ref().mPtr };
+                    let result: Vec<PathCommand> =
+                        gecko_path.mPath.iter().map(|gecko: &StylePathCommand| {
+                            // unsafe: cbindgen ensures the representation is the same.
+                            unsafe{ ::std::mem::transmute(*gecko) }
+                        }).collect();
+                    OffsetPath::Path(SVGPathData::new(result.into_boxed_slice()))
+                },
+                StyleShapeSourceType::None => OffsetPath::none(),
+                StyleShapeSourceType::Shape |
+                StyleShapeSourceType::Box |
+                StyleShapeSourceType::URL |
+                StyleShapeSourceType::Image => unreachable!("Unsupported offset-path type"),
+            }
+        }
+    }
+
     impl<'a> From<&'a StyleBasicShape> for BasicShape {
         fn from(other: &'a StyleBasicShape) -> Self {
             match other.mType {
                 StyleBasicShapeType::Inset => {
                     let t = LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[0]);
                     let r = LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[1]);
                     let b = LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[2]);
                     let l = LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[3]);
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -3048,17 +3048,17 @@ fn static_assert() {
                           animation-direction animation-fill-mode animation-play-state
                           animation-iteration-count animation-timing-function
                           transition-duration transition-delay
                           transition-timing-function transition-property
                           page-break-before page-break-after rotate
                           scroll-snap-points-x scroll-snap-points-y
                           scroll-snap-type-x scroll-snap-type-y scroll-snap-coordinate
                           perspective-origin -moz-binding will-change
-                          overscroll-behavior-x overscroll-behavior-y
+                          offset-path overscroll-behavior-x overscroll-behavior-y
                           overflow-clip-box-inline overflow-clip-box-block
                           perspective-origin -moz-binding will-change
                           shape-outside contain touch-action translate
                           scale""" %>
 <%self:impl_trait style_struct_name="Box" skip_longhands="${skip_box_longhands}">
     #[inline]
     pub fn set_display(&mut self, v: longhands::display::computed_value::T) {
         // unsafe: cbindgen ensures the representation is the same.
@@ -3676,16 +3676,61 @@ fn static_assert() {
         }
 
         return servo_flags;
     }
 
     ${impl_simple_copy("contain", "mContain")}
 
     ${impl_simple_type_with_conversion("touch_action")}
+
+    pub fn set_offset_path(&mut self, v: longhands::offset_path::computed_value::T) {
+        use gecko_bindings::bindings::{Gecko_NewStyleMotion, Gecko_NewStyleSVGPath};
+        use gecko_bindings::bindings::Gecko_SetStyleMotion;
+        use gecko_bindings::structs::StyleShapeSourceType;
+        use values::specified::OffsetPath;
+
+        let motion = unsafe { Gecko_NewStyleMotion().as_mut().unwrap() };
+        match v {
+            OffsetPath::None => motion.mOffsetPath.mType = StyleShapeSourceType::None,
+            OffsetPath::Path(servo_path) => {
+                motion.mOffsetPath.mType = StyleShapeSourceType::Path;
+                let gecko_path = unsafe {
+                    let ref mut source = motion.mOffsetPath;
+                    Gecko_NewStyleSVGPath(source);
+                    &mut source.__bindgen_anon_1.mSVGPath.as_mut().mPtr.as_mut().unwrap().mPath
+                };
+                unsafe { gecko_path.set_len(servo_path.commands().len() as u32) };
+                debug_assert_eq!(gecko_path.len(), servo_path.commands().len());
+                for (servo, gecko) in servo_path.commands().iter().zip(gecko_path.iter_mut()) {
+                    // unsafe: cbindgen ensures the representation is the same.
+                    *gecko = unsafe { transmute(*servo) };
+                }
+            },
+        }
+        unsafe { Gecko_SetStyleMotion(&mut self.gecko.mMotion, motion) };
+    }
+
+    pub fn clone_offset_path(&self) -> longhands::offset_path::computed_value::T {
+        use values::specified::OffsetPath;
+        match unsafe { self.gecko.mMotion.mPtr.as_ref() } {
+            None => OffsetPath::none(),
+            Some(v) => (&v.mOffsetPath).into()
+        }
+    }
+
+    pub fn copy_offset_path_from(&mut self, other: &Self) {
+        use gecko_bindings::bindings::Gecko_CopyStyleMotions;
+        unsafe { Gecko_CopyStyleMotions(&mut self.gecko.mMotion, other.gecko.mMotion.mPtr) };
+    }
+
+    pub fn reset_offset_path(&mut self, other: &Self) {
+        self.copy_offset_path_from(other);
+    }
+
 </%self:impl_trait>
 
 <%def name="simple_image_array_property(name, shorthand, field_name)">
     <%
         image_layers_field = "mImage" if shorthand == "background" else "mMask"
         copy_simple_image_array_property(name, shorthand, image_layers_field, field_name)
     %>
 
--- a/servo/components/style/properties/longhands/box.mako.rs
+++ b/servo/components/style/properties/longhands/box.mako.rs
@@ -351,16 +351,27 @@
     animation_value_type="ComputedValue",
     boxed=True,
     flags="CREATES_STACKING_CONTEXT FIXPOS_CB GETCS_NEEDS_LAYOUT_FLUSH",
     gecko_pref="layout.css.individual-transform.enabled",
     spec="https://drafts.csswg.org/css-transforms-2/#individual-transforms",
     servo_restyle_damage="reflow_out_of_flow"
 )}
 
+// Motion Path Module Level 1
+${helpers.predefined_type(
+    "offset-path",
+    "OffsetPath",
+    "computed::OffsetPath::none()",
+    animation_value_type="none",
+    gecko_pref="layout.css.motion-path.enabled",
+    flags="CREATES_STACKING_CONTEXT FIXPOS_CB",
+    spec="https://drafts.fxtf.org/motion-1/#offset-path-property"
+)}
+
 // CSSOM View Module
 // https://www.w3.org/TR/cssom-view-1/
 ${helpers.single_keyword("scroll-behavior",
                          "auto smooth",
                          gecko_pref="layout.css.scroll-behavior.property-enabled",
                          products="gecko",
                          spec="https://drafts.csswg.org/cssom-view/#propdef-scroll-behavior",
                          animation_value_type="discrete")}
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -60,16 +60,17 @@ pub use super::{Auto, Either, None_};
 pub use super::specified::{BorderStyle, TextDecorationLine};
 pub use self::length::{CalcLengthOrPercentage, Length, LengthOrNumber, LengthOrPercentage};
 pub use self::length::{LengthOrPercentageOrAuto, LengthOrPercentageOrNone, MaxLength, MozLength};
 pub use self::length::{CSSPixelLength, ExtremumLength, NonNegativeLength};
 pub use self::length::{NonNegativeLengthOrPercentage, NonNegativeLengthOrPercentageOrAuto};
 pub use self::list::Quotes;
 #[cfg(feature = "gecko")]
 pub use self::list::ListStyleType;
+pub use self::motion::OffsetPath;
 pub use self::outline::OutlineStyle;
 pub use self::percentage::{Percentage, NonNegativePercentage};
 pub use self::position::{GridAutoFlow, GridTemplateAreas, Position, ZIndex};
 pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind};
 pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth};
 pub use self::svg::MozContextProperties;
 pub use self::table::XSpan;
 pub use self::text::{InitialLetter, LetterSpacing, LineHeight, MozTabSize};
@@ -95,16 +96,17 @@ pub mod counters;
 pub mod effects;
 pub mod flex;
 pub mod font;
 #[cfg(feature = "gecko")]
 pub mod gecko;
 pub mod image;
 pub mod length;
 pub mod list;
+pub mod motion;
 pub mod outline;
 pub mod percentage;
 pub mod position;
 pub mod rect;
 pub mod resolution;
 pub mod svg;
 pub mod table;
 pub mod text;
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/computed/motion.rs
@@ -0,0 +1,10 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Computed types for CSS values that are related to motion path.
+
+/// A computed offset-path. The computed value is as specified value.
+///
+/// https://drafts.fxtf.org/motion-1/#offset-path-property
+pub use values::specified::motion::OffsetPath as OffsetPath;
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -53,16 +53,17 @@ pub use self::length::{AbsoluteLength, C
 pub use self::length::{FontRelativeLength, Length, LengthOrNumber};
 pub use self::length::{LengthOrPercentage, LengthOrPercentageOrAuto};
 pub use self::length::{LengthOrPercentageOrNone, MaxLength, MozLength};
 pub use self::length::{NoCalcLength, ViewportPercentageLength};
 pub use self::length::{NonNegativeLengthOrPercentage, NonNegativeLengthOrPercentageOrAuto};
 pub use self::list::Quotes;
 #[cfg(feature = "gecko")]
 pub use self::list::ListStyleType;
+pub use self::motion::OffsetPath;
 pub use self::outline::OutlineStyle;
 pub use self::rect::LengthOrNumberRect;
 pub use self::resolution::Resolution;
 pub use self::percentage::Percentage;
 pub use self::position::{GridAutoFlow, GridTemplateAreas, Position};
 pub use self::position::{PositionComponent, ZIndex};
 pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind};
 pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth};
@@ -96,16 +97,17 @@ pub mod flex;
 pub mod font;
 #[cfg(feature = "gecko")]
 pub mod gecko;
 pub mod grid;
 pub mod image;
 pub mod length;
 pub mod list;
 pub mod outline;
+pub mod motion;
 pub mod percentage;
 pub mod position;
 pub mod rect;
 pub mod resolution;
 pub mod source_size_list;
 pub mod svg;
 pub mod table;
 pub mod text;
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/specified/motion.rs
@@ -0,0 +1,558 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Specified types for CSS values that are related to motion path.
+
+use cssparser::Parser;
+use parser::{Parse, ParserContext};
+use std::fmt::{self, Write};
+use std::iter::Peekable;
+use std::str::Chars;
+use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
+use style_traits::values::SequenceWriter;
+use values::CSSFloat;
+
+/// The offset-path value.
+///
+/// https://drafts.fxtf.org/motion-1/#offset-path-property
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss)]
+pub enum OffsetPath {
+    // We could merge SVGPathData into ShapeSource, so we could reuse them. However,
+    // we don't want to support other value for offset-path, so use SVGPathData only for now.
+    /// Path value for path(<string>).
+    #[css(function)]
+    Path(SVGPathData),
+    /// None value.
+    None,
+    // Bug 1186329: Implement ray(), <basic-shape>, <geometry-box>, and <url>.
+}
+
+impl OffsetPath {
+    /// Return None.
+    #[inline]
+    pub fn none() -> Self {
+        OffsetPath::None
+    }
+}
+
+impl Parse for OffsetPath {
+    fn parse<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>
+    ) -> Result<Self, ParseError<'i>> {
+        // Parse none.
+        if input.try(|i| i.expect_ident_matching("none")).is_ok() {
+            return Ok(OffsetPath::none());
+        }
+
+        // Parse possible functions.
+        let location = input.current_source_location();
+        let function = input.expect_function()?.clone();
+        input.parse_nested_block(move |i| {
+            match_ignore_ascii_case! { &function,
+                // Bug 1186329: Implement the parser for ray(), <basic-shape>, <geometry-box>,
+                // and <url>.
+                "path" => SVGPathData::parse(context, i).map(OffsetPath::Path),
+                _ => {
+                    Err(location.new_custom_error(
+                        StyleParseErrorKind::UnexpectedFunction(function.clone())
+                    ))
+                },
+            }
+        })
+    }
+}
+
+/// SVG Path parser.
+struct PathParser<'a> {
+    chars: Peekable<Chars<'a>>,
+    path: Vec<PathCommand>,
+}
+
+macro_rules! parse_arguments {
+    (
+        $parser:ident,
+        $abs:ident,
+        $enum:ident,
+        [ $para:ident => $func:ident $(, $other_para:ident => $other_func:ident)* ]
+    ) => {
+        {
+            loop {
+                let $para = $func(&mut $parser.chars)?;
+                $(
+                    skip_comma_wsp(&mut $parser.chars);
+                    let $other_para = $other_func(&mut $parser.chars)?;
+                )*
+                $parser.path.push(PathCommand::$enum { $para $(, $other_para)*, $abs });
+
+                // End of string or the next character is a possible new command.
+                if !skip_wsp(&mut $parser.chars) ||
+                   $parser.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
+                    break;
+                }
+                skip_comma_wsp(&mut $parser.chars);
+            }
+            Ok(())
+        }
+    }
+}
+
+impl<'a> PathParser<'a> {
+    /// Parse a sub-path.
+    fn parse_subpath(&mut self) -> Result<(), ()> {
+        // Handle "moveto" Command first. If there is no "moveto", this is not a valid sub-path
+        // (i.e. not a valid moveto-drawto-command-group).
+        self.parse_moveto()?;
+
+        // Handle other commands.
+        loop {
+            skip_wsp(&mut self.chars);
+            if self.chars.peek().map_or(true, |m| *m == 'M' || *m == 'm') {
+                break;
+            }
+
+            match self.chars.next() {
+                Some(command) => {
+                    let abs = command.is_uppercase();
+                    macro_rules! parse_command {
+                        ( $($($p:pat)|+ => $parse_func:ident,)* ) => {
+                            match command {
+                                $(
+                                    $($p)|+ => {
+                                        skip_wsp(&mut self.chars);
+                                        self.$parse_func(abs)?;
+                                    },
+                                )*
+                                _ => return Err(()),
+                            }
+                        }
+                    }
+                    parse_command!(
+                        'Z' | 'z' => parse_closepath,
+                        'L' | 'l' => parse_lineto,
+                        'H' | 'h' => parse_h_lineto,
+                        'V' | 'v' => parse_v_lineto,
+                        'C' | 'c' => parse_curveto,
+                        'S' | 's' => parse_smooth_curveto,
+                        'Q' | 'q' => parse_quadratic_bezier_curveto,
+                        'T' | 't' => parse_smooth_quadratic_bezier_curveto,
+                        'A' | 'a' => parse_elliprical_arc,
+                    );
+                },
+                _ => break, // no more commands.
+            }
+        }
+        Ok(())
+    }
+
+    /// Parse "moveto" command.
+    fn parse_moveto(&mut self) -> Result<(), ()> {
+        let command = match self.chars.next() {
+            Some(c) if c == 'M' || c == 'm' => c,
+            _ => return Err(()),
+        };
+
+        skip_wsp(&mut self.chars);
+        let point = parse_coord(&mut self.chars)?;
+        let absolute = command == 'M';
+        self.path.push(PathCommand::MoveTo { point, absolute } );
+
+        // End of string or the next character is a possible new command.
+        if !skip_wsp(&mut self.chars) ||
+           self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
+            return Ok(());
+        }
+        skip_comma_wsp(&mut self.chars);
+
+        // If a moveto is followed by multiple pairs of coordinates, the subsequent
+        // pairs are treated as implicit lineto commands.
+        self.parse_lineto(absolute)
+    }
+
+    /// Parse "closepath" command.
+    fn parse_closepath(&mut self, _absolute: bool) -> Result<(), ()> {
+        self.path.push(PathCommand::ClosePath);
+        Ok(())
+    }
+
+    /// Parse "lineto" command.
+    fn parse_lineto(&mut self, absolute: bool) -> Result<(), ()> {
+        parse_arguments!(self, absolute, LineTo, [ point => parse_coord ])
+    }
+
+    /// Parse horizontal "lineto" command.
+    fn parse_h_lineto(&mut self, absolute: bool) -> Result<(), ()> {
+        parse_arguments!(self, absolute, HorizontalLineTo, [ x => parse_number ])
+    }
+
+    /// Parse vertical "lineto" command.
+    fn parse_v_lineto(&mut self, absolute: bool) -> Result<(), ()> {
+        parse_arguments!(self, absolute, VerticalLineTo, [ y => parse_number ])
+    }
+
+    /// Parse cubic Bézier curve command.
+    fn parse_curveto(&mut self, absolute: bool) -> Result<(), ()> {
+        parse_arguments!(self, absolute, CurveTo, [
+            control1 => parse_coord, control2 => parse_coord, point => parse_coord
+        ])
+    }
+
+    /// Parse smooth "curveto" command.
+    fn parse_smooth_curveto(&mut self, absolute: bool) -> Result<(), ()> {
+        parse_arguments!(self, absolute, SmoothCurveTo, [
+            control2 => parse_coord, point => parse_coord
+        ])
+    }
+
+    /// Parse quadratic Bézier curve command.
+    fn parse_quadratic_bezier_curveto(&mut self, absolute: bool) -> Result<(), ()> {
+        parse_arguments!(self, absolute, QuadBezierCurveTo, [
+            control1 => parse_coord, point => parse_coord
+        ])
+    }
+
+    /// Parse smooth quadratic Bézier curveto command.
+    fn parse_smooth_quadratic_bezier_curveto(&mut self, absolute: bool) -> Result<(), ()> {
+        parse_arguments!(self, absolute, SmoothQuadBezierCurveTo, [ point => parse_coord ])
+    }
+
+    /// Parse elliptical arc curve command.
+    fn parse_elliprical_arc(&mut self, absolute: bool) -> Result<(), ()> {
+        // Parse a flag whose value is '0' or '1'; otherwise, return Err(()).
+        let parse_flag = |iter: &mut Peekable<Chars>| -> Result<bool, ()> {
+            let value = match iter.peek() {
+                Some(c) if *c == '0' || *c == '1' => *c == '1',
+                _ => return Err(()),
+            };
+            iter.next();
+            Ok(value)
+        };
+        parse_arguments!(self, absolute, EllipticalArc, [
+            rx => parse_number,
+            ry => parse_number,
+            angle => parse_number,
+            large_arc_flag => parse_flag,
+            sweep_flag => parse_flag,
+            point => parse_coord
+        ])
+    }
+}
+
+/// The SVG path data.
+///
+/// https://www.w3.org/TR/SVG11/paths.html#PathData
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue)]
+pub struct SVGPathData(Box<[PathCommand]>);
+
+impl SVGPathData {
+    /// Return SVGPathData by a slice of PathCommand.
+    #[inline]
+    pub fn new(cmd: Box<[PathCommand]>) -> Self {
+        debug_assert!(!cmd.is_empty());
+        SVGPathData(cmd)
+    }
+
+    /// Get the array of PathCommand.
+    #[inline]
+    pub fn commands(&self) -> &[PathCommand] {
+        debug_assert!(!self.0.is_empty());
+        &self.0
+    }
+}
+
+impl ToCss for SVGPathData {
+    #[inline]
+    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+    where
+        W: fmt::Write
+    {
+        dest.write_char('"')?;
+        {
+            let mut writer = SequenceWriter::new(dest, " ");
+            for command in self.0.iter() {
+                writer.item(command)?;
+            }
+        }
+        dest.write_char('"')
+    }
+}
+
+impl Parse for SVGPathData {
+    // We cannot use cssparser::Parser to parse a SVG path string because the spec wants to make
+    // the SVG path string as compact as possible. (i.e. The whitespaces may be dropped.)
+    // e.g. "M100 200L100 200" is a valid SVG path string. If we use tokenizer, the first ident
+    // is "M100", instead of "M", and this is not correct. Therefore, we use a Peekable
+    // str::Char iterator to check each character.
+    fn parse<'i, 't>(
+        _context: &ParserContext,
+        input: &mut Parser<'i, 't>
+    ) -> Result<Self, ParseError<'i>> {
+        let location = input.current_source_location();
+        let path_string = input.expect_string()?.as_ref();
+        if path_string.is_empty() {
+            // Treat an empty string as invalid, so we will not set it.
+            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+        }
+
+        // Parse the svg path string as multiple sub-paths.
+        let mut path_parser = PathParser {
+            chars: path_string.chars().peekable(),
+            path: Vec::new(),
+        };
+        while skip_wsp(&mut path_parser.chars) {
+            if path_parser.parse_subpath().is_err() {
+                return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+            }
+        }
+
+        Ok(SVGPathData::new(path_parser.path.into_boxed_slice()))
+    }
+}
+
+
+/// The SVG path command.
+/// The fields of these commands are self-explanatory, so we skip the documents.
+/// Note: the index of the control points, e.g. control1, control2, are mapping to the control
+/// points of the Bézier curve in the spec.
+///
+/// https://www.w3.org/TR/SVG11/paths.html#PathData
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo)]
+#[allow(missing_docs)]
+#[repr(C, u8)]
+pub enum PathCommand {
+    /// The unknown type.
+    /// https://www.w3.org/TR/SVG/paths.html#__svg__SVGPathSeg__PATHSEG_UNKNOWN
+    Unknown,
+    /// The "moveto" command.
+    MoveTo { point: CoordPair, absolute: bool },
+    /// The "lineto" command.
+    LineTo { point: CoordPair, absolute: bool },
+    /// The horizontal "lineto" command.
+    HorizontalLineTo { x: CSSFloat, absolute: bool },
+    /// The vertical "lineto" command.
+    VerticalLineTo { y: CSSFloat, absolute: bool },
+    /// The cubic Bézier curve command.
+    CurveTo { control1: CoordPair, control2: CoordPair, point: CoordPair, absolute: bool },
+    /// The smooth curve command.
+    SmoothCurveTo { control2: CoordPair, point: CoordPair, absolute: bool },
+    /// The quadratic Bézier curve command.
+    QuadBezierCurveTo { control1: CoordPair, point: CoordPair, absolute: bool },
+    /// The smooth quadratic Bézier curve command.
+    SmoothQuadBezierCurveTo { point: CoordPair, absolute: bool },
+    /// The elliptical arc curve command.
+    EllipticalArc {
+        rx: CSSFloat,
+        ry: CSSFloat,
+        angle: CSSFloat,
+        large_arc_flag: bool,
+        sweep_flag: bool,
+        point: CoordPair,
+        absolute: bool
+    },
+    /// The "closepath" command.
+    ClosePath,
+}
+
+impl ToCss for PathCommand {
+    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+    where
+        W: fmt::Write
+    {
+        use self::PathCommand::*;
+        match *self {
+            Unknown => dest.write_str("X"),
+            ClosePath => dest.write_str("Z"),
+            MoveTo { point, absolute } => {
+                dest.write_char(if absolute { 'M' } else { 'm' })?;
+                dest.write_char(' ')?;
+                point.to_css(dest)
+            }
+            LineTo { point, absolute } => {
+                dest.write_char(if absolute { 'L' } else { 'l' })?;
+                dest.write_char(' ')?;
+                point.to_css(dest)
+            }
+            CurveTo { control1, control2, point, absolute } => {
+                dest.write_char(if absolute { 'C' } else { 'c' })?;
+                dest.write_char(' ')?;
+                control1.to_css(dest)?;
+                dest.write_char(' ')?;
+                control2.to_css(dest)?;
+                dest.write_char(' ')?;
+                point.to_css(dest)
+            },
+            QuadBezierCurveTo { control1, point, absolute } => {
+                dest.write_char(if absolute { 'Q' } else { 'q' })?;
+                dest.write_char(' ')?;
+                control1.to_css(dest)?;
+                dest.write_char(' ')?;
+                point.to_css(dest)
+            },
+            EllipticalArc { rx, ry, angle, large_arc_flag, sweep_flag, point, absolute } => {
+                dest.write_char(if absolute { 'A' } else { 'a' })?;
+                dest.write_char(' ')?;
+                rx.to_css(dest)?;
+                dest.write_char(' ')?;
+                ry.to_css(dest)?;
+                dest.write_char(' ')?;
+                angle.to_css(dest)?;
+                dest.write_char(' ')?;
+                (large_arc_flag as i32).to_css(dest)?;
+                dest.write_char(' ')?;
+                (sweep_flag as i32).to_css(dest)?;
+                dest.write_char(' ')?;
+                point.to_css(dest)
+            },
+            HorizontalLineTo { x, absolute } => {
+                dest.write_char(if absolute { 'H' } else { 'h' })?;
+                dest.write_char(' ')?;
+                x.to_css(dest)
+            },
+            VerticalLineTo { y, absolute } => {
+                dest.write_char(if absolute { 'V' } else { 'v' })?;
+                dest.write_char(' ')?;
+                y.to_css(dest)
+            },
+            SmoothCurveTo { control2, point, absolute } => {
+                dest.write_char(if absolute { 'S' } else { 's' })?;
+                dest.write_char(' ')?;
+                control2.to_css(dest)?;
+                dest.write_char(' ')?;
+                point.to_css(dest)
+            },
+            SmoothQuadBezierCurveTo { point, absolute } => {
+                dest.write_char(if absolute { 'T' } else { 't' })?;
+                dest.write_char(' ')?;
+                point.to_css(dest)
+            },
+        }
+    }
+}
+
+/// The path coord type.
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss)]
+#[repr(C)]
+pub struct CoordPair(CSSFloat, CSSFloat);
+
+impl CoordPair {
+    /// Create a CoordPair.
+    #[inline]
+    pub fn new(x: CSSFloat, y: CSSFloat) -> Self {
+        CoordPair(x, y)
+    }
+}
+
+/// Parse a pair of numbers into CoordPair.
+fn parse_coord(iter: &mut Peekable<Chars>) -> Result<CoordPair, ()> {
+    let x = parse_number(iter)?;
+    skip_comma_wsp(iter);
+    let y = parse_number(iter)?;
+    Ok(CoordPair::new(x, y))
+}
+
+/// This is a special version which parses the number for SVG Path. e.g. "M 0.6.5" should be parsed
+/// as MoveTo with a coordinate of ("0.6", ".5"), instead of treating 0.6.5 as a non-valid floating
+/// point number. In other words, the logic here is similar with that of
+/// tokenizer::consume_numeric, which also consumes the number as many as possible, but here the
+/// input is a Peekable and we only accept an integer of a floating point number.
+///
+/// The "number" syntax in https://www.w3.org/TR/SVG/paths.html#PathDataBNF
+fn parse_number(iter: &mut Peekable<Chars>) -> Result<CSSFloat, ()> {
+    // 1. Check optional sign.
+    let sign = if iter.peek().map_or(false, |&sign: &char| sign == '+' || sign == '-') {
+        if iter.next().unwrap() == '-' { -1. } else { 1. }
+    } else {
+        1.
+    };
+
+    // 2. Check integer part.
+    let mut integral_part: f64 = 0.;
+    let got_dot = if !iter.peek().map_or(false, |&n: &char| n == '.') {
+        // If the first digit in integer part is neither a dot nor a digit, this is not a number.
+        if iter.peek().map_or(true, |n: &char| !n.is_ascii_digit()) {
+            return Err(());
+        }
+
+        while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) {
+            integral_part =
+                integral_part * 10. + iter.next().unwrap().to_digit(10).unwrap() as f64;
+        }
+
+        iter.peek().map_or(false, |&n: &char| n == '.')
+    } else {
+        true
+    };
+
+    // 3. Check fractional part.
+    let mut fractional_part: f64 = 0.;
+    if got_dot {
+        // Consume '.'.
+        iter.next();
+        // If the first digit in fractional part is not a digit, this is not a number.
+        if iter.peek().map_or(true, |n: &char| !n.is_ascii_digit()) {
+            return Err(());
+        }
+
+        let mut factor = 0.1;
+        while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) {
+            fractional_part += iter.next().unwrap().to_digit(10).unwrap() as f64 * factor;
+            factor *= 0.1;
+        }
+    }
+
+    let mut value = sign * (integral_part + fractional_part);
+
+    // 4. Check exp part. The segment name of SVG Path doesn't include 'E' or 'e', so it's ok to
+    //    treat the numbers after 'E' or 'e' are in the exponential part.
+    if iter.peek().map_or(false, |&exp: &char| exp == 'E' || exp == 'e') {
+        // Consume 'E' or 'e'.
+        iter.next();
+        let exp_sign = if iter.peek().map_or(false, |&sign: &char| sign == '+' || sign == '-') {
+            if iter.next().unwrap() == '-' { -1. } else { 1. }
+        } else {
+            1.
+        };
+
+        let mut exp: f64 = 0.;
+        while iter.peek().map_or(false, |n: &char| n.is_ascii_digit()) {
+            exp = exp * 10. + iter.next().unwrap().to_digit(10).unwrap() as f64;
+        }
+
+        value *= f64::powf(10., exp * exp_sign);
+    }
+
+    if value.is_finite() {
+        Ok(value.min(::std::f32::MAX as f64).max(::std::f32::MIN as f64) as CSSFloat)
+    } else {
+        Err(())
+    }
+}
+
+/// Skip all svg whitespaces, and return true if |iter| hasn't finished.
+#[inline]
+fn skip_wsp(iter: &mut Peekable<Chars>) -> bool {
+    // Note: SVG 1.1 defines the whitespaces as \u{9}, \u{20}, \u{A}, \u{D}.
+    //       However, SVG 2 has one extra whitespace: \u{C}.
+    //       Therefore, we follow the newest spec for the definition of whitespace,
+    //       i.e. \u{9}, \u{20}, \u{A}, \u{C}, \u{D}, by is_ascii_whitespace().
+    while iter.peek().map_or(false, |c: &char| c.is_ascii_whitespace()) {
+        iter.next();
+    }
+    iter.peek().is_some()
+}
+
+/// Skip all svg whitespaces and one comma, and return true if |iter| hasn't finished.
+#[inline]
+fn skip_comma_wsp(iter: &mut Peekable<Chars>) -> bool {
+    if !skip_wsp(iter) {
+        return false;
+    }
+
+    if *iter.peek().unwrap() != ',' {
+        return true;
+    }
+    iter.next();
+
+    skip_wsp(iter)
+}
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -165098,19 +165098,31 @@
       [
        "/css/motion/offset-path-ray-ref.html",
        "=="
       ]
      ],
      {}
     ]
    ],
-   "css/motion/offset-path-string.html": [
-    [
-     "/css/motion/offset-path-string.html",
+   "css/motion/offset-path-string-001.html": [
+    [
+     "/css/motion/offset-path-string-001.html",
+     [
+      [
+       "/css/motion/offset-path-string-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/motion/offset-path-string-002.html": [
+    [
+     "/css/motion/offset-path-string-002.html",
      [
       [
        "/css/motion/offset-path-string-ref.html",
        "=="
       ]
      ],
      {}
     ]
@@ -568446,24 +568458,28 @@
   "css/motion/offset-path-ray-ref.html": [
    "fde97bd6b15cca64c06cd305822ad87dc008410f",
    "support"
   ],
   "css/motion/offset-path-ray.html": [
    "6c39e7b8f4cfafe05c07d166eb65570432912b7a",
    "reftest"
   ],
+  "css/motion/offset-path-string-001.html": [
+   "79d957d82b8e3c603ed16598f461a805c90681dd",
+   "reftest"
+  ],
+  "css/motion/offset-path-string-002.html": [
+   "0d2fcbbb661c2fe0e5b57ff780d78b2f8b6f627b",
+   "reftest"
+  ],
   "css/motion/offset-path-string-ref.html": [
    "5c5ff5f6f2ddc4696f2d51266199fe052464d9e6",
    "support"
   ],
-  "css/motion/offset-path-string.html": [
-   "79d957d82b8e3c603ed16598f461a805c90681dd",
-   "reftest"
-  ],
   "css/motion/offset-rotate-001.html": [
    "55147698a7f2f02a57f0fe3adc8b33257d1e212f",
    "reftest"
   ],
   "css/motion/offset-rotate-002.html": [
    "fb301be24efc2aa2e50da0aabe6009553b92b655",
    "reftest"
   ],
@@ -568495,21 +568511,21 @@
    "343d22e46b4714dde6f484b37ae8d3fd8772460a",
    "testharness"
   ],
   "css/motion/parsing/offset-parsing-valid.html": [
    "b645199f3a13015941648df08d8583b9a7fc7fed",
    "testharness"
   ],
   "css/motion/parsing/offset-path-parsing-invalid.html": [
-   "c0a32486922b4b1b482817f409571e1e6c4219f7",
+   "7fbd06a508a322ac0969eb11c4299de50fd254e7",
    "testharness"
   ],
   "css/motion/parsing/offset-path-parsing-valid.html": [
-   "c1e229e1a05a4c85845384ace9b884125f579415",
+   "e7797686e4ac524ac9dc9f8525dbd5a24adeec29",
    "testharness"
   ],
   "css/motion/parsing/offset-position-parsing-invalid.html": [
    "42370d44a38c2618d7f556d6be4b7a206e76b7e7",
    "testharness"
   ],
   "css/motion/parsing/offset-position-parsing-valid.html": [
    "3cf235cc855fc7e1325610ce4170974b746f1182",
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/motion/__dir__.ini
@@ -0,0 +1,1 @@
+prefs: [layout.css.motion-path.enabled:true]
--- a/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-001.html.ini
+++ b/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-001.html.ini
@@ -1,12 +1,9 @@
 [offset-path-interpolation-001.html]
-  ["path('M 0 0 H 1 H 2')" and "path('M 0 0 H 3')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('M 0 0 H 1 H 2')" and "path('M 0 0 H 3')" at progress -1]
     expected: FAIL
 
   [Animation between "path('M 0 0 H 1 H 2')" and "path('M 0 0 H 3')" at progress 0]
     expected: FAIL
 
   [Animation between "path('M 0 0 H 1 H 2')" and "path('M 0 0 H 3')" at progress 0.125]
     expected: FAIL
@@ -15,40 +12,25 @@
     expected: FAIL
 
   [Animation between "path('M 0 0 H 1 H 2')" and "path('M 0 0 H 3')" at progress 1]
     expected: FAIL
 
   [Animation between "path('M 0 0 H 1 H 2')" and "path('M 0 0 H 3')" at progress 2]
     expected: FAIL
 
-  ["path('M 1 2 L 3 4 Z')" and "none" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('M 1 2 L 3 4 Z')" and "none" at progress -1]
     expected: FAIL
 
   [Animation between "path('M 1 2 L 3 4 Z')" and "none" at progress 0]
     expected: FAIL
 
   [Animation between "path('M 1 2 L 3 4 Z')" and "none" at progress 0.125]
     expected: FAIL
 
-  [Animation between "path('M 1 2 L 3 4 Z')" and "none" at progress 0.875]
-    expected: FAIL
-
-  [Animation between "path('M 1 2 L 3 4 Z')" and "none" at progress 1]
-    expected: FAIL
-
-  [Animation between "path('M 1 2 L 3 4 Z')" and "none" at progress 2]
-    expected: FAIL
-
-  ["path('M 10 0 H 11')" and "path('M 20 0 V 2')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('M 10 0 H 11')" and "path('M 20 0 V 2')" at progress -1]
     expected: FAIL
 
   [Animation between "path('M 10 0 H 11')" and "path('M 20 0 V 2')" at progress 0]
     expected: FAIL
 
   [Animation between "path('M 10 0 H 11')" and "path('M 20 0 V 2')" at progress 0.125]
     expected: FAIL
@@ -57,19 +39,16 @@
     expected: FAIL
 
   [Animation between "path('M 10 0 H 11')" and "path('M 20 0 V 2')" at progress 1]
     expected: FAIL
 
   [Animation between "path('M 10 0 H 11')" and "path('M 20 0 V 2')" at progress 2]
     expected: FAIL
 
-  ["path('M 1 2 L 4 6 Z')" and "path('M 1 2 H 4 V 6')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('M 1 2 L 4 6 Z')" and "path('M 1 2 H 4 V 6')" at progress -1]
     expected: FAIL
 
   [Animation between "path('M 1 2 L 4 6 Z')" and "path('M 1 2 H 4 V 6')" at progress 0]
     expected: FAIL
 
   [Animation between "path('M 1 2 L 4 6 Z')" and "path('M 1 2 H 4 V 6')" at progress 0.125]
     expected: FAIL
@@ -78,19 +57,16 @@
     expected: FAIL
 
   [Animation between "path('M 1 2 L 4 6 Z')" and "path('M 1 2 H 4 V 6')" at progress 1]
     expected: FAIL
 
   [Animation between "path('M 1 2 L 4 6 Z')" and "path('M 1 2 H 4 V 6')" at progress 2]
     expected: FAIL
 
-  ["path('M 0 0 Z')" and "path('M 0 0 Z')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('M 0 0 Z')" and "path('M 0 0 Z')" at progress -1]
     expected: FAIL
 
   [Animation between "path('M 0 0 Z')" and "path('M 0 0 Z')" at progress 0]
     expected: FAIL
 
   [Animation between "path('M 0 0 Z')" and "path('M 0 0 Z')" at progress 0.125]
     expected: FAIL
@@ -99,19 +75,16 @@
     expected: FAIL
 
   [Animation between "path('M 0 0 Z')" and "path('M 0 0 Z')" at progress 1]
     expected: FAIL
 
   [Animation between "path('M 0 0 Z')" and "path('M 0 0 Z')" at progress 2]
     expected: FAIL
 
-  ["path('M 20 70')" and "path('M 100 30')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('M 20 70')" and "path('M 100 30')" at progress -1]
     expected: FAIL
 
   [Animation between "path('M 20 70')" and "path('M 100 30')" at progress 0]
     expected: FAIL
 
   [Animation between "path('M 20 70')" and "path('M 100 30')" at progress 0.125]
     expected: FAIL
@@ -120,19 +93,16 @@
     expected: FAIL
 
   [Animation between "path('M 20 70')" and "path('M 100 30')" at progress 1]
     expected: FAIL
 
   [Animation between "path('M 20 70')" and "path('M 100 30')" at progress 2]
     expected: FAIL
 
-  ["path('m 20 70')" and "path('m 100 30')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('m 20 70')" and "path('m 100 30')" at progress -1]
     expected: FAIL
 
   [Animation between "path('m 20 70')" and "path('m 100 30')" at progress 0]
     expected: FAIL
 
   [Animation between "path('m 20 70')" and "path('m 100 30')" at progress 0.125]
     expected: FAIL
@@ -141,19 +111,16 @@
     expected: FAIL
 
   [Animation between "path('m 20 70')" and "path('m 100 30')" at progress 1]
     expected: FAIL
 
   [Animation between "path('m 20 70')" and "path('m 100 30')" at progress 2]
     expected: FAIL
 
-  ["path('m 100 200 L 120 270')" and "path('m 100 200 L 200 230')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('m 100 200 L 120 270')" and "path('m 100 200 L 200 230')" at progress -1]
     expected: FAIL
 
   [Animation between "path('m 100 200 L 120 270')" and "path('m 100 200 L 200 230')" at progress 0]
     expected: FAIL
 
   [Animation between "path('m 100 200 L 120 270')" and "path('m 100 200 L 200 230')" at progress 0.125]
     expected: FAIL
@@ -162,19 +129,16 @@
     expected: FAIL
 
   [Animation between "path('m 100 200 L 120 270')" and "path('m 100 200 L 200 230')" at progress 1]
     expected: FAIL
 
   [Animation between "path('m 100 200 L 120 270')" and "path('m 100 200 L 200 230')" at progress 2]
     expected: FAIL
 
-  ["path('m 100 200 l 20 70')" and "path('m 100 200 l 100 30')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('m 100 200 l 20 70')" and "path('m 100 200 l 100 30')" at progress -1]
     expected: FAIL
 
   [Animation between "path('m 100 200 l 20 70')" and "path('m 100 200 l 100 30')" at progress 0]
     expected: FAIL
 
   [Animation between "path('m 100 200 l 20 70')" and "path('m 100 200 l 100 30')" at progress 0.125]
     expected: FAIL
--- a/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-002.html.ini
+++ b/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-002.html.ini
@@ -1,12 +1,9 @@
 [offset-path-interpolation-002.html]
-  ["path('M 20 10 C 32 42 52 62 120 2200')" and "path('M 20 10 C 40 50 60 70 200 3000')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('M 20 10 C 32 42 52 62 120 2200')" and "path('M 20 10 C 40 50 60 70 200 3000')" at progress -1]
     expected: FAIL
 
   [Animation between "path('M 20 10 C 32 42 52 62 120 2200')" and "path('M 20 10 C 40 50 60 70 200 3000')" at progress 0]
     expected: FAIL
 
   [Animation between "path('M 20 10 C 32 42 52 62 120 2200')" and "path('M 20 10 C 40 50 60 70 200 3000')" at progress 0.125]
     expected: FAIL
@@ -15,19 +12,16 @@
     expected: FAIL
 
   [Animation between "path('M 20 10 C 32 42 52 62 120 2200')" and "path('M 20 10 C 40 50 60 70 200 3000')" at progress 1]
     expected: FAIL
 
   [Animation between "path('M 20 10 C 32 42 52 62 120 2200')" and "path('M 20 10 C 40 50 60 70 200 3000')" at progress 2]
     expected: FAIL
 
-  ["path('m 20 10 c 12 32 32 52 100 2190')" and "path('m 20 10 c 20 40 40 60 180 2990')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('m 20 10 c 12 32 32 52 100 2190')" and "path('m 20 10 c 20 40 40 60 180 2990')" at progress -1]
     expected: FAIL
 
   [Animation between "path('m 20 10 c 12 32 32 52 100 2190')" and "path('m 20 10 c 20 40 40 60 180 2990')" at progress 0]
     expected: FAIL
 
   [Animation between "path('m 20 10 c 12 32 32 52 100 2190')" and "path('m 20 10 c 20 40 40 60 180 2990')" at progress 0.125]
     expected: FAIL
@@ -36,19 +30,16 @@
     expected: FAIL
 
   [Animation between "path('m 20 10 c 12 32 32 52 100 2190')" and "path('m 20 10 c 20 40 40 60 180 2990')" at progress 1]
     expected: FAIL
 
   [Animation between "path('m 20 10 c 12 32 32 52 100 2190')" and "path('m 20 10 c 20 40 40 60 180 2990')" at progress 2]
     expected: FAIL
 
-  ["path('M 20 10 Q 32 42 120 2200')" and "path('M 20 10 Q 40 50 200 3000')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('M 20 10 Q 32 42 120 2200')" and "path('M 20 10 Q 40 50 200 3000')" at progress -1]
     expected: FAIL
 
   [Animation between "path('M 20 10 Q 32 42 120 2200')" and "path('M 20 10 Q 40 50 200 3000')" at progress 0]
     expected: FAIL
 
   [Animation between "path('M 20 10 Q 32 42 120 2200')" and "path('M 20 10 Q 40 50 200 3000')" at progress 0.125]
     expected: FAIL
@@ -57,19 +48,16 @@
     expected: FAIL
 
   [Animation between "path('M 20 10 Q 32 42 120 2200')" and "path('M 20 10 Q 40 50 200 3000')" at progress 1]
     expected: FAIL
 
   [Animation between "path('M 20 10 Q 32 42 120 2200')" and "path('M 20 10 Q 40 50 200 3000')" at progress 2]
     expected: FAIL
 
-  ["path('m 20 10 q 12 32 100 2190')" and "path('m 20 10 q 20 40 180 2990')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('m 20 10 q 12 32 100 2190')" and "path('m 20 10 q 20 40 180 2990')" at progress -1]
     expected: FAIL
 
   [Animation between "path('m 20 10 q 12 32 100 2190')" and "path('m 20 10 q 20 40 180 2990')" at progress 0]
     expected: FAIL
 
   [Animation between "path('m 20 10 q 12 32 100 2190')" and "path('m 20 10 q 20 40 180 2990')" at progress 0.125]
     expected: FAIL
@@ -78,19 +66,16 @@
     expected: FAIL
 
   [Animation between "path('m 20 10 q 12 32 100 2190')" and "path('m 20 10 q 20 40 180 2990')" at progress 1]
     expected: FAIL
 
   [Animation between "path('m 20 10 q 12 32 100 2190')" and "path('m 20 10 q 20 40 180 2990')" at progress 2]
     expected: FAIL
 
-  ["path('M 100 400 A 10 20 30 1 0 140 450')" and "path('M 300 200 A 50 60 70 0 1 380 290')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('M 100 400 A 10 20 30 1 0 140 450')" and "path('M 300 200 A 50 60 70 0 1 380 290')" at progress -1]
     expected: FAIL
 
   [Animation between "path('M 100 400 A 10 20 30 1 0 140 450')" and "path('M 300 200 A 50 60 70 0 1 380 290')" at progress 0]
     expected: FAIL
 
   [Animation between "path('M 100 400 A 10 20 30 1 0 140 450')" and "path('M 300 200 A 50 60 70 0 1 380 290')" at progress 0.125]
     expected: FAIL
@@ -99,19 +84,16 @@
     expected: FAIL
 
   [Animation between "path('M 100 400 A 10 20 30 1 0 140 450')" and "path('M 300 200 A 50 60 70 0 1 380 290')" at progress 1]
     expected: FAIL
 
   [Animation between "path('M 100 400 A 10 20 30 1 0 140 450')" and "path('M 300 200 A 50 60 70 0 1 380 290')" at progress 2]
     expected: FAIL
 
-  ["path('m 100 400 a 10 20 30 1 0 40 50')" and "path('m 300 200 a 50 60 70 0 1 80 90')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('m 100 400 a 10 20 30 1 0 40 50')" and "path('m 300 200 a 50 60 70 0 1 80 90')" at progress -1]
     expected: FAIL
 
   [Animation between "path('m 100 400 a 10 20 30 1 0 40 50')" and "path('m 300 200 a 50 60 70 0 1 80 90')" at progress 0]
     expected: FAIL
 
   [Animation between "path('m 100 400 a 10 20 30 1 0 40 50')" and "path('m 300 200 a 50 60 70 0 1 80 90')" at progress 0.125]
     expected: FAIL
--- a/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-003.html.ini
+++ b/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-003.html.ini
@@ -1,12 +1,9 @@
 [offset-path-interpolation-003.html]
-  ["path('M 50 60 H 70')" and "path('M 10 140 H 270')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('M 50 60 H 70')" and "path('M 10 140 H 270')" at progress -1]
     expected: FAIL
 
   [Animation between "path('M 50 60 H 70')" and "path('M 10 140 H 270')" at progress 0]
     expected: FAIL
 
   [Animation between "path('M 50 60 H 70')" and "path('M 10 140 H 270')" at progress 0.125]
     expected: FAIL
@@ -15,19 +12,16 @@
     expected: FAIL
 
   [Animation between "path('M 50 60 H 70')" and "path('M 10 140 H 270')" at progress 1]
     expected: FAIL
 
   [Animation between "path('M 50 60 H 70')" and "path('M 10 140 H 270')" at progress 2]
     expected: FAIL
 
-  ["path('m 50 60 h 20')" and "path('m 10 140 h 260')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('m 50 60 h 20')" and "path('m 10 140 h 260')" at progress -1]
     expected: FAIL
 
   [Animation between "path('m 50 60 h 20')" and "path('m 10 140 h 260')" at progress 0]
     expected: FAIL
 
   [Animation between "path('m 50 60 h 20')" and "path('m 10 140 h 260')" at progress 0.125]
     expected: FAIL
@@ -36,19 +30,16 @@
     expected: FAIL
 
   [Animation between "path('m 50 60 h 20')" and "path('m 10 140 h 260')" at progress 1]
     expected: FAIL
 
   [Animation between "path('m 50 60 h 20')" and "path('m 10 140 h 260')" at progress 2]
     expected: FAIL
 
-  ["path('M 50 60 V 70')" and "path('M 10 140 V 270')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('M 50 60 V 70')" and "path('M 10 140 V 270')" at progress -1]
     expected: FAIL
 
   [Animation between "path('M 50 60 V 70')" and "path('M 10 140 V 270')" at progress 0]
     expected: FAIL
 
   [Animation between "path('M 50 60 V 70')" and "path('M 10 140 V 270')" at progress 0.125]
     expected: FAIL
@@ -57,19 +48,16 @@
     expected: FAIL
 
   [Animation between "path('M 50 60 V 70')" and "path('M 10 140 V 270')" at progress 1]
     expected: FAIL
 
   [Animation between "path('M 50 60 V 70')" and "path('M 10 140 V 270')" at progress 2]
     expected: FAIL
 
-  ["path('m 50 60 v 10')" and "path('m 10 140 v 130')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('m 50 60 v 10')" and "path('m 10 140 v 130')" at progress -1]
     expected: FAIL
 
   [Animation between "path('m 50 60 v 10')" and "path('m 10 140 v 130')" at progress 0]
     expected: FAIL
 
   [Animation between "path('m 50 60 v 10')" and "path('m 10 140 v 130')" at progress 0.125]
     expected: FAIL
@@ -78,19 +66,16 @@
     expected: FAIL
 
   [Animation between "path('m 50 60 v 10')" and "path('m 10 140 v 130')" at progress 1]
     expected: FAIL
 
   [Animation between "path('m 50 60 v 10')" and "path('m 10 140 v 130')" at progress 2]
     expected: FAIL
 
-  ["path('M 12 34 S 45 67 89 123')" and "path('M 20 26 S 61 51 113 99')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('M 12 34 S 45 67 89 123')" and "path('M 20 26 S 61 51 113 99')" at progress -1]
     expected: FAIL
 
   [Animation between "path('M 12 34 S 45 67 89 123')" and "path('M 20 26 S 61 51 113 99')" at progress 0]
     expected: FAIL
 
   [Animation between "path('M 12 34 S 45 67 89 123')" and "path('M 20 26 S 61 51 113 99')" at progress 0.125]
     expected: FAIL
@@ -99,19 +84,16 @@
     expected: FAIL
 
   [Animation between "path('M 12 34 S 45 67 89 123')" and "path('M 20 26 S 61 51 113 99')" at progress 1]
     expected: FAIL
 
   [Animation between "path('M 12 34 S 45 67 89 123')" and "path('M 20 26 S 61 51 113 99')" at progress 2]
     expected: FAIL
 
-  ["path('m 12 34 s 33 33 77 89')" and "path('m 20 26 s 41 25 93 73')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('m 12 34 s 33 33 77 89')" and "path('m 20 26 s 41 25 93 73')" at progress -1]
     expected: FAIL
 
   [Animation between "path('m 12 34 s 33 33 77 89')" and "path('m 20 26 s 41 25 93 73')" at progress 0]
     expected: FAIL
 
   [Animation between "path('m 12 34 s 33 33 77 89')" and "path('m 20 26 s 41 25 93 73')" at progress 0.125]
     expected: FAIL
@@ -120,19 +102,16 @@
     expected: FAIL
 
   [Animation between "path('m 12 34 s 33 33 77 89')" and "path('m 20 26 s 41 25 93 73')" at progress 1]
     expected: FAIL
 
   [Animation between "path('m 12 34 s 33 33 77 89')" and "path('m 20 26 s 41 25 93 73')" at progress 2]
     expected: FAIL
 
-  ["path('M 12 34 T 45 67')" and "path('M 20 26 T 61 51')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('M 12 34 T 45 67')" and "path('M 20 26 T 61 51')" at progress -1]
     expected: FAIL
 
   [Animation between "path('M 12 34 T 45 67')" and "path('M 20 26 T 61 51')" at progress 0]
     expected: FAIL
 
   [Animation between "path('M 12 34 T 45 67')" and "path('M 20 26 T 61 51')" at progress 0.125]
     expected: FAIL
@@ -141,19 +120,16 @@
     expected: FAIL
 
   [Animation between "path('M 12 34 T 45 67')" and "path('M 20 26 T 61 51')" at progress 1]
     expected: FAIL
 
   [Animation between "path('M 12 34 T 45 67')" and "path('M 20 26 T 61 51')" at progress 2]
     expected: FAIL
 
-  ["path('m 12 34 t 33 33')" and "path('m 20 26 t 41 25')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('m 12 34 t 33 33')" and "path('m 20 26 t 41 25')" at progress -1]
     expected: FAIL
 
   [Animation between "path('m 12 34 t 33 33')" and "path('m 20 26 t 41 25')" at progress 0]
     expected: FAIL
 
   [Animation between "path('m 12 34 t 33 33')" and "path('m 20 26 t 41 25')" at progress 0.125]
     expected: FAIL
--- a/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-004.html.ini
+++ b/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-004.html.ini
@@ -1,12 +1,9 @@
 [offset-path-interpolation-004.html]
-  ["path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')" and "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 300 100 z')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')" and "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 300 100 z')" at progress -1]
     expected: FAIL
 
   [Animation between "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')" and "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 300 100 z')" at progress 0]
     expected: FAIL
 
   [Animation between "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')" and "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 300 100 z')" at progress 0.125]
     expected: FAIL
@@ -15,19 +12,16 @@
     expected: FAIL
 
   [Animation between "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')" and "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 300 100 z')" at progress 1]
     expected: FAIL
 
   [Animation between "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')" and "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 300 100 z')" at progress 2]
     expected: FAIL
 
-  ["path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')" and "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 100 -100 z')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')" and "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 100 -100 z')" at progress -1]
     expected: FAIL
 
   [Animation between "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')" and "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 100 -100 z')" at progress 0]
     expected: FAIL
 
   [Animation between "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')" and "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 100 -100 z')" at progress 0.125]
     expected: FAIL
@@ -36,19 +30,16 @@
     expected: FAIL
 
   [Animation between "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')" and "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 100 -100 z')" at progress 1]
     expected: FAIL
 
   [Animation between "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')" and "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 100 -100 z')" at progress 2]
     expected: FAIL
 
-  ["path('m 10 20 l 40 50 z l 40 60 z m 60 70 l 90 60 z t 70 130')" and "path('M 210 220 L 170 190 Z L 90 120 Z M 110 130 L 200 230 Z T 220 220')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('m 10 20 l 40 50 z l 40 60 z m 60 70 l 90 60 z t 70 130')" and "path('M 210 220 L 170 190 Z L 90 120 Z M 110 130 L 200 230 Z T 220 220')" at progress -1]
     expected: FAIL
 
   [Animation between "path('m 10 20 l 40 50 z l 40 60 z m 60 70 l 90 60 z t 70 130')" and "path('M 210 220 L 170 190 Z L 90 120 Z M 110 130 L 200 230 Z T 220 220')" at progress 0]
     expected: FAIL
 
   [Animation between "path('m 10 20 l 40 50 z l 40 60 z m 60 70 l 90 60 z t 70 130')" and "path('M 210 220 L 170 190 Z L 90 120 Z M 110 130 L 200 230 Z T 220 220')" at progress 0.125]
     expected: FAIL
@@ -57,19 +48,16 @@
     expected: FAIL
 
   [Animation between "path('m 10 20 l 40 50 z l 40 60 z m 60 70 l 90 60 z t 70 130')" and "path('M 210 220 L 170 190 Z L 90 120 Z M 110 130 L 200 230 Z T 220 220')" at progress 1]
     expected: FAIL
 
   [Animation between "path('m 10 20 l 40 50 z l 40 60 z m 60 70 l 90 60 z t 70 130')" and "path('M 210 220 L 170 190 Z L 90 120 Z M 110 130 L 200 230 Z T 220 220')" at progress 2]
     expected: FAIL
 
-  ["path('m 10 20 c 40 50 30 60 80 70 c 120 130 170 140 110 160')" and "path('M 130 100 C 130 150 120 160 210 170 C 290 300 340 310 320 330')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('m 10 20 c 40 50 30 60 80 70 c 120 130 170 140 110 160')" and "path('M 130 100 C 130 150 120 160 210 170 C 290 300 340 310 320 330')" at progress -1]
     expected: FAIL
 
   [Animation between "path('m 10 20 c 40 50 30 60 80 70 c 120 130 170 140 110 160')" and "path('M 130 100 C 130 150 120 160 210 170 C 290 300 340 310 320 330')" at progress 0]
     expected: FAIL
 
   [Animation between "path('m 10 20 c 40 50 30 60 80 70 c 120 130 170 140 110 160')" and "path('M 130 100 C 130 150 120 160 210 170 C 290 300 340 310 320 330')" at progress 0.125]
     expected: FAIL
@@ -78,19 +66,16 @@
     expected: FAIL
 
   [Animation between "path('m 10 20 c 40 50 30 60 80 70 c 120 130 170 140 110 160')" and "path('M 130 100 C 130 150 120 160 210 170 C 290 300 340 310 320 330')" at progress 1]
     expected: FAIL
 
   [Animation between "path('m 10 20 c 40 50 30 60 80 70 c 120 130 170 140 110 160')" and "path('M 130 100 C 130 150 120 160 210 170 C 290 300 340 310 320 330')" at progress 2]
     expected: FAIL
 
-  ["path('m 10 20 q 30 60 40 50 q 110 80 90 80')" and "path('M 130 100 Q 120 160 130 150 Q 200 150 180 190')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('m 10 20 q 30 60 40 50 q 110 80 90 80')" and "path('M 130 100 Q 120 160 130 150 Q 200 150 180 190')" at progress -1]
     expected: FAIL
 
   [Animation between "path('m 10 20 q 30 60 40 50 q 110 80 90 80')" and "path('M 130 100 Q 120 160 130 150 Q 200 150 180 190')" at progress 0]
     expected: FAIL
 
   [Animation between "path('m 10 20 q 30 60 40 50 q 110 80 90 80')" and "path('M 130 100 Q 120 160 130 150 Q 200 150 180 190')" at progress 0.125]
     expected: FAIL
@@ -99,19 +84,16 @@
     expected: FAIL
 
   [Animation between "path('m 10 20 q 30 60 40 50 q 110 80 90 80')" and "path('M 130 100 Q 120 160 130 150 Q 200 150 180 190')" at progress 1]
     expected: FAIL
 
   [Animation between "path('m 10 20 q 30 60 40 50 q 110 80 90 80')" and "path('M 130 100 Q 120 160 130 150 Q 200 150 180 190')" at progress 2]
     expected: FAIL
 
-  ["path('m 10 20 s 30 60 40 50 s 110 60 90 70')" and "path('M 130 140 S 120 160 130 150 S 200 170 140 180')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('m 10 20 s 30 60 40 50 s 110 60 90 70')" and "path('M 130 140 S 120 160 130 150 S 200 170 140 180')" at progress -1]
     expected: FAIL
 
   [Animation between "path('m 10 20 s 30 60 40 50 s 110 60 90 70')" and "path('M 130 140 S 120 160 130 150 S 200 170 140 180')" at progress 0]
     expected: FAIL
 
   [Animation between "path('m 10 20 s 30 60 40 50 s 110 60 90 70')" and "path('M 130 140 S 120 160 130 150 S 200 170 140 180')" at progress 0.125]
     expected: FAIL
@@ -120,19 +102,16 @@
     expected: FAIL
 
   [Animation between "path('m 10 20 s 30 60 40 50 s 110 60 90 70')" and "path('M 130 140 S 120 160 130 150 S 200 170 140 180')" at progress 1]
     expected: FAIL
 
   [Animation between "path('m 10 20 s 30 60 40 50 s 110 60 90 70')" and "path('M 130 140 S 120 160 130 150 S 200 170 140 180')" at progress 2]
     expected: FAIL
 
-  ["path('m 10 20 h 30 v 60 h 10 v -10 l 110 60')" and "path('M 130 140 H 120 V 160 H 130 V 150 L 200 170')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('m 10 20 h 30 v 60 h 10 v -10 l 110 60')" and "path('M 130 140 H 120 V 160 H 130 V 150 L 200 170')" at progress -1]
     expected: FAIL
 
   [Animation between "path('m 10 20 h 30 v 60 h 10 v -10 l 110 60')" and "path('M 130 140 H 120 V 160 H 130 V 150 L 200 170')" at progress 0]
     expected: FAIL
 
   [Animation between "path('m 10 20 h 30 v 60 h 10 v -10 l 110 60')" and "path('M 130 140 H 120 V 160 H 130 V 150 L 200 170')" at progress 0.125]
     expected: FAIL
@@ -141,19 +120,16 @@
     expected: FAIL
 
   [Animation between "path('m 10 20 h 30 v 60 h 10 v -10 l 110 60')" and "path('M 130 140 H 120 V 160 H 130 V 150 L 200 170')" at progress 1]
     expected: FAIL
 
   [Animation between "path('m 10 20 h 30 v 60 h 10 v -10 l 110 60')" and "path('M 130 140 H 120 V 160 H 130 V 150 L 200 170')" at progress 2]
     expected: FAIL
 
-  ["path('m 10 20 a 10 20 30 1 0 40 50 a 110 120 30 1 1 140 50')" and "path('M 18 12 A 50 100 70 0 1 90 110 A 150 160 70 0 1 70 80')" are valid offset-path values]
-    expected: FAIL
-
   [Animation between "path('m 10 20 a 10 20 30 1 0 40 50 a 110 120 30 1 1 140 50')" and "path('M 18 12 A 50 100 70 0 1 90 110 A 150 160 70 0 1 70 80')" at progress -1]
     expected: FAIL
 
   [Animation between "path('m 10 20 a 10 20 30 1 0 40 50 a 110 120 30 1 1 140 50')" and "path('M 18 12 A 50 100 70 0 1 90 110 A 150 160 70 0 1 70 80')" at progress 0]
     expected: FAIL
 
   [Animation between "path('m 10 20 a 10 20 30 1 0 40 50 a 110 120 30 1 1 140 50')" and "path('M 18 12 A 50 100 70 0 1 90 110 A 150 160 70 0 1 70 80')" at progress 0.125]
     expected: FAIL
--- a/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-005.html.ini
+++ b/testing/web-platform/meta/css/motion/animation/offset-path-interpolation-005.html.ini
@@ -27,37 +27,19 @@
     expected: FAIL
 
   [Animation between "ray(0deg closest-corner)" and "none" at progress 0]
     expected: FAIL
 
   [Animation between "ray(0deg closest-corner)" and "none" at progress 0.125]
     expected: FAIL
 
-  [Animation between "ray(0deg closest-corner)" and "none" at progress 0.875]
-    expected: FAIL
-
-  [Animation between "ray(0deg closest-corner)" and "none" at progress 1]
-    expected: FAIL
-
-  [Animation between "ray(0deg closest-corner)" and "none" at progress 2]
-    expected: FAIL
-
   ["none" and "ray(20deg closest-side)" are valid offset-path values]
     expected: FAIL
 
-  [Animation between "none" and "ray(20deg closest-side)" at progress -1]
-    expected: FAIL
-
-  [Animation between "none" and "ray(20deg closest-side)" at progress 0]
-    expected: FAIL
-
-  [Animation between "none" and "ray(20deg closest-side)" at progress 0.125]
-    expected: FAIL
-
   [Animation between "none" and "ray(20deg closest-side)" at progress 0.875]
     expected: FAIL
 
   [Animation between "none" and "ray(20deg closest-side)" at progress 1]
     expected: FAIL
 
   [Animation between "none" and "ray(20deg closest-side)" at progress 2]
     expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/motion/offset-path-string-001.html.ini
@@ -0,0 +1,3 @@
+[offset-path-string-001.html]
+  expected: FAIL
+  bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1429299
deleted file mode 100644
--- a/testing/web-platform/meta/css/motion/offset-path-string.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[offset-path-string.html]
-  expected: FAIL
--- a/testing/web-platform/meta/css/motion/parsing/offset-path-parsing-valid.html.ini
+++ b/testing/web-platform/meta/css/motion/parsing/offset-path-parsing-valid.html.ini
@@ -1,21 +1,15 @@
 [offset-path-parsing-valid.html]
   [Serialization should round-trip after setting e.style['offset-path'\] = "ray(calc(180deg - 45deg) farthest-side)"]
     expected: FAIL
 
   [Serialization should round-trip after setting e.style['offset-path'\] = "fill-box ellipse(50% 60%)"]
     expected: FAIL
 
-  [e.style['offset-path'\] = "none" should set the property value]
-    expected: FAIL
-
-  [Serialization should round-trip after setting e.style['offset-path'\] = "none"]
-    expected: FAIL
-
   [e.style['offset-path'\] = "ray(0rad closest-side)" should set the property value]
     expected: FAIL
 
   [Serialization should round-trip after setting e.style['offset-path'\] = "ray(0rad closest-side)"]
     expected: FAIL
 
   [e.style['offset-path'\] = "ray(0.25turn closest-corner contain)" should set the property value]
     expected: FAIL
@@ -39,28 +33,16 @@
     expected: FAIL
 
   [Serialization should round-trip after setting e.style['offset-path'\] = "ray(-720deg sides)"]
     expected: FAIL
 
   [e.style['offset-path'\] = "ray(calc(180deg - 45deg) farthest-side)" should set the property value]
     expected: FAIL
 
-  [e.style['offset-path'\] = "path('m 0 0 h -100')" should set the property value]
-    expected: FAIL
-
-  [Serialization should round-trip after setting e.style['offset-path'\] = "path('m 0 0 h -100')"]
-    expected: FAIL
-
-  [e.style['offset-path'\] = "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 300 300 Z')" should set the property value]
-    expected: FAIL
-
-  [Serialization should round-trip after setting e.style['offset-path'\] = "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 300 300 Z')"]
-    expected: FAIL
-
   [e.style['offset-path'\] = "url(\\"http://www.example.com/index.html#polyline1\\")" should set the property value]
     expected: FAIL
 
   [Serialization should round-trip after setting e.style['offset-path'\] = "url(\\"http://www.example.com/index.html#polyline1\\")"]
     expected: FAIL
 
   [e.style['offset-path'\] = "circle(100px)" should set the property value]
     expected: FAIL
rename from testing/web-platform/tests/css/motion/offset-path-string.html
rename to testing/web-platform/tests/css/motion/offset-path-string-001.html
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/motion/offset-path-string-002.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Motion Path: path(string) paths</title>
+    <link rel="help" href="https://drafts.fxtf.org/motion-1/#offset-path-property">
+    <link rel="match" href="offset-path-string-ref.html">
+    <meta name="assert" content="This tests that path(<string>) generates a rotation and translation.">
+    <style>
+      #target {
+        position: absolute;
+        left: 300px;
+        top: 0px;
+        width: 300px;
+        height: 200px;
+        background-color: lime;
+        transform-origin: 0px 0px;
+        offset-path: path('m 0 120 v 200');
+      }
+    </style>
+  </head>
+  <body>
+    <div id="target"></div>
+  </body>
+</html>
--- a/testing/web-platform/tests/css/motion/parsing/offset-path-parsing-invalid.html
+++ b/testing/web-platform/tests/css/motion/parsing/offset-path-parsing-invalid.html
@@ -9,17 +9,17 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../support/parsing-testcommon.js"></script>
 </head>
 <body>
 <script>
 // arc path segments must have at least 7 arguments.
 // https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
-test_invalid_value("offset-path", "path('M 20 30 A 60 70 80')");
+test_invalid_value("offset-path", 'path("M 20 30 A 60 70 80")');
 
 test_invalid_value("offset-path", "ray(0 sides)");
 test_invalid_value("offset-path", "ray(0deg)");
 test_invalid_value("offset-path", "ray(closest-side)");
 test_invalid_value("offset-path", "ray(closest-side 0deg closest-side)");
 test_invalid_value("offset-path", "ray(0deg closest-side 0deg)");
 test_invalid_value("offset-path", "ray(contain 0deg closest-side contain)");
 
--- a/testing/web-platform/tests/css/motion/parsing/offset-path-parsing-valid.html
+++ b/testing/web-platform/tests/css/motion/parsing/offset-path-parsing-valid.html
@@ -16,18 +16,18 @@ test_valid_value("offset-path", "none");
 
 test_valid_value("offset-path", "ray(0rad closest-side)");
 test_valid_value("offset-path", "ray(0.25turn closest-corner contain)");
 test_valid_value("offset-path", "ray(200grad farthest-side)");
 test_valid_value("offset-path", "ray(270deg farthest-corner contain)");
 test_valid_value("offset-path", "ray(-720deg sides)");
 test_valid_value("offset-path", "ray(calc(180deg - 45deg) farthest-side)", "ray(calc(135deg) farthest-side)");
 
-test_valid_value("offset-path", "path('m 0 0 h -100')");
-test_valid_value("offset-path", "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 300 300 Z')");
+test_valid_value("offset-path", 'path("m 0 0 h -100")');
+test_valid_value("offset-path", 'path("M 0 0 L 100 100 M 100 200 L 200 200 Z L 300 300 Z")');
 
 test_valid_value("offset-path", 'url("http://www.example.com/index.html#polyline1")');
 
 test_valid_value("offset-path", "circle(100px)");
 test_valid_value("offset-path", "margin-box");
 test_valid_value("offset-path", "inset(10% 20% 30% 40%) border-box");
 test_valid_value("offset-path", "fill-box ellipse(50% 60%)", "ellipse(50% 60%) fill-box");
 </script>
--- a/toolkit/library/rust/shared/lib.rs
+++ b/toolkit/library/rust/shared/lib.rs
@@ -31,20 +31,28 @@ extern crate u2fhid;
 extern crate log;
 extern crate cosec;
 extern crate rsdparsa_capi;
 
 use std::boxed::Box;
 use std::env;
 use std::ffi::{CStr, CString};
 use std::os::raw::c_char;
+#[cfg(target_os = "android")]
+use std::os::raw::c_int;
+#[cfg(target_os = "android")]
+use log::Level;
+#[cfg(not(target_os = "android"))]
+use log::Log;
 use std::panic;
 
 extern "C" {
     fn gfx_critical_note(msg: *const c_char);
+    #[cfg(target_os = "android")]
+    fn __android_log_write(prio: c_int, tag: *const c_char, text: *const c_char) -> c_int;
 }
 
 struct GeckoLogger {
     logger: env_logger::Logger
 }
 
 impl GeckoLogger {
     fn new() -> GeckoLogger {
@@ -79,27 +87,50 @@ impl GeckoLogger {
     fn maybe_log_to_gfx_critical_note(&self, record: &log::Record) {
         if Self::should_log_to_gfx_critical_note(record) {
             let msg = CString::new(format!("{}", record.args())).unwrap();
             unsafe {
                 gfx_critical_note(msg.as_ptr());
             }
         }
     }
+
+    #[cfg(not(target_os = "android"))]
+    fn log_out(&self, record: &log::Record) {
+        self.logger.log(record);
+    }
+
+    #[cfg(target_os = "android")]
+    fn log_out(&self, record: &log::Record) {
+        let msg = CString::new(format!("{}", record.args())).unwrap();
+        let tag = CString::new(record.module_path().unwrap()).unwrap();
+        let prio = match record.metadata().level() {
+            Level::Error => 6 /* ERROR */,
+            Level::Warn => 5 /* WARN */,
+            Level::Info => 4 /* INFO */,
+            Level::Debug => 3 /* DEBUG */,
+            Level::Trace => 2 /* VERBOSE */,
+        };
+        // Output log directly to android log, since env_logger can output log
+        // only to stderr or stdout.
+        unsafe {
+            __android_log_write(prio, tag.as_ptr(), msg.as_ptr());
+        }
+    }
 }
 
 impl log::Log for GeckoLogger {
     fn enabled(&self, metadata: &log::Metadata) -> bool {
         self.logger.enabled(metadata)
     }
 
     fn log(&self, record: &log::Record) {
         // Forward log to gfxCriticalNote, if the log should be in gfx crash log.
         self.maybe_log_to_gfx_critical_note(record);
-        self.logger.log(record);
+        self.log_out(record);
     }
 
     fn flush(&self) { }
 }
 
 #[no_mangle]
 pub extern "C" fn GkRust_Init() {
     // Initialize logging.