Merge inbound to mozilla-central a=merge
authorarthur.iakab <aiakab@mozilla.com>
Sat, 25 Aug 2018 01:08:22 +0300
changeset 433389 90c564d87a5efe1481f403a0731e4e7dc896fc06
parent 433388 d2da77c319d28b325fc5ceba90fe2c45f5ba84ea (current diff)
parent 433387 21b885b5fc62ce75eb92b9412ea2e0eba1287e7d (diff)
child 433390 608bced17fb4802f3ffb3b15563ff932733384ce
child 433406 615a20bf1d16595140a02953d5e2ea6162a3ebae
push id68292
push useraiakab@mozilla.com
push dateFri, 24 Aug 2018 22:09:27 +0000
treeherderautoland@608bced17fb4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone63.0a1
first release with
nightly linux32
90c564d87a5e / 63.0a1 / 20180824222409 / files
nightly linux64
90c564d87a5e / 63.0a1 / 20180824222409 / files
nightly mac
90c564d87a5e / 63.0a1 / 20180824222409 / files
nightly win32
90c564d87a5e / 63.0a1 / 20180824222409 / files
nightly win64
90c564d87a5e / 63.0a1 / 20180824222409 / 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/client/aboutdebugging-new/src/components/DebugTargetItem.css
devtools/client/aboutdebugging-new/src/components/DebugTargetItem.js
devtools/client/aboutdebugging-new/src/components/DebugTargetList.css
devtools/client/aboutdebugging-new/src/components/DebugTargetList.js
devtools/client/aboutdebugging-new/src/components/DebugTargetPane.js
editor/nsEditorCID.h
mobile/android/chrome/content/browser.js
testing/web-platform/meta/MANIFEST.json
testing/web-platform/meta/webdriver/tests/maximize_window/user_prompts.py.ini
testing/web-platform/meta/webdriver/tests/new_session/create_alwaysMatch.py.ini
testing/web-platform/meta/webdriver/tests/new_session/create_firstMatch.py.ini
testing/web-platform/tests/service-workers/service-worker/resources/update-top-level-worker.py
testing/web-platform/tests/service-workers/service-worker/update-top-level.https.html
--- a/browser/base/content/test/siteIdentity/browser.ini
+++ b/browser/base/content/test/siteIdentity/browser.ini
@@ -51,17 +51,17 @@ support-files = ../permissions/permissio
 [browser_identityPopup_focus.js]
 [browser_insecureLoginForms.js]
 support-files =
   insecure_opener.html
   !/toolkit/components/passwordmgr/test/browser/form_basic.html
   !/toolkit/components/passwordmgr/test/browser/insecure_test.html
   !/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html
 [browser_mcb_redirect.js]
-skip-if = verify && !debug && os == 'mac'
+skip-if = (verify && !debug && os == 'mac') || (os == 'linux') || (os == 'mac') # Bug 1376771
 tags = mcb
 support-files =
   test_mcb_redirect.html
   test_mcb_redirect_image.html
   test_mcb_double_redirect_image.html
   test_mcb_redirect.js
   test_mcb_redirect.sjs
 [browser_mixed_content_cert_override.js]
