Merge inbound to mozilla-central. a=merge
authorMargareta Eliza Balazs <ebalazs@mozilla.com>
Fri, 13 Jul 2018 11:52:00 +0300
changeset 426485 e951f4ad123aa87d1d392c286db14cabb41a8560
parent 426460 254564563107faac0f16dd48fa6927ddbfac955c (current diff)
parent 426484 5173c4b6f97c04522159e58f46b9359975c8b9d9 (diff)
child 426486 1b52153377e34adda9c64c5637d5335ca2f8f403
child 426554 1e71794ed2ef7edbf66105612b50e4aee8f19c17
push id66319
push userebalazs@mozilla.com
push dateFri, 13 Jul 2018 08:58:55 +0000
treeherderautoland@1b52153377e3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone63.0a1
first release with
nightly linux32
e951f4ad123a / 63.0a1 / 20180713100116 / files
nightly linux64
e951f4ad123a / 63.0a1 / 20180713100116 / files
nightly mac
e951f4ad123a / 63.0a1 / 20180713100116 / files
nightly win32
e951f4ad123a / 63.0a1 / 20180713100116 / files
nightly win64
e951f4ad123a / 63.0a1 / 20180713100116 / 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/themes/images/debugger/breakpoint.svg
dom/base/nsDocument.cpp
dom/base/nsIAttribute.h
dom/chrome-webidl/moz.build
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -1899,17 +1899,21 @@ window._gBrowser = {
           getter = () => aTab.hasAttribute("muted");
           break;
         case "contentTitle":
           getter = () => SessionStore.getLazyTabValue(aTab, "title");
           break;
         case "currentURI":
           getter = () => {
             let url = SessionStore.getLazyTabValue(aTab, "url");
-            return Services.io.newURI(url);
+            // Avoid recreating the same nsIURI object over and over again...
+            if (browser._cachedCurrentURI) {
+              return browser._cachedCurrentURI;
+            }
+            return browser._cachedCurrentURI = Services.io.newURI(url);
           };
           break;
         case "didStartLoadSinceLastUserTyping":
           getter = () => () => false;
           break;
         case "fullZoom":
         case "textZoom":
           getter = () => 1;
@@ -1983,16 +1987,17 @@ window._gBrowser = {
       for (let name of this._browserBindingProperties) {
         delete browser[name];
       }
     }
 
     let { uriIsAboutBlank, remoteType, usingPreloadedContent } =
     aTab._browserParams;
     delete aTab._browserParams;
+    delete aTab._cachedCurrentURI;
 
     let notificationbox = this.getNotificationBox(browser);
     let uniqueId = this._generateUniquePanelID();
     notificationbox.id = uniqueId;
     aTab.linkedPanel = uniqueId;
 
     // Inject the <browser> into the DOM if necessary.
     if (!notificationbox.parentNode) {
--- a/devtools/client/debugger/new/README.mozilla
+++ b/devtools/client/debugger/new/README.mozilla
@@ -1,13 +1,13 @@
 This is the debugger.html project output.
 See https://github.com/devtools-html/debugger.html
 
-Version 70
+Version 71
 
-Comparison: https://github.com/devtools-html/debugger.html/compare/release-69...release-70
+Comparison: https://github.com/devtools-html/debugger.html/compare/release-70...release-71
 
 Packages:
 - babel-plugin-transform-es2015-modules-commonjs @6.26.2
 - babel-preset-react @6.24.1
-- react @16.2.0
-- react-dom @16.2.0
+- react @16.4.1
+- react-dom @16.4.1
 - webpack @3.11.0
--- a/devtools/client/debugger/new/dist/debugger.css
+++ b/devtools/client/debugger/new/dist/debugger.css
@@ -2701,42 +2701,16 @@ button.jump-definition {
   left: 0px;
   --editor-footer-height: 24px;
 }
 
 html[dir="rtl"] .editor-mount {
   direction: ltr;
 }
 
-.theme-light {
-  --gutter-hover-background-color: #dde1e4;
-}
-
-.theme-dark {
-  --gutter-hover-background-color: #414141;
-}
-
-:not(.empty-line):not(.new-breakpoint) > .CodeMirror-gutter-wrapper:hover {
-  width: 60px;
-  height: 13px;
-  left: -55px !important;
-  background-color: var(--gutter-hover-background-color) !important;
-  mask: url("chrome://devtools/skin/images/debugger/breakpoint.svg") no-repeat;
-  mask-size: 100%;
-  mask-position: 0 1px;
-}
-
-:not(.empty-line):not(.new-breakpoint)
-  > .CodeMirror-gutter-wrapper:hover
-  > .CodeMirror-linenumber {
-  left: auto !important;
-  right: 6px;
-  color: var(--theme-body-color);
-}
-
 .editor-wrapper .breakpoints {
   position: absolute;
   top: 0;
   left: 0;
 }
 
 .function-search {
   max-height: 300px;
@@ -2774,21 +2748,16 @@ html[dir="rtl"] .editor-mount {
   width: 20px;
   padding: 0px 5px;
   margin: 0px 4px;
   border-radius: 5px;
   border-color: blue;
   border: 1px solid #00b6ff;
 }
 
-.editor .breakpoint {
-  position: absolute;
-  right: -2px;
-}
-
 .editor.new-breakpoint.folding-enabled svg {
   right: -16px;
 }
 
 .new-breakpoint.has-condition svg {
   fill: var(--theme-graphs-yellow);
 }
 
--- a/devtools/client/debugger/new/index.html
+++ b/devtools/client/debugger/new/index.html
@@ -10,24 +10,18 @@
   <link rel="stylesheet" type="text/css" href="chrome://devtools/content/sourceeditor/codemirror/mozilla.css" />
   <link rel="stylesheet" type="text/css" href="resource://devtools/client/debugger/new/dist/debugger.css" />
 </head>
 
 <body>
   <div id="mount"></div>
   <script type="application/javascript" src="chrome://devtools/content/shared/theme-switching.js"></script>
   <script type="text/javascript">
-    try {
-      const { BrowserLoader } = ChromeUtils.import("resource://devtools/client/shared/browser-loader.js", {});
-      const { require } = BrowserLoader({
-        baseURI: "resource://devtools/client/debugger/new",
-        window,
-      });
-      Debugger = require("devtools/client/debugger/new/src/main");
-    } catch(e) {
-      dump("Exception happened while loading the debugger:\n");
-      dump(e + "\n");
-      dump(e.stack + "\n");
-    }
+    const { BrowserLoader } = ChromeUtils.import("resource://devtools/client/shared/browser-loader.js", {});
+    const { require } = BrowserLoader({
+      baseURI: "resource://devtools/client/debugger/new",
+      window,
+    });
+    Debugger = require("devtools/client/debugger/new/src/main");
   </script>
 </body>
 
-</html>
+</html>
\ No newline at end of file
--- a/devtools/client/debugger/new/src/client/firefox/events.js
+++ b/devtools/client/debugger/new/src/client/firefox/events.js
@@ -96,9 +96,9 @@ function workerListChanged() {
 }
 
 const clientEvents = {
   paused,
   resumed,
   newSource
 };
 exports.setupEvents = setupEvents;
-exports.clientEvents = clientEvents;
+exports.clientEvents = clientEvents;
\ No newline at end of file
--- a/devtools/client/debugger/new/src/components/Editor/Footer.js
+++ b/devtools/client/debugger/new/src/components/Editor/Footer.js
@@ -143,33 +143,33 @@ class SourceFooter extends _react.PureCo
 
   renderSourceSummary() {
     const {
       mappedSource,
       jumpToMappedLocation,
       selectedSource
     } = this.props;
 
-    if (mappedSource) {
-      const filename = (0, _source.getFilename)(mappedSource);
-      const tooltip = L10N.getFormatStr("sourceFooter.mappedSourceTooltip", filename);
-      const title = L10N.getFormatStr("sourceFooter.mappedSource", filename);
-      const mappedSourceLocation = {
-        sourceId: selectedSource.id,
-        line: 1,
-        column: 1
-      };
-      return _react2.default.createElement("button", {
-        className: "mapped-source",
-        onClick: () => jumpToMappedLocation(mappedSourceLocation),
-        title: tooltip
-      }, _react2.default.createElement("span", null, title));
+    if (!mappedSource) {
+      return null;
     }
 
-    return null;
+    const filename = (0, _source.getFilename)(mappedSource);
+    const tooltip = L10N.getFormatStr("sourceFooter.mappedSourceTooltip", filename);
+    const title = L10N.getFormatStr("sourceFooter.mappedSource", filename);
+    const mappedSourceLocation = {
+      sourceId: selectedSource.id,
+      line: 1,
+      column: 1
+    };
+    return _react2.default.createElement("button", {
+      className: "mapped-source",
+      onClick: () => jumpToMappedLocation(mappedSourceLocation),
+      title: tooltip
+    }, _react2.default.createElement("span", null, title));
   }
 
   render() {
     const {
       selectedSource,
       horizontal
     } = this.props;
 
--- a/devtools/client/debugger/new/src/selectors/breakpointSources.js
+++ b/devtools/client/debugger/new/src/selectors/breakpointSources.js
@@ -22,17 +22,12 @@ function getBreakpointsForSource(source,
 }
 
 function findBreakpointSources(sources, breakpoints) {
   const sourceIds = (0, _lodash.uniq)(breakpoints.valueSeq().filter(bp => !bp.hidden).map(bp => bp.location.sourceId).toJS());
   const breakpointSources = sourceIds.map(id => sources[id]).filter(source => source && !source.isBlackBoxed);
   return (0, _lodash.sortBy)(breakpointSources, source => (0, _source.getFilename)(source));
 }
 
-function _getBreakpointSources(breakpoints, sources) {
-  const breakpointSources = findBreakpointSources(sources, breakpoints);
-  return breakpointSources.map(source => ({
-    source,
-    breakpoints: getBreakpointsForSource(source, breakpoints)
-  }));
-}
-
-const getBreakpointSources = exports.getBreakpointSources = (0, _reselect.createSelector)(_selectors.getBreakpoints, _selectors.getSources, _getBreakpointSources);
\ No newline at end of file
+const getBreakpointSources = exports.getBreakpointSources = (0, _reselect.createSelector)(_selectors.getBreakpoints, _selectors.getSources, (breakpoints, sources) => findBreakpointSources(sources, breakpoints).map(source => ({
+  source,
+  breakpoints: getBreakpointsForSource(source, breakpoints)
+})));
\ No newline at end of file
--- a/devtools/client/debugger/new/src/selectors/getRelativeSources.js
+++ b/devtools/client/debugger/new/src/selectors/getRelativeSources.js
@@ -4,35 +4,41 @@ Object.defineProperty(exports, "__esModu
   value: true
 });
 exports.getRelativeSources = undefined;
 
 var _selectors = require("../selectors/index");
 
 var _lodash = require("devtools/client/shared/vendor/lodash");
 
-var _source = require("../utils/source");
+var _sourcesTree = require("../utils/sources-tree/index");
 
 var _reselect = require("devtools/client/debugger/new/dist/vendors").vendored["reselect"];
 
 /* 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/>. */
-function getRelativeUrl(url, root) {
+function getRelativeUrl(source, root) {
+  const {
+    group,
+    path
+  } = (0, _sourcesTree.getURL)(source);
+
   if (!root) {
-    return (0, _source.getSourcePath)(url);
+    return path;
   } // + 1 removes the leading "/"
 
 
+  const url = group + path;
   return url.slice(url.indexOf(root) + root.length + 1);
 }
 
 function formatSource(source, root) {
   return { ...source,
-    relativeUrl: getRelativeUrl(source.url, root)
+    relativeUrl: getRelativeUrl(source, root)
   };
 }
 
 function underRoot(source, root) {
   return source.url && source.url.includes(root);
 }
 /*
  * Gets the sources that are below a project root
--- a/devtools/client/debugger/new/src/utils/source.js
+++ b/devtools/client/debugger/new/src/utils/source.js
@@ -175,17 +175,17 @@ function getFilename(source) {
 
   if (!url) {
     return getFormattedSourceId(id);
   }
 
   const {
     filename
   } = (0, _sourcesTree.getURL)(source);
-  return filename;
+  return getRawSourceURL(filename);
 }
 /**
  * Provides a middle-trunated filename
  *
  * @memberof utils/source
  * @static
  */
 
@@ -455,10 +455,10 @@ function getSourceClassnames(source, sou
   if (isPretty(source)) {
     return "prettyPrint";
   }
 
   if (source.isBlackBoxed) {
     return "blackBox";
   }
 
-  return sourceTypes[(0, _sourcesTree.getFileExtension)(source.url)] || defaultClassName;
+  return sourceTypes[(0, _sourcesTree.getFileExtension)(source)] || defaultClassName;
 }
\ No newline at end of file
--- a/devtools/client/debugger/new/src/utils/sources-tree/addToTree.js
+++ b/devtools/client/debugger/new/src/utils/sources-tree/addToTree.js
@@ -115,19 +115,18 @@ function addSourceToNode(node, url, sour
   return contents;
 }
 /**
  * @memberof utils/sources-tree
  * @static
  */
 
 
-function addToTree(tree, source, debuggeeUrl, projectRoot) {
-  const url = (0, _getURL.getURL)(source, debuggeeUrl);
-  const debuggeeHost = (0, _treeOrder.getDomain)(debuggeeUrl);
+function addToTree(tree, source, debuggeeHost, projectRoot) {
+  const url = (0, _getURL.getURL)(source, debuggeeHost);
 
   if ((0, _utils.isInvalidUrl)(url, source)) {
     return;
   }
 
   const finalNode = traverseTree(url, tree, debuggeeHost); // $FlowIgnore
 
   finalNode.contents = addSourceToNode(finalNode, url, source);
--- a/devtools/client/debugger/new/src/utils/sources-tree/createTree.js
+++ b/devtools/client/debugger/new/src/utils/sources-tree/createTree.js
@@ -6,29 +6,32 @@ Object.defineProperty(exports, "__esModu
 exports.createTree = createTree;
 
 var _addToTree = require("./addToTree");
 
 var _collapseTree = require("./collapseTree");
 
 var _utils = require("./utils");
 
+var _treeOrder = require("./treeOrder");
+
 /* 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/>. */
 function createTree({
   sources,
   debuggeeUrl,
   projectRoot
 }) {
   const uncollapsedTree = (0, _utils.createDirectoryNode)("root", "", []);
+  const debuggeeHost = (0, _treeOrder.getDomain)(debuggeeUrl);
 
   for (const sourceId in sources) {
     const source = sources[sourceId];
-    (0, _addToTree.addToTree)(uncollapsedTree, source, debuggeeUrl, projectRoot);
+    (0, _addToTree.addToTree)(uncollapsedTree, source, debuggeeHost, projectRoot);
   }
 
   const sourceTree = (0, _collapseTree.collapseTree)(uncollapsedTree);
   return {
     uncollapsedTree,
     sourceTree,
     parentMap: (0, _utils.createParentMap)(sourceTree),
     focusedItem: null
--- a/devtools/client/debugger/new/src/utils/sources-tree/getDirectories.js
+++ b/devtools/client/debugger/new/src/utils/sources-tree/getDirectories.js
@@ -2,62 +2,53 @@
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 exports.getDirectories = getDirectories;
 
 var _utils = require("./utils");
 
-var _getURL = require("./getURL");
-
 /* 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/>. */
-function findSource(sourceTree, sourceUrl) {
-  let returnTarget = null;
+function _traverse(subtree, source) {
+  if (subtree.type === "source") {
+    if (subtree.contents.id === source.id) {
+      return subtree;
+    }
 
-  function _traverse(subtree) {
-    if (subtree.type === "directory") {
-      for (const child of subtree.contents) {
-        _traverse(child);
-      }
-    } else if (!returnTarget) {
-      if (subtree.path.replace(/http(s)?:\//, "") == sourceUrl) {
-        returnTarget = subtree;
-        return;
-      }
-    }
+    return null;
   }
 
-  sourceTree.contents.forEach(node => _traverse(node));
+  const matches = subtree.contents.map(child => _traverse(child, source));
+  return matches && matches.filter(Boolean)[0];
+}
 
-  if (!returnTarget) {
-    return sourceTree;
+function findSourceItem(sourceTree, source) {
+  return _traverse(sourceTree, source);
+}
+
+function getAncestors(sourceTree, item) {
+  if (!item) {
+    return null;
   }
 
-  return returnTarget;
+  const parentMap = (0, _utils.createParentMap)(sourceTree);
+  const directories = [];
+  directories.push(item);
+
+  while (true) {
+    item = parentMap.get(item);
+
+    if (!item) {
+      return directories;
+    }
+
+    directories.push(item);
+  }
 }
 
 function getDirectories(source, sourceTree) {
-  const url = (0, _getURL.getURL)(source);
-  const fullUrl = `${url.group}${url.path}`;
-  const parentMap = (0, _utils.createParentMap)(sourceTree);
-  const subtreeSource = findSource(sourceTree, fullUrl);
-
-  if (!subtreeSource) {
-    return [];
-  }
-
-  let node = subtreeSource;
-  const directories = [];
-  directories.push(subtreeSource);
-
-  while (true) {
-    node = parentMap.get(node);
-
-    if (!node) {
-      return directories;
-    }
-
-    directories.push(node);
-  }
+  const item = findSourceItem(sourceTree, source);
+  const ancestors = getAncestors(sourceTree, item);
+  return ancestors || [sourceTree];
 }
\ No newline at end of file
--- a/devtools/client/debugger/new/src/utils/sources-tree/getURL.js
+++ b/devtools/client/debugger/new/src/utils/sources-tree/getURL.js
@@ -31,17 +31,17 @@ function getFilenameFromPath(pathname) {
 
 const NoDomain = "(no domain)";
 const def = {
   path: "",
   group: "",
   filename: ""
 };
 
-function _getURL(source, debuggeeUrl) {
+function _getURL(source, defaultDomain) {
   const {
     url
   } = source;
 
   if (!url) {
     return def;
   }
 
@@ -93,21 +93,19 @@ function _getURL(source, debuggeeUrl) {
       if (pathname && pathname.startsWith("/")) {
         // use file protocol for a URL like "/foo/bar.js"
         return { ...def,
           path: path,
           filename,
           group: "file://"
         };
       } else if (host === null) {
-        // use anonymous group for weird URLs
-        const defaultDomain = (0, _url.parse)(debuggeeUrl).host;
         return { ...def,
           path: url,
-          group: defaultDomain,
+          group: defaultDomain || "",
           filename
         };
       }
 
       break;
 
     case "http:":
     case "https:":
@@ -120,18 +118,18 @@ function _getURL(source, debuggeeUrl) {
 
   return { ...def,
     path: path,
     group: protocol ? `${protocol}//` : "",
     filename
   };
 }
 
-function getURL(source, debuggeeUrl = "") {
+function getURL(source, debuggeeUrl) {
   if (urlMap.has(source)) {
     return urlMap.get(source) || def;
   }
 
-  const url = _getURL(source, debuggeeUrl);
+  const url = _getURL(source, debuggeeUrl || "");
 
   urlMap.set(source, url);
   return url;
 }
\ No newline at end of file
--- a/devtools/client/debugger/new/src/utils/sources-tree/updateTree.js
+++ b/devtools/client/debugger/new/src/utils/sources-tree/updateTree.js
@@ -8,16 +8,18 @@ exports.updateTree = updateTree;
 var _addToTree = require("./addToTree");
 
 var _collapseTree = require("./collapseTree");
 
 var _utils = require("./utils");
 
 var _lodash = require("devtools/client/shared/vendor/lodash");
 
+var _treeOrder = require("./treeOrder");
+
 /* 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/>. */
 function newSourcesSet(newSources, prevSources) {
   const newSourceIds = (0, _lodash.difference)(Object.keys(newSources), Object.keys(prevSources));
   const uniqSources = newSourceIds.map(id => newSources[id]);
   return uniqSources;
 }
@@ -26,19 +28,20 @@ function updateTree({
   newSources,
   prevSources,
   debuggeeUrl,
   projectRoot,
   uncollapsedTree,
   sourceTree
 }) {
   const newSet = newSourcesSet(newSources, prevSources);
+  const debuggeeHost = (0, _treeOrder.getDomain)(debuggeeUrl);
 
   for (const source of newSet) {
-    (0, _addToTree.addToTree)(uncollapsedTree, source, debuggeeUrl, projectRoot);
+    (0, _addToTree.addToTree)(uncollapsedTree, source, debuggeeHost, projectRoot);
   }
 
   const newSourceTree = (0, _collapseTree.collapseTree)(uncollapsedTree);
   return {
     uncollapsedTree,
     sourceTree: newSourceTree,
     parentMap: (0, _utils.createParentMap)(sourceTree),
     focusedItem: null
--- a/devtools/client/debugger/new/src/utils/sources-tree/utils.js
+++ b/devtools/client/debugger/new/src/utils/sources-tree/utils.js
@@ -17,16 +17,18 @@ exports.createDirectoryNode = createDire
 exports.createSourceNode = createSourceNode;
 exports.createParentMap = createParentMap;
 exports.getRelativePath = getRelativePath;
 
 var _url = require("devtools/client/debugger/new/dist/vendors").vendored["url"];
 
 var _source = require("../source");
 
+var _getURL = require("./getURL");
+
 /* 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/>. */
 const IGNORED_URLS = ["debugger eval code", "XStringBundle"];
 
 function nodeHasChildren(item) {
   return Array.isArray(item.contents) && item.type === "directory";
 }
@@ -64,28 +66,28 @@ function getSourceFromNode(item) {
     return contents;
   }
 }
 
 function isSource(item) {
   return item.type === "source";
 }
 
-function getFileExtension(url = "") {
-  const parsedUrl = (0, _url.parse)(url).pathname;
+function getFileExtension(source) {
+  const parsedUrl = (0, _getURL.getURL)(source).path;
 
   if (!parsedUrl) {
     return "";
   }
 
   return parsedUrl.split(".").pop();
 }
 
 function isNotJavaScript(source) {
-  return ["css", "svg", "png"].includes(getFileExtension(source.url));
+  return ["css", "svg", "png"].includes(getFileExtension(source));
 }
 
 function isInvalidUrl(url, source) {
   return IGNORED_URLS.indexOf(url) != -1 || !source.url || !url.group || (0, _source.isPretty)(source) || isNotJavaScript(source);
 }
 
 function partIsFile(index, parts, url) {
   const isLastPart = index === parts.length - 1;
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -258,17 +258,16 @@ devtools.jar:
     skin/images/help.svg (themes/images/help.svg)
     skin/images/read-only.svg (themes/images/read-only.svg)
 
     # Debugger
     skin/images/debugger/angular.svg (themes/images/debugger/angular.svg)
     skin/images/debugger/arrow.svg (themes/images/debugger/arrow.svg)
     skin/images/debugger/back.svg (themes/images/debugger/back.svg)
     skin/images/debugger/blackBox.svg (themes/images/debugger/blackBox.svg)
-    skin/images/debugger/breakpoint.svg (themes/images/debugger/breakpoint.svg)
     skin/images/debugger/close.svg (themes/images/debugger/close.svg)
     skin/images/debugger/coffeescript.svg (themes/images/debugger/coffeescript.svg)
     skin/images/debugger/disable-pausing.svg (themes/images/debugger/disable-pausing.svg)
     skin/images/debugger/domain.svg (themes/images/debugger/domain.svg)
     skin/images/debugger/extension.svg (themes/images/debugger/extension.svg)
     skin/images/debugger/file.svg (themes/images/debugger/file.svg)
     skin/images/debugger/folder.svg (themes/images/debugger/folder.svg)
     skin/images/debugger/forward.svg (themes/images/debugger/forward.svg)
deleted file mode 100644
--- a/devtools/client/themes/images/debugger/breakpoint.svg
+++ /dev/null
@@ -1,6 +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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 60 12">
-  <path id="base-path" d="M53.9,0H1C0.4,0,0,0.4,0,1v10c0,0.6,0.4,1,1,1h52.9c0.6,0,1.2-0.3,1.5-0.7L60,6l-4.4-5.3C55,0.3,54.5,0,53.9,0z"/>
-</svg>
--- a/dom/base/Attr.cpp
+++ b/dom/base/Attr.cpp
@@ -22,36 +22,26 @@
 #include "nsGkAtoms.h"
 #include "nsCOMArray.h"
 #include "nsNameSpaceManager.h"
 #include "nsNodeUtils.h"
 #include "nsTextNode.h"
 #include "mozAutoDocUpdate.h"
 #include "nsWrapperCacheInlines.h"
 
-nsIAttribute::nsIAttribute(nsDOMAttributeMap* aAttrMap,
-                           already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
-: nsINode(aNodeInfo), mAttrMap(aAttrMap)
-{
-}
-
-nsIAttribute::~nsIAttribute()
-{
-}
-
 namespace mozilla {
 namespace dom {
 
 //----------------------------------------------------------------------
 bool Attr::sInitialized;
 
 Attr::Attr(nsDOMAttributeMap *aAttrMap,
            already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
            const nsAString  &aValue)
-  : nsIAttribute(aAttrMap, aNodeInfo), mValue(aValue)
+  : nsINode(aNodeInfo), mAttrMap(aAttrMap), mValue(aValue)
 {
   MOZ_ASSERT(mNodeInfo, "We must get a nodeinfo here!");
   MOZ_ASSERT(mNodeInfo->NodeType() == ATTRIBUTE_NODE,
              "Wrong nodeType");
 
   // We don't add a reference to our content. It will tell us
   // to drop our reference when it goes away.
 }
@@ -84,27 +74,27 @@ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(
   }
   if (ownerElement &&
       mozilla::dom::FragmentOrElement::CanSkip(ownerElement, true)) {
     return true;
   }
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Attr)
-  return tmp->HasKnownLiveWrapperAndDoesNotNeedTracing(static_cast<nsIAttribute*>(tmp));
+  return tmp->HasKnownLiveWrapperAndDoesNotNeedTracing(tmp);
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Attr)
   return tmp->HasKnownLiveWrapper();
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
 
 // QueryInterface implementation for Attr
 NS_INTERFACE_TABLE_HEAD(Attr)
   NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
-  NS_INTERFACE_TABLE(Attr, nsINode, nsIAttribute, EventTarget)
+  NS_INTERFACE_TABLE(Attr, nsINode, EventTarget)
   NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Attr)
   NS_INTERFACE_MAP_ENTRY_TEAROFF(nsISupportsWeakReference,
                                  new nsNodeSupportsWeakRefTearoff(this))
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Attr)
 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(Attr,
                                                    nsNodeUtils::LastRelease(this))
--- a/dom/base/Attr.h
+++ b/dom/base/Attr.h
@@ -7,31 +7,31 @@
 /*
  * Implementation of DOM Core's Attr node.
  */
 
 #ifndef mozilla_dom_Attr_h
 #define mozilla_dom_Attr_h
 
 #include "mozilla/Attributes.h"
-#include "nsIAttribute.h"
+#include "nsINode.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsStubMutationObserver.h"
 
 class nsIDocument;
 
 namespace mozilla {
 class EventChainPreVisitor;
 namespace dom {
 
 // Attribute helper class used to wrap up an attribute with a dom
 // object that implements the DOM Attr interface.
-class Attr final : public nsIAttribute
+class Attr final : public nsINode
 {
   virtual ~Attr() {}
 
 public:
   Attr(nsDOMAttributeMap* aAttrMap,
        already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
        const nsAString& aValue);
 
@@ -46,38 +46,47 @@ public:
                                       nsIPrincipal* aSubjectPrincipal,
                                       ErrorResult& aError) override;
   virtual void GetNodeValueInternal(nsAString& aNodeValue) override;
   virtual void SetNodeValueInternal(const nsAString& aNodeValue,
                                     ErrorResult& aError) override;
 
   void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
 
-  // nsIAttribute interface
-  void SetMap(nsDOMAttributeMap *aMap) override;
+  nsDOMAttributeMap* GetMap()
+  {
+    return mAttrMap;
+  }
+
+  void SetMap(nsDOMAttributeMap *aMap);
+
   Element* GetElement() const;
-  nsresult SetOwnerDocument(nsIDocument* aDocument) override;
+
+  /**
+   * Called when our ownerElement is moved into a new document.
+   * Updates the nodeinfo of this node.
+   */
+  nsresult SetOwnerDocument(nsIDocument* aDocument);
 
   // nsINode interface
   virtual bool IsNodeOfType(uint32_t aFlags) const override;
   virtual uint32_t GetChildCount() const override;
   virtual nsIContent *GetChildAt_Deprecated(uint32_t aIndex) const override;
   virtual int32_t ComputeIndexOf(const nsINode* aPossibleChild) const override;
   virtual nsresult InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
                                      bool aNotify) override;
   virtual void RemoveChildNode(nsIContent* aKid, bool aNotify) override;
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
                          bool aPreallocateChildren) const override;
   virtual already_AddRefed<nsIURI> GetBaseURI(bool aTryUseXHRDocBaseURI = false) const override;
 
   static void Initialize();
   static void Shutdown();
 
-  NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Attr,
-                                                                   nsIAttribute)
+  NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(Attr)
 
   // WebIDL
   virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void GetName(nsAString& aName);
   void GetValue(nsAString& aValue);
 
   void SetValue(const nsAString& aValue, nsIPrincipal* aTriggeringPrincipal, ErrorResult& aRv);
@@ -95,15 +104,16 @@ protected:
   virtual Element* GetNameSpaceElement() override
   {
     return GetElement();
   }
 
   static bool sInitialized;
 
 private:
+  RefPtr<nsDOMAttributeMap> mAttrMap;
   nsString mValue;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_Attr_h */
--- a/dom/base/EventSource.cpp
+++ b/dom/base/EventSource.cpp
@@ -1189,17 +1189,17 @@ EventSourceImpl::ReestablishConnection()
   }
 
   nsresult rv;
   if (mIsMainThread) {
     rv = RestartConnection();
   } else {
     RefPtr<CallRestartConnection> runnable = new CallRestartConnection(this);
     ErrorResult result;
-    runnable->Dispatch(Terminating, result);
+    runnable->Dispatch(Canceling, result);
     MOZ_ASSERT(!result.Failed());
     rv = result.StealNSResult();
   }
   if (NS_FAILED(rv)) {
     return;
   }
 
   rv = mEventSource->CheckInnerWindowCorrectness();
@@ -1989,17 +1989,17 @@ EventSource::Constructor(const GlobalObj
   }
 
   // Worker side.
   WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(workerPrivate);
 
   RefPtr<InitRunnable> initRunnable =
     new InitRunnable(workerPrivate, eventSourceImp, aURL);
-  initRunnable->Dispatch(Terminating, aRv);
+  initRunnable->Dispatch(Canceling, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   aRv = initRunnable->ErrorCode();
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
@@ -2017,17 +2017,17 @@ EventSource::Constructor(const GlobalObj
 
     eventSource->mReadyState = EventSourceImpl::CONNECTING;
     return eventSource.forget();
   }
 
   // Let's connect to the server.
   RefPtr<ConnectRunnable> connectRunnable =
     new ConnectRunnable(workerPrivate, eventSourceImp);
-  connectRunnable->Dispatch(Terminating, aRv);
+  connectRunnable->Dispatch(Canceling, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   return eventSource.forget();
 }
 
 // nsWrapperCache
--- a/dom/base/ProcessGlobal.cpp
+++ b/dom/base/ProcessGlobal.cpp
@@ -2,18 +2,20 @@
 /* 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 http://mozilla.org/MPL/2.0/. */
 
 #include "ProcessGlobal.h"
 
 #include "nsContentCID.h"
+#include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/MessageManagerBinding.h"
 #include "mozilla/dom/ResolveSystemBinding.h"
+#include "mozilla/dom/ipc/SharedMap.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 bool ProcessGlobal::sWasCreated = false;
 
 ProcessGlobal::ProcessGlobal(nsFrameMessageManager* aMessageManager)
  : MessageManagerGlobal(aMessageManager),
@@ -67,16 +69,26 @@ ProcessGlobal::Get()
   }
   ProcessGlobal* global = static_cast<ProcessGlobal*>(service.get());
   if (global) {
     sWasCreated = true;
   }
   return global;
 }
 
+already_AddRefed<mozilla::dom::ipc::SharedMap>
+ProcessGlobal::SharedData()
+{
+  if (ContentChild* child = ContentChild::GetSingleton()) {
+    return do_AddRef(child->SharedData());
+  }
+  auto* ppmm = nsFrameMessageManager::sParentProcessManager;
+  return do_AddRef(ppmm->SharedData()->GetReadOnly());
+}
+
 bool
 ProcessGlobal::WasCreated()
 {
   return sWasCreated;
 }
 
 void
 ProcessGlobal::MarkForCC()
--- a/dom/base/ProcessGlobal.h
+++ b/dom/base/ProcessGlobal.h
@@ -20,16 +20,20 @@
 #include "nsIScriptObjectPrincipal.h"
 #include "nsServiceManagerUtils.h"
 #include "nsWeakReference.h"
 #include "nsWrapperCache.h"
 
 namespace mozilla {
 namespace dom {
 
+namespace ipc {
+  class SharedMap;
+}
+
 class ProcessGlobal :
   public nsIMessageSender,
   public nsMessageManagerScriptExecutor,
   public nsIGlobalObject,
   public nsIScriptObjectPrincipal,
   public nsSupportsWeakReference,
   public ipc::MessageManagerCallback,
   public MessageManagerGlobal,
@@ -79,16 +83,18 @@ public:
   {
     if (!mMessageManager) {
       aError.Throw(NS_ERROR_NULL_POINTER);
       return;
     }
     mMessageManager->GetInitialProcessData(aCx, aInitialProcessData, aError);
   }
 
+  already_AddRefed<ipc::SharedMap> SharedData();
+
   NS_FORWARD_SAFE_NSIMESSAGESENDER(mMessageManager)
 
   virtual void LoadScript(const nsAString& aURL);
 
   virtual JSObject* GetGlobalJSObject() override
   {
     return GetWrapper();
   }
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -65,17 +65,16 @@ EXPORTS += [
     'nsDOMTokenList.h',
     'nsFocusManager.h',
     'nsFrameLoader.h',  # Because binding headers include it.
     'nsFrameMessageManager.h',
     'nsGlobalWindow.h',  # Because binding headers include it.
     'nsGlobalWindowInner.h',  # Because binding headers include it.
     'nsGlobalWindowOuter.h',  # Because binding headers include it.
     'nsIAnimationObserver.h',
-    'nsIAttribute.h',
     'nsIContent.h',
     'nsIContentInlines.h',
     'nsIContentIterator.h',
     'nsIContentSerializer.h',
     'nsIdentifierMapEntry.h',
     'nsIDocument.h',
     'nsIDocumentInlines.h',
     'nsIDocumentObserver.h',
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -6750,30 +6750,28 @@ nsIDocument::GetCompatMode(nsString& aCo
 }
 
 void
 nsDOMAttributeMap::BlastSubtreeToPieces(nsINode* aNode)
 {
   if (Element* element = Element::FromNode(aNode)) {
     if (const nsDOMAttributeMap* map = element->GetAttributeMap()) {
       while (true) {
-        nsCOMPtr<nsIAttribute> attr;
+        RefPtr<Attr> attr;
         {
           // Use an iterator to get an arbitrary attribute from the
           // cache. The iterator must be destroyed before any other
           // operations on mAttributeCache, to avoid hash table
           // assertions.
           auto iter = map->mAttributeCache.ConstIter();
           if (iter.Done()) {
             break;
           }
           attr = iter.UserData();
         }
-        NS_ASSERTION(attr.get(),
-                     "non-nsIAttribute somehow made it into the hashmap?!");
 
         BlastSubtreeToPieces(attr);
 
         DebugOnly<nsresult> rv =
           element->UnsetAttr(attr->NodeInfo()->NamespaceID(),
                              attr->NodeInfo()->NameAtom(),
                              false);
 
--- a/dom/base/nsFrameMessageManager.cpp
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -43,16 +43,17 @@
 #include "mozilla/dom/ParentProcessMessageManager.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
 #include "mozilla/dom/ProcessGlobal.h"
 #include "mozilla/dom/ProcessMessageManager.h"
 #include "mozilla/dom/ResolveSystemBinding.h"
 #include "mozilla/dom/SameProcessMessageQueue.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/ipc/SharedMap.h"
 #include "mozilla/dom/ipc/StructuredCloneData.h"
 #include "mozilla/dom/DOMStringList.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "nsPrintfCString.h"
 #include "nsXULAppAPI.h"
 #include "nsQueryObject.h"
 #include "xpcprivate.h"
 #include <algorithm>
@@ -994,16 +995,29 @@ nsFrameMessageManager::GetInitialProcess
 
   if (!JS_WrapValue(aCx, &init)) {
     aError.NoteJSContextException(aCx);
     return;
   }
   aInitialProcessData.set(init);
 }
 
+WritableSharedMap*
+nsFrameMessageManager::SharedData()
+{
+  if (!mChrome || !mIsProcessManager) {
+    MOZ_ASSERT(false, "Should only call this binding method on ppmm");
+    return nullptr;
+  }
+  if (!mSharedData) {
+    mSharedData = new WritableSharedMap();
+  }
+  return mSharedData;
+}
+
 already_AddRefed<ProcessMessageManager>
 nsFrameMessageManager::GetProcessMessageManager(ErrorResult& aError)
 {
   RefPtr<ProcessMessageManager> pmm;
   if (mCallback) {
     pmm = mCallback->GetProcessMessageManager();
   }
   return pmm.forget();
--- a/dom/base/nsFrameMessageManager.h
+++ b/dom/base/nsFrameMessageManager.h
@@ -32,16 +32,21 @@
 #include "mozilla/dom/CallbackObject.h"
 #include "mozilla/dom/SameProcessMessageQueue.h"
 #include "mozilla/dom/ipc/StructuredCloneData.h"
 #include "mozilla/jsipc/CpowHolder.h"
 
 class nsFrameLoader;
 
 namespace mozilla {
+
+namespace ipc {
+  class FileDescriptor;
+}
+
 namespace dom {
 
 class nsIContentParent;
 class nsIContentChild;
 class ChildProcessMessageManager;
 class ChromeMessageBroadcaster;
 class ClonedMessageData;
 class MessageBroadcaster;
@@ -49,16 +54,18 @@ class MessageListener;
 class MessageListenerManager;
 class MessageManagerReporter;
 template<typename T> class Optional;
 class ParentProcessMessageManager;
 class ProcessMessageManager;
 
 namespace ipc {
 
+class WritableSharedMap;
+
 // Note: we round the time we spend to the nearest millisecond. So a min value
 // of 1 ms actually captures from 500us and above.
 static const uint32_t kMinTelemetrySyncMessageManagerLatencyMs = 1;
 
 enum class MessageManagerFlags {
   MM_NONE = 0,
   MM_CHROME = 1,
   MM_GLOBAL = 2,
@@ -228,16 +235,18 @@ public:
     SendMessage(aCx, aMessageName, aObj, aObjects, aPrincipal, false, aResult, aError);
   }
 
   // GlobalProcessScriptLoader
   void GetInitialProcessData(JSContext* aCx,
                              JS::MutableHandle<JS::Value> aInitialProcessData,
                              mozilla::ErrorResult& aError);
 
+  mozilla::dom::ipc::WritableSharedMap* SharedData();
+
   NS_DECL_NSIMESSAGESENDER
   NS_DECL_NSICONTENTFRAMEMESSAGEMANAGER
 
   static mozilla::dom::ProcessMessageManager* NewProcessMessageManager(bool aIsRemote);
 
   void ReceiveMessage(nsISupports* aTarget, nsFrameLoader* aTargetFrameLoader,
                       const nsAString& aMessage, bool aIsSync,
                       StructuredCloneData* aCloneData, mozilla::jsipc::CpowHolder* aCpows,
@@ -335,16 +344,17 @@ protected:
   bool mHandlingMessage;
   bool mClosed;    // true if we can no longer send messages
   bool mDisconnected;
   mozilla::dom::ipc::MessageManagerCallback* mCallback;
   nsAutoPtr<mozilla::dom::ipc::MessageManagerCallback> mOwnedCallback;
   nsTArray<nsString> mPendingScripts;
   nsTArray<bool> mPendingScriptsGlobalStates;
   JS::Heap<JS::Value> mInitialProcessData;
+  RefPtr<mozilla::dom::ipc::WritableSharedMap> mSharedData;
 
   void LoadPendingScripts(nsFrameMessageManager* aManager,
                           nsFrameMessageManager* aChildMM);
 public:
   static mozilla::dom::ParentProcessMessageManager* sParentProcessManager;
   static nsFrameMessageManager* sSameProcessParentManager;
   static nsTArray<nsCOMPtr<nsIRunnable> >* sPendingSameProcessAsyncMessages;
 private:
deleted file mode 100644
--- a/dom/base/nsIAttribute.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/* -*- 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 http://mozilla.org/MPL/2.0/. */
-
-#ifndef nsIAttribute_h___
-#define nsIAttribute_h___
-
-#include "nsINode.h"
-
-class nsDOMAttributeMap;
-
-#define NS_IATTRIBUTE_IID  \
-{ 0x84d43da7, 0xb45d, 0x47ae, \
-  { 0x8f, 0xbf, 0x95, 0x26, 0x78, 0x4d, 0x5e, 0x47 } }
-
-class nsIAttribute : public nsINode
-{
-public:
-  NS_DECLARE_STATIC_IID_ACCESSOR(NS_IATTRIBUTE_IID)
-
-  virtual void SetMap(nsDOMAttributeMap *aMap) = 0;
-
-  nsDOMAttributeMap *GetMap()
-  {
-    return mAttrMap;
-  }
-
-  mozilla::dom::NodeInfo *NodeInfo() const
-  {
-    return mNodeInfo;
-  }
-
-  /**
-   * Called when our ownerElement is moved into a new document.
-   * Updates the nodeinfo of this node.
-   */
-  virtual nsresult SetOwnerDocument(nsIDocument* aDocument) = 0;
-
-protected:
-#ifdef MOZILLA_INTERNAL_API
-  nsIAttribute(nsDOMAttributeMap *aAttrMap,
-               already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
-#endif //MOZILLA_INTERNAL_API
-  virtual ~nsIAttribute();
-
-  RefPtr<nsDOMAttributeMap> mAttrMap;
-};
-
-NS_DEFINE_STATIC_IID_ACCESSOR(nsIAttribute, NS_IATTRIBUTE_IID)
-
-#endif /* nsIAttribute_h___ */
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -278,17 +278,17 @@ nsINode::SubtreeRoot() const
   };
 
   // There are four cases of interest here.  nsINodes that are really:
   // 1. nsIDocument nodes - Are always in the document.
   // 2.a nsIContent nodes not in a shadow tree - Are either in the document,
   //     or mSubtreeRoot is updated in BindToTree/UnbindFromTree.
   // 2.b nsIContent nodes in a shadow tree - Are never in the document,
   //     ignore mSubtreeRoot and return the containing shadow root.
-  // 4. nsIAttribute nodes - Are never in the document, and mSubtreeRoot
+  // 4. Attr nodes - Are never in the document, and mSubtreeRoot
   //    is always 'this' (as set in nsINode's ctor).
   nsINode* node;
   if (IsInUncomposedDoc()) {
     node = OwnerDocAsNode();
   } else if (IsContent()) {
     ShadowRoot* containingShadow = AsContent()->GetContainingShadow();
     node = containingShadow ? containingShadow : mSubtreeRoot;
     if (!node) {
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -941,17 +941,17 @@ public:
    */
   nsIContent* GetParent() const {
     return MOZ_LIKELY(GetBoolFlag(ParentIsContent)) ?
       reinterpret_cast<nsIContent*>(mParent) : nullptr;
   }
 
   /**
    * Get the parent nsINode for this node. This can be either an nsIContent,
-   * an nsIDocument or an nsIAttribute.
+   * an nsIDocument or an Attr.
    * @return the parent node
    */
   nsINode* GetParentNode() const
   {
     return mParent;
   }
 
   /**
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -572,16 +572,29 @@ DOMInterfaces = {
     'nativeType': 'mozilla::dom::HTMLCanvasPrintState',
 },
 
 'MozChannel': {
     'nativeType': 'nsIChannel',
     'notflattened': True
 },
 
+'MozSharedMap': {
+    'nativeType': 'mozilla::dom::ipc::SharedMap',
+},
+
+'MozWritableSharedMap': {
+    'headerFile': 'mozilla/dom/ipc/SharedMap.h',
+    'nativeType': 'mozilla::dom::ipc::WritableSharedMap',
+},
+
+'MozSharedMapChangeEvent': {
+    'nativeType': 'mozilla::dom::ipc::SharedMapChangeEvent',
+},
+
 'MozStorageAsyncStatementParams': {
     'headerFile': 'mozilla/storage/mozStorageAsyncStatementParams.h',
     'nativeType': 'mozilla::storage::AsyncStatementParams',
 },
 
 'MozStorageStatementParams': {
     'headerFile': 'mozilla/storage/mozStorageStatementParams.h',
     'nativeType': 'mozilla::storage::StatementParams',
--- a/dom/cache/CacheWorkerHolder.cpp
+++ b/dom/cache/CacheWorkerHolder.cpp
@@ -15,17 +15,17 @@ namespace cache {
 
 // static
 already_AddRefed<CacheWorkerHolder>
 CacheWorkerHolder::Create(WorkerPrivate* aWorkerPrivate, Behavior aBehavior)
 {
   MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
 
   RefPtr<CacheWorkerHolder> workerHolder = new CacheWorkerHolder(aBehavior);
-  if (NS_WARN_IF(!workerHolder->HoldWorker(aWorkerPrivate, Terminating))) {
+  if (NS_WARN_IF(!workerHolder->HoldWorker(aWorkerPrivate, Canceling))) {
     return nullptr;
   }
 
   return workerHolder.forget();
 }
 
 // static
 already_AddRefed<CacheWorkerHolder>
@@ -88,19 +88,19 @@ CacheWorkerHolder::Notified() const
   return mNotified;
 }
 
 bool
 CacheWorkerHolder::Notify(WorkerStatus aStatus)
 {
   NS_ASSERT_OWNINGTHREAD(CacheWorkerHolder);
 
-  // When the service worker thread is stopped we will get Terminating,
-  // but nothing higher than that.  We must shut things down at Terminating.
-  if (aStatus < Terminating || mNotified) {
+  // When the service worker thread is stopped we will get Canceling,
+  // but nothing higher than that.  We must shut things down at Canceling.
+  if (aStatus < Canceling || mNotified) {
     return true;
   }
 
   mNotified = true;
 
   // Start the asynchronous destruction of our actors.  These will call back
   // into RemoveActor() once the actor is destroyed.
   for (uint32_t i = 0; i < mActorList.Length(); ++i) {
--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -1051,17 +1051,17 @@ ImageBitmap::CreateInternal(nsIGlobalObj
     RefPtr<CreateImageFromRawDataInMainThreadSyncTask> task
       = new CreateImageFromRawDataInMainThreadSyncTask(array.Data(),
                                                        dataLength,
                                                        imageStride,
                                                        FORMAT,
                                                        imageSize,
                                                        aCropRect,
                                                        getter_AddRefs(data));
-    task->Dispatch(Terminating, aRv);
+    task->Dispatch(Canceling, aRv);
   }
 
   if (NS_WARN_IF(!data)) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
   // Create an ImageBimtap.
@@ -2078,17 +2078,17 @@ ImageBitmap::Create(nsIGlobalObject* aGl
                                               aFormat, aLayout);
   } else {
     RefPtr<CreateImageFromBufferSourceRawDataInMainThreadSyncTask> task =
       new CreateImageFromBufferSourceRawDataInMainThreadSyncTask(bufferData + aOffset,
                                                                  bufferLength,
                                                                  aFormat,
                                                                  aLayout,
                                                                  getter_AddRefs(data));
-    task->Dispatch(Terminating, aRv);
+    task->Dispatch(Canceling, aRv);
     if (aRv.Failed()) {
       return promise.forget();
     }
   }
 
   if (NS_WARN_IF(!data)) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return promise.forget();
--- a/dom/chrome-webidl/MessageManager.webidl
+++ b/dom/chrome-webidl/MessageManager.webidl
@@ -452,16 +452,18 @@ interface GlobalProcessScriptLoader : Pr
    * attribute of its childprocessmessagemanager.
    *
    * This value will always be a JS object if it's not null or undefined. Different
    * users are expected to set properties on this object. The property name should be
    * unique enough that other Gecko consumers won't accidentally choose it.
    */
   [Throws]
   readonly attribute any initialProcessData;
+
+  readonly attribute MozWritableSharedMap sharedData;
 };
 
 [ChromeOnly, Global, NeedResolve]
 interface ContentFrameMessageManager : EventTarget
 {
   /**
    * The current top level window in the frame or null.
    */
@@ -497,16 +499,18 @@ ContentFrameMessageManager implements Me
 interface ContentProcessMessageManager
 {
   /**
    * Read out a copy of the object that was initialized in the parent
    * process via ProcessScriptLoader.initialProcessData.
    */
   [Throws]
   readonly attribute any initialProcessData;
+
+  readonly attribute MozSharedMap sharedData;
 };
 // MessageManagerGlobal inherits from SyncMessageSender, which is a real interface, not a
 // mixin. This will need to change when we implement mixins according to the current
 // WebIDL spec.
 ContentProcessMessageManager implements MessageManagerGlobal;
 
 /**
  * Message "broadcasters" don't have a single "other side" that they send messages to, but
new file mode 100644
--- /dev/null
+++ b/dom/chrome-webidl/MozSharedMap.webidl
@@ -0,0 +1,54 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ */
+
+typedef any StructuredClonable;
+
+[ChromeOnly]
+interface MozSharedMapChangeEvent : Event {
+  [Cached, Constant]
+  readonly attribute sequence<DOMString> changedKeys;
+};
+
+dictionary MozSharedMapChangeEventInit : EventInit {
+  required sequence<DOMString> changedKeys;
+};
+
+[ChromeOnly]
+interface MozSharedMap : EventTarget {
+  boolean has(DOMString name);
+
+  [Throws]
+  StructuredClonable get(DOMString name);
+
+  iterable<DOMString, StructuredClonable>;
+};
+
+[ChromeOnly]
+interface MozWritableSharedMap : MozSharedMap {
+  /**
+   * Sets the given key to the given structured-clonable value. The value is
+   * synchronously structured cloned, and the serialized value is saved in the
+   * map.
+   *
+   * Unless flush() is called, the new value will be broadcast to content
+   * processes after a short delay.
+   */
+  [Throws]
+  void set(DOMString name, StructuredClonable value);
+
+  /**
+   * Removes the given key from the map.
+   *
+   * Unless flush() is called, the removal will be broadcast to content
+   * processes after a short delay.
+   */
+  void delete(DOMString name);
+
+  /**
+   * Broadcasts any pending changes to all content processes.
+   */
+  void flush();
+};
--- a/dom/chrome-webidl/moz.build
+++ b/dom/chrome-webidl/moz.build
@@ -32,16 +32,17 @@ PREPROCESSED_WEBIDL_FILES = [
 WEBIDL_FILES = [
     'ChannelWrapper.webidl',
     'DominatorTree.webidl',
     'HeapSnapshot.webidl',
     'InspectorUtils.webidl',
     'MatchGlob.webidl',
     'MatchPattern.webidl',
     'MessageManager.webidl',
+    'MozSharedMap.webidl',
     'MozStorageAsyncStatementParams.webidl',
     'MozStorageStatementParams.webidl',
     'MozStorageStatementRow.webidl',
     'PrecompiledScript.webidl',
     'PromiseDebugging.webidl',
     'StructuredCloneHolder.webidl',
     'WebExtensionContentScript.webidl',
     'WebExtensionPolicy.webidl',
--- a/dom/clients/manager/ClientManager.cpp
+++ b/dom/clients/manager/ClientManager.cpp
@@ -62,17 +62,17 @@ ClientManager::ClientManager()
     // Note, it would be nice to replace this with a WorkerRef, but
     // currently there is no WorkerRef option that matches what we
     // need here.  We need something like a StrongWorkerRef that will
     // let us keep the worker alive until our actor is destroyed, but
     // we also need to use AllowIdleShutdownStart like WeakWorkerRef.
     // We need AllowIdleShutdownStart since every worker thread will
     // have a ClientManager to support creating its ClientSource.
     workerHolderToken =
-      WorkerHolderToken::Create(workerPrivate, Terminating,
+      WorkerHolderToken::Create(workerPrivate, Canceling,
                                 WorkerHolderToken::AllowIdleShutdownStart);
     if (NS_WARN_IF(!workerHolderToken)) {
       Shutdown();
       return;
     }
   }
 
   ClientManagerChild* actor = new ClientManagerChild(workerHolderToken);
--- a/dom/fetch/Request.cpp
+++ b/dom/fetch/Request.cpp
@@ -393,17 +393,17 @@ Request::Constructor(const GlobalObject&
           // ReferrerSameOriginChecker uses a sync loop to get the main thread
           // to perform the same-origin check.  Overall, on Workers this method
           // can create 3 sync loops (two for constructing URLs and one here) so
           // in the future we may want to optimize it all by off-loading all of
           // this work in a single sync loop.
           RefPtr<ReferrerSameOriginChecker> checker =
             new ReferrerSameOriginChecker(worker, referrerURL, rv);
           IgnoredErrorResult error;
-          checker->Dispatch(Terminating, error);
+          checker->Dispatch(Canceling, error);
           if (error.Failed() || NS_FAILED(rv)) {
             referrerURL.AssignLiteral(kFETCH_CLIENT_REFERRER_STR);
           }
         }
       }
       request->SetReferrer(referrerURL);
     }
   }
--- a/dom/file/FileBlobImpl.cpp
+++ b/dom/file/FileBlobImpl.cpp
@@ -173,17 +173,17 @@ FileBlobImpl::GetType(nsAString& aType)
         // return any valid value.
         return;
       }
 
       RefPtr<GetTypeRunnable> runnable =
         new GetTypeRunnable(workerPrivate, this);
 
       ErrorResult rv;
-      runnable->Dispatch(Terminating, rv);
+      runnable->Dispatch(Canceling, rv);
       if (NS_WARN_IF(rv.Failed())) {
         rv.SuppressException();
       }
       return;
     }
 
     nsresult rv;
     nsCOMPtr<nsIMIMEService> mimeService =
--- a/dom/file/FileReaderSync.cpp
+++ b/dom/file/FileReaderSync.cpp
@@ -439,17 +439,17 @@ FileReaderSync::SyncRead(nsIInputStream*
   nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(aStream);
   if (!asyncStream) {
     return rv;
   }
 
   WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(workerPrivate);
 
-  AutoSyncLoopHolder syncLoop(workerPrivate, Terminating);
+  AutoSyncLoopHolder syncLoop(workerPrivate, Canceling);
 
   nsCOMPtr<nsIEventTarget> syncLoopTarget = syncLoop.GetEventTarget();
   if (!syncLoopTarget) {
     // SyncLoop creation can fail if the worker is shutting down.
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
   RefPtr<ReadCallback> callback =
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -43,16 +43,17 @@
 #include "mozilla/dom/ProcessGlobal.h"
 #include "mozilla/dom/PushNotifier.h"
 #include "mozilla/dom/ServiceWorkerManager.h"
 #include "mozilla/dom/TabGroup.h"
 #include "mozilla/dom/nsIContentChild.h"
 #include "mozilla/dom/URLClassifierChild.h"
 #include "mozilla/dom/WorkerDebugger.h"
 #include "mozilla/dom/WorkerDebuggerManager.h"
+#include "mozilla/dom/ipc/SharedMap.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/Logging.h"
 #include "mozilla/psm/PSMContentListener.h"
 #include "mozilla/hal_sandbox/PHalChild.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/FileDescriptorSetChild.h"
 #include "mozilla/ipc/FileDescriptorUtils.h"
 #include "mozilla/ipc/GeckoChildProcessHost.h"
@@ -587,28 +588,33 @@ NS_INTERFACE_MAP_BEGIN(ContentChild)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentChild)
 NS_INTERFACE_MAP_END
 
 
 mozilla::ipc::IPCResult
 ContentChild::RecvSetXPCOMProcessAttributes(const XPCOMInitData& aXPCOMInit,
                                             const StructuredCloneData& aInitialData,
                                             nsTArray<LookAndFeelInt>&& aLookAndFeelIntCache,
-                                            nsTArray<SystemFontListEntry>&& aFontList)
+                                            nsTArray<SystemFontListEntry>&& aFontList,
+                                            const FileDescriptor& aSharedDataMapFile,
+                                            const uint32_t& aSharedDataMapSize)
 {
   if (!sShutdownCanary) {
     return IPC_OK();
   }
 
   mLookAndFeelCache = std::move(aLookAndFeelIntCache);
   mFontList = std::move(aFontList);
   gfx::gfxVars::SetValuesForInitialize(aXPCOMInit.gfxNonDefaultVarUpdates());
   InitXPCOM(aXPCOMInit, aInitialData);
   InitGraphicsDeviceData(aXPCOMInit.contentDeviceData());
 
+  mSharedData = new SharedMap(ProcessGlobal::Get(), aSharedDataMapFile,
+                              aSharedDataMapSize);
+
   return IPC_OK();
 }
 
 bool
 ContentChild::Init(MessageLoop* aIOLoop,
                    base::ProcessId aParentPid,
                    const char* aParentBuildID,
                    IPC::Channel* aChannel,
@@ -2373,16 +2379,18 @@ ContentChild::ActorDestroy(ActorDestroyR
 #else
   if (gFirstIdleTask) {
     gFirstIdleTask->Cancel();
     gFirstIdleTask = nullptr;
   }
 
   BlobURLProtocolHandler::RemoveDataEntries();
 
+  mSharedData = nullptr;
+
   mAlertObservers.Clear();
 
   mIdleObservers.Clear();
 
   nsCOMPtr<nsIConsoleService> svc(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
   if (svc) {
     svc->UnregisterListener(mConsoleListener);
     mConsoleListener->mChild = nullptr;
@@ -2553,16 +2561,36 @@ ContentChild::RecvRegisterStringBundles(
     stringBundleService->RegisterContentBundle(descriptor.bundleURL(), descriptor.mapFile(),
                                                descriptor.mapSize());
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+ContentChild::RecvUpdateSharedData(const FileDescriptor& aMapFile,
+                                   const uint32_t& aMapSize,
+                                   nsTArray<IPCBlob>&& aBlobs,
+                                   nsTArray<nsCString>&& aChangedKeys)
+{
+  if (mSharedData) {
+    nsTArray<RefPtr<BlobImpl>> blobImpls(aBlobs.Length());
+    for (auto& ipcBlob : aBlobs) {
+      blobImpls.AppendElement(IPCBlobUtils::Deserialize(ipcBlob));
+    }
+
+    mSharedData->Update(aMapFile, aMapSize,
+                        std::move(blobImpls),
+                        std::move(aChangedKeys));
+  }
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 ContentChild::RecvGeolocationUpdate(nsIDOMGeoPosition* aPosition)
 {
   nsCOMPtr<nsIGeolocationUpdate> gs =
     do_GetService("@mozilla.org/geolocation/service;1");
   if (!gs) {
     return IPC_OK();
   }
   gs->Update(aPosition);
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -61,16 +61,20 @@ nsresult GetObjDir(nsIFile **aObjDir);
 
 namespace ipc {
 class OptionalURIParams;
 class URIParams;
 }// namespace ipc
 
 namespace dom {
 
+namespace ipc {
+class SharedMap;
+}
+
 class AlertObserver;
 class ConsoleListener;
 class ClonedMessageData;
 class TabChild;
 class GetFilesHelperChild;
 class FileCreatorHelper;
 
 class ContentChild final : public PContentChild
@@ -159,16 +163,18 @@ public:
     mProfileDir = aProfileDir;
   }
 #endif
 
   bool IsAlive() const;
 
   bool IsShuttingDown() const;
 
+  ipc::SharedMap* SharedData() { return mSharedData; };
+
   static void AppendProcessId(nsACString& aName);
 
   static void UpdateCookieStatus(nsIChannel *aChannel);
 
   mozilla::ipc::IPCResult
   RecvInitContentBridgeChild(Endpoint<PContentBridgeChild>&& aEndpoint) override;
 
   mozilla::ipc::IPCResult
@@ -391,16 +397,21 @@ public:
 
   virtual mozilla::ipc::IPCResult RecvAsyncMessage(const nsString& aMsg,
                                                    InfallibleTArray<CpowEntry>&& aCpows,
                                                    const IPC::Principal& aPrincipal,
                                                    const ClonedMessageData& aData) override;
 
   mozilla::ipc::IPCResult RecvRegisterStringBundles(nsTArray<StringBundleDescriptor>&& stringBundles) override;
 
+  mozilla::ipc::IPCResult RecvUpdateSharedData(const FileDescriptor& aMapFile,
+                                               const uint32_t& aMapSize,
+                                               nsTArray<IPCBlob>&& aBlobs,
+                                               nsTArray<nsCString>&& aChangedKeys) override;
+
   virtual mozilla::ipc::IPCResult RecvGeolocationUpdate(nsIDOMGeoPosition* aPosition) override;
 
   virtual mozilla::ipc::IPCResult RecvGeolocationError(const uint16_t& errorCode) override;
 
   virtual mozilla::ipc::IPCResult RecvUpdateDictionaryList(InfallibleTArray<nsString>&& aDictionaries) override;
 
   virtual mozilla::ipc::IPCResult RecvUpdateFontList(InfallibleTArray<SystemFontListEntry>&& aFontList) override;
 
@@ -610,17 +621,19 @@ public:
           const bool& anonymize,
           const bool& minimizeMemoryUsage,
           const MaybeFileDesc& DMDFile) override;
 
   virtual mozilla::ipc::IPCResult
   RecvSetXPCOMProcessAttributes(const XPCOMInitData& aXPCOMInit,
                                 const StructuredCloneData& aInitialData,
                                 nsTArray<LookAndFeelInt>&& aLookAndFeelIntCache,
-                                nsTArray<SystemFontListEntry>&& aFontList) override;
+                                nsTArray<SystemFontListEntry>&& aFontList,
+                                const FileDescriptor& aSharedDataMapFile,
+                                const uint32_t& aSharedDataMapSize) override;
 
   virtual mozilla::ipc::IPCResult
   RecvProvideAnonymousTemporaryFile(const uint64_t& aID, const FileDescOrError& aFD) override;
 
   mozilla::ipc::IPCResult
   RecvSetPermissionsWithKey(const nsCString& aPermissionKey,
                             nsTArray<IPC::Permission>&& aPerms) override;
 
@@ -810,16 +823,18 @@ private:
   static ContentChild* sSingleton;
 
   class ShutdownCanary;
   static StaticAutoPtr<ShutdownCanary> sShutdownCanary;
 
   nsCOMPtr<nsIDomainPolicy> mPolicy;
   nsCOMPtr<nsITimer> mForceKillTimer;
 
+  RefPtr<ipc::SharedMap> mSharedData;
+
 #ifdef MOZ_GECKO_PROFILER
   RefPtr<ChildProfilerController> mProfilerController;
 #endif
 
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
   nsCOMPtr<nsIFile> mProfileDir;
 #endif
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -56,16 +56,17 @@
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/Permissions.h"
 #include "mozilla/dom/PresentationParent.h"
 #include "mozilla/dom/PPresentationParent.h"
 #include "mozilla/dom/PushNotifier.h"
 #include "mozilla/dom/quota/QuotaManagerService.h"
 #include "mozilla/dom/ServiceWorkerUtils.h"
 #include "mozilla/dom/URLClassifierParent.h"
+#include "mozilla/dom/ipc/SharedMap.h"
 #include "mozilla/embedding/printingui/PrintingParent.h"
 #include "mozilla/extensions/StreamFilterParent.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/hal_sandbox/PHalParent.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/CrashReporterHost.h"
@@ -2318,18 +2319,22 @@ ContentParent::InitInternal(ProcessPrior
 
   // Send the dynamic scalar definitions to the new process.
   TelemetryIPC::GetDynamicScalarDefinitions(xpcomInit.dynamicScalarDefs());
 
   // Must send screen info before send initialData
   ScreenManager& screenManager = ScreenManager::GetSingleton();
   screenManager.CopyScreensToRemote(this);
 
+  ipc::WritableSharedMap* sharedData = nsFrameMessageManager::sParentProcessManager->SharedData();
+  sharedData->Flush();
+
   Unused << SendSetXPCOMProcessAttributes(xpcomInit, initialData, lnfCache,
-                                          fontList);
+                                          fontList, sharedData->CloneMapFile(),
+                                          sharedData->MapSize());
 
   nsCOMPtr<nsIChromeRegistry> registrySvc = nsChromeRegistry::GetService();
   nsChromeRegistryChrome* chromeRegistry =
     static_cast<nsChromeRegistryChrome*>(registrySvc.get());
   chromeRegistry->SendRegisteredChrome(this);
 
   nsCOMPtr<nsIStringBundleService> stringBundleService =
     services::GetStringBundleService();
@@ -2868,16 +2873,17 @@ ContentParent::Observe(nsISupports* aSub
   if (mSubprocess && (!strcmp(aTopic, "profile-before-change") ||
                       !strcmp(aTopic, "xpcom-shutdown"))) {
     // Make sure that our process will get scheduled.
     ProcessPriorityManager::SetProcessPriority(this,
                                                PROCESS_PRIORITY_FOREGROUND);
 
     // Okay to call ShutDownProcess multiple times.
     ShutDownProcess(SEND_SHUTDOWN_MESSAGE);
+    MarkAsDead();
 
     // Wait for shutdown to complete, so that we receive any shutdown
     // data (e.g. telemetry) from the child before we quit.
     // This loop terminate prematurely based on mForceKillTimer.
     SpinEventLoopUntil([&]() { return !mIPCOpen || mCalledKillHard; });
     NS_ASSERTION(!mSubprocess, "Close should have nulled mSubprocess");
   }
 
--- a/dom/ipc/MemMapSnapshot.cpp
+++ b/dom/ipc/MemMapSnapshot.cpp
@@ -113,17 +113,29 @@ MemMapSnapshot::Freeze(AutoMemMap& aMem)
   // since we open and share a read-only file descriptor, and then delete the
   // file. But it doesn't hurt, either.
   chmod(mPath.get(), 0400);
 
   nsCOMPtr<nsIFile> file;
   MOZ_TRY(NS_NewNativeLocalFile(mPath, /* followLinks = */ false,
                                 getter_AddRefs(file)));
 
-  return aMem.init(file);
+  auto result = aMem.init(file);
+#ifdef XP_LINUX
+  // On Linux automation runs, every few hundred thousand calls, our attempt to
+  // stat the file that we just successfully opened fails with EBADF (bug
+  // 1472889). Presumably this is a race with a background thread that double
+  // closes a file, but is difficult to diagnose, so work around it by making a
+  // second mapping attempt if the first one fails.
+  if (!result.isOk()) {
+    aMem.reset();
+    result = aMem.init(file);
+  }
+#endif
+  return result;
 }
 
 #else
 #  error "Unsupported build configuration"
 #endif
 
 } // namespace ipc
 } // namespace mozilla
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -461,16 +461,20 @@ child:
 
     async UpdateAppLocales(nsCString[] appLocales);
     async UpdateRequestedLocales(nsCString[] requestedLocales);
 
     async ClearSiteDataReloadNeeded(nsString origin);
 
     async RegisterStringBundles(StringBundleDescriptor[] stringBundles);
 
+    async UpdateSharedData(FileDescriptor mapFile, uint32_t aSize,
+                           IPCBlob[] blobs,
+                           nsCString[] changedKeys);
+
     // nsIPermissionManager messages
     async AddPermission(Permission permission);
     async RemoveAllPermissions();
 
     async FlushMemory(nsString reason);
 
     async GarbageCollect();
     async CycleCollect();
@@ -508,17 +512,19 @@ child:
      * Send BlobURLRegistrationData to child process.
      */
     async InitBlobURLs(BlobURLRegistrationData[] registrations);
 
     async SetXPCOMProcessAttributes(XPCOMInitData xpcomInit,
                                     StructuredCloneData initialData,
                                     LookAndFeelInt[] lookAndFeelIntCache,
                                     /* used on MacOSX and Linux only: */
-                                    SystemFontListEntry[] systemFontList);
+                                    SystemFontListEntry[] systemFontList,
+                                    FileDescriptor sharedDataMapFile,
+                                    uint32_t sharedDataMapSize);
 
     // Notify child that last-pb-context-exited notification was observed
     async LastPrivateDocShellDestroyed();
 
     async NotifyProcessPriorityChanged(ProcessPriority priority);
     async MinimizeMemoryUsage();
 
     /**
new file mode 100644
--- /dev/null
+++ b/dom/ipc/SharedMap.cpp
@@ -0,0 +1,519 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "SharedMap.h"
+#include "SharedMapChangeEvent.h"
+
+#include "MemMapSnapshot.h"
+#include "ScriptPreloader-inl.h"
+
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/IPCBlobUtils.h"
+#include "mozilla/dom/ProcessGlobal.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+using namespace mozilla::loader;
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+namespace ipc {
+
+// Align to size of uintptr_t here, to be safe. It's probably not strictly
+// necessary, though.
+constexpr size_t kStructuredCloneAlign = sizeof(uintptr_t);
+
+
+static inline void
+AlignTo(size_t* aOffset, size_t aAlign)
+{
+  if (auto mod = *aOffset % aAlign) {
+    *aOffset += aAlign - mod;
+  }
+}
+
+
+SharedMap::SharedMap()
+  : DOMEventTargetHelper()
+{}
+
+SharedMap::SharedMap(nsIGlobalObject* aGlobal, const FileDescriptor& aMapFile,
+                     size_t aMapSize)
+  : DOMEventTargetHelper(aGlobal)
+{
+  mMapFile.reset(new FileDescriptor(aMapFile));
+  mMapSize = aMapSize;
+}
+
+
+bool
+SharedMap::Has(const nsACString& aName)
+{
+  return mEntries.Contains(aName);
+}
+
+void
+SharedMap::Get(JSContext* aCx,
+               const nsACString& aName,
+               JS::MutableHandleValue aRetVal,
+               ErrorResult& aRv)
+{
+  auto res = MaybeRebuild();
+  if (res.isErr()) {
+    aRv.Throw(res.unwrapErr());
+    return;
+  }
+
+  Entry* entry = mEntries.Get(aName);
+  if (!entry) {
+    aRetVal.setNull();
+    return;
+  }
+
+  entry->Read(aCx, aRetVal, aRv);
+}
+
+void
+SharedMap::Entry::Read(JSContext* aCx,
+                       JS::MutableHandleValue aRetVal,
+                       ErrorResult& aRv)
+{
+  if (mData.is<StructuredCloneData>()) {
+    // We have a temporary buffer for a key that was changed after the last
+    // snapshot. Just decode it directly.
+    auto& holder = mData.as<StructuredCloneData>();
+    holder.Read(aCx, aRetVal, aRv);
+    return;
+  }
+
+  // We have a pointer to a shared memory region containing our structured
+  // clone data. Create a temporary buffer to decode that data, and then
+  // discard it so that we don't keep a separate process-local copy around any
+  // longer than necessary.
+  StructuredCloneData holder;
+  if (!holder.CopyExternalData(Data(), Size())) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
+  if (mBlobCount) {
+    holder.BlobImpls().AppendElements(Blobs());
+  }
+  holder.Read(aCx, aRetVal, aRv);
+}
+
+FileDescriptor
+SharedMap::CloneMapFile()
+{
+  if (mMap.initialized()) {
+    return mMap.cloneHandle();
+  }
+  return *mMapFile;
+}
+
+void
+SharedMap::Update(const FileDescriptor& aMapFile, size_t aMapSize,
+                  nsTArray<RefPtr<BlobImpl>>&& aBlobs,
+                  nsTArray<nsCString>&& aChangedKeys)
+{
+  MOZ_DIAGNOSTIC_ASSERT(!mWritable);
+
+  mMap.reset();
+  if (mMapFile) {
+    *mMapFile = aMapFile;
+  } else {
+    mMapFile.reset(new FileDescriptor(aMapFile));
+  }
+  mMapSize = aMapSize;
+  mEntries.Clear();
+  mEntryArray.reset();
+
+  mBlobImpls = std::move(aBlobs);
+
+
+  AutoEntryScript aes(GetParentObject(), "SharedMap change event");
+  JSContext* cx = aes.cx();
+
+  RootedDictionary<MozSharedMapChangeEventInit> init(cx);
+  if (!init.mChangedKeys.SetCapacity(aChangedKeys.Length(), fallible)) {
+    NS_WARNING("Failed to dispatch SharedMap change event");
+    return;
+  }
+  for (auto& key : aChangedKeys) {
+    Unused << init.mChangedKeys.AppendElement(NS_ConvertUTF8toUTF16(key),
+                                              fallible);
+  }
+
+  RefPtr<SharedMapChangeEvent> event =
+    SharedMapChangeEvent::Constructor(this, NS_LITERAL_STRING("change"), init);
+  event->SetTrusted(true);
+
+  DispatchEvent(*event);
+}
+
+
+const nsTArray<SharedMap::Entry*>&
+SharedMap::EntryArray() const
+{
+  if (mEntryArray.isNothing()) {
+    MaybeRebuild();
+
+    mEntryArray.emplace(mEntries.Count());
+    auto& array = mEntryArray.ref();
+    for (auto& entry : IterHash(mEntries)) {
+      array.AppendElement(entry);
+    }
+  }
+
+  return mEntryArray.ref();
+}
+
+const nsString
+SharedMap::GetKeyAtIndex(uint32_t aIndex) const
+{
+  return NS_ConvertUTF8toUTF16(EntryArray()[aIndex]->Name());
+}
+
+JS::Value
+SharedMap::GetValueAtIndex(uint32_t aIndex) const
+{
+  JSObject* wrapper = GetWrapper();
+  MOZ_ASSERT(wrapper,
+             "Should never see GetValueAtIndex on a SharedMap without a live "
+             "wrapper");
+  if (!wrapper) {
+    return JS::NullValue();
+  }
+
+  AutoJSContext cx;
+
+  JSAutoRealm ar(cx, wrapper);
+
+  JS::RootedValue val(cx);
+  EntryArray()[aIndex]->Read(cx, &val, IgnoreErrors());
+
+  return val;
+}
+
+void
+SharedMap::Entry::TakeData(StructuredCloneData&& aHolder)
+{
+  mData = AsVariant(std::move(aHolder));
+
+  mSize = Holder().Data().Size();
+  mBlobCount = Holder().BlobImpls().Length();
+}
+
+void
+SharedMap::Entry::ExtractData(char* aDestPtr, uint32_t aNewOffset, uint16_t aNewBlobOffset)
+{
+  if (mData.is<StructuredCloneData>()) {
+    char* ptr = aDestPtr;
+    Holder().Data().ForEachDataChunk([&](const char* aData, size_t aSize) {
+        memcpy(ptr, aData, aSize);
+        ptr += aSize;
+        return true;
+    });
+    MOZ_ASSERT(uint32_t(ptr - aDestPtr) == mSize);
+  } else {
+    memcpy(aDestPtr, Data(), mSize);
+  }
+
+  mData = AsVariant(aNewOffset);
+  mBlobOffset = aNewBlobOffset;
+}
+
+Result<Ok, nsresult>
+SharedMap::MaybeRebuild()
+{
+  if (!mMapFile) {
+    return Ok();
+  }
+
+  // This function maps a shared memory region created by Serialize() and reads
+  // its header block to build a new mEntries hashtable of its contents.
+  //
+  // The entries created by this function contain a pointer to this SharedMap
+  // instance, and the offsets and sizes of their structured clone data within
+  // its shared memory region. When needed, that structured clone data is
+  // retrieved directly as indexes into the SharedMap's shared memory region.
+
+  MOZ_TRY(mMap.initWithHandle(*mMapFile, mMapSize));
+  mMapFile.reset();
+
+  // We should be able to pass this range as an initializer list or an immediate
+  // param, but gcc currently chokes on that if optimization is enabled, and
+  // initializes everything to 0.
+  Range<uint8_t> range(&mMap.get<uint8_t>()[0], mMap.size());
+  InputBuffer buffer(range);
+
+  uint32_t count;
+  buffer.codeUint32(count);
+
+  for (uint32_t i = 0; i < count; i++) {
+    auto entry = MakeUnique<Entry>(*this);
+    entry->Code(buffer);
+
+    // This buffer was created at runtime, during this session, so any errors
+    // indicate memory corruption, and are fatal.
+    MOZ_RELEASE_ASSERT(!buffer.error());
+
+    // Note: Order of evaluation of function arguments is not guaranteed, so we
+    // can't use entry.release() in place of entry.get() without entry->Name()
+    // sometimes resulting in a null dereference.
+    mEntries.Put(entry->Name(), entry.get());
+    Unused << entry.release();
+  }
+
+  return Ok();
+}
+
+void
+SharedMap::MaybeRebuild() const
+{
+  Unused << const_cast<SharedMap*>(this)->MaybeRebuild();
+}
+
+WritableSharedMap::WritableSharedMap()
+  : SharedMap()
+{
+  mWritable = true;
+  // Serialize the initial empty contents of the map immediately so that we
+  // always have a file descriptor to send to callers of CloneMapFile().
+  Unused << Serialize();
+  MOZ_RELEASE_ASSERT(mMap.initialized());
+}
+
+SharedMap*
+WritableSharedMap::GetReadOnly()
+{
+  if (!mReadOnly) {
+    mReadOnly = new SharedMap(ProcessGlobal::Get(), CloneMapFile(),
+                              MapSize());
+  }
+  return mReadOnly;
+}
+
+Result<Ok, nsresult>
+WritableSharedMap::Serialize()
+{
+  // Serializes a new snapshot of the map, initializes a new read-only shared
+  // memory region with its contents, and updates all entries to point to that
+  // new snapshot.
+  //
+  // The layout of the snapshot is as follows:
+  //
+  // - A header containing a uint32 count field containing the number of
+  //   entries in the map, followed by that number of serialized entry headers,
+  //   as produced by Entry::Code.
+  //
+  // - A data block containing structured clone data for each of the entries'
+  //   values. This data is referenced by absolute byte offsets from the start
+  //   of the shared memory region, encoded in each of the entry header values.
+  //   Each entry's data is aligned to kStructuredCloneAlign, and therefore may
+  //   have alignment padding before it.
+  //
+  // This serialization format is decoded by the MaybeRebuild() method of
+  // read-only SharedMap() instances, and used to populate their mEntries
+  // hashtables.
+  //
+  // Writable instances never read the header blocks, but instead directly
+  // update their Entry instances to point to the appropriate offsets in the
+  // shared memory region created by this function.
+
+  uint32_t count = mEntries.Count();
+
+  size_t dataSize = 0;
+  size_t headerSize = sizeof(count);
+  size_t blobCount = 0;
+
+  for (auto& entry : IterHash(mEntries)) {
+    headerSize += entry->HeaderSize();
+    blobCount += entry->BlobCount();
+
+    dataSize += entry->Size();
+    AlignTo(&dataSize, kStructuredCloneAlign);
+  }
+
+  size_t offset = headerSize;
+  AlignTo(&offset, kStructuredCloneAlign);
+
+  OutputBuffer header;
+  header.codeUint32(count);
+
+  MemMapSnapshot mem;
+  MOZ_TRY(mem.Init(offset + dataSize));
+
+  auto ptr = mem.Get<char>();
+
+  // We need to build the new array of blobs before we overwrite the existing
+  // one, since previously-serialized entries will store their blob references
+  // as indexes into our blobs array.
+  nsTArray<RefPtr<BlobImpl>> blobImpls(blobCount);
+
+  for (auto& entry : IterHash(mEntries)) {
+    AlignTo(&offset, kStructuredCloneAlign);
+
+    entry->ExtractData(&ptr[offset], offset, blobImpls.Length());
+    entry->Code(header);
+
+    offset += entry->Size();
+
+    if (entry->BlobCount()) {
+      mBlobImpls.AppendElements(entry->Blobs());
+    }
+  }
+
+  mBlobImpls = std::move(blobImpls);
+
+  // FIXME: We should create a separate OutputBuffer class which can encode to
+  // a static memory region rather than dynamically allocating and then
+  // copying.
+  MOZ_ASSERT(header.cursor() == headerSize);
+  memcpy(ptr.get(), header.Get(), header.cursor());
+
+  // We've already updated offsets at this point. We need this to succeed.
+  mMap.reset();
+  MOZ_RELEASE_ASSERT(mem.Finalize(mMap).isOk());
+
+  return Ok();
+}
+
+void
+WritableSharedMap::BroadcastChanges()
+{
+  if (mChangedKeys.IsEmpty()) {
+    return;
+  }
+
+  if (!Serialize().isOk()) {
+    return;
+  }
+
+  nsTArray<ContentParent*> parents;
+  ContentParent::GetAll(parents);
+  for (auto& parent : parents) {
+    nsTArray<IPCBlob> blobs(mBlobImpls.Length());
+
+    for (auto& blobImpl : mBlobImpls) {
+      nsresult rv = IPCBlobUtils::Serialize(blobImpl, parent,
+                                            *blobs.AppendElement());
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        continue;
+      }
+    }
+
+    Unused << parent->SendUpdateSharedData(CloneMapFile(), mMap.size(),
+                                           blobs, mChangedKeys);
+  }
+
+  if (mReadOnly) {
+    nsTArray<RefPtr<BlobImpl>> blobImpls(mBlobImpls);
+    mReadOnly->Update(CloneMapFile(), mMap.size(),
+                      std::move(blobImpls),
+                      std::move(mChangedKeys));
+  }
+
+  mChangedKeys.Clear();
+}
+
+void
+WritableSharedMap::Delete(const nsACString& aName)
+{
+  if (mEntries.Remove(aName)) {
+    KeyChanged(aName);
+  }
+}
+
+void
+WritableSharedMap::Set(JSContext* aCx,
+                       const nsACString& aName,
+                       JS::HandleValue aValue,
+                       ErrorResult& aRv)
+{
+  StructuredCloneData holder;
+
+  holder.Write(aCx, aValue, aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+
+  if (!holder.InputStreams().IsEmpty()) {
+    aRv.Throw(NS_ERROR_INVALID_ARG);
+    return;
+  }
+
+  Entry* entry = mEntries.LookupOrAdd(aName, *this, aName);
+  entry->TakeData(std::move(holder));
+
+  KeyChanged(aName);
+}
+
+void
+WritableSharedMap::Flush()
+{
+  BroadcastChanges();
+}
+
+void
+WritableSharedMap::IdleFlush()
+{
+  mPendingFlush = false;
+  Flush();
+}
+
+nsresult
+WritableSharedMap::KeyChanged(const nsACString& aName)
+{
+  if (!mChangedKeys.ContainsSorted(aName)) {
+    mChangedKeys.InsertElementSorted(aName);
+  }
+  mEntryArray.reset();
+
+  if (!mPendingFlush) {
+      MOZ_TRY(NS_IdleDispatchToCurrentThread(
+        NewRunnableMethod("WritableSharedMap::IdleFlush",
+                          this,
+                          &WritableSharedMap::IdleFlush)));
+      mPendingFlush = true;
+  }
+  return NS_OK;
+}
+
+
+JSObject*
+SharedMap::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
+{
+  return MozSharedMap_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+JSObject*
+WritableSharedMap::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
+{
+  return MozWritableSharedMap_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+/* static */ already_AddRefed<SharedMapChangeEvent>
+SharedMapChangeEvent::Constructor(EventTarget* aEventTarget,
+                                  const nsAString& aType,
+                                  const MozSharedMapChangeEventInit& aInit)
+{
+  RefPtr<SharedMapChangeEvent> event = new SharedMapChangeEvent(aEventTarget);
+
+  bool trusted = event->Init(aEventTarget);
+  event->InitEvent(aType, aInit.mBubbles, aInit.mCancelable);
+  event->SetTrusted(trusted);
+  event->SetComposed(aInit.mComposed);
+
+  event->mChangedKeys = aInit.mChangedKeys;
+
+  return event.forget();
+}
+
+} // ipc
+} // dom
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/dom/ipc/SharedMap.h
@@ -0,0 +1,393 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef dom_ipc_SharedMap_h
+#define dom_ipc_SharedMap_h
+
+#include "mozilla/dom/MozSharedMapBinding.h"
+
+#include "mozilla/AutoMemMap.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Variant.h"
+#include "nsClassHashtable.h"
+#include "nsTArray.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+namespace ipc {
+
+/**
+ * Together, the SharedMap and WritableSharedMap classes allow sharing a
+ * dynamically-updated, shared-memory key-value store across processes.
+ *
+ * The maps may only ever be updated in the parent process, via
+ * WritableSharedMap instances. When that map changes, its entire contents are
+ * serialized into a contiguous shared memory buffer, and broadcast to all child
+ * processes, which in turn update their entire map contents wholesale.
+ *
+ * Keys are arbitrary UTF-8 strings (currently exposed to JavaScript as UTF-16),
+ * and values are structured clone buffers. Values are eagerly encoded whenever
+ * they are updated, and lazily decoded each time they're read.
+ *
+ * Updates are batched. Rather than each key change triggering an immediate
+ * update, combined updates are broadcast after a delay. Changes are flushed
+ * immediately any time a new process is created. Additionally, any time a key
+ * is changed, a flush task is scheduled for the next time the event loop
+ * becomes idle. Changes can be flushed immediately by calling the flush()
+ * method.
+ *
+ *
+ * Whenever a read-only SharedMap is updated, it dispatches a "change" event.
+ * The event contains a "changedKeys" property with a list of all keys which
+ * were changed in the last update batch. Change events are never dispatched to
+ * WritableSharedMap instances.
+ */
+class SharedMap : public DOMEventTargetHelper
+{
+  using FileDescriptor = mozilla::ipc::FileDescriptor;
+
+public:
+
+  SharedMap();
+
+  SharedMap(nsIGlobalObject* aGlobal, const FileDescriptor&, size_t);
+
+  // Returns true if the map contains the given (UTF-8) key.
+  bool Has(const nsACString& name);
+
+  // If the map contains the given (UTF-8) key, decodes and returns a new copy
+  // of its value. Otherwise returns null.
+  void Get(JSContext* cx, const nsACString& name, JS::MutableHandleValue aRetVal,
+           ErrorResult& aRv);
+
+
+  // Conversion helpers for WebIDL callers
+  bool Has(const nsAString& aName)
+  {
+    return Has(NS_ConvertUTF16toUTF8(aName));
+  }
+
+  void Get(JSContext* aCx, const nsAString& aName, JS::MutableHandleValue aRetVal,
+           ErrorResult& aRv)
+  {
+    return Get(aCx, NS_ConvertUTF16toUTF8(aName), aRetVal, aRv);
+  }
+
+
+  /**
+   * WebIDL iterator glue.
+   */
+  uint32_t GetIterableLength() const
+  {
+    return EntryArray().Length();
+  }
+
+  /**
+   * These functions return the key or value, respectively, at the given index.
+   * The index *must* be less than the value returned by GetIterableLength(), or
+   * the program will crash.
+   */
+  const nsString GetKeyAtIndex(uint32_t aIndex) const;
+  // Note: This function should only be called if the instance has a live,
+  // cached wrapper. If it does not, this function will return null, and assert
+  // in debug builds.
+  // The returned value will always be in the same Realm as that wrapper.
+  JS::Value GetValueAtIndex(uint32_t aIndex) const;
+
+
+  /**
+   * Returns a copy of the read-only file descriptor which backs the shared
+   * memory region for this map. The file descriptor may be passed between
+   * processes, and used to update corresponding instances in child processes.
+   */
+  FileDescriptor CloneMapFile();
+
+  /**
+   * Returns the size of the memory mapped region that backs this map. Must be
+   * passed to the SharedMap() constructor or Update() method along with the
+   * descriptor returned by CloneMapFile() in order to initialize or update a
+   * child SharedMap.
+   */
+  size_t MapSize() const { return mMap.size(); }
+
+  /**
+   * Updates this instance to reflect the contents of the shared memory region
+   * in the given map file, and broadcasts a change event for the given set of
+   * changed (UTF-8-encoded) keys.
+   */
+  void Update(const FileDescriptor& aMapFile, size_t aMapSize,
+              nsTArray<RefPtr<BlobImpl>>&& aBlobs,
+              nsTArray<nsCString>&& aChangedKeys);
+
+
+  JSObject* WrapObject(JSContext* aCx, JS::HandleObject aGivenProto) override;
+
+protected:
+  ~SharedMap() override = default;
+
+  class Entry
+  {
+  public:
+    Entry(Entry&&) = delete;
+
+    explicit Entry(SharedMap& aMap, const nsACString& aName = EmptyCString())
+      : mMap(aMap)
+      , mName(aName)
+      , mData(AsVariant(uint32_t(0)))
+    {
+    }
+
+    ~Entry() = default;
+
+    /**
+     * Encodes or decodes this entry into or from the given OutputBuffer or
+     * InputBuffer.
+     */
+    template<typename Buffer>
+    void Code(Buffer& buffer)
+    {
+      DebugOnly<size_t> startOffset = buffer.cursor();
+
+      buffer.codeString(mName);
+      buffer.codeUint32(DataOffset());
+      buffer.codeUint32(mSize);
+      buffer.codeUint16(mBlobOffset);
+      buffer.codeUint16(mBlobCount);
+
+      MOZ_ASSERT(buffer.cursor() == startOffset + HeaderSize());
+    }
+
+    /**
+     * Returns the size that this entry will take up in the map header. This
+     * must be equal to the number of bytes encoded by Code().
+     */
+    size_t HeaderSize() const
+    {
+      return (sizeof(uint16_t) + mName.Length() +
+              sizeof(DataOffset()) +
+              sizeof(mSize) +
+              sizeof(mBlobOffset) +
+              sizeof(mBlobCount));
+    }
+
+    /**
+     * Updates the value of this entry to the given structured clone data, of
+     * which it takes ownership. The passed StructuredCloneData object must not
+     * be used after this call.
+     */
+    void TakeData(StructuredCloneData&&);
+
+    /**
+     * This is called while building a new snapshot of the SharedMap. aDestPtr
+     * must point to a buffer within the new snapshot with Size() bytes reserved
+     * for it, and `aNewOffset` must be the offset of that buffer from the start
+     * of the snapshot's memory region.
+     *
+     * This function copies the raw structured clone data for the entry's value
+     * to the new buffer, and updates its internal state for use with the new
+     * data. Its offset is updated to aNewOffset, and any StructuredCloneData
+     * object it holds is destroyed.
+     *
+     * After this call, the entry is only valid in reference to the new
+     * snapshot, and must not be accessed again until the SharedMap mMap has been
+     * updated to point to it.
+     */
+    void ExtractData(char* aDestPtr, uint32_t aNewOffset, uint16_t aNewBlobOffset);
+
+    // Returns the UTF-8-encoded name of the entry, which is used as its key in
+    // the map.
+    const nsCString& Name() const { return mName; }
+
+    // Decodes the entry's value into the current Realm of the given JS context
+    // and puts the result in aRetVal on success.
+    void Read(JSContext* aCx, JS::MutableHandleValue aRetVal,
+              ErrorResult& aRv);
+
+    // Returns the byte size of the entry's raw structured clone data.
+    uint32_t Size() const { return mSize; }
+
+  private:
+    // Returns a pointer to the entry value's structured clone data within the
+    // SharedMap's mapped memory region. This is *only* valid shen mData
+    // contains a uint32_t.
+    const char* Data() const
+    {
+      return mMap.Data() + DataOffset();
+    }
+
+    // Returns the offset of the entry value's structured clone data within the
+    // SharedMap's mapped memory region. This is *only* valid shen mData
+    // contains a uint32_t.
+    uint32_t& DataOffset()
+    {
+      return mData.as<uint32_t>();
+    }
+    const uint32_t& DataOffset() const
+    {
+      return mData.as<uint32_t>();
+    }
+
+  public:
+    uint16_t BlobOffset() const { return mBlobOffset; }
+    uint16_t BlobCount() const { return mBlobCount; }
+
+    Span<const RefPtr<BlobImpl>> Blobs()
+    {
+      if (mData.is<StructuredCloneData>()) {
+        return mData.as<StructuredCloneData>().BlobImpls();
+      }
+      return {&mMap.mBlobImpls[mBlobOffset], BlobCount()};
+    }
+
+  private:
+    // Returns the temporary StructuredCloneData object containing the entry's
+    // value. This is *only* value when mData contains a StructuredCloneDAta
+    // object.
+    const StructuredCloneData& Holder() const
+    {
+      return mData.as<StructuredCloneData>();
+    }
+
+    SharedMap& mMap;
+
+    // The entry's (UTF-8 encoded) name, which serves as its key in the map.
+    nsCString mName;
+
+    /**
+     * This member provides a reference to the entry's structured clone data.
+     * Its type varies depending on the state of the entry:
+     *
+     * - For entries which have been snapshotted into a shared memory region,
+     *   this is a uint32_t offset into the parent SharedMap's Data() buffer.
+     *
+     * - For entries which have been changed in a WritableSharedMap instance,
+     *   but not serialized to a shared memory snapshot yet, this is a
+     *   StructuredCloneData instance, containing a process-local copy of the
+     *   data. This will be discarded the next time the map is serialized, and
+     *   replaced with a buffer offset, as described above.
+     */
+    Variant<uint32_t, StructuredCloneData> mData;
+
+    // The size, in bytes, of the entry's structured clone data.
+    uint32_t mSize = 0;
+
+    uint16_t mBlobOffset = 0;
+    uint16_t mBlobCount = 0;
+  };
+
+  const nsTArray<Entry*>& EntryArray() const;
+
+  nsTArray<RefPtr<BlobImpl>> mBlobImpls;
+
+  // Rebuilds the entry hashtable mEntries from the values serialized in the
+  // current snapshot, if necessary. The hashtable is rebuilt lazily after
+  // construction and after every Update() call, so this function must be called
+  // before any attempt to access mEntries.
+  Result<Ok, nsresult> MaybeRebuild();
+  void MaybeRebuild() const;
+
+  // Note: This header is included by WebIDL binding headers, and therefore
+  // can't include "windows.h". Since FileDescriptor.h does include "windows.h"
+  // on Windows, we can only forward declare FileDescriptor, and can't include
+  // it as an inline member.
+  UniquePtr<FileDescriptor> mMapFile;
+  // The size of the memory-mapped region backed by mMapFile, in bytes.
+  size_t mMapSize = 0;
+
+  mutable nsClassHashtable<nsCStringHashKey, Entry> mEntries;
+  mutable Maybe<nsTArray<Entry*>> mEntryArray;
+
+  // Manages the memory mapping of the current snapshot. This is initialized
+  // lazily after each SharedMap construction or updated, based on the values in
+  // mMapFile and mMapSize.
+  loader::AutoMemMap mMap;
+
+  bool mWritable = false;
+
+  // Returns a pointer to the beginning of the memory mapped snapshot. Entry
+  // offsets are relative to this pointer, and Entry objects access their
+  // structured clone data by indexing this pointer.
+  char* Data() { return mMap.get<char>().get(); }
+};
+
+class WritableSharedMap final : public SharedMap
+{
+public:
+
+  WritableSharedMap();
+
+  // Sets the value of the given (UTF-8 encoded) key to a structured clone
+  // snapshot of the given value.
+  void Set(JSContext* cx, const nsACString& name, JS::HandleValue value, ErrorResult& aRv);
+
+  // Deletes the given (UTF-8 encoded) key from the map.
+  void Delete(const nsACString& name);
+
+
+  // Conversion helpers for WebIDL callers
+  void Set(JSContext* aCx, const nsAString& aName, JS::HandleValue aValue, ErrorResult& aRv)
+  {
+    return Set(aCx, NS_ConvertUTF16toUTF8(aName), aValue, aRv);
+  }
+
+  void Delete(const nsAString& aName)
+  {
+    return Delete(NS_ConvertUTF16toUTF8(aName));
+  }
+
+
+  // Flushes any queued changes to a new snapshot, and broadcasts it to all
+  // child SharedMap instances.
+  void Flush();
+
+
+  /**
+   * Returns the read-only SharedMap instance corresponding to this
+   * WritableSharedMap for use in the parent process.
+   */
+  SharedMap* GetReadOnly();
+
+
+  JSObject* WrapObject(JSContext* aCx, JS::HandleObject aGivenProto) override;
+
+protected:
+  ~WritableSharedMap() override = default;
+
+private:
+  // The set of (UTF-8 encoded) keys which have changed, or been deleted, since
+  // the last snapshot.
+  nsTArray<nsCString> mChangedKeys;
+
+  RefPtr<SharedMap> mReadOnly;
+
+  bool mPendingFlush = false;
+
+  // Creates a new snapshot of the map, and updates all Entry instance to
+  // reference its data.
+  Result<Ok, nsresult> Serialize();
+
+  void IdleFlush();
+
+  // If there have been any changes since the last snapshot, creates a new
+  // serialization and broadcasts it to all child SharedMap instances.
+  void BroadcastChanges();
+
+  // Marks the given (UTF-8 encoded) key as having changed. This adds it to
+  // mChangedKeys, if not already present, and schedules a flush for the next
+  // time the event loop is idle.
+  nsresult KeyChanged(const nsACString& aName);
+};
+
+} // ipc
+} // dom
+} // mozilla
+
+#endif // dom_ipc_SharedMap_h
new file mode 100644
--- /dev/null
+++ b/dom/ipc/SharedMapChangeEvent.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=99: */
+/* 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 dom_ipc_SharedMapChangeEvent_h
+#define dom_ipc_SharedMapChangeEvent_h
+
+#include "mozilla/dom/MozSharedMapBinding.h"
+
+#include "mozilla/dom/Event.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace dom {
+namespace ipc {
+
+class SharedMapChangeEvent final : public Event
+{
+public:
+  NS_INLINE_DECL_REFCOUNTING_INHERITED(SharedMapChangeEvent, Event)
+
+  JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
+  {
+    return MozSharedMapChangeEvent_Binding::Wrap(aCx, this, aGivenProto);
+  }
+
+  static already_AddRefed<SharedMapChangeEvent>
+  Constructor(EventTarget* aEventTarget,
+              const nsAString& aType,
+              const MozSharedMapChangeEventInit& aInit);
+
+  void GetChangedKeys(nsTArray<nsString>& aChangedKeys) const
+  {
+    aChangedKeys.AppendElements(mChangedKeys);
+  }
+
+protected:
+  ~SharedMapChangeEvent() override = default;
+
+private:
+  explicit SharedMapChangeEvent(EventTarget* aEventTarget)
+    : Event(aEventTarget, nullptr, nullptr)
+  {}
+
+  nsTArray<nsString> mChangedKeys;
+};
+
+} // ipc
+} // dom
+} // mozilla
+
+#endif // dom_ipc_SharedMapChangeEvent_h
--- a/dom/ipc/StructuredCloneData.cpp
+++ b/dom/ipc/StructuredCloneData.cpp
@@ -24,16 +24,21 @@
 #include "jsapi.h"
 
 using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace dom {
 namespace ipc {
 
+using mozilla::ipc::AutoIPCStream;
+using mozilla::ipc::IPCStream;
+using mozilla::ipc::PBackgroundChild;
+using mozilla::ipc::PBackgroundParent;
+
 StructuredCloneData::StructuredCloneData()
   : StructuredCloneData(StructuredCloneHolder::TransferringSupported)
 {}
 
 StructuredCloneData::StructuredCloneData(StructuredCloneData&& aOther)
   : StructuredCloneData(StructuredCloneHolder::TransferringSupported)
 {
   *this = std::move(aOther);
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -10,16 +10,18 @@ with Files("**"):
 XPIDL_SOURCES += [
     'nsIHangReport.idl',
 ]
 
 XPIDL_MODULE = 'dom'
 
 EXPORTS.mozilla.dom.ipc += [
     'IdType.h',
+    'SharedMap.h',
+    'SharedMapChangeEvent.h',
     'SharedStringMap.h',
     'StructuredCloneData.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'CoalescedInputData.h',
     'CoalescedMouseData.h',
     'CoalescedWheelData.h',
@@ -64,16 +66,17 @@ UNIFIED_SOURCES += [
     'FilePickerParent.cpp',
     'MemMapSnapshot.cpp',
     'MemoryReportRequest.cpp',
     'nsIContentChild.cpp',
     'nsIContentParent.cpp',
     'PermissionMessageUtils.cpp',
     'PreallocatedProcessManager.cpp',
     'ProcessPriorityManager.cpp',
+    'SharedMap.cpp',
     'SharedStringMap.cpp',
     'StructuredCloneData.cpp',
     'TabChild.cpp',
     'TabContext.cpp',
     'TabMessageUtils.cpp',
     'TabParent.cpp',
     'URLClassifierParent.cpp',
 ]
@@ -167,16 +170,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'andr
 if CONFIG['MOZ_TOOLKIT_SEARCH']:
     DEFINES['MOZ_TOOLKIT_SEARCH'] = True
 
 JAR_MANIFESTS += ['jar.mn']
 
 BROWSER_CHROME_MANIFESTS += ['tests/browser.ini']
 MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']
 MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
+XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell.ini']
 
 CXXFLAGS += CONFIG['TK_CFLAGS']
 
 if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
     CXXFLAGS += ['-Wno-error=shadow']
 
 if CONFIG['NIGHTLY_BUILD']:
     DEFINES['ASYNC_CONTENTPROC_LAUNCH'] = True
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/test_sharedMap.js
@@ -0,0 +1,162 @@
+"use strict";
+
+ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource://testing-common/ExtensionXPCShellUtils.jsm");
+
+const remote = AppConstants.platform !== "android";
+
+ExtensionTestUtils.init(this);
+
+let contentPage;
+
+function getContents(sharedMap = Services.cpmm.sharedData) {
+  return {
+    keys: Array.from(sharedMap.keys()),
+    values: Array.from(sharedMap.values()),
+    entries: Array.from(sharedMap.entries()),
+    getValues: Array.from(sharedMap.keys(),
+                          key => sharedMap.get(key)),
+  };
+}
+
+function checkMap(contents, expected) {
+  expected = Array.from(expected);
+
+  equal(contents.keys.length, expected.length,
+        "Got correct number of keys");
+  equal(contents.values.length, expected.length,
+        "Got correct number of values");
+  equal(contents.entries.length, expected.length,
+        "Got correct number of entries");
+
+  for (let [i, [key, val]] of contents.entries.entries()) {
+    equal(key, contents.keys[i], `keys()[${i}] matches entries()[${i}]`);
+    deepEqual(val, contents.values[i], `values()[${i}] matches entries()[${i}]`);
+  }
+
+  expected.sort(([a], [b]) => a.localeCompare(b));
+  contents.entries.sort(([a], [b]) => a.localeCompare(b));
+
+  for (let [i, [key, val]] of contents.entries.entries()) {
+    equal(key, expected[i][0], `expected[${i}].key matches entries()[${i}].key`);
+    deepEqual(val, expected[i][1], `expected[${i}].value matches entries()[${i}].value`);
+  }
+}
+
+function checkParentMap(expected) {
+  info("Checking parent map");
+  checkMap(getContents(Services.ppmm.sharedData), expected);
+}
+
+async function checkContentMaps(expected, parentOnly = false) {
+  info("Checking in-process content map");
+  checkMap(getContents(Services.cpmm.sharedData), expected);
+
+  if (!parentOnly) {
+    info("Checking out-of-process content map");
+    let contents = await contentPage.spawn(undefined, getContents);
+    checkMap(contents, expected);
+  }
+}
+
+add_task(async function setup() {
+  contentPage = await ExtensionTestUtils.loadContentPage("about:blank", {remote});
+  registerCleanupFunction(() => contentPage.close());
+});
+
+add_task(async function test_sharedMap() {
+  let {sharedData} = Services.ppmm;
+
+  info("Check that parent and child maps are both initially empty");
+
+  checkParentMap([]);
+  await checkContentMaps([]);
+
+  let expected = [
+    ["foo-a", {"foo": "a"}],
+    ["foo-b", {"foo": "b"}],
+    ["bar-c", null],
+    ["bar-d", 42],
+  ];
+
+  function setKey(key, val) {
+    sharedData.set(key, val);
+    expected = expected.filter(([k]) => k != key);
+    expected.push([key, val]);
+  }
+  function deleteKey(key) {
+    sharedData.delete(key);
+    expected = expected.filter(([k]) => k != key);
+  }
+
+  for (let [key, val] of expected) {
+    sharedData.set(key, val);
+  }
+
+  info("Add some entries, test that they are initially only available in the parent");
+
+  checkParentMap(expected);
+  await checkContentMaps([]);
+
+  info("Flush. Check that changes are visible in both parent and children");
+
+  sharedData.flush();
+
+  checkParentMap(expected);
+  await checkContentMaps(expected);
+
+  info("Add another entry. Check that it is initially only available in the parent");
+
+  let oldExpected = Array.from(expected);
+
+  setKey("baz-a", {meh: "meh"});
+
+  // When we do several checks in a row, we can't check the values in
+  // the content process, since the async checks may allow the idle
+  // flush task to run, and update it before we're ready.
+
+  checkParentMap(expected);
+  checkContentMaps(oldExpected, true);
+
+  info("Add another entry. Check that both new entries are only available in the parent");
+
+  setKey("baz-a", {meh: 12});
+
+  checkParentMap(expected);
+  checkContentMaps(oldExpected, true);
+
+  info("Delete an entry. Check that all changes are only visible in the parent");
+
+  deleteKey("foo-b");
+
+  checkParentMap(expected);
+  checkContentMaps(oldExpected, true);
+
+  info("Flush. Check that all entries are available in both parent and children");
+
+  sharedData.flush();
+
+  checkParentMap(expected);
+  await checkContentMaps(expected);
+
+
+  info("Test that entries are automatically flushed on idle:");
+
+  info("Add a new entry. Check that it is initially only available in the parent");
+
+  // Test the idle flush task.
+  oldExpected = Array.from(expected);
+
+  setKey("thing", "stuff");
+
+  checkParentMap(expected);
+  checkContentMaps(oldExpected, true);
+
+  info("Wait for an idle timeout. Check that changes are now visible in all children");
+
+  await new Promise(resolve => ChromeUtils.idleDispatch(resolve));
+
+  checkParentMap(expected);
+  await checkContentMaps(expected);
+});
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/xpcshell.ini
@@ -0,0 +1,2 @@
+
+[test_sharedMap.js]
--- a/dom/network/ConnectionWorker.cpp
+++ b/dom/network/ConnectionWorker.cpp
@@ -166,17 +166,17 @@ ConnectionWorker::Create(WorkerPrivate* 
     aRv.ThrowTypeError<MSG_WORKER_THREAD_SHUTTING_DOWN>();
     return nullptr;
   }
 
   hal::NetworkInformation networkInfo;
   RefPtr<InitializeRunnable> runnable =
     new InitializeRunnable(aWorkerPrivate, c->mProxy, networkInfo);
 
-  runnable->Dispatch(Terminating, aRv);
+  runnable->Dispatch(Canceling, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   c->Update(static_cast<ConnectionType>(networkInfo.type()),
             networkInfo.isWifi(), networkInfo.dhcpGateway(), false);
   return c.forget();
 }
--- a/dom/notification/Notification.cpp
+++ b/dom/notification/Notification.cpp
@@ -1828,17 +1828,17 @@ Notification::GetPermission(nsIGlobalObj
 {
   if (NS_IsMainThread()) {
     return GetPermissionInternal(aGlobal, aRv);
   } else {
     WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
     MOZ_ASSERT(worker);
     RefPtr<GetPermissionRunnable> r =
       new GetPermissionRunnable(worker);
-    r->Dispatch(Terminating, aRv);
+    r->Dispatch(Canceling, aRv);
     if (aRv.Failed()) {
       return NotificationPermission::Denied;
     }
 
     return r->GetPermission();
   }
 }
 
@@ -2584,17 +2584,17 @@ Notification::ShowPersistentNotification
       return nullptr;
     }
   } else {
     WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
     MOZ_ASSERT(worker);
     worker->AssertIsOnWorkerThread();
     RefPtr<CheckLoadRunnable> loadChecker =
       new CheckLoadRunnable(worker, NS_ConvertUTF16toUTF8(aScope));
-    loadChecker->Dispatch(Terminating, aRv);
+    loadChecker->Dispatch(Canceling, aRv);
     if (aRv.Failed()) {
       return nullptr;
     }
 
     if (NS_WARN_IF(NS_FAILED(loadChecker->Result()))) {
       if (loadChecker->Result() == NS_ERROR_NOT_AVAILABLE) {
         aRv.ThrowTypeError<MSG_NO_ACTIVE_WORKER>(aScope);
       } else {
--- a/dom/quota/StorageManager.cpp
+++ b/dom/quota/StorageManager.cpp
@@ -353,26 +353,26 @@ ExecuteOpOnMainOrWorkerThread(nsIGlobalO
     return nullptr;
   }
 
   switch (aType) {
     case RequestResolver::Type::Estimate: {
       RefPtr<EstimateWorkerMainThreadRunnable> runnnable =
         new EstimateWorkerMainThreadRunnable(promiseProxy->GetWorkerPrivate(),
                                              promiseProxy);
-      runnnable->Dispatch(Terminating, aRv);
+      runnnable->Dispatch(Canceling, aRv);
 
       break;
     }
 
     case RequestResolver::Type::Persisted: {
       RefPtr<PersistedWorkerMainThreadRunnable> runnnable =
         new PersistedWorkerMainThreadRunnable(promiseProxy->GetWorkerPrivate(),
                                               promiseProxy);
-      runnnable->Dispatch(Terminating, aRv);
+      runnnable->Dispatch(Canceling, aRv);
 
       break;
     }
 
     default:
       MOZ_CRASH("Invalid aRequest type");
   }
 
--- a/dom/serviceworkers/RemoteServiceWorkerContainerImpl.cpp
+++ b/dom/serviceworkers/RemoteServiceWorkerContainerImpl.cpp
@@ -193,17 +193,17 @@ RemoteServiceWorkerContainerImpl::Remote
   }
 
   RefPtr<WorkerHolderToken> workerHolderToken;
   if (!NS_IsMainThread()) {
     WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
     MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
 
     workerHolderToken =
-      WorkerHolderToken::Create(workerPrivate, Terminating,
+      WorkerHolderToken::Create(workerPrivate, Canceling,
                                 WorkerHolderToken::AllowIdleShutdownStart);
 
     if (NS_WARN_IF(!workerHolderToken)) {
       Shutdown();
       return;
     }
   }
 
--- a/dom/serviceworkers/RemoteServiceWorkerImpl.cpp
+++ b/dom/serviceworkers/RemoteServiceWorkerImpl.cpp
@@ -97,17 +97,17 @@ RemoteServiceWorkerImpl::RemoteServiceWo
   }
 
   RefPtr<WorkerHolderToken> workerHolderToken;
   if (!NS_IsMainThread()) {
     WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
     MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
 
     workerHolderToken =
-      WorkerHolderToken::Create(workerPrivate, Terminating,
+      WorkerHolderToken::Create(workerPrivate, Canceling,
                                 WorkerHolderToken::AllowIdleShutdownStart);
 
     if (NS_WARN_IF(!workerHolderToken)) {
       Shutdown();
       return;
     }
   }
 
--- a/dom/serviceworkers/RemoteServiceWorkerRegistrationImpl.cpp
+++ b/dom/serviceworkers/RemoteServiceWorkerRegistrationImpl.cpp
@@ -118,17 +118,17 @@ RemoteServiceWorkerRegistrationImpl::Rem
   }
 
   RefPtr<WorkerHolderToken> workerHolderToken;
   if (!NS_IsMainThread()) {
     WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
     MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
 
     workerHolderToken =
-      WorkerHolderToken::Create(workerPrivate, Terminating,
+      WorkerHolderToken::Create(workerPrivate, Canceling,
                                 WorkerHolderToken::AllowIdleShutdownStart);
 
     if (NS_WARN_IF(!workerHolderToken)) {
       Shutdown();
       return;
     }
   }
 
--- a/dom/serviceworkers/ServiceWorkerPrivate.cpp
+++ b/dom/serviceworkers/ServiceWorkerPrivate.cpp
@@ -279,17 +279,17 @@ public:
   }
 
   bool
   UseWorkerHolder()
   {
     MOZ_ASSERT(mWorkerPrivate);
     mWorkerPrivate->AssertIsOnWorkerThread();
     MOZ_ASSERT(!mWorkerHolderAdded);
-    mWorkerHolderAdded = HoldWorker(mWorkerPrivate, Terminating);
+    mWorkerHolderAdded = HoldWorker(mWorkerPrivate, Canceling);
     return mWorkerHolderAdded;
   }
 
   bool
   WaitOnPromise(Promise& aPromise) override
   {
     if (!mKeepAliveToken) {
       MOZ_ASSERT(!mSelfRef, "We shouldn't be holding a self reference!");
@@ -318,17 +318,17 @@ public:
     RemovePromise(Rejected);
   }
 
   bool
   Notify(WorkerStatus aStatus) override
   {
     MOZ_ASSERT(mWorkerPrivate);
     mWorkerPrivate->AssertIsOnWorkerThread();
-    if (aStatus < Terminating) {
+    if (aStatus < Canceling) {
       return true;
     }
 
     MaybeCleanup();
     return true;
   }
 
   void
@@ -714,29 +714,29 @@ public:
 
     // We need to listen for worker termination in case the event handler
     // never completes or never resolves the waitUntil promise. There are
     // two possible scenarios:
     // 1. The keepAlive token expires and the worker is terminated, in which
     //    case the registration/update promise will be rejected
     // 2. A new service worker is registered which will terminate the current
     //    installing worker.
-    if (NS_WARN_IF(!HoldWorker(mWorkerPrivate, Terminating))) {
+    if (NS_WARN_IF(!HoldWorker(mWorkerPrivate, Canceling))) {
       NS_WARNING("LifeCycleEventWatcher failed to add feature.");
       ReportResult(false);
       return false;
     }
 
     return true;
   }
 
   bool
   Notify(WorkerStatus aStatus) override
   {
-    if (aStatus < Terminating) {
+    if (aStatus < Canceling) {
       return true;
     }
 
     MOZ_ASSERT(GetCurrentThreadWorkerPrivate() == mWorkerPrivate);
     ReportResult(false);
 
     return true;
   }
@@ -1969,17 +1969,17 @@ ServiceWorkerPrivate::TerminateWorker()
   if (mWorkerPrivate) {
     if (DOMPrefs::ServiceWorkersTestingEnabled()) {
       nsCOMPtr<nsIObserverService> os = services::GetObserverService();
       if (os) {
         os->NotifyObservers(nullptr, "service-worker-shutdown", nullptr);
       }
     }
 
-    Unused << NS_WARN_IF(!mWorkerPrivate->Terminate());
+    Unused << NS_WARN_IF(!mWorkerPrivate->Cancel());
     mWorkerPrivate = nullptr;
     mSupportsArray.Clear();
 
     // Any pending events are never going to fire on this worker.  Cancel
     // them so that intercepted channels can be reset and other resources
     // cleaned up.
     nsTArray<RefPtr<WorkerRunnable>> pendingEvents;
     mPendingFunctionalEvents.SwapElements(pendingEvents);
--- a/dom/url/URLWorker.cpp
+++ b/dom/url/URLWorker.cpp
@@ -266,17 +266,17 @@ public:
     ErrorResult rv;
     nsContentUtils::GetUTFOrigin(mURI, mValue);
     return true;
   }
 
   void
   Dispatch(ErrorResult& aRv)
   {
-    WorkerMainThreadRunnable::Dispatch(Terminating, aRv);
+    WorkerMainThreadRunnable::Dispatch(Canceling, aRv);
   }
 
 private:
   nsAString& mValue;
   nsCOMPtr<nsIURI> mURI;
 };
 
 class ProtocolSetterRunnable : public WorkerMainThreadRunnable
@@ -320,17 +320,17 @@ public:
 
     mRetval = std::move(uri);
     return true;
   }
 
   void
   Dispatch(ErrorResult& aRv)
   {
-    WorkerMainThreadRunnable::Dispatch(Terminating, aRv);
+    WorkerMainThreadRunnable::Dispatch(Canceling, aRv);
   }
 
   nsIURI*
   GetRetval() const
   {
     return mRetval;
   }
 
@@ -376,17 +376,17 @@ URLWorker::CreateObjectURL(const GlobalO
   aRv = blobImpl->SetMutable(false);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   RefPtr<CreateURLRunnable> runnable =
     new CreateURLRunnable(workerPrivate, blobImpl, aResult);
 
-  runnable->Dispatch(Terminating, aRv);
+  runnable->Dispatch(Canceling, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   if (workerPrivate->IsSharedWorker() || workerPrivate->IsServiceWorker()) {
     WorkerGlobalScope* scope = workerPrivate->GlobalScope();
     MOZ_ASSERT(scope);
 
@@ -399,17 +399,17 @@ URLWorker::RevokeObjectURL(const GlobalO
                            ErrorResult& aRv)
 {
   JSContext* cx = aGlobal.Context();
   WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
 
   RefPtr<RevokeURLRunnable> runnable =
     new RevokeURLRunnable(workerPrivate, aUrl);
 
-  runnable->Dispatch(Terminating, aRv);
+  runnable->Dispatch(Canceling, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   if (workerPrivate->IsSharedWorker() || workerPrivate->IsServiceWorker()) {
     WorkerGlobalScope* scope = workerPrivate->GlobalScope();
     MOZ_ASSERT(scope);
 
@@ -422,17 +422,17 @@ URLWorker::IsValidURL(const GlobalObject
                       ErrorResult& aRv)
 {
   JSContext* cx = aGlobal.Context();
   WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
 
   RefPtr<IsValidURLRunnable> runnable =
     new IsValidURLRunnable(workerPrivate, aUrl);
 
-  runnable->Dispatch(Terminating, aRv);
+  runnable->Dispatch(Canceling, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return false;
   }
 
   return runnable->IsValidURL();
 }
 
 URLWorker::URLWorker(WorkerPrivate* aWorkerPrivate)
@@ -457,17 +457,17 @@ URLWorker::Init(const nsAString& aURL, c
       aRv.ThrowTypeError<MSG_INVALID_URL>(aURL);
       return;
     }
   }
 
   // create url proxy
   RefPtr<ConstructorRunnable> runnable =
     new ConstructorRunnable(mWorkerPrivate, aURL, aBase);
-  runnable->Dispatch(Terminating, aRv);
+  runnable->Dispatch(Canceling, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   nsCOMPtr<nsIURI> uri = runnable->GetURI(aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
@@ -484,17 +484,17 @@ URLWorker::SetHref(const nsAString& aHre
   nsresult rv = net_ExtractURLScheme(NS_ConvertUTF16toUTF8(aHref), scheme);
   if (NS_FAILED(rv)) {
     aRv.ThrowTypeError<MSG_INVALID_URL>(aHref);
     return;
   }
 
   RefPtr<ConstructorRunnable> runnable =
     new ConstructorRunnable(mWorkerPrivate, aHref, Optional<nsAString>());
-  runnable->Dispatch(Terminating, aRv);
+  runnable->Dispatch(Canceling, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   nsCOMPtr<nsIURI> uri = runnable->GetURI(aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
--- a/dom/websocket/WebSocket.cpp
+++ b/dom/websocket/WebSocket.cpp
@@ -1393,17 +1393,17 @@ WebSocket::ConstructorCommon(const Globa
                                     &column)) {
       NS_WARNING("Failed to get line number and filename in workers.");
     }
 
     RefPtr<InitRunnable> runnable =
       new InitRunnable(workerPrivate, webSocketImpl, !!aTransportProvider, aUrl,
                        protocolArray, nsDependentCString(file.get()), lineno,
                        column);
-    runnable->Dispatch(Terminating, aRv);
+    runnable->Dispatch(Canceling, aRv);
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
 
     aRv = runnable->ErrorCode();
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
@@ -1496,17 +1496,17 @@ WebSocket::ConstructorCommon(const Globa
 
     aRv = webSocket->mImpl->AsyncOpen(principal, windowID, aTransportProvider,
                                       aNegotiatedExtensions);
   } else {
     MOZ_ASSERT(!aTransportProvider && aNegotiatedExtensions.IsEmpty(),
                "not yet implemented");
     RefPtr<AsyncOpenRunnable> runnable =
       new AsyncOpenRunnable(webSocket->mImpl);
-    runnable->Dispatch(Terminating, aRv);
+    runnable->Dispatch(Canceling, aRv);
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
 
     aRv = runnable->ErrorCode();
   }
 
   if (NS_WARN_IF(aRv.Failed())) {
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -2209,17 +2209,17 @@ ScriptExecutorRunnable::LogExceptionToCo
 void
 LoadAllScripts(WorkerPrivate* aWorkerPrivate,
                nsTArray<ScriptLoadInfo>& aLoadInfos, bool aIsMainScript,
                WorkerScriptType aWorkerScriptType, ErrorResult& aRv)
 {
   aWorkerPrivate->AssertIsOnWorkerThread();
   NS_ASSERTION(!aLoadInfos.IsEmpty(), "Bad arguments!");
 
-  AutoSyncLoopHolder syncLoop(aWorkerPrivate, Terminating);
+  AutoSyncLoopHolder syncLoop(aWorkerPrivate, Canceling);
   nsCOMPtr<nsIEventTarget> syncLoopTarget = syncLoop.GetEventTarget();
   if (!syncLoopTarget) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   Maybe<ClientInfo> clientInfo;
   Maybe<ServiceWorkerDescriptor> controller;
@@ -2293,17 +2293,17 @@ ChannelFromScriptURLWorkerThread(JSConte
                                  WorkerLoadInfo& aLoadInfo)
 {
   aParent->AssertIsOnWorkerThread();
 
   RefPtr<ChannelGetterRunnable> getter =
     new ChannelGetterRunnable(aParent, aScriptURL, aLoadInfo);
 
   ErrorResult rv;
-  getter->Dispatch(Terminating, rv);
+  getter->Dispatch(Canceling, rv);
   if (rv.Failed()) {
     NS_ERROR("Failed to dispatch!");
     return rv.StealNSResult();
   }
 
   return getter->GetResult();
 }
 
--- a/dom/workers/Worker.cpp
+++ b/dom/workers/Worker.cpp
@@ -125,17 +125,17 @@ Worker::PostMessage(JSContext* aCx, JS::
 }
 
 void
 Worker::Terminate()
 {
   NS_ASSERT_OWNINGTHREAD(Worker);
 
   if (mWorkerPrivate) {
-    mWorkerPrivate->Terminate();
+    mWorkerPrivate->Cancel();
     mWorkerPrivate = nullptr;
   }
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(Worker)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Worker, DOMEventTargetHelper)
   if (tmp->mWorkerPrivate) {
--- a/dom/workers/WorkerHolder.cpp
+++ b/dom/workers/WorkerHolder.cpp
@@ -38,17 +38,17 @@ WorkerHolder::~WorkerHolder()
 }
 
 bool
 WorkerHolder::HoldWorker(WorkerPrivate* aWorkerPrivate,
                          WorkerStatus aFailStatus)
 {
   AssertOnOwningThread(mThread);
   MOZ_ASSERT(aWorkerPrivate);
-  MOZ_ASSERT(aFailStatus >= Terminating);
+  MOZ_ASSERT(aFailStatus >= Canceling);
 
   aWorkerPrivate->AssertIsOnWorkerThread();
 
   if (!aWorkerPrivate->AddHolder(this, aFailStatus)) {
     return false;
   }
 
   mWorkerPrivate = aWorkerPrivate;
--- a/dom/workers/WorkerHolder.h
+++ b/dom/workers/WorkerHolder.h
@@ -20,18 +20,16 @@ class WorkerPrivate;
  *
  * +========================================================+
  * |                     Closing Statuses                   |
  * +=============+=============+=================+==========+
  * |    status   | clear queue | abort execution | notified |
  * +=============+=============+=================+==========+
  * |   Closing   |     yes     |       no        |    no    |
  * +-------------+-------------+-----------------+----------+
- * | Terminating |     yes     |       yes       |   yes    |
- * +-------------+-------------+-----------------+----------+
  * |  Canceling  |     yes     |       yes       |   yes    |
  * +-------------+-------------+-----------------+----------+
  * |   Killing   |     yes     |       yes       |   yes    |
  * +-------------+-------------+-----------------+----------+
  */
 
 enum WorkerStatus
 {
@@ -43,21 +41,16 @@ enum WorkerStatus
 
   // Inner script called close() on the worker global scope. Setting this
   // status causes the worker to clear its queue of events but does not abort
   // the currently running script. WorkerHolder/WorkerRef objects are not going
   // to be notified because the behavior of APIs/Components should not change
   // during this status yet.
   Closing,
 
-  // Outer script called terminate() on the worker or the worker object was
-  // garbage collected in its outer script. Setting this status causes the
-  // worker to abort immediately and clear its queue of events.
-  Terminating,
-
   // Either the user navigated away from the owning page or the owning page fell
   // out of bfcache. Setting this status causes the worker to abort immediately.
   // Since the page has gone away the worker may not post any messages.
   Canceling,
 
   // The application is shutting down. Setting this status causes the worker to
   // abort immediately.
   Killing,
--- a/dom/workers/WorkerHolderToken.cpp
+++ b/dom/workers/WorkerHolderToken.cpp
@@ -87,18 +87,18 @@ WorkerHolderToken::~WorkerHolderToken()
   MOZ_ASSERT(mListenerList.IsEmpty());
 }
 
 bool
 WorkerHolderToken::Notify(WorkerStatus aStatus)
 {
   NS_ASSERT_OWNINGTHREAD(WorkerHolderToken);
 
-  // When the service worker thread is stopped we will get Terminating,
-  // but nothing higher than that.  We must shut things down at Terminating.
+  // When the service worker thread is stopped we will get Canceling,
+  // but nothing higher than that.  We must shut things down at Canceling.
   if (aStatus < mShutdownStatus || mShuttingDown) {
     return true;
   }
 
   // Start the asynchronous destruction of our actors.  These will call back
   // into RemoveActor() once the actor is destroyed.
   nsTObserverArray<Listener*>::ForwardIterator iter(mListenerList);
   while (iter.HasMore()) {
--- a/dom/workers/WorkerNavigator.cpp
+++ b/dom/workers/WorkerNavigator.cpp
@@ -171,17 +171,17 @@ WorkerNavigator::GetUserAgent(nsString& 
                               ErrorResult& aRv) const
 {
   WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(workerPrivate);
 
   RefPtr<GetUserAgentRunnable> runnable =
     new GetUserAgentRunnable(workerPrivate, aUserAgent);
 
-  runnable->Dispatch(Terminating, aRv);
+  runnable->Dispatch(Canceling, aRv);
 }
 
 uint64_t
 WorkerNavigator::HardwareConcurrency() const
 {
   RuntimeService* rts = RuntimeService::GetService();
   MOZ_ASSERT(rts);
 
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -502,18 +502,18 @@ class NotifyRunnable final : public Work
 {
   WorkerStatus mStatus;
 
 public:
   NotifyRunnable(WorkerPrivate* aWorkerPrivate, WorkerStatus aStatus)
   : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
     mStatus(aStatus)
   {
-    MOZ_ASSERT(aStatus == Closing || aStatus == Terminating ||
-               aStatus == Canceling || aStatus == Killing);
+    MOZ_ASSERT(aStatus == Closing || aStatus == Canceling ||
+               aStatus == Killing);
   }
 
 private:
   virtual bool
   PreDispatch(WorkerPrivate* aWorkerPrivate) override
   {
     aWorkerPrivate->AssertIsOnParentThread();
     return aWorkerPrivate->ModifyBusyCount(true);
@@ -1829,17 +1829,17 @@ WorkerPrivate::Notify(WorkerStatus aStat
     }
 #endif
 
     // Worker never got a chance to run, go ahead and delete it.
     ScheduleDeletion(WorkerPrivate::WorkerNeverRan);
     return true;
   }
 
-  NS_ASSERTION(aStatus != Terminating || mQueuedRunnables.IsEmpty(),
+  NS_ASSERTION(aStatus != Canceling || mQueuedRunnables.IsEmpty(),
                "Shouldn't have anything queued!");
 
   // Anything queued will be discarded.
   mQueuedRunnables.Clear();
 
   // No Canceling timeout is needed.
   if (mCancelingTimer) {
     mCancelingTimer->Cancel();
@@ -1884,17 +1884,17 @@ WorkerPrivate::Freeze(nsPIDOMWindowInner
     }
   }
 
   mParentFrozen = true;
 
   {
     MutexAutoLock lock(mMutex);
 
-    if (mParentStatus >= Terminating) {
+    if (mParentStatus >= Canceling) {
       return true;
     }
   }
 
   DisableDebugger();
 
   RefPtr<FreezeRunnable> runnable = new FreezeRunnable(this);
   if (!runnable->Dispatch()) {
@@ -1941,17 +1941,17 @@ WorkerPrivate::Thaw(nsPIDOMWindowInner* 
 
   MOZ_ASSERT(mParentFrozen);
 
   mParentFrozen = false;
 
   {
     MutexAutoLock lock(mMutex);
 
-    if (mParentStatus >= Terminating) {
+    if (mParentStatus >= Canceling) {
       return true;
     }
   }
 
   EnableDebugger();
 
   // Execute queued runnables before waking up the worker, otherwise the worker
   // could post new messages before we run those that have been queued.
@@ -1992,17 +1992,17 @@ WorkerPrivate::ParentWindowResumed()
   mParentWindowPausedDepth -= 1;
   if (mParentWindowPausedDepth > 0) {
     return;
   }
 
   {
     MutexAutoLock lock(mMutex);
 
-    if (mParentStatus >= Terminating) {
+    if (mParentStatus >= Canceling) {
       return;
     }
   }
 
   // Execute queued runnables before waking up, otherwise the worker could post
   // new messages before we run those that have been queued.
   if (!IsFrozen() && !mQueuedRunnables.IsEmpty()) {
     MOZ_ASSERT(IsDedicatedWorker());
@@ -2019,17 +2019,17 @@ WorkerPrivate::ParentWindowResumed()
 void
 WorkerPrivate::PropagateFirstPartyStorageAccessGranted()
 {
   AssertIsOnParentThread();
 
   {
     MutexAutoLock lock(mMutex);
 
-    if (mParentStatus >= Terminating) {
+    if (mParentStatus >= Canceling) {
       return;
     }
   }
 
   RefPtr<PropagateFirstPartyStorageAccessGrantedRunnable> runnable =
     new PropagateFirstPartyStorageAccessGrantedRunnable(this);
   Unused << NS_WARN_IF(!runnable->Dispatch());
 }
@@ -2057,17 +2057,17 @@ WorkerPrivate::ModifyBusyCount(bool aInc
     return true;
   }
 
   if (--mBusyCount == 0) {
 
     bool shouldCancel;
     {
       MutexAutoLock lock(mMutex);
-      shouldCancel = mParentStatus == Terminating;
+      shouldCancel = mParentStatus == Canceling;
     }
 
     if (shouldCancel && !Cancel()) {
       return false;
     }
   }
 
   return true;
@@ -3250,23 +3250,22 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
 
   EnableMemoryReporter();
 
   InitializeGCTimers();
 
   Maybe<JSAutoRealm> workerCompartment;
 
   for (;;) {
-    WorkerStatus currentStatus, previousStatus;
+    WorkerStatus currentStatus;
     bool debuggerRunnablesPending = false;
     bool normalRunnablesPending = false;
 
     {
       MutexAutoLock lock(mMutex);
-      previousStatus = mStatus;
 
       while (mControlQueue.IsEmpty() &&
              !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) &&
              !(normalRunnablesPending = NS_HasPendingEvents(mThread))) {
         WaitForWorkerEvents();
       }
 
       auto result = ProcessAllControlRunnablesLocked();
@@ -3280,35 +3279,35 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
       }
 
       currentStatus = mStatus;
     }
 
     // if all holders are done then we can kill this thread.
     if (currentStatus != Running && !HasActiveHolders()) {
 
-      // If we just changed status, we must schedule the current runnables.
-      if (previousStatus != Running && currentStatus != Killing) {
+      // Now we are ready to kill the worker thread.
+      if (currentStatus == Canceling) {
         NotifyInternal(Killing);
 
 #ifdef DEBUG
         {
           MutexAutoLock lock(mMutex);
           currentStatus = mStatus;
         }
         MOZ_ASSERT(currentStatus == Killing);
 #else
         currentStatus = Killing;
 #endif
       }
 
       // If we're supposed to die then we should exit the loop.
       if (currentStatus == Killing) {
         // The ClientSource should be cleared in NotifyInternal() when we reach
-        // or pass Terminating.
+        // or pass Canceling.
         MOZ_DIAGNOSTIC_ASSERT(!mClientSource);
 
         // Flush uncaught rejections immediately, without
         // waiting for a next tick.
         PromiseDebugging::FlushUncaughtRejections();
 
         ShutdownGCTimers();
 
@@ -3538,17 +3537,17 @@ WorkerPrivate::EnsurePerformanceStorage(
 }
 
 Maybe<ClientInfo>
 WorkerPrivate::GetClientInfo() const
 {
   AssertIsOnWorkerThread();
   Maybe<ClientInfo> clientInfo;
   if (!mClientSource) {
-    MOZ_DIAGNOSTIC_ASSERT(mStatus >= Terminating);
+    MOZ_DIAGNOSTIC_ASSERT(mStatus >= Canceling);
     return clientInfo;
   }
   clientInfo.emplace(mClientSource->Info());
   return clientInfo;
 }
 
 const ClientState
 WorkerPrivate::GetClientState() const
@@ -3561,33 +3560,33 @@ WorkerPrivate::GetClientState() const
 }
 
 const Maybe<ServiceWorkerDescriptor>
 WorkerPrivate::GetController()
 {
   AssertIsOnWorkerThread();
   {
     MutexAutoLock lock(mMutex);
-    if (mStatus >= Terminating) {
+    if (mStatus >= Canceling) {
       return Maybe<ServiceWorkerDescriptor>();
     }
   }
   MOZ_DIAGNOSTIC_ASSERT(mClientSource);
   return mClientSource->GetController();
 }
 
 void
 WorkerPrivate::Control(const ServiceWorkerDescriptor& aServiceWorker)
 {
   AssertIsOnWorkerThread();
   MOZ_DIAGNOSTIC_ASSERT(!IsChromeWorker());
   MOZ_DIAGNOSTIC_ASSERT(Type() != WorkerTypeService);
   {
     MutexAutoLock lock(mMutex);
-    if (mStatus >= Terminating) {
+    if (mStatus >= Canceling) {
       return;
     }
   }
   MOZ_DIAGNOSTIC_ASSERT(mClientSource);
 
   if (IsBlobURI(mLoadInfo.mBaseURI)) {
     // Blob URL workers can only become controlled by inheriting from
     // their parent.  Make sure to note this properly.
@@ -3600,17 +3599,17 @@ WorkerPrivate::Control(const ServiceWork
 }
 
 void
 WorkerPrivate::ExecutionReady()
 {
   AssertIsOnWorkerThread();
   {
     MutexAutoLock lock(mMutex);
-    if (mStatus >= Terminating) {
+    if (mStatus >= Canceling) {
       return;
     }
   }
   MOZ_DIAGNOSTIC_ASSERT(mClientSource);
   mClientSource->WorkerExecutionReady(this);
 }
 
 void
@@ -4190,17 +4189,17 @@ WorkerPrivate::CancelAllTimeouts()
   mTimer = nullptr;
   mTimerRunnable = nullptr;
 }
 
 already_AddRefed<nsIEventTarget>
 WorkerPrivate::CreateNewSyncLoop(WorkerStatus aFailStatus)
 {
   AssertIsOnWorkerThread();
-  MOZ_ASSERT(aFailStatus >= Terminating,
+  MOZ_ASSERT(aFailStatus >= Canceling,
              "Sync loops can be created when the worker is in Running/Closing state!");
 
   {
     MutexAutoLock lock(mMutex);
 
     if (mStatus >= aFailStatus) {
       return nullptr;
     }
@@ -4587,17 +4586,17 @@ WorkerPrivate::NotifyInternal(WorkerStat
   WorkerStatus previousStatus;
   {
     MutexAutoLock lock(mMutex);
 
     if (mStatus >= aStatus) {
       return true;
     }
 
-    if (aStatus >= Terminating) {
+    if (aStatus >= Canceling) {
       MutexAutoUnlock unlock(mMutex);
       mClientSource.reset();
       if (mScope) {
         mScope->NoteTerminating();
       }
     }
 
     // Make sure the hybrid event target stops dispatching runnables
@@ -4675,19 +4674,17 @@ WorkerPrivate::NotifyInternal(WorkerStat
       // parent thread, after CANCELING_TIMEOUT millseconds.
       RefPtr<CancelingWithTimeoutOnParentRunnable> rr =
         new CancelingWithTimeoutOnParentRunnable(this);
       rr->Dispatch();
     }
     return true;
   }
 
-  MOZ_ASSERT(aStatus == Terminating ||
-             aStatus == Canceling ||
-             aStatus == Killing);
+  MOZ_ASSERT(aStatus == Canceling || aStatus == Killing);
 
   // Always abort the script.
   return false;
 }
 
 void
 WorkerPrivate::ReportError(JSContext* aCx, JS::ConstUTF8CharsZ aToStringResult,
                            JSErrorReport* aReport)
@@ -5071,17 +5068,17 @@ WorkerPrivate::StartCancelingTimer()
 
   if (NS_WARN_IF(!mCancelingTimer)) {
     return;
   }
 
   // This is not needed if we are already in an advanced shutdown state.
   {
     MutexAutoLock lock(mMutex);
-    if (ParentStatus() >= Terminating) {
+    if (ParentStatus() >= Canceling) {
       return;
     }
   }
 
   uint32_t cancelingTimeoutMillis = DOMPrefs::WorkerCancelingTimeoutMillis();
 
   RefPtr<CancelingTimerCallback> callback = new CancelingTimerCallback(this);
   nsresult rv = mCancelingTimer->InitWithCallback(callback,
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -169,22 +169,16 @@ public:
 
   bool
   Kill()
   {
     return Notify(Killing);
   }
 
   bool
-  Terminate()
-  {
-    return Notify(Terminating);
-  }
-
-  bool
   Close();
 
   // The passed principal must be the Worker principal in case of a
   // ServiceWorker and the loading principal for any other type.
   static void
   OverrideLoadInfoLoadGroup(WorkerLoadInfo& aLoadInfo,
                             nsIPrincipal* aPrincipal);
 
@@ -585,17 +579,17 @@ public:
   GetPerformanceCounter();
 
   bool
   IsAcceptingEvents()
   {
     AssertIsOnParentThread();
 
     MutexAutoLock lock(mMutex);
-    return mParentStatus < Terminating;
+    return mParentStatus < Canceling;
   }
 
   WorkerStatus
   ParentStatusProtected()
   {
     AssertIsOnParentThread();
     MutexAutoLock lock(mMutex);
     return mParentStatus;
@@ -1221,17 +1215,17 @@ private:
     AssertIsOnWorkerThread();
 
     WorkerStatus status;
     {
       MutexAutoLock lock(mMutex);
       status = mStatus;
     }
 
-    if (status < Terminating) {
+    if (status < Canceling) {
       return true;
     }
 
     return false;
   }
 
   void
   CancelAllTimeouts();
@@ -1337,19 +1331,18 @@ private:
   // The worker is owned by its thread, which is represented here.  This is set
   // in Constructor() and emptied by WorkerFinishedRunnable, and conditionally
   // traversed by the cycle collector if the busy count is zero.
   //
   // There are 4 ways a worker can be terminated:
   // 1. GC/CC - When the worker is in idle state (busycount == 0), it allows to
   //    traverse the 'hidden' mParentEventTargetRef pointer. This is the exposed
   //    Worker webidl object. Doing this, CC will be able to detect a cycle and
-  //    Unlink is called. In Unlink, Worker calls Terminate().
-  // 2. Worker::Terminate() is called - the shutdown procedure starts
-  //    immediately.
+  //    Unlink is called. In Unlink, Worker calls Cancel().
+  // 2. Worker::Cancel() is called - the shutdown procedure starts immediately.
   // 3. WorkerScope::Close() is called - Similar to point 2.
   // 4. xpcom-shutdown notification - We call Kill().
   RefPtr<Worker> mParentEventTargetRef;
   RefPtr<WorkerPrivate> mSelfRef;
 
   // The lifetime of these objects within LoadInfo is managed explicitly;
   // they do not need to be cycle collected.
   WorkerLoadInfo mLoadInfo;
--- a/dom/workers/WorkerRunnable.h
+++ b/dom/workers/WorkerRunnable.h
@@ -404,17 +404,17 @@ protected:
                                     const nsACString& aTelemetryKey);
   ~WorkerMainThreadRunnable() {}
 
   virtual bool MainThreadRun() = 0;
 
 public:
   // Dispatch the runnable to the main thread.  If dispatch to main thread
   // fails, or if the worker is in a state equal or greater of aFailStatus, an
-  // error will be reported on aRv. Normally you want to use 'Terminating' for
+  // error will be reported on aRv. Normally you want to use 'Canceling' for
   // aFailStatus, except if you want an infallible runnable. In this case, use
   // 'Killing'.
   // In that case the error MUST be propagated out to script.
   void Dispatch(WorkerStatus aFailStatus, ErrorResult& aRv);
 
 private:
   NS_IMETHOD Run() override;
 };
--- a/dom/xhr/XMLHttpRequestWorker.cpp
+++ b/dom/xhr/XMLHttpRequestWorker.cpp
@@ -1799,32 +1799,32 @@ XMLHttpRequestWorker::SendInternal(SendR
   }
 
   AutoUnpinXHR autoUnpin(this);
   Maybe<AutoSyncLoopHolder> autoSyncLoop;
 
   nsCOMPtr<nsIEventTarget> syncLoopTarget;
   bool isSyncXHR = mProxy->mIsSyncXHR;
   if (isSyncXHR) {
-    autoSyncLoop.emplace(mWorkerPrivate, Terminating);
+    autoSyncLoop.emplace(mWorkerPrivate, Canceling);
     syncLoopTarget = autoSyncLoop->GetEventTarget();
     if (!syncLoopTarget) {
       aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
       return;
     }
   }
 
   mProxy->mOuterChannelId++;
 
   aRunnable->SetSyncLoopTarget(syncLoopTarget);
   aRunnable->SetHaveUploadListeners(hasUploadListeners);
 
   mStateData.mFlagSend = true;
 
-  aRunnable->Dispatch(Terminating, aRv);
+  aRunnable->Dispatch(Canceling, aRv);
   if (aRv.Failed()) {
     // Dispatch() may have spun the event loop and we may have already unrooted.
     // If so we don't want autoUnpin to try again.
     if (!mWorkerRef) {
       autoUnpin.Clear();
     }
     return;
   }
@@ -1883,17 +1883,17 @@ XMLHttpRequestWorker::Open(const nsACStr
   mProxy->mOuterEventStreamId++;
 
   RefPtr<OpenRunnable> runnable =
     new OpenRunnable(mWorkerPrivate, mProxy, aMethod, aUrl, aUser, aPassword,
                      mBackgroundRequest, mWithCredentials,
                      mTimeout, mResponseType);
 
   ++mProxy->mOpenCount;
-  runnable->Dispatch(Terminating, aRv);
+  runnable->Dispatch(Canceling, aRv);
   if (aRv.Failed()) {
     if (mProxy && !--mProxy->mOpenCount) {
       ReleaseProxy();
     }
 
     return;
   }
 
@@ -1920,17 +1920,17 @@ XMLHttpRequestWorker::SetRequestHeader(c
 
   if (!mProxy) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   RefPtr<SetRequestHeaderRunnable> runnable =
     new SetRequestHeaderRunnable(mWorkerPrivate, mProxy, aHeader, aValue);
-  runnable->Dispatch(Terminating, aRv);
+  runnable->Dispatch(Canceling, aRv);
 }
 
 void
 XMLHttpRequestWorker::SetTimeout(uint32_t aTimeout, ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   if (mCanceled) {
@@ -1943,17 +1943,17 @@ XMLHttpRequestWorker::SetTimeout(uint32_
   if (!mProxy) {
     // Open may not have been called yet, in which case we'll handle the
     // timeout in OpenRunnable.
     return;
   }
 
   RefPtr<SetTimeoutRunnable> runnable =
     new SetTimeoutRunnable(mWorkerPrivate, mProxy, aTimeout);
-  runnable->Dispatch(Terminating, aRv);
+  runnable->Dispatch(Canceling, aRv);
 }
 
 void
 XMLHttpRequestWorker::SetWithCredentials(bool aWithCredentials, ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   if (mCanceled) {
@@ -1966,17 +1966,17 @@ XMLHttpRequestWorker::SetWithCredentials
   if (!mProxy) {
     // Open may not have been called yet, in which case we'll handle the
     // credentials in OpenRunnable.
     return;
   }
 
   RefPtr<SetWithCredentialsRunnable> runnable =
     new SetWithCredentialsRunnable(mWorkerPrivate, mProxy, aWithCredentials);
-  runnable->Dispatch(Terminating, aRv);
+  runnable->Dispatch(Canceling, aRv);
 }
 
 void
 XMLHttpRequestWorker::SetMozBackgroundRequest(bool aBackgroundRequest,
                                               ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
@@ -1991,17 +1991,17 @@ XMLHttpRequestWorker::SetMozBackgroundRe
     // Open may not have been called yet, in which case we'll handle the
     // background request in OpenRunnable.
     return;
   }
 
   RefPtr<SetBackgroundRequestRunnable> runnable =
     new SetBackgroundRequestRunnable(mWorkerPrivate, mProxy,
                                      aBackgroundRequest);
-  runnable->Dispatch(Terminating, aRv);
+  runnable->Dispatch(Canceling, aRv);
 }
 
 XMLHttpRequestUpload*
 XMLHttpRequestWorker::GetUpload(ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   if (mCanceled) {
@@ -2184,17 +2184,17 @@ XMLHttpRequestWorker::Abort(ErrorResult&
     // No one did anything to us while we fired abort events, so reset our state
     // to "unsent"
     mStateData.mReadyState = 0;
   }
 
   mProxy->mOuterEventStreamId++;
 
   RefPtr<AbortRunnable> runnable = new AbortRunnable(mWorkerPrivate, mProxy);
-  runnable->Dispatch(Terminating, aRv);
+  runnable->Dispatch(Canceling, aRv);
 }
 
 void
 XMLHttpRequestWorker::GetResponseHeader(const nsACString& aHeader,
                                         nsACString& aResponseHeader, ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
@@ -2207,17 +2207,17 @@ XMLHttpRequestWorker::GetResponseHeader(
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   nsCString responseHeader;
   RefPtr<GetResponseHeaderRunnable> runnable =
     new GetResponseHeaderRunnable(mWorkerPrivate, mProxy, aHeader,
                                   responseHeader);
-  runnable->Dispatch(Terminating, aRv);
+  runnable->Dispatch(Canceling, aRv);
   if (aRv.Failed()) {
     return;
   }
   aResponseHeader = responseHeader;
 }
 
 void
 XMLHttpRequestWorker::GetAllResponseHeaders(nsACString& aResponseHeaders,
@@ -2233,17 +2233,17 @@ XMLHttpRequestWorker::GetAllResponseHead
   if (!mProxy) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   nsCString responseHeaders;
   RefPtr<GetAllResponseHeadersRunnable> runnable =
     new GetAllResponseHeadersRunnable(mWorkerPrivate, mProxy, responseHeaders);
-  runnable->Dispatch(Terminating, aRv);
+  runnable->Dispatch(Canceling, aRv);
   if (aRv.Failed()) {
     return;
   }
 
   aResponseHeaders = responseHeaders;
 }
 
 void
@@ -2265,17 +2265,17 @@ XMLHttpRequestWorker::OverrideMimeType(c
                   (mProxy->mSeenLoadStart ||
                    mStateData.mReadyState > 1))) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   RefPtr<OverrideMimeTypeRunnable> runnable =
     new OverrideMimeTypeRunnable(mWorkerPrivate, mProxy, aMimeType);
-  runnable->Dispatch(Terminating, aRv);
+  runnable->Dispatch(Canceling, aRv);
 }
 
 void
 XMLHttpRequestWorker::SetResponseType(XMLHttpRequestResponseType aResponseType,
                                       ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
@@ -2301,17 +2301,17 @@ XMLHttpRequestWorker::SetResponseType(XM
       (mProxy->mSeenLoadStart ||
        mStateData.mReadyState > 1)) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   RefPtr<SetResponseTypeRunnable> runnable =
     new SetResponseTypeRunnable(mWorkerPrivate, mProxy, aResponseType);
-  runnable->Dispatch(Terminating, aRv);
+  runnable->Dispatch(Canceling, aRv);
   if (aRv.Failed()) {
     return;
   }
 
   mResponseType = runnable->ResponseType();
 }
 
 void
--- a/dom/xslt/xpath/txMozillaXPathTreeWalker.cpp
+++ b/dom/xslt/xpath/txMozillaXPathTreeWalker.cpp
@@ -1,16 +1,15 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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 "txXPathTreeWalker.h"
 #include "nsAtom.h"
-#include "nsIAttribute.h"
 #include "nsINode.h"
 #include "nsPrintfCString.h"
 #include "nsReadableUtils.h"
 #include "nsString.h"
 #include "nsTextFragment.h"
 #include "txXMLUtils.h"
 #include "txLog.h"
 #include "nsUnicharUtils.h"
@@ -633,22 +632,20 @@ txXPathNativeNode::createXPathNode(nsICo
 }
 
 /* static */
 txXPathNode*
 txXPathNativeNode::createXPathNode(nsINode* aNode, bool aKeepRootAlive)
 {
     uint16_t nodeType = aNode->NodeType();
     if (nodeType == nsINode::ATTRIBUTE_NODE) {
-        nsCOMPtr<nsIAttribute> attr = do_QueryInterface(aNode);
-        NS_ASSERTION(attr, "doesn't implement nsIAttribute");
+        auto* attr = static_cast<Attr*>(aNode);
 
-        mozilla::dom::NodeInfo *nodeInfo = attr->NodeInfo();
-        mozilla::dom::Element* parent =
-          static_cast<Attr*>(attr.get())->GetElement();
+        NodeInfo* nodeInfo = attr->NodeInfo();
+        Element* parent = attr->GetElement();
         if (!parent) {
             return nullptr;
         }
 
         nsINode* root = aKeepRootAlive ? txXPathNode::RootOf(parent) : nullptr;
 
         uint32_t i, total = parent->GetAttrCount();
         for (i = 0; i < total; ++i) {
--- a/gfx/thebes/gfxUtils.cpp
+++ b/gfx/thebes/gfxUtils.cpp
@@ -1457,17 +1457,17 @@ gfxUtils::ThreadSafeGetFeatureStatus(con
   if (!NS_IsMainThread()) {
     dom::WorkerPrivate* workerPrivate = dom::GetCurrentThreadWorkerPrivate();
 
     RefPtr<GetFeatureStatusRunnable> runnable =
       new GetFeatureStatusRunnable(workerPrivate, gfxInfo, feature, failureId,
                                    status);
 
     ErrorResult rv;
-    runnable->Dispatch(dom::WorkerStatus::Terminating, rv);
+    runnable->Dispatch(dom::WorkerStatus::Canceling, rv);
     if (rv.Failed()) {
         // XXXbz This is totally broken, since we're supposed to just abort
         // everything up the callstack but the callers basically eat the
         // exception.  Ah, well.
         return rv.StealNSResult();
     }
 
     return runnable->GetNSResult();
--- a/gfx/vr/external_api/moz_external_vr.h
+++ b/gfx/vr/external_api/moz_external_vr.h
@@ -263,16 +263,17 @@ struct VRDisplayState
 #if defined(__ANDROID__)
   bool shutdown;
 #endif // defined(__ANDROID__)
   char mDisplayName[kVRDisplayNameMaxLen];
   VRDisplayCapabilityFlags mCapabilityFlags;
   VRFieldOfView mEyeFOV[VRDisplayState::NumEyes];
   Point3D_POD mEyeTranslation[VRDisplayState::NumEyes];
   IntSize_POD mEyeResolution;
+  bool mSuppressFrames;
   bool mIsConnected;
   bool mIsMounted;
   FloatSize_POD mStageSize;
   // We can't use a Matrix4x4 here unless we ensure it's a POD type
   float mSittingToStandingTransform[16];
   uint64_t mLastSubmittedFrameId;
   bool mLastSubmittedFrameSuccessful;
   uint32_t mPresentingGeneration;
--- a/gfx/vr/gfxVRExternal.cpp
+++ b/gfx/vr/gfxVRExternal.cpp
@@ -230,18 +230,18 @@ VRDisplayExternal::SubmitFrame(const lay
   VRSystemManagerExternal* manager = vm->GetExternalManager();
   manager->PushState(&state, true);
   sPushIndex++;
 
   VRDisplayState displayState;
   memset(&displayState, 0, sizeof(VRDisplayState));
   while (displayState.mLastSubmittedFrameId < aFrameId) {
     if (manager->PullState(&displayState, &mLastSensorState, mDisplayInfo.mControllerState)) {
-      if (!displayState.mIsConnected) {
-        // Service has shut down or hardware has been disconnected
+      if (displayState.mSuppressFrames || !displayState.mIsConnected) {
+        // External implementation wants to supress frames, service has shut down or hardware has been disconnected.
         return false;
       }
     }
 #ifdef XP_WIN
     Sleep(0);
 #else
     sleep(0);
 #endif
--- a/gfx/vr/ipc/VRMessageUtils.h
+++ b/gfx/vr/ipc/VRMessageUtils.h
@@ -39,16 +39,17 @@ struct ParamTraits<mozilla::gfx::VRDispl
     //        this code can be refactored out if we use
     //        shmem between the VR and content process.
     nsCString displayName;
     displayName.Assign(aParam.mDisplayName);
     WriteParam(aMsg, displayName);
     WriteParam(aMsg, aParam.mCapabilityFlags);
     WriteParam(aMsg, aParam.mEyeResolution.width);
     WriteParam(aMsg, aParam.mEyeResolution.height);
+    WriteParam(aMsg, aParam.mSuppressFrames);
     WriteParam(aMsg, aParam.mIsConnected);
     WriteParam(aMsg, aParam.mIsMounted);
     WriteParam(aMsg, aParam.mStageSize.width);
     WriteParam(aMsg, aParam.mStageSize.height);
     WriteParam(aMsg, aParam.mLastSubmittedFrameId);
     WriteParam(aMsg, aParam.mPresentingGeneration);
     for (int i = 0; i < 16; i++) {
       // TODO - Should probably memcpy the whole array or
@@ -66,16 +67,17 @@ struct ParamTraits<mozilla::gfx::VRDispl
 
   static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
   {
     nsCString displayName;
     if (!ReadParam(aMsg, aIter, &(displayName)) ||
         !ReadParam(aMsg, aIter, &(aResult->mCapabilityFlags)) ||
         !ReadParam(aMsg, aIter, &(aResult->mEyeResolution.width)) ||
         !ReadParam(aMsg, aIter, &(aResult->mEyeResolution.height)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mSuppressFrames)) ||
         !ReadParam(aMsg, aIter, &(aResult->mIsConnected)) ||
         !ReadParam(aMsg, aIter, &(aResult->mIsMounted)) ||
         !ReadParam(aMsg, aIter, &(aResult->mStageSize.width)) ||
         !ReadParam(aMsg, aIter, &(aResult->mStageSize.height)) ||
         !ReadParam(aMsg, aIter, &(aResult->mLastSubmittedFrameId)) ||
         !ReadParam(aMsg, aIter, &(aResult->mPresentingGeneration))) {
       return false;
     }
--- a/js/src/jit/RegisterSets.h
+++ b/js/src/jit/RegisterSets.h
@@ -915,17 +915,17 @@ class SpecializedRegSet<Accessors, Regis
             takeUnchecked(reg.gpr());
     }
 
     void take(Register reg) {
         MOZ_ASSERT(this->has(reg));
         takeUnchecked(reg);
     }
     void take(FloatRegister reg) {
-        MOZ_ASSERT(has(reg));
+        MOZ_ASSERT(this->has(reg));
         takeUnchecked(reg);
     }
     void take(AnyRegister reg) {
         if (reg.isFloat())
             take(reg.fpu());
         else
             take(reg.gpr());
     }
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/statements/for-in-with-gc-and-unvisited-deletion.js
@@ -0,0 +1,76 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+//-----------------------------------------------------------------------------
+var gTestfile = "for-in-with-gc-and-unvisited-deletion.js";
+var BUGNUMBER = 1462939;
+var summary =
+  "Don't mishandle deletion of a property from the internal iterator " +
+  "created for a for-in loop, when a gc occurs just after it";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+function testOneDeletion()
+{
+  var o = {
+    p: 1,
+    r: 3,
+    s: 4,
+  };
+
+  for (var i in o)
+  {
+    gc();
+    delete o.s;
+  }
+}
+testOneDeletion();
+
+function testTwoDeletions()
+{
+  var o = {
+    p: 1,
+    r: 3,
+    s: 4,
+    t: 5,
+  };
+
+  for (var i in o)
+  {
+    gc();
+    delete o.t;
+    delete o.s;
+  }
+}
+testTwoDeletions();
+
+function testThreeDeletions()
+{
+  var o = {
+    p: 1,
+    r: 3,
+    s: 4,
+    t: 5,
+    x: 7,
+  };
+
+  for (var i in o)
+  {
+    gc();
+    delete o.x;
+    delete o.t;
+    delete o.s;
+  }
+}
+testThreeDeletions();
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/statements/for-in-with-gc-during-iterator-init.js
@@ -0,0 +1,30 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+//-----------------------------------------------------------------------------
+var gTestfile = "for-in-with-gc-during-iterator-init.js";
+var BUGNUMBER = 1464472;
+var summary =
+  "Properly trace NativeIterator when a GC occurs during its initialization";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+gczeal(17, 1);
+for (var i = 0; i < 100; ++i)
+{
+  Object.prototype[1012] = "value";
+  imports = {};
+  for (dmod in imports)
+    continue; // gc occurs here converting 1012 to string
+}
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
--- a/js/src/vm/Iteration.cpp
+++ b/js/src/vm/Iteration.cpp
@@ -51,31 +51,47 @@ using mozilla::DebugOnly;
 using mozilla::Maybe;
 using mozilla::PodCopy;
 using mozilla::PodEqual;
 
 typedef Rooted<PropertyIteratorObject*> RootedPropertyIteratorObject;
 
 static const gc::AllocKind ITERATOR_FINALIZE_KIND = gc::AllocKind::OBJECT2_BACKGROUND;
 
+// Beware!  This function may have to trace incompletely-initialized
+// |NativeIterator| allocations if the |IdToString| in that constructor recurs
+// into this code.
 void
 NativeIterator::trace(JSTracer* trc)
 {
     TraceNullableEdge(trc, &objectBeingIterated_, "objectBeingIterated_");
 
     // The SuppressDeletedPropertyHelper loop can GC, so make sure that if the
     // GC removes any elements from the list, it won't remove this one.
     if (iterObj_)
         TraceManuallyBarrieredEdge(trc, &iterObj_, "iterObj");
 
+    // The limits below are correct at every instant of |NativeIterator|
+    // initialization, with the end-pointer incremented as each new guard is
+    // created, so they're safe to use here.
     std::for_each(guardsBegin(), guardsEnd(),
                   [trc](HeapReceiverGuard& guard) {
                       guard.trace(trc);
                   });
 
+    // But as properties must be created *before* guards, |propertiesBegin()|
+    // that depends on |guardsEnd()| having its final value can't safely be
+    // used.  Until this is fully initialized, use |propertyCursor_| instead,
+    // which points at the start of properties even in partially initialized
+    // |NativeIterator|s.  (|propertiesEnd()| is safe at all times with respect
+    // to the properly-chosen beginning.)
+    //
+    // Note that we must trace all properties (not just those not yet visited,
+    // or just visited, due to |NativeIterator::previousPropertyWas|) for
+    // |NativeIterator|s to be reusable.
     GCPtrFlatString* begin = MOZ_LIKELY(isInitialized()) ? propertiesBegin() : propertyCursor_;
     std::for_each(begin, propertiesEnd(),
                   [trc](GCPtrFlatString& prop) {
                       // Properties begin life non-null and never *become*
                       // null.  (Deletion-suppression will shift trailing
                       // properties over a deleted property in the properties
                       // array, but it doesn't null them out.)
                       TraceEdge(trc, &prop, "prop");
@@ -669,17 +685,17 @@ NativeIterator::NativeIterator(JSContext
     iterObj_(propIter),
     // NativeIterator initially acts (before full initialization) as if it
     // contains no guards...
     guardsEnd_(guardsBegin()),
     // ...and no properties.
     propertyCursor_(reinterpret_cast<GCPtrFlatString*>(guardsBegin() + numGuards)),
     propertiesEnd_(propertyCursor_),
     guardKey_(guardKey),
-    flags_(0)
+    flags_(0) // note: no Flags::Initialized
 {
     MOZ_ASSERT(!*hadError);
 
     // NOTE: This must be done first thing: PropertyIteratorObject::finalize
     //       can only free |this| (and not leak it) if this has happened.
     propIter->setNativeIterator(this);
 
     for (size_t i = 0, len = props.length(); i < len; i++) {
@@ -735,16 +751,18 @@ NativeIterator::NativeIterator(JSContext
             // necessarily have static prototypes).
             pobj = pobj->staticPrototype();
         } while (pobj);
 
         guardKey_ = key;
         MOZ_ASSERT(i == numGuards);
     }
 
+    // |guardsEnd_| is now guaranteed to point at the start of properties, so
+    // we can mark this initialized.
     MOZ_ASSERT(static_cast<void*>(guardsEnd_) == propertyCursor_);
     markInitialized();
 
     MOZ_ASSERT(!*hadError);
 }
 
 static inline PropertyIteratorObject*
 VectorToKeyIterator(JSContext* cx, HandleObject obj, AutoIdVector& props, uint32_t numGuards)
--- a/js/src/vm/Iteration.h
+++ b/js/src/vm/Iteration.h
@@ -53,19 +53,43 @@ struct NativeIterator
 
     uint32_t guardKey_; // initialized by constructor
 
   public:
     // For cacheable native iterators, whether the iterator is currently
     // active.  Not serialized by XDR.
     struct Flags
     {
+        // This flag is set when all guards and properties associated with this
+        // NativeIterator have been initialized, such that |guardsEnd_|, in
+        // addition to being the end of guards, is also the beginning of
+        // properties.
+        //
+        // This flag is only *not* set when a NativeIterator is in the process
+        // of being constructed.  At such time |guardsEnd_| accounts only for
+        // guards that have been initialized -- potentially none of them.
+        // Instead, |propertyCursor_| is initialized to the ultimate/actual
+        // start of properties and must be used instead of |propertiesBegin()|,
+        // which asserts that this flag is present to guard against misuse.
         static constexpr uint32_t Initialized = 0x1;
+
+        // This flag indicates that this NativeIterator is currently being used
+        // to enumerate an object's properties and has not yet been closed.
         static constexpr uint32_t Active = 0x2;
+
+        // This flag indicates that the object being enumerated by this
+        // |NativeIterator| had a property deleted from it before it was
+        // visited, forcing the properties array in this to be mutated to
+        // remove it.
         static constexpr uint32_t HasUnvisitedPropertyDeletion = 0x4;
+
+        // If any of these bits are set on a |NativeIterator|, it isn't
+        // currently reusable.  (An active |NativeIterator| can't be stolen
+        // *right now*; a |NativeIterator| that's had its properties mutated
+        // can never be reused, because it would give incorrect results.)
         static constexpr uint32_t NotReusable = Active | HasUnvisitedPropertyDeletion;
     };
 
   private:
     uint32_t flags_ = 0; // consists of Flags bits
 
     /* While in compartment->enumerators, these form a doubly linked list. */
     NativeIterator* next_ = nullptr;
@@ -243,16 +267,22 @@ struct NativeIterator
     void markInactive() {
         MOZ_ASSERT(isInitialized());
 
         flags_ &= ~Flags::Active;
     }
 
     bool isReusable() const {
         MOZ_ASSERT(isInitialized());
+
+        // Cached NativeIterators are reusable if they're not currently active
+        // and their properties array hasn't been mutated, i.e. if only
+        // |Flags::Initialized| is set.  Using |Flags::NotReusable| to test
+        // would also work, but this formulation is safer against memory
+        // corruption.
         return flags_ == Flags::Initialized;
     }
 
     void markHasUnvisitedPropertyDeletion() {
         MOZ_ASSERT(isInitialized());
 
         flags_ |= Flags::HasUnvisitedPropertyDeletion;
     }
--- a/js/xpconnect/loader/AutoMemMap.cpp
+++ b/js/xpconnect/loader/AutoMemMap.cpp
@@ -3,16 +3,17 @@
 /* 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 "AutoMemMap.h"
 #include "ScriptPreloader-inl.h"
 
 #include "mozilla/Unused.h"
+#include "mozilla/ipc/FileDescriptor.h"
 #include "nsIFile.h"
 
 #include <private/pprio.h>
 
 namespace mozilla {
 namespace loader {
 
 using namespace mozilla::ipc;
@@ -137,22 +138,21 @@ AutoMemMap::cloneHandle() const
     return cloneFileDescriptor();
 }
 
 #endif
 
 void
 AutoMemMap::reset()
 {
+    if (addr && !persistent_) {
+        Unused << NS_WARN_IF(PR_MemUnmap(addr, size()) != PR_SUCCESS);
+        addr = nullptr;
+    }
     if (fileMap) {
-        if (addr && !persistent_) {
-            Unused << NS_WARN_IF(PR_MemUnmap(addr, size()) != PR_SUCCESS);
-            addr = nullptr;
-        }
-
         Unused << NS_WARN_IF(PR_CloseFileMap(fileMap) != PR_SUCCESS);
         fileMap = nullptr;
     }
 #ifdef XP_WIN
     if (handle_) {
       CloseHandle(handle_);
       handle_ = nullptr;
     }
--- a/js/xpconnect/loader/AutoMemMap.h
+++ b/js/xpconnect/loader/AutoMemMap.h
@@ -5,24 +5,27 @@
 
 #ifndef loader_AutoMemMap_h
 #define loader_AutoMemMap_h
 
 #include "mozilla/FileUtils.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/RangedPtr.h"
 #include "mozilla/Result.h"
-#include "mozilla/ipc/FileDescriptor.h"
 #include "nsIMemoryReporter.h"
 
 #include <prio.h>
 
 class nsIFile;
 
 namespace mozilla {
+namespace ipc {
+  class FileDescriptor;
+}
+
 namespace loader {
 
 using mozilla::ipc::FileDescriptor;
 
 class AutoMemMap
 {
     public:
         AutoMemMap() = default;
--- a/mfbt/TypeTraits.h
+++ b/mfbt/TypeTraits.h
@@ -94,16 +94,17 @@ template<> struct IsIntegralHelper<int> 
 template<> struct IsIntegralHelper<unsigned int>       : TrueType {};
 template<> struct IsIntegralHelper<long>               : TrueType {};
 template<> struct IsIntegralHelper<unsigned long>      : TrueType {};
 template<> struct IsIntegralHelper<long long>          : TrueType {};
 template<> struct IsIntegralHelper<unsigned long long> : TrueType {};
 template<> struct IsIntegralHelper<bool>               : TrueType {};
 template<> struct IsIntegralHelper<wchar_t>            : TrueType {};
 template<> struct IsIntegralHelper<char16_t>           : TrueType {};
+template<> struct IsIntegralHelper<char32_t>           : TrueType {};
 
 } /* namespace detail */
 
 /**
  * IsIntegral determines whether a type is an integral type.
  *
  * mozilla::IsIntegral<int>::value is true;
  * mozilla::IsIntegral<unsigned short>::value is true;
--- a/taskcluster/ci/beetmover-repackage/kind.yml
+++ b/taskcluster/ci/beetmover-repackage/kind.yml
@@ -7,16 +7,17 @@ loader: taskgraph.loader.single_dep:load
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.beetmover_repackage_l10n:transforms
    - taskgraph.transforms.beetmover_repackage:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - repackage-signing
+   - repackage-signing-l10n
    - partials-signing
 
 only-for-build-platforms:
    - linux-nightly/opt
    - linux64-nightly/opt
    - macosx64-nightly/opt
    - win32-nightly/opt
    - win64-nightly/opt
--- a/taskcluster/ci/l10n/kind.yml
+++ b/taskcluster/ci/l10n/kind.yml
@@ -1,23 +1,27 @@
 # 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/.
 
-loader: taskgraph.loader.single_dep:loader
+loader: taskgraph.loader.multi_dep:loader
 
+group-by: platform
 
 transforms:
    - taskgraph.transforms.l10n:transforms
    - taskgraph.transforms.use_toolchains:transforms
    - taskgraph.transforms.job:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - build
+   - build-signing
+   - repackage
+   - repackage-signing
    - toolchain
 
 only-for-build-platforms:
    - linux64/opt
    - linux/opt
    - android-api-16/opt
    - macosx64/opt
    - win32/opt
@@ -94,37 +98,37 @@ job-template:
             win32: windows2012-32/opt
             win64: windows2012-64/opt
             android-api-16: android-4-0-armv7-api16/opt
    env:
       by-build-platform:
          linux.*:   # linux64 and 32 get same treatment here
             EN_US_PACKAGE_NAME: target.tar.bz2
             EN_US_BINARY_URL:
-               task-reference: https://queue.taskcluster.net/v1/task/<signed-build>/artifacts/{artifact_prefix}
+               task-reference: https://queue.taskcluster.net/v1/task/<build-signing>/artifacts/{artifact_prefix}
             MAR_TOOLS_URL:
-               task-reference: https://queue.taskcluster.net/v1/task/<unsigned-build>/artifacts/{artifact_prefix}/host/bin
+               task-reference: https://queue.taskcluster.net/v1/task/<build>/artifacts/{artifact_prefix}/host/bin
          macosx64:
             EN_US_PACKAGE_NAME: target.dmg
             EN_US_BINARY_URL:
                task-reference: https://queue.taskcluster.net/v1/task/<repackage>/artifacts/{artifact_prefix}
             MAR_TOOLS_URL:
-               task-reference: https://queue.taskcluster.net/v1/task/<unsigned-build>/artifacts/{artifact_prefix}/host/bin
+               task-reference: https://queue.taskcluster.net/v1/task/<build>/artifacts/{artifact_prefix}/host/bin
          win.*:
             EN_US_PACKAGE_NAME: target.zip
             EN_US_BINARY_URL:
-               task-reference: https://queue.taskcluster.net/v1/task/<signed-build>/artifacts/{artifact_prefix}
+               task-reference: https://queue.taskcluster.net/v1/task/<build-signing>/artifacts/{artifact_prefix}
             EN_US_INSTALLER_BINARY_URL:
-               task-reference: https://queue.taskcluster.net/v1/task/<repackage-signed>/artifacts/{artifact_prefix}
+               task-reference: https://queue.taskcluster.net/v1/task/<repackage-signing>/artifacts/{artifact_prefix}
             MAR_TOOLS_URL:
-               task-reference: https://queue.taskcluster.net/v1/task/<unsigned-build>/artifacts/{artifact_prefix}/host/bin
+               task-reference: https://queue.taskcluster.net/v1/task/<build>/artifacts/{artifact_prefix}/host/bin
          android-api-16:
             EN_US_PACKAGE_NAME: target.apk
             EN_US_BINARY_URL:
-               task-reference: https://queue.taskcluster.net/v1/task/<unsigned-build>/artifacts/{artifact_prefix}
+               task-reference: https://queue.taskcluster.net/v1/task/<build>/artifacts/{artifact_prefix}
    mozharness:
       config:
          by-build-platform:
             linux:
                - single_locale/{project}.py
                - single_locale/linux32.py
                - single_locale/tc_common.py
                - single_locale/tc_linux32.py
--- a/taskcluster/ci/nightly-l10n/kind.yml
+++ b/taskcluster/ci/nightly-l10n/kind.yml
@@ -1,22 +1,27 @@
 # 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/.
 
-loader: taskgraph.loader.single_dep:loader
+loader: taskgraph.loader.multi_dep:loader
+
+group-by: platform
 
 transforms:
    - taskgraph.transforms.l10n:transforms
    - taskgraph.transforms.use_toolchains:transforms
    - taskgraph.transforms.job:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - build
+   - build-signing
+   - repackage
+   - repackage-signing
    - toolchain
 
 only-for-build-platforms:
    - linux64-nightly/opt
    - linux-nightly/opt
    - android-api-16-nightly/opt
    - macosx64-nightly/opt
    - win32-nightly/opt
@@ -118,37 +123,37 @@ job-template:
             win32-devedition-nightly: windows2012-32-devedition/opt
             win64-devedition-nightly: windows2012-64-devedition/opt
             android-api-16-nightly: android-4-0-armv7-api16/opt
    env:
       by-build-platform:
          linux.*:   # linux64 and 32 get same treatment here
             EN_US_PACKAGE_NAME: target.tar.bz2
             EN_US_BINARY_URL:
-               task-reference: https://queue.taskcluster.net/v1/task/<signed-build>/artifacts/{artifact_prefix}
+               task-reference: https://queue.taskcluster.net/v1/task/<build-signing>/artifacts/{artifact_prefix}
             MAR_TOOLS_URL:
-               task-reference: https://queue.taskcluster.net/v1/task/<unsigned-build>/artifacts/{artifact_prefix}/host/bin
+               task-reference: https://queue.taskcluster.net/v1/task/<build>/artifacts/{artifact_prefix}/host/bin
          macosx64.*:
             EN_US_PACKAGE_NAME: target.dmg
             EN_US_BINARY_URL:
                task-reference: https://queue.taskcluster.net/v1/task/<repackage>/artifacts/{artifact_prefix}
             MAR_TOOLS_URL:
-               task-reference: https://queue.taskcluster.net/v1/task/<unsigned-build>/artifacts/{artifact_prefix}/host/bin
+               task-reference: https://queue.taskcluster.net/v1/task/<build>/artifacts/{artifact_prefix}/host/bin
          win.*:
             EN_US_PACKAGE_NAME: target.zip
             EN_US_BINARY_URL:
-               task-reference: https://queue.taskcluster.net/v1/task/<signed-build>/artifacts/{artifact_prefix}
+               task-reference: https://queue.taskcluster.net/v1/task/<build-signing>/artifacts/{artifact_prefix}
             EN_US_INSTALLER_BINARY_URL:
-               task-reference: https://queue.taskcluster.net/v1/task/<repackage-signed>/artifacts/{artifact_prefix}
+               task-reference: https://queue.taskcluster.net/v1/task/<repackage-signing>/artifacts/{artifact_prefix}
             MAR_TOOLS_URL:
-               task-reference: https://queue.taskcluster.net/v1/task/<unsigned-build>/artifacts/{artifact_prefix}/host/bin
+               task-reference: https://queue.taskcluster.net/v1/task/<build>/artifacts/{artifact_prefix}/host/bin
          android-api-16-nightly:
             EN_US_PACKAGE_NAME: target.apk
             EN_US_BINARY_URL:
-               task-reference: https://queue.taskcluster.net/v1/task/<unsigned-build>/artifacts/{artifact_prefix}/en-US
+               task-reference: https://queue.taskcluster.net/v1/task/<build>/artifacts/{artifact_prefix}/en-US
    mozharness:
       config:
          by-build-platform:
             linux-nightly:
                - single_locale/{project}.py
                - single_locale/linux32.py
                - single_locale/tc_common.py
                - single_locale/tc_linux32.py
--- a/taskcluster/ci/partials/kind.yml
+++ b/taskcluster/ci/partials/kind.yml
@@ -6,16 +6,17 @@ loader: taskgraph.loader.single_dep:load
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.partials:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - repackage-signing
+   - repackage-signing-l10n
 
 only-for-attributes:
    - nightly
 
 only-for-build-platforms:
    - macosx64-nightly/opt
    - macosx64-devedition-nightly/opt
    - win32-nightly/opt
copy from taskcluster/ci/repackage-signing/kind.yml
copy to taskcluster/ci/repackage-signing-l10n/kind.yml
--- a/taskcluster/ci/repackage-signing/kind.yml
+++ b/taskcluster/ci/repackage-signing-l10n/kind.yml
@@ -6,17 +6,16 @@ loader: taskgraph.loader.single_dep:load
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.repackage_signing:transforms
    - taskgraph.transforms.repackage_routes:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
-   - repackage
    - repackage-l10n
 
 only-for-build-platforms:
    - linux-nightly/opt
    - linux-devedition-nightly/opt
    - linux64-nightly/opt
    - linux64-devedition-nightly/opt
    - linux64-asan-reporter-nightly/opt
--- a/taskcluster/ci/repackage-signing/kind.yml
+++ b/taskcluster/ci/repackage-signing/kind.yml
@@ -7,17 +7,16 @@ loader: taskgraph.loader.single_dep:load
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.repackage_signing:transforms
    - taskgraph.transforms.repackage_routes:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - repackage
-   - repackage-l10n
 
 only-for-build-platforms:
    - linux-nightly/opt
    - linux-devedition-nightly/opt
    - linux64-nightly/opt
    - linux64-devedition-nightly/opt
    - linux64-asan-reporter-nightly/opt
    - macosx64-nightly/opt
--- a/taskcluster/docs/kinds.rst
+++ b/taskcluster/docs/kinds.rst
@@ -433,16 +433,21 @@ repackage-l10n
 Repackage-L10n is a ```Repackage``` task split up to be suitable for use after l10n repacks.
 
 
 repackage-signing
 -----------------
 Repackage-signing take the repackaged installers (windows) and update packaging (with
 the signed internal bits) and signs them.
 
+repackage-signing-l10n
+----------------------
+Repackage-signing take the repackaged installers (windows) and update packaging (with
+the signed internal bits) and signs them for localized versions.
+
 repo-update
 -----------
 Repo-Update tasks are tasks that perform some action on the project repo itself,
 in order to update its state in some way.
 
 partials
 --------
 Partials takes the complete.mar files produced in previous tasks and generates partial
new file mode 100644
--- /dev/null
+++ b/taskcluster/taskgraph/loader/multi_dep.py
@@ -0,0 +1,99 @@
+# 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/.
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import copy
+
+
+# Define a collection of group_by functions
+GROUP_BY_MAP = {}
+
+
+def group_by(name):
+    def wrapper(func):
+        GROUP_BY_MAP[name] = func
+        return func
+    return wrapper
+
+
+def loader(kind, path, config, params, loaded_tasks):
+    """
+    Load tasks based on the jobs dependant kinds, designed for use as
+    multiple-dependent needs.
+
+    Required ``group-by-fn`` is used to define how we coalesce the
+    multiple deps together to pass to transforms, e.g. all kinds specified get
+    collapsed by platform with `platform`
+
+    The `only-for-build-platforms` kind configuration, if specified, will limit
+    the build platforms for which a job will be created. Alternatively there is
+    'not-for-build-platforms' kind configuration which will be consulted only after
+    'only-for-build-platforms' is checked (if present), and omit any jobs where the
+    build platform matches.
+
+    Optional ``job-template`` kind configuration value, if specified, will be used to
+    pass configuration down to the specified transforms used.
+    """
+    job_template = config.get('job-template')
+
+    for dep_tasks in group_tasks(config, loaded_tasks):
+        job = {'dependent-tasks': dep_tasks}
+        if job_template:
+            job.update(copy.deepcopy(job_template))
+        # copy shipping_product from upstream
+        product = dep_tasks[dep_tasks.keys()[0]].attributes.get(
+            'shipping_product',
+            dep_tasks[dep_tasks.keys()[0]].task.get('shipping-product')
+        )
+        if product:
+            job.setdefault('shipping-product', product)
+
+        yield job
+
+
+def group_tasks(config, tasks):
+    group_by_fn = GROUP_BY_MAP[config['group-by']]
+
+    groups = group_by_fn(config, tasks)
+
+    for combinations in groups.itervalues():
+        kinds = [f.kind for f in combinations]
+        assert_unique_members(kinds, error_msg=(
+            "Multi_dep.py should have filtered down to one task per kind"))
+        dependencies = {t.kind: copy.deepcopy(t) for t in combinations}
+        yield dependencies
+
+
+@group_by('platform')
+def platform_grouping(config, tasks):
+    only_platforms = config.get('only-for-build-platforms')
+    not_platforms = config.get('not-for-build-platforms')
+
+    groups = {}
+    for task in tasks:
+        if task.kind not in config.get('kind-dependencies', []):
+            continue
+        platform = task.attributes.get('build_platform')
+        build_type = task.attributes.get('build_type')
+        product = task.attributes.get('shipping_product',
+                                      task.task.get('shipping-product'))
+
+        # Skip only_ and not_ platforms that don't match
+        if only_platforms or not_platforms:
+            if not platform or not build_type:
+                continue
+            combined_platform = "{}/{}".format(platform, build_type)
+            if only_platforms and combined_platform not in only_platforms:
+                continue
+            elif not_platforms and combined_platform in not_platforms:
+                continue
+
+        groups.setdefault((platform, build_type, product), []).append(task)
+    return groups
+
+
+def assert_unique_members(kinds, error_msg=None):
+    if len(kinds) != len(set(kinds)):
+        raise Exception(error_msg)
--- a/taskcluster/taskgraph/transforms/beetmover_repackage.py
+++ b/taskcluster/taskgraph/transforms/beetmover_repackage.py
@@ -214,18 +214,21 @@ def make_task_description(config, jobs):
         repackage_name = "repackage"
         # repackage-l10n actually uses the repackage depname here
         repackage_dependencies = {"repackage":
                                   dep_job.dependencies[repackage_name]
                                   }
         dependencies.update(repackage_dependencies)
 
         # If this isn't a direct dependency, it won't be in there.
-        if 'repackage-signing' not in dependencies:
+        if 'repackage-signing' not in dependencies and \
+                'repackage-signing-l10n' not in dependencies:
             repackage_signing_name = "repackage-signing"
+            if job.get('locale'):
+                repackage_signing_name = "repackage-signing-l10n"
             repackage_signing_deps = {"repackage-signing":
                                       dep_job.dependencies[repackage_signing_name]
                                       }
             dependencies.update(repackage_signing_deps)
 
         attributes = copy_attributes_from_dependent_job(dep_job)
         if job.get('locale'):
             attributes['locale'] = job['locale']
--- a/taskcluster/taskgraph/transforms/beetmover_repackage_partner.py
+++ b/taskcluster/taskgraph/transforms/beetmover_repackage_partner.py
@@ -96,16 +96,17 @@ def make_task_description(config, jobs):
         if not repack_id:
             raise Exception("Cannot find repack id!")
 
         attributes = dep_job.attributes
         build_platform = attributes.get("build_platform")
         if not build_platform:
             raise Exception("Cannot find build platform!")
 
+        label = dep_job.label.replace("repackage-signing-l10n", "beetmover-")
         label = dep_job.label.replace("repackage-signing-", "beetmover-")
         label = label.replace("repackage-", "beetmover-")
         label = label.replace("chunking-dummy-", "beetmover-")
         description = (
             "Beetmover submission for repack_id '{repack_id}' for build '"
             "{build_platform}/{build_type}'".format(
                 repack_id=repack_id,
                 build_platform=build_platform,
--- a/taskcluster/taskgraph/transforms/l10n.py
+++ b/taskcluster/taskgraph/transforms/l10n.py
@@ -92,18 +92,18 @@ l10n_description_schema = Schema({
         # Type of index
         Optional('type'): basestring,
     },
     # Description of the localized task
     Required('description'): _by_platform(basestring),
 
     Optional('run-on-projects'): job_description_schema['run-on-projects'],
 
-    # task object of the dependent task
-    Required('dependent-task'): object,
+    # dictionary of dependent task objects, keyed by kind.
+    Required('dependent-tasks'): {basestring: object},
 
     # worker-type to utilize
     Required('worker-type'): _by_platform(basestring),
 
     # File which contains the used locales
     Required('locales-file'): _by_platform(basestring),
 
     # Tooltool visibility required for task.
@@ -192,28 +192,28 @@ def _remove_locales(locales, to_remove=N
     return {
         locale: revision for locale, revision in locales.items() if locale not in to_remove
     }
 
 
 @transforms.add
 def setup_name(config, jobs):
     for job in jobs:
-        dep = job['dependent-task']
+        dep = job['dependent-tasks']['build']
         # Set the name to the same as the dep task, without kind name.
         # Label will get set automatically with this kinds name.
         job['name'] = job.get('name',
                               dep.task['metadata']['name'][len(dep.kind) + 1:])
         yield job
 
 
 @transforms.add
 def copy_in_useful_magic(config, jobs):
     for job in jobs:
-        dep = job['dependent-task']
+        dep = job['dependent-tasks']['build']
         attributes = copy_attributes_from_dependent_job(dep)
         attributes.update(job.get('attributes', {}))
         # build-platform is needed on `job` for by-build-platform
         job['build-platform'] = attributes.get("build_platform")
         job['attributes'] = attributes
         yield job
 
 
@@ -224,30 +224,29 @@ def validate_early(config, jobs):
                         "In job {!r}:".format(job.get('name', 'unknown')))
         yield job
 
 
 @transforms.add
 def setup_nightly_dependency(config, jobs):
     """ Sets up a task dependency to the signing job this relates to """
     for job in jobs:
-        job['dependencies'] = {'unsigned-build': job['dependent-task'].label}
+        job['dependencies'] = {'build': job['dependent-tasks']['build'].label}
         if job['attributes']['build_platform'].startswith('win') or \
                 job['attributes']['build_platform'].startswith('linux'):
-            # Weave these in and just assume they will be there in the resulting graph
             job['dependencies'].update({
-                'signed-build': 'build-signing-{}'.format(job['name']),
+                'build-signing': job['dependent-tasks']['build-signing'].label,
             })
         if job['attributes']['build_platform'].startswith('macosx'):
             job['dependencies'].update({
-                'repackage': 'repackage-{}'.format(job['name'])
+                'repackage': job['dependent-tasks']['repackage'].label
             })
         if job['attributes']['build_platform'].startswith('win'):
             job['dependencies'].update({
-                'repackage-signed': 'repackage-signing-{}'.format(job['name'])
+                'repackage-signing': job['dependent-tasks']['repackage-signing'].label
             })
         yield job
 
 
 @transforms.add
 def handle_keyed_by(config, jobs):
     """Resolve fields that can be keyed by platform, etc."""
     fields = [
--- a/taskcluster/taskgraph/transforms/partials.py
+++ b/taskcluster/taskgraph/transforms/partials.py
@@ -76,16 +76,19 @@ def make_task_description(config, jobs):
 
         # If the list is empty there's no available history for this platform
         # and locale combination, so we can't build any partials.
         if not builds:
             continue
 
         signing_task = None
         for dependency in sorted(dependencies.keys()):
+            if 'repackage-signing-l10n' in dependency:
+                signing_task = dependency
+                break
             if 'repackage-signing' in dependency:
                 signing_task = dependency
                 break
         signing_task_ref = '<{}>'.format(signing_task)
 
         extra = {'funsize': {'partials': list()}}
         update_number = 1
         artifact_path = "{}{}".format(
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -103,16 +103,18 @@ const {
 const {
   EventEmitter,
 } = ExtensionCommon;
 
 XPCOMUtils.defineLazyGetter(this, "console", ExtensionCommon.getConsole);
 
 XPCOMUtils.defineLazyGetter(this, "LocaleData", () => ExtensionCommon.LocaleData);
 
+const {sharedData} = Services.ppmm;
+
 // The userContextID reserved for the extension storage (its purpose is ensuring that the IndexedDB
 // storage used by the browser.storage.local API is not directly accessible from the extension code).
 XPCOMUtils.defineLazyGetter(this, "WEBEXT_STORAGE_USER_CONTEXT_ID", () => {
   return ContextualIdentityService.getDefaultPrivateIdentity(
     "userContextIdInternal.webextStorageLocal").userContextId;
 });
 
 // The maximum time to wait for extension child shutdown blockers to complete.
@@ -1240,25 +1242,29 @@ class LangpackBootstrapScope {
   }
 
   shutdown(data, reason) {
     this.langpack.shutdown();
     this.langpack = null;
   }
 }
 
+let activeExtensionIDs = new Set();
+
 /**
  * This class is the main representation of an active WebExtension
  * in the main process.
  * @extends ExtensionData
  */
 class Extension extends ExtensionData {
   constructor(addonData, startupReason) {
     super(addonData.resourceURI);
 
+    this.sharedDataKeys = new Set();
+
     this.uuid = UUIDMap.get(addonData.id);
     this.instanceId = getUniqueId();
 
     this.MESSAGE_EMIT_EVENT = `Extension:EmitEvent:${this.instanceId}`;
     Services.ppmm.addMessageListener(this.MESSAGE_EMIT_EVENT, this);
 
     if (addonData.cleanupFile) {
       Services.obs.addObserver(this, "xpcom-shutdown");
@@ -1302,16 +1308,18 @@ class Extension extends ExtensionData {
     this.onShutdown = new Set();
 
     this.uninstallURL = null;
 
     this.whiteListedHosts = null;
     this._optionalOrigins = null;
     this.webAccessibleResources = null;
 
+    this.registeredContentScripts = new Map();
+
     this.emitter = new EventEmitter();
 
     /* eslint-disable mozilla/balanced-listeners */
     this.on("add-permissions", (ignoreEvent, permissions) => {
       for (let perm of permissions.permissions) {
         this.permissions.add(perm);
       }
 
@@ -1508,38 +1516,51 @@ class Extension extends ExtensionData {
 
     if (this.errors.length) {
       return Promise.reject({errors: this.errors});
     }
 
     return manifest;
   }
 
+  get contentSecurityPolicy() {
+    return this.manifest.content_security_policy;
+  }
+
+  get backgroundScripts() {
+    return (this.manifest.background &&
+            this.manifest.background.scripts);
+  }
+
   // Representation of the extension to send to content
   // processes. This should include anything the content process might
   // need.
   serialize() {
     return {
       id: this.id,
       uuid: this.uuid,
       name: this.name,
+      contentSecurityPolicy: this.contentSecurityPolicy,
       instanceId: this.instanceId,
-      manifest: this.manifest,
       resourceURL: this.resourceURL,
-      baseURL: this.baseURI.spec,
       contentScripts: this.contentScripts,
-      registeredContentScripts: new Map(),
       webAccessibleResources: this.webAccessibleResources.map(res => res.glob),
       whiteListedHosts: this.whiteListedHosts.patterns.map(pat => pat.pattern),
-      localeData: this.localeData.serialize(),
+      permissions: this.permissions,
+      optionalPermissions: this.manifest.optional_permissions,
+    };
+  }
+
+  // Extended serialized data which is only needed in the extensions process,
+  // and is never deserialized in web content processes.
+  serializeExtended() {
+    return {
+      backgroundScripts: this.backgroundScripts,
       childModules: this.modules && this.modules.child,
       dependencies: this.dependencies,
-      permissions: this.permissions,
-      principal: this.principal,
-      optionalPermissions: this.manifest.optional_permissions,
       schemaURLs: this.schemaURLs,
     };
   }
 
   get contentScripts() {
     return this.manifest.content_scripts || [];
   }
 
@@ -1572,42 +1593,55 @@ class Extension extends ExtensionData {
       ppmm.addMessageListener(msg + "Complete", listener, true);
       Services.obs.addObserver(observer, "message-manager-close");
       Services.obs.addObserver(observer, "message-manager-disconnect");
 
       ppmm.broadcastAsyncMessage(msg, data);
     });
   }
 
+  setSharedData(key, value) {
+    key = `extension/${this.id}/${key}`;
+    this.sharedDataKeys.add(key);
+
+    sharedData.set(key, value);
+  }
+
+  getSharedData(key, value) {
+    key = `extension/${this.id}/${key}`;
+    return sharedData.get(key);
+  }
+
+  initSharedData() {
+    this.setSharedData("", this.serialize());
+    this.setSharedData("extendedData", this.serializeExtended());
+    this.setSharedData("locales", this.localeData.serialize());
+    this.setSharedData("manifest", this.manifest);
+    this.updateContentScripts();
+  }
+
+  updateContentScripts() {
+    this.setSharedData("contentScripts", this.registeredContentScripts);
+  }
+
   runManifest(manifest) {
     let promises = [];
     for (let directive in manifest) {
       if (manifest[directive] !== null) {
         promises.push(Management.emit(`manifest_${directive}`, directive, this, manifest));
 
         promises.push(Management.asyncEmitManifestEntry(this, directive));
       }
     }
 
-    let data = Services.ppmm.initialProcessData;
-    if (!data["Extension:Extensions"]) {
-      data["Extension:Extensions"] = [];
-    }
-
-    let serial = this.serialize();
+    activeExtensionIDs.add(this.id);
+    sharedData.set("extensions/activeIDs", activeExtensionIDs);
 
-    // Map of the programmatically registered content script definitions
-    // (by string scriptId), used in ext-contentScripts.js to propagate
-    // the registered content scripts to the child content processes
-    // (e.g. when a new content process starts after a content process crash).
-    this.registeredContentScripts = serial.registeredContentScripts;
-
-    data["Extension:Extensions"].push(serial);
-
-    return this.broadcast("Extension:Startup", serial).then(() => {
+    Services.ppmm.sharedData.flush();
+    return this.broadcast("Extension:Startup", this.id).then(() => {
       return Promise.all(promises);
     });
   }
 
   /**
    * Call the close() method on the given object when this extension
    * is shut down.  This can happen during browser shutdown, or when
    * an extension is manually disabled or uninstalled.
@@ -1722,16 +1756,18 @@ class Extension extends ExtensionData {
       }
 
       if (this.hasShutdown) {
         return;
       }
 
       GlobalManager.init(this);
 
+      this.initSharedData();
+
       this.policy.active = false;
       this.policy = processScript.initExtension(this);
       this.policy.extension = this;
 
       this.updatePermissions(this.startupReason);
 
       // The "startup" Management event sent on the extension instance itself
       // is emitted just before the Management "startup" event,
@@ -1793,18 +1829,22 @@ class Extension extends ExtensionData {
       Services.ppmm.broadcastAsyncMessage("Extension:FlushJarCache", {path: file.path});
     }
 
     if (this.cleanupFile ||
         ["ADDON_INSTALL", "ADDON_UNINSTALL", "ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(reason)) {
       StartupCache.clearAddonData(this.id);
     }
 
-    let data = Services.ppmm.initialProcessData;
-    data["Extension:Extensions"] = data["Extension:Extensions"].filter(e => e.id !== this.id);
+    activeExtensionIDs.delete(this.id);
+    sharedData.set("extensions/activeIDs", activeExtensionIDs);
+
+    for (let key of this.sharedDataKeys) {
+      sharedData.delete(key);
+    }
 
     Services.ppmm.removeMessageListener(this.MESSAGE_EMIT_EVENT, this);
 
     this.updatePermissions(this.shutdownReason);
 
     if (!this.manifest) {
       this.policy.active = false;
 
--- a/toolkit/components/extensions/ExtensionChild.jsm
+++ b/toolkit/components/extensions/ExtensionChild.jsm
@@ -54,16 +54,18 @@ const {
   LocalAPIImplementation,
   LocaleData,
   NoCloneSpreadArgs,
   SchemaAPIInterface,
   defineLazyGetter,
   withHandlingUserInput,
 } = ExtensionCommon;
 
+const {sharedData} = Services.cpmm;
+
 const isContentProcess = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
 
 // Copy an API object from |source| into the scope |dest|.
 function injectAPI(source, dest) {
   for (let prop in source) {
     // Skip names prefixed with '_'.
     if (prop[0] == "_") {
       continue;
@@ -597,43 +599,45 @@ class BrowserExtensionContent extends Ev
   constructor(data) {
     super();
 
     this.data = data;
     this.id = data.id;
     this.uuid = data.uuid;
     this.instanceId = data.instanceId;
 
-    this.childModules = data.childModules;
-    this.dependencies = data.dependencies;
-    this.schemaURLs = data.schemaURLs;
+    if (WebExtensionPolicy.isExtensionProcess) {
+      Object.assign(this, this.getSharedData("extendedData"));
+    }
 
     this.MESSAGE_EMIT_EVENT = `Extension:EmitEvent:${this.instanceId}`;
     Services.cpmm.addMessageListener(this.MESSAGE_EMIT_EVENT, this);
 
     defineLazyGetter(this, "scripts", () => {
       return data.contentScripts.map(scriptData => new ExtensionContent.Script(this, scriptData));
     });
 
     this.webAccessibleResources = data.webAccessibleResources.map(res => new MatchGlob(res));
     this.permissions = data.permissions;
     this.optionalPermissions = data.optionalPermissions;
-    this.principal = data.principal;
 
     let restrictSchemes = !this.hasPermission("mozillaAddons");
 
     this.whiteListedHosts = new MatchPatternSet(data.whiteListedHosts, {restrictSchemes, ignorePath: true});
 
     this.apiManager = this.getAPIManager();
 
-    this.localeData = new LocaleData(data.localeData);
+    this._manifest = null;
+    this._localeData = null;
 
-    this.manifest = data.manifest;
-    this.baseURL = data.baseURL;
-    this.baseURI = Services.io.newURI(data.baseURL);
+    this.baseURI = Services.io.newURI(`moz-extension://${this.uuid}/`);
+    this.baseURL = this.baseURI.spec;
+
+    this.principal = Services.scriptSecurityManager.createCodebasePrincipal(
+      this.baseURI, {});
 
     // Only used in addon processes.
     this.views = new Set();
 
     // Only used for devtools views.
     this.devtoolsViews = new Set();
 
     /* eslint-disable mozilla/balanced-listeners */
@@ -678,23 +682,43 @@ class BrowserExtensionContent extends Ev
         this.policy.allowedOrigins = this.whiteListedHosts;
       }
     });
     /* eslint-enable mozilla/balanced-listeners */
 
     ExtensionManager.extensions.set(this.id, this);
   }
 
+  getSharedData(key, value) {
+    return sharedData.get(`extension/${this.id}/${key}`);
+  }
+
+  get localeData() {
+    if (!this._localeData) {
+      this._localeData = new LocaleData(this.getSharedData("locales"));
+    }
+    return this._localeData;
+  }
+
+  get manifest() {
+    if (!this._manifest) {
+      this._manifest = this.getSharedData("manifest");
+    }
+    return this._manifest;
+  }
+
   getAPIManager() {
     let apiManagers = [ExtensionPageChild.apiManager];
 
-    for (let id of this.dependencies) {
-      let extension = processScript.getExtensionChild(id);
-      if (extension) {
-        apiManagers.push(extension.experimentAPIManager);
+    if (this.dependencies) {
+      for (let id of this.dependencies) {
+        let extension = processScript.getExtensionChild(id);
+        if (extension) {
+          apiManagers.push(extension.experimentAPIManager);
+        }
       }
     }
 
     if (this.childModules) {
       this.experimentAPIManager =
         new ExtensionCommon.LazyAPIManager("addon", this.childModules, this.schemaURLs);
 
       apiManagers.push(this.experimentAPIManager);
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -135,28 +135,30 @@ let apiManager = new class extends Schem
 
       this.initGlobal();
       for (let script of scripts) {
         script.executeInGlobal(this.global);
       }
 
       // Load order matters here. The base manifest defines types which are
       // extended by other schemas, so needs to be loaded first.
-      return Schemas.load(BASE_SCHEMA, AppConstants.DEBUG).then(() => {
+      return Schemas.load(BASE_SCHEMA).then(() => {
         let promises = [];
         for (let [/* name */, url] of XPCOMUtils.enumerateCategoryEntries(CATEGORY_EXTENSION_SCHEMAS)) {
           promises.push(Schemas.load(url));
         }
         for (let [url, {content}] of this.schemaURLs) {
           promises.push(Schemas.load(url, content));
         }
         for (let url of schemaURLs) {
           promises.push(Schemas.load(url));
         }
-        return Promise.all(promises);
+        return Promise.all(promises).then(() => {
+          Schemas.updateSharedSchemas();
+        });
       });
     })();
 
     /* eslint-disable mozilla/balanced-listeners */
     Services.mm.addMessageListener("Extension:GetTabAndWindowId", this);
     /* eslint-enable mozilla/balanced-listeners */
 
     this.initialized = promise;
@@ -744,29 +746,24 @@ class DevToolsExtensionPageContextParent
 
     super.shutdown();
   }
 }
 
 ParentAPIManager = {
   proxyContexts: new Map(),
 
-  parentMessageManagers: new Set(),
-
   init() {
     Services.obs.addObserver(this, "message-manager-close");
-    Services.obs.addObserver(this, "ipc:content-created");
 
     Services.mm.addMessageListener("API:CreateProxyContext", this);
     Services.mm.addMessageListener("API:CloseProxyContext", this, true);
     Services.mm.addMessageListener("API:Call", this);
     Services.mm.addMessageListener("API:AddListener", this);
     Services.mm.addMessageListener("API:RemoveListener", this);
-
-    this.schemaHook = this.schemaHook.bind(this);
   },
 
   attachMessageManager(extension, processMessageManager) {
     extension.parentMessageManager = processMessageManager;
   },
 
   async observe(subject, topic, data) {
     if (topic === "message-manager-close") {
@@ -778,33 +775,16 @@ ParentAPIManager = {
       }
 
       // Reset extension message managers when their child processes shut down.
       for (let extension of GlobalManager.extensionMap.values()) {
         if (extension.parentMessageManager === mm) {
           extension.parentMessageManager = null;
         }
       }
-
-      this.parentMessageManagers.delete(mm);
-    } else if (topic === "ipc:content-created") {
-      let mm = subject.QueryInterface(Ci.nsIInterfaceRequestor)
-                      .getInterface(Ci.nsIMessageSender);
-      if (mm.remoteType === E10SUtils.EXTENSION_REMOTE_TYPE) {
-        this.parentMessageManagers.add(mm);
-        mm.sendAsyncMessage("Schema:Add", Schemas.schemaJSON);
-
-        Schemas.schemaHook = this.schemaHook;
-      }
-    }
-  },
-
-  schemaHook(schemas) {
-    for (let mm of this.parentMessageManagers) {
-      mm.sendAsyncMessage("Schema:Add", schemas);
     }
   },
 
   shutdownExtension(extensionId) {
     for (let [childId, context] of this.proxyContexts) {
       if (context.extension.id == extensionId) {
         if (["ADDON_DISABLE", "ADDON_UNINSTALL"].includes(context.extension.shutdownReason)) {
           let modules = apiManager.eventModules.get("disable");
--- a/toolkit/components/extensions/Schemas.jsm
+++ b/toolkit/components/extensions/Schemas.jsm
@@ -27,16 +27,19 @@ ChromeUtils.defineModuleGetter(this, "Ne
 XPCOMUtils.defineLazyServiceGetter(this, "contentPolicyService",
                                    "@mozilla.org/addons/content-policy;1",
                                    "nsIAddonContentPolicy");
 
 XPCOMUtils.defineLazyGetter(this, "StartupCache", () => ExtensionParent.StartupCache);
 
 var EXPORTED_SYMBOLS = ["SchemaRoot", "Schemas"];
 
+const KEY_CONTENT_SCHEMAS = "extensions-framework/schemas/content";
+const KEY_PRIVILEGED_SCHEMAS = "extensions-framework/schemas/privileged";
+
 const {DEBUG} = AppConstants;
 
 const isParentProcess = Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT;
 
 function readJSON(url) {
   return new Promise((resolve, reject) => {
     NetUtil.asyncFetch({uri: url, loadUsingSystemPrincipal: true}, (inputStream, status) => {
       if (!Components.isSuccessCode(status)) {
@@ -3052,20 +3055,23 @@ this.Schemas = {
   initialized: false,
 
   REVOKE: Symbol("@@revoke"),
 
   // Maps a schema URL to the JSON contained in that schema file. This
   // is useful for sending the JSON across processes.
   schemaJSON: new Map(),
 
-  // A separate map of schema JSON which should be available in all
-  // content processes.
+
+  // A map of schema JSON which should be available in all content processes.
   contentSchemaJSON: new Map(),
 
+  // A map of schema JSON which should only be available to extension processes.
+  privilegedSchemaJSON: new Map(),
+
   _rootSchema: null,
 
   get rootSchema() {
     if (!this.initialized) {
       this.init();
     }
     if (!this._rootSchema) {
       this._rootSchema = new SchemaRoot(null, this.schemaJSON);
@@ -3080,45 +3086,30 @@ this.Schemas = {
 
   init() {
     if (this.initialized) {
       return;
     }
     this.initialized = true;
 
     if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
-      let data = Services.cpmm.initialProcessData;
-      let schemas = data["Extension:Schemas"];
-      if (schemas) {
-        this.schemaJSON = schemas;
+      let addSchemas = schemas => {
+        for (let [key, value] of schemas.entries()) {
+          this.schemaJSON.set(key, value);
+        }
+      };
+
+      if (WebExtensionPolicy.isExtensionProcess || DEBUG) {
+        addSchemas(Services.cpmm.sharedData.get(KEY_PRIVILEGED_SCHEMAS));
       }
 
-      Services.cpmm.addMessageListener("Schema:Add", this);
-    }
-  },
-
-  receiveMessage(msg) {
-    let {data} = msg;
-    switch (msg.name) {
-      case "Schema:Add":
-        // If we're given a Map, the ordering of the initial items
-        // matters, so swap with our current data to make sure its
-        // entries appear first.
-        if (typeof data.get === "function") {
-          // Create a new Map so we're sure it's in the same compartment.
-          [this.schemaJSON, data] = [new Map(data), this.schemaJSON];
-        }
-
-        for (let [url, schema] of data) {
-          this.schemaJSON.set(url, schema);
-        }
-        if (this._rootSchema) {
-          throw new Error("Schema loaded after root schema populated");
-        }
-        break;
+      let schemas = Services.cpmm.sharedData.get(KEY_CONTENT_SCHEMAS);
+      if (schemas) {
+        addSchemas(schemas);
+      }
     }
   },
 
   _loadCachedSchemasPromise: null,
   loadCachedSchemas() {
     if (!this._loadCachedSchemasPromise) {
       this._loadCachedSchemasPromise = StartupCache.schemas.getAll().then(results => {
         return results;
@@ -3128,30 +3119,32 @@ this.Schemas = {
     return this._loadCachedSchemasPromise;
   },
 
   addSchema(url, schema, content = false) {
     this.schemaJSON.set(url, schema);
 
     if (content) {
       this.contentSchemaJSON.set(url, schema);
-
-      let data = Services.ppmm.initialProcessData;
-      data["Extension:Schemas"] = this.contentSchemaJSON;
-
-      Services.ppmm.broadcastAsyncMessage("Schema:Add", [[url, schema]]);
-    } else if (this.schemaHook) {
-      this.schemaHook([[url, schema]]);
+    } else {
+      this.privilegedSchemaJSON.set(url, schema);
     }
 
     if (this._rootSchema) {
       throw new Error("Schema loaded after root schema populated");
     }
   },
 
+  updateSharedSchemas() {
+    let {sharedData} = Services.ppmm;
+
+    sharedData.set(KEY_CONTENT_SCHEMAS, this.contentSchemaJSON);
+    sharedData.set(KEY_PRIVILEGED_SCHEMAS, this.privilegedSchemaJSON);
+  },
+
   fetch(url) {
     return readJSONAndBlobbify(url);
   },
 
   processSchema(json) {
     return blobbify(json);
   },
 
--- a/toolkit/components/extensions/extension-process-script.js
+++ b/toolkit/components/extensions/extension-process-script.js
@@ -25,16 +25,22 @@ ChromeUtils.import("resource://gre/modul
 
 XPCOMUtils.defineLazyGetter(this, "console", () => ExtensionCommon.getConsole());
 
 const {
   DefaultWeakMap,
   getInnerWindowID,
 } = ExtensionUtils;
 
+const {sharedData} = Services.cpmm;
+
+function getData(extension, key = "") {
+  return sharedData.get(`extension/${extension.id}/${key}`);
+}
+
 // We need to avoid touching Services.appinfo here in order to prevent
 // the wrong version from being cached during xpcshell test startup.
 // eslint-disable-next-line mozilla/use-services
 const appinfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
 const isContentProcess = appinfo.processType == appinfo.PROCESS_TYPE_CONTENT;
 
 function tryMatchPatternSet(patterns, options) {
   try {
@@ -296,30 +302,18 @@ ExtensionManager = {
     MessageChannel.setupMessageManagers([Services.cpmm]);
 
     Services.cpmm.addMessageListener("Extension:Startup", this);
     Services.cpmm.addMessageListener("Extension:Shutdown", this);
     Services.cpmm.addMessageListener("Extension:FlushJarCache", this);
     Services.cpmm.addMessageListener("Extension:RegisterContentScript", this);
     Services.cpmm.addMessageListener("Extension:UnregisterContentScripts", this);
 
-    let procData = Services.cpmm.initialProcessData || {};
-
-    for (let data of procData["Extension:Extensions"] || []) {
-      this.initExtension(data);
-    }
-
-    if (isContentProcess) {
-      // Make sure we handle new schema data until Schemas.jsm is loaded.
-      if (!procData["Extension:Schemas"]) {
-        procData["Extension:Schemas"] = new Map();
-      }
-      this.schemaJSON = procData["Extension:Schemas"];
-
-      Services.cpmm.addMessageListener("Schema:Add", this);
+    for (let id of sharedData.get("extensions/activeIDs") || []) {
+      this.initExtension(getData({id}));
     }
   },
 
   initExtensionPolicy(extension) {
     let policy = WebExtensionPolicy.getByID(extension.id);
     if (!policy) {
       let localizeCallback, allowedOrigins, webAccessibleResources;
       let restrictSchemes = !extension.permissions.has("mozillaAddons");
@@ -331,59 +325,64 @@ ExtensionManager = {
         webAccessibleResources = extension.webAccessibleResources;
       } else {
         // We have serialized extension data;
         localizeCallback = str => extensions.get(policy).localize(str);
         allowedOrigins = new MatchPatternSet(extension.whiteListedHosts, {restrictSchemes});
         webAccessibleResources = extension.webAccessibleResources.map(host => new MatchGlob(host));
       }
 
+      let {backgroundScripts} = extension;
+      if (!backgroundScripts && WebExtensionPolicy.isExtensionProcess) {
+        ({backgroundScripts} = getData(extension, "extendedData") || {});
+      }
+
       policy = new WebExtensionPolicy({
         id: extension.id,
         mozExtensionHostname: extension.uuid,
         name: extension.name,
         baseURL: extension.resourceURL,
 
         permissions: Array.from(extension.permissions),
         allowedOrigins,
         webAccessibleResources,
 
-        contentSecurityPolicy: extension.manifest.content_security_policy,
+        contentSecurityPolicy: extension.contentSecurityPolicy,
 
         localizeCallback,
 
-        backgroundScripts: (extension.manifest.background &&
-                            extension.manifest.background.scripts),
+        backgroundScripts,
 
         contentScripts: extension.contentScripts.map(script => parseScriptOptions(script, restrictSchemes)),
       });
 
       policy.debugName = `${JSON.stringify(policy.name)} (ID: ${policy.id}, ${policy.getURL()})`;
 
       // Register any existent dynamically registered content script for the extension
       // when a content process is started for the first time (which also cover
       // a content process that crashed and it has been recreated).
       const registeredContentScripts = this.registeredContentScripts.get(policy);
 
-      if (extension.registeredContentScripts) {
-        for (let [scriptId, options] of extension.registeredContentScripts) {
-          const parsedOptions = parseScriptOptions(options, restrictSchemes);
-          const script = new WebExtensionContentScript(policy, parsedOptions);
-          policy.registerContentScript(script);
-          registeredContentScripts.set(scriptId, script);
-        }
+      for (let [scriptId, options] of getData(extension, "contentScripts") || []) {
+        const parsedOptions = parseScriptOptions(options, restrictSchemes);
+        const script = new WebExtensionContentScript(policy, parsedOptions);
+        policy.registerContentScript(script);
+        registeredContentScripts.set(scriptId, script);
       }
 
       policy.active = true;
       policy.initData = extension;
     }
     return policy;
   },
 
   initExtension(data) {
+    if (typeof data === "string") {
+      data = getData({id: data});
+    }
     let policy = this.initExtensionPolicy(data);
 
     DocumentManager.initExtension(policy);
   },
 
   receiveMessage({name, data}) {
     switch (name) {
       case "Extension:Startup": {
@@ -410,33 +409,16 @@ ExtensionManager = {
       }
 
       case "Extension:FlushJarCache": {
         ExtensionUtils.flushJarCache(data.path);
         Services.cpmm.sendAsyncMessage("Extension:FlushJarCacheComplete");
         break;
       }
 
-      case "Schema:Add": {
-        // If we're given a Map, the ordering of the initial items
-        // matters, so swap with our current data to make sure its
-        // entries appear first.
-        if (typeof data.get === "function") {
-          [this.schemaJSON, data] = [data, this.schemaJSON];
-
-          Services.cpmm.initialProcessData["Extension:Schemas"] =
-            this.schemaJSON;
-        }
-
-        for (let [url, schema] of data) {
-          this.schemaJSON.set(url, schema);
-        }
-        break;
-      }
-
       case "Extension:RegisterContentScript": {
         let policy = WebExtensionPolicy.getByID(data.id);
 
         if (policy) {
           const registeredContentScripts = this.registeredContentScripts.get(policy);
 
           if (registeredContentScripts.has(data.scriptId)) {
             Cu.reportError(new Error(
--- a/toolkit/components/extensions/parent/ext-contentScripts.js
+++ b/toolkit/components/extensions/parent/ext-contentScripts.js
@@ -126,16 +126,17 @@ this.contentScripts = class extends Exte
           return;
         }
 
         const scriptIds = Array.from(parentScriptsMap.keys());
 
         for (let scriptId of scriptIds) {
           extension.registeredContentScripts.delete(scriptId);
         }
+        extension.updateContentScripts();
 
         extension.broadcast("Extension:UnregisterContentScripts", {
           id: extension.id,
           scriptIds,
         });
       },
     });
 
@@ -157,16 +158,17 @@ this.contentScripts = class extends Exte
 
           await extension.broadcast("Extension:RegisterContentScript", {
             id: extension.id,
             options: scriptOptions,
             scriptId,
           });
 
           extension.registeredContentScripts.set(scriptId, scriptOptions);
+          extension.updateContentScripts();
 
           return scriptId;
         },
 
         // This method is not available to the extension code, the extension code
         // doesn't have access to the internally used scriptId, on the contrary
         // the extension code will call script.unregister on the script API object
         // that is resolved from the register API method returned promise.
@@ -175,16 +177,17 @@ this.contentScripts = class extends Exte
           if (!contentScript) {
             Cu.reportError(new Error(`No such content script ID: ${scriptId}`));
 
             return;
           }
 
           parentScriptsMap.delete(scriptId);
           extension.registeredContentScripts.delete(scriptId);
+          extension.updateContentScripts();
 
           contentScript.destroy();
 
           await extension.broadcast("Extension:UnregisterContentScripts", {
             id: extension.id,
             scriptIds: [scriptId],
           });
         },
--- a/xpcom/base/nsMemoryReporterManager.cpp
+++ b/xpcom/base/nsMemoryReporterManager.cpp
@@ -1516,17 +1516,17 @@ NS_IMPL_ISUPPORTS(DMDReporter, nsIMemory
 } // namespace mozilla
 
 #endif  // MOZ_DMD
 
 /**
  ** nsMemoryReporterManager implementation
  **/
 
-NS_IMPL_ISUPPORTS(nsMemoryReporterManager, nsIMemoryReporterManager)
+NS_IMPL_ISUPPORTS(nsMemoryReporterManager, nsIMemoryReporterManager, nsIMemoryReporter)
 
 NS_IMETHODIMP
 nsMemoryReporterManager::Init()
 {
   if (!NS_IsMainThread()) {
     MOZ_CRASH();
   }
 
@@ -1609,16 +1609,19 @@ nsMemoryReporterManager::Init()
 #ifdef XP_WIN
   RegisterStrongReporter(new WindowsAddressSpaceReporter());
 #endif
 
 #ifdef XP_UNIX
   nsMemoryInfoDumper::Initialize();
 #endif
 
+  // Report our own memory usage as well.
+  RegisterWeakReporter(this);
+
   return NS_OK;
 }
 
 nsMemoryReporterManager::nsMemoryReporterManager()
   : mMutex("nsMemoryReporterManager::mMutex")
   , mIsRegistrationBlocked(false)
   , mStrongReporters(new StrongReportersTable())
   , mWeakReporters(new WeakReportersTable())
@@ -1636,16 +1639,32 @@ nsMemoryReporterManager::nsMemoryReporte
 nsMemoryReporterManager::~nsMemoryReporterManager()
 {
   delete mStrongReporters;
   delete mWeakReporters;
   NS_ASSERTION(!mSavedStrongReporters, "failed to restore strong reporters");
   NS_ASSERTION(!mSavedWeakReporters, "failed to restore weak reporters");
 }
 
+NS_IMETHODIMP
+nsMemoryReporterManager::CollectReports(nsIHandleReportCallback* aHandleReport,
+                                        nsISupports* aData, bool aAnonymize)
+{
+  size_t n = MallocSizeOf(this);
+  n += mStrongReporters->ShallowSizeOfIncludingThis(MallocSizeOf);
+  n += mWeakReporters->ShallowSizeOfIncludingThis(MallocSizeOf);
+
+  MOZ_COLLECT_REPORT(
+    "explicit/memory-reporter-manager", KIND_HEAP, UNITS_BYTES,
+    n,
+    "Memory used by the memory reporter infrastructure.");
+
+  return NS_OK;
+}
+
 #ifdef DEBUG_CHILD_PROCESS_MEMORY_REPORTING
 #define MEMORY_REPORTING_LOG(format, ...) \
   printf_stderr("++++ MEMORY REPORTING: " format, ##__VA_ARGS__);
 #else
 #define MEMORY_REPORTING_LOG(...)
 #endif
 
 NS_IMETHODIMP
--- a/xpcom/base/nsMemoryReporterManager.h
+++ b/xpcom/base/nsMemoryReporterManager.h
@@ -20,23 +20,27 @@ namespace mozilla {
 class MemoryReportingProcess;
 namespace dom {
 class MemoryReport;
 } // namespace dom
 } // namespace mozilla
 
 class nsITimer;
 
-class nsMemoryReporterManager final : public nsIMemoryReporterManager
+class nsMemoryReporterManager final : public nsIMemoryReporterManager,
+                                      public nsIMemoryReporter
 {
   virtual ~nsMemoryReporterManager();
 
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIMEMORYREPORTERMANAGER
+  NS_DECL_NSIMEMORYREPORTER
+
+  MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
 
   nsMemoryReporterManager();
 
   // Gets the memory reporter manager service.
   static nsMemoryReporterManager* GetOrCreate()
   {
     nsCOMPtr<nsIMemoryReporterManager> imgr =
       do_GetService("@mozilla.org/memory-reporter-manager;1");