Merge inbound to mozilla-central. a=merge
authorNoemi Erli <nerli@mozilla.com>
Wed, 22 Aug 2018 12:49:02 +0300
changeset 432775 3b5452b3778153f6f223bb2177235835b2314eb6
parent 432757 357d549504fb6e1330bc0aa152cafcd27b0c9a2a (current diff)
parent 432774 c3bb26f985552814f9e35639a15acd10a15878bb (diff)
child 432780 2ee4398514ac09b08a48a737d7e22cd63184901d
child 432803 e5ac55ad643d95424b8891446a3743b53bcf6bfe
push id34486
push usernerli@mozilla.com
push dateWed, 22 Aug 2018 09:49:20 +0000
treeherdermozilla-central@3b5452b37781 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone63.0a1
first release with
nightly linux32
3b5452b37781 / 63.0a1 / 20180822100104 / files
nightly linux64
3b5452b37781 / 63.0a1 / 20180822100104 / files
nightly mac
3b5452b37781 / 63.0a1 / 20180822100104 / files
nightly win32
3b5452b37781 / 63.0a1 / 20180822100104 / files
nightly win64
3b5452b37781 / 63.0a1 / 20180822100104 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
devtools/server/actors/animation-type-longhand.js
devtools/shared/css/generated/properties-db.js
dom/svg/SVGPathData.cpp
dom/svg/SVGPathData.h
dom/svg/moz.build
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
layout/generic/nsFloatManager.cpp
layout/painting/ActiveLayerTracker.cpp
layout/painting/nsDisplayList.cpp
layout/painting/nsDisplayList.h
layout/style/ServoBindings.cpp
layout/style/ServoBindings.h
layout/style/ServoBindings.toml
layout/style/ServoCSSPropList.mako.py
layout/style/nsComputedDOMStyle.cpp
layout/style/nsStyleConsts.h
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/nsStyleTransformMatrix.cpp
layout/style/nsStyleTransformMatrix.h
layout/style/test/property_database.js
modules/libpref/init/all.js
servo/components/style/cbindgen.toml
servo/components/style/gecko/conversions.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/longhands/box.mako.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/specified/mod.rs
testing/web-platform/meta/MANIFEST.json
testing/web-platform/meta/css/motion/animation/offset-path-interpolation-001.html.ini
testing/web-platform/meta/css/motion/animation/offset-path-interpolation-002.html.ini
testing/web-platform/meta/css/motion/animation/offset-path-interpolation-003.html.ini
testing/web-platform/meta/css/motion/animation/offset-path-interpolation-004.html.ini
testing/web-platform/meta/css/motion/animation/offset-path-interpolation-005.html.ini
testing/web-platform/meta/css/motion/offset-path-string.html.ini
testing/web-platform/meta/css/motion/parsing/offset-path-parsing-valid.html.ini
testing/web-platform/tests/css/motion/offset-path-string-001.html
testing/web-platform/tests/css/motion/offset-path-string.html
testing/web-platform/tests/css/motion/parsing/offset-path-parsing-invalid.html
testing/web-platform/tests/css/motion/parsing/offset-path-parsing-valid.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.