--- a/browser/base/content/test/tabs/browser.ini
+++ b/browser/base/content/test/tabs/browser.ini
@@ -47,16 +47,17 @@ skip-if = (debug && os == 'linux' && bit
 support-files = file_new_tab_page.html
 [browser_new_tab_in_privileged_process_pref.js]
 skip-if = !e10s # Pref and test only relevant for e10s.
 [browser_new_web_tab_in_file_process_pref.js]
 skip-if = !e10s # Pref and test only relevant for e10s.
 [browser_newwindow_tabstrip_overflow.js]
 [browser_open_newtab_start_observer_notification.js]
 [browser_opened_file_tab_navigated_to_web.js]
+skip-if = (os == 'mac' && debug) || (os == 'linux' && debug) # Bug 1356347
 [browser_overflowScroll.js]
 [browser_pinnedTabs_clickOpen.js]
 [browser_pinnedTabs_closeByKeyboard.js]
 [browser_pinnedTabs.js]
 [browser_positional_attributes.js]
 skip-if = (verify && (os == 'win' || os == 'mac'))
 [browser_preloadedBrowser_zoom.js]
 [browser_reload_deleted_file.js]
--- a/build/valgrind/cross-architecture.sup
+++ b/build/valgrind/cross-architecture.sup
@@ -21,16 +21,23 @@
 {
    PR_SetEnv requires its argument to be leaked, but does not appear on stacks. (See bug 793549.)
    Memcheck:Leak
    ...
    fun:_ZL13SaveWordToEnvPKcRK12nsTSubstringIcE
    ...
 }
 {
+   PR_SetEnv requires its argument to be leaked, but does not appear on stacks. (See bug 793549.)
+   Memcheck:Leak
+   ...
+   fun:SaveWordToEnv
+   ...
+}
+{
    PR_SetEnv requires its argument to be leaked, but does not appear on stacks. (See bug 944133.)
    Memcheck:Leak
    ...
    fun:_ZN13CrashReporter14SetRestartArgsEiPPc
    ...
 }
 {
    PR_SetEnv requires its argument to be leaked, but does not appear on stacks. (See bug 793548.)
--- a/devtools/client/aboutdebugging-new/aboutdebugging.css
+++ b/devtools/client/aboutdebugging-new/aboutdebugging.css
@@ -1,21 +1,22 @@
 /* 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/. */
 
 @import "chrome://global/skin/in-content/common.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/App.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/ConnectPage.css";
-@import "resource://devtools/client/aboutdebugging-new/src/components/DebugTargetItem.css";
-@import "resource://devtools/client/aboutdebugging-new/src/components/DebugTargetList.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/RuntimeInfo.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/Sidebar.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/SidebarItem.css";
+@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.css";
+@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/ExtensionDetail.css";
+@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/WorkerDetail.css";
 
 :root {
   /* Import css variables from common.css */
   --text-color: var(--in-content-page-color);
 }
 
 html, body {
   margin: 0;
--- a/devtools/client/aboutdebugging-new/src/actions/runtime.js
+++ b/devtools/client/aboutdebugging-new/src/actions/runtime.js
@@ -5,31 +5,35 @@
 "use strict";
 
 const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
 const { BrowserToolboxProcess } =
   require("resource://devtools/client/framework/ToolboxProcess.jsm");
 const { Cc, Ci } = require("chrome");
 const { DebuggerClient } = require("devtools/shared/client/debugger-client");
 const { DebuggerServer } = require("devtools/server/main");
+const { gDevToolsBrowser } = require("devtools/client/framework/devtools-browser");
 
 const {
   CONNECT_RUNTIME_FAILURE,
   CONNECT_RUNTIME_START,
   CONNECT_RUNTIME_SUCCESS,
   DEBUG_TARGETS,
   DISCONNECT_RUNTIME_FAILURE,
   DISCONNECT_RUNTIME_START,
   DISCONNECT_RUNTIME_SUCCESS,
   REQUEST_EXTENSIONS_FAILURE,
   REQUEST_EXTENSIONS_START,
   REQUEST_EXTENSIONS_SUCCESS,
   REQUEST_TABS_FAILURE,
   REQUEST_TABS_START,
   REQUEST_TABS_SUCCESS,
+  REQUEST_WORKERS_FAILURE,
+  REQUEST_WORKERS_START,
+  REQUEST_WORKERS_SUCCESS,
 } = require("../constants");
 
 let browserToolboxProcess = null;
 
 function connectRuntime() {
   return async (dispatch, getState) => {
     dispatch({ type: CONNECT_RUNTIME_START });
 
@@ -38,16 +42,17 @@ function connectRuntime() {
     const client = new DebuggerClient(DebuggerServer.connectPipe());
 
     try {
       await client.connect();
 
       dispatch({ type: CONNECT_RUNTIME_SUCCESS, client });
       dispatch(requestExtensions());
       dispatch(requestTabs());
+      dispatch(requestWorkers());
     } catch (e) {
       dispatch({ type: CONNECT_RUNTIME_FAILURE, error: e.message });
     }
   };
 }
 
 function disconnectRuntime() {
   return async (dispatch, getState) => {
@@ -62,36 +67,49 @@ function disconnectRuntime() {
       dispatch({ type: DISCONNECT_RUNTIME_SUCCESS });
     } catch (e) {
       dispatch({ type: DISCONNECT_RUNTIME_FAILURE, error: e.message });
     }
   };
 }
 
 function inspectDebugTarget(type, id) {
-  if (type === DEBUG_TARGETS.TAB) {
-    window.open(`about:devtools-toolbox?type=tab&id=${ id }`);
-  } else if (type === DEBUG_TARGETS.EXTENSION) {
-    // Close previous addon debugging toolbox.
-    if (browserToolboxProcess) {
-      browserToolboxProcess.close();
-    }
+  return async (_, getState) => {
+    switch (type) {
+      case DEBUG_TARGETS.TAB: {
+        // Open tab debugger in new window.
+        window.open(`about:devtools-toolbox?type=tab&id=${ id }`);
+        break;
+      }
+      case DEBUG_TARGETS.EXTENSION: {
+        // Close current debugging toolbox and open a new one.
+        if (browserToolboxProcess) {
+          browserToolboxProcess.close();
+        }
 
-    browserToolboxProcess = BrowserToolboxProcess.init({
-      addonID: id,
-      onClose: () => {
-        browserToolboxProcess = null;
+        browserToolboxProcess = BrowserToolboxProcess.init({
+          addonID: id,
+          onClose: () => {
+            browserToolboxProcess = null;
+          }
+        });
+        break;
       }
-    });
-  } else {
-    console.error(`Failed to inspect the debug target of type: ${ type } id: ${ id }`);
-  }
+      case DEBUG_TARGETS.WORKER: {
+        // Open worker toolbox in new window.
+        gDevToolsBrowser.openWorkerToolbox(getState().runtime.client, id);
+        break;
+      }
 
-  // We cancel the redux flow here since the inspection does not need to update the state.
-  return () => {};
+      default: {
+        console.error("Failed to inspect the debug target of " +
+                      `type: ${ type } id: ${ id }`);
+      }
+    }
+  };
 }
 
 function installTemporaryExtension() {
   const fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
   fp.init(window, "Select Manifest File or Package (.xpi)", Ci.nsIFilePicker.modeOpen);
   fp.open(async res => {
     if (res == Ci.nsIFilePicker.returnCancel || !fp.file) {
       return;
@@ -111,16 +129,28 @@ function installTemporaryExtension() {
     } catch (e) {
       console.error(e);
     }
   });
 
   return () => {};
 }
 
+function pushServiceWorker(actor) {
+  return async (_, getState) => {
+    const client = getState().runtime.client;
+
+    try {
+      await client.request({ to: actor, type: "push" });
+    } catch (e) {
+      console.error(e);
+    }
+  };
+}
+
 function reloadTemporaryExtension(actor) {
   return async (_, getState) => {
     const client = getState().runtime.client;
 
     try {
       await client.request({ to: actor, type: "reload" });
     } catch (e) {
       console.error(e);
@@ -176,18 +206,58 @@ function requestExtensions() {
         temporaryExtensions,
       });
     } catch (e) {
       dispatch({ type: REQUEST_EXTENSIONS_FAILURE, error: e.message });
     }
   };
 }
 
+function requestWorkers() {
+  return async (dispatch, getState) => {
+    dispatch({ type: REQUEST_WORKERS_START });
+
+    const client = getState().runtime.client;
+
+    try {
+      const {
+        other: otherWorkers,
+        service: serviceWorkers,
+        shared: sharedWorkers,
+      } = await client.mainRoot.listAllWorkers();
+
+      dispatch({
+        type: REQUEST_WORKERS_SUCCESS,
+        otherWorkers,
+        serviceWorkers,
+        sharedWorkers,
+      });
+    } catch (e) {
+      dispatch({ type: REQUEST_WORKERS_FAILURE, error: e.message });
+    }
+  };
+}
+
+function startServiceWorker(actor) {
+  return async (_, getState) => {
+    const client = getState().runtime.client;
+
+    try {
+      await client.request({ to: actor, type: "start" });
+    } catch (e) {
+      console.error(e);
+    }
+  };
+}
+
 module.exports = {
   connectRuntime,
   disconnectRuntime,
   inspectDebugTarget,
   installTemporaryExtension,
+  pushServiceWorker,
   reloadTemporaryExtension,
   removeTemporaryExtension,
   requestTabs,
   requestExtensions,
+  requestWorkers,
+  startServiceWorker,
 };
--- a/devtools/client/aboutdebugging-new/src/components/RuntimePage.js
+++ b/devtools/client/aboutdebugging-new/src/components/RuntimePage.js
@@ -4,66 +4,113 @@
 
 "use strict";
 
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { createFactory, PureComponent } = 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 DebugTargetPane = createFactory(require("./DebugTargetPane"));
+const DebugTargetPane = createFactory(require("./debugtarget/DebugTargetPane"));
+const ExtensionDetail = createFactory(require("./debugtarget/ExtensionDetail"));
+const InspectAction = createFactory(require("./debugtarget/InspectAction"));
 const RuntimeInfo = createFactory(require("./RuntimeInfo"));
+const ServiceWorkerAction = createFactory(require("./debugtarget/ServiceWorkerAction"));
+const TabDetail = createFactory(require("./debugtarget/TabDetail"));
+const TemporaryExtensionAction = createFactory(require("./debugtarget/TemporaryExtensionAction"));
 const TemporaryExtensionInstaller =
   createFactory(require("./debugtarget/TemporaryExtensionInstaller"));
+const WorkerDetail = createFactory(require("./debugtarget/WorkerDetail"));
 
 const Services = require("Services");
 
 class RuntimePage extends PureComponent {
   static get propTypes() {
     return {
       dispatch: PropTypes.func.isRequired,
       installedExtensions: PropTypes.arrayOf(PropTypes.object).isRequired,
+      otherWorkers: PropTypes.arrayOf(PropTypes.object).isRequired,
+      serviceWorkers: PropTypes.arrayOf(PropTypes.object).isRequired,
+      sharedWorkers: PropTypes.arrayOf(PropTypes.object).isRequired,
       tabs: PropTypes.arrayOf(PropTypes.object).isRequired,
       temporaryExtensions: PropTypes.arrayOf(PropTypes.object).isRequired,
     };
   }
 
   render() {
-    const { dispatch, installedExtensions, tabs, temporaryExtensions } = this.props;
+    const {
+      dispatch,
+      installedExtensions,
+      otherWorkers,
+      serviceWorkers,
+      sharedWorkers,
+      tabs,
+      temporaryExtensions,
+    } = this.props;
 
     return dom.article(
       {
         className: "page",
       },
       RuntimeInfo({
         icon: "chrome://branding/content/icon64.png",
         name: Services.appinfo.name,
         version: Services.appinfo.version,
       }),
       TemporaryExtensionInstaller({ dispatch }),
       DebugTargetPane({
+        actionComponent: TemporaryExtensionAction,
+        detailComponent: ExtensionDetail,
         dispatch,
         name: "Temporary Extensions",
         targets: temporaryExtensions,
       }),
       DebugTargetPane({
+        actionComponent: InspectAction,
+        detailComponent: ExtensionDetail,
         dispatch,
         name: "Extensions",
         targets: installedExtensions,
       }),
       DebugTargetPane({
+        actionComponent: InspectAction,
+        detailComponent: TabDetail,
         dispatch,
         name: "Tabs",
         targets: tabs
       }),
+      DebugTargetPane({
+        actionComponent: ServiceWorkerAction,
+        detailComponent: WorkerDetail,
+        dispatch,
+        name: "Service Workers",
+        targets: serviceWorkers
+      }),
+      DebugTargetPane({
+        actionComponent: InspectAction,
+        detailComponent: WorkerDetail,
+        dispatch,
+        name: "Shared Workers",
+        targets: sharedWorkers
+      }),
+      DebugTargetPane({
+        actionComponent: InspectAction,
+        detailComponent: WorkerDetail,
+        dispatch,
+        name: "Other Workers",
+        targets: otherWorkers
+      }),
     );
   }
 }
 
 const mapStateToProps = state => {
   return {
     installedExtensions: state.runtime.installedExtensions,
+    otherWorkers: state.runtime.otherWorkers,
+    serviceWorkers: state.runtime.serviceWorkers,
+    sharedWorkers: state.runtime.sharedWorkers,
     tabs: state.runtime.tabs,
     temporaryExtensions: state.runtime.temporaryExtensions,
   };
 };
 
 module.exports = connect(mapStateToProps)(RuntimePage);
rename from devtools/client/aboutdebugging-new/src/components/DebugTargetItem.css
rename to devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.css
rename from devtools/client/aboutdebugging-new/src/components/DebugTargetItem.js
rename to devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.js
--- a/devtools/client/aboutdebugging-new/src/components/DebugTargetItem.js
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.js
@@ -1,93 +1,59 @@
 /* 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/. */
 
 "use strict";
 
-const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
+const { PureComponent } = 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 ExtensionDetail = createFactory(require("./debugtarget/ExtensionDetail"));
-const TabDetail = createFactory(require("./debugtarget/TabDetail"));
-const TemporaryExtensionAction =
-  createFactory(require("./debugtarget/TemporaryExtensionAction"));
-
-const Actions = require("../actions/index");
-const { DEBUG_TARGETS } = require("../constants");
-
 /**
  * This component displays debug target.
  */
 class DebugTargetItem extends PureComponent {
   static get propTypes() {
     return {
+      actionComponent: PropTypes.any.isRequired,
+      detailComponent: PropTypes.any.isRequired,
       dispatch: PropTypes.func.isRequired,
       target: PropTypes.object.isRequired,
     };
   }
 
-  inspect() {
-    const { dispatch, target } = this.props;
-    dispatch(Actions.inspectDebugTarget(target.type, target.id));
-  }
-
   renderAction() {
-    const { dispatch, target } = this.props;
-
-    return dom.div(
-      {},
-      dom.button(
-        {
-          onClick: e => this.inspect(),
-          className: "aboutdebugging-button",
-        },
-        "Inspect"
-      ),
-      target.details.temporarilyInstalled
-        ? TemporaryExtensionAction({ dispatch, target }) : null,
-    );
+    const { actionComponent, dispatch, target } = this.props;
+    return actionComponent({ dispatch, target });
   }
 
   renderDetail() {
-    const { target } = this.props;
-
-    switch (target.type) {
-      case DEBUG_TARGETS.EXTENSION:
-        return ExtensionDetail({ target });
-      case DEBUG_TARGETS.TAB:
-        return TabDetail({ target });
-
-      default:
-        return null;
-    }
+    const { detailComponent, target } = this.props;
+    return detailComponent({ target });
   }
 
   renderIcon() {
     return dom.img({
       className: "debug-target-item__icon",
       src: this.props.target.icon,
     });
   }
 
   renderInfo() {
-    const { target } = this.props;
-
     return dom.div(
       {
         className: "debug-target-item__info",
       },
       dom.div(
         {
           className: "debug-target-item__info__name ellipsis-text",
-          title: target.name,
+          title: this.props.target.name,
         },
-        target.name
+        this.props.target.name
       ),
       this.renderDetail(),
     );
   }
 
   render() {
     return dom.li(
       {
rename from devtools/client/aboutdebugging-new/src/components/DebugTargetList.css
rename to devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.css
rename from devtools/client/aboutdebugging-new/src/components/DebugTargetList.js
rename to devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.js
--- a/devtools/client/aboutdebugging-new/src/components/DebugTargetList.js
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.js
@@ -11,26 +11,29 @@ const PropTypes = require("devtools/clie
 const DebugTargetItem = createFactory(require("./DebugTargetItem"));
 
 /**
  * This component displays list of debug target.
  */
 class DebugTargetList extends PureComponent {
   static get propTypes() {
     return {
+      actionComponent: PropTypes.any.isRequired,
+      detailComponent: PropTypes.any.isRequired,
       dispatch: PropTypes.func.isRequired,
       targets: PropTypes.arrayOf(PropTypes.object).isRequired,
     };
   }
 
   render() {
-    const { dispatch, targets } = this.props;
+    const { actionComponent, detailComponent, dispatch, targets } = this.props;
 
     return dom.ul(
       {
         className: "debug-target-list",
       },
-      targets.map(target => DebugTargetItem({ dispatch, target })),
+      targets.map(target =>
+        DebugTargetItem({ actionComponent, detailComponent, dispatch, target })),
     );
   }
 }
 
 module.exports = DebugTargetList;
rename from devtools/client/aboutdebugging-new/src/components/DebugTargetPane.js
rename to devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetPane.js
--- a/devtools/client/aboutdebugging-new/src/components/DebugTargetPane.js
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetPane.js
@@ -11,26 +11,28 @@ const PropTypes = require("devtools/clie
 const DebugTargetList = createFactory(require("./DebugTargetList"));
 
 /**
  * This component provides list for debug target and name area.
  */
 class DebugTargetPane extends PureComponent {
   static get propTypes() {
     return {
+      actionComponent: PropTypes.any.isRequired,
+      detailComponent: PropTypes.any.isRequired,
       dispatch: PropTypes.func.isRequired,
       name: PropTypes.string.isRequired,
       targets: PropTypes.arrayOf(PropTypes.Object).isRequired,
     };
   }
 
   render() {
-    const { dispatch, name, targets } = this.props;
+    const { actionComponent, detailComponent, dispatch, name, targets } = this.props;
 
     return dom.section(
       {},
       dom.h2({}, name),
-      DebugTargetList({ dispatch, targets }),
+      DebugTargetList({ actionComponent, detailComponent, dispatch, targets }),
     );
   }
 }
 
 module.exports = DebugTargetPane;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/InspectAction.js
@@ -0,0 +1,40 @@
+/* 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/. */
+
+"use strict";
+
+const { PureComponent } = 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 Actions = require("../../actions/index");
+
+/**
+ * This component provides inspect button.
+ */
+class InspectAction extends PureComponent {
+  static get propTypes() {
+    return {
+      dispatch: PropTypes.func.isRequired,
+      target: PropTypes.object.isRequired,
+    };
+  }
+
+  inspect() {
+    const { dispatch, target } = this.props;
+    dispatch(Actions.inspectDebugTarget(target.type, target.id));
+  }
+
+  render() {
+    return dom.button(
+      {
+        onClick: e => this.inspect(),
+        className: "aboutdebugging-button",
+      },
+      "Inspect"
+    );
+  }
+}
+
+module.exports = InspectAction;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/ServiceWorkerAction.js
@@ -0,0 +1,70 @@
+/* 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/. */
+
+"use strict";
+
+const { createFactory, PureComponent } = 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 InspectAction = createFactory(require("./InspectAction"));
+
+const Actions = require("../../actions/index");
+
+/**
+ * This component displays buttons for service worker.
+ */
+class ServiceWorkerAction extends PureComponent {
+  static get propTypes() {
+    return {
+      dispatch: PropTypes.func.isRequired,
+      target: PropTypes.object.isRequired,
+    };
+  }
+
+  push() {
+    const { dispatch, target } = this.props;
+    dispatch(Actions.pushServiceWorker(target.id));
+  }
+
+  start() {
+    const { dispatch, target } = this.props;
+    dispatch(Actions.startServiceWorker(target.details.registrationActor));
+  }
+
+  _renderAction() {
+    const { dispatch, target } = this.props;
+    const { isActive, isRunning } = target.details;
+
+    if (!isRunning) {
+      return this._renderButton("Start", this.start.bind(this));
+    }
+
+    if (!isActive) {
+      // Only debug button is available if the service worker is not active.
+      return InspectAction({ dispatch, target });
+    }
+
+    return [
+      this._renderButton("Push", this.push.bind(this)),
+      InspectAction({ dispatch, target }),
+    ];
+  }
+
+  _renderButton(label, onClick) {
+    return dom.button(
+      {
+        className: "aboutdebugging-button",
+        onClick: e => onClick(),
+      },
+      label,
+    );
+  }
+
+  render() {
+    return dom.div({}, this._renderAction());
+  }
+}
+
+module.exports = ServiceWorkerAction;
--- a/devtools/client/aboutdebugging-new/src/components/debugtarget/TemporaryExtensionAction.js
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/TemporaryExtensionAction.js
@@ -1,22 +1,24 @@
 /* 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/. */
 
 "use strict";
 
-const { PureComponent } = require("devtools/client/shared/vendor/react");
+const { createFactory, PureComponent } = 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 InspectAction = createFactory(require("./InspectAction"));
+
 const Actions = require("../../actions/index");
 
 /**
- * This component provides components that reload/remove temporary extension.
+ * This component provides components that inspect/reload/remove temporary extension.
  */
 class TemporaryExtensionAction extends PureComponent {
   static get propTypes() {
     return {
       dispatch: PropTypes.func.isRequired,
       target: PropTypes.object.isRequired,
     };
   }
@@ -27,28 +29,32 @@ class TemporaryExtensionAction extends P
   }
 
   remove() {
     const { dispatch, target } = this.props;
     dispatch(Actions.removeTemporaryExtension(target.id));
   }
 
   render() {
-    return [
+    const { dispatch, target } = this.props;
+
+    return dom.div(
+      {},
+      InspectAction({ dispatch, target }),
       dom.button(
         {
           className: "aboutdebugging-button",
           onClick: e => this.reload()
         },
         "Reload",
       ),
       dom.button(
         {
           className: "aboutdebugging-button",
           onClick: e => this.remove()
         },
         "Remove",
       ),
-    ];
+    );
   }
 }
 
 module.exports = TemporaryExtensionAction;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/WorkerDetail.css
@@ -0,0 +1,52 @@
+/* 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/. */
+
+.worker-detail {
+  --worker-status-font-size: 10px;
+}
+
+/*
+ * The current layout of worker detail is
+ *
+ *  +----------------+--------------------+
+ *  | detail name dt | detail value dd    |
+ *  | (60px )        | (auto)             |
+ *  +----------------+--------------------+
+ *  | detail name dt | detail value dd    |
+ *  +----------------+--------------------+
+ *  | detail name dt | detail value dd    |
+ *  +----------------+--------------------+
+ */
+.worker-detail {
+  display: grid;
+  grid-template-columns: 60px auto;
+  margin-block-start: 4px;
+}
+
+/*
+ * worker-detail__status has a ui like badge and the color change by the status.
+ * For now, the background-color of running status is palegreen, stopped is lightgrey
+ * though, might be changed since this is not Photon color.
+ */
+.worker-detail__status {
+  border-style: solid;
+  border-width: 1px;
+  box-sizing: border-box;
+  display: inline-block;
+  font-size: var(--worker-status-font-size);
+  margin-block-start: 6px;
+  padding-block-start: 2px;
+  padding-block-end: 2px;
+  text-align: center;
+}
+
+.worker-detail__status--running {
+  border-color: limegreen;
+  background-color: palegreen;
+}
+
+.worker-detail__status--stopped {
+  border-color: grey;
+  background-color: lightgrey;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/WorkerDetail.js
@@ -0,0 +1,71 @@
+/* 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/. */
+
+"use strict";
+
+const { PureComponent } = 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 {
+  SERVICE_WORKER_FETCH_STATES,
+} = require("../../constants");
+
+/**
+ * This component displays detail information for worker.
+ */
+class WorkerDetail extends PureComponent {
+  static get propTypes() {
+    return {
+      target: PropTypes.object.isRequired,
+    };
+  }
+
+  renderFetch() {
+    const { fetch } = this.props.target.details;
+    const label = fetch === SERVICE_WORKER_FETCH_STATES.LISTENING
+                    ? "Listening for fetch events"
+                    : "Not listening for fetch events";
+    return this.renderField("Fetch", label);
+  }
+
+  renderField(name, value) {
+    return [
+      dom.dt({}, name),
+      dom.dd(
+        {
+          className: "ellipsis-text",
+          title: value,
+        },
+        value,
+      ),
+    ];
+  }
+
+  renderStatus() {
+    const status = this.props.target.details.status.toLowerCase();
+
+    return dom.div(
+      {
+        className: `worker-detail__status worker-detail__status--${ status }`,
+      },
+      status
+    );
+  }
+
+  render() {
+    const { fetch, scope, status } = this.props.target.details;
+
+    return dom.dl(
+      {
+        className: "worker-detail",
+      },
+      fetch ? this.renderFetch() : null,
+      scope ? this.renderField("Scope", scope) : null,
+      status ? this.renderStatus() : null,
+    );
+  }
+}
+
+module.exports = WorkerDetail;
--- a/devtools/client/aboutdebugging-new/src/components/debugtarget/moz.build
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/moz.build
@@ -1,11 +1,20 @@
 # 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/.
 
 DevToolsModules(
+    'DebugTargetItem.css',
+    'DebugTargetItem.js',
+    'DebugTargetList.css',
+    'DebugTargetList.js',
+    'DebugTargetPane.js',
     'ExtensionDetail.css',
     'ExtensionDetail.js',
+    'InspectAction.js',
+    'ServiceWorkerAction.js',
     'TabDetail.js',
     'TemporaryExtensionAction.js',
     'TemporaryExtensionInstaller.js',
+    'WorkerDetail.css',
+    'WorkerDetail.js',
 )
--- a/devtools/client/aboutdebugging-new/src/components/moz.build
+++ b/devtools/client/aboutdebugging-new/src/components/moz.build
@@ -6,21 +6,16 @@ DIRS += [
     'debugtarget',
 ]
 
 DevToolsModules(
     'App.css',
     'App.js',
     'ConnectPage.css',
     'ConnectPage.js',
-    'DebugTargetItem.css',
-    'DebugTargetItem.js',
-    'DebugTargetList.css',
-    'DebugTargetList.js',
-    'DebugTargetPane.js',
     'RuntimeInfo.css',
     'RuntimeInfo.js',
     'RuntimePage.js',
     'Sidebar.css',
     'Sidebar.js',
     'SidebarItem.css',
     'SidebarItem.js',
 )
--- a/devtools/client/aboutdebugging-new/src/constants.js
+++ b/devtools/client/aboutdebugging-new/src/constants.js
@@ -14,25 +14,42 @@ const actionTypes = {
   NETWORK_LOCATIONS_UPDATED: "NETWORK_LOCATIONS_UPDATED",
   PAGE_SELECTED: "PAGE_SELECTED",
   REQUEST_EXTENSIONS_FAILURE: "REQUEST_EXTENSIONS_FAILURE",
   REQUEST_EXTENSIONS_START: "REQUEST_EXTENSIONS_START",
   REQUEST_EXTENSIONS_SUCCESS: "REQUEST_EXTENSIONS_SUCCESS",
   REQUEST_TABS_FAILURE: "REQUEST_TABS_FAILURE",
   REQUEST_TABS_START: "REQUEST_TABS_START",
   REQUEST_TABS_SUCCESS: "REQUEST_TABS_SUCCESS",
+  REQUEST_WORKERS_FAILURE: "REQUEST_WORKERS_FAILURE",
+  REQUEST_WORKERS_START: "REQUEST_WORKERS_START",
+  REQUEST_WORKERS_SUCCESS: "REQUEST_WORKERS_SUCCESS",
 };
 
 const DEBUG_TARGETS = {
   EXTENSION: "EXTENSION",
   TAB: "TAB",
+  WORKER: "WORKER",
 };
 
 const PAGES = {
   THIS_FIREFOX: "this-firefox",
   CONNECT: "connect",
 };
 
+const SERVICE_WORKER_FETCH_STATES = {
+  LISTENING: "LISTENING",
+  NOT_LISTENING: "NOT_LISTENING",
+};
+
+const SERVICE_WORKER_STATUSES = {
+  RUNNING: "RUNNING",
+  REGISTERING: "REGISTERING",
+  STOPPED: "STOPPED",
+};
+
 // flatten constants
 module.exports = Object.assign({}, {
   DEBUG_TARGETS,
   PAGES,
+  SERVICE_WORKER_FETCH_STATES,
+  SERVICE_WORKER_STATUSES,
 }, actionTypes);
--- a/devtools/client/aboutdebugging-new/src/create-store.js
+++ b/devtools/client/aboutdebugging-new/src/create-store.js
@@ -6,25 +6,32 @@
 
 const { applyMiddleware, createStore } = require("devtools/client/shared/vendor/redux");
 const { thunk } = require("devtools/client/shared/redux/middleware/thunk.js");
 
 const rootReducer = require("./reducers/index");
 const { RuntimeState } = require("./reducers/runtime-state");
 const { UiState } = require("./reducers/ui-state");
 const debugTargetListenerMiddleware = require("./middleware/debug-target-listener");
+const extensionComponentDataMiddleware = require("./middleware/extension-component-data");
+const tabComponentDataMiddleware = require("./middleware/tab-component-data");
+const workerComponentDataMiddleware = require("./middleware/worker-component-data");
 const { getNetworkLocations } = require("./modules/network-locations");
 
 function configureStore() {
   const initialState = {
     runtime: new RuntimeState(),
     ui: getUiState()
   };
 
-  const middleware = applyMiddleware(thunk, debugTargetListenerMiddleware);
+  const middleware = applyMiddleware(thunk,
+                                     debugTargetListenerMiddleware,
+                                     extensionComponentDataMiddleware,
+                                     tabComponentDataMiddleware,
+                                     workerComponentDataMiddleware);
 
   return createStore(rootReducer, initialState, middleware);
 }
 
 function getUiState() {
   const locations = getNetworkLocations();
   return new UiState(locations);
 }
--- a/devtools/client/aboutdebugging-new/src/middleware/debug-target-listener.js
+++ b/devtools/client/aboutdebugging-new/src/middleware/debug-target-listener.js
@@ -7,23 +7,23 @@
 const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
 
 const {
   CONNECT_RUNTIME_SUCCESS,
   DISCONNECT_RUNTIME_START,
 } = require("../constants");
 const Actions = require("../actions/index");
 
-function debugTargetListenerMiddleware(state) {
+function debugTargetListenerMiddleware(store) {
   const onExtensionsUpdated = () => {
-    state.dispatch(Actions.requestExtensions());
+    store.dispatch(Actions.requestExtensions());
   };
 
   const onTabsUpdated = () => {
-    state.dispatch(Actions.requestTabs());
+    store.dispatch(Actions.requestTabs());
   };
 
   const extensionsListener = {
     onDisabled() {
       onExtensionsUpdated();
     },
 
     onEnabled() {
@@ -42,26 +42,42 @@ function debugTargetListenerMiddleware(s
       onExtensionsUpdated();
     },
 
     onUninstalling() {
       onExtensionsUpdated();
     },
   };
 
+  const onWorkersUpdated = () => {
+    store.dispatch(Actions.requestWorkers());
+  };
+
   return next => action => {
     switch (action.type) {
       case CONNECT_RUNTIME_SUCCESS: {
-        action.client.addListener("tabListChanged", onTabsUpdated);
+        const { client } = action;
+        client.addListener("tabListChanged", onTabsUpdated);
         AddonManager.addAddonListener(extensionsListener);
+        client.addListener("workerListChanged", onWorkersUpdated);
+        client.addListener("serviceWorkerRegistrationListChanged", onWorkersUpdated);
+        client.addListener("processListChanged", onWorkersUpdated);
+        client.addListener("registration-changed", onWorkersUpdated);
+        client.addListener("push-subscription-modified", onWorkersUpdated);
         break;
       }
       case DISCONNECT_RUNTIME_START: {
-        state.getState().runtime.client.removeListener("tabListChanged", onTabsUpdated);
+        const { client } = store.getState().runtime;
+        client.removeListener("tabListChanged", onTabsUpdated);
         AddonManager.removeAddonListener(extensionsListener);
+        client.removeListener("workerListChanged", onWorkersUpdated);
+        client.removeListener("serviceWorkerRegistrationListChanged", onWorkersUpdated);
+        client.removeListener("processListChanged", onWorkersUpdated);
+        client.removeListener("registration-changed", onWorkersUpdated);
+        client.removeListener("push-subscription-modified", onWorkersUpdated);
         break;
       }
     }
 
     return next(action);
   };
 }
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/middleware/extension-component-data.js
@@ -0,0 +1,70 @@
+/* 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/. */
+
+"use strict";
+
+const {
+  DEBUG_TARGETS,
+  REQUEST_EXTENSIONS_SUCCESS,
+} = require("../constants");
+
+/**
+ * This middleware converts extensions object that get from DebuggerClient.listAddons()
+ * to data which is used in DebugTargetItem.
+ */
+const extensionComponentDataMiddleware = store => next => action => {
+  switch (action.type) {
+    case REQUEST_EXTENSIONS_SUCCESS: {
+      action.installedExtensions = toComponentData(action.installedExtensions);
+      action.temporaryExtensions = toComponentData(action.temporaryExtensions);
+      break;
+    }
+  }
+
+  return next(action);
+};
+
+function getFilePath(extension) {
+  // Only show file system paths, and only for temporarily installed add-ons.
+  if (!extension.temporarilyInstalled ||
+      !extension.url ||
+      !extension.url.startsWith("file://")) {
+    return null;
+  }
+
+  // Strip a leading slash from Windows drive letter URIs.
+  // file:///home/foo ~> /home/foo
+  // file:///C:/foo ~> C:/foo
+  const windowsRegex = /^file:\/\/\/([a-zA-Z]:\/.*)/;
+
+  if (windowsRegex.test(extension.url)) {
+    return windowsRegex.exec(extension.url)[1];
+  }
+
+  return extension.url.slice("file://".length);
+}
+
+function toComponentData(extensions) {
+  return extensions.map(extension => {
+    const type = DEBUG_TARGETS.EXTENSION;
+    const { actor, iconURL, id, manifestURL, name } = extension;
+    const icon = iconURL || "chrome://mozapps/skin/extensions/extensionGeneric.svg";
+    const location = getFilePath(extension);
+    const uuid = manifestURL ? /moz-extension:\/\/([^/]*)/.exec(manifestURL)[1] : null;
+    return {
+      name,
+      icon,
+      id,
+      type,
+      details: {
+        actor,
+        location,
+        manifestURL,
+        uuid,
+      },
+    };
+  });
+}
+
+module.exports = extensionComponentDataMiddleware;
--- a/devtools/client/aboutdebugging-new/src/middleware/moz.build
+++ b/devtools/client/aboutdebugging-new/src/middleware/moz.build
@@ -1,7 +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/.
 
 DevToolsModules(
     'debug-target-listener.js',
+    'extension-component-data.js',
+    'tab-component-data.js',
+    'worker-component-data.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/middleware/tab-component-data.js
@@ -0,0 +1,48 @@
+/* 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/. */
+
+"use strict";
+
+const {
+  DEBUG_TARGETS,
+  REQUEST_TABS_SUCCESS,
+} = require("../constants");
+
+/**
+ * This middleware converts tabs object that get from DebuggerClient.listTabs() to data
+ * which is used in DebugTargetItem.
+ */
+const tabComponentDataMiddleware = store => next => action => {
+  switch (action.type) {
+    case REQUEST_TABS_SUCCESS: {
+      action.tabs = toComponentData(action.tabs);
+      break;
+    }
+  }
+
+  return next(action);
+};
+
+function toComponentData(tabs) {
+  return tabs.map(tab => {
+    const type = DEBUG_TARGETS.TAB;
+    const id = tab.outerWindowID;
+    const icon = tab.favicon
+      ? `data:image/png;base64,${ btoa(String.fromCharCode.apply(String, tab.favicon)) }`
+      : "chrome://devtools/skin/images/globe.svg";
+    const name = tab.title || tab.url;
+    const url = tab.url;
+    return {
+      name,
+      icon,
+      id,
+      type,
+      details: {
+        url,
+      },
+    };
+  });
+}
+
+module.exports = tabComponentDataMiddleware;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/middleware/worker-component-data.js
@@ -0,0 +1,78 @@
+/* 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/. */
+
+"use strict";
+
+const {
+  DEBUG_TARGETS,
+  REQUEST_WORKERS_SUCCESS,
+  SERVICE_WORKER_FETCH_STATES,
+  SERVICE_WORKER_STATUSES,
+} = require("../constants");
+
+/**
+ * This middleware converts workers object that get from DebuggerClient.listAllWorkers()
+ * to data which is used in DebugTargetItem.
+ */
+const workerComponentDataMiddleware = store => next => action => {
+  switch (action.type) {
+    case REQUEST_WORKERS_SUCCESS: {
+      action.otherWorkers = toComponentData(action.otherWorkers);
+      action.serviceWorkers = toComponentData(action.serviceWorkers, true);
+      action.sharedWorkers = toComponentData(action.sharedWorkers);
+      break;
+    }
+  }
+
+  return next(action);
+};
+
+function getServiceWorkerStatus(isActive, isRunning) {
+  if (isActive && isRunning) {
+    return SERVICE_WORKER_STATUSES.RUNNING;
+  } else if (isActive) {
+    return SERVICE_WORKER_STATUSES.STOPPED;
+  }
+  // We cannot get service worker registrations unless the registration is in
+  // ACTIVE state. Unable to know the actual state ("installing", "waiting"), we
+  // display a custom state "registering" for now. See Bug 1153292.
+  return SERVICE_WORKER_STATUSES.REGISTERING;
+}
+
+function toComponentData(workers, isServiceWorker) {
+  return workers.map(worker => {
+    const type = DEBUG_TARGETS.WORKER;
+    const id = worker.workerTargetActor;
+    const icon = "chrome://devtools/skin/images/debugging-workers.svg";
+    let { fetch, name, registrationActor, scope } = worker;
+    let isActive = false;
+    let isRunning = false;
+    let status = null;
+
+    if (isServiceWorker) {
+      fetch = fetch ? SERVICE_WORKER_FETCH_STATES.LISTENING
+                    : SERVICE_WORKER_FETCH_STATES.NOT_LISTENING;
+      isActive = worker.active;
+      isRunning = !!worker.workerTargetActor;
+      status = getServiceWorkerStatus(isActive, isRunning);
+    }
+
+    return {
+      name,
+      icon,
+      id,
+      type,
+      details: {
+        fetch,
+        isActive,
+        isRunning,
+        registrationActor,
+        scope,
+        status,
+      },
+    };
+  });
+}
+
+module.exports = workerComponentDataMiddleware;
--- a/devtools/client/aboutdebugging-new/src/reducers/runtime-state.js
+++ b/devtools/client/aboutdebugging-new/src/reducers/runtime-state.js
@@ -1,117 +1,57 @@
 /* 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/. */
 
 "use strict";
 
 const {
   CONNECT_RUNTIME_SUCCESS,
-  DEBUG_TARGETS,
   DISCONNECT_RUNTIME_SUCCESS,
   REQUEST_EXTENSIONS_SUCCESS,
   REQUEST_TABS_SUCCESS,
+  REQUEST_WORKERS_SUCCESS,
 } = require("../constants");
 
 function RuntimeState() {
   return {
     client: null,
     installedExtensions: [],
+    otherWorkers: [],
+    serviceWorkers: [],
+    sharedWorkers: [],
     tabs: [],
     temporaryExtensions: [],
   };
 }
 
 function runtimeReducer(state = RuntimeState(), action) {
   switch (action.type) {
     case CONNECT_RUNTIME_SUCCESS: {
       const { client } = action;
       return Object.assign({}, state, { client });
     }
     case DISCONNECT_RUNTIME_SUCCESS: {
       return RuntimeState();
     }
     case REQUEST_EXTENSIONS_SUCCESS: {
       const { installedExtensions, temporaryExtensions } = action;
-      return Object.assign({}, state, {
-        installedExtensions: toExtensionComponentData(installedExtensions),
-        temporaryExtensions: toExtensionComponentData(temporaryExtensions),
-      });
+      return Object.assign({}, state, { installedExtensions, temporaryExtensions });
     }
     case REQUEST_TABS_SUCCESS: {
       const { tabs } = action;
-      return Object.assign({}, state, { tabs: toTabComponentData(tabs) });
+      return Object.assign({}, state, { tabs });
+    }
+    case REQUEST_WORKERS_SUCCESS: {
+      const { otherWorkers, serviceWorkers, sharedWorkers } = action;
+      return Object.assign({}, state, { otherWorkers, serviceWorkers, sharedWorkers });
     }
 
     default:
       return state;
   }
 }
 
-function getExtensionFilePath(extension) {
-  // Only show file system paths, and only for temporarily installed add-ons.
-  if (!extension.temporarilyInstalled ||
-      !extension.url ||
-      !extension.url.startsWith("file://")) {
-    return null;
-  }
-
-  // Strip a leading slash from Windows drive letter URIs.
-  // file:///home/foo ~> /home/foo
-  // file:///C:/foo ~> C:/foo
-  const windowsRegex = /^file:\/\/\/([a-zA-Z]:\/.*)/;
-
-  if (windowsRegex.test(extension.url)) {
-    return windowsRegex.exec(extension.url)[1];
-  }
-
-  return extension.url.slice("file://".length);
-}
-
-function toExtensionComponentData(extensions) {
-  return extensions.map(extension => {
-    const type = DEBUG_TARGETS.EXTENSION;
-    const { actor, iconURL, id, manifestURL, name, temporarilyInstalled } = extension;
-    const icon = iconURL || "chrome://mozapps/skin/extensions/extensionGeneric.svg";
-    const location = getExtensionFilePath(extension);
-    const uuid = manifestURL ? /moz-extension:\/\/([^/]*)/.exec(manifestURL)[1] : null;
-    return {
-      name,
-      icon,
-      id,
-      type,
-      details: {
-        actor,
-        location,
-        manifestURL,
-        temporarilyInstalled,
-        uuid,
-      },
-    };
-  });
-}
-
-function toTabComponentData(tabs) {
-  return tabs.map(tab => {
-    const type = DEBUG_TARGETS.TAB;
-    const id = tab.outerWindowID;
-    const icon = tab.favicon
-      ? `data:image/png;base64,${ btoa(String.fromCharCode.apply(String, tab.favicon)) }`
-      : "chrome://devtools/skin/images/globe.svg";
-    const name = tab.title || tab.url;
-    const url = tab.url;
-    return {
-      name,
-      icon,
-      id,
-      type,
-      details: {
-        url,
-      },
-    };
-  });
-}
-
 module.exports = {
   RuntimeState,
   runtimeReducer,
 };
--- a/devtools/shared/css/generated/properties-db.js
+++ b/devtools/shared/css/generated/properties-db.js
@@ -4879,16 +4879,17 @@ exports.CSS_PROPERTIES = {
       "ellipse",
       "fill-box",
       "inherit",
       "initial",
       "inset",
       "margin-box",
       "none",
       "padding-box",
+      "path",
       "polygon",
       "stroke-box",
       "unset",
       "url",
       "view-box"
     ]
   },
   "clip-rule": {
@@ -8264,16 +8265,17 @@ exports.CSS_PROPERTIES = {
       "ellipse",
       "inherit",
       "initial",
       "inset",
       "linear-gradient",
       "margin-box",
       "none",
       "padding-box",
+      "path",
       "polygon",
       "radial-gradient",
       "repeating-linear-gradient",
       "repeating-radial-gradient",
       "unset",
       "url"
     ]
   },
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -6,17 +6,16 @@
 
 #include "nsTextEditorState.h"
 #include "mozilla/TextInputListener.h"
 
 #include "nsCOMPtr.h"
 #include "nsIPresShell.h"
 #include "nsView.h"
 #include "nsCaret.h"
-#include "nsEditorCID.h"
 #include "nsLayoutCID.h"
 #include "nsITextControlFrame.h"
 #include "nsContentCreatorFunctions.h"
 #include "nsTextControlFrame.h"
 #include "nsIControllers.h"
 #include "nsITransactionManager.h"
 #include "nsIControllerContext.h"
 #include "nsAttrValue.h"
new file mode 100644
--- /dev/null
+++ b/dom/security/fuzztest/csp_fuzzer.cpp
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+#include "FuzzingInterface.h"
+#include "nsCSPContext.h"
+#include "nsNetUtil.h"
+#include "nsStringFwd.h"
+
+static int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+  nsresult ret;
+  nsCOMPtr<nsIURI> selfURI;
+  ret = NS_NewURI(getter_AddRefs(selfURI), "http://selfuri.com");
+  if (ret != NS_OK)
+    return 0;
+
+  mozilla::OriginAttributes attrs;
+  nsCOMPtr<nsIPrincipal> selfURIPrincipal =
+    mozilla::BasePrincipal::CreateCodebasePrincipal(selfURI, attrs);
+  if (!selfURIPrincipal)
+    return 0;
+
+  nsCOMPtr<nsIContentSecurityPolicy> csp =
+    do_CreateInstance(NS_CSPCONTEXT_CONTRACTID, &ret);
+  if (ret != NS_OK)
+    return 0;
+
+  ret = csp->SetRequestContext(nullptr, selfURIPrincipal);
+  if (ret != NS_OK)
+    return 0;
+
+  NS_ConvertASCIItoUTF16 policy(reinterpret_cast<const char*>(data), size);
+  if (!policy.get())
+    return 0;
+  csp->AppendPolicy(policy, false, false);
+
+  return 0;
+}
+
+MOZ_FUZZING_INTERFACE_RAW(nullptr, LLVMFuzzerTestOneInput, ContentSecurityPolicyParser);
+
new file mode 100644
--- /dev/null
+++ b/dom/security/fuzztest/csp_fuzzer.dict
@@ -0,0 +1,95 @@
+### dom/security/nsCSPParser.cpp
+# tokens
+":"
+";"
+"/"
+"+"
+"-"
+"."
+"_"
+"~"
+"*"
+"'"
+"#"
+"?"
+"%"
+"!"
+"$"
+"&"
+"("
+")"
+"="
+"@"
+
+### https://www.w3.org/TR/{CSP,CSP2,CSP3}/
+# directive names
+"default-src"
+"script-src"
+"object-src"
+"style-src"
+"img-src"
+"media-src"
+"frame-src"
+"font-src"
+"connect-src"
+"report-uri"
+"frame-ancestors"
+"reflected-xss"
+"base-uri"
+"form-action"
+"manifest-src"
+"upgrade-insecure-requests"
+"child-src"
+"block-all-mixed-content"
+"require-sri-for"
+"sandbox"
+"worker-src"
+"plugin-types"
+"disown-opener"
+"report-to"
+
+# directive values
+"'self'"
+"'unsafe-inline'"
+"'unsafe-eval'"
+"'none'"
+"'strict-dynamic'"
+"'unsafe-hashed-attributes'"
+"'nonce-AA=='"
+"'sha256-fw=='"
+"'sha384-/w=='"
+"'sha512-//8='"
+
+# subresources
+"a"
+"audio"
+"embed"
+"iframe"
+"img"
+"link"
+"object"
+"script"
+"source"
+"style"
+"track"
+"video"
+
+# sandboxing flags
+"allow-forms"
+"allow-pointer-lock"
+"allow-popups"
+"allow-same-origin"
+"allow-scripts"
+"allow-top-navigation"
+
+# URI components
+"https:"
+"ws:"
+"blob:"
+"data:"
+"filesystem:"
+"javascript:"
+"http://"
+"selfuri.com"
+"127.0.0.1"
+"::1"
new file mode 100644
--- /dev/null
+++ b/dom/security/fuzztest/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Library('FuzzingDOMSecurity')
+
+LOCAL_INCLUDES += [
+    '/dom/security',
+    '/netwerk/base',
+]
+
+include('/tools/fuzzing/libfuzzer-config.mozbuild')
+
+SOURCES += [
+    'csp_fuzzer.cpp'
+]
+
+FINAL_LIBRARY = 'xul-gtest'
+
--- a/dom/security/moz.build
+++ b/dom/security/moz.build
@@ -46,8 +46,16 @@ include('/ipc/chromium/chromium-config.m
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/caps',
     '/docshell/base',  # for nsDocShell.h
     '/netwerk/base',
     '/netwerk/protocol/data', # for nsDataHandler.h
 ]
+
+include('/tools/fuzzing/libfuzzer-config.mozbuild')
+
+if CONFIG['FUZZING_INTERFACES']:
+    TEST_DIRS += [
+        'fuzztest'
+    ]
+
--- a/dom/serviceworkers/ServiceWorkerRegistration.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistration.cpp
@@ -217,27 +217,16 @@ ServiceWorkerRegistration::Update(ErrorR
     return nullptr;
   }
 
   RefPtr<Promise> outer = Promise::Create(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
-  if (RefPtr<ServiceWorkerGlobalScope> serviceWorkerGlobal =
-        do_QueryObject(global)) {
-    WorkerPrivate* wp;
-    if (serviceWorkerGlobal->Registration() == this &&
-        (wp = GetCurrentThreadWorkerPrivate()) &&
-        wp->IsLoadingWorkerScript()) {
-      outer->MaybeResolve(*this);
-      return outer.forget();
-    }
-  }
-
   RefPtr<ServiceWorkerRegistration> self = this;
 
   mPendingUpdatePromises += 1;
 
   mInner->Update(
     [outer, self](const ServiceWorkerRegistrationDescriptor& aDesc) {
       auto scopeExit = MakeScopeExit([&] { self->UpdatePromiseSettled(); });
       nsIGlobalObject* global = self->GetParentObject();
--- a/dom/serviceworkers/ServiceWorkerRegistrationImpl.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistrationImpl.cpp
@@ -821,18 +821,23 @@ ServiceWorkerRegistrationWorkerThread::U
 
   // Eventually we need to support all workers, but for right now this
   // code assumes we're on a service worker global as self.registration.
   if (NS_WARN_IF(!workerRef->Private()->IsServiceWorker())) {
     aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR));
     return;
   }
 
-  // This is ensured by the binding layer.
-  MOZ_ASSERT(!workerRef->Private()->IsLoadingWorkerScript());
+  // Avoid infinite update loops by ignoring update() calls during top
+  // level script evaluation.  See:
+  // https://github.com/slightlyoff/ServiceWorker/issues/800
+  if (workerRef->Private()->IsLoadingWorkerScript()) {
+    aSuccessCB(mDescriptor);
+    return;
+  }
 
   auto promise = MakeRefPtr<ServiceWorkerRegistrationPromise::Private>(__func__);
   auto holder =
     MakeRefPtr<DOMMozPromiseRequestHolder<ServiceWorkerRegistrationPromise>>(global);
 
   promise->Then(
     global->EventTargetFor(TaskCategory::Other), __func__,
     [successCB = std::move(aSuccessCB), holder] (const ServiceWorkerRegistrationDescriptor& aDescriptor) {
--- a/dom/svg/SVGPathData.cpp
+++ b/dom/svg/SVGPathData.cpp
@@ -555,17 +555,18 @@ SVGPathData::BuildPathForMeasuring() con
 
 // 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)
+                       Float aStrokeWidth,
+                       float aZoomFactor)
 {
   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);
   };
@@ -587,16 +588,20 @@ SVGPathData::BuildPath(const nsTArray<St
   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
 
+  auto scale = [aZoomFactor](const Point& p) {
+    return Point(p.x * aZoomFactor, p.y * aZoomFactor);
+  };
+
   // 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:
@@ -605,43 +610,43 @@ SVGPathData::BuildPath(const nsTArray<St
         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);
+        aBuilder->MoveTo(scale(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);
+          aBuilder->LineTo(scale(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);
+          aBuilder->BezierTo(scale(cp1), scale(cp2), scale(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) {
@@ -650,96 +655,96 @@ SVGPathData::BuildPath(const nsTArray<St
         }
 
         // 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);
+          aBuilder->BezierTo(scale(tcp1), scale(tcp2), scale(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);
+            aBuilder->LineTo(scale(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);
+              aBuilder->BezierTo(scale(cp1), scale(cp2), scale(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);
+          aBuilder->LineTo(scale(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);
+          aBuilder->LineTo(scale(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);
+          aBuilder->BezierTo(scale(cp1), scale(cp2), scale(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);
+          aBuilder->BezierTo(scale(tcp1), scale(tcp2), scale(segEnd));
         }
         break;
       }
       case StylePathCommand::Tag::Unknown:
         MOZ_ASSERT_UNREACHABLE("Unacceptable path segment type");
         return nullptr;
     }
 
--- a/dom/svg/SVGPathData.h
+++ b/dom/svg/SVGPathData.h
@@ -173,17 +173,18 @@ public:
    * 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);
+            Float aStrokeWidth,
+            float aZoomFactor = 1.0);
 
   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/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -672,17 +672,16 @@ SharedWorkerGlobalScope::Close()
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
   mWorkerPrivate->CloseInternal();
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerGlobalScope, WorkerGlobalScope,
                                    mClients, mRegistration)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerGlobalScope)
-  NS_INTERFACE_MAP_ENTRY_CONCRETE(ServiceWorkerGlobalScope)
 NS_INTERFACE_MAP_END_INHERITING(WorkerGlobalScope)
 
 NS_IMPL_ADDREF_INHERITED(ServiceWorkerGlobalScope, WorkerGlobalScope)
 NS_IMPL_RELEASE_INHERITED(ServiceWorkerGlobalScope, WorkerGlobalScope)
 
 ServiceWorkerGlobalScope::ServiceWorkerGlobalScope(WorkerPrivate* aWorkerPrivate,
                                                    const ServiceWorkerRegistrationDescriptor& aRegistrationDescriptor)
   : WorkerGlobalScope(aWorkerPrivate)
--- a/dom/workers/WorkerScope.h
+++ b/dom/workers/WorkerScope.h
@@ -297,29 +297,25 @@ public:
   }
 
   void
   Close();
 
   IMPL_EVENT_HANDLER(connect)
 };
 
-#define NS_DOM_SERVICEWORKERGLOBALSCOPE_IID \
-  {0x552bfa7e, 0x0dd5, 0x4e94, {0xa0, 0x43, 0xff, 0x34, 0x6b, 0x6e, 0x04, 0x46}}
-
 class ServiceWorkerGlobalScope final : public WorkerGlobalScope
 {
   const nsString mScope;
   RefPtr<Clients> mClients;
   RefPtr<ServiceWorkerRegistration> mRegistration;
 
   ~ServiceWorkerGlobalScope();
 
 public:
-  NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOM_SERVICEWORKERGLOBALSCOPE_IID)
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServiceWorkerGlobalScope,
                                            WorkerGlobalScope)
   IMPL_EVENT_HANDLER(notificationclick)
   IMPL_EVENT_HANDLER(notificationclose)
 
   ServiceWorkerGlobalScope(WorkerPrivate* aWorkerPrivate,
                            const ServiceWorkerRegistrationDescriptor& aRegistrationDescriptor);
@@ -354,18 +350,16 @@ public:
   GetOnfetch();
 
   void
   SetOnfetch(mozilla::dom::EventHandlerNonNull* aCallback);
 
   void EventListenerAdded(nsAtom* aType) override;
 };
 
-NS_DEFINE_STATIC_IID_ACCESSOR(ServiceWorkerGlobalScope, NS_DOM_SERVICEWORKERGLOBALSCOPE_IID)
-
 class WorkerDebuggerGlobalScope final : public DOMEventTargetHelper,
                                         public nsIGlobalObject
 {
   WorkerPrivate* mWorkerPrivate;
   RefPtr<Console> mConsole;
   nsCOMPtr<nsISerialEventTarget> mSerialEventTarget;
 
 public:
--- a/editor/moz.build
+++ b/editor/moz.build
@@ -26,18 +26,14 @@ XPIDL_SOURCES += [
     'nsIHTMLObjectResizer.idl',
     'nsIPlaintextEditor.idl',
     'nsITableEditor.idl',
     'nsIURIRefObject.idl',
 ]
 
 XPIDL_MODULE = 'editor'
 
-EXPORTS += [
-    'nsEditorCID.h',
-]
-
 TESTING_JS_MODULES += [
     'AsyncSpellCheckTestHelper.jsm',
 ]
 
 with Files('**'):
     BUG_COMPONENT = ('Core', 'Editor')
deleted file mode 100644
--- a/editor/nsEditorCID.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/* 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/. */
-
-#ifndef NSEDITORCID_H___
-
-#define NS_EDITOR_CID \
-{/* {D3DE3431-8A75-11d2-918C-0080C8E44DB5}*/ \
-0xd3de3431, 0x8a75, 0x11d2, \
-{ 0x91, 0x8c, 0x0, 0x80, 0xc8, 0xe4, 0x4d, 0xb5 } }
-
-#define NS_TEXTEDITOR_CID \
-{/* {e197cc01-cfe1-11d4-8eb0-87ae406dfd3f}*/ \
-0xe197cc01, 0xcfe1, 0x11d4, \
-{ 0x8e, 0xb0, 0x87, 0xae, 0x40, 0x6d, 0xfd, 0x3f } }
-
-#define NS_HTMLEDITOR_CID \
-{/* {ed0244e0-c144-11d2-8f4c-006008159b0c}*/ \
-0xed0244e0, 0xc144, 0x11d2, \
-{ 0x8f, 0x4c, 0x0, 0x60, 0x08, 0x15, 0x9b, 0x0c } }
-
-#endif //NSEDITORCID_H___
-
-
-
--- a/js/src/jit-test/tests/wasm/ion-args.js
+++ b/js/src/jit-test/tests/wasm/ion-args.js
@@ -5,20 +5,27 @@ let { exports } = wasmEvalText(`(module
 
     (func (export "f32") (result f32) (param f32)
      get_local 0
     )
 
     (func (export "f64") (result f64) (param f64)
      get_local 0
     )
+
+    (func (export "mixed_args") (result f64)
+        (param i32) (param i32) (param i32) (param i32) (param i32) ;; 5 i32
+        (param $f64 f64) ;; 1 f64
+        (param i32)
+     get_local $f64
+    )
 )`);
 
 const options = getJitCompilerOptions();
-const jitThreshold = options['ion.warmup.trigger'] * 2;
+const jitThreshold = options['ion.warmup.trigger'] * 2 + 2;
 
 let coercions = {
     i32(x) { return x|0; },
     f32(x) { return Math.fround(x); },
     f64(x) { return +x; }
 }
 
 function call(func, coercion, arg) {
@@ -33,28 +40,98 @@ function call(func, coercion, arg) {
         try {
             assertEq(func(arg), expected);
         } catch(e) {
             assertEq(e.message, expected);
         }
     }
 }
 
-const inputs = [
-    42,
-    3.5,
-    -0,
-    -Infinity,
-    2**32,
-    true,
-    Symbol(),
-    undefined,
-    null,
-    {},
-    { valueOf() { return 13.37; } },
-    "bonjour"
-];
+// Test misc kinds of arguments.
+(function() {
+    const inputs = [
+        42,
+        3.5,
+        -0,
+        -Infinity,
+        2**32,
+        true,
+        Symbol(),
+        undefined,
+        null,
+        {},
+        { valueOf() { return 13.37; } },
+        "bonjour"
+    ];
+
+    for (let arg of inputs) {
+        for (let func of ['i32', 'f32', 'f64']) {
+            call(exports[func], coercions[func], arg);
+        }
+    }
+})();
+
+// Test mixup of float and int arguments.
+(function() {
+    for (let i = 0; i < 10; i++) {
+        assertEq(exports.mixed_args(i, i+1, i+2, i+3, i+4, i+0.5, i+5), i+0.5);
+    }
+})();
+
+// Test high number of arguments.
+// All integers.
+let {func} = wasmEvalText(`(module
+    (func (export "func") (result i32)
+        ${Array(32).join('(param i32)')}
+        (param $last i32)
+     get_local $last
+    )
+)`).exports;
 
-for (let arg of inputs) {
-    for (let func of ['i32', 'f32', 'f64']) {
-        call(exports[func], coercions[func], arg);
+(function() {
+    for (let i = 0; i < 10; i++) {
+        assertEq(func(i, i+1, i+2, i+3, i+4, i+5, i+6, i+7, i+8, i+9, i+10, i+11, i+12, i+13, i+14, i+15,
+                      i+16, i+17, i+18, i+19, i+20, i+21, i+22, i+23, i+24, i+25, i+26, i+27, i+28, i+29, i+30, i+31
+                 ), i+31);
+    }
+})();
+
+// All floats.
+func = wasmEvalText(`(module
+    (func (export "func") (result i32)
+        ${Array(32).join('(param f64)')}
+        (param $last i32)
+     get_local $last
+    )
+)`).exports.func;
+
+(function() {
+    for (let i = 0; i < 10; i++) {
+        assertEq(func(i, i+1, i+2, i+3, i+4, i+5, i+6, i+7, i+8, i+9, i+10, i+11, i+12, i+13, i+14, i+15,
+                      i+16, i+17, i+18, i+19, i+20, i+21, i+22, i+23, i+24, i+25, i+26, i+27, i+28, i+29, i+30, i+31
+                 ), i+31);
     }
+})();
+
+// Mix em up! 1 i32, then 1 f32, then 1 f64, and again up to 32 args.
+let params = [];
+for (let i = 0; i < 32; i++) {
+    params.push((i % 3 == 0) ? 'i32' :
+                (i % 3 == 1) ? 'f32' :
+                'f64'
+               );
 }
+
+func = wasmEvalText(`(module
+    (func (export "func") (result i32)
+        ${Array(32).join('(param f64)')}
+        (param $last i32)
+     get_local $last
+    )
+)`).exports.func;
+
+(function() {
+    for (let i = 0; i < 10; i++) {
+        assertEq(func(i, i+1, i+2, i+3, i+4, i+5, i+6, i+7, i+8, i+9, i+10, i+11, i+12, i+13, i+14, i+15,
+                      i+16, i+17, i+18, i+19, i+20, i+21, i+22, i+23, i+24, i+25, i+26, i+27, i+28, i+29, i+30, i+31
+                 ), i+31);
+    }
+})();
--- a/js/src/jit-test/tests/wasm/ion-error-i64.js
+++ b/js/src/jit-test/tests/wasm/ion-error-i64.js
@@ -5,33 +5,45 @@ if (!options['baseline.enable'])
     quit();
 
 const { nextLineNumber, startProfiling, endProfiling, assertEqPreciseStacks } = WasmHelpers;
 
 const TRIGGER = options['ion.warmup.trigger'] + 10;
 const ITER = 2 * TRIGGER;
 const EXCEPTION_ITER = ITER - 2;
 
-enableGeckoProfiling();
-
 var instance = wasmEvalText(`(module
     (func $add (export "add") (result i32) (param i32) (param i32)
      get_local 0
      get_local 1
      i32.add
     )
 
     (func $addi64 (export "add64") (result i64) (param i32) (param i32)
      get_local 0
      get_local 1
      call $add
      i64.extend_s/i32
     )
+
+    (func $add_two_i64 (export "add_two_i64") (result i64) (param i64) (param i64)
+     get_local 0
+     get_local 1
+     i64.add
+    )
 )`).exports;
 
+(function() {
+    // In ion-eager mode, make sure we don't try to inline a function that
+    // takes or returns i64 arguments.
+    assertErrorMessage(() => instance.add_two_i64(0, 1), TypeError, /cannot pass i64 to or from JS/);
+})();
+
+enableGeckoProfiling();
+
 var callToMain;
 
 function main() {
     var arrayCallLine = nextLineNumber(13);
     for (var i = 0; i < ITER; i++) {
         var arr = [instance.add, (x,y)=>x+y];
         if (i === EXCEPTION_ITER) {
             arr[0] = instance.add64;
@@ -68,17 +80,19 @@ function main() {
             assertEq(callsites[0], 'main');
             assertEq(callsites[1], ''); // global scope
 
             // Which line numbers appear in the error stack.
             let lines = stack.map(s => s.split(':')[1]);
             assertEq(+lines[0], arrayCallLine);
             assertEq(+lines[1], callToMain);
         } else if ((i % 2) == 0) {
+            // Regular call to wasm add on 32 bits integers.
             assertEqPreciseStacks(profilingStack, [
+                ['', '0', ''],                // supa-dupa fast path
                 ['', '>', '0,>', '>', ''],    // fast path
                 ['', '!>', '0,!>', '!>', ''], // slow path
             ]);
         }
     }
 }
 
 callToMain = nextLineNumber();
--- a/js/src/jit-test/tests/wasm/ion-error-ool.js
+++ b/js/src/jit-test/tests/wasm/ion-error-ool.js
@@ -7,16 +7,17 @@ if (!options['baseline.enable'])
 const { assertStackTrace, startProfiling, endProfiling, assertEqPreciseStacks } = WasmHelpers;
 
 const TRIGGER = options['baseline.warmup.trigger'] + 10;
 const ITER = 2 * TRIGGER;
 const EXCEPTION_ITER = TRIGGER + 5;
 
 const SLOW_ENTRY_STACK = ['', '!>', '0,!>', '!>', ''];
 const FAST_ENTRY_STACK = ['', '>', '0,>', '>', ''];
+const INLINED_CALL_STACK = ['', '0', ''];
 const FAST_OOL_ENTRY_STACK = ['', '>', '<,>', 'ool>,>', '<,>', '>', '0,>', '>', ''];
 const EXCEPTION_ENTRY_STACK = ['', '>', '<,>', 'ool>,>', '<,>', '>', ''];
 
 enableGeckoProfiling();
 
 for (let type of ['i32', 'f32', 'f64']) {
     var instance = wasmEvalText(`(module
         (func $add (export "add") (result ${type}) (param ${type}) (param ${type})
@@ -39,17 +40,17 @@ for (let type of ['i32', 'f32', 'f64']) 
     }
 
     var x = 0;
     function main() {
         let observedStacks = [0, 0, 0];
         for (var i = 0; i < ITER; i++) {
             startProfiling();
             loopBody(i + 1, i + EXCEPTION_ITER + 1);
-            assertEqPreciseStacks(endProfiling(), [FAST_ENTRY_STACK, SLOW_ENTRY_STACK]);
+            assertEqPreciseStacks(endProfiling(), [INLINED_CALL_STACK, FAST_ENTRY_STACK, SLOW_ENTRY_STACK]);
 
             if (i === EXCEPTION_ITER) {
                 x = { valueOf: function innerValueOf() { throw new Error("ph34r"); }};
             } else {
                 x = i;
             }
 
             startProfiling();
--- a/js/src/jit-test/tests/wasm/ion-error-throw.js
+++ b/js/src/jit-test/tests/wasm/ion-error-throw.js
@@ -20,25 +20,26 @@ let { add } = wasmEvalText(`(module
      get_local 0
      get_local 1
      i32.add
     )
 )`).exports;
 
 const SLOW_ENTRY_STACK = ['', '!>', '0,!>', '!>', ''];
 const FAST_ENTRY_STACK = ['', '>', '0,>', '>', ''];
+const INLINED_CALL_STACK = ['', '0', ''];
 
 function main() {
     for (let i = 0; i < 50; i++) {
         startProfiling();
         try {
             assertEq(add(i, i+1), 2*i+1);
         } catch (e) {
             assertEq(i, 42);
             assertEq(e.message.includes("unreachable"), true);
             assertStackTrace(e, ['wasm-function[0]', 'main', '']);
         }
         let stack = endProfiling();
-        assertEqPreciseStacks(stack, [FAST_ENTRY_STACK, SLOW_ENTRY_STACK]);
+        assertEqPreciseStacks(stack, [INLINED_CALL_STACK, FAST_ENTRY_STACK, SLOW_ENTRY_STACK]);
     }
 }
 
 main();
--- a/js/src/jit-test/tests/wasm/ion-lazy-tables.js
+++ b/js/src/jit-test/tests/wasm/ion-lazy-tables.js
@@ -7,32 +7,34 @@ if (!options['baseline.enable'])
 const { assertStackTrace, startProfiling, endProfiling, assertEqPreciseStacks } = WasmHelpers;
 
 const TRIGGER = options['baseline.warmup.trigger'] + 10;
 const ITER = 2 * TRIGGER;
 const EXCEPTION_ITER = TRIGGER + 5;
 
 const SLOW_ENTRY_STACK = ['', '!>', '0,!>', '!>', ''];
 const FAST_ENTRY_STACK = ['', '>', '0,>', '>', ''];
+const INLINED_CALL_STACK = ['', '0', ''];
+const EXPECTED_STACKS = [SLOW_ENTRY_STACK, FAST_ENTRY_STACK, INLINED_CALL_STACK];
 
 function main() {
     var { table } = wasmEvalText(`(module
         (func $add (result i32) (param i32) (param i32)
          get_local 0
          get_local 1
          i32.add
         )
         (table (export "table") 10 anyfunc)
         (elem (i32.const 0) $add)
     )`).exports;
 
     for (var i = 0; i < ITER; i++) {
         startProfiling();
         assertEq(table.get(0)(i, i+1), i*2+1);
-        assertEqPreciseStacks(endProfiling(), [FAST_ENTRY_STACK, SLOW_ENTRY_STACK]);
+        assertEqPreciseStacks(endProfiling(), EXPECTED_STACKS);
     }
 }
 
 function withTier2() {
     setJitCompilerOption('wasm.delay-tier2', 1);
 
     var module = new WebAssembly.Module(wasmTextToBinary(`(module
         (func $add (result i32) (param i32) (param i32)
@@ -45,23 +47,23 @@ function withTier2() {
     )`));
     var { table } = new WebAssembly.Instance(module).exports;
 
     let i = 0;
     do {
         i++;
         startProfiling();
         assertEq(table.get(0)(i, i+1), i*2+1);
-        assertEqPreciseStacks(endProfiling(), [FAST_ENTRY_STACK, SLOW_ENTRY_STACK]);
+        assertEqPreciseStacks(endProfiling(), EXPECTED_STACKS);
     } while (!wasmHasTier2CompilationCompleted(module));
 
     for (i = 0; i < ITER; i++) {
         startProfiling();
         assertEq(table.get(0)(i, i+1), i*2+1);
-        assertEqPreciseStacks(endProfiling(), [FAST_ENTRY_STACK, SLOW_ENTRY_STACK]);
+        assertEqPreciseStacks(endProfiling(), EXPECTED_STACKS);
     }
 
     setJitCompilerOption('wasm.delay-tier2', 0);
 }
 
 enableGeckoProfiling();
 main();
 withTier2();
--- a/js/src/jit-test/tests/wasm/profiling.js
+++ b/js/src/jit-test/tests/wasm/profiling.js
@@ -1,16 +1,21 @@
 if (!WasmHelpers.isSingleStepProfilingEnabled)
     quit();
 
 const Module = WebAssembly.Module;
 const Instance = WebAssembly.Instance;
 const Table = WebAssembly.Table;
 
-const { assertEqImpreciseStacks, startProfiling, endProfiling } = WasmHelpers;
+const {
+    assertEqImpreciseStacks,
+    assertEqPreciseStacks,
+    startProfiling,
+    endProfiling
+} = WasmHelpers;
 
 function test(code, importObj, expectedStacks)
 {
     enableGeckoProfiling();
 
     var f = wasmEvalText(code, importObj).exports[""];
     startProfiling();
     f();
@@ -375,8 +380,38 @@ for (let type of ['f32', 'f64']) {
     var code = wasmTextToBinary('(module (func (export "run") (result i32) i32.const 42))');
     var i = new WebAssembly.Instance(new WebAssembly.Module(code));
     assertEq(i.exports.run(), 42);
  `);
 
  disableSingleStepProfiling();
  disableGeckoProfiling();
 })();
+
+// Ion->wasm calls.
+let func = wasmEvalText(`(module
+    (func $inner (result i32) (param i32) (param i32)
+        get_local 0
+        get_local 1
+        i32.add
+    )
+    (func (export "add") (result i32) (param i32) (param i32)
+     get_local 0
+     get_local 1
+     call $inner
+    )
+)`).exports.add;
+
+(function() {
+    enableGeckoProfiling();
+    // 10 is enough in ion eager mode.
+    for (let i = 0; i < 10; i++) {
+        enableSingleStepProfiling();
+        let res = func(i - 1, i + 1);
+        assertEqPreciseStacks(disableSingleStepProfiling(), [
+            ['', '>', '1,>', '0,1,>' , '1,>', '>', ''],      // slow entry
+            ['', '!>', '1,!>', '0,1,!>' , '1,!>', '!>', ''], // fast entry
+            ['', '1', '0,1' , '1', ''],                      // inlined jit call
+        ]);
+        assertEq(res, i+i);
+    }
+    disableGeckoProfiling();
+})();
--- a/js/src/jit/Bailouts.h
+++ b/js/src/jit/Bailouts.h
@@ -103,17 +103,17 @@ static const uint32_t BAILOUT_RETURN_FAT
 static const uint32_t BAILOUT_RETURN_OVERRECURSED = 2;
 
 // This address is a magic number made to cause crashes while indicating that we
 // are making an attempt to mark the stack during a bailout.
 static const uint32_t FAKE_EXITFP_FOR_BAILOUT_ADDR = 0xba2;
 static uint8_t* const FAKE_EXITFP_FOR_BAILOUT =
     reinterpret_cast<uint8_t*>(FAKE_EXITFP_FOR_BAILOUT_ADDR);
 
-static_assert(!(FAKE_EXITFP_FOR_BAILOUT_ADDR & JitActivation::ExitFpWasmBit),
+static_assert(!(FAKE_EXITFP_FOR_BAILOUT_ADDR & wasm::ExitOrJitEntryFPTag),
               "FAKE_EXITFP_FOR_BAILOUT could be mistaken as a low-bit tagged wasm exit fp");
 
 // BailoutStack is an architecture specific pointer to the stack, given by the
 // bailout handler.
 class BailoutStack;
 class InvalidationBailoutStack;
 
 // Must be implemented by each architecture.
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -49,16 +49,17 @@
 #include "vm/AsyncIteration.h"
 #include "vm/MatchPairs.h"
 #include "vm/RegExpObject.h"
 #include "vm/RegExpStatics.h"
 #include "vm/StringType.h"
 #include "vm/TraceLogging.h"
 #include "vm/TypedArrayObject.h"
 #include "vtune/VTuneWrapper.h"
+#include "wasm/WasmStubs.h"
 
 #include "builtin/Boolean-inl.h"
 #include "jit/MacroAssembler-inl.h"
 #include "jit/shared/CodeGenerator-shared-inl.h"
 #include "jit/shared/Lowering-shared-inl.h"
 #include "jit/TemplateObject-inl.h"
 #include "vm/Interpreter-inl.h"
 #include "vm/JSScript-inl.h"
@@ -13551,13 +13552,140 @@ CodeGenerator::visitGetPrototypeOf(LGetP
     masm.jump(ool->rejoin());
 
     masm.bind(&hasProto);
     masm.tagValue(JSVAL_TYPE_OBJECT, scratch, out);
 
     masm.bind(ool->rejoin());
 }
 
+template <size_t NumDefs>
+void
+CodeGenerator::emitIonToWasmCallBase(LIonToWasmCallBase<NumDefs>* lir)
+{
+    wasm::JitCallStackArgVector stackArgs;
+    masm.propagateOOM(stackArgs.reserve(lir->numOperands()));
+    if (masm.oom())
+        return;
+
+    const wasm::FuncExport& funcExport = lir->mir()->funcExport();
+    const wasm::FuncType& sig = funcExport.funcType();
+
+    ABIArgGenerator abi;
+    for (size_t i = 0; i < lir->numOperands(); i++) {
+        MIRType argMir;
+        switch (sig.args()[i].code()) {
+          case wasm::ValType::I32:
+          case wasm::ValType::F32:
+          case wasm::ValType::F64:
+            argMir = ToMIRType(sig.args()[i]);
+            break;
+          case wasm::ValType::I64:
+          case wasm::ValType::Ref:
+          case wasm::ValType::AnyRef:
+            // Don't forget to trace GC type arguments in TraceJitExitFrames
+            // when they're enabled.
+            MOZ_CRASH("unexpected argument type when calling from ion to wasm");
+        }
+
+        ABIArg arg = abi.next(argMir);
+        switch (arg.kind()) {
+          case ABIArg::GPR:
+          case ABIArg::FPU: {
+            MOZ_ASSERT(ToAnyRegister(lir->getOperand(i)) == arg.reg());
+            stackArgs.infallibleEmplaceBack(wasm::JitCallStackArg());
+            break;
+          }
+          case ABIArg::Stack: {
+            const LAllocation* larg = lir->getOperand(i);
+            if (larg->isConstant())
+                stackArgs.infallibleEmplaceBack(ToInt32(larg));
+            else if (larg->isGeneralReg())
+                stackArgs.infallibleEmplaceBack(ToRegister(larg));
+            else if (larg->isFloatReg())
+                stackArgs.infallibleEmplaceBack(ToFloatRegister(larg));
+            else
+                stackArgs.infallibleEmplaceBack(ToAddress(larg));
+            break;
+          }
+#ifdef JS_CODEGEN_REGISTER_PAIR
+          case ABIArg::GPR_PAIR: {
+            MOZ_CRASH("no way to pass i64, and wasm uses hardfp for function calls");
+          }
+#endif
+          case ABIArg::Uninitialized: {
+            MOZ_CRASH("Uninitialized ABIArg kind");
+          }
+        }
+    }
+
+    switch (sig.ret().code()) {
+      case wasm::ExprType::Void:
+        MOZ_ASSERT(lir->mir()->type() == MIRType::Value);
+        break;
+      case wasm::ExprType::I32:
+        MOZ_ASSERT(lir->mir()->type() == MIRType::Int32);
+        MOZ_ASSERT(ToRegister(lir->output()) == ReturnReg);
+        break;
+      case wasm::ExprType::F32:
+        MOZ_ASSERT(lir->mir()->type() == MIRType::Float32);
+        MOZ_ASSERT(ToFloatRegister(lir->output()) == ReturnFloat32Reg);
+        break;
+      case wasm::ExprType::F64:
+        MOZ_ASSERT(lir->mir()->type() == MIRType::Double);
+        MOZ_ASSERT(ToFloatRegister(lir->output()) == ReturnDoubleReg);
+        break;
+      case wasm::ExprType::Ref:
+      case wasm::ExprType::AnyRef:
+      case wasm::ExprType::I64:
+        // Don't forget to trace GC type return value in TraceJitExitFrames
+        // when they're enabled.
+        MOZ_CRASH("unexpected return type when calling from ion to wasm");
+      case wasm::ExprType::Limit:
+        MOZ_CRASH("Limit");
+    }
+
+    bool profilingEnabled = isProfilerInstrumentationEnabled();
+    WasmInstanceObject* instObj = lir->mir()->instanceObject();
+
+    bool wasmGcEnabled = false;
+#ifdef ENABLE_WASM_GC
+    wasmGcEnabled = gen->options.wasmGcEnabled();
+#endif
+
+    Register scratch = ToRegister(lir->temp());
+
+    uint32_t callOffset;
+    GenerateDirectCallFromJit(masm,
+                              funcExport,
+                              instObj->instance(),
+                              stackArgs,
+                              profilingEnabled,
+                              wasmGcEnabled,
+                              scratch,
+                              &callOffset);
+
+    // Add the instance object to the constant pool, so it is transferred to
+    // the owning IonScript and so that it gets traced as long as the IonScript
+    // lives.
+
+    uint32_t unused;
+    masm.propagateOOM(graph.addConstantToPool(ObjectValue(*instObj), &unused));
+
+    markSafepointAt(callOffset, lir);
+}
+
+void
+CodeGenerator::visitIonToWasmCall(LIonToWasmCall* lir)
+{
+    emitIonToWasmCallBase(lir);
+}
+void
+CodeGenerator::visitIonToWasmCallV(LIonToWasmCallV* lir)
+{
+    emitIonToWasmCallBase(lir);
+}
+
 static_assert(!std::is_polymorphic<CodeGenerator>::value,
               "CodeGenerator should not have any virtual methods");
 
 } // namespace jit
 } // namespace js
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -214,16 +214,19 @@ class CodeGenerator final : public CodeG
     template <class IteratorObject, class OrderedHashTable>
     void emitGetNextEntryForIterator(LGetNextEntryForIterator* lir);
 
     template <class OrderedHashTable>
     void emitLoadIteratorValues(Register result, Register temp, Register front);
 
     void emitWasmCallBase(MWasmCall* mir, bool needsBoundsCheck);
 
+    template<size_t NumDefs>
+    void emitIonToWasmCallBase(LIonToWasmCallBase<NumDefs>* lir);
+
     IonScriptCounts* maybeCreateScriptCounts();
 
     // This function behaves like testValueTruthy with the exception that it can
     // choose to let control flow fall through when the object is truthy, as
     // an optimization. Use testValueTruthy when it's required to branch to one
     // of the two labels.
     void testValueTruthyKernel(const ValueOperand& value,
                                const LDefinition* scratch1, const LDefinition* scratch2,
--- a/js/src/jit/CompileWrappers.cpp
+++ b/js/src/jit/CompileWrappers.cpp
@@ -317,18 +317,24 @@ CompileRealm::setSingletonsAsValues()
 {
     realm()->behaviors().setSingletonsAsValues();
 }
 
 JitCompileOptions::JitCompileOptions()
   : cloneSingletons_(false),
     profilerSlowAssertionsEnabled_(false),
     offThreadCompilationAvailable_(false)
+#ifdef ENABLE_WASM_GC
+    , wasmGcEnabled_(false)
+#endif
 {
 }
 
 JitCompileOptions::JitCompileOptions(JSContext* cx)
 {
     cloneSingletons_ = cx->realm()->creationOptions().cloneSingletons();
     profilerSlowAssertionsEnabled_ = cx->runtime()->geckoProfiler().enabled() &&
                                      cx->runtime()->geckoProfiler().slowAssertionsEnabled();
     offThreadCompilationAvailable_ = OffThreadCompilationAvailable(cx);
+#ifdef ENABLE_WASM_GC
+    wasmGcEnabled_ = cx->options().wasmGc();
+#endif
 }
--- a/js/src/jit/CompileWrappers.h
+++ b/js/src/jit/CompileWrappers.h
@@ -133,18 +133,27 @@ class JitCompileOptions
     bool profilerSlowAssertionsEnabled() const {
         return profilerSlowAssertionsEnabled_;
     }
 
     bool offThreadCompilationAvailable() const {
         return offThreadCompilationAvailable_;
     }
 
+#ifdef ENABLE_WASM_GC
+    bool wasmGcEnabled() const {
+        return wasmGcEnabled_;
+    }
+#endif
+
   private:
     bool cloneSingletons_;
     bool profilerSlowAssertionsEnabled_;
     bool offThreadCompilationAvailable_;
+#ifdef ENABLE_WASM_GC
+    bool wasmGcEnabled_;
+#endif
 };
 
 } // namespace jit
 } // namespace js
 
 #endif // jit_CompileWrappers_h
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -11698,17 +11698,17 @@ IonBuilder::setPropTryCommonDOMSetter(bo
 
 AbortReasonOr<Ok>
 IonBuilder::setPropTryTypedObject(bool* emitted, MDefinition* obj,
                                   PropertyName* name, MDefinition* value)
 {
     TypedObjectPrediction fieldPrediction;
     size_t fieldOffset;
     size_t fieldIndex;
-    bool fieldMutable;
+    bool fieldMutable = false;
     if (!typedObjectHasField(obj, name, &fieldOffset, &fieldPrediction, &fieldIndex, &fieldMutable))
         return Ok();
 
     if (!fieldMutable)
         return Ok();
 
     switch (fieldPrediction.kind()) {
       case type::Reference:
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -745,16 +745,17 @@ class IonBuilder
                                   const Class* clasp3 = nullptr,
                                   const Class* clasp4 = nullptr);
     InliningResult inlineGuardToClass(CallInfo& callInfo, const Class* clasp);
     InliningResult inlineIsConstructing(CallInfo& callInfo);
     InliningResult inlineSubstringKernel(CallInfo& callInfo);
     InliningResult inlineObjectHasPrototype(CallInfo& callInfo);
     InliningResult inlineFinishBoundFunctionInit(CallInfo& callInfo);
     InliningResult inlineIsPackedArray(CallInfo& callInfo);
+    InliningResult inlineWasmCall(CallInfo& callInfo, JSFunction* target);
 
     // Testing functions.
     InliningResult inlineBailout(CallInfo& callInfo);
     InliningResult inlineAssertFloat32(CallInfo& callInfo);
     InliningResult inlineAssertRecoveredOnBailout(CallInfo& callInfo);
 
     // Bind function.
     InliningResult inlineBoundFunction(CallInfo& callInfo, JSFunction* target);
--- a/js/src/jit/IonTypes.h
+++ b/js/src/jit/IonTypes.h
@@ -413,17 +413,17 @@ enum class IntConversionInputKind {
     NumbersOnly,
     NumbersOrBoolsOnly,
     Any
 };
 
 // The ordering of this enumeration is important: Anything < Value is a
 // specialized type. Furthermore, anything < String has trivial conversion to
 // a number.
-enum class MIRType
+enum class MIRType: uint8_t
 {
     Undefined,
     Null,
     Boolean,
     Int32,
     Int64,
     Double,
     Float32,
@@ -457,17 +457,17 @@ enum class MIRType
     Bool16x8  = Boolean | (3 << VECTOR_SCALE_SHIFT),
     Bool32x4  = Boolean | (2 << VECTOR_SCALE_SHIFT),
     Doublex2  = Double  | (1 << VECTOR_SCALE_SHIFT)
 };
 
 static inline bool
 IsSimdType(MIRType type)
 {
-    return ((unsigned(type) >> VECTOR_SCALE_SHIFT) & VECTOR_SCALE_MASK) != 0;
+    return ((uint8_t(type) >> VECTOR_SCALE_SHIFT) & VECTOR_SCALE_MASK) != 0;
 }
 
 static inline MIRType
 MIRTypeFromValueType(JSValueType type)
 {
     // This function does not deal with magic types. Magic constants should be
     // filtered out in MIRTypeFromValue.
     switch (type) {
--- a/js/src/jit/JSJitFrameIter.cpp
+++ b/js/src/jit/JSJitFrameIter.cpp
@@ -27,24 +27,25 @@ JSJitFrameIter::JSJitFrameIter(const Jit
         current_ = activation_->bailoutData()->fp();
         frameSize_ = activation_->bailoutData()->topFrameSize();
         type_ = JitFrame_Bailout;
     } else {
         MOZ_ASSERT(!TlsContext.get()->inUnsafeCallWithABI);
     }
 }
 
-JSJitFrameIter::JSJitFrameIter(const JitActivation* activation, uint8_t* fp)
+JSJitFrameIter::JSJitFrameIter(const JitActivation* activation, FrameType frameType, uint8_t* fp)
   : current_(fp),
-    type_(JitFrame_JSJitToWasm),
+    type_(frameType),
     returnAddressToFp_(nullptr),
     frameSize_(0),
     cachedSafepointIndex_(nullptr),
     activation_(activation)
 {
+    MOZ_ASSERT(type_ == JitFrame_JSJitToWasm || type_ == JitFrame_Exit);
     MOZ_ASSERT(!activation_->bailoutData());
     MOZ_ASSERT(!TlsContext.get()->inUnsafeCallWithABI);
 }
 
 bool
 JSJitFrameIter::checkInvalidation() const
 {
     IonScript* dummy;
--- a/js/src/jit/JSJitFrameIter.h
+++ b/js/src/jit/JSJitFrameIter.h
@@ -114,17 +114,17 @@ class JSJitFrameIter
     void dumpBaseline() const;
 
   public:
     // See comment above the class.
     explicit JSJitFrameIter(const JitActivation* activation);
 
     // A constructor specialized for jit->wasm frames, which starts at a
     // specific FP.
-    JSJitFrameIter(const JitActivation* activation, uint8_t* fp);
+    JSJitFrameIter(const JitActivation* activation, FrameType frameType, uint8_t* fp);
 
     // Used only by DebugModeOSRVolatileJitFrameIter.
     void exchangeReturnAddressIfMatch(uint8_t* oldAddr, uint8_t* newAddr) {
         if (returnAddressToFp_ == oldAddr)
             returnAddressToFp_ = newAddr;
     }
 
     // Current frame information.
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -1165,16 +1165,22 @@ TraceJitExitFrame(JSTracer* trc, const J
     if (frame.isExitFrameLayout<CalledFromJitExitFrameLayout>()) {
         auto* layout = frame.exitFrame()->as<CalledFromJitExitFrameLayout>();
         JitFrameLayout* jsLayout = layout->jsFrame();
         jsLayout->replaceCalleeToken(TraceCalleeToken(trc, jsLayout->calleeToken()));
         TraceThisAndArguments(trc, frame, jsLayout);
         return;
     }
 
+    if (frame.isExitFrameLayout<DirectWasmJitCallFrameLayout>()) {
+        // Nothing to do: we can't have object arguments (yet!) and the callee
+        // is traced elsewhere.
+        return;
+    }
+
     if (frame.isBareExit()) {
         // Nothing to trace. Fake exit frame pushed for VM functions with
         // nothing to trace on the stack.
         return;
     }
 
     MOZ_ASSERT(frame.exitFrame()->isWrapperExit());
 
--- a/js/src/jit/JitFrames.h
+++ b/js/src/jit/JitFrames.h
@@ -466,28 +466,29 @@ class IonICCallFrameLayout : public Comm
     }
     static size_t Size() {
         return sizeof(IonICCallFrameLayout);
     }
 };
 
 enum class ExitFrameType : uint8_t
 {
-    CallNative        = 0x0,
-    ConstructNative   = 0x1,
-    IonDOMGetter      = 0x2,
-    IonDOMSetter      = 0x3,
-    IonDOMMethod      = 0x4,
-    IonOOLNative      = 0x5,
-    IonOOLProxy       = 0x6,
-    WasmJitEntry      = 0x7,
-    InterpreterStub   = 0xFC,
-    VMFunction        = 0xFD,
-    LazyLink          = 0xFE,
-    Bare              = 0xFF,
+    CallNative          = 0x0,
+    ConstructNative     = 0x1,
+    IonDOMGetter        = 0x2,
+    IonDOMSetter        = 0x3,
+    IonDOMMethod        = 0x4,
+    IonOOLNative        = 0x5,
+    IonOOLProxy         = 0x6,
+    WasmGenericJitEntry = 0x7,
+    DirectWasmJitCall   = 0x8,
+    InterpreterStub     = 0xFC,
+    VMFunction          = 0xFD,
+    LazyLink            = 0xFE,
+    Bare                = 0xFF,
 };
 
 // GC related data used to keep alive data surrounding the Exit frame.
 class ExitFooterFrame
 {
     // Stores the ExitFrameType or, for ExitFrameType::VMFunction, the
     // VMFunction*.
     uintptr_t data_;
@@ -850,41 +851,50 @@ class LazyLinkExitFrameLayout : public C
 };
 
 class InterpreterStubExitFrameLayout : public CalledFromJitExitFrameLayout
 {
   public:
     static ExitFrameType Type() { return ExitFrameType::InterpreterStub; }
 };
 
-class WasmExitFrameLayout : CalledFromJitExitFrameLayout
+class WasmGenericJitEntryFrameLayout : CalledFromJitExitFrameLayout
 {
   public:
-    static ExitFrameType Type() { return ExitFrameType::WasmJitEntry; }
+    static ExitFrameType Type() { return ExitFrameType::WasmGenericJitEntry; }
 };
 
 template<>
 inline bool
 ExitFrameLayout::is<CalledFromJitExitFrameLayout>()
 {
     return is<InterpreterStubExitFrameLayout>() ||
            is<LazyLinkExitFrameLayout>() ||
-           is<WasmExitFrameLayout>();
+           is<WasmGenericJitEntryFrameLayout>();
 }
 
 template <>
 inline CalledFromJitExitFrameLayout*
 ExitFrameLayout::as<CalledFromJitExitFrameLayout>()
 {
     MOZ_ASSERT(is<CalledFromJitExitFrameLayout>());
     uint8_t* sp = reinterpret_cast<uint8_t*>(this);
     sp -= CalledFromJitExitFrameLayout::offsetOfExitFrame();
     return reinterpret_cast<CalledFromJitExitFrameLayout*>(sp);
 }
 
+class DirectWasmJitCallFrameLayout
+{
+  protected: // silence clang warning about unused private fields
+    ExitFooterFrame footer_;
+    ExitFrameLayout exit_;
+  public:
+    static ExitFrameType Type() { return ExitFrameType::DirectWasmJitCall; }
+};
+
 class ICStub;
 
 class JitStubFrameLayout : public CommonFrameLayout
 {
     // Info on the stack
     //
     // --------------------
     // |JitStubFrameLayout|
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -5338,10 +5338,63 @@ LIRGenerator::visitArgumentState(MArgume
 }
 
 void
 LIRGenerator::visitUnknownValue(MUnknownValue* ins)
 {
     MOZ_CRASH("Can not lower unknown value.");
 }
 
+void
+LIRGenerator::visitIonToWasmCall(MIonToWasmCall* ins)
+{
+    // The instruction needs a temp register:
+    // - that's not the FramePointer, since wasm is going to use it in the
+    // function.
+    // - that's not aliasing an input register.
+    LDefinition scratch = tempFixed(ABINonArgReg0);
+
+    // Also prevent register allocation from using wasm's FramePointer, in
+    // non-profiling mode.
+    LDefinition fp = gen->isProfilerInstrumentationEnabled()
+                   ? LDefinition::BogusTemp()
+                   : tempFixed(FramePointer);
+
+    // Note that since this is a LIR call instruction, regalloc will prevent
+    // the use*AtStart below from reusing any of the temporaries.
+
+    LInstruction* lir;
+    if (ins->type() == MIRType::Value)
+        lir = allocateVariadic<LIonToWasmCallV>(ins->numOperands(), scratch, fp);
+    else
+        lir = allocateVariadic<LIonToWasmCall>(ins->numOperands(), scratch, fp);
+    if (!lir) {
+        abort(AbortReason::Alloc, "Couldn't allocate for LIonToWasmCallBase");
+        return;
+    }
+
+    ABIArgGenerator abi;
+    for (unsigned i = 0; i < ins->numOperands(); i++) {
+        MDefinition* argDef = ins->getOperand(i);
+        ABIArg arg = abi.next(ToMIRType(argDef->type()));
+        switch (arg.kind()) {
+          case ABIArg::GPR:
+          case ABIArg::FPU:
+            lir->setOperand(i, useFixedAtStart(argDef, arg.reg()));
+            break;
+          case ABIArg::Stack:
+            lir->setOperand(i, useAtStart(argDef));
+            break;
+#ifdef JS_CODEGEN_REGISTER_PAIR
+          case ABIArg::GPR_PAIR:
+            MOZ_CRASH("no way to pass i64, and wasm uses hardfp for function calls");
+#endif
+          case ABIArg::Uninitialized:
+            MOZ_CRASH("Uninitialized ABIArg kind");
+        }
+    }
+
+    defineReturn(lir, ins);
+    assignSafepoint(lir, ins);
+}
+
 static_assert(!std::is_polymorphic<LIRGenerator>::value,
               "LIRGenerator should not have any virtual methods");
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -26,25 +26,27 @@
 #include "jit/MIRGraph.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/ArrayBufferObject.h"
 #include "vm/JSObject.h"
 #include "vm/ProxyObject.h"
 #include "vm/SelfHosting.h"
 #include "vm/SharedArrayObject.h"
 #include "vm/TypedArrayObject.h"
+#include "wasm/WasmInstance.h"
 
 #include "jit/shared/Lowering-shared-inl.h"
 #include "vm/JSScript-inl.h"
 #include "vm/NativeObject-inl.h"
 #include "vm/StringObject-inl.h"
 #include "vm/UnboxedObject-inl.h"
 
 using mozilla::ArrayLength;
 using mozilla::AssertedCast;
+using mozilla::Maybe;
 
 using JS::DoubleNaNValue;
 using JS::TrackedOutcome;
 
 namespace js {
 namespace jit {
 
 IonBuilder::InliningResult
@@ -52,17 +54,21 @@ IonBuilder::inlineNativeCall(CallInfo& c
 {
     MOZ_ASSERT(target->isNative());
 
     if (!optimizationInfo().inlineNative()) {
         trackOptimizationOutcome(TrackedOutcome::CantInlineDisabledIon);
         return InliningStatus_NotInlined;
     }
 
-    if (!target->hasJitInfo() || target->jitInfo()->type() != JSJitInfo::InlinableNative) {
+    bool isWasmCall = target->isWasmOptimized();
+    if (!isWasmCall &&
+        (!target->hasJitInfo() ||
+        target->jitInfo()->type() != JSJitInfo::InlinableNative))
+    {
         // Reaching here means we tried to inline a native for which there is no
         // Ion specialization.
         trackOptimizationOutcome(TrackedOutcome::CantInlineNativeNoSpecialization);
         return InliningStatus_NotInlined;
     }
 
     // Don't inline if we're constructing and new.target != callee. This can
     // happen with Reflect.construct or derived class constructors.
@@ -76,16 +82,19 @@ IonBuilder::inlineNativeCall(CallInfo& c
 
     if (shouldAbortOnPreliminaryGroups(callInfo.thisArg()))
         return InliningStatus_NotInlined;
     for (size_t i = 0; i < callInfo.argc(); i++) {
         if (shouldAbortOnPreliminaryGroups(callInfo.getArg(i)))
             return InliningStatus_NotInlined;
     }
 
+    if (isWasmCall)
+        return inlineWasmCall(callInfo, target);
+
     switch (InlinableNative inlNative = target->jitInfo()->inlinableNative) {
       // Array natives.
       case InlinableNative::Array:
         return inlineArray(callInfo);
       case InlinableNative::ArrayIsArray:
         return inlineArrayIsArray(callInfo);
       case InlinableNative::ArrayJoin:
         return inlineArrayJoin(callInfo);
@@ -3768,15 +3777,92 @@ IonBuilder::inlineConstructTypedObject(C
     MNewTypedObject* ins = MNewTypedObject::New(alloc(), constraints(), templateObject,
                                                 templateObject->group()->initialHeap(constraints()));
     current->add(ins);
     current->push(ins);
 
     return InliningStatus_Inlined;
 }
 
+IonBuilder::InliningResult
+IonBuilder::inlineWasmCall(CallInfo& callInfo, JSFunction* target)
+{
+    MOZ_ASSERT(target->isWasmOptimized());
+    MOZ_ASSERT(target->realm() == script()->realm());
+
+    // Don't inline wasm constructors.
+    if (callInfo.constructing()) {
+        trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm);
+        return InliningStatus_NotInlined;
+    }
+
+    wasm::Instance& inst = wasm::ExportedFunctionToInstance(target);
+    uint32_t funcIndex = inst.code().getFuncIndex(target);
+
+    auto bestTier = inst.code().bestTier();
+    const wasm::FuncExport& funcExport = inst.metadata(bestTier).lookupFuncExport(funcIndex);
+    const wasm::FuncType& sig = funcExport.funcType();
+
+    // Check that the function doesn't take or return non-compatible JS
+    // argument types before adding nodes to the MIR graph, otherwise they'd be
+    // dead code.
+    if (sig.hasI64ArgOrRet() || sig.temporarilyUnsupportedAnyRef())
+        return InliningStatus_NotInlined;
+
+    // If there are too many arguments, don't inline (we won't be able to store
+    // the arguments in the LIR node).
+    static constexpr size_t MaxNumInlinedArgs = 8;
+    static_assert(MaxNumInlinedArgs <= MaxNumLInstructionOperands,
+                  "inlined arguments can all be LIR operands");
+    if (sig.args().length() > MaxNumInlinedArgs)
+        return InliningStatus_NotInlined;
+
+    auto* call = MIonToWasmCall::New(alloc(), inst.object(), funcExport);
+    if (!call)
+        return abort(AbortReason::Alloc);
+
+    Maybe<MDefinition*> undefined;
+    for (size_t i = 0; i < sig.args().length(); i++) {
+        if (!alloc().ensureBallast())
+            return abort(AbortReason::Alloc);
+
+        // Add undefined if an argument is missing.
+        if (i >= callInfo.argc() && !undefined)
+            undefined.emplace(constant(UndefinedValue()));
+
+        MDefinition* arg = i >= callInfo.argc() ? *undefined : callInfo.getArg(i);
+
+        MInstruction* conversion = nullptr;
+        switch (sig.args()[i].code()) {
+          case wasm::ValType::I32:
+            conversion = MTruncateToInt32::New(alloc(), arg);
+            break;
+          case wasm::ValType::F32:
+            conversion = MToFloat32::New(alloc(), arg);
+            break;
+          case wasm::ValType::F64:
+            conversion = MToDouble::New(alloc(), arg);
+            break;
+          case wasm::ValType::I64:
+          case wasm::ValType::AnyRef:
+          case wasm::ValType::Ref:
+            MOZ_CRASH("impossible per above check");
+        }
+
+        current->add(conversion);
+        call->initArg(i, conversion);
+    }
+
+    current->push(call);
+    current->add(call);
+
+    callInfo.setImplicitlyUsedUnchecked();
+
+    return InliningStatus_Inlined;
+}
+
 #define ADD_NATIVE(native) const JSJitInfo JitInfo_##native { \
     { nullptr }, { uint16_t(InlinableNative::native) }, { 0 }, JSJitInfo::InlinableNative };
     INLINABLE_NATIVE_LIST(ADD_NATIVE)
 #undef ADD_NATIVE
 
 } // namespace jit
 } // namespace js
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -21,16 +21,17 @@
 #include "jit/AtomicOperations.h"
 #include "jit/BaselineInspector.h"
 #include "jit/IonBuilder.h"
 #include "jit/JitSpewer.h"
 #include "jit/MIRGraph.h"
 #include "jit/RangeAnalysis.h"
 #include "js/Conversions.h"
 #include "util/Text.h"
+#include "wasm/WasmCode.h"
 
 #include "builtin/Boolean-inl.h"
 
 #include "vm/JSAtom-inl.h"
 #include "vm/JSObject-inl.h"
 #include "vm/JSScript-inl.h"
 #include "vm/UnboxedObject-inl.h"
 
@@ -6300,8 +6301,38 @@ jit::PropertyWriteNeedsTypeBarrier(TempA
                 return true;
             excluded->watchStateChangeForUnboxedConvertedToNative(constraints);
         }
     }
 
     *pobj = AddGroupGuard(alloc, current, *pobj, excluded, /* bailOnEquality = */ true);
     return false;
 }
+
+MIonToWasmCall*
+MIonToWasmCall::New(TempAllocator& alloc, WasmInstanceObject* instanceObj,
+                    const wasm::FuncExport& funcExport)
+{
+    wasm::ExprType retType = funcExport.funcType().ret();
+
+    MIRType resultType = retType.code() == wasm::ExprType::Void
+                       ? MIRType::Value
+                       : ToMIRType(retType);
+
+    auto* ins = new(alloc) MIonToWasmCall(instanceObj, resultType, funcExport);
+    if (!ins->init(alloc, funcExport.funcType().args().length()))
+        return nullptr;
+    return ins;
+}
+
+bool
+MIonToWasmCall::appendRoots(MRootList& roots) const
+{
+    return roots.append(instanceObj_);
+}
+
+#ifdef DEBUG
+bool
+MIonToWasmCall::isConsistentFloat32Use(MUse* use) const
+{
+    return funcExport_.funcType().args()[use->index()].code() == wasm::ValType::F32;
+}
+#endif
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -31,16 +31,20 @@
 #include "vm/EnvironmentObject.h"
 #include "vm/RegExpObject.h"
 #include "vm/SharedMem.h"
 #include "vm/TypedArrayObject.h"
 #include "vm/UnboxedObject.h"
 
 namespace js {
 
+namespace wasm {
+class FuncExport;
+}
+
 class StringObject;
 
 namespace jit {
 
 // Forward declarations of MIR types.
 #define FORWARD_DECLARE(op) class M##op;
  MIR_OPCODE_LIST(FORWARD_DECLARE)
 #undef FORWARD_DECLARE
@@ -13961,16 +13965,57 @@ class MUnknownValue : public MNullaryIns
         setResultType(MIRType::Value);
     }
 
   public:
     INSTRUCTION_HEADER(UnknownValue)
     TRIVIAL_NEW_WRAPPERS
 };
 
+class MIonToWasmCall final :
+    public MVariadicInstruction,
+    public NoTypePolicy::Data
+{
+    CompilerGCPointer<WasmInstanceObject*> instanceObj_;
+    const wasm::FuncExport& funcExport_;
+
+    MIonToWasmCall(WasmInstanceObject* instanceObj, MIRType resultType,
+                   const wasm::FuncExport& funcExport)
+      : MVariadicInstruction(classOpcode),
+        instanceObj_(instanceObj),
+        funcExport_(funcExport)
+    {
+        setResultType(resultType);
+    }
+
+  public:
+    INSTRUCTION_HEADER(IonToWasmCall);
+
+    static MIonToWasmCall* New(TempAllocator& alloc, WasmInstanceObject* instanceObj,
+                               const wasm::FuncExport& funcExport);
+
+    void initArg(size_t i, MDefinition* arg) {
+        initOperand(i, arg);
+    }
+
+    WasmInstanceObject* instanceObject() const {
+        return instanceObj_;
+    }
+    const wasm::FuncExport& funcExport() const {
+        return funcExport_;
+    }
+    bool possiblyCalls() const override {
+        return true;
+    }
+    bool appendRoots(MRootList& roots) const override;
+#ifdef DEBUG
+    bool isConsistentFloat32Use(MUse* use) const override;
+#endif
+};
+
 #undef INSTRUCTION_HEADER
 
 void MUse::init(MDefinition* producer, MNode* consumer)
 {
     MOZ_ASSERT(!consumer_, "Initializing MUse that already has a consumer");
     MOZ_ASSERT(!producer_, "Initializing MUse that already has a producer");
     initUnchecked(producer, consumer);
 }
--- a/js/src/jit/MacroAssembler-inl.h
+++ b/js/src/jit/MacroAssembler-inl.h
@@ -213,16 +213,24 @@ MacroAssembler::callJit(JitCode* callee)
 uint32_t
 MacroAssembler::callJit(TrampolinePtr code)
 {
     AutoProfilerCallInstrumentation profiler(*this);
     call(code);
     return currentOffset();
 }
 
+uint32_t
+MacroAssembler::callJit(ImmPtr callee)
+{
+    AutoProfilerCallInstrumentation profiler(*this);
+    call(callee);
+    return currentOffset();
+}
+
 void
 MacroAssembler::makeFrameDescriptor(Register frameSizeReg, FrameType type, uint32_t headerSize)
 {
     // See JitFrames.h for a description of the frame descriptor format.
     // The saved-frame bit is zero for new frames. See js::SavedStacks.
 
     lshiftPtr(Imm32(FRAMESIZE_SHIFT), frameSizeReg);
 
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -592,16 +592,17 @@ class MacroAssembler : public MacroAssem
     //
     // These functions return the offset of the return address, in order to use
     // the return address to index the safepoints, which are used to list all
     // live registers.
     inline uint32_t callJitNoProfiler(Register callee);
     inline uint32_t callJit(Register callee);
     inline uint32_t callJit(JitCode* code);
     inline uint32_t callJit(TrampolinePtr code);
+    inline uint32_t callJit(ImmPtr callee);
 
     // The frame descriptor is the second field of all Jit frames, pushed before
     // calling the Jit function.  It is a composite value defined in JitFrames.h
     inline void makeFrameDescriptor(Register frameSizeReg, FrameType type, uint32_t headerSize);
 
     // Push the frame descriptor, based on the statically known framePushed.
     inline void pushStaticFrameDescriptor(FrameType type, uint32_t headerSize);
 
--- a/js/src/jit/RegisterAllocator.h
+++ b/js/src/jit/RegisterAllocator.h
@@ -249,16 +249,25 @@ class InstructionDataMap
     LNode*& operator[](uint32_t ins) {
         return insData_[ins];
     }
     LNode* const& operator[](uint32_t ins) const {
         return insData_[ins];
     }
 };
 
+inline void
+TakeJitRegisters(bool isProfiling, AllocatableRegisterSet* set)
+{
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM64)
+    if (isProfiling)
+        set->take(AnyRegister(FramePointer));
+#endif
+}
+
 // Common superclass for register allocators.
 class RegisterAllocator
 {
     void operator=(const RegisterAllocator&) = delete;
     RegisterAllocator(const RegisterAllocator&) = delete;
 
   protected:
     // Context
@@ -275,24 +284,20 @@ class RegisterAllocator
     Vector<CodePosition, 12, SystemAllocPolicy> exitPositions;
 
     RegisterAllocator(MIRGenerator* mir, LIRGenerator* lir, LIRGraph& graph)
       : mir(mir),
         lir(lir),
         graph(graph),
         allRegisters_(RegisterSet::All())
     {
-        if (mir->compilingWasm()) {
+        if (mir->compilingWasm())
             takeWasmRegisters(allRegisters_);
-        } else {
-#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM64)
-            if (mir->instrumentedProfiling())
-                allRegisters_.take(AnyRegister(FramePointer));
-#endif
-        }
+        else
+            TakeJitRegisters(mir->instrumentedProfiling(), &allRegisters_);
     }
 
     MOZ_MUST_USE bool init();
 
     TempAllocator& alloc() const {
         return mir->alloc();
     }
 
--- a/js/src/jit/RegisterSets.h
+++ b/js/src/jit/RegisterSets.h
@@ -14,34 +14,37 @@
 
 #include "jit/JitAllocPolicy.h"
 #include "jit/Registers.h"
 
 namespace js {
 namespace jit {
 
 struct AnyRegister {
-    typedef uint32_t Code;
+    typedef uint8_t Code;
 
-    static const uint32_t Total = Registers::Total + FloatRegisters::Total;
-    static const uint32_t Invalid = UINT_MAX;
+    static const uint8_t Total = Registers::Total + FloatRegisters::Total;
+    static const uint8_t Invalid = UINT8_MAX;
+
+    static_assert(size_t(Registers::Total) + FloatRegisters::Total <= UINT8_MAX,
+                  "Number of registers must fit in uint8_t");
 
   private:
     Code code_;
 
   public:
     AnyRegister() : code_(Invalid) {}
 
     explicit AnyRegister(Register gpr) {
         code_ = gpr.code();
     }
     explicit AnyRegister(FloatRegister fpu) {
         code_ = fpu.code() + Registers::Total;
     }
-    static AnyRegister FromCode(uint32_t i) {
+    static AnyRegister FromCode(uint8_t i) {
         MOZ_ASSERT(i < Total);
         AnyRegister r;
         r.code_ = i;
         return r;
     }
     bool isFloat() const {
         MOZ_ASSERT(isValid());
         return code_ >= Registers::Total;
@@ -67,26 +70,26 @@ struct AnyRegister {
     }
     Code code() const {
         MOZ_ASSERT(isValid());
         return code_;
     }
     bool volatile_() const {
         return isFloat() ? fpu().volatile_() : gpr().volatile_();
     }
-    AnyRegister aliased(uint32_t aliasIdx) const {
+    AnyRegister aliased(uint8_t aliasIdx) const {
         AnyRegister ret;
         if (isFloat())
             ret = AnyRegister(fpu().aliased(aliasIdx));
         else
             ret = AnyRegister(gpr().aliased(aliasIdx));
         MOZ_ASSERT_IF(aliasIdx == 0, ret == *this);
         return ret;
     }
-    uint32_t numAliased() const {
+    uint8_t numAliased() const {
         if (isFloat())
             return fpu().numAliased();
         return gpr().numAliased();
     }
     bool aliases(const AnyRegister& other) const {
         if (isFloat() && other.isFloat())
             return fpu().aliases(other.fpu());
         if (!isFloat() && !other.isFloat())
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -776,17 +776,17 @@ int32_t
 GetIndexFromString(JSString* str)
 {
     // We shouldn't GC here as this is called directly from IC code.
     AutoUnsafeCallWithABI unsafe;
 
     if (!str->isFlat())
         return -1;
 
-    uint32_t index;
+    uint32_t index = UINT32_MAX;
     if (!str->asFlat().isIndex(&index) || index > INT32_MAX)
         return -1;
 
     return int32_t(index);
 }
 
 JSObject*
 WrapObjectPure(JSContext* cx, JSObject* obj)
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -9830,12 +9830,51 @@ class LGetPrototypeOf : public LInstruct
         setOperand(0, target);
     }
 
     const LAllocation* target() {
         return getOperand(0);
     }
 };
 
+template<size_t NumDefs>
+class LIonToWasmCallBase : public LVariadicInstruction<NumDefs, 2>
+{
+    using Base = LVariadicInstruction<NumDefs, 2>;
+  public:
+    explicit LIonToWasmCallBase(LNode::Opcode classOpcode, uint32_t numOperands,
+                                const LDefinition& temp, const LDefinition& fp)
+      : Base(classOpcode, numOperands)
+    {
+        this->setIsCall();
+        this->setTemp(0, temp);
+        this->setTemp(1, fp);
+    }
+    MIonToWasmCall* mir() const {
+        return this->mir_->toIonToWasmCall();
+    }
+    const LDefinition* temp() {
+        return this->getTemp(0);
+    }
+};
+
+class LIonToWasmCall : public LIonToWasmCallBase<1>
+{
+  public:
+    LIR_HEADER(IonToWasmCall);
+    LIonToWasmCall(uint32_t numOperands, const LDefinition& temp, const LDefinition& fp)
+      : LIonToWasmCallBase<1>(classOpcode, numOperands, temp, fp)
+    {}
+};
+
+class LIonToWasmCallV : public LIonToWasmCallBase<BOX_PIECES>
+{
+  public:
+    LIR_HEADER(IonToWasmCallV);
+    LIonToWasmCallV(uint32_t numOperands, const LDefinition& temp, const LDefinition& fp)
+      : LIonToWasmCallBase<BOX_PIECES>(classOpcode, numOperands, temp, fp)
+    {}
+};
+
 } // namespace jit
 } // namespace js
 
 #endif /* jit_shared_LIR_shared_h */
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -31,16 +31,17 @@
 #include "jsapi.h"
 #include "jsnum.h"
 #include "jstypes.h"
 #include "jsutil.h"
 
 #include "builtin/String.h"
 #include "js/Conversions.h"
 #include "js/Date.h"
+#include "js/LocaleSensitive.h"
 #include "js/Wrapper.h"
 #include "util/StringBuffer.h"
 #include "util/Text.h"
 #include "vm/DateTime.h"
 #include "vm/GlobalObject.h"
 #include "vm/Interpreter.h"
 #include "vm/JSContext.h"
 #include "vm/JSObject.h"
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -609,22 +609,23 @@ JitFrameIter::settle()
         // [--------------------]
         // [JIT FRAME           ]
         // [WASM JIT ENTRY FRAME] <-- we're here
         //
         // The wasm iterator has saved the previous jit frame pointer for us.
 
         MOZ_ASSERT(wasmFrame.done());
         uint8_t* prevFP = wasmFrame.unwoundIonCallerFP();
+        jit::FrameType prevFrameType = wasmFrame.unwoundIonFrameType();
 
         if (mustUnwindActivation_)
             act_->setJSExitFP(prevFP);
 
         iter_.destroy();
-        iter_.construct<jit::JSJitFrameIter>(act_, prevFP);
+        iter_.construct<jit::JSJitFrameIter>(act_, prevFrameType, prevFP);
         MOZ_ASSERT(!asJSJit().done());
         return;
     }
 }
 
 void
 JitFrameIter::operator++()
 {
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1617,23 +1617,20 @@ class ActivationIterator
 
 namespace jit {
 
 class BailoutFrameInfo;
 
 // A JitActivation is used for frames running in Baseline or Ion.
 class JitActivation : public Activation
 {
-  public:
-    static const uintptr_t ExitFpWasmBit = 0x1;
-
-  private:
     // If Baseline, Ion or Wasm code is on the stack, and has called into C++,
     // this will be aligned to an ExitFrame. The last bit indicates if it's a
-    // wasm frame (bit set to ExitFpWasmBit) or not (bit set to !ExitFpWasmBit).
+    // wasm frame (bit set to wasm::ExitOrJitEntryFPTag) or not
+    // (bit set to ~wasm::ExitOrJitEntryFPTag).
     uint8_t* packedExitFP_;
 
     // When hasWasmExitFP(), encodedWasmExitReason_ holds ExitReason.
     uint32_t encodedWasmExitReason_;
 
     JitActivation* prevJitActivation_;
 
     // Rematerialized Ion frames which has info copied out of snapshots. Maps
@@ -1699,24 +1696,24 @@ class JitActivation : public Activation
     static size_t offsetOfPrevJitActivation() {
         return offsetof(JitActivation, prevJitActivation_);
     }
 
     bool hasExitFP() const {
         return !!packedExitFP_;
     }
     uint8_t* jsOrWasmExitFP() const {
-        return (uint8_t*)(uintptr_t(packedExitFP_) & ~ExitFpWasmBit);
+        return (uint8_t*)(uintptr_t(packedExitFP_) & ~wasm::ExitOrJitEntryFPTag);
     }
     static size_t offsetOfPackedExitFP() {
         return offsetof(JitActivation, packedExitFP_);
     }
 
     bool hasJSExitFP() const {
-        return !(uintptr_t(packedExitFP_) & ExitFpWasmBit);
+        return !(uintptr_t(packedExitFP_) & wasm::ExitOrJitEntryFPTag);
     }
     uint8_t* jsExitFP() const {
         MOZ_ASSERT(hasJSExitFP());
         return packedExitFP_;
     }
     void setJSExitFP(uint8_t* fp) {
         packedExitFP_ = fp;
     }
@@ -1797,26 +1794,26 @@ class JitActivation : public Activation
         return lastProfilingCallSite_;
     }
     void setLastProfilingCallSite(void* ptr) {
         lastProfilingCallSite_ = ptr;
     }
 
     // WebAssembly specific attributes.
     bool hasWasmExitFP() const {
-        return uintptr_t(packedExitFP_) & ExitFpWasmBit;
+        return uintptr_t(packedExitFP_) & wasm::ExitOrJitEntryFPTag;
     }
     wasm::Frame* wasmExitFP() const {
         MOZ_ASSERT(hasWasmExitFP());
-        return (wasm::Frame*)(uintptr_t(packedExitFP_) & ~ExitFpWasmBit);
+        return (wasm::Frame*)(uintptr_t(packedExitFP_) & ~wasm::ExitOrJitEntryFPTag);
     }
     void setWasmExitFP(const wasm::Frame* fp) {
         if (fp) {
-            MOZ_ASSERT(!(uintptr_t(fp) & ExitFpWasmBit));
-            packedExitFP_ = (uint8_t*)(uintptr_t(fp) | ExitFpWasmBit);
+            MOZ_ASSERT(!(uintptr_t(fp) & wasm::ExitOrJitEntryFPTag));
+            packedExitFP_ = (uint8_t*)(uintptr_t(fp) | wasm::ExitOrJitEntryFPTag);
             MOZ_ASSERT(hasWasmExitFP());
         } else {
             packedExitFP_ = nullptr;
         }
     }
     wasm::ExitReason wasmExitReason() const {
         MOZ_ASSERT(hasWasmExitFP());
         return wasm::ExitReason::Decode(encodedWasmExitReason_);
--- a/js/src/wasm/WasmFrameIter.cpp
+++ b/js/src/wasm/WasmFrameIter.cpp
@@ -35,16 +35,17 @@ using mozilla::Maybe;
 
 WasmFrameIter::WasmFrameIter(JitActivation* activation, wasm::Frame* fp)
   : activation_(activation),
     code_(nullptr),
     codeRange_(nullptr),
     lineOrBytecode_(0),
     fp_(fp ? fp : activation->wasmExitFP()),
     unwoundIonCallerFP_(nullptr),
+    unwoundIonFrameType_(jit::FrameType(-1)),
     unwind_(Unwind::False),
     unwoundAddressOfReturnAddress_(nullptr)
 {
     MOZ_ASSERT(fp_);
 
     // When the stack is captured during a trap (viz., to create the .stack
     // for an Error object), use the pc/bytecode information captured by the
     // signal handler in the runtime.
@@ -107,17 +108,49 @@ WasmFrameIter::operator++()
     popFrame();
 }
 
 void
 WasmFrameIter::popFrame()
 {
     Frame* prevFP = fp_;
     fp_ = prevFP->callerFP;
-    MOZ_ASSERT(!(uintptr_t(fp_) & JitActivation::ExitFpWasmBit));
+
+    if (uintptr_t(fp_) & ExitOrJitEntryFPTag) {
+        // We just unwound a frame pointer which has the low bit set,
+        // indicating this is a direct call from the jit into the wasm
+        // function's body. The call stack resembles this at this point:
+        //
+        // |---------------------|
+        // |      JIT FRAME      |
+        // | JIT FAKE EXIT FRAME | <-- tagged fp_
+        // |      WASM FRAME     | <-- prevFP (already unwound)
+        // |---------------------|
+        //
+        // fp_ points to the fake exit frame set up by the jit caller, and the
+        // return-address-to-fp is in JIT code, thus doesn't belong to any wasm
+        // instance's code (in particular, there's no associated CodeRange).
+        // Mark the frame as such and untag FP.
+        MOZ_ASSERT(!LookupCode(prevFP->returnAddress));
+
+        unwoundIonCallerFP_ = (uint8_t*)(uintptr_t(fp_) & ~uintptr_t(ExitOrJitEntryFPTag));
+        unwoundIonFrameType_ = JitFrame_Exit;
+
+        fp_ = nullptr;
+        code_ = nullptr;
+        codeRange_ = nullptr;
+
+        if (unwind_ == Unwind::True) {
+            activation_->setJSExitFP(unwoundIonCallerFP_);
+            unwoundAddressOfReturnAddress_ = &prevFP->returnAddress;
+        }
+
+        MOZ_ASSERT(done());
+        return;
+    }
 
     if (!fp_) {
         code_ = nullptr;
         codeRange_ = nullptr;
 
         if (unwind_ == Unwind::True) {
             // We're exiting via the interpreter entry; we can safely reset
             // exitFP.
@@ -130,17 +163,30 @@ WasmFrameIter::popFrame()
     }
 
     void* returnAddress = prevFP->returnAddress;
 
     code_ = LookupCode(returnAddress, &codeRange_);
     MOZ_ASSERT(codeRange_);
 
     if (codeRange_->isJitEntry()) {
+        // This wasm function has been called through the generic JIT entry by
+        // a JIT caller, so the call stack resembles this:
+        //
+        // |---------------------|
+        // |      JIT FRAME      |
+        // |  JSJIT TO WASM EXIT | <-- fp_
+        // |    WASM JIT ENTRY   | <-- prevFP (already unwound)
+        // |      WASM FRAME     | (already unwound)
+        // |---------------------|
+        //
+        // The next value of FP is just a regular jit frame used marked to
+        // know that we should transition to a JSJit frame iterator.
         unwoundIonCallerFP_ = (uint8_t*) fp_;
+        unwoundIonFrameType_ = JitFrame_JSJitToWasm;
 
         fp_ = nullptr;
         code_ = nullptr;
         codeRange_ = nullptr;
 
         if (unwind_ == Unwind::True) {
             activation_->setJSExitFP(unwoundIonCallerFP_);
             unwoundAddressOfReturnAddress_ = &prevFP->returnAddress;
@@ -270,16 +316,24 @@ WasmFrameIter::debugEnabled() const
 
 DebugFrame*
 WasmFrameIter::debugFrame() const
 {
     MOZ_ASSERT(!done());
     return DebugFrame::from(fp_);
 }
 
+jit::FrameType
+WasmFrameIter::unwoundIonFrameType() const
+{
+    MOZ_ASSERT(unwoundIonCallerFP_);
+    MOZ_ASSERT(unwoundIonFrameType_ != jit::FrameType(-1));
+    return unwoundIonFrameType_;
+}
+
 /*****************************************************************************/
 // Prologue/epilogue code generation
 
 // These constants reflect statically-determined offsets in the
 // prologue/epilogue. The offsets are dynamically asserted during code
 // generation.
 #if defined(JS_CODEGEN_X64)
 static const unsigned PushedRetAddr = 0;
@@ -353,19 +407,19 @@ wasm::SetExitFP(MacroAssembler& masm, Ex
 {
     MOZ_ASSERT(!reason.isNone());
 
     LoadActivation(masm, scratch);
 
     masm.store32(Imm32(reason.encode()),
                  Address(scratch, JitActivation::offsetOfEncodedWasmExitReason()));
 
-    masm.orPtr(Imm32(JitActivation::ExitFpWasmBit), FramePointer);
+    masm.orPtr(Imm32(ExitOrJitEntryFPTag), FramePointer);
     masm.storePtr(FramePointer, Address(scratch, JitActivation::offsetOfPackedExitFP()));
-    masm.andPtr(Imm32(int32_t(~JitActivation::ExitFpWasmBit)), FramePointer);
+    masm.andPtr(Imm32(int32_t(~ExitOrJitEntryFPTag)), FramePointer);
 }
 
 void
 wasm::ClearExitFP(MacroAssembler& masm, Register scratch)
 {
     LoadActivation(masm, scratch);
     masm.storePtr(ImmWord(0x0), Address(scratch, JitActivation::offsetOfPackedExitFP()));
     masm.store32(Imm32(0x0), Address(scratch, JitActivation::offsetOfEncodedWasmExitReason()));
@@ -624,17 +678,17 @@ AssertNoWasmExitFPInJitExit(MacroAssembl
     // JIT exit stub from/to normal wasm code, packedExitFP is not tagged wasm.
 #ifdef DEBUG
     Register scratch = ABINonArgReturnReg0;
     LoadActivation(masm, scratch);
 
     Label ok;
     masm.branchTestPtr(Assembler::Zero,
                        Address(scratch, JitActivation::offsetOfPackedExitFP()),
-                       Imm32(uintptr_t(JitActivation::ExitFpWasmBit)),
+                       Imm32(uintptr_t(ExitOrJitEntryFPTag)),
                        &ok);
     masm.breakpoint();
     masm.bind(&ok);
 #endif
 }
 
 void
 wasm::GenerateJitExitPrologue(MacroAssembler& masm, unsigned framePushed, CallableOffsets* offsets)
@@ -730,23 +784,39 @@ ProfilingFrameIterator::ProfilingFrameIt
     unwoundIonCallerFP_(nullptr),
     exitReason_(ExitReason::Fixed::ImportJit)
 {
     MOZ_ASSERT(fp);
     initFromExitFP(fp);
 }
 
 static inline void
+AssertDirectJitCall(const void* fp)
+{
+    // Called via an inlined fast JIT to wasm call: in this case, FP is
+    // pointing in the middle of the exit frame, right before the exit
+    // footer; ensure the exit frame type is the expected one.
+#ifdef DEBUG
+    auto* jitCaller = (ExitFrameLayout*)(uintptr_t(fp) & ~ExitOrJitEntryFPTag);
+    MOZ_ASSERT(jitCaller->footer()->type() == jit::ExitFrameType::DirectWasmJitCall);
+#endif
+}
+
+static inline void
 AssertMatchesCallSite(void* callerPC, Frame* callerFP)
 {
 #ifdef DEBUG
     const CodeRange* callerCodeRange;
     const Code* code = LookupCode(callerPC, &callerCodeRange);
 
-    MOZ_ASSERT(code);
+    if (!code) {
+        AssertDirectJitCall(callerFP);
+        return;
+    }
+
     MOZ_ASSERT(callerCodeRange);
 
     if (callerCodeRange->isInterpEntry()) {
         MOZ_ASSERT(callerFP == nullptr);
         return;
     }
 
     if (callerCodeRange->isJitEntry()) {
@@ -763,17 +833,28 @@ void
 ProfilingFrameIterator::initFromExitFP(const Frame* fp)
 {
     MOZ_ASSERT(fp);
     stackAddress_ = (void*)fp;
 
     void* pc = fp->returnAddress;
 
     code_ = LookupCode(pc, &codeRange_);
-    MOZ_ASSERT(code_);
+
+    if (!code_) {
+        // This is a direct call from the JIT, the caller FP is pointing to a
+        // tagged JIT caller's frame.
+        MOZ_ASSERT(uintptr_t(fp->callerFP) & ExitOrJitEntryFPTag);
+        AssertDirectJitCall(fp->callerFP);
+
+        unwoundIonCallerFP_ = (uint8_t*)(uintptr_t(fp->callerFP) & ~ExitOrJitEntryFPTag);
+        MOZ_ASSERT(done());
+        return;
+    }
+
     MOZ_ASSERT(codeRange_);
 
     // Since we don't have the pc for fp, start unwinding at the caller of fp.
     // This means that the innermost frame is skipped. This is fine because:
     //  - for import exit calls, the innermost frame is a thunk, so the first
     //    frame that shows up is the function calling the import;
     //  - for Math and other builtin calls, we note the absence of an exit
     //    reason and inject a fake "builtin" frame; and
@@ -803,27 +884,39 @@ ProfilingFrameIterator::initFromExitFP(c
       case CodeRange::Throw:
       case CodeRange::FarJumpIsland:
         MOZ_CRASH("Unexpected CodeRange kind");
     }
 
     MOZ_ASSERT(!done());
 }
 
+static void
+AssertCallerFP(DebugOnly<bool> fpWasTagged, Frame* const fp, void** const sp)
+{
+    MOZ_ASSERT_IF(!fpWasTagged.value,
+                  fp == reinterpret_cast<Frame*>(sp)->callerFP);
+    MOZ_ASSERT_IF(fpWasTagged.value,
+                  (Frame*)(uintptr_t(fp) | 0x1) == reinterpret_cast<Frame*>(sp)->callerFP);
+}
+
 bool
 js::wasm::StartUnwinding(const RegisterState& registers, UnwindState* unwindState,
                          bool* unwoundCaller)
 {
     // Shorthands.
     uint8_t* const pc = (uint8_t*) registers.pc;
     void** const sp = (void**) registers.sp;
 
-    // The frame pointer might be in the process of tagging/untagging; make
-    // sure it's untagged.
-    Frame* const fp = (Frame*) (intptr_t(registers.fp) & ~JitActivation::ExitFpWasmBit);
+    // The frame pointer might be:
+    // - in the process of tagging/untagging when calling into the JITs;
+    // make sure it's untagged.
+    // - tagged by an direct JIT call.
+    DebugOnly<bool> fpWasTagged = uintptr_t(registers.fp) & ExitOrJitEntryFPTag;
+    Frame* const fp = (Frame*) (intptr_t(registers.fp) & ~ExitOrJitEntryFPTag);
 
     // Get the CodeRange describing pc and the base address to which the
     // CodeRange is relative. If the pc is not in a wasm module or a builtin
     // thunk, then execution must be entering from or leaving to the C++ caller
     // that pushed the JitActivation.
     const CodeRange* codeRange;
     uint8_t* codeBase;
     const Code* code = nullptr;
@@ -922,29 +1015,29 @@ js::wasm::StartUnwinding(const RegisterS
         } else if (offsetFromEntry >= PushedTLS && offsetFromEntry < PushedFP) {
             // The return address and caller's TLS have been pushed on the
             // stack; fp is still the caller's fp.
             fixedPC = sp[1];
             fixedFP = fp;
             AssertMatchesCallSite(fixedPC, fixedFP);
         } else if (offsetFromEntry == PushedFP) {
             // The full Frame has been pushed; fp is still the caller's fp.
-            MOZ_ASSERT(fp == reinterpret_cast<Frame*>(sp)->callerFP);
+            AssertCallerFP(fpWasTagged, fp, sp);
             fixedPC = reinterpret_cast<Frame*>(sp)->returnAddress;
             fixedFP = fp;
             AssertMatchesCallSite(fixedPC, fixedFP);
 #if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
         } else if (offsetInCode >= codeRange->ret() - PoppedFP &&
                    offsetInCode <= codeRange->ret())
         {
             (void)PoppedTLSReg;
             // The fixedFP field of the Frame has been loaded into fp.
             // The ra and TLS might also be loaded, but the Frame structure is
             // still on stack, so we can acess the ra form there.
-            MOZ_ASSERT(fp == reinterpret_cast<Frame*>(sp)->callerFP);
+            AssertCallerFP(fpWasTagged, fp, sp);
             fixedPC = reinterpret_cast<Frame*>(sp)->returnAddress;
             fixedFP = fp;
             AssertMatchesCallSite(fixedPC, fixedFP);
 #elif defined(JS_CODEGEN_ARM64)
         // The stack pointer does not move until all values have
         // been restored so several cases can be coalesced here.
         } else if (offsetInCode >= codeRange->ret() - PoppedFP &&
                    offsetInCode <= codeRange->ret())
@@ -1012,17 +1105,17 @@ js::wasm::StartUnwinding(const RegisterS
             // this frame; drop it.
             return false;
         }
 #endif
         fixedFP = offsetFromEntry < SetJitEntryFP ? (Frame*) sp : fp;
         fixedPC = nullptr;
 
         // On the error return path, FP might be set to FailFP. Ignore these transient frames.
-        if (intptr_t(fixedFP) == (FailFP & ~JitActivation::ExitFpWasmBit))
+        if (intptr_t(fixedFP) == (FailFP & ~ExitOrJitEntryFPTag))
             return false;
         break;
       case CodeRange::Throw:
         // The throw stub executes a small number of instructions before popping
         // the entire activation. To simplify testing, we simply pretend throw
         // stubs have already popped the entire stack.
         return false;
     }
@@ -1058,48 +1151,59 @@ ProfilingFrameIterator::ProfilingFrameIt
     if (!StartUnwinding(state, &unwindState, &unwoundCaller)) {
         MOZ_ASSERT(done());
         return;
     }
 
     if (unwoundCaller) {
         callerFP_ = unwindState.fp;
         callerPC_ = unwindState.pc;
+        // If the original FP value is tagged, then we're being called through
+        // a direct JIT call. We can't observe transient tagged values of FP
+        // (during wasm::SetExitFP) here because StartUnwinding would not have
+        // unwound for us in this case.
+        if ((uintptr_t(state.fp) & ExitOrJitEntryFPTag))
+            unwoundIonCallerFP_ = (uint8_t*) callerFP_;
     } else {
         callerFP_ = unwindState.fp->callerFP;
         callerPC_ = unwindState.fp->returnAddress;
+        // See comment above.
+        if ((uintptr_t(callerFP_) & ExitOrJitEntryFPTag))
+            unwoundIonCallerFP_ = (uint8_t*)(uintptr_t(callerFP_) & ~ExitOrJitEntryFPTag);
     }
 
-    if (unwindState.codeRange->isJitEntry())
+    if (unwindState.codeRange->isJitEntry()) {
+        MOZ_ASSERT(!unwoundIonCallerFP_);
         unwoundIonCallerFP_ = (uint8_t*) callerFP_;
+    }
 
     if (unwindState.codeRange->isInterpEntry()) {
         unwindState.codeRange = nullptr;
         exitReason_ = ExitReason(ExitReason::Fixed::FakeInterpEntry);
     }
 
     code_ = unwindState.code;
     codeRange_ = unwindState.codeRange;
     stackAddress_ = state.sp;
     MOZ_ASSERT(!done());
 }
 
 void
 ProfilingFrameIterator::operator++()
 {
     if (!exitReason_.isNone()) {
-        DebugOnly<ExitReason> prevExitReason = exitReason_;
+        DebugOnly<bool> wasInterpEntry = exitReason_.isInterpEntry();
         exitReason_ = ExitReason::None();
-        MOZ_ASSERT(!codeRange_ == prevExitReason.value.isInterpEntry());
-        MOZ_ASSERT(done() == prevExitReason.value.isInterpEntry());
+        MOZ_ASSERT((!codeRange_) == wasInterpEntry);
+        MOZ_ASSERT(done() == wasInterpEntry);
         return;
     }
 
     if (unwoundIonCallerFP_) {
-        MOZ_ASSERT(codeRange_->isJitEntry());
+        MOZ_ASSERT(codeRange_->isFunction() || codeRange_->isJitEntry());
         callerPC_ = nullptr;
         callerFP_ = nullptr;
         codeRange_ = nullptr;
         MOZ_ASSERT(done());
         return;
     }
 
     if (!callerPC_) {
@@ -1115,16 +1219,27 @@ ProfilingFrameIterator::operator++()
         exitReason_ = ExitReason(ExitReason::Fixed::FakeInterpEntry);
         codeRange_ = nullptr;
         callerPC_ = nullptr;
         MOZ_ASSERT(!done());
         return;
     }
 
     code_ = LookupCode(callerPC_, &codeRange_);
+
+    if (!code_ && uintptr_t(callerFP_) & ExitOrJitEntryFPTag) {
+        // The parent frame is an inlined wasm call, the tagged FP points to
+        // the fake exit frame.
+        MOZ_ASSERT(!codeRange_);
+        AssertDirectJitCall(callerFP_);
+        unwoundIonCallerFP_ = (uint8_t*) (uintptr_t(callerFP_) & ~uintptr_t(ExitOrJitEntryFPTag));
+        MOZ_ASSERT(done());
+        return;
+    }
+
     MOZ_ASSERT(codeRange_);
 
     if (codeRange_->isJitEntry()) {
         unwoundIonCallerFP_ = (uint8_t*) callerFP_;
         MOZ_ASSERT(!done());
         return;
     }
 
--- a/js/src/wasm/WasmFrameIter.h
+++ b/js/src/wasm/WasmFrameIter.h
@@ -14,16 +14,17 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #ifndef wasm_frame_iter_h
 #define wasm_frame_iter_h
 
+#include "jit/JSJitFrameIter.h"
 #include "js/ProfilingFrameIterator.h"
 #include "js/TypeDecls.h"
 #include "wasm/WasmTypes.h"
 
 namespace js {
 
 namespace jit {
 class MacroAssembler;
@@ -59,16 +60,17 @@ class WasmFrameIter
 
   private:
     jit::JitActivation* activation_;
     const Code* code_;
     const CodeRange* codeRange_;
     unsigned lineOrBytecode_;
     Frame* fp_;
     uint8_t* unwoundIonCallerFP_;
+    jit::FrameType unwoundIonFrameType_;
     Unwind unwind_;
     void** unwoundAddressOfReturnAddress_;
 
     void popFrame();
 
   public:
     // See comment above this class definition.
     explicit WasmFrameIter(jit::JitActivation* activation, Frame* fp = nullptr);
@@ -83,16 +85,17 @@ class WasmFrameIter
     unsigned lineOrBytecode() const;
     uint32_t funcIndex() const;
     unsigned computeLine(uint32_t* column) const;
     const CodeRange* codeRange() const { return codeRange_; }
     Instance* instance() const;
     void** unwoundAddressOfReturnAddress() const;
     bool debugEnabled() const;
     DebugFrame* debugFrame() const;
+    jit::FrameType unwoundIonFrameType() const;
     uint8_t* unwoundIonCallerFP() const { return unwoundIonCallerFP_; }
 };
 
 enum class SymbolicAddress;
 
 // An ExitReason describes the possible reasons for leaving compiled wasm
 // code or the state of not having left compiled wasm code
 // (ExitReason::None). It is either a known reason, or a enumeration to a native
@@ -259,12 +262,25 @@ typedef JS::ProfilingFrameIterator::Regi
 //
 // Returns true if it was possible to get to a clear state, or false if the
 // frame should be ignored.
 
 bool
 StartUnwinding(const RegisterState& registers, UnwindState* unwindState,
                bool* unwoundCaller);
 
+// Bit set as the lowest bit of a frame pointer, used in two different mutually
+// exclusive situations:
+// - either it's a low bit tag in a FramePointer value read from the
+// Frame::callerFP of an inner wasm frame. This indicates the previous call
+// frame has been set up by a JIT caller that directly called into a wasm
+// function's body. This is only stored in Frame::callerFP for a wasm frame
+// called from JIT code, and thus it can not appear in a JitActivation's
+// exitFP.
+// - or it's the low big tag set when exiting wasm code in JitActivation's
+// exitFP.
+
+constexpr uintptr_t ExitOrJitEntryFPTag = 0x1;
+
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_frame_iter_h
--- a/js/src/wasm/WasmStubs.cpp
+++ b/js/src/wasm/WasmStubs.cpp
@@ -15,16 +15,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "wasm/WasmStubs.h"
 
 #include "mozilla/ArrayUtils.h"
 
+#include "jit/RegisterAllocator.h"
 #include "wasm/WasmCode.h"
 #include "wasm/WasmGenerator.h"
 #include "wasm/WasmInstance.h"
 
 #include "jit/MacroAssembler-inl.h"
 
 using namespace js;
 using namespace js::jit;
@@ -496,17 +497,18 @@ GenerateJitEntryThrow(MacroAssembler& ma
     MOZ_ASSERT(masm.framePushed() == frameSize);
 
     GenerateJitEntryLoadTls(masm, frameSize);
 
     masm.freeStack(frameSize);
     MoveSPForJitABI(masm);
 
     masm.loadPtr(Address(WasmTlsReg, offsetof(TlsData, cx)), ScratchIonEntry);
-    masm.enterFakeExitFrameForWasm(ScratchIonEntry, ScratchIonEntry, ExitFrameType::WasmJitEntry);
+    masm.enterFakeExitFrameForWasm(ScratchIonEntry, ScratchIonEntry,
+                                   ExitFrameType::WasmGenericJitEntry);
 
     masm.loadPtr(Address(WasmTlsReg, offsetof(TlsData, instance)), ScratchIonEntry);
     masm.loadPtr(Address(ScratchIonEntry, Instance::offsetOfJSJitExceptionHandler()),
                  ScratchIonEntry);
     masm.jump(ScratchIonEntry);
 }
 
 // Generate a stub that enters wasm from a jit code caller via the jit ABI.
@@ -839,16 +841,174 @@ GenerateJitEntry(MacroAssembler& masm, s
     // Prepare to throw: reload WasmTlsReg from the frame.
     masm.bind(&exception);
     masm.setFramePushed(frameSize);
     GenerateJitEntryThrow(masm, frameSize);
 
     return FinishOffsets(masm, offsets);
 }
 
+void
+wasm::GenerateDirectCallFromJit(MacroAssembler& masm,
+                                const FuncExport& fe,
+                                const Instance& inst,
+                                const JitCallStackArgVector& stackArgs,
+                                bool profilingEnabled,
+                                bool wasmGcEnabled,
+                                Register scratch,
+                                uint32_t* callOffset)
+{
+    MOZ_ASSERT(!IsCompilingWasm());
+
+    size_t framePushedAtStart = masm.framePushed();
+
+    if (profilingEnabled) {
+        // FramePointer isn't volatile, manually preserve it because it will be
+        // clobbered below.
+        masm.Push(FramePointer);
+    } else {
+#ifdef DEBUG
+        // Ensure that the FramePointer is actually Ion-volatile. This might
+        // assert when bug 1426134 lands.
+        AllocatableRegisterSet set(RegisterSet::All());
+        TakeJitRegisters(/* profiling */ false, &set);
+        MOZ_ASSERT(set.has(FramePointer),
+                   "replace the whole if branch by the then body when this fails");
+#endif
+    }
+
+    // Push a special frame descriptor that indicates the frame size so we can
+    // directly iterate from the current JIT frame without an extra call.
+    *callOffset = masm.buildFakeExitFrame(scratch);
+    masm.loadJSContext(scratch);
+
+    masm.moveStackPtrTo(FramePointer);
+    masm.enterFakeExitFrame(scratch, scratch, ExitFrameType::DirectWasmJitCall);
+    masm.orPtr(Imm32(ExitOrJitEntryFPTag), FramePointer);
+
+    // Move stack arguments to their final locations.
+    unsigned bytesNeeded = StackArgBytes(fe.funcType().args());
+    bytesNeeded = StackDecrementForCall(WasmStackAlignment, masm.framePushed(), bytesNeeded);
+    if (bytesNeeded)
+        masm.reserveStack(bytesNeeded);
+
+    for (ABIArgValTypeIter iter(fe.funcType().args()); !iter.done(); iter++) {
+        MOZ_ASSERT_IF(iter->kind() == ABIArg::GPR, iter->gpr() != scratch);
+        MOZ_ASSERT_IF(iter->kind() == ABIArg::GPR, iter->gpr() != FramePointer);
+        if (iter->kind() != ABIArg::Stack)
+            continue;
+
+        Address dst(masm.getStackPointer(), iter->offsetFromArgBase());
+
+        const JitCallStackArg& stackArg = stackArgs[iter.index()];
+        switch (stackArg.tag()) {
+          case JitCallStackArg::Tag::Imm32:
+            masm.storePtr(ImmWord(stackArg.imm32()), dst);
+            break;
+          case JitCallStackArg::Tag::GPR:
+            MOZ_ASSERT(stackArg.gpr() != scratch);
+            MOZ_ASSERT(stackArg.gpr() != FramePointer);
+            masm.storePtr(stackArg.gpr(), dst);
+            break;
+          case JitCallStackArg::Tag::FPU:
+            switch (iter.mirType()) {
+              case MIRType::Double:
+                masm.storeDouble(stackArg.fpu(), dst);
+                break;
+              case MIRType::Float32:
+                masm.storeFloat32(stackArg.fpu(), dst);
+                break;
+              default:
+                MOZ_CRASH("unexpected MIR type for a float register in wasm fast call");
+            }
+            break;
+          case JitCallStackArg::Tag::Address: {
+            // The address offsets were valid *before* we pushed our frame.
+            Address src = stackArg.addr();
+            src.offset += masm.framePushed() - framePushedAtStart;
+            switch (iter.mirType()) {
+              case MIRType::Double:
+                masm.loadDouble(src, ScratchDoubleReg);
+                masm.storeDouble(ScratchDoubleReg, dst);
+                break;
+              case MIRType::Float32:
+                masm.loadFloat32(src, ScratchFloat32Reg);
+                masm.storeFloat32(ScratchFloat32Reg, dst);
+                break;
+              case MIRType::Int32:
+                masm.loadPtr(src, scratch);
+                masm.storePtr(scratch, dst);
+                break;
+              default:
+                MOZ_CRASH("unexpected MIR type for a stack slot in wasm fast call");
+            }
+            break;
+          }
+          case JitCallStackArg::Tag::Undefined: {
+            MOZ_CRASH("can't happen because of arg.kind() check");
+          }
+        }
+    }
+
+    // Load tls; from now on, WasmTlsReg is live.
+    masm.movePtr(ImmPtr(inst.tlsData()), WasmTlsReg);
+    masm.loadWasmPinnedRegsFromTls();
+
+#ifdef ENABLE_WASM_GC
+    if (wasmGcEnabled)
+        SuppressGC(masm, 1, ABINonArgReg0);
+#endif
+
+    // Actual call.
+    const wasm::CodeTier& codeTier = inst.code().codeTier(inst.code().bestTier());
+    uint32_t offset = codeTier.metadata().codeRanges[fe.funcCodeRangeIndex()].funcNormalEntry();
+    void* callee = codeTier.segment().base() + offset;
+
+    masm.assertStackAlignment(WasmStackAlignment);
+    masm.callJit(ImmPtr(callee));
+    masm.assertStackAlignment(WasmStackAlignment);
+
+#ifdef ENABLE_WASM_GC
+    if (wasmGcEnabled)
+        SuppressGC(masm, -1, WasmTlsReg);
+#endif
+
+    masm.branchPtr(Assembler::Equal, FramePointer, Imm32(wasm::FailFP), masm.exceptionLabel());
+
+    // Store the return value in the appropriate place.
+    switch (fe.funcType().ret().code()) {
+      case wasm::ExprType::Void:
+        masm.moveValue(UndefinedValue(), JSReturnOperand);
+        break;
+      case wasm::ExprType::I32:
+        break;
+      case wasm::ExprType::F32:
+        masm.canonicalizeFloat(ReturnFloat32Reg);
+        break;
+      case wasm::ExprType::F64:
+        masm.canonicalizeDouble(ReturnDoubleReg);
+        break;
+      case wasm::ExprType::Ref:
+      case wasm::ExprType::AnyRef:
+      case wasm::ExprType::I64:
+        MOZ_CRASH("unexpected return type when calling from ion to wasm");
+      case wasm::ExprType::Limit:
+        MOZ_CRASH("Limit");
+    }
+
+    // Free args + frame descriptor.
+    masm.leaveExitFrame(bytesNeeded + ExitFrameLayout::Size());
+
+    // If we pushed it, free FramePointer.
+    if (profilingEnabled)
+        masm.Pop(FramePointer);
+
+    MOZ_ASSERT(framePushedAtStart == masm.framePushed());
+}
+
 static void
 StackCopy(MacroAssembler& masm, MIRType type, Register scratch, Address src, Address dst)
 {
     if (type == MIRType::Int32) {
         masm.load32(src, scratch);
         masm.store32(scratch, dst);
     } else if (type == MIRType::Int64) {
 #if JS_BITS_PER_WORD == 32
--- a/js/src/wasm/WasmStubs.h
+++ b/js/src/wasm/WasmStubs.h
@@ -36,12 +36,93 @@ extern bool
 GenerateStubs(const ModuleEnvironment& env, const FuncImportVector& imports,
               const FuncExportVector& exports, CompiledCode* code);
 
 extern bool
 GenerateEntryStubs(jit::MacroAssembler& masm, size_t funcExportIndex,
                    const FuncExport& funcExport, const Maybe<jit::ImmPtr>& callee,
                    bool isAsmJS, HasGcTypes gcTypesEnabled, CodeRangeVector* codeRanges);
 
+// An argument that will end up on the stack according to the system ABI, to be
+// passed to GenerateDirectCallFromJit. Since the direct JIT call creates its
+// own frame, it is its responsibility to put stack arguments to their expected
+// locations; so the caller of GenerateDirectCallFromJit can put them anywhere.
+
+class JitCallStackArg
+{
+  public:
+    enum class Tag {
+        Imm32,
+        GPR,
+        FPU,
+        Address,
+        Undefined,
+    };
+
+  private:
+    Tag tag_;
+    union U {
+        int32_t imm32_;
+        jit::Register gpr_;
+        jit::FloatRegister fpu_;
+        jit::Address addr_;
+        U() {}
+    } arg;
+
+  public:
+    JitCallStackArg()
+      : tag_(Tag::Undefined)
+    {}
+    explicit JitCallStackArg(int32_t imm32)
+      : tag_(Tag::Imm32)
+    {
+        arg.imm32_ = imm32;
+    }
+    explicit JitCallStackArg(jit::Register gpr)
+      : tag_(Tag::GPR)
+    {
+        arg.gpr_ = gpr;
+    }
+    explicit JitCallStackArg(jit::FloatRegister fpu)
+      : tag_(Tag::FPU)
+    {
+        new (&arg) jit::FloatRegister(fpu);
+    }
+    explicit JitCallStackArg(const jit::Address& addr)
+      : tag_(Tag::Address)
+    {
+        new (&arg) jit::Address(addr);
+    }
+
+    Tag tag() const { return tag_; }
+    int32_t imm32() const { MOZ_ASSERT(tag_ == Tag::Imm32); return arg.imm32_; }
+    jit::Register gpr() const { MOZ_ASSERT(tag_ == Tag::GPR); return arg.gpr_; }
+    jit::FloatRegister fpu() const { MOZ_ASSERT(tag_ == Tag::FPU); return arg.fpu_; }
+    const jit::Address& addr() const { MOZ_ASSERT(tag_ == Tag::Address); return arg.addr_; }
+};
+
+using JitCallStackArgVector = Vector<JitCallStackArg, 4, SystemAllocPolicy>;
+
+// Generates an inline wasm call (during jit compilation) to a specific wasm
+// function (as specifed by the given FuncExport).
+// This call doesn't go through a wasm entry, but rather creates its own
+// inlined exit frame.
+// Assumes:
+// - all the registers have been preserved by the caller,
+// - all arguments passed in registers have been set up at the expected
+//   locations,
+// - all arguments passed on stack slot are alive as defined by a corresponding
+//   JitCallStackArg.
+
+extern void
+GenerateDirectCallFromJit(jit::MacroAssembler& masm,
+                          const FuncExport& fe,
+                          const Instance& inst,
+                          const JitCallStackArgVector& stackArgs,
+                          bool profilingEnabled,
+                          bool wasmGcEnabled,
+                          jit::Register scratch,
+                          uint32_t* callOffset);
+
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_stubs_h
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -89,19 +89,17 @@
 using mozilla::dom::PushNotifier;
 #define PUSHNOTIFIER_CID \
 { 0x2fc2d3e3, 0x020f, 0x404e, { 0xb0, 0x6a, 0x6e, 0xcf, 0x3e, 0xa2, 0x33, 0x4a } }
 
 #include "AudioChannelAgent.h"
 using mozilla::dom::AudioChannelAgent;
 
 // Editor stuff
-#include "nsEditorCID.h"
 #include "mozilla/EditorController.h" //CID
-#include "mozilla/HTMLEditor.h"
 
 #include "nsScriptSecurityManager.h"
 #include "ExpandedPrincipal.h"
 #include "mozilla/ContentPrincipal.h"
 #include "mozilla/NullPrincipal.h"
 #include "mozilla/SystemPrincipal.h"
 #include "nsNetCID.h"
 #if defined(MOZ_WIDGET_ANDROID)
@@ -174,22 +172,18 @@ using mozilla::gmp::GeckoMediaPluginServ
 #define NS_EDITINGCOMMANDTABLE_CID \
 { 0xcb38a746, 0xbeb8, 0x43f3, \
   { 0x94, 0x29, 0x77, 0x96, 0xe1, 0xa9, 0x3f, 0xb4 } }
 
 #define NS_HAPTICFEEDBACK_CID \
 { 0x1f15dbc8, 0xbfaa, 0x45de, \
   { 0x8a, 0x46, 0x08, 0xe2, 0xe2, 0x63, 0x26, 0xb0 } }
 
-NS_GENERIC_FACTORY_CONSTRUCTOR(TextEditor)
-
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsParserUtils)
 
-NS_GENERIC_FACTORY_CONSTRUCTOR(HTMLEditor)
-
 // PresentationDeviceManager
 /* e1e79dec-4085-4994-ac5b-744b016697e6 */
 #define PRESENTATION_DEVICE_MANAGER_CID \
 { 0xe1e79dec, 0x4085, 0x4994, { 0xac, 0x5b, 0x74, 0x4b, 0x01, 0x66, 0x97, 0xe6 } }
 
 #define PRESENTATION_TCP_SESSION_TRANSPORT_CID \
 { 0xc9d023f4, 0x6228, 0x4c07, { 0x8b, 0x1d, 0x9c, 0x19, 0x57, 0x3f, 0xaa, 0x27 } }
 
@@ -528,26 +522,24 @@ NS_DEFINE_NAMED_CID(NS_PLUGINDOCLOADERFA
 NS_DEFINE_NAMED_CID(NS_PLUGINDOCUMENT_CID);
 NS_DEFINE_NAMED_CID(NS_VIDEODOCUMENT_CID);
 NS_DEFINE_NAMED_CID(NS_STYLESHEETSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_HOSTOBJECTURI_CID);
 NS_DEFINE_NAMED_CID(NS_HOSTOBJECTURIMUTATOR_CID);
 NS_DEFINE_NAMED_CID(NS_SDBCONNECTION_CID);
 NS_DEFINE_NAMED_CID(NS_DOMSESSIONSTORAGEMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_DOMLOCALSTORAGEMANAGER_CID);
-NS_DEFINE_NAMED_CID(NS_TEXTEDITOR_CID);
 NS_DEFINE_NAMED_CID(DOMREQUEST_SERVICE_CID);
 NS_DEFINE_NAMED_CID(QUOTAMANAGER_SERVICE_CID);
 NS_DEFINE_NAMED_CID(SERVICEWORKERMANAGER_CID);
 NS_DEFINE_NAMED_CID(STORAGEACTIVITYSERVICE_CID);
 NS_DEFINE_NAMED_CID(NOTIFICATIONTELEMETRYSERVICE_CID);
 NS_DEFINE_NAMED_CID(PUSHNOTIFIER_CID);
 NS_DEFINE_NAMED_CID(WORKERDEBUGGERMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_AUDIOCHANNELAGENT_CID);
-NS_DEFINE_NAMED_CID(NS_HTMLEDITOR_CID);
 NS_DEFINE_NAMED_CID(NS_EDITORCONTROLLER_CID);
 NS_DEFINE_NAMED_CID(NS_EDITINGCONTROLLER_CID);
 NS_DEFINE_NAMED_CID(NS_EDITORCOMMANDTABLE_CID);
 NS_DEFINE_NAMED_CID(NS_EDITINGCOMMANDTABLE_CID);
 NS_DEFINE_NAMED_CID(NS_GEOLOCATION_CID);
 NS_DEFINE_NAMED_CID(NS_AUDIOCHANNEL_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_WEBSOCKETEVENT_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_FOCUSMANAGER_CID);
@@ -765,26 +757,24 @@ static const mozilla::Module::CIDEntry k
   { &kNS_PLUGINDOCUMENT_CID, false, nullptr, CreatePluginDocument },
   { &kNS_VIDEODOCUMENT_CID, false, nullptr, CreateVideoDocument },
   { &kNS_STYLESHEETSERVICE_CID, false, nullptr, nsStyleSheetServiceConstructor },
   { &kNS_HOSTOBJECTURI_CID, false, nullptr, BlobURLMutatorConstructor }, // do_CreateInstance returns mutator
   { &kNS_HOSTOBJECTURIMUTATOR_CID, false, nullptr, BlobURLMutatorConstructor },
   { &kNS_SDBCONNECTION_CID, false, nullptr, SDBConnection::Create },
   { &kNS_DOMSESSIONSTORAGEMANAGER_CID, false, nullptr, SessionStorageManagerConstructor },
   { &kNS_DOMLOCALSTORAGEMANAGER_CID, false, nullptr, LocalStorageManagerConstructor },
-  { &kNS_TEXTEDITOR_CID, false, nullptr, TextEditorConstructor },
   { &kDOMREQUEST_SERVICE_CID, false, nullptr, DOMRequestServiceConstructor },
   { &kQUOTAMANAGER_SERVICE_CID, false, nullptr, QuotaManagerServiceConstructor },
   { &kSERVICEWORKERMANAGER_CID, false, nullptr, ServiceWorkerManagerConstructor },
   { &kSTORAGEACTIVITYSERVICE_CID, false, nullptr, StorageActivityServiceConstructor },
   { &kNOTIFICATIONTELEMETRYSERVICE_CID, false, nullptr, NotificationTelemetryServiceConstructor },
   { &kPUSHNOTIFIER_CID, false, nullptr, PushNotifierConstructor },
   { &kWORKERDEBUGGERMANAGER_CID, true, nullptr, WorkerDebuggerManagerConstructor },
   { &kNS_AUDIOCHANNELAGENT_CID, true, nullptr, AudioChannelAgentConstructor },
-  { &kNS_HTMLEDITOR_CID, false, nullptr, HTMLEditorConstructor },
   { &kNS_EDITORCONTROLLER_CID, false, nullptr, EditorControllerConstructor },
   { &kNS_EDITINGCONTROLLER_CID, false, nullptr, nsEditingControllerConstructor },
   { &kNS_EDITORCOMMANDTABLE_CID, false, nullptr, nsEditorCommandTableConstructor },
   { &kNS_EDITINGCOMMANDTABLE_CID, false, nullptr, nsEditingCommandTableConstructor },
   { &kNS_GEOLOCATION_CID, false, nullptr, GeolocationConstructor },
   { &kNS_AUDIOCHANNEL_SERVICE_CID, false, nullptr, AudioChannelServiceConstructor },
   { &kNS_WEBSOCKETEVENT_SERVICE_CID, false, nullptr, WebSocketEventServiceConstructor },
   { &kNS_FOCUSMANAGER_CID, false, nullptr, CreateFocusManager },
@@ -867,26 +857,24 @@ static const mozilla::Module::ContractID
   { NS_WINDOWCONTROLLER_CONTRACTID, &kNS_WINDOWCONTROLLER_CID },
   { PLUGIN_DLF_CONTRACTID, &kNS_PLUGINDOCLOADERFACTORY_CID },
   { NS_STYLESHEETSERVICE_CONTRACTID, &kNS_STYLESHEETSERVICE_CID },
   { NS_SDBCONNECTION_CONTRACTID, &kNS_SDBCONNECTION_CID },
   { "@mozilla.org/dom/localStorage-manager;1", &kNS_DOMLOCALSTORAGEMANAGER_CID },
   // Keeping the old ContractID for backward compatibility
   { "@mozilla.org/dom/storagemanager;1", &kNS_DOMLOCALSTORAGEMANAGER_CID },
   { "@mozilla.org/dom/sessionStorage-manager;1", &kNS_DOMSESSIONSTORAGEMANAGER_CID },
-  { "@mozilla.org/editor/texteditor;1", &kNS_TEXTEDITOR_CID },
   { DOMREQUEST_SERVICE_CONTRACTID, &kDOMREQUEST_SERVICE_CID },
   { QUOTAMANAGER_SERVICE_CONTRACTID, &kQUOTAMANAGER_SERVICE_CID },
   { SERVICEWORKERMANAGER_CONTRACTID, &kSERVICEWORKERMANAGER_CID },
   { STORAGE_ACTIVITY_SERVICE_CONTRACTID, &kSTORAGEACTIVITYSERVICE_CID },
   { NOTIFICATIONTELEMETRYSERVICE_CONTRACTID, &kNOTIFICATIONTELEMETRYSERVICE_CID },
   { PUSHNOTIFIER_CONTRACTID, &kPUSHNOTIFIER_CID },
   { WORKERDEBUGGERMANAGER_CONTRACTID, &kWORKERDEBUGGERMANAGER_CID },
   { NS_AUDIOCHANNELAGENT_CONTRACTID, &kNS_AUDIOCHANNELAGENT_CID },
-  { "@mozilla.org/editor/htmleditor;1", &kNS_HTMLEDITOR_CID },
   { "@mozilla.org/editor/editorcontroller;1", &kNS_EDITORCONTROLLER_CID },
   { "@mozilla.org/editor/editingcontroller;1", &kNS_EDITINGCONTROLLER_CID },
   { "@mozilla.org/geolocation;1", &kNS_GEOLOCATION_CID },
   { "@mozilla.org/audiochannel/service;1", &kNS_AUDIOCHANNEL_SERVICE_CID },
   { "@mozilla.org/websocketevent/service;1", &kNS_WEBSOCKETEVENT_SERVICE_CID },
   { "@mozilla.org/focus-manager;1", &kNS_FOCUSMANAGER_CID },
 #ifdef MOZ_WEBSPEECH_TEST_BACKEND
   { NS_SPEECH_RECOGNITION_SERVICE_CONTRACTID_PREFIX "fake", &kNS_FAKE_SPEECH_RECOGNITION_SERVICE_CID },
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -2708,18 +2708,18 @@ ComputeClipForMaskItem(nsDisplayListBuil
   gfxPoint devPixelOffsetToUserSpace = nsLayoutUtils::PointToGfxPoint(
       offsetToUserSpace, devPixelRatio);
   gfxMatrix cssToDevMatrix = nsSVGUtils::GetCSSPxToDevPxMatrix(aMaskedFrame);
 
   nsPoint toReferenceFrame;
   aBuilder->FindReferenceFrameFor(aMaskedFrame, &toReferenceFrame);
 
   Maybe<gfxRect> combinedClip;
-  if (maskUsage.shouldApplyBasicShape) {
-    Rect result = nsCSSClipPathInstance::GetBoundingRectForBasicShapeClip(
+  if (maskUsage.shouldApplyBasicShapeOrPath) {
+    Rect result = nsCSSClipPathInstance::GetBoundingRectForBasicShapeOrPathClip(
         aMaskedFrame, svgReset->mClipPath);
     combinedClip = Some(ThebesRect(result));
   } else if (maskUsage.shouldApplyClipPath) {
     gfxRect result = nsSVGUtils::GetBBox(aMaskedFrame,
         nsSVGUtils::eBBoxIncludeClipped |
         nsSVGUtils::eBBoxIncludeFill |
         nsSVGUtils::eBBoxIncludeMarkers |
         nsSVGUtils::eBBoxIncludeStroke |
--- a/layout/inspector/tests/test_bug877690.html
+++ b/layout/inspector/tests/test_bug877690.html
@@ -185,17 +185,17 @@ function do_test() {
   ok(testValues(values, expected), "property text-shadow's values");
 
   var expected = [ "inherit", "initial", "unset", "inset", "none", ...allColors ];
   var values = InspectorUtils.getCSSValuesForProperty("box-shadow");
   ok(testValues(values, expected), "property box-shadow's values");
 
   // Regression test for bug 1255379.
   var expected = [ "inherit", "initial", "unset", "none", "url",
-                   "polygon", "circle", "ellipse", "inset",
+                   "polygon", "circle", "ellipse", "inset", "path",
                    "fill-box", "stroke-box", "view-box", "margin-box",
                    "border-box", "padding-box", "content-box" ];
   var values = InspectorUtils.getCSSValuesForProperty("clip-path");
   ok(testValues(values, expected), "property clip-path's values");
 
   var expected = [ "inherit", "initial", "unset", "auto", "rect" ];
   var values = InspectorUtils.getCSSValuesForProperty("clip");
   ok(testValues(values, expected), "property clip's values");
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/svg-integration/clip-path/clip-path-path-001-ref.html
@@ -0,0 +1,29 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<html>
+<head>
+  <title>CSS Masking: Reference for clip-path's path function with nonzero</title>
+  <style type="text/css">
+    #rect {
+      width: 100px;
+      height: 100px;
+      background-color: green;
+      clip-path: url("#clip");
+    }
+  </style>
+</head>
+<body>
+  <p>The test passes if there are a green filled rect.</p>
+  <div id="rect"></div>
+  <svg height="0" width="0">
+    <defs>
+      <clipPath id="clip">
+        <path clip-rule="nonzero" d="M10,10h80v80h-80zM25,25h50v50h-50z"/>
+      </clipPath>
+    </defs>
+  </svg>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/svg-integration/clip-path/clip-path-path-001.html
@@ -0,0 +1,24 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<html>
+<head>
+  <title>CSS Masking: Test clip-path property and path function with nonzero</title>
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-2/#funcdef-path">
+  <link rel="stylesheet" href="chrome://reftest/content/path.css">
+  <link rel="match" href="clip-path-path-001-ref.html">
+</head>
+<style>
+  #rect {
+    width: 100px;
+    height: 100px;
+    background-color: green;
+  }
+</style>
+<body>
+  <p>The test passes if there are a green filled rect.</p>
+  <div id="rect" class="path_nonzero_rect"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/svg-integration/clip-path/clip-path-path-002-ref.html
@@ -0,0 +1,29 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<html>
+<head>
+  <title>CSS Masking: Reference for clip-path's path function with evenodd</title>
+  <style type="text/css">
+    #rect {
+      width: 100px;
+      height: 100px;
+      background-color: green;
+      clip-path: url("#clip");
+    }
+  </style>
+</head>
+<body>
+  <p>The test passes if there are a green hollow rect.</p>
+  <div id="rect"></div>
+  <svg height="0" width="0">
+    <defs>
+      <clipPath id="clip">
+        <path clip-rule="evenodd" d="M10,10h80v80h-80zM25,25h50v50h-50z"/>
+      </clipPath>
+    </defs>
+  </svg>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/svg-integration/clip-path/clip-path-path-002.html
@@ -0,0 +1,24 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<html>
+<head>
+  <title>CSS Masking: Test clip-path property and path function with evenodd</title>
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-2/#funcdef-path">
+  <link rel="stylesheet" href="chrome://reftest/content/path.css">
+  <link rel="match" href="clip-path-path-002-ref.html">
+</head>
+<style>
+  #rect {
+    width: 100px;
+    height: 100px;
+    background-color: green;
+  }
+</style>
+<body>
+  <p>The test passes if there are a green hollow rect.</p>
+  <div id="rect" class="path_evenodd_rect"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/svg-integration/clip-path/path.css
@@ -0,0 +1,7 @@
+.path_nonzero_rect {
+  clip-path: path(nonzero, 'M10,10h80v80h-80zM25,25h50v50h-50z');
+}
+
+.path_evenodd_rect {
+  clip-path: path(evenodd, 'M10,10h80v80h-80zM25,25h50v50h-50z');
+}
--- a/layout/reftests/svg/svg-integration/clip-path/reftest.list
+++ b/layout/reftests/svg/svg-integration/clip-path/reftest.list
@@ -54,8 +54,11 @@ fuzzy-if(webrender,64-64,1106-1106) == c
 fuzzy(0-64,0-146) fuzzy-if(webrender,90-90,132-132) == clip-path-inset-002a.html clip-path-inset-002-ref.html
 fuzzy(0-64,0-146) fuzzy-if(webrender,90-90,132-132) == clip-path-inset-002b.html clip-path-inset-002-ref.html
 fuzzy(0-64,0-146) fuzzy-if(webrender,90-90,132-132) == clip-path-inset-002c.html clip-path-inset-002-ref.html
 fuzzy(0-64,0-340) fuzzy-if(webrender,104-104,311-311) == clip-path-inset-003.html clip-path-inset-003-ref.html
 
 == clip-path-stroke-001.html clip-path-stroke-001-ref.html
 
 == clip-path-transform-001.html clip-path-transform-001-ref.html
+
+== clip-path-path-001.html clip-path-path-001-ref.html
+== clip-path-path-002.html clip-path-path-002-ref.html
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -1973,28 +1973,34 @@ private:
 
 struct StyleSVGPath final
 {
   const nsTArray<StylePathCommand>& Path() const
   {
     return mPath;
   }
 
+  StyleFillRule FillRule() const
+  {
+    return mFillRule;
+  }
+
   bool operator==(const StyleSVGPath& aOther) const
   {
-    return mPath == aOther.mPath;
+    return mPath == aOther.mPath && mFillRule == aOther.mFillRule;
   }
 
   bool operator!=(const StyleSVGPath& aOther) const
   {
     return !(*this == aOther);
   }
 
 private:
   nsTArray<StylePathCommand> mPath;
+  StyleFillRule mFillRule = StyleFillRule::Nonzero;
 };
 
 struct StyleShapeSource final
 {
   StyleShapeSource();
 
   StyleShapeSource(const StyleShapeSource& aSource);
 
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -8124,16 +8124,29 @@ if (false) {
   gCSSProperties["-moz-font-smoothing-background-color"] = {
     // domProp: "MozFontSmoothingBackgroundColor",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "transparent" ],
     other_values: [ "green", "#fc3" ],
     invalid_values: [ "000000", "ff00ff" ]
   };
+
+  // |clip-path: path()| is chrome-only.
+  gCSSProperties["clip-path"].other_values.push(
+    "path(nonzero, 'M 10 10 h 100 v 100 h-100 v-100 z')",
+    "path(evenodd, 'M 10 10 h 100 v 100 h-100 v-100 z')",
+    "path('M10,30A20,20 0,0,1 50,30A20,20 0,0,1 90,30Q90,60 50,90Q10,60 10,30z')",
+  );
+
+  gCSSProperties["clip-path"].invalid_values.push(
+    "path(nonzero)",
+    "path(evenodd, '')",
+    "path(abs, 'M 10 10 L 10 10 z')",
+  );
 }
 
 if (IsCSSPropertyPrefEnabled("layout.css.filters.enabled")) {
   gCSSProperties["filter"].invalid_values.push("drop-shadow(unset, 2px 2px)", "drop-shadow(2px 2px, unset)");
 }
 
 if (IsCSSPropertyPrefEnabled("layout.css.prefixes.gradients")) {
   gCSSProperties["background-image"].invalid_values.push(
--- a/layout/svg/nsCSSClipPathInstance.cpp
+++ b/layout/svg/nsCSSClipPathInstance.cpp
@@ -5,54 +5,56 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Main header first:
 #include "nsCSSClipPathInstance.h"
 
 #include "gfx2DGlue.h"
 #include "gfxContext.h"
 #include "gfxPlatform.h"
+#include "mozilla/dom/SVGPathData.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/PathHelpers.h"
 #include "mozilla/ShapeUtils.h"
 #include "nsCSSRendering.h"
 #include "nsIFrame.h"
 #include "nsLayoutUtils.h"
 #include "nsSVGElement.h"
 #include "nsSVGUtils.h"
 #include "nsSVGViewBox.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 
 /* static*/ void
-nsCSSClipPathInstance::ApplyBasicShapeClip(gfxContext& aContext,
-                                           nsIFrame* aFrame)
+nsCSSClipPathInstance::ApplyBasicShapeOrPathClip(gfxContext& aContext,
+                                                 nsIFrame* aFrame)
 {
   auto& clipPathStyle = aFrame->StyleSVGReset()->mClipPath;
 
 #ifdef DEBUG
   StyleShapeSourceType type = clipPathStyle.GetType();
   MOZ_ASSERT(type == StyleShapeSourceType::Shape ||
-             type == StyleShapeSourceType::Box,
-             "This function is used with basic-shape and geometry-box only.");
+             type == StyleShapeSourceType::Box ||
+             type == StyleShapeSourceType::Path,
+             "This is used with basic-shape, geometry-box, and path() only");
 #endif
 
   nsCSSClipPathInstance instance(aFrame, clipPathStyle);
 
   aContext.NewPath();
   RefPtr<Path> path = instance.CreateClipPath(aContext.GetDrawTarget());
   aContext.SetPath(path);
   aContext.Clip();
 }
 
 /* static*/ bool
-nsCSSClipPathInstance::HitTestBasicShapeClip(nsIFrame* aFrame,
-                                             const gfxPoint& aPoint)
+nsCSSClipPathInstance::HitTestBasicShapeOrPathClip(nsIFrame* aFrame,
+                                                   const gfxPoint& aPoint)
 {
   auto& clipPathStyle = aFrame->StyleSVGReset()->mClipPath;
   StyleShapeSourceType type = clipPathStyle.GetType();
   MOZ_ASSERT(type != StyleShapeSourceType::None, "unexpected none value");
   // In the future nsCSSClipPathInstance may handle <clipPath> references as
   // well. For the time being return early.
   if (type == StyleShapeSourceType::URL) {
     return false;
@@ -64,33 +66,39 @@ nsCSSClipPathInstance::HitTestBasicShape
     gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
   RefPtr<Path> path = instance.CreateClipPath(drawTarget);
   float pixelRatio = float(AppUnitsPerCSSPixel()) /
                      aFrame->PresContext()->AppUnitsPerDevPixel();
   return path->ContainsPoint(ToPoint(aPoint) * pixelRatio, Matrix());
 }
 
 /* static */ Rect
-nsCSSClipPathInstance::GetBoundingRectForBasicShapeClip(nsIFrame* aFrame,
-                                                        const StyleShapeSource& aClipPathStyle)
+nsCSSClipPathInstance::GetBoundingRectForBasicShapeOrPathClip(
+  nsIFrame* aFrame,
+  const StyleShapeSource& aClipPathStyle)
 {
   MOZ_ASSERT(aClipPathStyle.GetType() == StyleShapeSourceType::Shape ||
-             aClipPathStyle.GetType() == StyleShapeSourceType::Box);
+             aClipPathStyle.GetType() == StyleShapeSourceType::Box ||
+             aClipPathStyle.GetType() == StyleShapeSourceType::Path);
 
   nsCSSClipPathInstance instance(aFrame, aClipPathStyle);
 
   RefPtr<DrawTarget> drawTarget =
     gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
   RefPtr<Path> path = instance.CreateClipPath(drawTarget);
   return path->GetBounds();
 }
 
 already_AddRefed<Path>
 nsCSSClipPathInstance::CreateClipPath(DrawTarget* aDrawTarget)
 {
+  if (mClipPathStyle.GetType() == StyleShapeSourceType::Path) {
+    return CreateClipPathPath(aDrawTarget);
+  }
+
   nsRect r =
     nsLayoutUtils::ComputeGeometryBox(mTargetFrame,
                                       mClipPathStyle.GetReferenceBox());
 
   if (mClipPathStyle.GetType() != StyleShapeSourceType::Shape) {
     // TODO Clip to border-radius/reference box if no shape
     // was specified.
     RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
@@ -208,8 +216,23 @@ nsCSSClipPathInstance::CreateClipPathIns
                                       appUnitsPerDevPixel, &corners);
 
     AppendRoundedRectToPath(builder, insetRectPixels, corners, true);
   } else {
     AppendRectToPath(builder, insetRectPixels, true);
   }
   return builder->Finish();
 }
+
+already_AddRefed<Path>
+nsCSSClipPathInstance::CreateClipPathPath(DrawTarget* aDrawTarget)
+{
+  const StyleSVGPath* path = mClipPathStyle.GetPath();
+  MOZ_ASSERT(path);
+
+  RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(
+    path->FillRule() == StyleFillRule::Nonzero ? FillRule::FILL_WINDING
+                                               : FillRule::FILL_EVEN_ODD);
+  float scale = float(AppUnitsPerCSSPixel()) /
+                mTargetFrame->PresContext()->AppUnitsPerDevPixel();
+  return SVGPathData::BuildPath(
+    path->Path(), builder, NS_STYLE_STROKE_LINECAP_BUTT, 0.0, scale);
+}
--- a/layout/svg/nsCSSClipPathInstance.h
+++ b/layout/svg/nsCSSClipPathInstance.h
@@ -18,24 +18,26 @@ namespace mozilla {
 
 class nsCSSClipPathInstance
 {
   typedef mozilla::gfx::DrawTarget DrawTarget;
   typedef mozilla::gfx::Path Path;
   typedef mozilla::gfx::Rect Rect;
 
 public:
-  static void ApplyBasicShapeClip(gfxContext& aContext,
-                                  nsIFrame* aFrame);
+  static void ApplyBasicShapeOrPathClip(gfxContext& aContext,
+                                        nsIFrame* aFrame);
   // aPoint is in CSS pixels.
-  static bool HitTestBasicShapeClip(nsIFrame* aFrame,
-                                    const gfxPoint& aPoint);
+  static bool HitTestBasicShapeOrPathClip(nsIFrame* aFrame,
+                                          const gfxPoint& aPoint);
 
-  static Rect GetBoundingRectForBasicShapeClip(nsIFrame* aFrame,
-                                               const StyleShapeSource& aClipPathStyle);
+  static Rect GetBoundingRectForBasicShapeOrPathClip(
+    nsIFrame* aFrame,
+    const StyleShapeSource& aClipPathStyle);
+
 private:
   explicit nsCSSClipPathInstance(nsIFrame* aFrame,
                                  const StyleShapeSource aClipPathStyle)
     : mTargetFrame(aFrame)
     , mClipPathStyle(aClipPathStyle)
   {
   }
 
@@ -48,16 +50,17 @@ private:
                                                const nsRect& aRefBox);
 
   already_AddRefed<Path> CreateClipPathPolygon(DrawTarget* aDrawTarget,
                                                const nsRect& aRefBox);
 
   already_AddRefed<Path> CreateClipPathInset(DrawTarget* aDrawTarget,
                                              const nsRect& aRefBox);
 
+  already_AddRefed<Path> CreateClipPathPath(DrawTarget* aDrawTarget);
 
   /**
    * The frame for the element that is currently being clipped.
    */
   nsIFrame* mTargetFrame;
   StyleShapeSource mClipPathStyle;
 };
 
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -804,23 +804,23 @@ nsSVGIntegrationUtils::PaintMask(const P
     ctx.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, maskUsage.opacity);
     autoPop.SetContext(&ctx);
   }
 
   gfxContextMatrixAutoSaveRestore matSR;
 
   // Paint clip-path-basic-shape onto ctx
   gfxContextAutoSaveRestore basicShapeSR;
-  if (maskUsage.shouldApplyBasicShape) {
+  if (maskUsage.shouldApplyBasicShapeOrPath) {
     matSR.SetContext(&ctx);
 
     MoveContextOriginToUserSpace(firstFrame, aParams);
 
     basicShapeSR.SetContext(&ctx);
-    nsCSSClipPathInstance::ApplyBasicShapeClip(ctx, frame);
+    nsCSSClipPathInstance::ApplyBasicShapeOrPathClip(ctx, frame);
     if (!maskUsage.shouldGenerateMaskLayer) {
       // Only have basic-shape clip-path effect. Fill clipped region by
       // opaque white.
       ctx.SetColor(Color(1.0, 1.0, 1.0, 1.0));
       ctx.Fill();
 
       return true;
     }
@@ -992,27 +992,27 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
                                       maskSurface, maskTransform);
       }
     }
   }
 
   /* If this frame has only a trivial clipPath, set up cairo's clipping now so
    * we can just do normal painting and get it clipped appropriately.
    */
-  if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) {
+  if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
     gfxContextMatrixAutoSaveRestore matSR(&context);
 
     MoveContextOriginToUserSpace(firstFrame, aParams);
 
     MOZ_ASSERT(!maskUsage.shouldApplyClipPath ||
-               !maskUsage.shouldApplyBasicShape);
+               !maskUsage.shouldApplyBasicShapeOrPath);
     if (maskUsage.shouldApplyClipPath) {
       clipPathFrame->ApplyClipPath(context, frame, cssPxToDevPxMatrix);
     } else {
-      nsCSSClipPathInstance::ApplyBasicShapeClip(context, frame);
+      nsCSSClipPathInstance::ApplyBasicShapeOrPathClip(context, frame);
     }
   }
 
   /* Paint the child */
   context.SetMatrix(matrixAutoSaveRestore.Matrix());
   BasicLayerManager* basic = aParams.layerManager->AsBasicLayerManager();
   RefPtr<gfxContext> oldCtx = basic->GetTarget();
   basic->SetTarget(&context);
@@ -1031,26 +1031,26 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
     Color overlayColor(0.0f, 0.0f, 0.0f, 0.8f);
     if (maskUsage.shouldGenerateMaskLayer) {
       overlayColor.r = 1.0f; // red represents css positioned mask.
     }
     if (maskUsage.shouldApplyClipPath ||
         maskUsage.shouldGenerateClipMaskLayer) {
       overlayColor.g = 1.0f; // green represents clip-path:<clip-source>.
     }
-    if (maskUsage.shouldApplyBasicShape) {
+    if (maskUsage.shouldApplyBasicShapeOrPath) {
       overlayColor.b = 1.0f; // blue represents
                              // clip-path:<basic-shape>||<geometry-box>.
     }
 
     context.SetColor(overlayColor);
     context.Fill();
   }
 
-  if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) {
+  if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
     context.PopClip();
   }
 
   if (shouldPushMask) {
     context.PopGroupAndBlend();
   }
 
 }
--- a/layout/svg/nsSVGUtils.cpp
+++ b/layout/svg/nsSVGUtils.cpp
@@ -518,21 +518,23 @@ nsSVGUtils::DetermineMaskUsage(nsIFrame*
           aUsage.shouldApplyClipPath = true;
         } else {
           aUsage.shouldGenerateClipMaskLayer = true;
         }
       }
       break;
     case StyleShapeSourceType::Shape:
     case StyleShapeSourceType::Box:
-      aUsage.shouldApplyBasicShape = true;
+    case StyleShapeSourceType::Path:
+      aUsage.shouldApplyBasicShapeOrPath = true;
       break;
     case StyleShapeSourceType::None:
       MOZ_ASSERT(!aUsage.shouldGenerateClipMaskLayer &&
-                 !aUsage.shouldApplyClipPath && !aUsage.shouldApplyBasicShape);
+                 !aUsage.shouldApplyClipPath &&
+                 !aUsage.shouldApplyBasicShapeOrPath);
       break;
     default:
       MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type.");
       break;
   }
 }
 
 class MixModeBlender {
@@ -802,21 +804,21 @@ nsSVGUtils::PaintFrameWithEffects(nsIFra
                                     maskFrame ? 1.0 : maskUsage.opacity,
                                     maskSurface, maskTransform);
     }
   }
 
   /* If this frame has only a trivial clipPath, set up cairo's clipping now so
    * we can just do normal painting and get it clipped appropriately.
    */
-  if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) {
+  if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
     if (maskUsage.shouldApplyClipPath) {
       clipPathFrame->ApplyClipPath(aContext, aFrame, aTransform);
     } else {
-      nsCSSClipPathInstance::ApplyBasicShapeClip(aContext, aFrame);
+      nsCSSClipPathInstance::ApplyBasicShapeOrPathClip(aContext, aFrame);
     }
   }
 
   /* Paint the child */
   if (effectProperties.HasValidFilter()) {
     nsRegion* dirtyRegion = nullptr;
     nsRegion tmpDirtyRegion;
     if (aDirtyRect) {
@@ -851,17 +853,17 @@ nsSVGUtils::PaintFrameWithEffects(nsIFra
 
     SVGPaintCallback paintCallback;
     nsFilterInstance::PaintFilteredFrame(aFrame, target, &paintCallback,
                                          dirtyRegion, aImgParams);
   } else {
      svgFrame->PaintSVG(*target, aTransform, aImgParams, aDirtyRect);
   }
 
-  if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) {
+  if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
     aContext.PopClip();
   }
 
   if (shouldPushMask) {
     target->PopGroupAndBlend();
   }
 
   if (blender.ShouldCreateDrawTargetForBlend()) {
@@ -873,17 +875,17 @@ nsSVGUtils::PaintFrameWithEffects(nsIFra
 bool
 nsSVGUtils::HitTestClip(nsIFrame *aFrame, const gfxPoint &aPoint)
 {
   SVGObserverUtils::EffectProperties props =
     SVGObserverUtils::GetEffectProperties(aFrame);
   if (!props.mClipPath) {
     const nsStyleSVGReset *style = aFrame->StyleSVGReset();
     if (style->HasClipPath()) {
-      return nsCSSClipPathInstance::HitTestBasicShapeClip(aFrame, aPoint);
+      return nsCSSClipPathInstance::HitTestBasicShapeOrPathClip(aFrame, aPoint);
     }
     return true;
   }
 
   if (props.HasInvalidClipPath()) {
     // clipPath is not a valid resource, so nothing gets painted, so
     // hit-testing must fail.
     return false;
--- a/layout/svg/nsSVGUtils.h
+++ b/layout/svg/nsSVGUtils.h
@@ -601,29 +601,32 @@ public:
   static nsRect ToCanvasBounds(const gfxRect &aUserspaceRect,
                                const gfxMatrix &aToCanvas,
                                const nsPresContext *presContext);
 
   struct MaskUsage {
     bool shouldGenerateMaskLayer;
     bool shouldGenerateClipMaskLayer;
     bool shouldApplyClipPath;
-    bool shouldApplyBasicShape;
+    bool shouldApplyBasicShapeOrPath;
     float opacity;
 
     MaskUsage()
-      : shouldGenerateMaskLayer(false), shouldGenerateClipMaskLayer(false),
-        shouldApplyClipPath(false), shouldApplyBasicShape(false), opacity(0.0)
+      : shouldGenerateMaskLayer(false)
+      , shouldGenerateClipMaskLayer(false)
+      , shouldApplyClipPath(false)
+      , shouldApplyBasicShapeOrPath(false)
+      , opacity(0.0)
     { }
 
     bool shouldDoSomething() {
       return shouldGenerateMaskLayer
           || shouldGenerateClipMaskLayer
           || shouldApplyClipPath
-          || shouldApplyBasicShape
+          || shouldApplyBasicShapeOrPath
           || opacity != 1.0;
     }
   };
 
   static void DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity,
                                  MaskUsage& aUsage);
 
   static float ComputeOpacity(nsIFrame* aFrame, bool aHandleOpacity);
--- a/layout/tools/reftest/jar.mn
+++ b/layout/tools/reftest/jar.mn
@@ -3,12 +3,13 @@ reftest.jar:
   content/globals.jsm (globals.jsm)
   content/reftest-content.js (reftest-content.js)
   content/AsyncSpellCheckTestHelper.jsm (../../../editor/AsyncSpellCheckTestHelper.jsm)
   content/httpd.jsm (../../../netwerk/test/httpserver/httpd.js)
   content/StructuredLog.jsm (../../../testing/modules/StructuredLog.jsm)
   content/PerTestCoverageUtils.jsm (../../../tools/code-coverage/PerTestCoverageUtils.jsm)
   content/input.css (../../../editor/reftests/xul/input.css)
   content/moz-bool-pref.css (../../../layout/reftests/css-parsing/moz-bool-pref.css)
+  content/path.css (../../../layout/reftests/svg/svg-integration/clip-path/path.css)
   content/progress.css (../../../layout/reftests/forms/progress/style.css)
 *  content/manifest.jsm (manifest.jsm)
 *  content/reftest.jsm (reftest.jsm)
   content/reftest.xul (reftest.xul)
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt
@@ -42,17 +42,17 @@ class SessionLifecycleTest : BaseSession
         }
         sessionRule.session.reload()
         sessionRule.session.waitForPageStop()
     }
 
     @Test fun open_allowCallsWhileClosed() {
         sessionRule.session.close()
 
-        sessionRule.session.loadUri(HELLO_HTML_PATH)
+        sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.session.reload()
 
         sessionRule.session.open()
         sessionRule.session.waitForPageStops(2)
     }
 
     @Test(expected = IllegalStateException::class)
     fun open_throwOnAlreadyOpen() {
--- a/netwerk/base/RedirectChannelRegistrar.cpp
+++ b/netwerk/base/RedirectChannelRegistrar.cpp
@@ -1,25 +1,50 @@
 /* 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/. */
 
 #include "RedirectChannelRegistrar.h"
+#include "mozilla/StaticPtr.h"
 
 namespace mozilla {
 namespace net {
 
+namespace {
+StaticRefPtr<RedirectChannelRegistrar> gSingleton;
+}
+
 NS_IMPL_ISUPPORTS(RedirectChannelRegistrar, nsIRedirectChannelRegistrar)
 
 RedirectChannelRegistrar::RedirectChannelRegistrar()
   : mRealChannels(32)
   , mParentChannels(32)
   , mId(1)
   , mLock("RedirectChannelRegistrar")
 {
+  MOZ_ASSERT(!gSingleton);
+}
+
+// static
+already_AddRefed<nsIRedirectChannelRegistrar>
+RedirectChannelRegistrar::GetOrCreate()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!gSingleton) {
+    gSingleton = new RedirectChannelRegistrar();
+  }
+  return do_AddRef(gSingleton);
+}
+
+// static
+void
+RedirectChannelRegistrar::Shutdown()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  gSingleton = nullptr;
 }
 
 NS_IMETHODIMP
 RedirectChannelRegistrar::RegisterChannel(nsIChannel *channel,
                                           uint32_t *_retval)
 {
   MutexAutoLock lock(mLock);
 
--- a/netwerk/base/RedirectChannelRegistrar.h
+++ b/netwerk/base/RedirectChannelRegistrar.h
@@ -21,16 +21,23 @@ class RedirectChannelRegistrar final : p
   NS_DECL_ISUPPORTS
   NS_DECL_NSIREDIRECTCHANNELREGISTRAR
 
   RedirectChannelRegistrar();
 
 private:
   ~RedirectChannelRegistrar() = default;
 
+public:
+  // Singleton accessor
+  static already_AddRefed<nsIRedirectChannelRegistrar> GetOrCreate();
+
+  // Cleanup the singleton instance on shutdown
+  static void Shutdown();
+
 protected:
   typedef nsInterfaceHashtable<nsUint32HashKey, nsIChannel>
           ChannelHashtable;
   typedef nsInterfaceHashtable<nsUint32HashKey, nsIParentChannel>
           ParentChannelHashtable;
 
   ChannelHashtable mRealChannels;
   ParentChannelHashtable mParentChannels;
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -171,16 +171,17 @@ EXPORTS.mozilla.net += [
     'ChannelDiverterChild.h',
     'ChannelDiverterParent.h',
     'Dashboard.h',
     'DashboardTypes.h',
     'IOActivityMonitor.h',
     'MemoryDownloader.h',
     'PartiallySeekableInputStream.h',
     'Predictor.h',
+    'RedirectChannelRegistrar.h',
     'ReferrerPolicy.h',
     'SimpleChannelParent.h',
     'TCPFastOpen.h',
 ]
 
 UNIFIED_SOURCES += [
     'ArrayBufferInputStream.cpp',
     'BackgroundFileSaver.cpp',
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -42,17 +42,17 @@
 #include "nsIMutable.h"
 #include "nsINode.h"
 #include "nsIObjectLoadingContent.h"
 #include "nsIOfflineCacheUpdate.h"
 #include "nsIPersistentProperties2.h"
 #include "nsIPrivateBrowsingChannel.h"
 #include "nsIPropertyBag2.h"
 #include "nsIProtocolProxyService.h"
-#include "nsIRedirectChannelRegistrar.h"
+#include "mozilla/net/RedirectChannelRegistrar.h"
 #include "nsIRequestObserverProxy.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsISensitiveInfoHiddenURI.h"
 #include "nsISimpleStreamListener.h"
 #include "nsISocketProvider.h"
 #include "nsISocketProviderService.h"
 #include "nsIStandardURL.h"
 #include "nsIStreamLoader.h"
@@ -2752,21 +2752,19 @@ NS_IsHSTSUpgradeRedirect(nsIChannel *aOl
   return NS_SUCCEEDED(upgradedURI->Equals(newURI, &res)) && res;
 }
 
 nsresult
 NS_LinkRedirectChannels(uint32_t channelId,
                         nsIParentChannel *parentChannel,
                         nsIChannel **_result)
 {
-  nsresult rv;
-
   nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
-      do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
+      RedirectChannelRegistrar::GetOrCreate();
+  MOZ_ASSERT(registrar);
 
   return registrar->LinkChannels(channelId,
                                  parentChannel,
                                  _result);
 }
 
 nsresult NS_MaybeOpenChannelUsingOpen2(nsIChannel* aChannel,
                                        nsIInputStream **aStream)
--- a/netwerk/build/nsNetCID.h
+++ b/netwerk/build/nsNetCID.h
@@ -469,27 +469,16 @@
 
 /**
  * Contract ID for a service implementing nsIURIClassifier that identifies
  * phishing and malware sites.
  */
 #define NS_URICLASSIFIERSERVICE_CONTRACTID \
     "@mozilla.org/uriclassifierservice"
 
-// Redirect channel registrar used for redirect to various protocols
-#define NS_REDIRECTCHANNELREGISTRAR_CONTRACTID \
-    "@mozilla.org/redirectchannelregistrar;1"
-#define NS_REDIRECTCHANNELREGISTRAR_CID \
-{ /* {b69043a6-8929-4d60-8d17-a27e44a8393e} */ \
-    0xb69043a6, \
-    0x8929, \
-    0x4d60, \
-    { 0x8d, 0x17, 0xa2, 0x7e, 0x44, 0xa8, 0x39, 0x3e } \
-}
-
 // service implementing nsINetworkPredictor
 #define NS_NETWORKPREDICTOR_CONTRACTID \
     "@mozilla.org/network/predictor;1"
 #define NS_NETWORKPREDICTOR_CID \
 { /* {969adfdf-7221-4419-aecf-05f8faf00c9b} */ \
     0x969adfdf, \
     0x7221, \
     0x4419, \
--- a/netwerk/build/nsNetModule.cpp
+++ b/netwerk/build/nsNetModule.cpp
@@ -34,16 +34,17 @@
 #include "nsDNSPrefetch.h"
 #include "nsAboutProtocolHandler.h"
 #include "nsXULAppAPI.h"
 #include "nsCategoryCache.h"
 #include "nsIContentSniffer.h"
 #include "Predictor.h"
 #include "nsIThreadPool.h"
 #include "mozilla/net/NeckoChild.h"
+#include "RedirectChannelRegistrar.h"
 
 #include "nsNetCID.h"
 
 #if defined(XP_MACOSX) || defined(XP_WIN) || defined(XP_LINUX)
 #define BUILD_NETWORK_INFO_SERVICE 1
 #endif
 
 typedef nsCategoryCache<nsIContentSniffer> ContentSnifferCache;
@@ -126,20 +127,16 @@ NS_GENERIC_AGGREGATED_CONSTRUCTOR_INIT(n
 NS_GENERIC_FACTORY_CONSTRUCTOR(ArrayBufferInputStream)
 
 #include "nsEffectiveTLDService.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsEffectiveTLDService, Init)
 
 #include "nsSerializationHelper.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSerializationHelper)
 
-#include "RedirectChannelRegistrar.h"
-typedef mozilla::net::RedirectChannelRegistrar RedirectChannelRegistrar;
-NS_GENERIC_FACTORY_CONSTRUCTOR(RedirectChannelRegistrar)
-
 #include "CacheStorageService.h"
 typedef mozilla::net::CacheStorageService CacheStorageService;
 NS_GENERIC_FACTORY_CONSTRUCTOR(CacheStorageService)
 
 #include "LoadContextInfo.h"
 typedef mozilla::net::LoadContextInfoFactory LoadContextInfoFactory;
 NS_GENERIC_FACTORY_CONSTRUCTOR(LoadContextInfoFactory)
 
@@ -647,16 +644,18 @@ static void nsNetShutdown()
     // Release DNS service reference.
     nsDNSPrefetch::Shutdown();
 
     // Release the Websocket Admission Manager
     mozilla::net::WebSocketChannel::Shutdown();
 
     mozilla::net::Http2CompressionCleanup();
 
+    mozilla::net::RedirectChannelRegistrar::Shutdown();
+
     delete gNetSniffers;
     gNetSniffers = nullptr;
     delete gDataSniffers;
     gDataSniffers = nullptr;
 }
 
 NS_DEFINE_NAMED_CID(NS_IOSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_STREAMTRANSPORTSERVICE_CID);
@@ -763,17 +762,16 @@ NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERV
 #elif defined(MOZ_WIDGET_COCOA)
 NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID);
 #elif defined(MOZ_WIDGET_ANDROID)
 NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID);
 #elif defined(XP_LINUX)
 NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_SERIALIZATION_HELPER_CID);
-NS_DEFINE_NAMED_CID(NS_REDIRECTCHANNELREGISTRAR_CID);
 NS_DEFINE_NAMED_CID(NS_CACHE_STORAGE_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_NSILOADCONTEXTINFOFACTORY_CID);
 NS_DEFINE_NAMED_CID(NS_NETWORKPREDICTOR_CID);
 NS_DEFINE_NAMED_CID(NS_CAPTIVEPORTAL_CID);
 NS_DEFINE_NAMED_CID(NS_REQUESTCONTEXTSERVICE_CID);
 #ifdef BUILD_NETWORK_INFO_SERVICE
 NS_DEFINE_NAMED_CID(NETWORKINFOSERVICE_CID);
 #endif // BUILD_NETWORK_INFO_SERVICE
@@ -888,17 +886,16 @@ static const mozilla::Module::CIDEntry k
 #elif defined(MOZ_WIDGET_COCOA)
     { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsNetworkLinkServiceConstructor },
 #elif defined(MOZ_WIDGET_ANDROID)
     { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsAndroidNetworkLinkServiceConstructor },
 #elif defined(XP_LINUX)
     { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsNotifyAddrListenerConstructor },
 #endif
     { &kNS_SERIALIZATION_HELPER_CID, false, nullptr, nsSerializationHelperConstructor },
-    { &kNS_REDIRECTCHANNELREGISTRAR_CID, false, nullptr, RedirectChannelRegistrarConstructor },
     { &kNS_CACHE_STORAGE_SERVICE_CID, false, nullptr, CacheStorageServiceConstructor },
     { &kNS_NSILOADCONTEXTINFOFACTORY_CID, false, nullptr, LoadContextInfoFactoryConstructor },
     { &kNS_NETWORKPREDICTOR_CID, false, nullptr, mozilla::net::Predictor::Create },
     { &kNS_CAPTIVEPORTAL_CID, false, nullptr, mozilla::net::nsICaptivePortalServiceConstructor },
     { &kNS_REQUESTCONTEXTSERVICE_CID, false, nullptr, RequestContextServiceConstructor },
 #ifdef BUILD_NETWORK_INFO_SERVICE
     { &kNETWORKINFOSERVICE_CID, false, nullptr, nsNetworkInfoServiceConstructor },
 #endif
@@ -1012,17 +1009,16 @@ static const mozilla::Module::ContractID
 #elif defined(MOZ_WIDGET_COCOA)
     { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
 #elif defined(MOZ_WIDGET_ANDROID)
     { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
 #elif defined(XP_LINUX)
     { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
 #endif
     { NS_SERIALIZATION_HELPER_CONTRACTID, &kNS_SERIALIZATION_HELPER_CID },
-    { NS_REDIRECTCHANNELREGISTRAR_CONTRACTID, &kNS_REDIRECTCHANNELREGISTRAR_CID },
     { NS_CACHE_STORAGE_SERVICE_CONTRACTID, &kNS_CACHE_STORAGE_SERVICE_CID },
     { NS_CACHE_STORAGE_SERVICE_CONTRACTID2, &kNS_CACHE_STORAGE_SERVICE_CID },
     { NS_NSILOADCONTEXTINFOFACTORY_CONTRACTID, &kNS_NSILOADCONTEXTINFOFACTORY_CID },
     { NS_NETWORKPREDICTOR_CONTRACTID, &kNS_NETWORKPREDICTOR_CID },
     { NS_CAPTIVEPORTAL_CONTRACTID, &kNS_CAPTIVEPORTAL_CID },
     { NS_REQUESTCONTEXTSERVICE_CONTRACTID, &kNS_REQUESTCONTEXTSERVICE_CID },
 #ifdef BUILD_NETWORK_INFO_SERVICE
     { NETWORKINFOSERVICE_CONTRACT_ID, &kNETWORKINFOSERVICE_CID },
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -40,17 +40,17 @@
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "nsICachingChannel.h"
 #include "mozilla/LoadInfo.h"
 #include "nsQueryObject.h"
 #include "mozilla/BasePrincipal.h"
 #include "nsCORSListenerProxy.h"
 #include "nsIIPCSerializableInputStream.h"
 #include "nsIPrompt.h"
-#include "nsIRedirectChannelRegistrar.h"
+#include "mozilla/net/RedirectChannelRegistrar.h"
 #include "nsIWindowWatcher.h"
 #include "nsIDocument.h"
 #include "nsStreamUtils.h"
 #include "nsStringStream.h"
 #include "nsIStorageStream.h"
 #include "nsThreadUtils.h"
 #include "nsQueryObject.h"
 #include "nsIURIClassifier.h"
@@ -967,17 +967,17 @@ HttpChannelParent::RecvRedirect2Verify(c
   // Continue the verification procedure if child has veto the redirection.
   if (NS_FAILED(result)) {
     ContinueRedirect2Verify(result);
     return IPC_OK();
   }
 
   // Wait for background channel ready on target channel
   nsCOMPtr<nsIRedirectChannelRegistrar> redirectReg =
-    do_GetService(NS_REDIRECTCHANNELREGISTRAR_CONTRACTID);
+    RedirectChannelRegistrar::GetOrCreate();
   MOZ_ASSERT(redirectReg);
 
   nsCOMPtr<nsIParentChannel> redirectParentChannel;
   rv = redirectReg->GetParentChannel(mRedirectRegistrarId,
                                      getter_AddRefs(redirectParentChannel));
   MOZ_ASSERT(redirectParentChannel);
   if (!redirectParentChannel) {
     ContinueRedirect2Verify(rv);
--- a/netwerk/protocol/http/HttpChannelParentListener.cpp
+++ b/netwerk/protocol/http/HttpChannelParentListener.cpp
@@ -10,17 +10,17 @@
 #include "HttpChannelParentListener.h"
 #include "mozilla/dom/ServiceWorkerInterceptController.h"
 #include "mozilla/dom/ServiceWorkerUtils.h"
 #include "mozilla/net/HttpChannelParent.h"
 #include "mozilla/Unused.h"
 #include "nsIAuthPrompt.h"
 #include "nsIAuthPrompt2.h"
 #include "nsIHttpHeaderVisitor.h"
-#include "nsIRedirectChannelRegistrar.h"
+#include "mozilla/net/RedirectChannelRegistrar.h"
 #include "nsIPromptFactory.h"
 #include "nsIWindowWatcher.h"
 #include "nsQueryObject.h"
 
 using mozilla::Unused;
 using mozilla::dom::ServiceWorkerInterceptController;
 using mozilla::dom::ServiceWorkerParentInterceptEnabled;
 
@@ -174,18 +174,18 @@ HttpChannelParentListener::AsyncOnChanne
   if (!activeRedirectingChannel) {
     NS_ERROR("Channel got a redirect response, but doesn't implement "
              "nsIParentRedirectingChannel to handle it.");
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   // Register the new channel and obtain id for it
   nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
-      do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
+      RedirectChannelRegistrar::GetOrCreate();
+  MOZ_ASSERT(registrar);
 
   rv = registrar->RegisterChannel(newChannel, &mRedirectChannelId);
   NS_ENSURE_SUCCESS(rv, rv);
 
   LOG(("Registered %p channel under id=%d", newChannel, mRedirectChannelId));
 
   return activeRedirectingChannel->StartRedirect(mRedirectChannelId,
                                                  newChannel,
@@ -203,18 +203,18 @@ HttpChannelParentListener::OnRedirectRes
   LOG(("HttpChannelParentListener::OnRedirectResult [this=%p, suc=%d]",
        this, succeeded));
 
   nsresult rv;
 
   nsCOMPtr<nsIParentChannel> redirectChannel;
   if (mRedirectChannelId) {
     nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
-        do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
+        RedirectChannelRegistrar::GetOrCreate();
+    MOZ_ASSERT(registrar);
 
     rv = registrar->GetParentChannel(mRedirectChannelId,
                                      getter_AddRefs(redirectChannel));
     if (NS_FAILED(rv) || !redirectChannel) {
       // Redirect might get canceled before we got AsyncOnChannelRedirect
       LOG(("Registered parent channel not found under id=%d", mRedirectChannelId));
 
       nsCOMPtr<nsIChannel> newChannel;
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -331,16 +331,17 @@ nsHttpChannel::nsHttpChannel()
     , mConcurrentCacheAccess(0)
     , mIsPartialRequest(0)
     , mHasAutoRedirectVetoNotifier(0)
     , mPinCacheContent(0)
     , mIsCorsPreflightDone(0)
     , mStronglyFramed(false)
     , mUsedNetwork(0)
     , mAuthConnectionRestartable(0)
+    , mTrackingProtectionCancellationPending(0)
     , mPushedStream(nullptr)
     , mLocalBlocklist(false)
     , mOnTailUnblock(nullptr)
     , mWarningReporter(nullptr)
     , mIsReadingFromCache(false)
     , mFirstResponseSource(RESPONSE_PENDING)
     , mRaceCacheWithNetwork(false)
     , mRaceDelay(0)
@@ -6053,20 +6054,28 @@ nsHttpChannel::CancelForTrackingProtecti
     // Check if request was cancelled during on-modify-request or on-useragent.
     if (mCanceled) {
         return mStatus;
     }
 
     if (mSuspendCount) {
         LOG(("Waiting until resume in Cancel [this=%p]\n", this));
         MOZ_ASSERT(!mCallOnResume);
+        mTrackingProtectionCancellationPending = 1;
         mCallOnResume = &nsHttpChannel::HandleContinueCancelledByTrackingProtection;
         return NS_OK;
     }
 
+    // Check to see if we should redirect this channel elsewhere by
+    // nsIHttpChannel.redirectTo API request
+    if (mAPIRedirectToURI) {
+        mTrackingProtectionCancellationPending = 1;
+        return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
+    }
+
     return CancelInternal(NS_ERROR_TRACKING_URI);
 }
 
 void
 nsHttpChannel::ContinueCancelledByTrackingProtection()
 {
     MOZ_ASSERT(NS_IsMainThread());
     // We should never have a pump open while a CORS preflight is in progress.
@@ -6074,22 +6083,35 @@ nsHttpChannel::ContinueCancelledByTracki
 
     LOG(("nsHttpChannel::ContinueCancelledByTrackingProtection [this=%p]\n",
          this));
     if (mCanceled) {
         LOG(("  ignoring; already canceled\n"));
         return;
     }
 
+    // Check to see if we should redirect this channel elsewhere by
+    // nsIHttpChannel.redirectTo API request
+    if (mAPIRedirectToURI) {
+        Unused << AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
+        return;
+    }
+
     Unused << CancelInternal(NS_ERROR_TRACKING_URI);
 }
 
 nsresult
 nsHttpChannel::CancelInternal(nsresult status)
 {
+    bool trackingProtectionCancellationPending =
+      !!mTrackingProtectionCancellationPending;
+    if (status == NS_ERROR_TRACKING_URI) {
+      mTrackingProtectionCancellationPending = 0;
+    }
+
     mCanceled = true;
     mStatus = status;
     if (mProxyRequest)
         mProxyRequest->Cancel(status);
     CancelNetworkRequest(status);
     mCacheInputStream.CloseAndRelease();
     if (mCachePump)
         mCachePump->Cancel(status);
@@ -6097,16 +6119,20 @@ nsHttpChannel::CancelInternal(nsresult s
         mAuthProvider->Cancel(status);
     if (mPreflightChannel)
         mPreflightChannel->Cancel(status);
     if (mRequestContext && mOnTailUnblock) {
         mOnTailUnblock = nullptr;
         mRequestContext->CancelTailedRequest(this);
         CloseCacheEntry(false);
         Unused << AsyncAbort(status);
+    } else if (trackingProtectionCancellationPending) {
+        // If we're coming from an asynchronous path when canceling a channel due
+        // to tracking protection, we need to AsyncAbort the channel now.
+        Unused << AsyncAbort(status);
     }
     return NS_OK;
 }
 
 void
 nsHttpChannel::CancelNetworkRequest(nsresult aStatus)
 {
     if (mTransaction) {
@@ -6558,16 +6584,24 @@ nsHttpChannel::BeginConnect()
 
 nsresult
 nsHttpChannel::BeginConnectActual()
 {
     if (mCanceled) {
         return mStatus;
     }
 
+    if (mTrackingProtectionCancellationPending) {
+        LOG(("Waiting for tracking protection cancellation in BeginConnectActual [this=%p]\n", this));
+        MOZ_ASSERT(!mCallOnResume ||
+                   mCallOnResume == &nsHttpChannel::HandleContinueCancelledByTrackingProtection,
+                   "We should be paused waiting for cancellation from tracking protection");
+        return NS_OK;
+    }
+
     if (!mConnectionInfo->UsingHttpProxy() &&
         !(mLoadFlags & (LOAD_NO_NETWORK_IO | LOAD_ONLY_FROM_CACHE))) {
         // Start a DNS lookup very early in case the real open is queued the DNS can
         // happen in parallel. Do not do so in the presence of an HTTP proxy as
         // all lookups other than for the proxy itself are done by the proxy.
         // Also we don't do a lookup if the LOAD_NO_NETWORK_IO or
         // LOAD_ONLY_FROM_CACHE flags are set.
         //
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -623,16 +623,21 @@ private:
     uint32_t                          mStronglyFramed : 1;
 
     // true if an HTTP transaction is created for the socket thread
     uint32_t                          mUsedNetwork : 1;
 
     // the next authentication request can be sent on a whole new connection
     uint32_t                          mAuthConnectionRestartable : 1;
 
+    // True if the channel classifier has marked the channel to be cancelled
+    // due to the tracking protection rules, but the asynchronous cancellation
+    // process hasn't finished yet.
+    uint32_t                          mTrackingProtectionCancellationPending : 1;
+
     nsTArray<nsContinueRedirectionFunc> mRedirectFuncStack;
 
     // Needed for accurate DNS timing
     RefPtr<nsDNSPrefetch>           mDNSPrefetch;
 
     Http2PushedStream                 *mPushedStream;
     // True if the channel's principal was found on a phishing, malware, or
     // tracking (if tracking protection is enabled) blocklist
--- a/python/mozbuild/mozbuild/compilation/database.py
+++ b/python/mozbuild/mozbuild/compilation/database.py
@@ -121,16 +121,22 @@ class CompileDBBackend(CommonBackend):
 
         import json
         # Output the database (a JSON file) to objdir/compile_commands.json
         outputfile = os.path.join(self.environment.topobjdir, 'compile_commands.json')
         with self._write_file(outputfile) as jsonout:
             json.dump(db, jsonout, indent=0)
 
     def _process_unified_sources(self, obj):
+        if not obj.have_unified_mapping:
+            for f in list(sorted(obj.files)):
+                self._build_db_line(obj.objdir, obj.relsrcdir, obj.config, f,
+                                    obj.canonical_suffix)
+            return
+
         # For unified sources, only include the unified source file.
         # Note that unified sources are never used for host sources.
         for f in obj.unified_source_mapping:
             self._build_db_line(obj.objdir, obj.relsrcdir, obj.config, f[0],
                                 obj.canonical_suffix)
             for entry in f[1]:
                 self._build_db_line(obj.objdir, obj.relsrcdir, obj.config,
                                     entry, obj.canonical_suffix, unified=f[0])
--- a/servo/components/style/gecko/conversions.rs
+++ b/servo/components/style/gecko/conversions.rs
@@ -665,20 +665,21 @@ pub mod basic_shape {
     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, PolygonCoord};
+    use values::generics::basic_shape::{Circle, Ellipse, FillRule, Path, PolygonCoord};
     use values::generics::basic_shape::{GeometryBox, ShapeBox, ShapeSource};
     use values::generics::border::BorderRadius as GenericBorderRadius;
     use values::generics::rect::Rect;
+    use values::specified::SVGPathData;
 
     impl StyleShapeSource {
         /// Convert StyleShapeSource to ShapeSource except URL and Image
         /// types.
         fn into_shape_source<ReferenceBox, ImageOrUrl>(
             &self,
         ) -> Option<ShapeSource<BasicShape, ReferenceBox, ImageOrUrl>>
         where
@@ -693,17 +694,44 @@ 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,
+                StyleShapeSourceType::Path => {
+                    let path = self.to_svg_path().expect("expect an SVGPathData");
+                    let gecko_path = unsafe { &*self.__bindgen_anon_1.mSVGPath.as_ref().mPtr };
+                    let fill = if gecko_path.mFillRule == StyleFillRule::Evenodd {
+                        FillRule::Evenodd
+                    } else {
+                        FillRule::Nonzero
+                    };
+                    Some(ShapeSource::Path(Path { fill, path }))
+                },
+            }
+        }
+
+        /// Generate a SVGPathData from StyleShapeSource if possible.
+        fn to_svg_path(&self) -> Option<SVGPathData> {
+            use gecko_bindings::structs::StylePathCommand;
+            use values::specified::svg_path::PathCommand;
+            match self.mType {
+                StyleShapeSourceType::Path => {
+                    let gecko_path = unsafe { &*self.__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();
+                    Some(SVGPathData::new(result.into_boxed_slice()))
+                },
+                _ => None,
             }
         }
     }
 
     impl<'a> From<&'a StyleShapeSource> for ClippingShape {
         fn from(other: &'a StyleShapeSource) -> Self {
             match other.mType {
                 StyleShapeSourceType::URL => unsafe {
@@ -737,27 +765,19 @@ pub mod basic_shape {
                     .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()))
+                    OffsetPath::Path(other.to_svg_path().expect("Cannot convert to SVGPathData"))
                 },
                 StyleShapeSourceType::None => OffsetPath::none(),
                 StyleShapeSourceType::Shape |
                 StyleShapeSourceType::Box |
                 StyleShapeSourceType::URL |
                 StyleShapeSourceType::Image => unreachable!("Unsupported offset-path type"),
             }
         }
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -3678,37 +3678,26 @@ 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::bindings::{Gecko_NewStyleMotion, Gecko_SetStyleMotion};
         use gecko_bindings::structs::StyleShapeSourceType;
+        use values::generics::basic_shape::FillRule;
         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) };
-                }
+            OffsetPath::Path(p) => {
+                set_style_svg_path(&mut motion.mOffsetPath, &p, FillRule::Nonzero)
             },
         }
         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() } {
@@ -4977,16 +4966,45 @@ fn static_assert() {
     }
 
     #[inline]
     pub fn has_line_through(&self) -> bool {
         (self.gecko.mTextDecorationLine & (structs::NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH as u8)) != 0
     }
 </%self:impl_trait>
 
+// Set SVGPathData to StyleShapeSource.
+fn set_style_svg_path(
+    shape_source: &mut structs::mozilla::StyleShapeSource,
+    servo_path: &values::specified::svg_path::SVGPathData,
+    fill: values::generics::basic_shape::FillRule,
+) {
+    use gecko_bindings::bindings::Gecko_NewStyleSVGPath;
+    use gecko_bindings::structs::StyleShapeSourceType;
+
+    // Setup type.
+    shape_source.mType = StyleShapeSourceType::Path;
+
+    // Setup path.
+    let gecko_path = unsafe {
+        Gecko_NewStyleSVGPath(shape_source);
+        &mut shape_source.__bindgen_anon_1.mSVGPath.as_mut().mPtr.as_mut().unwrap()
+    };
+    unsafe { gecko_path.mPath.set_len(servo_path.commands().len() as u32) };
+    debug_assert_eq!(gecko_path.mPath.len(), servo_path.commands().len());
+    for (servo, gecko) in servo_path.commands().iter().zip(gecko_path.mPath.iter_mut()) {
+        // unsafe: cbindgen ensures the representation is the same.
+        *gecko = unsafe { transmute(*servo) };
+    }
+
+    // Setup fill-rule.
+    // unsafe: cbindgen ensures the representation is the same.
+    gecko_path.mFillRule = unsafe { transmute(fill) };
+}
+
 <%def name="impl_shape_source(ident, gecko_ffi_name)">
     pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
         use gecko_bindings::bindings::{Gecko_NewBasicShape, Gecko_DestroyShapeSource};
         use gecko_bindings::structs::{StyleBasicShape, StyleBasicShapeType, StyleShapeSourceType};
         use gecko_bindings::structs::{StyleGeometryBox, StyleShapeSource};
         use gecko::conversions::basic_shape::set_corners_from_radius;
         use gecko::values::GeckoStyleCoordConvertible;
         use values::generics::basic_shape::{BasicShape, ShapeSource};
@@ -5016,16 +5034,17 @@ fn static_assert() {
                <% raise Exception("Unknown property: %s" % ident) %>
             }
             % endif
             ShapeSource::None => {} // don't change the type
             ShapeSource::Box(reference) => {
                 ${ident}.mReferenceBox = reference.into();
                 ${ident}.mType = StyleShapeSourceType::Box;
             }
+            ShapeSource::Path(p) => set_style_svg_path(${ident}, &p.path, p.fill),
             ShapeSource::Shape(servo_shape, maybe_box) => {
                 fn init_shape(${ident}: &mut StyleShapeSource, basic_shape_type: StyleBasicShapeType)
                               -> &mut StyleBasicShape {
                     unsafe {
                         // Create StyleBasicShape in StyleShapeSource. mReferenceBox and mType
                         // will be set manually later.
                         Gecko_NewBasicShape(${ident}, basic_shape_type);
                         &mut *${ident}.__bindgen_anon_1.mBasicShape.as_mut().mPtr
--- a/servo/components/style/values/generics/basic_shape.rs
+++ b/servo/components/style/values/generics/basic_shape.rs
@@ -7,16 +7,17 @@
 
 use std::fmt::{self, Write};
 use style_traits::{CssWriter, ToCss};
 use values::animated::{Animate, Procedure, ToAnimatedZero};
 use values::distance::{ComputeSquaredDistance, SquaredDistance};
 use values::generics::border::BorderRadius;
 use values::generics::position::Position;
 use values::generics::rect::Rect;
+use values::specified::SVGPathData;
 
 /// A clipping shape, for `clip-path`.
 pub type ClippingShape<BasicShape, Url> = ShapeSource<BasicShape, GeometryBox, Url>;
 
 /// <https://drafts.fxtf.org/css-masking-1/#typedef-geometry-box>
 #[allow(missing_docs)]
 #[derive(Animate, Clone, Copy, Debug, MallocSizeOf, PartialEq,
          SpecifiedValueInfo, ToComputedValue, ToCss)]
@@ -49,16 +50,19 @@ pub enum ShapeBox {
          ToComputedValue, ToCss)]
 pub enum ShapeSource<BasicShape, ReferenceBox, ImageOrUrl> {
     #[animation(error)]
     ImageOrUrl(ImageOrUrl),
     Shape(BasicShape, Option<ReferenceBox>),
     #[animation(error)]
     Box(ReferenceBox),
     #[animation(error)]
+    #[css(function)]
+    Path(Path),
+    #[animation(error)]
     None,
 }
 
 #[allow(missing_docs)]
 #[derive(Animate, Clone, ComputeSquaredDistance, Debug, MallocSizeOf, PartialEq,
          SpecifiedValueInfo, ToComputedValue, ToCss)]
 pub enum BasicShape<H, V, LengthOrPercentage> {
     Inset(#[css(field_bound)] InsetRect<LengthOrPercentage>),
@@ -139,16 +143,29 @@ pub struct PolygonCoord<LengthOrPercenta
 #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq,
          SpecifiedValueInfo, ToComputedValue, ToCss)]
 #[repr(u8)]
 pub enum FillRule {
     Nonzero,
     Evenodd,
 }
 
+/// The path function defined in css-shape-2.
+///
+/// https://drafts.csswg.org/css-shapes-2/#funcdef-path
+#[css(comma)]
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss)]
+pub struct Path {
+    /// The filling rule for the svg path.
+    #[css(skip_if = "fill_is_default")]
+    pub fill: FillRule,
+    /// The svg path data.
+    pub path: SVGPathData,
+}
+
 // FIXME(nox): Implement ComputeSquaredDistance for T types and stop
 // using PartialEq here, this will let us derive this impl.
 impl<B, T, U> ComputeSquaredDistance for ShapeSource<B, T, U>
 where
     B: ComputeSquaredDistance,
     T: PartialEq,
 {
     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
--- a/servo/components/style/values/specified/basic_shape.rs
+++ b/servo/components/style/values/specified/basic_shape.rs
@@ -9,19 +9,21 @@
 
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
 use std::borrow::Cow;
 use std::fmt::{self, Write};
 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
 use values::computed::Percentage;
 use values::generics::basic_shape as generic;
-use values::generics::basic_shape::{FillRule, GeometryBox, PolygonCoord, ShapeBox, ShapeSource};
+use values::generics::basic_shape::{FillRule, GeometryBox, Path, PolygonCoord};
+use values::generics::basic_shape::{ShapeBox, ShapeSource};
 use values::generics::rect::Rect;
 use values::specified::LengthOrPercentage;
+use values::specified::SVGPathData;
 use values::specified::border::BorderRadius;
 use values::specified::image::Image;
 use values::specified::position::{HorizontalPosition, Position, PositionComponent};
 use values::specified::position::{Side, VerticalPosition};
 use values::specified::url::SpecifiedUrl;
 
 /// A specified clipping shape.
 pub type ClippingShape = generic::ClippingShape<BasicShape, SpecifiedUrl>;
@@ -42,22 +44,52 @@ pub type Circle = generic::Circle<Horizo
 pub type Ellipse = generic::Ellipse<HorizontalPosition, VerticalPosition, LengthOrPercentage>;
 
 /// The specified value of `ShapeRadius`
 pub type ShapeRadius = generic::ShapeRadius<LengthOrPercentage>;
 
 /// The specified value of `Polygon`
 pub type Polygon = generic::Polygon<LengthOrPercentage>;
 
-impl<ReferenceBox, ImageOrUrl> Parse for ShapeSource<BasicShape, ReferenceBox, ImageOrUrl>
+impl Parse for ClippingShape {
+    #[inline]
+    fn parse<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Self, ParseError<'i>> {
+        // |clip-path:path()| is a chrome-only property value support for now. `path()` is
+        // defined in css-shape-2, but the spec is not stable enough, and we haven't decided
+        // to make it public yet. However, it has some benefits for the front-end, so we
+        // implement it.
+        if context.chrome_rules_enabled() {
+            if let Ok(p) = input.try(|i| Path::parse(context, i)) {
+                return Ok(ShapeSource::Path(p));
+            }
+        }
+        Self::parse_internal(context, input)
+    }
+}
+
+impl Parse for FloatAreaShape {
+    #[inline]
+    fn parse<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Self, ParseError<'i>> {
+        Self::parse_internal(context, input)
+    }
+}
+
+impl<ReferenceBox, ImageOrUrl> ShapeSource<BasicShape, ReferenceBox, ImageOrUrl>
 where
     ReferenceBox: Parse,
     ImageOrUrl: Parse,
 {
-    fn parse<'i, 't>(
+    /// The internal parser for ShapeSource.
+    fn parse_internal<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
     ) -> Result<Self, ParseError<'i>> {
         if input.try(|i| i.expect_ident_matching("none")).is_ok() {
             return Ok(ShapeSource::None);
         }
 
         if let Ok(image_or_url) = input.try(|i| ImageOrUrl::parse(context, i)) {
@@ -388,8 +420,34 @@ impl Polygon {
         })?;
 
         Ok(Polygon {
             fill: fill,
             coordinates: buf,
         })
     }
 }
+
+impl Parse for Path {
+    fn parse<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Self, ParseError<'i>> {
+        input.expect_function_matching("path")?;
+        input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
+    }
+}
+
+impl Path {
+    /// Parse the inner arguments of a `path` function.
+    fn parse_function_arguments<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Self, ParseError<'i>> {
+        let fill = input.try(|i| -> Result<_, ParseError> {
+            let fill = FillRule::parse(i)?;
+            i.expect_comma()?;
+            Ok(fill)
+        }).unwrap_or_default();
+        let path = SVGPathData::parse(context, input)?;
+        Ok(Path { fill, path })
+    }
+}
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -63,16 +63,17 @@ 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};
 pub use self::svg::MozContextProperties;
+pub use self::svg_path::SVGPathData;
 pub use self::table::XSpan;
 pub use self::text::{InitialLetter, LetterSpacing, LineHeight, MozTabSize, TextAlign};
 pub use self::text::{TextEmphasisPosition, TextEmphasisStyle};
 pub use self::text::{TextAlignKeyword, TextDecorationLine, TextOverflow, WordSpacing};
 pub use self::time::Time;
 pub use self::transform::{Rotate, Scale, TimingFunction, Transform};
 pub use self::transform::{TransformOrigin, TransformStyle, Translate};
 pub use self::ui::{ColorOrAuto, Cursor, MozForceBrokenImageIcon};
@@ -104,16 +105,17 @@ 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 svg_path;
 pub mod table;
 pub mod text;
 pub mod time;
 pub mod transform;
 pub mod ui;
 pub mod url;
 
 /// Parse a `<number>` value, with a given clamping mode.
--- a/servo/components/style/values/specified/motion.rs
+++ b/servo/components/style/values/specified/motion.rs
@@ -1,22 +1,18 @@
 /* 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;
+use style_traits::{ParseError, StyleParseErrorKind};
+use values::specified::SVGPathData;
 
 /// 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.
@@ -58,501 +54,8 @@ impl Parse for OffsetPath {
                     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)
-}
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/specified/svg_path.rs
@@ -0,0 +1,517 @@
+/* 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 SVG 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 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::new(path_string);
+        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)
+    }
+}
+
+
+/// 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> {
+    /// Return a PathParser.
+    #[inline]
+    fn new(string: &'a str) -> Self {
+        PathParser {
+            chars: string.chars().peekable(),
+            path: Vec::new(),
+        }
+    }
+
+    /// 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
+        ])
+    }
+}
+
+
+/// 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/taskcluster/ci/valgrind/kind.yml
+++ b/taskcluster/ci/valgrind/kind.yml
@@ -26,17 +26,16 @@ jobs:
             kind: build
             tier: 1
         worker-type: aws-provisioner-v1/gecko-{level}-b-linux
         worker:
             docker-image: {in-tree: valgrind-build}
             max-run-time: 72000
             env:
                 PERFHERDER_EXTRA_OPTIONS: valgrind
-                FORCE_GCC: '1'
         run:
             using: mozharness
             actions: [get-secrets build valgrind-test]
             custom-build-variant-cfg: valgrind
             config:
                 - builds/releng_base_firefox.py
                 - builds/releng_base_linux_64_builds.py
             script: "mozharness/scripts/fx_desktop_build.py"
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py
@@ -162,19 +162,25 @@ class TestCapabilityMatching(MarionetteT
                 self.marionette.start_session({"setWindowRect": False})
         else:
             self.marionette.start_session({"setWindowRect": False})
             self.delete_session()
             with self.assertRaisesRegexp(SessionNotCreatedException, "InvalidArgumentError"):
                 self.marionette.start_session({"setWindowRect": True})
 
     def test_timeouts(self):
-        timeouts = {u"implicit": 123, u"pageLoad": 456, u"script": 789}
-        caps = {"timeouts": timeouts}
-        self.marionette.start_session(caps)
+        for value in ["", 2.5, {}, []]:
+            print("  type {}".format(type(value)))
+            with self.assertRaises(SessionNotCreatedException):
+                self.marionette.start_session({"timeouts": {"pageLoad": value}})
+
+        self.delete_session()
+
+        timeouts = {"implicit": 0, "pageLoad": 2.0, "script": 2**53 - 1}
+        self.marionette.start_session({"timeouts": timeouts})
         self.assertIn("timeouts", self.marionette.session_capabilities)
         self.assertDictEqual(self.marionette.session_capabilities["timeouts"], timeouts)
         self.assertDictEqual(self.marionette._send_message("WebDriver:GetTimeouts"), timeouts)
 
     def test_unhandled_prompt_behavior(self):
         behaviors = [
             "accept",
             "accept and notify",
--- a/testing/marionette/test/unit/test_capabilities.js
+++ b/testing/marionette/test/unit/test_capabilities.js
@@ -35,60 +35,53 @@ add_test(function test_Timeouts_toJSON()
   let ts = new Timeouts();
   deepEqual(ts.toJSON(), {"implicit": 0, "pageLoad": 300000, "script": 30000});
 
   run_next_test();
 });
 
 add_test(function test_Timeouts_fromJSON() {
   let json = {
-    implicit: 10,
-    pageLoad: 20,
-    script: 30,
+    implicit: 0,
+    pageLoad: 2.0,
+    script: Number.MAX_SAFE_INTEGER,
   };
   let ts = Timeouts.fromJSON(json);
   equal(ts.implicit, json.implicit);
   equal(ts.pageLoad, json.pageLoad);
   equal(ts.script, json.script);
 
   run_next_test();
 });
 
 add_test(function test_Timeouts_fromJSON_unrecognised_field() {
   let json = {
     sessionId: "foobar",
-    script: 42,
   };
   try {
     Timeouts.fromJSON(json);
   } catch (e) {
     equal(e.name, InvalidArgumentError.name);
     equal(e.message, "Unrecognised timeout: sessionId");
   }
 
   run_next_test();
 });
 
-add_test(function test_Timeouts_fromJSON_invalid_type() {
-  try {
-    Timeouts.fromJSON({script: "foobar"});
-  } catch (e) {
-    equal(e.name, InvalidArgumentError.name);
-    equal(e.message, "Expected [object String] \"script\" to be a positive integer, got [object String] \"foobar\"");
+add_test(function test_Timeouts_fromJSON_invalid_types() {
+  for (let value of [null, [], {}, false, "10", 2.5]) {
+    Assert.throws(() => Timeouts.fromJSON({"script": value}), /InvalidArgumentError/);
   }
 
   run_next_test();
 });
 
 add_test(function test_Timeouts_fromJSON_bounds() {
-  try {
-    Timeouts.fromJSON({script: -42});
-  } catch (e) {
-    equal(e.name, InvalidArgumentError.name);
-    equal(e.message, "Expected [object String] \"script\" to be a positive integer, got [object Number] -42");
+  for (let value of [-1, Number.MAX_SAFE_INTEGER + 1]) {
+    Assert.throws(() => Timeouts.fromJSON({"script": value}), /InvalidArgumentError/);
   }
 
   run_next_test();
 });
 
 add_test(function test_PageLoadStrategy() {
   equal(PageLoadStrategy.None, "none");
   equal(PageLoadStrategy.Eager, "eager");
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -300273,21 +300273,16 @@
      {}
     ]
    ],
    "service-workers/service-worker/resources/update-recovery-worker.py": [
     [
      {}
     ]
    ],
-   "service-workers/service-worker/resources/update-top-level-worker.py": [
-    [
-     {}
-    ]
-   ],
    "service-workers/service-worker/resources/update-worker.py": [
     [
      {}
     ]
    ],
    "service-workers/service-worker/resources/update/update-after-oneday.https.html": [
     [
      {}
@@ -390577,22 +390572,16 @@
     ]
    ],
    "service-workers/service-worker/update-result.https.html": [
     [
      "/service-workers/service-worker/update-result.https.html",
      {}
     ]
    ],
-   "service-workers/service-worker/update-top-level.https.html": [
-    [
-     "/service-workers/service-worker/update-top-level.https.html",
-     {}
-    ]
-   ],
    "service-workers/service-worker/update.https.html": [
     [
      "/service-workers/service-worker/update.https.html",
      {}
     ]
    ],
    "service-workers/service-worker/waiting.https.html": [
     [
@@ -423115,16 +423104,24 @@
     ]
    ],
    "webdriver/tests/set_timeouts/set.py": [
     [
      "/webdriver/tests/set_timeouts/set.py",
      {}
     ]
    ],
+   "webdriver/tests/set_timeouts/user_prompts.py": [
+    [
+     "/webdriver/tests/set_timeouts/user_prompts.py",
+     {
+      "timeout": "long"
+     }
+    ]
+   ],
    "webdriver/tests/set_window_rect/set.py": [
     [
      "/webdriver/tests/set_window_rect/set.py",
      {
       "timeout": "long"
      }
     ]
    ],
@@ -559719,17 +559716,17 @@
    "2151f3b0c5dd883ffd2e04157726474eff0a5c2b",
    "support"
   ],
   "css/css-values/reference/ic-unit-002-ref.html": [
    "962a748d2c059b45fca4d7788b04b77c61e0b923",
    "support"
   ],
   "css/css-values/reference/vh_not_refreshing_on_chrome-ref.html": [
-   "32ce9ada155649e9a4935e1abdddc3d3aadfca73",
+   "279d1c69b9f7fbe60edb55b7e8a5d507eda24936",
    "support"
   ],
   "css/css-values/reference/vh_not_refreshing_on_chrome_iframe-ref.html": [
    "5e35f6261e266be2981ae79038e464ac354cc8ef",
    "support"
   ],
   "css/css-values/support/1x1-green.png": [
    "b98ca0ba0a03c580ac339e4a3653539cfa8edc71",
@@ -559911,17 +559908,17 @@
    "ce3d4f0c68e1fef0972eb15fa8174cb394cc901c",
    "support"
   ],
   "css/css-values/support/vh-support-transform-translate-iframe.html": [
    "f0b1b54c12d35da74bf19849612574a20339fe6a",
    "support"
   ],
   "css/css-values/support/vh_not_refreshing_on_chrome_iframe.html": [
-   "c58ec57a58f29d4774b199ca82dd6d16607d4735",
+   "8d8e9b49d4aa3d9804a176852288e32ccaaa47d8",
    "support"
   ],
   "css/css-values/unset-value-storage.html": [
    "5869e9e6072910301ba41746f03eaf297079d98e",
    "testharness"
   ],
   "css/css-values/urls/empty.html": [
    "3ab7079396c517d7abd6334b0cfadf7eda471115",
@@ -559979,17 +559976,17 @@
    "c65b5493feabb40950b8dab27d0f86ba48bd9bb6",
    "support"
   ],
   "css/css-values/vh-zero-support.html": [
    "1c1bcd1761a2ffccfc1a92a10ddf860712606fa1",
    "support"
   ],
   "css/css-values/vh_not_refreshing_on_chrome.html": [
-   "b4e0a413ab975dea429ca35642dc075a54127117",
+   "52a45a114c85bf96a175ca583d8a2b6f54b9ab6c",
    "reftest"
   ],
   "css/css-values/viewport-relative-lengths-scaled-viewport.html": [
    "a901000394d35eed94237b3ce5acc74b1b0e7bc9",
    "testharness"
   ],
   "css/css-values/viewport-units-css2-001.html": [
    "f0feb6d993537833569bd739e4d903b8e510881b",
@@ -632490,20 +632487,16 @@
   "service-workers/service-worker/resources/update-nocookie-worker.py": [
    "0f09b7e32c03c3724cbb55e88f19c0b9b4d78db7",
    "support"
   ],
   "service-workers/service-worker/resources/update-recovery-worker.py": [
    "8aaa5ca934457714ee0e529ad4b2b1740d9758dd",
    "support"
   ],
-  "service-workers/service-worker/resources/update-top-level-worker.py": [
-   "f77ef284ac0745bd6d31e642742438766f14e32e",
-   "support"
-  ],
   "service-workers/service-worker/resources/update-worker.py": [
    "bc9b32ad3e68870d9f540524e70cd7947346e5c8",
    "support"
   ],
   "service-workers/service-worker/resources/update/update-after-oneday.https.html": [
    "9d4c98272187e52947e710189c9fa1cfafbf4b22",
    "support"
   ],
@@ -632674,20 +632667,16 @@
   "service-workers/service-worker/update-recovery.https.html": [
    "3b3d955b142bff67c3d2c2a2d1742888c661c69d",
    "testharness"
   ],
   "service-workers/service-worker/update-result.https.html": [
    "d8ed94f776650c8a40ba82df9ca5e909b460bb79",
    "testharness"
   ],
-  "service-workers/service-worker/update-top-level.https.html": [
-   "e382028b44a9d19b26b3c15a3bba17fa6a0d9bcb",
-   "testharness"
-  ],
   "service-workers/service-worker/update.https.html": [
    "6717d4d7ac289c8a18b1500e21795fd16c5321e7",
    "testharness"
   ],
   "service-workers/service-worker/waiting.https.html": [
    "499e581eb353a2e433580e413a730bea2a9b7ad9",
    "testharness"
   ],
@@ -648599,17 +648588,17 @@
    "f00f6042b085dc09876f67ae650ff62ceb3eb76a",
    "wdspec"
   ],
   "webdriver/tests/new_session/default_values.py": [
    "61816812eb1e79e4cc96190e09e597a878bdebee",
    "wdspec"
   ],
   "webdriver/tests/new_session/invalid_capabilities.py": [
-   "83f93ea22f7ed28fa28ab05d36387df828716026",
+   "f31ce3b8b6fd5f8e4a9ff4d0137debdb7dacdea4",
    "wdspec"
   ],
   "webdriver/tests/new_session/merge.py": [
    "857d289fcaf054492e17ba730c6f530d55fe2640",
    "wdspec"
   ],
   "webdriver/tests/new_session/page_load_strategy.py": [
    "69288ef43335605e4c9d21cfa56bd2806f6e92b0",
@@ -648623,17 +648612,17 @@
    "3e8520718237e6d74fe368d68c4a7bf2ead08c9e",
    "wdspec"
   ],
   "webdriver/tests/new_session/support/__init__.py": [
    "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
    "support"
   ],
   "webdriver/tests/new_session/support/create.py": [
-   "85ae1cd4ea85e0a1e0d712b1a7803d6066ab8739",
+   "475fe5a424fe609f0a7e55164e56378e229e4885",
    "support"
   ],
   "webdriver/tests/new_session/timeouts.py": [
    "c5adb9396819beb03d4627ef9890958fb687b58d",
    "wdspec"
   ],
   "webdriver/tests/page_source/__init__.py": [
    "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
@@ -648663,17 +648652,21 @@
    "edc37d6edb483c232401676f6c11ab7512774605",
    "wdspec"
   ],
   "webdriver/tests/set_timeouts/__init__.py": [
    "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
    "support"
   ],
   "webdriver/tests/set_timeouts/set.py": [
-   "e603e217ec7d73bf7bc59f1d2e8687a89c818c47",
+   "a78ab2e68e82ba28c15748bb98239b3d232dc9f1",
+   "wdspec"
+  ],
+  "webdriver/tests/set_timeouts/user_prompts.py": [
+   "a98d87e9b2e2ca252a3ed7cf215a20bd1c299818",
    "wdspec"
   ],
   "webdriver/tests/set_window_rect/__init__.py": [
    "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
    "support"
   ],
   "webdriver/tests/set_window_rect/set.py": [
    "928fd622efc22f520f4c6a9f783f60ec270146e2",
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/rendering/non-replaced-elements/the-fieldset-element-0/legend-position-relative.html.ini
@@ -0,0 +1,3 @@
+[legend-position-relative.html]
+  expected:
+    if webrender: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/webdriver/tests/maximize_window/user_prompts.py.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[user_prompts.py]
-  disabled:
-    if os == "linux": https://bugzilla.mozilla.org/show_bug.cgi?id=1399633
deleted file mode 100644
--- a/testing/web-platform/meta/webdriver/tests/new_session/create_alwaysMatch.py.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[create_alwaysMatch.py]
-  [test_valid[timeouts-value10\]]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/webdriver/tests/new_session/create_firstMatch.py.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[create_firstMatch.py]
-  [test_valid[timeouts-value10\]]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/tests/service-workers/service-worker/resources/update-top-level-worker.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import time
-
-def main(request, response):
-    # no-cache itself to ensure the user agent finds a new version for each update.
-    headers = [('Cache-Control', 'no-cache, must-revalidate'),
-               ('Pragma', 'no-cache')]
-    content_type = 'application/javascript'
-
-    headers.append(('Content-Type', content_type))
-
-    body = '''
-let promise = self.registration.update()
-onmessage = (evt) => {
-  promise.then(r => {
-    evt.source.postMessage(self.registration === r ? 'PASS' : 'FAIL');
-  });
-};'''
-    return headers, '/* %s %s */ %s' % (time.time(), time.clock(), body)
deleted file mode 100644
--- a/testing/web-platform/tests/service-workers/service-worker/update-top-level.https.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<!DOCTYPE html>
-<title>Service Worker: Registration update()</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/test-helpers.sub.js"></script>
-<script>
-'use strict';
-
-function wait_for_message() {
-  return new Promise(resolve => {
-    navigator.serviceWorker.addEventListener("message",
-      e => {
-        resolve(e.data);
-      }, { once: true });
-  });
-}
-
-promise_test(async t => {
-  const script = './resources/update-top-level-worker.py';
-  const scope = './resources/empty.html?update-result';
-
-  let reg = await navigator.serviceWorker.register(script, { scope });
-  t.add_cleanup(async _ => await reg.unregister());
-  await wait_for_state(t, reg.installing, 'activated');
-
-  reg.addEventListener("updatefound",
-    () => assert_unreached("shouldn't find an update"));
-
-  reg.active.postMessage("ping");
-  assert_equals(await wait_for_message(), 'PASS', 'did not hang');
-}, 'A serviceworker with a top-level update should not hang');
-</script>
--- a/testing/web-platform/tests/webdriver/tests/new_session/invalid_capabilities.py
+++ b/testing/web-platform/tests/webdriver/tests/new_session/invalid_capabilities.py
@@ -1,12 +1,14 @@
 import pytest
 
+from conftest import product, flatten
+
+from tests.new_session.support.create import invalid_data, invalid_extensions
 from tests.support.asserts import assert_error
-from conftest import product, flatten
 
 
 @pytest.mark.parametrize("value", [None, 1, "{}", []])
 def test_invalid_capabilites(new_session, value):
     response, _ = new_session({"capabilities": value})
     assert_error(response, "invalid argument")
 
 
@@ -21,78 +23,30 @@ def test_invalid_always_match(new_sessio
 @pytest.mark.parametrize("value", [None, 1, "[]", {}])
 def test_invalid_first_match(new_session, add_browser_capabilities, value):
     capabilities = {"alwaysMatch": add_browser_capabilities({}), "firstMatch": value}
 
     response, _ = new_session({"capabilities": capabilities})
     assert_error(response, "invalid argument")
 
 
-invalid_data = [
-    ("acceptInsecureCerts", [1, [], {}, "false"]),
-    ("browserName", [1, [], {}, False]),
-    ("browserVersion", [1, [], {}, False]),
-    ("platformName", [1, [], {}, False]),
-    ("pageLoadStrategy", [1, [], {}, False, "invalid", "NONE", "Eager", "eagerblah", "interactive",
-                          " eager", "eager "]),
-    ("proxy", [1, [], "{}", {"proxyType": "SYSTEM"}, {"proxyType": "systemSomething"},
-               {"proxy type": "pac"}, {"proxy-Type": "system"}, {"proxy_type": "system"},
-               {"proxytype": "system"}, {"PROXYTYPE": "system"}, {"proxyType": None},
-               {"proxyType": 1}, {"proxyType": []}, {"proxyType": {"value": "system"}},
-               {" proxyType": "system"}, {"proxyType ": "system"}, {"proxyType ": " system"},
-               {"proxyType": "system "}]),
-    ("timeouts", [1, [], "{}", False, {"pageLOAD": 10}, {"page load": 10},
-                  {"page load": 10}, {"pageLoad": "10"}, {"pageLoad": {"value": 10}},
-                  {"invalid": 10}, {"pageLoad": -1}, {"pageLoad": 2**64},
-                  {"pageLoad": None}, {"pageLoad": 1.1}, {"pageLoad": 10, "invalid": 10},
-                  {" pageLoad": 10}, {"pageLoad ": 10}]),
-    ("unhandledPromptBehavior", [1, [], {}, False, "DISMISS", "dismissABC", "Accept",
-                                 " dismiss", "dismiss "])
-]
-
-
 @pytest.mark.parametrize("body", [lambda key, value: {"alwaysMatch": {key: value}},
                                   lambda key, value: {"firstMatch": [{key: value}]}])
 @pytest.mark.parametrize("key,value", flatten(product(*item) for item in invalid_data))
 def test_invalid_values(new_session, add_browser_capabilities, body, key, value):
     capabilities = body(key, value)
     if "alwaysMatch" in capabilities:
         capabilities["alwaysMatch"] = add_browser_capabilities(capabilities["alwaysMatch"])
     else:
         capabilities["firstMatch"][0] = add_browser_capabilities(capabilities["firstMatch"][0])
 
     response, _ = new_session({"capabilities": capabilities})
     assert_error(response, "invalid argument")
 
 
-invalid_extensions = [
-    "firefox",
-    "firefox_binary",
-    "firefoxOptions",
-    "chromeOptions",
-    "automaticInspection",
-    "automaticProfiling",
-    "platform",
-    "version",
-    "browser",
-    "platformVersion",
-    "javascriptEnabled",
-    "nativeEvents",
-    "seleniumProtocol",
-    "profile",
-    "trustAllSSLCertificates",
-    "initialBrowserUrl",
-    "requireWindowFocus",
-    "logFile",
-    "logLevel",
-    "safari.options",
-    "ensureCleanSession",
-]
-
-
 @pytest.mark.parametrize("body", [lambda key, value: {"alwaysMatch": {key: value}},
                                   lambda key, value: {"firstMatch": [{key: value}]}])
 @pytest.mark.parametrize("key", invalid_extensions)
 def test_invalid_extensions(new_session, add_browser_capabilities, body, key):
     capabilities = body(key, {})
     if "alwaysMatch" in capabilities:
         capabilities["alwaysMatch"] = add_browser_capabilities(capabilities["alwaysMatch"])
     else:
--- a/testing/web-platform/tests/webdriver/tests/new_session/support/create.py
+++ b/testing/web-platform/tests/webdriver/tests/new_session/support/create.py
@@ -1,15 +1,130 @@
 # Note that we can only test things here all implementations must support
 valid_data = [
-    ("acceptInsecureCerts", [False, None]),
-    ("browserName", [None]),
-    ("browserVersion", [None]),
-    ("platformName", [None]),
-    ("pageLoadStrategy", ["none", "eager", "normal", None]),
-    ("proxy", [None]),
-    ("timeouts", [{"script": 0, "pageLoad": 2.0, "implicit": 2**53 - 1},
-                  {"script": 50, "pageLoad": 25},
-                  {"script": 500},
-                  {}]),
-    ("unhandledPromptBehavior", ["dismiss", "accept", None]),
-    ("test:extension", [True, "abc", 123, [], {"key": "value"}, None]),
+    ("acceptInsecureCerts", [
+        False, None,
+    ]),
+    ("browserName", [
+        None,
+    ]),
+    ("browserVersion", [
+        None,
+    ]),
+    ("platformName", [
+        None,
+    ]),
+    ("pageLoadStrategy", [
+        None,
+        "none",
+        "eager",
+        "normal",
+    ]),
+    ("proxy", [
+        None,
+    ]),
+    ("timeouts", [
+        None, {},
+        {"script": 0, "pageLoad": 2.0, "implicit": 2**53 - 1},
+        {"script": 50, "pageLoad": 25},
+        {"script": 500},
+    ]),
+    ("unhandledPromptBehavior", [
+        "dismiss",
+        "accept",
+        None,
+    ]),
+    ("test:extension", [
+        None, False, "abc", 123, [],
+        {"key": "value"},
+    ]),
 ]
+
+invalid_data = [
+    ("acceptInsecureCerts", [
+        1, [], {}, "false",
+    ]),
+    ("browserName", [
+        1, [], {}, False,
+    ]),
+    ("browserVersion", [
+        1, [], {}, False,
+    ]),
+    ("platformName", [
+        1, [], {}, False,
+    ]),
+    ("pageLoadStrategy", [
+        1, [], {}, False,
+        "invalid",
+        "NONE",
+        "Eager",
+        "eagerblah",
+        "interactive",
+        " eager",
+        "eager "]),
+    ("proxy", [
+        1, [], "{}",
+        {"proxyType": "SYSTEM"},
+        {"proxyType": "systemSomething"},
+        {"proxy type": "pac"},
+        {"proxy-Type": "system"},
+        {"proxy_type": "system"},
+        {"proxytype": "system"},
+        {"PROXYTYPE": "system"},
+        {"proxyType": None},
+        {"proxyType": 1},
+        {"proxyType": []},
+        {"proxyType": {"value": "system"}},
+        {" proxyType": "system"},
+        {"proxyType ": "system"},
+        {"proxyType ": " system"},
+        {"proxyType": "system "},
+    ]),
+    ("timeouts", [
+        1, [], "{}", False,
+        {"invalid": 10},
+        {"PAGELOAD": 10},
+        {"page load": 10},
+        {" pageLoad": 10},
+        {"pageLoad ": 10},
+        {"pageLoad": None},
+        {"pageLoad": False},
+        {"pageLoad": []},
+        {"pageLoad": "10"},
+        {"pageLoad": 2.5},
+        {"pageLoad": -1},
+        {"pageLoad": 2**53},
+        {"pageLoad": {"value": 10}},
+        {"pageLoad": 10, "invalid": 10},
+    ]),
+    ("unhandledPromptBehavior", [
+        1, [], {}, False,
+        "DISMISS",
+        "dismissABC",
+        "Accept",
+        " dismiss",
+        "dismiss ",
+    ])
+]
+
+invalid_extensions = [
+    "firefox",
+    "firefox_binary",
+    "firefoxOptions",
+    "chromeOptions",
+    "automaticInspection",
+    "automaticProfiling",
+    "platform",
+    "version",
+    "browser",
+    "platformVersion",
+    "javascriptEnabled",
+    "nativeEvents",
+    "seleniumProtocol",
+    "profile",
+    "trustAllSSLCertificates",
+    "initialBrowserUrl",
+    "requireWindowFocus",
+    "logFile",
+    "logLevel",
+    "safari.options",
+    "ensureCleanSession",
+]
--- a/testing/web-platform/tests/webdriver/tests/set_timeouts/set.py
+++ b/testing/web-platform/tests/webdriver/tests/set_timeouts/set.py
@@ -1,8 +1,10 @@
+import pytest
+
 from webdriver.transport import Response
 
 from tests.support.asserts import assert_error, assert_success
 
 
 def set_timeouts(session, timeouts):
     return session.transport.send(
         "POST", "session/{session_id}/timeouts".format(**vars(session)),
@@ -11,19 +13,65 @@ def set_timeouts(session, timeouts):
 
 def test_null_parameter_value(session, http):
     path = "/session/{session_id}/timeouts".format(**vars(session))
     with http.post(path, None) as response:
         assert_error(Response.from_http(response), "invalid argument")
 
 
 def test_null_response_value(session):
-    response = set_timeouts(session, {"implicit": 1000})
+    timeouts = {"implicit": 10, "pageLoad": 10, "script": 10}
+    response = set_timeouts(session, timeouts)
     value = assert_success(response)
     assert value is None
 
-    response = set_timeouts(session, {"pageLoad": 1000})
-    value = assert_success(response)
-    assert value is None
+
+@pytest.mark.parametrize("value", [1, "{}", False, []])
+def test_parameters_invalid(session, value):
+    response = set_timeouts(session, value)
+    assert_error(response, "invalid argument")
+
+
+def test_parameters_empty_no_change(session):
+    original = session.timeouts._get()
+
+    response = set_timeouts(session, {})
+    assert_success(response)
+
+    assert session.timeouts._get() == original
+
+
+def test_key_invalid(session):
+    response = set_timeouts(session, {"foo": 1000})
+    assert_error(response, "invalid argument")
+
 
-    response = set_timeouts(session, {"script": 1000})
-    value = assert_success(response)
-    assert value is None
+@pytest.mark.parametrize("typ", ["implicit", "pageLoad", "script"])
+@pytest.mark.parametrize("value", [0, 2.0, 2**53 - 1])
+def test_positive_integer(session, typ, value):
+    response = set_timeouts(session, {typ: value})
+    assert_success(response)
+
+    assert session.timeouts._get(typ) == value
+
+
+@pytest.mark.parametrize("typ", ["implicit", "pageLoad", "script"])
+@pytest.mark.parametrize("value", [None, [], {}, False, "10"])
+def test_value_invalid_types(session, typ, value):
+    response = set_timeouts(session, {typ: value})
+    assert_error(response, "invalid argument")
+
+
+@pytest.mark.parametrize("typ", ["implicit", "pageLoad", "script"])
+@pytest.mark.parametrize("value", [-1, 2.5, 2**53])
+def test_value_positive_integer(session, typ, value):
+    response = set_timeouts(session, {typ: value})
+    assert_error(response, "invalid argument")
+
+
+def test_set_all_fields(session):
+    timeouts = {"implicit": 10, "pageLoad": 20, "script": 30}
+    response = set_timeouts(session, timeouts)
+    assert_success(response)
+
+    assert session.timeouts.implicit == 10
+    assert session.timeouts.page_load == 20
+    assert session.timeouts.script == 30
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/webdriver/tests/set_timeouts/user_prompts.py
@@ -0,0 +1,62 @@
+# META: timeout=long
+
+import pytest
+
+from tests.support.asserts import assert_success
+
+
+def set_timeouts(session, timeouts):
+    return session.transport.send(
+        "POST", "session/{session_id}/timeouts".format(**vars(session)),
+        timeouts)
+
+
+@pytest.fixture
+def check_user_prompt_not_closed(session, create_dialog):
+    def check_user_prompt_not_closed(dialog_type):
+        create_dialog(dialog_type, text=dialog_type)
+
+        response = set_timeouts(session, {"script": 100})
+        assert_success(response)
+
+        assert session.alert.text == dialog_type
+        session.alert.dismiss()
+
+        assert session.timeouts.script == 100
+
+    return check_user_prompt_not_closed
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "accept"})
+@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"])
+def test_accept(check_user_prompt_not_closed, dialog_type):
+    check_user_prompt_not_closed(dialog_type)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "accept and notify"})
+@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"])
+def test_accept_and_notify(check_user_prompt_not_closed, dialog_type):
+    check_user_prompt_not_closed(dialog_type)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "dismiss"})
+@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"])
+def test_dismiss(check_user_prompt_not_closed, dialog_type):
+    check_user_prompt_not_closed(dialog_type)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "dismiss and notify"})
+@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"])
+def test_dismiss_and_notify(check_user_prompt_not_closed, dialog_type):
+    check_user_prompt_not_closed(dialog_type)
+
+
+@pytest.mark.capabilities({"unhandledPromptBehavior": "ignore"})
+@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"])
+def test_ignore(check_user_prompt_not_closed, dialog_type):
+    check_user_prompt_not_closed(dialog_type)
+
+
+@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"])
+def test_default(check_user_prompt_not_closed, dialog_type):
+    check_user_prompt_not_closed(dialog_type)
--- a/testing/webdriver/src/capabilities.rs
+++ b/testing/webdriver/src/capabilities.rs
@@ -1,8 +1,9 @@
+use common::MAX_SAFE_INTEGER;
 use error::{ErrorStatus, WebDriverError, WebDriverResult};
 use serde_json::{Map, Value};
 use std::convert::From;
 use url::Url;
 
 pub type Capabilities = Map<String, Value>;
 
 /// Trait for objects that can be used to inspect browser capabilities
@@ -319,24 +320,36 @@ impl SpecNewSessionParameters {
             ErrorStatus::InvalidArgument,
             "timeouts capability is not an object"
         );
 
         for (key, value) in obj.iter() {
             match &**key {
                 x @ "script" | x @ "pageLoad" | x @ "implicit" => {
                     let timeout = try_opt!(
-                        value.as_i64(),
+                        value.as_f64(),
                         ErrorStatus::InvalidArgument,
-                        format!("{} timeouts value is not an integer: {}", x, value)
+                        format!("{} timeouts value is not a number: {}", x, value)
                     );
-                    if timeout < 0 {
+                    if timeout < 0.0 || timeout.fract() != 0.0 {
                         return Err(WebDriverError::new(
                             ErrorStatus::InvalidArgument,
-                            format!("{} timeouts value is negative: {}", x, timeout),
+                            format!(
+                                "'{}' timeouts value is not a positive Integer: {}",
+                                x, timeout
+                            ),
+                        ));
+                    }
+                    if (timeout as u64) > MAX_SAFE_INTEGER {
+                        return Err(WebDriverError::new(
+                            ErrorStatus::InvalidArgument,
+                            format!(
+                                "'{}' timeouts value is greater than maximum safe integer: {}",
+                                x, timeout
+                            ),
                         ));
                     }
                 }
 
                 x => {
                     return Err(WebDriverError::new(
                         ErrorStatus::InvalidArgument,
                         format!("Invalid timeouts capability entry: {}", x),
--- a/testing/webdriver/src/command.rs
+++ b/testing/webdriver/src/command.rs
@@ -1,12 +1,12 @@
 use actions::ActionSequence;
 use capabilities::{BrowserCapabilities, Capabilities, CapabilitiesMatching,
                    LegacyNewSessionParameters, SpecNewSessionParameters};
-use common::{Date, FrameId, LocatorStrategy, WebElement};
+use common::{Date, FrameId, LocatorStrategy, WebElement, MAX_SAFE_INTEGER};
 use error::{ErrorStatus, WebDriverError, WebDriverResult};
 use httpapi::{Route, VoidWebDriverExtensionRoute, WebDriverExtensionRoute};
 use regex::Captures;
 use serde::de::{self, Deserialize, Deserializer};
 use serde_json::{self, Value};
 
 #[derive(Debug, PartialEq)]
 pub enum WebDriverCommand<T: WebDriverExtensionCommand> {
@@ -487,25 +487,66 @@ pub struct SwitchToWindowParameters {
 }
 
 #[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub struct TakeScreenshotParameters {
     pub element: Option<WebElement>,
 }
 
 #[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
 pub struct TimeoutsParameters {
-    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(
+        default, skip_serializing_if = "Option::is_none", deserialize_with = "deserialize_to_u64"
+    )]
     pub implicit: Option<u64>,
-    #[serde(rename = "pageLoad", skip_serializing_if = "Option::is_none")]
+    #[serde(
+        default,
+        rename = "pageLoad",
+        skip_serializing_if = "Option::is_none",
+        deserialize_with = "deserialize_to_u64"
+    )]
     pub page_load: Option<u64>,
-    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(
+        default, skip_serializing_if = "Option::is_none", deserialize_with = "deserialize_to_u64"
+    )]
     pub script: Option<u64>,
 }
 
+fn deserialize_to_u64<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    let opt = Option::deserialize(deserializer)?.map(|value: f64| value);
+    let value = match opt {
+        Some(n) => {
+            if n < 0.0 || n.fract() != 0.0 {
+                return Err(de::Error::custom(format!(
+                    "'{}' is not a positive Integer",
+                    n
+                )));
+            }
+            if (n as u64) > MAX_SAFE_INTEGER {
+                return Err(de::Error::custom(format!(
+                    "'{}' is greater than maximum safe integer",
+                    n
+                )));
+            }
+            Some(n as u64)
+        }
+        None => {
+            return Err(de::Error::custom(format!(
+                "'null' is not a positive Integer"
+            )));
+        }
+    };
+
+    Ok(value)
+}
+
 /// A top-level browsing context’s window rect is a dictionary of the
 /// [`screenX`], [`screenY`], `width`, and `height` attributes of the
 /// `WindowProxy`.
 ///
 /// In some user agents the operating system’s window dimensions, including
 /// decorations, are provided by the proprietary `window.outerWidth` and
 /// `window.outerHeight` DOM properties.
 ///
@@ -967,69 +1008,52 @@ mod tests {
     #[test]
     fn test_json_take_screenshot_parameters_with_invalid_element_field() {
         let json = r#"{"element":"foo"}"#;
         assert!(serde_json::from_str::<TakeScreenshotParameters>(&json).is_err());
     }
 
     #[test]
     fn test_json_timeout_parameters_with_values() {
-        let json = r#"{"implicit":1,"pageLoad":2,"script":3}"#;
+        let json = r#"{"implicit":0,"pageLoad":2.0,"script":9007199254740991}"#;
         let data = TimeoutsParameters {
-            implicit: Some(1u64),
+            implicit: Some(0u64),
             page_load: Some(2u64),
-            script: Some(3u64),
+            script: Some(9007199254740991u64),
         };
 
         check_deserialize(&json, &data);
     }
 
     #[test]
+    fn test_json_timeout_parameters_with_invalid_values() {
+        let json = r#"{"implicit":-1,"pageLoad":2.5,"script":9007199254740992}"#;
+        assert!(serde_json::from_str::<TimeoutsParameters>(&json).is_err());
+    }
+
+    #[test]
     fn test_json_timeout_parameters_with_optional_null_field() {
         let json = r#"{"implicit":null,"pageLoad":null,"script":null}"#;
-        let data = TimeoutsParameters {
-            implicit: None,
-            page_load: None,
-            script: None,
-        };
 
-        check_deserialize(&json, &data);
+        assert!(serde_json::from_str::<TimeoutsParameters>(&json).is_err());
     }
 
     #[test]
     fn test_json_timeout_parameters_without_optional_null_field() {
         let json = r#"{}"#;
         let data = TimeoutsParameters {
             implicit: None,
             page_load: None,
             script: None,
         };
 
         check_deserialize(&json, &data);
     }
 
     #[test]
-    fn test_json_timeout_parameters_with_invalid_implicit_value() {
-        let json = r#"{"implicit":1.1}"#;
-        assert!(serde_json::from_str::<TimeoutsParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_timeout_parameters_with_invalid_page_load_value() {
-        let json = r#"{"pageLoad":1.2}"#;
-        assert!(serde_json::from_str::<TimeoutsParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_timeout_parameters_with_invalid_script_value() {
-        let json = r#"{"script":1.3}"#;
-        assert!(serde_json::from_str::<TimeoutsParameters>(&json).is_err());
-    }
-
-    #[test]
     fn test_json_window_rect_parameters_with_values() {
         let json = r#"{"x":0,"y":1,"width":2,"height":3}"#;
         let data = WindowRectParameters {
             x: Some(0i32),
             y: Some(1i32),
             width: Some(2i32),
             height: Some(3i32),
         };
--- a/testing/webdriver/src/common.rs
+++ b/testing/webdriver/src/common.rs
@@ -1,14 +1,16 @@
 use serde::ser::{Serialize, Serializer};
 
 pub static ELEMENT_KEY: &'static str = "element-6066-11e4-a52e-4f735466cecf";
 pub static FRAME_KEY: &'static str = "frame-075b-4da1-b6ba-e579c2d3230a";
 pub static WINDOW_KEY: &'static str = "window-fcc6-11e5-b4f8-330a88ab9d7f";
 
+pub static MAX_SAFE_INTEGER: u64 = 9007199254740991;
+
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
 pub struct Cookie {
     pub name: String,
     pub value: String,
     pub path: Option<String>,
     pub domain: Option<String>,
     #[serde(default)]
     pub secure: bool,
--- a/toolkit/components/antitracking/AntiTrackingCommon.cpp
+++ b/toolkit/components/antitracking/AntiTrackingCommon.cpp
@@ -440,17 +440,17 @@ AntiTrackingCommon::IsFirstPartyStorageA
     return access != nsICookiePermission::ACCESS_DENY;
   }
 
   if (NS_WARN_IF(NS_FAILED(rv) || !channelPrincipal)) {
     LOG(("No channel principal, bail out early"));
     return false;
   }
 
-  int32_t behavior = CookiesBehavior(channelPrincipal);
+  int32_t behavior = CookiesBehavior(toplevelPrincipal);
   if (behavior == nsICookieService::BEHAVIOR_ACCEPT) {
     LOG(("The cookie behavior pref mandates accepting all cookies!"));
     return true;
   }
 
   if (behavior == nsICookieService::BEHAVIOR_REJECT) {
     LOG(("The cookie behavior pref mandates rejecting all cookies!"));
     return false;
@@ -490,17 +490,17 @@ AntiTrackingCommon::IsFirstPartyStorageA
 
     // parentPrincipal can be null if the parent window is not the top-level
     // window.
     if (loadInfo->TopLevelPrincipal()) {
       LOG(("Parent window is the top-level window, bail out early"));
       return false;
     }
 
-    parentPrincipal = loadInfo->TriggeringPrincipal();
+    parentPrincipal = toplevelPrincipal;
     if (NS_WARN_IF(!parentPrincipal)) {
       LOG(("No triggering principal, this shouldn't be happening! Bail out early"));
       // Why we are here?!?
       return true;
     }
   }
 
   // Not a tracker.
--- a/toolkit/components/antitracking/test/browser/.eslintrc.js
+++ b/toolkit/components/antitracking/test/browser/.eslintrc.js
@@ -1,7 +1,11 @@
 "use strict";
 
 module.exports = {
   "extends": [
     "plugin:mozilla/browser-test"
-  ]
+  ],
+
+  "env": {
+    "webextensions": true,
+  },
 };
--- a/toolkit/components/antitracking/test/browser/browser.ini
+++ b/toolkit/components/antitracking/test/browser/browser.ini
@@ -18,13 +18,14 @@ support-files = server.sjs
 [browser_blockingIndexedDb.js]
 [browser_blockingStorage.js]
 [browser_blockingWorkers.js]
 [browser_blockingMessaging.js]
 [browser_blockingNoOpener.js]
 [browser_existingCookiesForSubresources.js]
 [browser_imageCache.js]
 support-files = image.sjs
+[browser_onBeforeRequestNotificationForTrackingResources.js]
 [browser_onModifyRequestNotificationForTrackingResources.js]
 [browser_subResources.js]
 support-files = subResources.sjs
 [browser_script.js]
 support-files = tracker.js
new file mode 100644
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_onBeforeRequestNotificationForTrackingResources.js
@@ -0,0 +1,91 @@
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+/**
+ * This test ensures that onBeforeRequest is dispatched for webRequest loads that
+ * are blocked by tracking protection.  It sets up a page with a third-party script
+ * resource on it that is blocked by TP, and sets up an onBeforeRequest listener
+ * which waits to be notified about that resource.  The test would time out if the
+ * onBeforeRequest listener isn't called dispatched before the load is canceled.
+ */
+
+let extension;
+add_task(async function() {
+  extension = ExtensionTestUtils.loadExtension({
+    manifest: {permissions: ["webRequest", "webRequestBlocking", "*://*/*"]},
+    async background() {
+      let gExpectedResourcesSeen = 0;
+      function onBeforeRequest(details) {
+        let spec = details.url;
+        browser.test.log("Observed channel for " + spec);
+        // We would use TEST_3RD_PARTY_DOMAIN_TP here, but the variable is inaccessible
+        // since it is defined in head.js!
+        if (!spec.startsWith("https://tracking.example.com/")) {
+          return undefined;
+        }
+        if (spec.endsWith("empty.js")) {
+          browser.test.succeed("Correct resource observed");
+          ++gExpectedResourcesSeen;
+        } else if (spec.endsWith("empty.js?redirect")) {
+          return {redirectUrl: spec.replace("empty.js?redirect", "head.js")};
+        } else if (spec.endsWith("head.js")) {
+          ++gExpectedResourcesSeen;
+        }
+        if (gExpectedResourcesSeen == 2) {
+          browser.webRequest.onBeforeRequest.removeListener(onBeforeRequest);
+          browser.test.sendMessage("finish");
+        }
+        return undefined;
+      }
+
+      browser.webRequest.onBeforeRequest.addListener(onBeforeRequest,
+                                                     {urls: ["*://*/*"]},
+                                                     ["blocking"]);
+      browser.test.sendMessage("ready");
+    },
+  });
+  await extension.startup();
+  await extension.awaitMessage("ready");
+});
+
+add_task(async function() {
+  info("Starting subResources test");
+
+  await SpecialPowers.flushPrefEnv();
+  await SpecialPowers.pushPrefEnv({"set": [
+    ["browser.contentblocking.enabled", true],
+    ["privacy.trackingprotection.enabled", true],
+    // the test doesn't open a private window, so we don't care about this pref's value
+    ["privacy.trackingprotection.pbmode.enabled", false],
+    // tracking annotations aren't needed in this test, only TP is needed
+    ["privacy.trackingprotection.annotate_channels", false],
+    // prevent the content blocking on-boarding UI to start mid-way through the test!
+    ["privacy.trackingprotection.introCount", ContentBlocking.MAX_INTROS],
+  ]});
+
+  await UrlClassifierTestUtils.addTestTrackers();
+
+  let promise = extension.awaitMessage("finish");
+
+  info("Creating a new tab");
+  let tab = BrowserTestUtils.addTab(gBrowser, TEST_EMBEDDER_PAGE);
+  gBrowser.selectedTab = tab;
+
+  let browser = gBrowser.getBrowserForTab(tab);
+  await BrowserTestUtils.browserLoaded(browser);
+
+  await promise;
+
+  info("Verify the number of tracking nodes found");
+  await ContentTask.spawn(browser,
+                          { expected: 3,
+                          },
+                          async function(obj) {
+    is(content.document.blockedTrackingNodeCount, obj.expected, "Expected tracking nodes found");
+  });
+
+  info("Removing the tab");
+  BrowserTestUtils.removeTab(tab);
+
+  UrlClassifierTestUtils.cleanupTestTrackers();
+  await extension.unload();
+});
--- a/toolkit/components/antitracking/test/browser/browser_onModifyRequestNotificationForTrackingResources.js
+++ b/toolkit/components/antitracking/test/browser/browser_onModifyRequestNotificationForTrackingResources.js
@@ -1,31 +1,48 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 /**
  * This test ensures that http-on-modify-request is dispatched for channels that
  * are blocked by tracking protection.  It sets up a page with a third-party script
  * resource on it that is blocked by TP, and sets up an http-on-modify-request
  * observer which waits to be notified about that resource.  The test would time out
  * if the http-on-modify-request notification isn't dispatched before the channel is
  * canceled.
  */
 
+let gExpectedResourcesSeen = 0;
 async function onModifyRequest() {
   return new Promise((resolve, reject) => {
     Services.obs.addObserver(function observer(subject, topic, data) {
       let httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
       let spec = httpChannel.URI.spec;
       info("Observed channel for " + spec);
       if (httpChannel.URI.prePath + "/" != TEST_3RD_PARTY_DOMAIN_TP) {
         return;
       }
-      ok(spec.endsWith("empty.js"), "Correct resource observed");
-      Services.obs.removeObserver(observer, "http-on-modify-request");
-      resolve();
+      if (spec.endsWith("empty.js")) {
+        ok(true, "Correct resource observed");
+        ++gExpectedResourcesSeen;
+      } else if (spec.endsWith("empty.js?redirect")) {
+        httpChannel.redirectTo(Services.io.newURI(spec.replace("empty.js?redirect", "head.js")));
+      } else if (spec.endsWith("empty.js?redirect2")) {
+        httpChannel.suspend();
+        setTimeout(() => {
+          httpChannel.redirectTo(Services.io.newURI(spec.replace("empty.js?redirect2", "head.js")));
+          httpChannel.resume();
+        }, 100);
+      } else if (spec.endsWith("head.js")) {
+        ++gExpectedResourcesSeen;
+      }
+      if (gExpectedResourcesSeen == 3) {
+        Services.obs.removeObserver(observer, "http-on-modify-request");
+        resolve();
+      }
     }, "http-on-modify-request");
   });
 }
 
 add_task(async function() {
   info("Starting subResources test");
 
   await SpecialPowers.flushPrefEnv();
@@ -48,13 +65,21 @@ add_task(async function() {
   let tab = BrowserTestUtils.addTab(gBrowser, TEST_EMBEDDER_PAGE);
   gBrowser.selectedTab = tab;
 
   let browser = gBrowser.getBrowserForTab(tab);
   await BrowserTestUtils.browserLoaded(browser);
 
   await promise;
 
+  info("Verify the number of tracking nodes found");
+  await ContentTask.spawn(browser,
+                          { expected: gExpectedResourcesSeen,
+                          },
+                          async function(obj) {
+    is(content.document.blockedTrackingNodeCount, obj.expected, "Expected tracking nodes found");
+  });
+
   info("Removing the tab");
   BrowserTestUtils.removeTab(tab);
 
   UrlClassifierTestUtils.cleanupTestTrackers();
 });
--- a/toolkit/components/antitracking/test/browser/embedder.html
+++ b/toolkit/components/antitracking/test/browser/embedder.html
@@ -1,2 +1,4 @@
 <!DOCTYPE html>
 <script src="https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/empty.js"></script>
+<script src="https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/empty.js?redirect"></script>
+<script src="https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/empty.js?redirect2"></script>
--- a/toolkit/components/thumbnails/test/browser.ini
+++ b/toolkit/components/thumbnails/test/browser.ini
@@ -32,15 +32,15 @@ skip-if = verify
 [browser_thumbnails_bg_captureIfMissing.js]
 [browser_thumbnails_bg_image_capture.js]
 [browser_thumbnails_bg_topsites.js]
 [browser_thumbnails_bug726727.js]
 [browser_thumbnails_bug727765.js]
 [browser_thumbnails_bug818225.js]
 skip-if = (verify && debug && (os == 'linux'))
 [browser_thumbnails_capture.js]
-skip-if = os == "mac" && !debug # bug 1314039
+skip-if = (os == "mac" && !debug) || (os == 'win') # bug 1314039 # Bug 1345418
 [browser_thumbnails_expiration.js]
 [browser_thumbnails_privacy.js]
 [browser_thumbnails_redirect.js]
 [browser_thumbnails_storage.js]
 [browser_thumbnails_storage_migrate3.js]
 [browser_thumbnails_update.js]
--- a/toolkit/themes/osx/global/tree.css
+++ b/toolkit/themes/osx/global/tree.css
@@ -1,23 +1,14 @@
 /* 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/. */
 
 %include ../../shared/tree.inc.css
 
-/* ::::: tree focusring ::::: */
-
-.focusring > .tree-stack > .tree-rows > .tree-bodybox {
-  border: 1px solid transparent;
-}
-
-.focusring:focus > .tree-stack > .tree-rows > .tree-bodybox {
-  border: 1px solid -moz-mac-focusring;
-}
 
 /* ::::: editable tree ::::: */
 
 .tree-input {
   -moz-appearance: none;
   border-width: 0;
   box-shadow: var(--focus-ring-box-shadow);
 }
--- a/toolkit/themes/shared/tree.inc.css
+++ b/toolkit/themes/shared/tree.inc.css
@@ -12,27 +12,16 @@
 
 tree {
   margin: 0 4px;
   background-color: -moz-Field;
   color: -moz-FieldText;
   -moz-appearance: listbox;
 }
 
-/* ::::: tree focusring ::::: */
-
-.focusring > .tree-stack > .tree-rows > .tree-bodybox {
-  border: 1px solid transparent;
-}
-
-.focusring:focus > .tree-stack > .tree-rows > .tree-bodybox {
-  border: 1px solid #000000;
-}
-
-
 /* ::::: tree rows ::::: */
 
 treechildren::-moz-tree-row {
   border: 1px solid transparent;
   min-height: 24px;
   height: 1.3em;
 }