Merge inbound to mozilla-central. a=merge
authorGurzau Raul <rgurzau@mozilla.com>
Sat, 28 Jul 2018 12:45:44 +0300
changeset 428844 0e816b4f580a6ccdae161c80f57f51128b98f021
parent 428821 8a1379826329a3d0080ad5a0c3633c9b3ddf4578 (current diff)
parent 428843 48ecd927fd87a9deffe4da6998badf7fd71afae2 (diff)
child 428847 fe6d3afb3cabccbe891f561c592e51069068354e
child 428859 2961b5e8758b629b814caf83ab3c4a8d82343873
push id34346
push userrgurzau@mozilla.com
push dateSat, 28 Jul 2018 09:46:41 +0000
treeherdermozilla-central@0e816b4f580a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone63.0a1
first release with
nightly linux32
0e816b4f580a / 63.0a1 / 20180728101501 / files
nightly linux64
0e816b4f580a / 63.0a1 / 20180728101501 / files
nightly mac
0e816b4f580a / 63.0a1 / 20180728101501 / files
nightly win32
0e816b4f580a / 63.0a1 / 20180728101501 / files
nightly win64
0e816b4f580a / 63.0a1 / 20180728101501 / 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
--- a/accessible/aom/AccessibleNode.cpp
+++ b/accessible/aom/AccessibleNode.cpp
@@ -42,16 +42,17 @@ NS_INTERFACE_MAP_END
 NS_IMPL_CYCLE_COLLECTING_ADDREF(AccessibleNode)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(AccessibleNode)
 
 AccessibleNode::AccessibleNode(nsINode* aNode) :
     mDoubleProperties(3),
     mIntProperties(3),
     mUIntProperties(6),
     mBooleanProperties(0),
+    mRelationProperties(3),
     mStringProperties(16),
     mDOMNode(aNode)
 {
   nsAccessibilityService* accService = GetOrCreateAccService();
   if (!accService) {
     return;
   }
 
--- a/accessible/aom/AccessibleNode.h
+++ b/accessible/aom/AccessibleNode.h
@@ -46,28 +46,45 @@ struct ParentObject;
     return GetProperty(AOMStringProperty::e##name, a##name); \
   }                                                          \
                                                              \
   void Set##name(const nsAString& a##name)                   \
   {                                                          \
     SetProperty(AOMStringProperty::e##name, a##name);        \
   }                                                          \
 
+#define ANODE_RELATION_FUNC(name)                       \
+  already_AddRefed<AccessibleNode> Get##name()          \
+  {                                                     \
+    return GetProperty(AOMRelationProperty::e##name);   \
+  }                                                     \
+                                                        \
+  void Set##name(AccessibleNode* a##name)               \
+  {                                                     \
+    SetProperty(AOMRelationProperty::e##name, a##name); \
+  }                                                     \
+
 #define ANODE_PROPS(typeName, type, ...)                     \
   enum class AOM##typeName##Property {                       \
     MOZ_FOR_EACH(ANODE_ENUM, (), (__VA_ARGS__))              \
   };                                                         \
   MOZ_FOR_EACH(ANODE_FUNC, (typeName, type,), (__VA_ARGS__)) \
 
 #define ANODE_STRING_PROPS(...)                               \
   enum class AOMStringProperty {                              \
     MOZ_FOR_EACH(ANODE_ENUM, (), (__VA_ARGS__))               \
   };                                                          \
   MOZ_FOR_EACH(ANODE_STRING_FUNC, (), (__VA_ARGS__))          \
 
+#define ANODE_RELATION_PROPS(...)                               \
+  enum class AOMRelationProperty {                              \
+    MOZ_FOR_EACH(ANODE_ENUM, (), (__VA_ARGS__))                 \
+  };                                                            \
+  MOZ_FOR_EACH(ANODE_RELATION_FUNC, (), (__VA_ARGS__))          \
+
 #define ANODE_ACCESSOR_MUTATOR(typeName, type, defVal)                          \
   nsDataHashtable<nsUint32HashKey, type> m##typeName##Properties;               \
                                                                                 \
   dom::Nullable<type> GetProperty(AOM##typeName##Property aProperty)            \
   {                                                                             \
     type value = defVal;                                                        \
     if (m##typeName##Properties.Get(static_cast<int>(aProperty), &value)) {     \
       return dom::Nullable<type>(value);                                        \
@@ -159,16 +176,22 @@ public:
   )
 
   ANODE_PROPS(Double, double,
     ValueMax,
     ValueMin,
     ValueNow
   )
 
+  ANODE_RELATION_PROPS(
+    ActiveDescendant,
+    Details,
+    ErrorMessage
+  )
+
 protected:
   AccessibleNode(const AccessibleNode& aCopy) = delete;
   AccessibleNode& operator=(const AccessibleNode& aCopy) = delete;
   virtual ~AccessibleNode();
 
   void GetProperty(AOMStringProperty aProperty, nsAString& aRetval) {
     nsString data;
     if (!mStringProperties.Get(static_cast<int>(aProperty), &data)) {
@@ -208,19 +231,39 @@ protected:
                                            : mBooleanProperties & ~(1U << (2 * num + 1)));
     }
   }
 
   ANODE_ACCESSOR_MUTATOR(Double, double, 0.0)
   ANODE_ACCESSOR_MUTATOR(Int, int32_t, 0)
   ANODE_ACCESSOR_MUTATOR(UInt, uint32_t, 0)
 
+  already_AddRefed<AccessibleNode> GetProperty(AOMRelationProperty aProperty)
+  {
+    RefPtr<AccessibleNode> data;
+    if (mRelationProperties.Get(static_cast<int>(aProperty), &data)) {
+      return data.forget();
+    }
+    return nullptr;
+  }
+
+  void SetProperty(AOMRelationProperty aProperty,
+                   AccessibleNode* aValue)
+  {
+    if (!aValue) {
+      mRelationProperties.Remove(static_cast<int>(aProperty));
+    } else {
+      mRelationProperties.Put(static_cast<int>(aProperty), aValue);
+    }
+  }
+
   // The 2k'th bit indicates whether the k'th boolean property is used(1) or not(0)
   // and 2k+1'th bit contains the property's value(1:true, 0:false)
   uint32_t mBooleanProperties;
+  nsDataHashtable<nsUint32HashKey, RefPtr<AccessibleNode> > mRelationProperties;
   nsDataHashtable<nsUint32HashKey, nsString> mStringProperties;
 
   RefPtr<a11y::Accessible> mIntl;
   RefPtr<nsINode> mDOMNode;
   RefPtr<dom::DOMStringList> mStates;
 };
 
 } // dom
--- a/accessible/jsat/AccessFu.jsm
+++ b/accessible/jsat/AccessFu.jsm
@@ -24,16 +24,23 @@ const GECKOVIEW_MESSAGE = {
   PREVIOUS: "GeckoView:AccessibilityPrevious",
   SCROLL_BACKWARD: "GeckoView:AccessibilityScrollBackward",
   SCROLL_FORWARD: "GeckoView:AccessibilityScrollForward",
   EXPLORE_BY_TOUCH: "GeckoView:AccessibilityExploreByTouch",
   SET_SELECTION: "GeckoView:AccessibilitySetSelection",
   CLIPBOARD: "GeckoView:AccessibilityClipboard",
 };
 
+const ACCESSFU_MESSAGE = {
+  PRESENT: "AccessFu:Present",
+  DOSCROLL: "AccessFu:DoScroll",
+};
+
+const FRAME_SCRIPT = "chrome://global/content/accessibility/content-script.js";
+
 var AccessFu = {
   /**
    * A lazy getter for event handler that binds the scope to AccessFu object.
    */
   get handleEvent() {
     delete this.handleEvent;
     this.handleEvent = this._handleEvent.bind(this);
     return this.handleEvent;
@@ -59,21 +66,16 @@ var AccessFu = {
     Services.obs.addObserver(this, "inprocess-browser-shown");
     Services.ww.registerNotification(this);
 
     let windows = Services.wm.getEnumerator(null);
     while (windows.hasMoreElements()) {
       this._attachWindow(windows.getNext());
     }
 
-    if (this.readyCallback) {
-      this.readyCallback();
-      delete this.readyCallback;
-    }
-
     Logger.info("AccessFu:Enabled");
   },
 
   /**
    * Disable AccessFu and return to default interaction mode.
    */
   disable: function disable() {
     if (!this._enabled) {
@@ -102,62 +104,55 @@ var AccessFu = {
   },
 
   receiveMessage: function receiveMessage(aMessage) {
     Logger.debug(() => {
       return ["Recieved", aMessage.name, JSON.stringify(aMessage.json)];
     });
 
     switch (aMessage.name) {
-      case "AccessFu:Ready":
-        let mm = Utils.getMessageManager(aMessage.target);
-        if (this._enabled) {
-          mm.sendAsyncMessage("AccessFu:Start",
-                              {method: "start", buildApp: Utils.MozBuildApp});
-        }
-        break;
-      case "AccessFu:Present":
+      case ACCESSFU_MESSAGE.PRESENT:
         this._output(aMessage.json, aMessage.target);
         break;
-      case "AccessFu:DoScroll":
+      case ACCESSFU_MESSAGE.DOSCROLL:
         this.Input.doScroll(aMessage.json, aMessage.target);
         break;
     }
   },
 
   _attachWindow: function _attachWindow(win) {
     let wtype = win.document.documentElement.getAttribute("windowtype");
     if (wtype != "navigator:browser" && wtype != "navigator:geckoview") {
       // Don't attach to non-browser or geckoview windows.
       return;
     }
 
-    for (let mm of Utils.getAllMessageManagers(win)) {
-      this._addMessageListeners(mm);
-      this._loadFrameScript(mm);
+    // Set up frame script
+    let mm = win.messageManager;
+    for (let messageName of Object.values(ACCESSFU_MESSAGE)) {
+      mm.addMessageListener(messageName, this);
     }
+    mm.loadFrameScript(FRAME_SCRIPT, true);
 
-    win.addEventListener("TabOpen", this);
-    win.addEventListener("TabClose", this);
     win.addEventListener("TabSelect", this);
     if (win.WindowEventDispatcher) {
       // desktop mochitests don't have this.
       win.WindowEventDispatcher.registerListener(this,
         Object.values(GECKOVIEW_MESSAGE));
     }
   },
 
   _detachWindow: function _detachWindow(win) {
-    for (let mm of Utils.getAllMessageManagers(win)) {
-      mm.sendAsyncMessage("AccessFu:Stop");
-      this._removeMessageListeners(mm);
+    let mm = win.messageManager;
+    mm.broadcastAsyncMessage("AccessFu:Stop");
+    mm.removeDelayedFrameScript(FRAME_SCRIPT);
+    for (let messageName of Object.values(ACCESSFU_MESSAGE)) {
+      mm.removeMessageListener(messageName, this);
     }
 
-    win.removeEventListener("TabOpen", this);
-    win.removeEventListener("TabClose", this);
     win.removeEventListener("TabSelect", this);
     if (win.WindowEventDispatcher) {
       // desktop mochitests don't have this.
       win.WindowEventDispatcher.unregisterListener(this,
         Object.values(GECKOVIEW_MESSAGE));
     }
   },
 
@@ -188,48 +183,16 @@ var AccessFu = {
     }
 
     if (this._notifyOutputPref.value) {
       Services.obs.notifyObservers(null, "accessibility-output",
                                    JSON.stringify(aPresentationData));
     }
   },
 
-  _loadFrameScript: function _loadFrameScript(aMessageManager) {
-    if (!this._processedMessageManagers.includes(aMessageManager)) {
-      aMessageManager.loadFrameScript(
-        "chrome://global/content/accessibility/content-script.js", true);
-      this._processedMessageManagers.push(aMessageManager);
-    } else if (this._enabled) {
-      // If the content-script is already loaded and AccessFu is enabled,
-      // send an AccessFu:Start message.
-      aMessageManager.sendAsyncMessage("AccessFu:Start",
-        {method: "start", buildApp: Utils.MozBuildApp});
-    }
-  },
-
-  _addMessageListeners: function _addMessageListeners(aMessageManager) {
-    aMessageManager.addMessageListener("AccessFu:Present", this);
-    aMessageManager.addMessageListener("AccessFu:Ready", this);
-    aMessageManager.addMessageListener("AccessFu:DoScroll", this);
-  },
-
-  _removeMessageListeners: function _removeMessageListeners(aMessageManager) {
-    aMessageManager.removeMessageListener("AccessFu:Present", this);
-    aMessageManager.removeMessageListener("AccessFu:Ready", this);
-    aMessageManager.removeMessageListener("AccessFu:DoScroll", this);
-  },
-
-  _handleMessageManager: function _handleMessageManager(aMessageManager) {
-    if (this._enabled) {
-      this._addMessageListeners(aMessageManager);
-    }
-    this._loadFrameScript(aMessageManager);
-  },
-
   onEvent(event, data, callback) {
     switch (event) {
       case GECKOVIEW_MESSAGE.SETTINGS:
         if (data.enabled) {
           this._enable();
         } else {
           this._disable();
         }
@@ -275,52 +238,28 @@ var AccessFu = {
       case GECKOVIEW_MESSAGE.CLIPBOARD:
         this.Input.clipboard(data);
         break;
     }
   },
 
   observe: function observe(aSubject, aTopic, aData) {
     switch (aTopic) {
-      case "remote-browser-shown":
-      case "inprocess-browser-shown":
-      {
-        // Ignore notifications that aren't from a Browser
-        let frameLoader = aSubject;
-        if (!frameLoader.ownerIsMozBrowserFrame) {
-          return;
-        }
-        this._handleMessageManager(frameLoader.messageManager);
-        break;
-      }
       case "domwindowopened": {
-        this._attachWindow(aSubject.QueryInterface(Ci.nsIDOMWindow));
+        let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
+        win.addEventListener("load", () => {
+          this._attachWindow(win);
+        }, { once: true });
         break;
       }
     }
   },
 
   _handleEvent: function _handleEvent(aEvent) {
     switch (aEvent.type) {
-      case "TabOpen":
-      {
-        let mm = Utils.getMessageManager(aEvent.target);
-        this._handleMessageManager(mm);
-        break;
-      }
-      case "TabClose":
-      {
-        let mm = Utils.getMessageManager(aEvent.target);
-        let mmIndex = this._processedMessageManagers.indexOf(mm);
-        if (mmIndex > -1) {
-          this._removeMessageListeners(mm);
-          this._processedMessageManagers.splice(mmIndex, 1);
-        }
-        break;
-      }
       case "TabSelect":
       {
         if (this._focused) {
           // We delay this for half a second so the awesomebar could close,
           // and we could use the current coordinates for the content item.
           // XXX TODO figure out how to avoid magic wait here.
           this.autoMove({
             delay: 500,
@@ -345,20 +284,16 @@ var AccessFu = {
   },
 
   // So we don't enable/disable twice
   _enabled: false,
 
   // Layerview is focused
   _focused: false,
 
-  // Keep track of message managers tha already have a 'content-script.js'
-  // injected.
-  _processedMessageManagers: [],
-
   /**
    * Adjusts the given bounds that are defined in device display pixels
    * to client-relative CSS pixels of the chrome window.
    * @param {Rect} aJsonBounds the bounds to adjust
    * @param {Window} aWindow the window containing the item
    */
   screenToClientBounds(aJsonBounds, aWindow) {
       let bounds = new Rect(aJsonBounds.left, aJsonBounds.top,
@@ -408,28 +343,16 @@ var Input = {
     mm.sendAsyncMessage("AccessFu:Clipboard", aDetails);
   },
 
   activateCurrent: function activateCurrent(aData) {
     let mm = Utils.getMessageManager();
     mm.sendAsyncMessage("AccessFu:Activate", { offset: 0 });
   },
 
-  // XXX: This is here for backwards compatability with screen reader simulator
-  // it should be removed when the extension is updated on amo.
-  scroll: function scroll(aPage, aHorizontal) {
-    this.sendScrollMessage(aPage, aHorizontal);
-  },
-
-  sendScrollMessage: function sendScrollMessage(aPage, aHorizontal) {
-    const mm = Utils.getMessageManager();
-    mm.sendAsyncMessage("AccessFu:Scroll",
-      {page: aPage, horizontal: aHorizontal, origin: "top"});
-  },
-
   doScroll: function doScroll(aDetails, aBrowser) {
     let horizontal = aDetails.horizontal;
     let page = aDetails.page;
     let win = aBrowser.ownerGlobal;
     let winUtils = win.windowUtils;
     let p = AccessFu.screenToClientBounds(aDetails.bounds, win).center();
     winUtils.sendWheelEvent(p.x, p.y,
       horizontal ? page : 0, horizontal ? 0 : page, 0,
--- a/accessible/jsat/EventManager.jsm
+++ b/accessible/jsat/EventManager.jsm
@@ -20,19 +20,18 @@ ChromeUtils.defineModuleGetter(this, "St
   "resource://gre/modules/accessibility/Constants.jsm");
 ChromeUtils.defineModuleGetter(this, "clearTimeout",
   "resource://gre/modules/Timer.jsm");
 ChromeUtils.defineModuleGetter(this, "setTimeout",
   "resource://gre/modules/Timer.jsm");
 
 var EXPORTED_SYMBOLS = ["EventManager"];
 
-function EventManager(aContentScope, aContentControl) {
+function EventManager(aContentScope) {
   this.contentScope = aContentScope;
-  this.contentControl = aContentControl;
   this.addEventListener = this.contentScope.addEventListener.bind(
     this.contentScope);
   this.removeEventListener = this.contentScope.removeEventListener.bind(
     this.contentScope);
   this.sendMsgFunc = this.contentScope.sendAsyncMessage.bind(
     this.contentScope);
   this.webProgress = this.contentScope.docShell.
     QueryInterface(Ci.nsIInterfaceRequestor).
@@ -80,16 +79,20 @@ this.EventManager.prototype = {
       this.removeEventListener("resize", this, true);
     } catch (x) {
       // contentScope is dead.
     } finally {
       this._started = false;
     }
   },
 
+  get contentControl() {
+    return this.contentScope._jsat_contentControl;
+  },
+
   handleEvent: function handleEvent(aEvent) {
     Logger.debug(() => {
       return ["DOMEvent", aEvent.type];
     });
 
     try {
       switch (aEvent.type) {
       case "wheel":
@@ -242,17 +245,17 @@ this.EventManager.prototype = {
         if (![Roles.CHROME_WINDOW,
              Roles.DOCUMENT,
              Roles.APPLICATION].includes(acc.role)) {
           this.contentControl.autoMove(acc);
         }
 
         this.present(Presentation.focused(acc));
 
-       if (this.inTest) {
+       if (Utils.inTest) {
         this.sendMsgFunc("AccessFu:Focused");
        }
        break;
       }
       case Events.DOCUMENT_LOAD_COMPLETE:
       {
         let position = this.contentControl.vc.position;
         // Check if position is in the subtree of the DOCUMENT_LOAD_COMPLETE
--- a/accessible/jsat/Utils.jsm
+++ b/accessible/jsat/Utils.jsm
@@ -77,51 +77,16 @@ var Utils = { // jshint ignore:line
 
   getCurrentBrowser: function getCurrentBrowser(aWindow) {
     let win = aWindow ||
       Services.wm.getMostRecentWindow("navigator:browser") ||
       Services.wm.getMostRecentWindow("navigator:geckoview");
     return win.document.querySelector("browser[type=content][primary=true]");
   },
 
-  getAllMessageManagers: function getAllMessageManagers(aWindow) {
-    let messageManagers = new Set();
-
-    function collectLeafMessageManagers(mm) {
-      for (let i = 0; i < mm.childCount; i++) {
-        let childMM = mm.getChildAt(i);
-
-        if ("sendAsyncMessage" in childMM) {
-          messageManagers.add(childMM);
-        } else {
-          collectLeafMessageManagers(childMM);
-        }
-      }
-    }
-
-    collectLeafMessageManagers(aWindow.messageManager);
-
-    let browser = this.getCurrentBrowser(aWindow);
-    let document = browser ? browser.contentDocument : null;
-
-    if (document) {
-      let remoteframes = document.querySelectorAll("iframe");
-
-      for (let i = 0; i < remoteframes.length; ++i) {
-        let mm = this.getMessageManager(remoteframes[i]);
-        if (mm) {
-          messageManagers.add(mm);
-        }
-      }
-
-    }
-
-    return messageManagers;
-  },
-
   get isContentProcess() {
     delete this.isContentProcess;
     this.isContentProcess =
       Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
     return this.isContentProcess;
   },
 
   localize: function localize(aOutput) {
@@ -413,31 +378,30 @@ var Logger = { // jshint ignore:line
   DEBUG: 0,
   INFO: 1,
   WARNING: 2,
   ERROR: 3,
   _LEVEL_NAMES: ["GESTURE", "DEBUG", "INFO", "WARNING", "ERROR"],
 
   logLevel: 1, // INFO;
 
-  test: false,
+  // Note: used for testing purposes. If true, also log to the console service.
+  useConsoleService: false,
 
   log: function log(aLogLevel) {
     if (aLogLevel < this.logLevel) {
       return;
     }
 
     let args = Array.prototype.slice.call(arguments, 1);
     let message = (typeof(args[0]) === "function" ? args[0]() : args).join(" ");
     message = "[" + Utils.ScriptName + "] " + this._LEVEL_NAMES[aLogLevel + 1] +
       " " + message + "\n";
     dump(message);
-    // Note: used for testing purposes. If |this.test| is true, also log to
-    // the console service.
-    if (this.test) {
+    if (this.useConsoleService) {
       try {
         Services.console.logStringMessage(message);
       } catch (ex) {
         // There was an exception logging to the console service.
       }
     }
   },
 
--- a/accessible/jsat/content-script.js
+++ b/accessible/jsat/content-script.js
@@ -1,134 +1,54 @@
 /* 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/. */
 
 /* eslint-env mozilla/frame-script */
 
 ChromeUtils.defineModuleGetter(this, "Logger",
   "resource://gre/modules/accessibility/Utils.jsm");
-ChromeUtils.defineModuleGetter(this, "Presentation",
-  "resource://gre/modules/accessibility/Presentation.jsm");
 ChromeUtils.defineModuleGetter(this, "Utils",
   "resource://gre/modules/accessibility/Utils.jsm");
 ChromeUtils.defineModuleGetter(this, "EventManager",
   "resource://gre/modules/accessibility/EventManager.jsm");
 ChromeUtils.defineModuleGetter(this, "ContentControl",
   "resource://gre/modules/accessibility/ContentControl.jsm");
-ChromeUtils.defineModuleGetter(this, "Roles",
-  "resource://gre/modules/accessibility/Constants.jsm");
 ChromeUtils.defineModuleGetter(this, "States",
   "resource://gre/modules/accessibility/Constants.jsm");
 
 Logger.info("content-script.js", content.document.location);
 
-var eventManager = null;
-var contentControl = null;
+function onStop(m) {
+  Logger.debug("AccessFu:Stop");
 
-function forwardToParent(aMessage) {
-  // XXX: This is a silly way to make a deep copy
-  let newJSON = JSON.parse(JSON.stringify(aMessage.json));
-  newJSON.origin = "child";
-  sendAsyncMessage(aMessage.name, newJSON);
+  removeMessageListener("AccessFu:Stop", onStop);
+
+  this._jsat_eventManager.stop();
+  this._jsat_contentControl.stop();
 }
 
-function forwardToChild(aMessage, aListener, aVCPosition) {
-  let acc = aVCPosition || Utils.getVirtualCursor(content.document).position;
-
-  if (!Utils.isAliveAndVisible(acc) || acc.role != Roles.INTERNAL_FRAME) {
-    return false;
-  }
+addMessageListener("AccessFu:Stop", onStop);
 
-  Logger.debug(() => {
-    return ["forwardToChild", Logger.accessibleToString(acc),
-            aMessage.name, JSON.stringify(aMessage.json, null, "  ")];
-  });
-
-  let mm = Utils.getMessageManager(acc.DOMNode);
-
-  if (aListener) {
-    mm.addMessageListener(aMessage.name, aListener);
-  }
+if (!this._jsat_contentControl) {
+  this._jsat_contentControl = new ContentControl(this);
+}
+this._jsat_contentControl.start();
 
-  // XXX: This is a silly way to make a deep copy
-  let newJSON = JSON.parse(JSON.stringify(aMessage.json));
-  newJSON.origin = "parent";
-  if (Utils.isContentProcess) {
-    // XXX: OOP content's screen offset is 0,
-    // so we remove the real screen offset here.
-    newJSON.x -= content.mozInnerScreenX;
-    newJSON.y -= content.mozInnerScreenY;
-  }
-  mm.sendAsyncMessage(aMessage.name, newJSON);
-  return true;
+if (!this._jsat_eventManager) {
+  this._jsat_eventManager = new EventManager(this);
 }
+this._jsat_eventManager.start();
 
-function presentCaretChange(aText, aOldOffset, aNewOffset) {
-  if (aOldOffset !== aNewOffset) {
-    let msg = Presentation.textSelectionChanged(aText, aNewOffset, aNewOffset,
-                                                aOldOffset, aOldOffset, true);
-    sendAsyncMessage("AccessFu:Present", msg);
+function contentStarted() {
+  let accDoc = Utils.AccService.getAccessibleFor(content.document);
+  if (accDoc && !Utils.getState(accDoc).contains(States.BUSY)) {
+    sendAsyncMessage("AccessFu:ContentStarted");
+  } else {
+    content.setTimeout(contentStarted, 0);
   }
 }
 
-function scroll(aMessage) {
-  let position = Utils.getVirtualCursor(content.document).position;
-  if (!forwardToChild(aMessage, scroll, position)) {
-    sendAsyncMessage("AccessFu:DoScroll",
-                     { bounds: Utils.getBounds(position),
-                       page: aMessage.json.page,
-                       horizontal: aMessage.json.horizontal });
-  }
+if (Utils.inTest) {
+  // During a test we want to wait for the document to finish loading for
+  // consistency.
+  contentStarted();
 }
-
-addMessageListener(
-  "AccessFu:Start",
-  function(m) {
-    if (m.json.logLevel) {
-      Logger.logLevel = Logger[m.json.logLevel];
-    }
-
-    Logger.debug("AccessFu:Start");
-    if (m.json.buildApp)
-      Utils.MozBuildApp = m.json.buildApp;
-
-    addMessageListener("AccessFu:Scroll", scroll);
-
-    if (!contentControl) {
-      contentControl = new ContentControl(this);
-    }
-    contentControl.start();
-
-    if (!eventManager) {
-      eventManager = new EventManager(this, contentControl);
-    }
-    eventManager.inTest = m.json.inTest;
-    eventManager.start();
-
-    function contentStarted() {
-      let accDoc = Utils.AccService.getAccessibleFor(content.document);
-      if (accDoc && !Utils.getState(accDoc).contains(States.BUSY)) {
-        sendAsyncMessage("AccessFu:ContentStarted");
-      } else {
-        content.setTimeout(contentStarted, 0);
-      }
-    }
-
-    if (m.json.inTest) {
-      // During a test we want to wait for the document to finish loading for
-      // consistency.
-      contentStarted();
-    }
-  });
-
-addMessageListener(
-  "AccessFu:Stop",
-  function(m) {
-    Logger.debug("AccessFu:Stop");
-
-    removeMessageListener("AccessFu:Scroll", scroll);
-
-    eventManager.stop();
-    contentControl.stop();
-  });
-
-sendAsyncMessage("AccessFu:Ready");
--- a/accessible/tests/mochitest/aom/test_general.html
+++ b/accessible/tests/mochitest/aom/test_general.html
@@ -79,16 +79,23 @@
   function testUIntProp(anode, prop) {
     is(anode[prop], null, `anode.${prop} should be null`);
     anode[prop] = 4294967295;
     is(anode[prop], 4294967295, `anode.${prop} was assigned 4294967295`);
     anode[prop] = null;
     is(anode[prop], null, `anode.${prop} was assigned null`);
   }
 
+  function testRelationProp(anode, node, prop) {
+    is(anode[prop], null, `anode.${prop} should be null`);
+    anode[prop] = node.accessibleNode;
+    is(anode[prop], node.accessibleNode, `anode.${prop} was assigned AccessibleNode`);
+    anode[prop] = null;
+    is(anode[prop], null, `anode.${prop} was assigned null`);
+  }
   // Check that the WebIDL is as expected.
   function checkImplementation(ifrDoc) {
     let anode = ifrDoc.accessibleNode;
     ok(anode, "DOM document has accessible node");
 
     is(anode.computedRole, "document", "correct role of a document accessible node");
     is(anode.DOMNode, ifrDoc, "correct DOM Node of a document accessible node");
 
@@ -183,12 +190,18 @@
     anode = node.accessibleNode;
     is(anode, node.accessibleNode, "an AccessibleNode is properly cached");
 
     // Adopting node to another document doesn't change .accessibleNode
     let anotherDoc = document.implementation.createDocument("", "", null);
     let adopted_node = anotherDoc.adoptNode(node);
     is(anode, adopted_node.accessibleNode, "adopting node to another document doesn't change node.accessibleNode");
 
+    const relationProps = ["activeDescendant", "details", "errorMessage"];
+
+    for (const relationProp of relationProps) {
+      testRelationProp(anode, node, relationProp);
+    }
+
     finish();
   }
   </script>
 </head>
--- a/accessible/tests/mochitest/jsat/jsatcommon.js
+++ b/accessible/tests/mochitest/jsat/jsatcommon.js
@@ -100,59 +100,58 @@ var AccessFuTest = {
   _waitForExplicitFinish: false,
 
   waitForExplicitFinish: function AccessFuTest_waitForExplicitFinish() {
     this._waitForExplicitFinish = true;
   },
 
   finish: function AccessFuTest_finish() {
     // Disable the console service logging.
-    Logger.test = false;
+    Logger.useConsoleService = false;
     Logger.logLevel = Logger.INFO;
     // Finish through idle callback to let AccessFu._disable complete.
     SimpleTest.executeSoon(function() {
       // May be redundant, but for cleanup's sake.
       AccessFu.disable();
       SimpleTest.finish();
     });
   },
 
   nextTest: function AccessFuTest_nextTest() {
-    var result = gIterator.next();
-    if (result.done) {
-      this.finish();
-      return;
-    }
-    var testFunc = result.value;
-    testFunc();
+    SimpleTest.executeSoon(() => {
+      var result = gIterator.next();
+      if (result.done) {
+        this.finish();
+        return;
+      }
+      var testFunc = result.value;
+      testFunc();
+    });
   },
 
   runTests: function AccessFuTest_runTests(aAdditionalPrefs) {
     if (gTestFuncs.length === 0) {
       ok(false, "No tests specified!");
       SimpleTest.finish();
       return;
     }
 
     // Create an Iterator for gTestFuncs array.
     gIterator = (function* () {
       for (var testFunc of gTestFuncs) {
         yield testFunc;
       }
     })();
 
+    Logger.useConsoleService = true;
+    Logger.logLevel = Logger.DEBUG;
+
     // Start AccessFu and put it in stand-by.
     ChromeUtils.import("resource://gre/modules/accessibility/AccessFu.jsm");
 
-    AccessFu.readyCallback = function readyCallback() {
-      // Enable logging to the console service.
-      Logger.test = true;
-      Logger.logLevel = Logger.DEBUG;
-    };
-
     var prefs = [["accessibility.accessfu.notify_output", 1]];
     prefs.push.apply(prefs, aAdditionalPrefs);
 
     SpecialPowers.pushPrefEnv({ "set": prefs }, function() {
       if (AccessFuTest._waitForExplicitFinish) {
         // Run all test functions asynchronously.
         AccessFuTest.nextTest();
       } else {
@@ -211,48 +210,41 @@ class AccessFuContentTestRunner {
   isFocused(aExpected) {
     var doc = currentTabDocument();
     SimpleTest.is(doc.activeElement, doc.querySelector(aExpected),
       "Correct element is focused: " + aExpected);
   }
 
   async setupMessageManager(aMessageManager) {
     function contentScript() {
+      ChromeUtils.import("resource://gre/modules/accessibility/Utils.jsm");
+      Logger.logLevel = "DEBUG";
+      Utils.inTest = true;
+
       addMessageListener("AccessFuTest:Focus", aMessage => {
         var elem = content.document.querySelector(aMessage.data.selector);
         if (elem) {
           elem.focus();
         }
       });
 
       addMessageListener("AccessFuTest:Blur", () => {
         content.document.activeElement.blur();
       });
     }
 
     aMessageManager.loadFrameScript(
       "data:,(" + contentScript.toString() + ")();", false);
 
-    let readyPromise = new Promise(resolve =>
-      aMessageManager.addMessageListener("AccessFu:Ready", resolve));
-
     aMessageManager.loadFrameScript(
       "chrome://global/content/accessibility/content-script.js", false);
 
-    await readyPromise;
-
     let startedPromise = new Promise(resolve =>
       aMessageManager.addMessageListener("AccessFu:ContentStarted", resolve));
 
-    aMessageManager.sendAsyncMessage("AccessFu:Start",
-      { buildApp: "browser",
-        androidSdkVersion: Utils.AndroidSdkVersion,
-        logLevel: "DEBUG",
-        inTest: true });
-
     await startedPromise;
 
     aMessageManager.addMessageListener("AccessFu:Present", this);
   }
 
   receiveMessage(aMessage) {
     if (aMessage.name != "AccessFu:Present" || !aMessage.data) {
       return;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1371,16 +1371,19 @@ var gBrowserInit = {
     // avoid an about:blank flash.
     let tabToAdopt = this.getTabToAdopt();
     if (tabToAdopt) {
       // Stop the about:blank load
       gBrowser.stop();
       // make sure it has a docshell
       gBrowser.docShell;
 
+      // Remove the speculative focus from the urlbar to let the url be formatted.
+      gURLBar.removeAttribute("focused");
+
       try {
         gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, tabToAdopt);
       } catch (e) {
         Cu.reportError(e);
       }
 
       // Clear the reference to the tab once its adoption has been completed.
       this._clearTabToAdopt();
--- a/devtools/client/memory/components/tree-map/color-coarse-type.js
+++ b/devtools/client/memory/components/tree-map/color-coarse-type.js
@@ -3,17 +3,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 /**
  * Color the boxes in the treemap
  */
 
-const TYPES = [ "objects", "other", "strings", "scripts" ];
+const TYPES = [ "objects", "other", "strings", "scripts", "domNode" ];
 
 // The factors determine how much the hue shifts
 const TYPE_FACTOR = TYPES.length * 3;
 const DEPTH_FACTOR = -10;
 const H = 0.5;
 const S = 0.6;
 const L = 0.9;
 
--- a/devtools/client/memory/constants.js
+++ b/devtools/client/memory/constants.js
@@ -117,31 +117,33 @@ actions.EXPAND_DOMINATOR_TREE_NODE = "ex
 actions.COLLAPSE_DOMINATOR_TREE_NODE = "collapse-dominator-tree-node";
 
 actions.RESIZE_SHORTEST_PATHS = "resize-shortest-paths";
 
 /** * Census Displays ***************************************************************/
 
 const COUNT = Object.freeze({ by: "count", count: true, bytes: true });
 const INTERNAL_TYPE = Object.freeze({ by: "internalType", then: COUNT });
+const DESCRIPTIVE_TYPE = Object.freeze({ by: "descriptiveType", then: COUNT });
 const ALLOCATION_STACK = Object.freeze({
   by: "allocationStack", then: COUNT,
   noStack: COUNT
 });
 const OBJECT_CLASS = Object.freeze({ by: "objectClass", then: COUNT, other: COUNT });
 const COARSE_TYPE = Object.freeze({
   by: "coarseType",
   objects: OBJECT_CLASS,
   strings: COUNT,
   scripts: {
     by: "filename",
     then: INTERNAL_TYPE,
     noFilename: INTERNAL_TYPE
   },
   other: INTERNAL_TYPE,
+  domNode: DESCRIPTIVE_TYPE,
 });
 
 exports.censusDisplays = Object.freeze({
   coarseType: Object.freeze({
     displayName: "Type",
     get tooltip() {
       // Importing down here is necessary because of the circular dependency
       // this introduces with `./utils.js`.
@@ -181,16 +183,17 @@ const DOMINATOR_TREE_LABEL_COARSE_TYPE =
     then: Object.freeze({
       by: "filename",
       then: COUNT,
       noFilename: COUNT,
     }),
   }),
   strings: INTERNAL_TYPE,
   other: INTERNAL_TYPE,
+  domNode: DESCRIPTIVE_TYPE,
 });
 
 exports.labelDisplays = Object.freeze({
   coarseType: Object.freeze({
     displayName: "Type",
     get tooltip() {
       const { L10N } = require("./utils");
       return L10N.getStr("dominatorTreeDisplays.coarseType.tooltip");
--- a/devtools/client/memory/test/unit/test_tree-map-01.js
+++ b/devtools/client/memory/test/unit/test_tree-map-01.js
@@ -27,18 +27,18 @@ add_task(async function() {
   const borderWidth = () => 1;
   const dragZoom = {
     offsetX: 0,
     offsetY: 0,
     zoom: 0
   };
   drawBox(ctx, node, borderWidth, dragZoom, padding);
   ok(true, JSON.stringify([ctx, fillRectValues, strokeRectValues]));
-  equal(ctx.fillStyle, "hsl(210,60%,70%)", "The fillStyle is set");
-  equal(ctx.strokeStyle, "hsl(210,60%,35%)", "The strokeStyle is set");
+  equal(ctx.fillStyle, "hsl(204,60%,70%)", "The fillStyle is set");
+  equal(ctx.strokeStyle, "hsl(204,60%,35%)", "The strokeStyle is set");
   equal(ctx.lineWidth, 1, "The lineWidth is set");
   deepEqual(fillRectValues, [10.5, 20.5, 49, 69], "Draws a filled rectangle");
   deepEqual(strokeRectValues, [10.5, 20.5, 49, 69], "Draws a stroked rectangle");
 
   dragZoom.zoom = 0.5;
 
   drawBox(ctx, node, borderWidth, dragZoom, padding);
   ok(true, JSON.stringify([ctx, fillRectValues, strokeRectValues]));
--- a/devtools/shared/heapsnapshot/CensusUtils.js
+++ b/devtools/shared/heapsnapshot/CensusUtils.js
@@ -75,30 +75,39 @@ EDGES.bucket = function(breakdown, repor
 EDGES.internalType = function(breakdown, report) {
   return Object.keys(report).map(key => ({
     edge: key,
     referent: report[key],
     breakdown: breakdown.then
   }));
 };
 
+EDGES.descriptiveType = function(breakdown, report) {
+  return Object.keys(report).map(key => ({
+    edge: key,
+    referent: report[key],
+    breakdown: breakdown.then
+  }));
+};
+
 EDGES.objectClass = function(breakdown, report) {
   return Object.keys(report).map(key => ({
     edge: key,
     referent: report[key],
     breakdown: key === "other" ? breakdown.other : breakdown.then
   }));
 };
 
 EDGES.coarseType = function(breakdown, report) {
   return [
     { edge: "objects", referent: report.objects, breakdown: breakdown.objects },
     { edge: "scripts", referent: report.scripts, breakdown: breakdown.scripts },
     { edge: "strings", referent: report.strings, breakdown: breakdown.strings },
     { edge: "other", referent: report.other, breakdown: breakdown.other },
+    { edge: "domNode", referent: report.domNode, breakdown: breakdown.domNode },
   ];
 };
 
 EDGES.allocationStack = function(breakdown, report) {
   const edges = [];
   report.forEach((value, key) => {
     edges.push({
       edge: key,
--- a/devtools/shared/heapsnapshot/CoreDump.pb.cc
+++ b/devtools/shared/heapsnapshot/CoreDump.pb.cc
@@ -43,16 +43,18 @@ public:
  ::google::protobuf::internal::ExplicitlyConstructed<Node>
      _instance;
   ::google::protobuf::internal::ArenaStringPtr typename__;
   ::google::protobuf::uint64 typenameref_;
   ::google::protobuf::internal::ArenaStringPtr jsobjectclassname_;
   ::google::protobuf::uint64 jsobjectclassnameref_;
   ::google::protobuf::internal::ArenaStringPtr scriptfilename_;
   ::google::protobuf::uint64 scriptfilenameref_;
+  ::google::protobuf::internal::ArenaStringPtr descriptivetypename_;
+  ::google::protobuf::uint64 descriptivetypenameref_;
 } _Node_default_instance_;
 class EdgeDefaultTypeInternal {
 public:
  ::google::protobuf::internal::ExplicitlyConstructed<Edge>
      _instance;
   ::google::protobuf::internal::ArenaStringPtr name_;
   ::google::protobuf::uint64 nameref_;
 } _Edge_default_instance_;
@@ -1744,16 +1746,18 @@ const int Node::kTypeNameRefFieldNumber;
 const int Node::kSizeFieldNumber;
 const int Node::kEdgesFieldNumber;
 const int Node::kAllocationStackFieldNumber;
 const int Node::kJsObjectClassNameFieldNumber;
 const int Node::kJsObjectClassNameRefFieldNumber;
 const int Node::kCoarseTypeFieldNumber;
 const int Node::kScriptFilenameFieldNumber;
 const int Node::kScriptFilenameRefFieldNumber;
+const int Node::kDescriptiveTypeNameFieldNumber;
+const int Node::kDescriptiveTypeNameRefFieldNumber;
 #endif  // !defined(_MSC_VER) || _MSC_VER >= 1900
 
 Node::Node()
   : ::google::protobuf::MessageLite(), _internal_metadata_(NULL) {
   if (GOOGLE_PREDICT_TRUE(this != internal_default_instance())) {
     protobuf_CoreDump_2eproto::InitDefaults();
   }
   SharedCtor();
@@ -1811,27 +1815,42 @@ Node::Node(const Node& from)
     case kScriptFilenameRef: {
       set_scriptfilenameref(from.scriptfilenameref());
       break;
     }
     case SCRIPTFILENAMEORREF_NOT_SET: {
       break;
     }
   }
+  clear_has_descriptiveTypeNameOrRef();
+  switch (from.descriptiveTypeNameOrRef_case()) {
+    case kDescriptiveTypeName: {
+      set_descriptivetypename(from.descriptivetypename());
+      break;
+    }
+    case kDescriptiveTypeNameRef: {
+      set_descriptivetypenameref(from.descriptivetypenameref());
+      break;
+    }
+    case DESCRIPTIVETYPENAMEORREF_NOT_SET: {
+      break;
+    }
+  }
   // @@protoc_insertion_point(copy_constructor:mozilla.devtools.protobuf.Node)
 }
 
 void Node::SharedCtor() {
   _cached_size_ = 0;
   ::memset(&allocationstack_, 0, static_cast<size_t>(
       reinterpret_cast<char*>(&coarsetype_) -
       reinterpret_cast<char*>(&allocationstack_)) + sizeof(coarsetype_));
   clear_has_TypeNameOrRef();
   clear_has_JSObjectClassNameOrRef();
   clear_has_ScriptFilenameOrRef();
+  clear_has_descriptiveTypeNameOrRef();
 }
 
 Node::~Node() {
   // @@protoc_insertion_point(destructor:mozilla.devtools.protobuf.Node)
   SharedDtor();
 }
 
 void Node::SharedDtor() {
@@ -1840,16 +1859,19 @@ void Node::SharedDtor() {
     clear_TypeNameOrRef();
   }
   if (has_JSObjectClassNameOrRef()) {
     clear_JSObjectClassNameOrRef();
   }
   if (has_ScriptFilenameOrRef()) {
     clear_ScriptFilenameOrRef();
   }
+  if (has_descriptiveTypeNameOrRef()) {
+    clear_descriptiveTypeNameOrRef();
+  }
 }
 
 void Node::SetCachedSize(int size) const {
   GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
   _cached_size_ = size;
   GOOGLE_SAFE_CONCURRENT_WRITES_END();
 }
 const Node& Node::default_instance() {
@@ -1914,16 +1936,34 @@ void Node::clear_ScriptFilenameOrRef() {
     }
     case SCRIPTFILENAMEORREF_NOT_SET: {
       break;
     }
   }
   _oneof_case_[2] = SCRIPTFILENAMEORREF_NOT_SET;
 }
 
+void Node::clear_descriptiveTypeNameOrRef() {
+// @@protoc_insertion_point(one_of_clear_start:mozilla.devtools.protobuf.Node)
+  switch (descriptiveTypeNameOrRef_case()) {
+    case kDescriptiveTypeName: {
+      descriptiveTypeNameOrRef_.descriptivetypename_.DestroyNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+      break;
+    }
+    case kDescriptiveTypeNameRef: {
+      // No need to clear
+      break;
+    }
+    case DESCRIPTIVETYPENAMEORREF_NOT_SET: {
+      break;
+    }
+  }
+  _oneof_case_[3] = DESCRIPTIVETYPENAMEORREF_NOT_SET;
+}
+
 
 void Node::Clear() {
 // @@protoc_insertion_point(message_clear_start:mozilla.devtools.protobuf.Node)
   ::google::protobuf::uint32 cached_has_bits = 0;
   // Prevent compiler warnings about cached_has_bits being unused
   (void) cached_has_bits;
 
   edges_.Clear();
@@ -1935,16 +1975,17 @@ void Node::Clear() {
   if (cached_has_bits & 14u) {
     ::memset(&id_, 0, static_cast<size_t>(
         reinterpret_cast<char*>(&coarsetype_) -
         reinterpret_cast<char*>(&id_)) + sizeof(coarsetype_));
   }
   clear_TypeNameOrRef();
   clear_JSObjectClassNameOrRef();
   clear_ScriptFilenameOrRef();
+  clear_descriptiveTypeNameOrRef();
   _has_bits_.Clear();
   _internal_metadata_.Clear();
 }
 
 bool Node::MergePartialFromCodedStream(
     ::google::protobuf::io::CodedInputStream* input) {
 #define DO_(EXPRESSION) if (!GOOGLE_PREDICT_TRUE(EXPRESSION)) goto failure
   ::google::protobuf::uint32 tag;
@@ -2102,16 +2143,43 @@ bool Node::MergePartialFromCodedStream(
                  input, &ScriptFilenameOrRef_.scriptfilenameref_)));
           set_has_scriptfilenameref();
         } else {
           goto handle_unusual;
         }
         break;
       }
 
+      // optional bytes descriptiveTypeName = 12;
+      case 12: {
+        if (static_cast< ::google::protobuf::uint8>(tag) ==
+            static_cast< ::google::protobuf::uint8>(98u /* 98 & 0xFF */)) {
+          DO_(::google::protobuf::internal::WireFormatLite::ReadBytes(
+                input, this->mutable_descriptivetypename()));
+        } else {
+          goto handle_unusual;
+        }
+        break;
+      }
+
+      // optional uint64 descriptiveTypeNameRef = 13;
+      case 13: {
+        if (static_cast< ::google::protobuf::uint8>(tag) ==
+            static_cast< ::google::protobuf::uint8>(104u /* 104 & 0xFF */)) {
+          clear_descriptiveTypeNameOrRef();
+          DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+                   ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>(
+                 input, &descriptiveTypeNameOrRef_.descriptivetypenameref_)));
+          set_has_descriptivetypenameref();
+        } else {
+          goto handle_unusual;
+        }
+        break;
+      }
+
       default: {
       handle_unusual:
         if (tag == 0) {
           goto success;
         }
         DO_(::google::protobuf::internal::WireFormatLite::SkipField(
             input, tag, &unknown_fields_stream));
         break;
@@ -2187,16 +2255,26 @@ void Node::SerializeWithCachedSizes(
       ::google::protobuf::internal::WireFormatLite::WriteBytesMaybeAliased(
         10, this->scriptfilename(), output);
       break;
     case kScriptFilenameRef:
       ::google::protobuf::internal::WireFormatLite::WriteUInt64(11, this->scriptfilenameref(), output);
       break;
     default: ;
   }
+  switch (descriptiveTypeNameOrRef_case()) {
+    case kDescriptiveTypeName:
+      ::google::protobuf::internal::WireFormatLite::WriteBytesMaybeAliased(
+        12, this->descriptivetypename(), output);
+      break;
+    case kDescriptiveTypeNameRef:
+      ::google::protobuf::internal::WireFormatLite::WriteUInt64(13, this->descriptivetypenameref(), output);
+      break;
+    default: ;
+  }
   output->WriteRaw(_internal_metadata_.unknown_fields().data(),
                    static_cast<int>(_internal_metadata_.unknown_fields().size()));
   // @@protoc_insertion_point(serialize_end:mozilla.devtools.protobuf.Node)
 }
 
 size_t Node::ByteSizeLong() const {
 // @@protoc_insertion_point(message_byte_size_start:mozilla.devtools.protobuf.Node)
   size_t total_size = 0;
@@ -2296,16 +2374,35 @@ size_t Node::ByteSizeLong() const {
         ::google::protobuf::internal::WireFormatLite::UInt64Size(
           this->scriptfilenameref());
       break;
     }
     case SCRIPTFILENAMEORREF_NOT_SET: {
       break;
     }
   }
+  switch (descriptiveTypeNameOrRef_case()) {
+    // optional bytes descriptiveTypeName = 12;
+    case kDescriptiveTypeName: {
+      total_size += 1 +
+        ::google::protobuf::internal::WireFormatLite::BytesSize(
+          this->descriptivetypename());
+      break;
+    }
+    // optional uint64 descriptiveTypeNameRef = 13;
+    case kDescriptiveTypeNameRef: {
+      total_size += 1 +
+        ::google::protobuf::internal::WireFormatLite::UInt64Size(
+          this->descriptivetypenameref());
+      break;
+    }
+    case DESCRIPTIVETYPENAMEORREF_NOT_SET: {
+      break;
+    }
+  }
   int cached_size = ::google::protobuf::internal::ToCachedSize(total_size);
   GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
   _cached_size_ = cached_size;
   GOOGLE_SAFE_CONCURRENT_WRITES_END();
   return total_size;
 }
 
 void Node::CheckTypeAndMergeFrom(
@@ -2371,16 +2468,29 @@ void Node::MergeFrom(const Node& from) {
     case kScriptFilenameRef: {
       set_scriptfilenameref(from.scriptfilenameref());
       break;
     }
     case SCRIPTFILENAMEORREF_NOT_SET: {
       break;
     }
   }
+  switch (from.descriptiveTypeNameOrRef_case()) {
+    case kDescriptiveTypeName: {
+      set_descriptivetypename(from.descriptivetypename());
+      break;
+    }
+    case kDescriptiveTypeNameRef: {
+      set_descriptivetypenameref(from.descriptivetypenameref());
+      break;
+    }
+    case DESCRIPTIVETYPENAMEORREF_NOT_SET: {
+      break;
+    }
+  }
 }
 
 void Node::CopyFrom(const Node& from) {
 // @@protoc_insertion_point(class_specific_copy_from_start:mozilla.devtools.protobuf.Node)
   if (&from == this) return;
   Clear();
   MergeFrom(from);
 }
@@ -2401,16 +2511,18 @@ void Node::InternalSwap(Node* other) {
   swap(size_, other->size_);
   swap(coarsetype_, other->coarsetype_);
   swap(TypeNameOrRef_, other->TypeNameOrRef_);
   swap(_oneof_case_[0], other->_oneof_case_[0]);
   swap(JSObjectClassNameOrRef_, other->JSObjectClassNameOrRef_);
   swap(_oneof_case_[1], other->_oneof_case_[1]);
   swap(ScriptFilenameOrRef_, other->ScriptFilenameOrRef_);
   swap(_oneof_case_[2], other->_oneof_case_[2]);
+  swap(descriptiveTypeNameOrRef_, other->descriptiveTypeNameOrRef_);
+  swap(_oneof_case_[3], other->_oneof_case_[3]);
   swap(_has_bits_[0], other->_has_bits_[0]);
   _internal_metadata_.Swap(&other->_internal_metadata_);
   swap(_cached_size_, other->_cached_size_);
 }
 
 ::std::string Node::GetTypeName() const {
   return "mozilla.devtools.protobuf.Node";
 }
@@ -2933,16 +3045,140 @@ void Node::set_scriptfilenameref(::googl
   if (!has_scriptfilenameref()) {
     clear_ScriptFilenameOrRef();
     set_has_scriptfilenameref();
   }
   ScriptFilenameOrRef_.scriptfilenameref_ = value;
   // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.scriptFilenameRef)
 }
 
+// optional bytes descriptiveTypeName = 12;
+bool Node::has_descriptivetypename() const {
+  return descriptiveTypeNameOrRef_case() == kDescriptiveTypeName;
+}
+void Node::set_has_descriptivetypename() {
+  _oneof_case_[3] = kDescriptiveTypeName;
+}
+void Node::clear_descriptivetypename() {
+  if (has_descriptivetypename()) {
+    descriptiveTypeNameOrRef_.descriptivetypename_.DestroyNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+    clear_has_descriptiveTypeNameOrRef();
+  }
+}
+const ::std::string& Node::descriptivetypename() const {
+  // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+  if (has_descriptivetypename()) {
+    return descriptiveTypeNameOrRef_.descriptivetypename_.GetNoArena();
+  }
+  return *&::google::protobuf::internal::GetEmptyStringAlreadyInited();
+}
+void Node::set_descriptivetypename(const ::std::string& value) {
+  // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+  if (!has_descriptivetypename()) {
+    clear_descriptiveTypeNameOrRef();
+    set_has_descriptivetypename();
+    descriptiveTypeNameOrRef_.descriptivetypename_.UnsafeSetDefault(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+  descriptiveTypeNameOrRef_.descriptivetypename_.SetNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(), value);
+  // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+}
+#if LANG_CXX11
+void Node::set_descriptivetypename(::std::string&& value) {
+  // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+  if (!has_descriptivetypename()) {
+    clear_descriptiveTypeNameOrRef();
+    set_has_descriptivetypename();
+    descriptiveTypeNameOrRef_.descriptivetypename_.UnsafeSetDefault(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+  descriptiveTypeNameOrRef_.descriptivetypename_.SetNoArena(
+    &::google::protobuf::internal::GetEmptyStringAlreadyInited(), ::std::move(value));
+  // @@protoc_insertion_point(field_set_rvalue:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+}
+#endif
+void Node::set_descriptivetypename(const char* value) {
+  GOOGLE_DCHECK(value != NULL);
+  if (!has_descriptivetypename()) {
+    clear_descriptiveTypeNameOrRef();
+    set_has_descriptivetypename();
+    descriptiveTypeNameOrRef_.descriptivetypename_.UnsafeSetDefault(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+  descriptiveTypeNameOrRef_.descriptivetypename_.SetNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      ::std::string(value));
+  // @@protoc_insertion_point(field_set_char:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+}
+void Node::set_descriptivetypename(const void* value, size_t size) {
+  if (!has_descriptivetypename()) {
+    clear_descriptiveTypeNameOrRef();
+    set_has_descriptivetypename();
+    descriptiveTypeNameOrRef_.descriptivetypename_.UnsafeSetDefault(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+  descriptiveTypeNameOrRef_.descriptivetypename_.SetNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(), ::std::string(
+      reinterpret_cast<const char*>(value), size));
+  // @@protoc_insertion_point(field_set_pointer:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+}
+::std::string* Node::mutable_descriptivetypename() {
+  if (!has_descriptivetypename()) {
+    clear_descriptiveTypeNameOrRef();
+    set_has_descriptivetypename();
+    descriptiveTypeNameOrRef_.descriptivetypename_.UnsafeSetDefault(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+  // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+  return descriptiveTypeNameOrRef_.descriptivetypename_.MutableNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+::std::string* Node::release_descriptivetypename() {
+  // @@protoc_insertion_point(field_release:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+  if (has_descriptivetypename()) {
+    clear_has_descriptiveTypeNameOrRef();
+    return descriptiveTypeNameOrRef_.descriptivetypename_.ReleaseNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  } else {
+    return NULL;
+  }
+}
+void Node::set_allocated_descriptivetypename(::std::string* descriptivetypename) {
+  if (!has_descriptivetypename()) {
+    descriptiveTypeNameOrRef_.descriptivetypename_.UnsafeSetDefault(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+  clear_descriptiveTypeNameOrRef();
+  if (descriptivetypename != NULL) {
+    set_has_descriptivetypename();
+    descriptiveTypeNameOrRef_.descriptivetypename_.SetAllocatedNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+        descriptivetypename);
+  }
+  // @@protoc_insertion_point(field_set_allocated:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+}
+
+// optional uint64 descriptiveTypeNameRef = 13;
+bool Node::has_descriptivetypenameref() const {
+  return descriptiveTypeNameOrRef_case() == kDescriptiveTypeNameRef;
+}
+void Node::set_has_descriptivetypenameref() {
+  _oneof_case_[3] = kDescriptiveTypeNameRef;
+}
+void Node::clear_descriptivetypenameref() {
+  if (has_descriptivetypenameref()) {
+    descriptiveTypeNameOrRef_.descriptivetypenameref_ = GOOGLE_ULONGLONG(0);
+    clear_has_descriptiveTypeNameOrRef();
+  }
+}
+::google::protobuf::uint64 Node::descriptivetypenameref() const {
+  // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.descriptiveTypeNameRef)
+  if (has_descriptivetypenameref()) {
+    return descriptiveTypeNameOrRef_.descriptivetypenameref_;
+  }
+  return GOOGLE_ULONGLONG(0);
+}
+void Node::set_descriptivetypenameref(::google::protobuf::uint64 value) {
+  if (!has_descriptivetypenameref()) {
+    clear_descriptiveTypeNameOrRef();
+    set_has_descriptivetypenameref();
+  }
+  descriptiveTypeNameOrRef_.descriptivetypenameref_ = value;
+  // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.descriptiveTypeNameRef)
+}
+
 bool Node::has_TypeNameOrRef() const {
   return TypeNameOrRef_case() != TYPENAMEORREF_NOT_SET;
 }
 void Node::clear_has_TypeNameOrRef() {
   _oneof_case_[0] = TYPENAMEORREF_NOT_SET;
 }
 bool Node::has_JSObjectClassNameOrRef() const {
   return JSObjectClassNameOrRef_case() != JSOBJECTCLASSNAMEORREF_NOT_SET;
@@ -2951,25 +3187,34 @@ void Node::clear_has_JSObjectClassNameOr
   _oneof_case_[1] = JSOBJECTCLASSNAMEORREF_NOT_SET;
 }
 bool Node::has_ScriptFilenameOrRef() const {
   return ScriptFilenameOrRef_case() != SCRIPTFILENAMEORREF_NOT_SET;
 }
 void Node::clear_has_ScriptFilenameOrRef() {
   _oneof_case_[2] = SCRIPTFILENAMEORREF_NOT_SET;
 }
+bool Node::has_descriptiveTypeNameOrRef() const {
+  return descriptiveTypeNameOrRef_case() != DESCRIPTIVETYPENAMEORREF_NOT_SET;
+}
+void Node::clear_has_descriptiveTypeNameOrRef() {
+  _oneof_case_[3] = DESCRIPTIVETYPENAMEORREF_NOT_SET;
+}
 Node::TypeNameOrRefCase Node::TypeNameOrRef_case() const {
   return Node::TypeNameOrRefCase(_oneof_case_[0]);
 }
 Node::JSObjectClassNameOrRefCase Node::JSObjectClassNameOrRef_case() const {
   return Node::JSObjectClassNameOrRefCase(_oneof_case_[1]);
 }
 Node::ScriptFilenameOrRefCase Node::ScriptFilenameOrRef_case() const {
   return Node::ScriptFilenameOrRefCase(_oneof_case_[2]);
 }
+Node::DescriptiveTypeNameOrRefCase Node::descriptiveTypeNameOrRef_case() const {
+  return Node::DescriptiveTypeNameOrRefCase(_oneof_case_[3]);
+}
 #endif  // PROTOBUF_INLINE_NOT_IN_HEADERS
 
 // ===================================================================
 
 #if !defined(_MSC_VER) || _MSC_VER >= 1900
 const int Edge::kReferentFieldNumber;
 const int Edge::kNameFieldNumber;
 const int Edge::kNameRefFieldNumber;
--- a/devtools/shared/heapsnapshot/CoreDump.pb.h
+++ b/devtools/shared/heapsnapshot/CoreDump.pb.h
@@ -600,16 +600,22 @@ class Node : public ::google::protobuf::
   };
 
   enum ScriptFilenameOrRefCase {
     kScriptFilename = 10,
     kScriptFilenameRef = 11,
     SCRIPTFILENAMEORREF_NOT_SET = 0,
   };
 
+  enum DescriptiveTypeNameOrRefCase {
+    kDescriptiveTypeName = 12,
+    kDescriptiveTypeNameRef = 13,
+    DESCRIPTIVETYPENAMEORREF_NOT_SET = 0,
+  };
+
   static inline const Node* internal_default_instance() {
     return reinterpret_cast<const Node*>(
                &_Node_default_instance_);
   }
   static PROTOBUF_CONSTEXPR int const kIndexInFileMessages =
     3;
 
   void Swap(Node* other);
@@ -759,48 +765,77 @@ class Node : public ::google::protobuf::
 
   // optional uint64 scriptFilenameRef = 11;
   bool has_scriptfilenameref() const;
   void clear_scriptfilenameref();
   static const int kScriptFilenameRefFieldNumber = 11;
   ::google::protobuf::uint64 scriptfilenameref() const;
   void set_scriptfilenameref(::google::protobuf::uint64 value);
 
+  // optional bytes descriptiveTypeName = 12;
+  bool has_descriptivetypename() const;
+  void clear_descriptivetypename();
+  static const int kDescriptiveTypeNameFieldNumber = 12;
+  const ::std::string& descriptivetypename() const;
+  void set_descriptivetypename(const ::std::string& value);
+  #if LANG_CXX11
+  void set_descriptivetypename(::std::string&& value);
+  #endif
+  void set_descriptivetypename(const char* value);
+  void set_descriptivetypename(const void* value, size_t size);
+  ::std::string* mutable_descriptivetypename();
+  ::std::string* release_descriptivetypename();
+  void set_allocated_descriptivetypename(::std::string* descriptivetypename);
+
+  // optional uint64 descriptiveTypeNameRef = 13;
+  bool has_descriptivetypenameref() const;
+  void clear_descriptivetypenameref();
+  static const int kDescriptiveTypeNameRefFieldNumber = 13;
+  ::google::protobuf::uint64 descriptivetypenameref() const;
+  void set_descriptivetypenameref(::google::protobuf::uint64 value);
+
   TypeNameOrRefCase TypeNameOrRef_case() const;
   JSObjectClassNameOrRefCase JSObjectClassNameOrRef_case() const;
   ScriptFilenameOrRefCase ScriptFilenameOrRef_case() const;
+  DescriptiveTypeNameOrRefCase descriptiveTypeNameOrRef_case() const;
   // @@protoc_insertion_point(class_scope:mozilla.devtools.protobuf.Node)
  private:
   void set_has_id();
   void clear_has_id();
   void set_has_typename_();
   void set_has_typenameref();
   void set_has_size();
   void clear_has_size();
   void set_has_allocationstack();
   void clear_has_allocationstack();
   void set_has_jsobjectclassname();
   void set_has_jsobjectclassnameref();
   void set_has_coarsetype();
   void clear_has_coarsetype();
   void set_has_scriptfilename();
   void set_has_scriptfilenameref();
+  void set_has_descriptivetypename();
+  void set_has_descriptivetypenameref();
 
   inline bool has_TypeNameOrRef() const;
   void clear_TypeNameOrRef();
   inline void clear_has_TypeNameOrRef();
 
   inline bool has_JSObjectClassNameOrRef() const;
   void clear_JSObjectClassNameOrRef();
   inline void clear_has_JSObjectClassNameOrRef();
 
   inline bool has_ScriptFilenameOrRef() const;
   void clear_ScriptFilenameOrRef();
   inline void clear_has_ScriptFilenameOrRef();
 
+  inline bool has_descriptiveTypeNameOrRef() const;
+  void clear_descriptiveTypeNameOrRef();
+  inline void clear_has_descriptiveTypeNameOrRef();
+
   ::google::protobuf::internal::InternalMetadataWithArenaLite _internal_metadata_;
   ::google::protobuf::internal::HasBits<1> _has_bits_;
   mutable int _cached_size_;
   ::google::protobuf::RepeatedPtrField< ::mozilla::devtools::protobuf::Edge > edges_;
   ::mozilla::devtools::protobuf::StackFrame* allocationstack_;
   ::google::protobuf::uint64 id_;
   ::google::protobuf::uint64 size_;
   ::google::protobuf::uint32 coarsetype_;
@@ -814,17 +849,22 @@ class Node : public ::google::protobuf::
     ::google::protobuf::internal::ArenaStringPtr jsobjectclassname_;
     ::google::protobuf::uint64 jsobjectclassnameref_;
   } JSObjectClassNameOrRef_;
   union ScriptFilenameOrRefUnion {
     ScriptFilenameOrRefUnion() {}
     ::google::protobuf::internal::ArenaStringPtr scriptfilename_;
     ::google::protobuf::uint64 scriptfilenameref_;
   } ScriptFilenameOrRef_;
-  ::google::protobuf::uint32 _oneof_case_[3];
+  union DescriptiveTypeNameOrRefUnion {
+    DescriptiveTypeNameOrRefUnion() {}
+    ::google::protobuf::internal::ArenaStringPtr descriptivetypename_;
+    ::google::protobuf::uint64 descriptivetypenameref_;
+  } descriptiveTypeNameOrRef_;
+  ::google::protobuf::uint32 _oneof_case_[4];
 
   friend struct protobuf_CoreDump_2eproto::TableStruct;
 };
 // -------------------------------------------------------------------
 
 class Edge : public ::google::protobuf::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.devtools.protobuf.Edge) */ {
  public:
   Edge();
@@ -2052,16 +2092,140 @@ inline void Node::set_scriptfilenameref(
   if (!has_scriptfilenameref()) {
     clear_ScriptFilenameOrRef();
     set_has_scriptfilenameref();
   }
   ScriptFilenameOrRef_.scriptfilenameref_ = value;
   // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.scriptFilenameRef)
 }
 
+// optional bytes descriptiveTypeName = 12;
+inline bool Node::has_descriptivetypename() const {
+  return descriptiveTypeNameOrRef_case() == kDescriptiveTypeName;
+}
+inline void Node::set_has_descriptivetypename() {
+  _oneof_case_[3] = kDescriptiveTypeName;
+}
+inline void Node::clear_descriptivetypename() {
+  if (has_descriptivetypename()) {
+    descriptiveTypeNameOrRef_.descriptivetypename_.DestroyNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+    clear_has_descriptiveTypeNameOrRef();
+  }
+}
+inline const ::std::string& Node::descriptivetypename() const {
+  // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+  if (has_descriptivetypename()) {
+    return descriptiveTypeNameOrRef_.descriptivetypename_.GetNoArena();
+  }
+  return *&::google::protobuf::internal::GetEmptyStringAlreadyInited();
+}
+inline void Node::set_descriptivetypename(const ::std::string& value) {
+  // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+  if (!has_descriptivetypename()) {
+    clear_descriptiveTypeNameOrRef();
+    set_has_descriptivetypename();
+    descriptiveTypeNameOrRef_.descriptivetypename_.UnsafeSetDefault(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+  descriptiveTypeNameOrRef_.descriptivetypename_.SetNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(), value);
+  // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+}
+#if LANG_CXX11
+inline void Node::set_descriptivetypename(::std::string&& value) {
+  // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+  if (!has_descriptivetypename()) {
+    clear_descriptiveTypeNameOrRef();
+    set_has_descriptivetypename();
+    descriptiveTypeNameOrRef_.descriptivetypename_.UnsafeSetDefault(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+  descriptiveTypeNameOrRef_.descriptivetypename_.SetNoArena(
+    &::google::protobuf::internal::GetEmptyStringAlreadyInited(), ::std::move(value));
+  // @@protoc_insertion_point(field_set_rvalue:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+}
+#endif
+inline void Node::set_descriptivetypename(const char* value) {
+  GOOGLE_DCHECK(value != NULL);
+  if (!has_descriptivetypename()) {
+    clear_descriptiveTypeNameOrRef();
+    set_has_descriptivetypename();
+    descriptiveTypeNameOrRef_.descriptivetypename_.UnsafeSetDefault(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+  descriptiveTypeNameOrRef_.descriptivetypename_.SetNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+      ::std::string(value));
+  // @@protoc_insertion_point(field_set_char:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+}
+inline void Node::set_descriptivetypename(const void* value, size_t size) {
+  if (!has_descriptivetypename()) {
+    clear_descriptiveTypeNameOrRef();
+    set_has_descriptivetypename();
+    descriptiveTypeNameOrRef_.descriptivetypename_.UnsafeSetDefault(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+  descriptiveTypeNameOrRef_.descriptivetypename_.SetNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(), ::std::string(
+      reinterpret_cast<const char*>(value), size));
+  // @@protoc_insertion_point(field_set_pointer:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+}
+inline ::std::string* Node::mutable_descriptivetypename() {
+  if (!has_descriptivetypename()) {
+    clear_descriptiveTypeNameOrRef();
+    set_has_descriptivetypename();
+    descriptiveTypeNameOrRef_.descriptivetypename_.UnsafeSetDefault(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+  // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+  return descriptiveTypeNameOrRef_.descriptivetypename_.MutableNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+}
+inline ::std::string* Node::release_descriptivetypename() {
+  // @@protoc_insertion_point(field_release:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+  if (has_descriptivetypename()) {
+    clear_has_descriptiveTypeNameOrRef();
+    return descriptiveTypeNameOrRef_.descriptivetypename_.ReleaseNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  } else {
+    return NULL;
+  }
+}
+inline void Node::set_allocated_descriptivetypename(::std::string* descriptivetypename) {
+  if (!has_descriptivetypename()) {
+    descriptiveTypeNameOrRef_.descriptivetypename_.UnsafeSetDefault(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  }
+  clear_descriptiveTypeNameOrRef();
+  if (descriptivetypename != NULL) {
+    set_has_descriptivetypename();
+    descriptiveTypeNameOrRef_.descriptivetypename_.SetAllocatedNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(),
+        descriptivetypename);
+  }
+  // @@protoc_insertion_point(field_set_allocated:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+}
+
+// optional uint64 descriptiveTypeNameRef = 13;
+inline bool Node::has_descriptivetypenameref() const {
+  return descriptiveTypeNameOrRef_case() == kDescriptiveTypeNameRef;
+}
+inline void Node::set_has_descriptivetypenameref() {
+  _oneof_case_[3] = kDescriptiveTypeNameRef;
+}
+inline void Node::clear_descriptivetypenameref() {
+  if (has_descriptivetypenameref()) {
+    descriptiveTypeNameOrRef_.descriptivetypenameref_ = GOOGLE_ULONGLONG(0);
+    clear_has_descriptiveTypeNameOrRef();
+  }
+}
+inline ::google::protobuf::uint64 Node::descriptivetypenameref() const {
+  // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.descriptiveTypeNameRef)
+  if (has_descriptivetypenameref()) {
+    return descriptiveTypeNameOrRef_.descriptivetypenameref_;
+  }
+  return GOOGLE_ULONGLONG(0);
+}
+inline void Node::set_descriptivetypenameref(::google::protobuf::uint64 value) {
+  if (!has_descriptivetypenameref()) {
+    clear_descriptiveTypeNameOrRef();
+    set_has_descriptivetypenameref();
+  }
+  descriptiveTypeNameOrRef_.descriptivetypenameref_ = value;
+  // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.descriptiveTypeNameRef)
+}
+
 inline bool Node::has_TypeNameOrRef() const {
   return TypeNameOrRef_case() != TYPENAMEORREF_NOT_SET;
 }
 inline void Node::clear_has_TypeNameOrRef() {
   _oneof_case_[0] = TYPENAMEORREF_NOT_SET;
 }
 inline bool Node::has_JSObjectClassNameOrRef() const {
   return JSObjectClassNameOrRef_case() != JSOBJECTCLASSNAMEORREF_NOT_SET;
@@ -2070,25 +2234,34 @@ inline void Node::clear_has_JSObjectClas
   _oneof_case_[1] = JSOBJECTCLASSNAMEORREF_NOT_SET;
 }
 inline bool Node::has_ScriptFilenameOrRef() const {
   return ScriptFilenameOrRef_case() != SCRIPTFILENAMEORREF_NOT_SET;
 }
 inline void Node::clear_has_ScriptFilenameOrRef() {
   _oneof_case_[2] = SCRIPTFILENAMEORREF_NOT_SET;
 }
+inline bool Node::has_descriptiveTypeNameOrRef() const {
+  return descriptiveTypeNameOrRef_case() != DESCRIPTIVETYPENAMEORREF_NOT_SET;
+}
+inline void Node::clear_has_descriptiveTypeNameOrRef() {
+  _oneof_case_[3] = DESCRIPTIVETYPENAMEORREF_NOT_SET;
+}
 inline Node::TypeNameOrRefCase Node::TypeNameOrRef_case() const {
   return Node::TypeNameOrRefCase(_oneof_case_[0]);
 }
 inline Node::JSObjectClassNameOrRefCase Node::JSObjectClassNameOrRef_case() const {
   return Node::JSObjectClassNameOrRefCase(_oneof_case_[1]);
 }
 inline Node::ScriptFilenameOrRefCase Node::ScriptFilenameOrRef_case() const {
   return Node::ScriptFilenameOrRefCase(_oneof_case_[2]);
 }
+inline Node::DescriptiveTypeNameOrRefCase Node::descriptiveTypeNameOrRef_case() const {
+  return Node::DescriptiveTypeNameOrRefCase(_oneof_case_[3]);
+}
 // -------------------------------------------------------------------
 
 // Edge
 
 // optional uint64 referent = 1;
 inline bool Edge::has_referent() const {
   return (_has_bits_[0] & 0x00000001u) != 0;
 }
--- a/devtools/shared/heapsnapshot/CoreDump.proto
+++ b/devtools/shared/heapsnapshot/CoreDump.proto
@@ -101,41 +101,47 @@ message StackFrame {
 
         optional bool          isSystem     = 9;
         optional bool          isSelfHosted = 10;
     }
 }
 
 // A serialized version of `JS::ubi::Node` and its outgoing edges.
 message Node {
-    optional uint64     id                   = 1;
+    optional uint64     id                     = 1;
 
     // De-duplicated two-byte string.
     oneof TypeNameOrRef {
-        bytes           typeName             = 2;
-        uint64          typeNameRef          = 3;
+        bytes           typeName               = 2;
+        uint64          typeNameRef            = 3;
     }
 
-    optional uint64     size                 = 4;
-    repeated Edge       edges                = 5;
-    optional StackFrame allocationStack      = 6;
+    optional uint64     size                   = 4;
+    repeated Edge       edges                  = 5;
+    optional StackFrame allocationStack        = 6;
 
     // De-duplicated one-byte string.
     oneof JSObjectClassNameOrRef {
-        bytes           jsObjectClassName    = 7;
-        uint64          jsObjectClassNameRef = 8;
+        bytes           jsObjectClassName      = 7;
+        uint64          jsObjectClassNameRef   = 8;
     }
 
     // JS::ubi::CoarseType. Defaults to Other.
-    optional uint32     coarseType           = 9 [default = 0];
+    optional uint32     coarseType             = 9 [default = 0];
 
     // De-duplicated one-byte string.
     oneof ScriptFilenameOrRef {
-        bytes           scriptFilename       = 10;
-        uint64          scriptFilenameRef    = 11;
+        bytes           scriptFilename         = 10;
+        uint64          scriptFilenameRef      = 11;
+    }
+
+    // De-duplicated one-byte string.
+    oneof descriptiveTypeNameOrRef {
+        bytes           descriptiveTypeName    = 12;
+        uint64          descriptiveTypeNameRef = 13;
     }
 }
 
 // A serialized edge from the heap graph.
 message Edge {
     optional uint64 referent    = 1;
 
     // De-duplicated two-byte string.
--- a/devtools/shared/heapsnapshot/DeserializedNode.h
+++ b/devtools/shared/heapsnapshot/DeserializedNode.h
@@ -65,50 +65,55 @@ struct DeserializedNode {
   const char16_t*     typeName;
   uint64_t            size;
   EdgeVector          edges;
   Maybe<StackFrameId> allocationStack;
   // A borrowed reference to a string owned by this node's owning HeapSnapshot.
   const char*         jsObjectClassName;
   // A borrowed reference to a string owned by this node's owning HeapSnapshot.
   const char*         scriptFilename;
+  // A borrowed reference to a string owned by this node's owning HeapSnapshot.
+  const char16_t*     descriptiveTypeName;
   // A weak pointer to this node's owning `HeapSnapshot`. Safe without
   // AddRef'ing because this node's lifetime is equal to that of its owner.
   HeapSnapshot*       owner;
 
   DeserializedNode(NodeId id,
                    JS::ubi::CoarseType coarseType,
                    const char16_t* typeName,
                    uint64_t size,
                    EdgeVector&& edges,
                    const Maybe<StackFrameId>& allocationStack,
                    const char* className,
                    const char* filename,
+                   const char16_t* descriptiveName,
                    HeapSnapshot& owner)
     : id(id)
     , coarseType(coarseType)
     , typeName(typeName)
     , size(size)
     , edges(std::move(edges))
     , allocationStack(allocationStack)
     , jsObjectClassName(className)
     , scriptFilename(filename)
+    , descriptiveTypeName(descriptiveName)
     , owner(&owner)
   { }
   virtual ~DeserializedNode() { }
 
   DeserializedNode(DeserializedNode&& rhs)
     : id(rhs.id)
     , coarseType(rhs.coarseType)
     , typeName(rhs.typeName)
     , size(rhs.size)
     , edges(std::move(rhs.edges))
     , allocationStack(rhs.allocationStack)
     , jsObjectClassName(rhs.jsObjectClassName)
     , scriptFilename(rhs.scriptFilename)
+    , descriptiveTypeName(rhs.descriptiveTypeName)
     , owner(rhs.owner)
   { }
 
   DeserializedNode& operator=(DeserializedNode&& rhs)
   {
     MOZ_ASSERT(&rhs != this);
     this->~DeserializedNode();
     new(this) DeserializedNode(std::move(rhs));
@@ -127,16 +132,17 @@ protected:
     : id(id)
     , coarseType(JS::ubi::CoarseType::Other)
     , typeName(typeName)
     , size(size)
     , edges()
     , allocationStack(Nothing())
     , jsObjectClassName(nullptr)
     , scriptFilename(nullptr)
+    , descriptiveTypeName(nullptr)
     , owner(nullptr)
   { }
 
 private:
   DeserializedNode(const DeserializedNode&) = delete;
   DeserializedNode& operator=(const DeserializedNode&) = delete;
 };
 
@@ -254,16 +260,17 @@ public:
 
   CoarseType coarseType() const final { return get().coarseType; }
   Id identifier() const override { return get().id; }
   bool isLive() const override { return false; }
   const char16_t* typeName() const override;
   Node::Size size(mozilla::MallocSizeOf mallocSizeof) const override;
   const char* jsObjectClassName() const override { return get().jsObjectClassName; }
   const char* scriptFilename() const final { return get().scriptFilename; }
+  const char16_t* descriptiveTypeName() const override { return get().descriptiveTypeName; }
 
   bool hasAllocationStack() const override { return get().allocationStack.isSome(); }
   StackFrame allocationStack() const override;
 
   // We ignore the `bool wantNames` parameter because we can't control whether
   // the core dump was serialized with edge names or not.
   js::UniquePtr<EdgeRange> edges(JSContext* cx, bool) const override;
 
--- a/devtools/shared/heapsnapshot/HeapSnapshot.cpp
+++ b/devtools/shared/heapsnapshot/HeapSnapshot.cpp
@@ -279,21 +279,31 @@ HeapSnapshot::saveNode(const protobuf::N
   const char* scriptFilename = nullptr;
   if (node.ScriptFilenameOrRef_case() != protobuf::Node::SCRIPTFILENAMEORREF_NOT_SET) {
     Maybe<StringOrRef> scriptFilenameOrRef = GET_STRING_OR_REF(node, scriptfilename);
     scriptFilename = getOrInternString<char>(internedOneByteStrings, scriptFilenameOrRef);
     if (NS_WARN_IF(!scriptFilename))
       return false;
   }
 
+  const char16_t* descriptiveTypeName = nullptr;
+  if (node.descriptiveTypeNameOrRef_case() != protobuf::Node::DESCRIPTIVETYPENAMEORREF_NOT_SET) {
+    Maybe<StringOrRef> descriptiveTypeNameOrRef = GET_STRING_OR_REF(node, descriptivetypename);
+    descriptiveTypeName = getOrInternString<char16_t>(internedTwoByteStrings, descriptiveTypeNameOrRef);
+    if (NS_WARN_IF(!descriptiveTypeName))
+        return false;
+  }
+
   if (NS_WARN_IF(!nodes.putNew(id, DeserializedNode(id, coarseType, typeName,
                                                     size, std::move(edges),
                                                     allocationStack,
                                                     jsObjectClassName,
-                                                    scriptFilename, *this))))
+                                                    scriptFilename,
+                                                    descriptiveTypeName,
+                                                     *this))))
   {
     return false;
   };
 
   return true;
 }
 
 bool
@@ -1335,16 +1345,26 @@ public:
       if (NS_WARN_IF(!attachOneByteString(scriptFilename,
                                           [&] (std::string* name) { protobufNode.set_allocated_scriptfilename(name); },
                                           [&] (uint64_t ref) { protobufNode.set_scriptfilenameref(ref); })))
       {
         return false;
       }
     }
 
+    if (ubiNode.descriptiveTypeName()) {
+      auto descriptiveTypeName = TwoByteString(ubiNode.descriptiveTypeName());
+      if (NS_WARN_IF(!attachTwoByteString(descriptiveTypeName,
+                                          [&] (std::string* name) { protobufNode.set_allocated_descriptivetypename(name); },
+                                          [&] (uint64_t ref) { protobufNode.set_descriptivetypenameref(ref); })))
+      {
+        return false;
+      }
+    }
+
     return writeMessage(protobufNode);
   }
 };
 
 // A JS::ubi::BreadthFirst handler that serializes a snapshot of the heap into a
 // core dump.
 class MOZ_STACK_CLASS HeapSnapshotHandler
 {
--- a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_01.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_01.js
@@ -18,26 +18,31 @@ const breakdown = {
   scripts: {
     by: "internalType",
     then: { by: "count", count: true, bytes: true },
   },
   other: {
     by: "internalType",
     then: { by: "count", count: true, bytes: true },
   },
+  domNode: {
+    by: "descriptiveType",
+    then: { by: "count", count: true, bytes: true },
+  },
 };
 
 const description = {
   objects: {
     Function: { count: 1, bytes: 32 },
     other: { count: 0, bytes: 0 }
   },
   strings: {},
   scripts: {},
-  other: {}
+  other: {},
+  domNode: {}
 };
 
 const expected = [
   "objects",
   "Function"
 ];
 
 const shallowSize = 32;
--- a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_02.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_02.js
@@ -18,25 +18,30 @@ const breakdown = {
   scripts: {
     by: "internalType",
     then: { by: "count", count: true, bytes: true },
   },
   other: {
     by: "internalType",
     then: { by: "count", count: true, bytes: true },
   },
+  domNode: {
+    by: "descriptiveType",
+    then: { by: "count", count: true, bytes: true },
+  },
 };
 
 const description = {
   objects: {
     other: { count: 1, bytes: 10 }
   },
   strings: {},
   scripts: {},
-  other: {}
+  other: {},
+  domNode: {}
 };
 
 const expected = [
   "objects",
   "other"
 ];
 
 const shallowSize = 10;
--- a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_03.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_03.js
@@ -18,27 +18,32 @@ const breakdown = {
   scripts: {
     by: "internalType",
     then: { by: "count", count: true, bytes: true },
   },
   other: {
     by: "internalType",
     then: { by: "count", count: true, bytes: true },
   },
+  domNode: {
+    by: "descriptiveType",
+    then: { by: "count", count: true, bytes: true },
+  },
 };
 
 const description = {
   objects: {
     other: { count: 0, bytes: 0 }
   },
   strings: {
     "JSString": { count: 1, bytes: 42 },
   },
   scripts: {},
-  other: {}
+  other: {},
+  domNode: {}
 };
 
 const expected = [
   "strings",
   "JSString"
 ];
 
 const shallowSize = 42;
--- a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_04.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_04.js
@@ -22,28 +22,33 @@ const breakdown = {
   scripts: {
     by: "internalType",
     then: { by: "count", count: true, bytes: true },
   },
   other: {
     by: "internalType",
     then: { by: "count", count: true, bytes: true },
   },
+  domNode: {
+    by: "descriptiveType",
+    then: { by: "count", count: true, bytes: true },
+  },
 };
 
 const stack = saveStack();
 
 const description = {
   objects: {
     Array: new Map([[stack, { count: 1, bytes: 512 }]]),
     other: { count: 0, bytes: 0 }
   },
   strings: {},
   scripts: {},
-  other: {}
+  other: {},
+  domNode: {}
 };
 
 const expected = [
   "objects",
   "Array",
   stack
 ];
 
--- a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_partialTraversal_01.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_partialTraversal_01.js
@@ -33,29 +33,31 @@ const mockDominatorTree = {
   getImmediatelyDominated: id => (tree.get(id) || []).slice()
 };
 
 const mockSnapshot = {
   describeNode: _ => ({
     objects: { count: 0, bytes: 0 },
     strings: { count: 0, bytes: 0 },
     scripts: { count: 0, bytes: 0 },
-    other: { SomeType: { count: 1, bytes: 10 } }
+    other: { SomeType: { count: 1, bytes: 10 } },
+    domNode: { count: 0, bytes: 0 },
   })
 };
 
 const breakdown = {
   by: "coarseType",
   objects: { by: "count", count: true, bytes: true },
   strings: { by: "count", count: true, bytes: true },
   scripts: { by: "count", count: true, bytes: true },
   other: {
     by: "internalType",
     then: { by: "count", count: true, bytes: true }
   },
+  domNode: { by: "count", count: true, bytes: true },
 };
 
 const expected = {
   nodeId: 100,
   label: [
     "other",
     "SomeType"
   ],
--- a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_deleteHeapSnapshot_03.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_deleteHeapSnapshot_03.js
@@ -5,16 +5,17 @@
 // Test other dominatorTrees can still be retrieved after deleting a snapshot
 
 const breakdown = {
   by: "coarseType",
   objects: { by: "count", count: true, bytes: true },
   scripts: { by: "count", count: true, bytes: true },
   strings: { by: "count", count: true, bytes: true },
   other: { by: "count", count: true, bytes: true },
+  domNode: { by: "count", count: true, bytes: true },
 };
 
 async function createSnapshotAndDominatorTree(client) {
   const snapshotFilePath = saveNewHeapSnapshot();
   await client.readHeapSnapshot(snapshotFilePath);
   const dominatorTreeId = await client.computeDominatorTree(snapshotFilePath);
   return { dominatorTreeId, snapshotFilePath };
 }
--- a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getCensusIndividuals_01.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getCensusIndividuals_01.js
@@ -7,16 +7,17 @@
 const COUNT = { by: "count", count: true, bytes: true };
 
 const CENSUS_BREAKDOWN = {
   by: "coarseType",
   objects: COUNT,
   strings: COUNT,
   scripts: COUNT,
   other: COUNT,
+  domNode: COUNT,
 };
 
 const LABEL_BREAKDOWN = {
   by: "internalType",
   then: COUNT,
 };
 
 const MAX_INDIVIDUALS = 10;
--- a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getDominatorTree_01.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getDominatorTree_01.js
@@ -5,16 +5,17 @@
 // Test the HeapAnalyses{Client,Worker} "getDominatorTree" request.
 
 const breakdown = {
   by: "coarseType",
   objects: { by: "count", count: true, bytes: true },
   scripts: { by: "count", count: true, bytes: true },
   strings: { by: "count", count: true, bytes: true },
   other: { by: "count", count: true, bytes: true },
+  domNode: { by: "count", count: true, bytes: true },
 };
 
 add_task(async function() {
   const client = new HeapAnalysesClient();
 
   const snapshotFilePath = saveNewHeapSnapshot();
   await client.readHeapSnapshot(snapshotFilePath);
   ok(true, "Should have read the heap snapshot");
--- a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getImmediatelyDominated_01.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getImmediatelyDominated_01.js
@@ -5,16 +5,17 @@
 // Test the HeapAnalyses{Client,Worker} "getImmediatelyDominated" request.
 
 const breakdown = {
   by: "coarseType",
   objects: { by: "count", count: true, bytes: true },
   scripts: { by: "count", count: true, bytes: true },
   strings: { by: "count", count: true, bytes: true },
   other: { by: "count", count: true, bytes: true },
+  domNode: { by: "count", count: true, bytes: true },
 };
 
 add_task(async function() {
   const client = new HeapAnalysesClient();
 
   const snapshotFilePath = saveNewHeapSnapshot();
   await client.readHeapSnapshot(snapshotFilePath);
   const dominatorTreeId = await client.computeDominatorTree(snapshotFilePath);
--- a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensusDiff_02.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensusDiff_02.js
@@ -19,16 +19,20 @@ const BREAKDOWN = {
   strings: {
     by: "internalType",
     then: { by: "count", count: true, bytes: true },
   },
   other: {
     by: "internalType",
     then: { by: "count", count: true, bytes: true },
   },
+  domNode: {
+    by: "descriptiveType",
+    then: { by: "count", count: true, bytes: true },
+  },
 };
 
 add_task(async function() {
   const firstSnapshotFilePath = saveNewHeapSnapshot();
   const secondSnapshotFilePath = saveNewHeapSnapshot();
 
   const client = new HeapAnalysesClient();
   await client.readHeapSnapshot(firstSnapshotFilePath);
--- a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_07.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_07.js
@@ -19,16 +19,20 @@ const BREAKDOWN = {
   strings: {
     by: "internalType",
     then: { by: "count", count: true, bytes: true },
   },
   other: {
     by: "internalType",
     then: { by: "count", count: true, bytes: true },
   },
+  domNode: {
+    by: "descriptiveType",
+    then: { by: "count", count: true, bytes: true },
+  },
 };
 
 add_task(async function() {
   const client = new HeapAnalysesClient();
 
   const snapshotFilePath = saveNewHeapSnapshot();
   await client.readHeapSnapshot(snapshotFilePath);
   ok(true, "Should have read the heap snapshot");
--- a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-02.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-02.js
@@ -9,29 +9,31 @@
 const countBreakdown = { by: "count", count: true, bytes: true };
 
 const BREAKDOWN = {
   by: "coarseType",
   objects: { by: "objectClass", then: countBreakdown },
   strings: countBreakdown,
   scripts: countBreakdown,
   other: { by: "internalType", then: countBreakdown },
+  domNode: countBreakdown,
 };
 
 const REPORT = {
   "objects": {
     "Function": { bytes: 10, count: 1 },
     "Array": { bytes: 20, count: 2 },
   },
   "strings": { bytes: 10, count: 1 },
   "scripts": { bytes: 1, count: 1 },
   "other": {
     "js::Shape": { bytes: 30, count: 3 },
     "js::Shape2": { bytes: 40, count: 4 }
   },
+  "domNode": { bytes: 0, count: 0 }
 };
 
 const EXPECTED = {
   name: null,
   bytes: 0,
   totalBytes: 111,
   count: 0,
   totalCount: 12,
--- a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-07.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-07.js
@@ -21,32 +21,37 @@ function run_test() {
     strings: {
       by: "internalType",
       then: { by: "count", count: true, bytes: true },
     },
     other: {
       by: "internalType",
       then: { by: "count", count: true, bytes: true },
     },
+    domNode: {
+      by: "descriptiveType",
+      then: { by: "count", count: true, bytes: true },
+    }
   };
 
   const REPORT = {
     objects: {
       Array: { bytes: 50, count: 5 },
       other: { bytes: 0, count: 0 },
     },
     scripts: {
       "js::jit::JitScript": { bytes: 30, count: 3 },
     },
     strings: {
       JSAtom: { bytes: 60, count: 6 },
     },
     other: {
       "js::Shape": { bytes: 80, count: 8 },
-    }
+    },
+    domNode: { }
   };
 
   const EXPECTED = {
     name: null,
     bytes: 0,
     totalBytes: 220,
     count: 0,
     totalCount: 22,
@@ -67,28 +72,28 @@ function run_test() {
             children: [
               {
                 name: null,
                 bytes: 0,
                 totalBytes: 220,
                 count: 0,
                 totalCount: 22,
                 children: undefined,
-                id: 14,
-                parent: 13,
+                id: 15,
+                parent: 14,
                 reportLeafIndex: undefined,
               }
             ],
-            id: 13,
-            parent: 12,
+            id: 14,
+            parent: 13,
             reportLeafIndex: undefined,
           }
         ],
-        id: 12,
-        parent: 11,
+        id: 13,
+        parent: 12,
         reportLeafIndex: 9,
       },
       {
         name: "JSAtom",
         bytes: 60,
         totalBytes: 60,
         count: 6,
         totalCount: 6,
@@ -102,28 +107,28 @@ function run_test() {
             children: [
               {
                 name: null,
                 bytes: 0,
                 totalBytes: 220,
                 count: 0,
                 totalCount: 22,
                 children: undefined,
-                id: 17,
-                parent: 16,
+                id: 18,
+                parent: 17,
                 reportLeafIndex: undefined,
               }
             ],
-            id: 16,
-            parent: 15,
+            id: 17,
+            parent: 16,
             reportLeafIndex: undefined,
           }
         ],
-        id: 15,
-        parent: 11,
+        id: 16,
+        parent: 12,
         reportLeafIndex: 7,
       },
       {
         name: "Array",
         bytes: 50,
         totalBytes: 50,
         count: 5,
         totalCount: 5,
@@ -137,28 +142,28 @@ function run_test() {
             children: [
               {
                 name: null,
                 bytes: 0,
                 totalBytes: 220,
                 count: 0,
                 totalCount: 22,
                 children: undefined,
-                id: 20,
-                parent: 19,
+                id: 21,
+                parent: 20,
                 reportLeafIndex: undefined,
               }
             ],
-            id: 19,
-            parent: 18,
+            id: 20,
+            parent: 19,
             reportLeafIndex: undefined,
           }
         ],
-        id: 18,
-        parent: 11,
+        id: 19,
+        parent: 12,
         reportLeafIndex: 2,
       },
       {
         name: "js::jit::JitScript",
         bytes: 30,
         totalBytes: 30,
         count: 3,
         totalCount: 3,
@@ -172,30 +177,30 @@ function run_test() {
             children: [
               {
                 name: null,
                 bytes: 0,
                 totalBytes: 220,
                 count: 0,
                 totalCount: 22,
                 children: undefined,
-                id: 23,
-                parent: 22,
+                id: 24,
+                parent: 23,
                 reportLeafIndex: undefined,
               }
             ],
-            id: 22,
-            parent: 21,
+            id: 23,
+            parent: 22,
             reportLeafIndex: undefined,
           }
         ],
-        id: 21,
-        parent: 11,
+        id: 22,
+        parent: 12,
         reportLeafIndex: 5,
       },
     ],
-    id: 11,
+    id: 12,
     parent: undefined,
     reportLeafIndex: undefined,
   };
 
   compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { invert: true });
 }
--- a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-10.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-10.js
@@ -15,27 +15,29 @@ function run_test() {
       then: { by: "count", count: true, bytes: true },
     },
     other: {
       by: "internalType",
       then: { by: "count", count: true, bytes: true },
     },
     strings: { by: "count", count: true, bytes: true },
     scripts: { by: "count", count: true, bytes: true },
+    domNode: { by: "count", count: true, bytes: true },
   };
 
   const REPORT = {
     objects: {
       Array: { count: 1, bytes: 10 },
     },
     other: {
       Array: { count: 1, bytes: 10 },
     },
     strings: { count: 0, bytes: 0 },
     scripts: { count: 0, bytes: 0 },
+    domNode: {count: 0, bytes: 0 },
   };
 
   const node = censusReportToCensusTreeNode(BREAKDOWN, REPORT, { invert: true });
 
   equal(node.children[0].name, "Array");
   equal(node.children[0].reportLeafIndex.size, 2);
   dumpn(`node.children[0].reportLeafIndex = ${[...node.children[0].reportLeafIndex]}`);
   ok(node.children[0].reportLeafIndex.has(2));
--- a/devtools/shared/heapsnapshot/tests/unit/test_census_diff_03.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_diff_03.js
@@ -5,16 +5,17 @@
 // Test diffing census reports of breakdown by "coarseType".
 
 const BREAKDOWN = {
   by: "coarseType",
   objects: { by: "count", count: true, bytes: true },
   scripts: { by: "count", count: true, bytes: true },
   strings: { by: "count", count: true, bytes: true },
   other: { by: "count", count: true, bytes: true },
+  domNode: { by: "count", count: true, bytes: true },
 };
 
 const REPORT1 = {
   objects: {
     count: 1,
     bytes: 10,
   },
   scripts: {
@@ -24,16 +25,20 @@ const REPORT1 = {
   strings: {
     count: 1,
     bytes: 10,
   },
   other: {
     count: 3,
     bytes: 30,
   },
+  domNode: {
+    count: 0,
+    bytes: 0,
+  },
 };
 
 const REPORT2 = {
   objects: {
     count: 1,
     bytes: 10,
   },
   scripts: {
@@ -43,16 +48,20 @@ const REPORT2 = {
   strings: {
     count: 2,
     bytes: 20,
   },
   other: {
     count: 4,
     bytes: 40,
   },
+  domNode: {
+    count: 0,
+    bytes: 0,
+  },
 };
 
 const EXPECTED = {
   objects: {
     count: 0,
     bytes: 0,
   },
   scripts: {
@@ -62,13 +71,17 @@ const EXPECTED = {
   strings: {
     count: 1,
     bytes: 10,
   },
   other: {
     count: 1,
     bytes: 10,
   },
+  domNode: {
+    count: 0,
+    bytes: 0,
+  },
 };
 
 function run_test() {
   assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
 }
--- a/devtools/shared/heapsnapshot/tests/unit/test_census_diff_06.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_diff_06.js
@@ -26,16 +26,20 @@ const BREAKDOWN = {
   scripts: {
     by: "internalType",
     then: { by: "count", count: false, bytes: true }
   },
   other: {
     by: "internalType",
     then: { by: "count", count: false, bytes: true }
   },
+  domNode: {
+    by: "internalType",
+    then: { by: "count", count: false, bytes: true }
+  },
 };
 
 const stack1 = saveStack();
 const stack2 = saveStack();
 const stack3 = saveStack();
 
 const REPORT1 = {
   objects: new Map([
@@ -54,17 +58,18 @@ const REPORT1 = {
     JSLinearString: { bytes: 5 },
   },
   scripts: {
     JSScript: { bytes: 1 },
     "js::jit::JitCode": { bytes: 2 },
   },
   other: {
     "mozilla::dom::Thing": { bytes: 1 },
-  }
+  },
+  domNode: {}
 };
 
 const REPORT2 = {
   objects: new Map([
     [stack2, { Array: { bytes: 1 },
                Date: { bytes: 2 },
                other: { bytes: 3 },
     }],
@@ -80,17 +85,18 @@ const REPORT2 = {
   },
   scripts: {
     JSScript: { bytes: 2 },
     "js::LazyScript": { bytes: 42 },
     "js::jit::JitCode": { bytes: 1 },
   },
   other: {
     "mozilla::dom::OtherThing": { bytes: 1 },
-  }
+  },
+  domNode: {}
 };
 
 const EXPECTED = {
   "objects": new Map([
     [stack1, { Function: { bytes: -1 },
                Object: { bytes: -2 },
                other: { bytes: 0 },
     }],
@@ -125,14 +131,15 @@ const EXPECTED = {
   },
   "other": {
     "mozilla::dom::Thing": {
       "bytes": -1
     },
     "mozilla::dom::OtherThing": {
       "bytes": 1
     }
-  }
+  },
+  "domNode": {},
 };
 
 function run_test() {
   assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
 }
--- a/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_01.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_01.js
@@ -19,16 +19,20 @@ function run_test() {
     strings: {
       by: "internalType",
       then: { by: "count", count: true, bytes: true },
     },
     other: {
       by: "internalType",
       then: { by: "count", count: true, bytes: true },
     },
+    domNode: {
+      by: "descriptiveType",
+      then: { by: "count", count: true, bytes: true },
+    },
   };
 
   const REPORT = {
     objects: {
       Array: { bytes: 50, count: 5 },
       UInt8Array: { bytes: 80, count: 8 },
       Int32Array: { bytes: 320, count: 32 },
       other: { bytes: 0, count: 0 },
@@ -36,17 +40,18 @@ function run_test() {
     scripts: {
       "js::jit::JitScript": { bytes: 30, count: 3 },
     },
     strings: {
       JSAtom: { bytes: 60, count: 6 },
     },
     other: {
       "js::Shape": { bytes: 80, count: 8 },
-    }
+    },
+    domNode: {}
   };
 
   const EXPECTED = {
     name: null,
     bytes: 0,
     totalBytes: 620,
     count: 0,
     totalCount: 62,
@@ -60,47 +65,47 @@ function run_test() {
         children: [
           {
             name: "Int32Array",
             bytes: 320,
             totalBytes: 320,
             count: 32,
             totalCount: 32,
             children: undefined,
-            id: 15,
-            parent: 14,
+            id: 16,
+            parent: 15,
             reportLeafIndex: 4,
           },
           {
             name: "UInt8Array",
             bytes: 80,
             totalBytes: 80,
             count: 8,
             totalCount: 8,
             children: undefined,
-            id: 16,
-            parent: 14,
+            id: 17,
+            parent: 15,
             reportLeafIndex: 3,
           },
           {
             name: "Array",
             bytes: 50,
             totalBytes: 50,
             count: 5,
             totalCount: 5,
             children: undefined,
-            id: 17,
-            parent: 14,
+            id: 18,
+            parent: 15,
             reportLeafIndex: 2,
           }
         ],
-        id: 14,
-        parent: 13,
+        id: 15,
+        parent: 14,
         reportLeafIndex: undefined,
       }
     ],
-    id: 13,
+    id: 14,
     parent: undefined,
     reportLeafIndex: undefined,
   };
 
   compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { filter: "Array" });
 }
--- a/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_03.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_03.js
@@ -19,16 +19,20 @@ function run_test() {
     strings: {
       by: "internalType",
       then: { by: "count", count: true, bytes: true },
     },
     other: {
       by: "internalType",
       then: { by: "count", count: true, bytes: true },
     },
+    domNode: {
+      by: "descriptiveType",
+      then: { by: "count", count: true, bytes: true },
+    },
   };
 
   const REPORT = {
     objects: {
       Array: { bytes: 50, count: 5 },
       UInt8Array: { bytes: 80, count: 8 },
       Int32Array: { bytes: 320, count: 32 },
       other: { bytes: 0, count: 0 },
@@ -36,25 +40,26 @@ function run_test() {
     scripts: {
       "js::jit::JitScript": { bytes: 30, count: 3 },
     },
     strings: {
       JSAtom: { bytes: 60, count: 6 },
     },
     other: {
       "js::Shape": { bytes: 80, count: 8 },
-    }
+    },
+    domNode: {}
   };
 
   const EXPECTED = {
     name: null,
     bytes: 0,
     totalBytes: 620,
     count: 0,
     totalCount: 62,
     children: undefined,
-    id: 13,
+    id: 14,
     parent: undefined,
     reportLeafIndex: undefined,
   };
 
   compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { filter: "zzzzzzzzzzzzzzzzzzzz" });
 }
--- a/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_04.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_04.js
@@ -14,16 +14,17 @@ function run_test() {
     objects: { by: "objectClass", then: COUNT, other: COUNT },
     strings: COUNT,
     scripts: {
       by: "filename",
       then: INTERNAL_TYPE,
       noFilename: INTERNAL_TYPE
     },
     other: INTERNAL_TYPE,
+    domNode: { by: "descriptiveType", then: COUNT, other: COUNT },
   };
 
   const REPORT = {
     objects: {
       Function: {
         count: 7,
         bytes: 70
       },
@@ -44,17 +45,18 @@ function run_test() {
       count: 2,
       bytes: 20
     },
     other: {
       "js::Shape": {
         count: 1,
         bytes: 10
       }
-    }
+    },
+    domNode: {}
   };
 
   const EXPECTED = {
     name: null,
     bytes: 0,
     totalBytes: 200,
     count: 0,
     totalCount: 20,
@@ -68,36 +70,36 @@ function run_test() {
         totalCount: 13,
         children: [
           {
             name: "Function",
             bytes: 70,
             totalBytes: 70,
             count: 7,
             totalCount: 7,
-            id: 13,
-            parent: 12,
+            id: 14,
+            parent: 13,
             children: undefined,
             reportLeafIndex: 2,
           },
           {
             name: "Array",
             bytes: 60,
             totalBytes: 60,
             count: 6,
             totalCount: 6,
-            id: 14,
-            parent: 12,
+            id: 15,
+            parent: 13,
             children: undefined,
             reportLeafIndex: 3,
           },
         ],
-        id: 12,
-        parent: 11,
+        id: 13,
+        parent: 12,
         reportLeafIndex: undefined,
       }
     ],
-    id: 11,
+    id: 12,
     reportLeafIndex: undefined,
   };
 
   compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { filter: "objects" });
 }
--- a/devtools/shared/heapsnapshot/tests/unit/test_getReportLeaves_01.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_getReportLeaves_01.js
@@ -23,16 +23,20 @@ function run_test() {
         by: "internalType",
         then: { by: "count", count: true, bytes: true },
       },
     },
     other: {
       by: "internalType",
       then: { by: "count", count: true, bytes: true },
     },
+    domNode: {
+      by: "descriptiveType",
+      then: { by: "count", count: true, bytes: true },
+    },
   };
 
   const REPORT = {
     objects: {
       Array: { count: 6, bytes: 60 },
       Function: { count: 1, bytes: 10 },
       Object: { count: 1, bytes: 10 },
       RegExp: { count: 1, bytes: 10 },
@@ -48,16 +52,17 @@ function run_test() {
         JSScript: { count: 1, bytes: 10 },
         "js::jit::IonScript": { count: 1, bytes: 10 },
       },
     },
     other: {
       "js::Shape": { count: 7, bytes: 70 },
       "js::BaseShape": { count: 1, bytes: 10 },
     },
+    domNode: { },
   };
 
   const root = censusReportToCensusTreeNode(BREAKDOWN, REPORT);
   dumpn("CensusTreeNode tree = " + JSON.stringify(root, null, 4));
 
   (function assertEveryNodeCanFindItsLeaf(node) {
     if (node.reportLeafIndex) {
       const [ leaf ] = CensusUtils.getReportLeaves(new Set([node.reportLeafIndex]),
new file mode 100644
--- /dev/null
+++ b/docshell/base/BrowsingContext.cpp
@@ -0,0 +1,265 @@
+/* -*- 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/. */
+
+#include "BrowsingContext.h"
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Logging.h"
+#include "mozilla/StaticPtr.h"
+
+#include "nsDataHashtable.h"
+#include "nsRefPtrHashtable.h"
+#include "nsIDocShell.h"
+#include "nsContentUtils.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+static LazyLogModule gBrowsingContextLog("BrowsingContext");
+
+static StaticAutoPtr<BrowsingContext::Children> sRootBrowsingContexts;
+
+static StaticAutoPtr<nsDataHashtable<nsUint64HashKey, BrowsingContext*>>
+  sBrowsingContexts;
+
+// TODO(farre): This duplicates some of the work performed by the
+// bfcache. This should be unified. [Bug 1471601]
+static StaticAutoPtr<nsRefPtrHashtable<nsUint64HashKey, BrowsingContext>>
+  sCachedBrowsingContexts;
+
+/* static */ void
+BrowsingContext::Init()
+{
+  if (!sRootBrowsingContexts) {
+    sRootBrowsingContexts = new BrowsingContext::Children();
+    ClearOnShutdown(&sRootBrowsingContexts);
+  }
+
+  if (!sBrowsingContexts) {
+    sBrowsingContexts =
+      new nsDataHashtable<nsUint64HashKey, BrowsingContext*>();
+    ClearOnShutdown(&sBrowsingContexts);
+  }
+
+  if (!sCachedBrowsingContexts) {
+    sCachedBrowsingContexts =
+      new nsRefPtrHashtable<nsUint64HashKey, BrowsingContext>();
+    ClearOnShutdown(&sCachedBrowsingContexts);
+  }
+}
+
+/* static */ LogModule*
+BrowsingContext::GetLog()
+{
+  return gBrowsingContextLog;
+}
+
+// TODO(farre): BrowsingContext::CleanupContexts starts from the list
+// of root BrowsingContexts. This isn't enough when separate
+// BrowsingContext nodes of a BrowsingContext tree not in a crashing
+// child process are from that process and thus needs to be
+// cleaned. [Bug 1472108]
+/* static */ void
+BrowsingContext::CleanupContexts(uint64_t aProcessId)
+{
+  if (sRootBrowsingContexts) {
+    RefPtr<BrowsingContext> context = sRootBrowsingContexts->getFirst();
+
+    while (context) {
+      RefPtr<BrowsingContext> next = context->getNext();
+      if (context->IsOwnedByProcess() &&
+          aProcessId == context->OwnerProcessId()) {
+        context->Detach();
+      }
+      context = next;
+    }
+  }
+}
+
+/* static */ already_AddRefed<BrowsingContext>
+BrowsingContext::Get(uint64_t aId)
+{
+  RefPtr<BrowsingContext> abc = sBrowsingContexts->Get(aId);
+  return abc.forget();
+}
+
+BrowsingContext::BrowsingContext(nsIDocShell* aDocShell)
+  : mBrowsingContextId(nsContentUtils::GenerateBrowsingContextId())
+  , mProcessId(Nothing())
+  , mDocShell(aDocShell)
+{
+  sBrowsingContexts->Put(mBrowsingContextId, this);
+}
+
+BrowsingContext::BrowsingContext(uint64_t aBrowsingContextId,
+                                 const nsAString& aName,
+                                 const Maybe<uint64_t>& aProcessId)
+  : mBrowsingContextId(aBrowsingContextId)
+  , mProcessId(aProcessId)
+  , mName(aName)
+{
+  // mProcessId only really has a meaning in the parent process, where
+  // it keeps track of which BrowsingContext is actually holding the
+  // nsDocShell.
+  MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess() || aProcessId.isNothing());
+  sBrowsingContexts->Put(mBrowsingContextId, this);
+}
+
+void
+BrowsingContext::Attach(BrowsingContext* aParent)
+{
+  if (isInList()) {
+    MOZ_LOG(GetLog(),
+            LogLevel::Debug,
+            ("%s: Connecting already existing 0x%08" PRIx64 " to 0x%08" PRIx64,
+             XRE_IsParentProcess() ? "Parent" : "Child",
+             Id(),
+             aParent ? aParent->Id() : 0));
+    MOZ_DIAGNOSTIC_ASSERT(sBrowsingContexts->Contains(Id()));
+    MOZ_DIAGNOSTIC_ASSERT(!IsCached());
+    return;
+  }
+
+  bool wasCached = sCachedBrowsingContexts->Remove(Id());
+
+  MOZ_LOG(GetLog(),
+          LogLevel::Debug,
+          ("%s: %s 0x%08" PRIx64 " to 0x%08" PRIx64,
+           wasCached ? "Re-connecting" : "Connecting",
+           XRE_IsParentProcess() ? "Parent" : "Child",
+           Id(),
+           aParent ? aParent->Id() : 0));
+
+  auto* children = aParent ? &aParent->mChildren : sRootBrowsingContexts.get();
+  children->insertBack(this);
+  mParent = aParent;
+
+  if (!XRE_IsContentProcess()) {
+    return;
+  }
+
+  auto cc = dom::ContentChild::GetSingleton();
+  MOZ_DIAGNOSTIC_ASSERT(cc);
+  cc->SendAttachBrowsingContext(
+    dom::BrowsingContextId(mParent ? mParent->Id() : 0),
+    dom::BrowsingContextId(Id()),
+    mName);
+}
+
+void
+BrowsingContext::Detach()
+{
+  RefPtr<BrowsingContext> kungFuDeathGrip(this);
+
+  if (sCachedBrowsingContexts) {
+    sCachedBrowsingContexts->Remove(Id());
+  }
+
+  if (!isInList()) {
+    MOZ_LOG(GetLog(),
+            LogLevel::Debug,
+            ("%s: Detaching already detached 0x%08" PRIx64,
+             XRE_IsParentProcess() ? "Parent" : "Child",
+             Id()));
+    return;
+  }
+
+  MOZ_LOG(GetLog(),
+          LogLevel::Debug,
+          ("%s: Detaching 0x%08" PRIx64 " from 0x%08" PRIx64,
+           XRE_IsParentProcess() ? "Parent" : "Child",
+           Id(),
+           mParent ? mParent->Id() : 0));
+
+  remove();
+
+  if (!XRE_IsContentProcess()) {
+    return;
+  }
+
+  auto cc = dom::ContentChild::GetSingleton();
+  MOZ_DIAGNOSTIC_ASSERT(cc);
+  cc->SendDetachBrowsingContext(dom::BrowsingContextId(Id()),
+                                false /* aMoveToBFCache */);
+}
+
+void
+BrowsingContext::CacheChildren()
+{
+  if (mChildren.isEmpty()) {
+    return;
+  }
+
+  MOZ_LOG(GetLog(),
+          LogLevel::Debug,
+          ("%s: Caching children of 0x%08" PRIx64 "",
+           XRE_IsParentProcess() ? "Parent" : "Child",
+           Id()));
+
+  while (!mChildren.isEmpty()) {
+    RefPtr<BrowsingContext> child = mChildren.popFirst();
+    sCachedBrowsingContexts->Put(child->Id(), child);
+  }
+
+  if (!XRE_IsContentProcess()) {
+    return;
+  }
+
+  auto cc = dom::ContentChild::GetSingleton();
+  MOZ_DIAGNOSTIC_ASSERT(cc);
+  cc->SendDetachBrowsingContext(dom::BrowsingContextId(Id()),
+                                true /* aMoveToBFCache */);
+}
+
+bool
+BrowsingContext::IsCached()
+{
+  return sCachedBrowsingContexts->Contains(Id());
+}
+
+uint64_t
+BrowsingContext::OwnerProcessId() const
+{
+  MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+  return mProcessId.value();
+}
+
+BrowsingContext::~BrowsingContext()
+{
+  MOZ_DIAGNOSTIC_ASSERT(!isInList());
+
+  if (sBrowsingContexts) {
+    sBrowsingContexts->Remove(mBrowsingContextId);
+  }
+}
+
+static void
+ImplCycleCollectionUnlink(BrowsingContext::Children& aField)
+{
+  aField.clear();
+}
+
+static void
+ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+                            BrowsingContext::Children& aField,
+                            const char* aName,
+                            uint32_t aFlags = 0)
+{
+  for (BrowsingContext* aContext : aField) {
+    aCallback.NoteNativeChild(aContext,
+                              NS_CYCLE_COLLECTION_PARTICIPANT(BrowsingContext));
+  }
+}
+
+NS_IMPL_CYCLE_COLLECTION(BrowsingContext, mDocShell, mChildren)
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(BrowsingContext, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(BrowsingContext, Release)
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/docshell/base/BrowsingContext.h
@@ -0,0 +1,109 @@
+/* -*- 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 BrowsingContext_h
+#define BrowsingContext_h
+
+#include "mozilla/LinkedList.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/WeakPtr.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsString.h"
+
+class nsIDocShell;
+
+namespace mozilla {
+
+class LogModule;
+
+namespace dom {
+
+// BrowsingContext, in this context, is the cross process replicated
+// environment in which information about documents is stored. In
+// particular the tree structure of nested browsing contexts is
+// represented by the tree of BrowsingContexts.
+//
+// The tree of BrowsingContexts in created in step with its
+// corresponding nsDocShell, and when nsDocShells are connected
+// through a parent/child relationship, so are BrowsingContexts. The
+// major difference is that BrowsingContexts are replicated (synced)
+// to the parent process, making it possible to traverse the
+// BrowsingContext tree for a tab, in both the parent and the child
+// process.
+class BrowsingContext
+  : public SupportsWeakPtr<BrowsingContext>
+  , public LinkedListElement<RefPtr<BrowsingContext>>
+{
+public:
+  static void Init();
+  static LogModule* GetLog();
+  static void CleanupContexts(uint64_t aProcessId);
+
+  static already_AddRefed<BrowsingContext> Get(uint64_t aId);
+
+  // Create a new BrowsingContext for 'aDocShell'. The id will be
+  // generated so that it is unique across all content child processes
+  // and the content parent process.
+  explicit BrowsingContext(nsIDocShell* aDocShell);
+  // Create a BrowsingContext for a particular BrowsingContext id, in
+  // the case where the id is known beforehand and a nsDocShell isn't
+  // needed (e.g. when creating BrowsingContexts in the parent
+  // process).
+  BrowsingContext(uint64_t aBrowsingContextId,
+                  const nsAString& aName,
+                  const Maybe<uint64_t>& aProcessId = Nothing());
+
+  // Attach the current BrowsingContext to its parent, in both the
+  // child and the parent process. If 'aParent' is null, 'this' is
+  // taken to be a root BrowsingContext.
+  void Attach(BrowsingContext* aParent);
+
+  // Detach the current BrowsingContext from its parent, in both the
+  // child and the parent process.
+  void Detach();
+
+  // Remove all children from the current BrowsingContext and cache
+  // them to allow them to be attached again.
+  void CacheChildren();
+
+  bool IsCached();
+
+  void SetName(const nsAString& aName) { mName = aName; }
+  void GetName(nsAString& aName) { aName = mName; }
+  bool NameEquals(const nsAString& aName) { return mName.Equals(aName); }
+
+  uint64_t Id() const { return mBrowsingContextId; }
+  uint64_t OwnerProcessId() const;
+  bool IsOwnedByProcess() const { return mProcessId.isSome(); }
+
+  BrowsingContext* Parent() const { return mParent; }
+
+  MOZ_DECLARE_WEAKREFERENCE_TYPENAME(BrowsingContext)
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(BrowsingContext)
+  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(BrowsingContext)
+
+  using Children = AutoCleanLinkedList<RefPtr<BrowsingContext>>;
+
+private:
+  ~BrowsingContext();
+
+  const uint64_t mBrowsingContextId;
+
+  // Indicates which process owns the docshell. Only valid in the
+  // parent process.
+  Maybe<uint64_t> mProcessId;
+
+  WeakPtr<BrowsingContext> mParent;
+  Children mChildren;
+  nsCOMPtr<nsIDocShell> mDocShell;
+  nsString mName;
+};
+
+} // namespace dom
+} // namespace mozilla
+#endif
--- a/docshell/base/moz.build
+++ b/docshell/base/moz.build
@@ -72,17 +72,22 @@ EXPORTS += [
     'SerializedLoadContext.h',
 ]
 
 EXPORTS.mozilla += [
     'IHistory.h',
     'LoadContext.h',
 ]
 
+EXPORTS.mozilla.dom += [
+    'BrowsingContext.h',
+]
+
 UNIFIED_SOURCES += [
+    'BrowsingContext.cpp',
     'LoadContext.cpp',
     'nsAboutRedirector.cpp',
     'nsDefaultURIFixup.cpp',
     'nsDocShell.cpp',
     'nsDocShellEditorData.cpp',
     'nsDocShellEnumerator.cpp',
     'nsDocShellLoadInfo.cpp',
     'nsDocShellTreeOwner.cpp',
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -470,16 +470,18 @@ nsDocShell::Init()
   // ref to us...  use an InterfaceRequestorProxy to do this.
   nsCOMPtr<nsIInterfaceRequestor> proxy =
     new InterfaceRequestorProxy(static_cast<nsIInterfaceRequestor*>(this));
   mLoadGroup->SetNotificationCallbacks(proxy);
 
   rv = nsDocLoader::AddDocLoaderAsChildOfRoot(this);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  mBrowsingContext = new BrowsingContext(this);
+
   // Add as |this| a progress listener to itself.  A little weird, but
   // simpler than reproducing all the listener-notification logic in
   // overrides of the various methods via which nsDocLoader can be
   // notified.   Note that this holds an nsWeakPtr to ourselves, so it's ok.
   return AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT |
                                      nsIWebProgress::NOTIFY_STATE_NETWORK);
 }
 
@@ -500,17 +502,18 @@ nsDocShell::DestroyChildren()
   nsDocLoader::DestroyChildren();
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsDocShell,
                                    nsDocLoader,
                                    mSessionStorageManager,
                                    mScriptGlobal,
                                    mInitialClientSource,
-                                   mSessionHistory)
+                                   mSessionHistory,
+                                   mBrowsingContext)
 
 NS_IMPL_ADDREF_INHERITED(nsDocShell, nsDocLoader)
 NS_IMPL_RELEASE_INHERITED(nsDocShell, nsDocLoader)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocShell)
   NS_INTERFACE_MAP_ENTRY(nsIDocShell)
   NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeItem)
   NS_INTERFACE_MAP_ENTRY(nsIWebNavigation)
@@ -2534,32 +2537,32 @@ nsDocShell::NotifyScrollObservers()
 
 //*****************************************************************************
 // nsDocShell::nsIDocShellTreeItem
 //*****************************************************************************
 
 NS_IMETHODIMP
 nsDocShell::GetName(nsAString& aName)
 {
-  aName = mName;
+  mBrowsingContext->GetName(aName);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::SetName(const nsAString& aName)
 {
-  mName = aName;
+  mBrowsingContext->SetName(aName);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::NameEquals(const nsAString& aName, bool* aResult)
 {
   NS_ENSURE_ARG_POINTER(aResult);
-  *aResult = mName.Equals(aName);
+  *aResult = mBrowsingContext->NameEquals(aName);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::GetCustomUserAgent(nsAString& aCustomUserAgent)
 {
   aCustomUserAgent = mCustomUserAgent;
   return NS_OK;
@@ -2846,16 +2849,17 @@ nsDocShell::SetDocLoaderParent(nsDocLoad
   // Curse ambiguous nsISupports inheritance!
   nsISupports* parent = GetAsSupports(aParent);
 
   // If parent is another docshell, we inherit all their flags for
   // allowing plugins, scripting etc.
   bool value;
   nsString customUserAgent;
   nsCOMPtr<nsIDocShell> parentAsDocShell(do_QueryInterface(parent));
+
   if (parentAsDocShell) {
     if (mAllowPlugins && NS_SUCCEEDED(parentAsDocShell->GetAllowPlugins(&value))) {
       SetAllowPlugins(value);
     }
     if (mAllowJavascript && NS_SUCCEEDED(parentAsDocShell->GetAllowJavascript(&value))) {
       SetAllowJavascript(value);
     }
     if (mAllowMetaRedirects && NS_SUCCEEDED(parentAsDocShell->GetAllowMetaRedirects(&value))) {
@@ -3254,17 +3258,17 @@ nsDocShell::AssertOriginAttributesMatchP
 nsresult
 nsDocShell::DoFindItemWithName(const nsAString& aName,
                                nsIDocShellTreeItem* aRequestor,
                                nsIDocShellTreeItem* aOriginalRequestor,
                                bool aSkipTabGroup,
                                nsIDocShellTreeItem** aResult)
 {
   // First we check our name.
-  if (mName.Equals(aName) && ItemIsActive(this) &&
+  if (mBrowsingContext->NameEquals(aName) && ItemIsActive(this) &&
       CanAccessItem(this, aOriginalRequestor)) {
     NS_ADDREF(*aResult = this);
     return NS_OK;
   }
 
   // Second we check our children making sure not to ask a child if
   // it is the aRequestor.
 #ifdef DEBUG
@@ -3545,16 +3549,18 @@ nsDocShell::AddChild(nsIDocShellTreeItem
 
   aChild->SetTreeOwner(mTreeOwner);
 
   nsCOMPtr<nsIDocShell> childAsDocShell(do_QueryInterface(aChild));
   if (!childAsDocShell) {
     return NS_OK;
   }
 
+  childAsDocShell->AttachBrowsingContext(this);
+
   // charset, style-disabling, and zoom will be inherited in SetupNewViewer()
 
   // Now take this document's charset and set the child's parentCharset field
   // to it. We'll later use that field, in the loading process, for the
   // charset choosing algorithm.
   // If we fail, at any point, we just return NS_OK.
   // This code has some performance impact. But this will be reduced when
   // the current charset will finally be stored as an Atom, avoiding the
@@ -3607,16 +3613,21 @@ nsDocShell::RemoveChild(nsIDocShellTreeI
   NS_ENSURE_ARG_POINTER(aChild);
 
   RefPtr<nsDocLoader> childAsDocLoader = GetAsDocLoader(aChild);
   NS_ENSURE_TRUE(childAsDocLoader, NS_ERROR_UNEXPECTED);
 
   nsresult rv = RemoveChildLoader(childAsDocLoader);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  nsCOMPtr<nsIDocShell> childAsDocShell(do_QueryInterface(aChild));
+  if (childAsDocShell) {
+    childAsDocShell->DetachBrowsingContext();
+  }
+
   aChild->SetTreeOwner(nullptr);
 
   return nsDocLoader::AddDocLoaderAsChildOfRoot(childAsDocLoader);
 }
 
 NS_IMETHODIMP
 nsDocShell::GetChildAt(int32_t aIndex, nsIDocShellTreeItem** aChild)
 {
@@ -5285,16 +5296,18 @@ nsDocShell::Destroy()
   if (mSessionHistory) {
     // We want to destroy these content viewers now rather than
     // letting their destruction wait for the session history
     // entries to get garbage collected.  (Bug 488394)
     mSessionHistory->EvictLocalContentViewers();
     mSessionHistory = nullptr;
   }
 
+  mBrowsingContext->Detach();
+
   SetTreeOwner(nullptr);
 
   mOnePermittedSandboxedNavigator = nullptr;
 
   // required to break ref cycle
   mSecurityUI = nullptr;
 
   // Cancel any timers that were set for this docshell; this is needed
@@ -7745,16 +7758,18 @@ nsDocShell::CaptureState()
   uint32_t childCount = mChildList.Length();
   for (uint32_t i = 0; i < childCount; ++i) {
     nsCOMPtr<nsIDocShellTreeItem> childShell = do_QueryInterface(ChildAt(i));
     NS_ASSERTION(childShell, "null child shell");
 
     mOSHE->AddChildShell(childShell);
   }
 
+  mBrowsingContext->CacheChildren();
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::RestorePresentationEvent::Run()
 {
   if (mDocShell && NS_FAILED(mDocShell->RestoreFromHistory())) {
     NS_WARNING("RestoreFromHistory failed");
@@ -14218,8 +14233,36 @@ nsDocShell::GetColorMatrix(uint32_t* aMa
   return NS_OK;
 }
 
 bool
 nsDocShell::IsForceReloading()
 {
   return IsForceReloadType(mLoadType);
 }
+
+already_AddRefed<BrowsingContext>
+nsDocShell::GetBrowsingContext() const
+{
+  RefPtr<BrowsingContext> browsingContext = mBrowsingContext;
+  return browsingContext.forget();
+}
+
+void
+nsIDocShell::AttachBrowsingContext(nsIDocShell* aParentDocShell)
+{
+  RefPtr<BrowsingContext> childContext =
+    nsDocShell::Cast(this)->GetBrowsingContext();
+  RefPtr<BrowsingContext> parentContext;
+  if (aParentDocShell) {
+    parentContext =
+      nsDocShell::Cast(aParentDocShell)->GetBrowsingContext();
+  }
+  childContext->Attach(parentContext);
+}
+
+void
+nsIDocShell::DetachBrowsingContext()
+{
+  RefPtr<BrowsingContext> browsingContext =
+    nsDocShell::Cast(this)->GetBrowsingContext();
+  browsingContext->Detach();
+}
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -10,16 +10,17 @@
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Move.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
 
+#include "mozilla/dom/BrowsingContext.h"
 #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
 #include "mozilla/gfx/Matrix.h"
 #include "mozilla/dom/ChildSHistory.h"
 
 #include "nsIAuthPromptProvider.h"
 #include "nsIBaseWindow.h"
 #include "nsIClipboardCommands.h"
 #include "nsIDeprecationWarner.h"
@@ -374,16 +375,19 @@ public:
   {
     return static_cast<nsDocShell*>(aDocShell);
   }
 
   // Returns true if the current load is a force reload (started by holding
   // shift while triggering reload)
   bool IsForceReloading();
 
+  already_AddRefed<mozilla::dom::BrowsingContext>
+  GetBrowsingContext() const;
+
 private: // member functions
   friend class nsDSURIContentListener;
   friend class FramingChecker;
   friend class OnLinkClickEvent;
 
   // It is necessary to allow adding a timeline marker wherever a docshell
   // instance is available. This operation happens frequently and needs to
   // be very fast, so instead of using a Map or having to search for some
@@ -902,17 +906,16 @@ private: // data members
   static bool sUseErrorPages;
 
 #ifdef DEBUG
   // We're counting the number of |nsDocShells| to help find leaks
   static unsigned long gNumberOfDocShells;
 #endif /* DEBUG */
 
   nsID mHistoryID;
-  nsString mName;
   nsString mTitle;
   nsString mCustomUserAgent;
   nsCString mOriginalUriString;
   nsWeakPtr mOnePermittedSandboxedNavigator;
   nsWeakPtr mOpener;
   nsTObserverArray<nsWeakPtr> mPrivacyObservers;
   nsTObserverArray<nsWeakPtr> mReflowObservers;
   nsTObserverArray<nsWeakPtr> mScrollObservers;
@@ -927,16 +930,17 @@ private: // data members
   nsCOMPtr<nsIMutableArray> mRefreshURIList;
   nsCOMPtr<nsIMutableArray> mSavedRefreshURIList;
   nsCOMPtr<nsIDOMStorageManager> mSessionStorageManager;
   nsCOMPtr<nsIContentViewer> mContentViewer;
   nsCOMPtr<nsIWidget> mParentWidget;
   RefPtr<mozilla::dom::ChildSHistory> mSessionHistory;
   nsCOMPtr<nsIWebBrowserFind> mFind;
   nsCOMPtr<nsICommandManager> mCommandManager;
+  RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
 
   // Dimensions of the docshell
   nsIntRect mBounds;
 
   /**
    * Content-Type Hint of the most-recently initiated load. Used for
    * session history entries.
    */
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -17,16 +17,17 @@
 #include "nsIURI.h"
 class nsPresContext;
 class nsIPresShell;
 class nsDocShellLoadInfo;
 namespace mozilla {
 class Encoding;
 class HTMLEditor;
 namespace dom {
+class BrowsingContext;
 class ClientSource;
 } // namespace dom
 }
 %}
 
 /**
  * The nsIDocShell interface.
  */
@@ -1175,16 +1176,19 @@ interface nsIDocShell : nsIDocShellTreeI
 
 %{C++
   /**
    * These methods call nsDocShell::GetHTMLEditorInternal() and
    * nsDocShell::SetHTMLEditorInternal() with static_cast.
    */
   mozilla::HTMLEditor* GetHTMLEditor();
   nsresult SetHTMLEditor(mozilla::HTMLEditor* aHTMLEditor);
+
+  void AttachBrowsingContext(nsIDocShell* aParentDocShell);
+  void DetachBrowsingContext();
 %}
 
   /**
    * Allowed CSS display modes. This needs to be kept in
    * sync with similar values in nsStyleConsts.h
    */
   const unsigned long DISPLAY_MODE_BROWSER = 0;
   const unsigned long DISPLAY_MODE_MINIMAL_UI = 1;
--- a/docshell/build/nsDocShellModule.cpp
+++ b/docshell/build/nsDocShellModule.cpp
@@ -2,16 +2,18 @@
 /* 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 "mozilla/ModuleUtils.h"
 #include "nsDocShellCID.h"
 
+#include "mozilla/dom/BrowsingContext.h"
+
 #include "nsDocShell.h"
 #include "nsDefaultURIFixup.h"
 #include "nsWebNavigationInfo.h"
 #include "nsAboutRedirector.h"
 #include "nsCDefaultURIFixup.h"
 
 // uriloader
 #include "nsURILoader.h"
@@ -48,16 +50,17 @@ static nsresult
 Initialize()
 {
   MOZ_ASSERT(!gInitialized, "docshell module already initialized");
   if (gInitialized) {
     return NS_OK;
   }
   gInitialized = true;
 
+  mozilla::dom::BrowsingContext::Init();
   nsresult rv = nsSHistory::Startup();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 static void
 Shutdown()
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1173,61 +1173,47 @@ Navigator::GetMediaDevices(ErrorResult& 
 
 void
 Navigator::MozGetUserMedia(const MediaStreamConstraints& aConstraints,
                            NavigatorUserMediaSuccessCallback& aOnSuccess,
                            NavigatorUserMediaErrorCallback& aOnError,
                            CallerType aCallerType,
                            ErrorResult& aRv)
 {
-  CallbackObjectHolder<NavigatorUserMediaSuccessCallback,
-                       nsIDOMGetUserMediaSuccessCallback> holder1(&aOnSuccess);
-  nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onsuccess =
-    holder1.ToXPCOMCallback();
-
-  CallbackObjectHolder<NavigatorUserMediaErrorCallback,
-                       nsIDOMGetUserMediaErrorCallback> holder2(&aOnError);
-  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onerror = holder2.ToXPCOMCallback();
-
   if (!mWindow || !mWindow->GetOuterWindow() ||
       mWindow->GetOuterWindow()->GetCurrentInnerWindow() != mWindow) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return;
   }
 
+  MediaManager::GetUserMediaSuccessCallback onsuccess(&aOnSuccess);
+  MediaManager::GetUserMediaErrorCallback onerror(&aOnError);
+
   MediaManager* manager = MediaManager::Get();
-  aRv = manager->GetUserMedia(mWindow, aConstraints, onsuccess, onerror,
-                              aCallerType);
+  aRv = manager->GetUserMedia(mWindow, aConstraints, std::move(onsuccess),
+                              std::move(onerror), aCallerType);
 }
 
 void
 Navigator::MozGetUserMediaDevices(const MediaStreamConstraints& aConstraints,
                                   MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
                                   NavigatorUserMediaErrorCallback& aOnError,
                                   uint64_t aInnerWindowID,
                                   const nsAString& aCallID,
                                   ErrorResult& aRv)
 {
-  CallbackObjectHolder<MozGetUserMediaDevicesSuccessCallback,
-                       nsIGetUserMediaDevicesSuccessCallback> holder1(&aOnSuccess);
-  nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onsuccess =
-    holder1.ToXPCOMCallback();
-
-  CallbackObjectHolder<NavigatorUserMediaErrorCallback,
-                       nsIDOMGetUserMediaErrorCallback> holder2(&aOnError);
-  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onerror = holder2.ToXPCOMCallback();
-
   if (!mWindow || !mWindow->GetOuterWindow() ||
       mWindow->GetOuterWindow()->GetCurrentInnerWindow() != mWindow) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return;
   }
 
   MediaManager* manager = MediaManager::Get();
-  aRv = manager->GetUserMediaDevices(mWindow, aConstraints, onsuccess, onerror,
+  // XXXbz aOnError seems to be unused?
+  aRv = manager->GetUserMediaDevices(mWindow, aConstraints, aOnSuccess,
                                      aInnerWindowID, aCallID);
 }
 
 //*****************************************************************************
 //    Navigator::nsINavigatorBattery
 //*****************************************************************************
 
 Promise*
--- a/dom/base/NodeUbiReporting.cpp
+++ b/dom/base/NodeUbiReporting.cpp
@@ -59,16 +59,22 @@ JS::ubi::Concrete<nsINode>::size(mozilla
   AutoSuppressGCAnalysis suppress;
   mozilla::SizeOfState sz(mallocSizeOf);
   nsWindowSizes wn(sz);
   size_t n = 0;
   get().AddSizeOfIncludingThis(wn, &n);
   return n;
 }
 
+const char16_t*
+JS::ubi::Concrete<nsINode>::descriptiveTypeName() const
+{
+  return get().NodeName().get();
+}
+
 JS::ubi::Node::Size
 JS::ubi::Concrete<nsIDocument>::size(mozilla::MallocSizeOf mallocSizeOf) const
 {
   AutoSuppressGCAnalysis suppress;
   mozilla::SizeOfState sz(mallocSizeOf);
   nsWindowSizes wn(sz);
   getDoc().DocAddSizeOfIncludingThis(wn);
   return wn.getTotalSize();
--- a/dom/base/NodeUbiReporting.h
+++ b/dom/base/NodeUbiReporting.h
@@ -31,17 +31,18 @@ protected:
   explicit Concrete(nsINode *ptr) : Base(ptr) { }
 
 public:
   static void construct(void *storage, nsINode *ptr);
   Size size(mozilla::MallocSizeOf mallocSizeOf) const override;
   js::UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override;
 
   nsINode& get() const { return *static_cast<nsINode*>(ptr); }
-  CoarseType coarseType() const final { return CoarseType::Other; }
+  CoarseType coarseType() const final { return CoarseType::DOMNode; }
+  const char16_t* descriptiveTypeName() const override;
 };
 
 template<>
 class Concrete<nsIContent> : public Concrete<nsINode>
 {
 protected:
   explicit Concrete(nsIContent *ptr) : Concrete<nsINode>(ptr) { }
 
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -10795,38 +10795,54 @@ nsContentUtils::IsLocalRefURL(const nsSt
 }
 
 /* static */ bool
 nsContentUtils::IsLocalRefURL(const nsACString& aString)
 {
   return ::IsLocalRefURL(aString);
 }
 
-// Tab ID is composed in a similar manner of Window ID.
-static uint64_t gNextTabId = 0;
-static const uint64_t kTabIdProcessBits = 32;
-static const uint64_t kTabIdTabBits = 64 - kTabIdProcessBits;
+static const uint64_t kIdProcessBits = 32;
+static const uint64_t kIdBits = 64 - kIdProcessBits;
 
 /* static */ uint64_t
-nsContentUtils::GenerateTabId()
+GenerateProcessSpecificId(uint64_t aId)
 {
   uint64_t processId = 0;
   if (XRE_IsContentProcess()) {
     ContentChild* cc = ContentChild::GetSingleton();
     processId = cc->GetID();
   }
 
-  MOZ_RELEASE_ASSERT(processId < (uint64_t(1) << kTabIdProcessBits));
-  uint64_t processBits = processId & ((uint64_t(1) << kTabIdProcessBits) - 1);
-
-  uint64_t tabId = ++gNextTabId;
-  MOZ_RELEASE_ASSERT(tabId < (uint64_t(1) << kTabIdTabBits));
-  uint64_t tabBits = tabId & ((uint64_t(1) << kTabIdTabBits) - 1);
-
-  return (processBits << kTabIdTabBits) | tabBits;
+  MOZ_RELEASE_ASSERT(processId < (uint64_t(1) << kIdProcessBits));
+  uint64_t processBits = processId & ((uint64_t(1) << kIdProcessBits) - 1);
+
+  uint64_t id = aId;
+  MOZ_RELEASE_ASSERT(id < (uint64_t(1) << kIdBits));
+  uint64_t bits = id & ((uint64_t(1) << kIdBits) - 1);
+
+  return (processBits << kIdBits) | bits;
+}
+
+// Tab ID is composed in a similar manner of Window ID.
+static uint64_t gNextTabId = 0;
+
+/* static */ uint64_t
+nsContentUtils::GenerateTabId()
+{
+  return GenerateProcessSpecificId(++gNextTabId);
+}
+
+// Browsing context ID is composed in a similar manner of Window ID.
+static uint64_t gNextBrowsingContextId = 0;
+
+/* static */ uint64_t
+nsContentUtils::GenerateBrowsingContextId()
+{
+  return GenerateProcessSpecificId(++gNextBrowsingContextId);
 }
 
 /* static */ bool
 nsContentUtils::GetUserIsInteracting()
 {
   return UserInteractionObserver::sUserActive;
 }
 
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -3162,16 +3162,22 @@ public:
   IsCustomElementsEnabled() { return sIsCustomElementsEnabled; }
 
   /**
    * Compose a tab id with process id and a serial number.
    */
   static uint64_t GenerateTabId();
 
   /**
+   * Generate an id for a BrowsingContext using a range of serial
+   * numbers reserved for the current process.
+   */
+  static uint64_t GenerateBrowsingContextId();
+
+  /**
    * Check whether we should skip moving the cursor for a same-value .value set
    * on a text input or textarea.
    */
   static bool
   SkipCursorMoveForSameValueSet() { return sSkipCursorMoveForSameValueSet; }
 
   /**
    * Determine whether or not the user is currently interacting with the web
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -684,16 +684,18 @@ nsFrameLoader::AddTreeItemToTreeOwner(ns
     // content.
 
     aItem->SetItemType(aParentType);
   }
 
   // Now that we have our type set, add ourselves to the parent, as needed.
   if (aParentNode) {
     aParentNode->AddChild(aItem);
+  } else if (nsCOMPtr<nsIDocShell> childAsDocShell = do_QueryInterface(aItem)) {
+    childAsDocShell->AttachBrowsingContext(aParentNode);
   }
 
   bool retval = false;
   if (aParentType == nsIDocShellTreeItem::typeChrome && isContent) {
     retval = true;
 
     bool is_primary =
       mOwnerContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::primary,
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -428,21 +428,35 @@ public:
   {
     UnlinkSelf();
     mPtrBits = aOther.mPtrBits;
     aOther.mPtrBits = 0;
   }
 
   void operator=(const CallbackObjectHolder& aOther) = delete;
 
+  void Reset()
+  {
+    UnlinkSelf();
+  }
+
   nsISupports* GetISupports() const
   {
     return reinterpret_cast<nsISupports*>(mPtrBits & ~XPCOMCallbackFlag);
   }
 
+  already_AddRefed<nsISupports> Forget()
+  {
+    // This can be called from random threads.  Make sure to not refcount things
+    // in here!
+    nsISupports* supp = GetISupports();
+    mPtrBits = 0;
+    return dont_AddRef(supp);
+  }
+
   // Boolean conversion operator so people can use this in boolean tests
   explicit operator bool() const
   {
     return GetISupports();
   }
 
   CallbackObjectHolder Clone() const
   {
--- a/dom/bindings/WebIDLGlobalNameHash.cpp
+++ b/dom/bindings/WebIDLGlobalNameHash.cpp
@@ -6,16 +6,17 @@
 
 #include "WebIDLGlobalNameHash.h"
 #include "js/Class.h"
 #include "js/GCAPI.h"
 #include "js/Id.h"
 #include "js/Wrapper.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/HashFunctions.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/dom/DOMJSClass.h"
 #include "mozilla/dom/DOMJSProxyHandler.h"
 #include "mozilla/dom/JSSlots.h"
 #include "mozilla/dom/PrototypeList.h"
 #include "mozilla/dom/RegisterBindings.h"
@@ -87,17 +88,17 @@ struct WebIDLNameTableEntry : public PLD
   {
     if (mNameLength != aKey->mLength) {
       return false;
     }
 
     const char* name = WebIDLGlobalNameHash::sNames + mNameOffset;
 
     if (aKey->mLatin1String) {
-      return PodEqual(aKey->mLatin1String, name, aKey->mLength);
+      return ArrayEqual(aKey->mLatin1String, name, aKey->mLength);
     }
 
     return nsCharTraits<char16_t>::compareASCII(aKey->mTwoBytesString, name,
                                                 aKey->mLength) == 0;
   }
 
   static KeyTypePointer KeyToPointer(KeyType aKey)
   {
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -31,16 +31,17 @@
 #endif
 #include "mozilla/AntiTrackingCommon.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "mozilla/DataStorage.h"
 #include "mozilla/devtools/HeapSnapshotTempFileHelperParent.h"
 #include "mozilla/docshell/OfflineCacheUpdateParent.h"
+#include "mozilla/dom/BrowsingContext.h"
 #include "mozilla/dom/ClientManager.h"
 #include "mozilla/dom/ClientOpenWindowOpActors.h"
 #include "mozilla/dom/DataTransfer.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FileCreatorHelper.h"
 #include "mozilla/dom/FileSystemSecurity.h"
 #include "mozilla/dom/IPCBlobUtils.h"
@@ -1890,16 +1891,18 @@ ContentParent::ActorDestroy(ActorDestroy
     BlobURLProtocolHandler::RemoveDataEntry(mBlobURLs[i]);
   }
 
   mBlobURLs.Clear();
 
 #if defined(XP_WIN32) && defined(ACCESSIBILITY)
   a11y::AccessibleWrap::ReleaseContentProcessIdFor(ChildID());
 #endif
+
+  BrowsingContext::CleanupContexts(ChildID());
 }
 
 bool
 ContentParent::TryToRecycle()
 {
   // This life time check should be replaced by a memory health check (memory usage + fragmentation).
   const double kMaxLifeSpan = 5;
   if (mShutdownPending ||
@@ -5972,8 +5975,119 @@ ContentParent::RecvFirstPartyStorageAcce
                                                            FirstPartyStorageAccessGrantedForOriginResolver&& aResolver)
 {
   AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(aParentPrincipal,
                                                                                  aTrackingOrigin,
                                                                                  aGrantedOrigin,
                                                                                  std::move(aResolver));
   return IPC_OK();
 }
+
+mozilla::ipc::IPCResult
+ContentParent::RecvAttachBrowsingContext(
+  const BrowsingContextId& aParentId,
+  const BrowsingContextId& aChildId,
+  const nsString& aName)
+{
+  RefPtr<BrowsingContext> parent = BrowsingContext::Get(aParentId);
+  if (aParentId && !parent) {
+    // Unless 'aParentId' is 0 (which it is when the child is a root
+    // BrowsingContext) there should always be a corresponding
+    // 'parent'. The only reason for there not beeing one is if the
+    // parent has already been detached, in which case the
+    // BrowsingContext that tries to attach itself to the context with
+    // 'aParentId' is surely doomed and we can safely do nothing.
+
+    // TODO(farre): When we start syncing/moving BrowsingContexts to
+    // other child processes is it possible to get into races where
+    // constructive operations on already detached BrowsingContexts
+    // are requested? This needs to be answered/handled, but for now
+    // return early. [Bug 1471598]
+    MOZ_LOG(
+      BrowsingContext::GetLog(),
+      LogLevel::Debug,
+      ("ParentIPC: Trying to attach to already detached parent 0x%08" PRIx64,
+       (uint64_t)aParentId));
+    return IPC_OK();
+  }
+
+  if (parent && parent->OwnerProcessId() != ChildID()) {
+    // Where trying attach a child BrowsingContext to a parent
+    // BrowsingContext in another process. This is illegal since the
+    // only thing that could create that child BrowsingContext is a
+    // parent docshell in the same process as that BrowsingContext.
+
+    // TODO(farre): We're doing nothing now, but is that exactly what
+    // we want? Maybe we want to crash the child currently calling
+    // SendAttachBrowsingContext and/or the child that originally
+    // called SendAttachBrowsingContext or possibly all children that
+    // has a BrowsingContext connected to the child that currently
+    // called SendAttachBrowsingContext? [Bug 1471598]
+    MOZ_LOG(BrowsingContext::GetLog(),
+            LogLevel::Warning,
+            ("ParentIPC: Trying to attach to out of process parent context "
+             "0x%08" PRIx64,
+             parent->Id()));
+    return IPC_OK();
+  }
+
+  RefPtr<BrowsingContext> child = BrowsingContext::Get(aChildId);
+  if (child && !child->IsCached()) {
+    // This is highly suspicious. BrowsingContexts should only be
+    // attached at most once, but finding one indicates that someone
+    // is doing something they shouldn't.
+
+    // TODO(farre): To crash or not to crash. Same reasoning as in
+    // above TODO. [Bug 1471598]
+    MOZ_LOG(BrowsingContext::GetLog(),
+            LogLevel::Warning,
+            ("ParentIPC: Trying to attach already attached 0x%08" PRIx64
+             " to 0x%08" PRIx64,
+             child->Id(),
+             (uint64_t)aParentId));
+    return IPC_OK();
+  }
+
+  if (!child) {
+    child = new BrowsingContext(aChildId, aName, Some(ChildID()));
+  }
+  child->Attach(parent);
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ContentParent::RecvDetachBrowsingContext(const BrowsingContextId& aContextId,
+                                         const bool& aMoveToBFCache)
+{
+  RefPtr<BrowsingContext> context = BrowsingContext::Get(aContextId);
+
+  if (!context) {
+    MOZ_LOG(BrowsingContext::GetLog(),
+            LogLevel::Debug,
+            ("ParentIPC: Trying to detach already detached 0x%08" PRIx64,
+             (uint64_t)aContextId));
+    return IPC_OK();
+  }
+
+  if (context->OwnerProcessId() != ChildID()) {
+    // Where trying to detach a child BrowsingContext in another child
+    // process. This is illegal since the owner of the BrowsingContext
+    // is the proccess with the in-process docshell, which is tracked
+    // by OwnerProcessId.
+
+    // TODO(farre): To crash or not to crash. Same reasoning as in
+    // above TODO. [Bug 1471598]
+    MOZ_LOG(BrowsingContext::GetLog(),
+            LogLevel::Warning,
+            ("ParentIPC: Trying to detach out of process context 0x%08" PRIx64,
+             context->Id()));
+    return IPC_OK();
+  }
+
+  if (aMoveToBFCache) {
+    context->CacheChildren();
+  } else {
+    context->Detach();
+  }
+
+  return IPC_OK();
+}
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -677,16 +677,25 @@ public:
   void SetInputPriorityEventEnabled(bool aEnabled);
   bool IsInputPriorityEventEnabled()
   {
     return mIsInputPriorityEventEnabled;
   }
 
   static bool IsInputEventQueueSupported();
 
+  virtual mozilla::ipc::IPCResult RecvAttachBrowsingContext(
+    const BrowsingContextId& aParentContextId,
+    const BrowsingContextId& aContextId,
+    const nsString& aName) override;
+
+  virtual mozilla::ipc::IPCResult RecvDetachBrowsingContext(
+    const BrowsingContextId& aContextId,
+    const bool& aMoveToBFCache) override;
+
 protected:
   void OnChannelConnected(int32_t pid) override;
 
   virtual void ActorDestroy(ActorDestroyReason why) override;
 
   bool ShouldContinueFromReplyTimeout() override;
 
   void OnVarChanged(const GfxVarUpdate& aVar) override;
--- a/dom/ipc/IdType.h
+++ b/dom/ipc/IdType.h
@@ -10,20 +10,20 @@
 #include "ipc/IPCMessageUtils.h"
 
 namespace IPC {
 template<typename T> struct ParamTraits;
 } // namespace IPC
 
 namespace mozilla {
 namespace dom {
+class BrowsingContext;
 class ContentParent;
 class TabParent;
 
-
 template<typename T>
 class IdType
 {
   friend struct IPC::ParamTraits<IdType<T>>;
 
 public:
   IdType() : mId(0) {}
   explicit IdType(uint64_t aId) : mId(aId) {}
@@ -41,17 +41,17 @@ public:
     return mId < rhs.mId;
   }
 private:
   uint64_t mId;
 };
 
 typedef IdType<TabParent> TabId;
 typedef IdType<ContentParent> ContentParentId;
-
+typedef IdType<BrowsingContext> BrowsingContextId;
 } // namespace dom
 } // namespace mozilla
 
 namespace IPC {
 
 template<typename T>
 struct ParamTraits<mozilla::dom::IdType<T>>
 {
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -95,16 +95,17 @@ using mozilla::Telemetry::HistogramAccum
 using mozilla::Telemetry::KeyedHistogramAccumulation from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::ScalarAction from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::DynamicScalarDefinition from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::ChildEventData from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::DiscardedData from "mozilla/TelemetryComms.h";
 using mozilla::CrossProcessMutexHandle from "mozilla/ipc/CrossProcessMutex.h";
 using refcounted class nsIInputStream from "mozilla/ipc/IPCStreamUtils.h";
+using mozilla::dom::BrowsingContextId from "mozilla/dom/ipc/IdType.h";
 
 union ChromeRegistryItem
 {
     ChromePackage;
     OverrideMapping;
     SubstitutionMapping;
 };
 
@@ -1172,16 +1173,45 @@ parent:
      * A 3rd party tracking origin (aTrackingOrigin) has received the permission
      * granted to have access to aGrantedOrigin when loaded by aParentPrincipal.
      */
     async FirstPartyStorageAccessGrantedForOrigin(Principal aParentPrincipal,
                                                   nsCString aTrackingOrigin,
                                                   nsCString aGrantedOrigin)
           returns (bool unused);
 
+    /**
+     * Sync the BrowsingContext with id 'aContextId' and name 'aName'
+     * to the parent, and attach it to the BrowsingContext with id
+     * 'aParentContextId'. If 'aParentContextId' is '0' the
+     * BrowsingContext is a root in the BrowsingContext
+     * tree. AttachBrowsingContext must only be called at most once
+     * for any child BrowsingContext, and only for BrowsingContexts
+     * where the parent and the child context contains their
+     * nsDocShell.
+     */
+    async AttachBrowsingContext(BrowsingContextId aParentContextId,
+                                BrowsingContextId aContextId,
+                                nsString aName);
+
+    /**
+     * Remove the synced BrowsingContext with id 'aContextId' from the
+     * parent. DetachBrowsingContext is only needed to be called once
+     * for any BrowsingContext, since detaching a node in the
+     * BrowsingContext detaches the entire sub-tree rooted at that
+     * node. Calling DetachBrowsingContext with an already detached
+     * BrowsingContext effectively does nothing. Note that it is not
+     * an error to call DetachBrowsingContext on a BrowsingContext
+     * belonging to an already detached subtree. The 'aMoveToBFCache'
+     * paramater controls if detaching a BrowsingContext should move
+     * it to the bfcache allowing it to be re-attached if navigated
+     * to.
+     */
+    async DetachBrowsingContext(BrowsingContextId aContextId,
+                                bool aMoveToBFCache);
 both:
      async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
                         Principal aPrincipal, ClonedMessageData aData);
 
     /**
      * Notify `push-subscription-modified` observers in the parent and child.
      */
     async NotifyPushSubscriptionModifiedObservers(nsCString scope,
--- a/dom/media/MediaDevices.cpp
+++ b/dom/media/MediaDevices.cpp
@@ -1,16 +1,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 "mozilla/dom/MediaDevices.h"
 #include "mozilla/dom/MediaStreamBinding.h"
 #include "mozilla/dom/MediaDeviceInfo.h"
 #include "mozilla/dom/MediaDevicesBinding.h"
+#include "mozilla/dom/NavigatorBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/MediaManager.h"
 #include "MediaTrackConstraints.h"
 #include "nsContentUtils.h"
 #include "nsIEventTarget.h"
 #include "nsINamed.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIPermissionManager.h"
@@ -177,21 +178,22 @@ NS_IMPL_ISUPPORTS(MediaDevices::GumRejec
 already_AddRefed<Promise>
 MediaDevices::GetUserMedia(const MediaStreamConstraints& aConstraints,
                            CallerType aCallerType,
                            ErrorResult &aRv)
 {
   RefPtr<Promise> p = Promise::Create(GetParentObject(), aRv);
   NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
 
-  RefPtr<GumResolver> resolver = new GumResolver(p);
-  RefPtr<GumRejecter> rejecter = new GumRejecter(p);
+  MediaManager::GetUserMediaSuccessCallback resolver(new GumResolver(p));
+  MediaManager::GetUserMediaErrorCallback rejecter(new GumRejecter(p));
 
   aRv = MediaManager::Get()->GetUserMedia(GetOwner(), aConstraints,
-                                          resolver, rejecter,
+                                          std::move(resolver),
+                                          std::move(rejecter),
                                           aCallerType);
   return p.forget();
 }
 
 already_AddRefed<Promise>
 MediaDevices::EnumerateDevices(CallerType aCallerType, ErrorResult &aRv)
 {
   RefPtr<Promise> p = Promise::Create(GetParentObject(), aRv);
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -94,16 +94,87 @@
 
 // XXX Workaround for bug 986974 to maintain the existing broken semantics
 template<>
 struct nsIMediaDevice::COMTypeInfo<mozilla::MediaDevice, void> {
   static const nsIID kIID;
 };
 const nsIID nsIMediaDevice::COMTypeInfo<mozilla::MediaDevice, void>::kIID = NS_IMEDIADEVICE_IID;
 
+// A specialization of nsMainThreadPtrHolder for
+// mozilla::dom::CallbackObjectHolder.  See documentation for
+// nsMainThreadPtrHolder in nsProxyRelease.h.  This specialization lets us avoid
+// wrapping the CallbackObjectHolder into a separate refcounted object.
+template<class WebIDLCallbackT, class XPCOMCallbackT>
+class nsMainThreadPtrHolder<mozilla::dom::CallbackObjectHolder<WebIDLCallbackT,
+                                                               XPCOMCallbackT>> final
+{
+  typedef mozilla::dom::CallbackObjectHolder<WebIDLCallbackT,
+                                             XPCOMCallbackT> Holder;
+public:
+  nsMainThreadPtrHolder(const char* aName, Holder&& aHolder)
+    : mHolder(std::move(aHolder))
+#ifndef RELEASE_OR_BETA
+    , mName(aName)
+#endif
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+private:
+  // We can be released on any thread.
+  ~nsMainThreadPtrHolder()
+  {
+    if (NS_IsMainThread()) {
+      mHolder.Reset();
+    } else if (mHolder.GetISupports()) {
+      nsCOMPtr<nsIEventTarget> target = do_GetMainThread();
+      MOZ_ASSERT(target);
+      NS_ProxyRelease(
+#ifdef RELEASE_OR_BETA
+        nullptr,
+#else
+        mName,
+#endif
+        target, mHolder.Forget());
+    }
+  }
+
+public:
+  Holder* get()
+  {
+    // Nobody should be touching the raw pointer off-main-thread.
+    if (MOZ_UNLIKELY(!NS_IsMainThread())) {
+      NS_ERROR("Can't dereference nsMainThreadPtrHolder off main thread");
+      MOZ_CRASH();
+    }
+    return &mHolder;
+  }
+
+  bool operator!() const
+  {
+    return !mHolder;
+  }
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsMainThreadPtrHolder<Holder>)
+
+private:
+  // Our holder.
+  Holder mHolder;
+
+#ifndef RELEASE_OR_BETA
+  const char* mName = nullptr;
+#endif
+
+  // Copy constructor and operator= not implemented. Once constructed, the
+  // holder is immutable.
+  Holder& operator=(const nsMainThreadPtrHolder& aOther) = delete;
+  nsMainThreadPtrHolder(const nsMainThreadPtrHolder& aOther) = delete;
+};
+
 namespace {
 already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() {
   nsCOMPtr<nsIAsyncShutdownService> svc = mozilla::services::GetAsyncShutdown();
   MOZ_RELEASE_ASSERT(svc);
 
   nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase;
   nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(shutdownPhase));
   if (!shutdownPhase) {
@@ -226,16 +297,40 @@ static uint16_t
 FromCaptureState(CaptureState aState)
 {
   MOZ_ASSERT(aState == CaptureState::Off ||
              aState == CaptureState::Enabled ||
              aState == CaptureState::Disabled);
   return static_cast<uint16_t>(aState);
 }
 
+static void
+CallOnError(MediaManager::GetUserMediaErrorCallback* aCallback,
+            MediaStreamError& aError)
+{
+  MOZ_ASSERT(aCallback);
+  if (aCallback->HasWebIDLCallback()) {
+    aCallback->GetWebIDLCallback()->Call(aError);
+  } else {
+    aCallback->GetXPCOMCallback()->OnError(&aError);
+  }
+}
+
+static void
+CallOnSuccess(MediaManager::GetUserMediaSuccessCallback* aCallback,
+              DOMMediaStream& aStream)
+{
+  MOZ_ASSERT(aCallback);
+  if (aCallback->HasWebIDLCallback()) {
+    aCallback->GetWebIDLCallback()->Call(aStream);
+  } else {
+    aCallback->GetXPCOMCallback()->OnSuccess(&aStream);
+  }
+}
+
 /**
  * SourceListener has threadsafe refcounting for use across the main, media and
  * MSG threads. But it has a non-threadsafe SupportsWeakPtr for WeakPtr usage
  * only from main thread, to ensure that garbage- and cycle-collected objects
  * don't hold a reference to it during late shutdown.
  *
  * There's also a hard reference to the SourceListener through its
  * SourceStreamListener and the MediaStreamGraph. MediaStreamGraph
@@ -771,17 +866,17 @@ private:
 };
 
 /**
  * Send an error back to content. Do this only on the main thread.
  */
 class ErrorCallbackRunnable : public Runnable
 {
 public:
-  ErrorCallbackRunnable(const nsMainThreadPtrHandle<nsIDOMGetUserMediaErrorCallback>& aOnFailure,
+  ErrorCallbackRunnable(const nsMainThreadPtrHandle<MediaManager::GetUserMediaErrorCallback>& aOnFailure,
                         MediaMgrError& aError,
                         uint64_t aWindowID)
     : Runnable("ErrorCallbackRunnable")
     , mOnFailure(aOnFailure)
     , mError(&aError)
     , mWindowID(aWindowID)
     , mManager(MediaManager::GetInstance())
   {
@@ -796,24 +891,24 @@ public:
     if (!(mManager->IsWindowStillActive(mWindowID))) {
       return NS_OK;
     }
     // This is safe since we're on main-thread, and the windowlist can only
     // be invalidated from the main-thread (see OnNavigation)
     if (auto* window = nsGlobalWindowInner::GetInnerWindowWithId(mWindowID)) {
       RefPtr<MediaStreamError> error =
         new MediaStreamError(window->AsInner(), *mError);
-      mOnFailure->OnError(error);
+      CallOnError(mOnFailure, *error);
     }
     return NS_OK;
   }
 private:
   ~ErrorCallbackRunnable() override = default;
 
-  nsMainThreadPtrHandle<nsIDOMGetUserMediaErrorCallback> mOnFailure;
+  nsMainThreadPtrHandle<MediaManager::GetUserMediaErrorCallback> mOnFailure;
   RefPtr<MediaMgrError> mError;
   uint64_t mWindowID;
   RefPtr<MediaManager> mManager; // get ref to this when creating the runnable
 };
 
 /**
  * nsIMediaDevice implementation.
  */
@@ -1159,18 +1254,18 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(FakeT
  * though that would complicate the constructors some.  Currently the
  * GetUserMedia spec does not allow for more than 2 streams to be obtained in
  * one call, to simplify handling of constraints.
  */
 class GetUserMediaStreamRunnable : public Runnable
 {
 public:
   GetUserMediaStreamRunnable(
-    const nsMainThreadPtrHandle<nsIDOMGetUserMediaSuccessCallback>& aOnSuccess,
-    const nsMainThreadPtrHandle<nsIDOMGetUserMediaErrorCallback>& aOnFailure,
+    const nsMainThreadPtrHandle<MediaManager::GetUserMediaSuccessCallback>& aOnSuccess,
+    const nsMainThreadPtrHandle<MediaManager::GetUserMediaErrorCallback>& aOnFailure,
     uint64_t aWindowID,
     GetUserMediaWindowListener* aWindowListener,
     SourceListener* aSourceListener,
     const ipc::PrincipalInfo& aPrincipalInfo,
     const MediaStreamConstraints& aConstraints,
     MediaDevice* aAudioDevice,
     MediaDevice* aVideoDevice,
     PeerIdentity* aPeerIdentity,
@@ -1191,17 +1286,17 @@ public:
   }
 
   ~GetUserMediaStreamRunnable() {}
 
   class TracksAvailableCallback : public OnTracksAvailableCallback
   {
   public:
     TracksAvailableCallback(MediaManager* aManager,
-                            const nsMainThreadPtrHandle<nsIDOMGetUserMediaSuccessCallback>& aSuccess,
+                            const nsMainThreadPtrHandle<MediaManager::GetUserMediaSuccessCallback>& aSuccess,
                             const RefPtr<GetUserMediaWindowListener>& aWindowListener,
                             DOMMediaStream* aStream)
       : mWindowListener(aWindowListener),
         mOnSuccess(aSuccess),
         mManager(aManager),
         mStream(aStream)
     {}
     void NotifyTracksAvailable(DOMMediaStream* aStream) override
@@ -1213,20 +1308,20 @@ public:
 
       // Start currentTime from the point where this stream was successfully
       // returned.
       aStream->SetLogicalStreamStartTime(aStream->GetPlaybackStream()->GetCurrentTime());
 
       // This is safe since we're on main-thread, and the windowlist can only
       // be invalidated from the main-thread (see OnNavigation)
       LOG(("Returning success for getUserMedia()"));
-      mOnSuccess->OnSuccess(aStream);
+      CallOnSuccess(mOnSuccess, *aStream);
     }
     RefPtr<GetUserMediaWindowListener> mWindowListener;
-    nsMainThreadPtrHandle<nsIDOMGetUserMediaSuccessCallback> mOnSuccess;
+    nsMainThreadPtrHandle<MediaManager::GetUserMediaSuccessCallback> mOnSuccess;
     RefPtr<MediaManager> mManager;
     // Keep the DOMMediaStream alive until the NotifyTracksAvailable callback
     // has fired, otherwise we might immediately destroy the DOMMediaStream and
     // shut down the underlying MediaStream prematurely.
     // This creates a cycle which is broken when NotifyTracksAvailable
     // is fired (which will happen unless the browser shuts down,
     // since we only add this callback when we've successfully appended
     // the desired tracks in the MediaStreamGraph) or when
@@ -1454,17 +1549,17 @@ public:
     if (!domStream || !stream || sHasShutdown) {
       LOG(("Returning error for getUserMedia() - no stream"));
 
       if (auto* window = nsGlobalWindowInner::GetInnerWindowWithId(mWindowID)) {
         RefPtr<MediaStreamError> error = new MediaStreamError(window->AsInner(),
             MediaStreamError::Name::AbortError,
             sHasShutdown ? NS_LITERAL_STRING("In shutdown") :
                            NS_LITERAL_STRING("No stream."));
-        mOnFailure->OnError(error);
+        CallOnError(mOnFailure, *error);
       }
       return NS_OK;
     }
 
     // Activate our source listener. We'll call Start() on the source when we
     // get a callback that the MediaStream has started consuming. The listener
     // is freed when the page is invalidated (on navigation or close).
     mWindowListener->Activate(mSourceListener, stream, mAudioDevice, mVideoDevice);
@@ -1506,32 +1601,32 @@ public:
         // Only run if the window is still active for our window listener.
         if (!(manager->IsWindowStillActive(windowID))) {
           return;
         }
         // This is safe since we're on main-thread, and the windowlist can only
         // be invalidated from the main-thread (see OnNavigation)
         if (auto* window = nsGlobalWindowInner::GetInnerWindowWithId(windowID)) {
           auto streamError = MakeRefPtr<MediaStreamError>(window->AsInner(), *error);
-          onFailure->OnError(streamError);
+          CallOnError(onFailure, *streamError);
         }
       });
 
     if (!IsPincipalInfoPrivate(mPrincipalInfo)) {
       // Call GetPrincipalKey again, this time w/persist = true, to promote
       // deviceIds to persistent, in case they're not already. Fire'n'forget.
       RefPtr<Pledge<nsCString>> p =
         media::GetPrincipalKey(mPrincipalInfo, true);
     }
     return NS_OK;
   }
 
 private:
-  nsMainThreadPtrHandle<nsIDOMGetUserMediaSuccessCallback> mOnSuccess;
-  nsMainThreadPtrHandle<nsIDOMGetUserMediaErrorCallback> mOnFailure;
+  nsMainThreadPtrHandle<MediaManager::GetUserMediaSuccessCallback> mOnSuccess;
+  nsMainThreadPtrHandle<MediaManager::GetUserMediaErrorCallback> mOnFailure;
   MediaStreamConstraints mConstraints;
   RefPtr<MediaDevice> mAudioDevice;
   RefPtr<MediaDevice> mVideoDevice;
   uint64_t mWindowID;
   RefPtr<GetUserMediaWindowListener> mWindowListener;
   RefPtr<SourceListener> mSourceListener;
   ipc::PrincipalInfo mPrincipalInfo;
   RefPtr<PeerIdentity> mPeerIdentity;
@@ -1667,18 +1762,18 @@ MediaManager::SelectSettings(
  * Do not run this on the main thread. The success and error callbacks *MUST*
  * be dispatched on the main thread!
  */
 class GetUserMediaTask : public Runnable
 {
 public:
   GetUserMediaTask(
     const MediaStreamConstraints& aConstraints,
-    const nsMainThreadPtrHandle<nsIDOMGetUserMediaSuccessCallback>& aOnSuccess,
-    const nsMainThreadPtrHandle<nsIDOMGetUserMediaErrorCallback>& aOnFailure,
+    const nsMainThreadPtrHandle<MediaManager::GetUserMediaSuccessCallback>& aOnSuccess,
+    const nsMainThreadPtrHandle<MediaManager::GetUserMediaErrorCallback>& aOnFailure,
     uint64_t aWindowID,
     GetUserMediaWindowListener* aWindowListener,
     SourceListener* aSourceListener,
     MediaEnginePrefs& aPrefs,
     const ipc::PrincipalInfo& aPrincipalInfo,
     bool aIsChrome,
     MediaManager::MediaDeviceSet* aMediaDeviceSet,
     bool aShouldFocusSource)
@@ -1817,17 +1912,17 @@ public:
     MOZ_ASSERT(mOnFailure);
 
     // We add a disabled listener to the StreamListeners array until accepted
     // If this was the only active MediaStream, remove the window from the list.
     if (NS_IsMainThread()) {
       if (auto* window = nsGlobalWindowInner::GetInnerWindowWithId(mWindowID)) {
         RefPtr<MediaStreamError> error = new MediaStreamError(window->AsInner(),
                                                               aName, aMessage);
-        mOnFailure->OnError(error);
+        CallOnError(mOnFailure, *error);
       }
       // Should happen *after* error runs for consistency, but may not matter
       mWindowListener->Remove(mSourceListener);
     } else {
       // This will re-check the window being alive on main-thread
       Fail(aName, aMessage);
     }
 
@@ -1867,18 +1962,18 @@ public:
   GetWindowID()
   {
     return mWindowID;
   }
 
 private:
   MediaStreamConstraints mConstraints;
 
-  nsMainThreadPtrHandle<nsIDOMGetUserMediaSuccessCallback> mOnSuccess;
-  nsMainThreadPtrHandle<nsIDOMGetUserMediaErrorCallback> mOnFailure;
+  nsMainThreadPtrHandle<MediaManager::GetUserMediaSuccessCallback> mOnSuccess;
+  nsMainThreadPtrHandle<MediaManager::GetUserMediaErrorCallback> mOnFailure;
   uint64_t mWindowID;
   RefPtr<GetUserMediaWindowListener> mWindowListener;
   RefPtr<SourceListener> mSourceListener;
   RefPtr<MediaDevice> mAudioDevice;
   RefPtr<MediaDevice> mVideoDevice;
   MediaEnginePrefs mPrefs;
   ipc::PrincipalInfo mPrincipalInfo;
   bool mIsChrome;
@@ -2492,59 +2587,59 @@ ReduceConstraint(
 /**
  * The entry point for this file. A call from Navigator::mozGetUserMedia
  * will end up here. MediaManager is a singleton that is responsible
  * for handling all incoming getUserMedia calls from every window.
  */
 nsresult
 MediaManager::GetUserMedia(nsPIDOMWindowInner* aWindow,
                            const MediaStreamConstraints& aConstraintsPassedIn,
-                           nsIDOMGetUserMediaSuccessCallback* aOnSuccess,
-                           nsIDOMGetUserMediaErrorCallback* aOnFailure,
+                           GetUserMediaSuccessCallback&& aOnSuccess,
+                           GetUserMediaErrorCallback&& aOnFailure,
                            dom::CallerType aCallerType)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aWindow);
-  MOZ_ASSERT(aOnFailure);
-  MOZ_ASSERT(aOnSuccess);
-  nsMainThreadPtrHandle<nsIDOMGetUserMediaSuccessCallback> onSuccess(
-      new nsMainThreadPtrHolder<nsIDOMGetUserMediaSuccessCallback>(
-          "GetUserMedia::SuccessCallback", aOnSuccess));
-  nsMainThreadPtrHandle<nsIDOMGetUserMediaErrorCallback> onFailure(
-      new nsMainThreadPtrHolder<nsIDOMGetUserMediaErrorCallback>(
-          "GetUserMedia::FailureCallback", aOnFailure));
+  MOZ_ASSERT(aOnFailure.GetISupports());
+  MOZ_ASSERT(aOnSuccess.GetISupports());
+  nsMainThreadPtrHandle<GetUserMediaSuccessCallback> onSuccess(
+      new nsMainThreadPtrHolder<GetUserMediaSuccessCallback>(
+          "GetUserMedia::SuccessCallback", std::move(aOnSuccess)));
+  nsMainThreadPtrHandle<GetUserMediaErrorCallback> onFailure(
+      new nsMainThreadPtrHolder<GetUserMediaErrorCallback>(
+          "GetUserMedia::FailureCallback", std::move(aOnFailure)));
   uint64_t windowID = aWindow->WindowID();
 
   MediaStreamConstraints c(aConstraintsPassedIn); // use a modifiable copy
 
   // Do all the validation we can while we're sync (to return an
   // already-rejected promise on failure).
 
   if (!IsOn(c.mVideo) && !IsOn(c.mAudio)) {
     RefPtr<MediaStreamError> error =
         new MediaStreamError(aWindow,
                              MediaStreamError::Name::TypeError,
                              NS_LITERAL_STRING("audio and/or video is required"));
-    onFailure->OnError(error);
+    CallOnError(onFailure, *error);
     return NS_OK;
   }
 
   if (!IsFullyActive(aWindow)) {
     RefPtr<MediaStreamError> error =
         new MediaStreamError(aWindow, MediaStreamError::Name::InvalidStateError);
-    onFailure->OnError(error);
+    CallOnError(onFailure, *error);
     return NS_OK;
   }
 
   if (sHasShutdown) {
     RefPtr<MediaStreamError> error =
         new MediaStreamError(aWindow,
                              MediaStreamError::Name::AbortError,
                              NS_LITERAL_STRING("In shutdown"));
-    onFailure->OnError(error);
+    CallOnError(onFailure, *error);
     return NS_OK;
   }
 
   // Determine permissions early (while we still have a stack).
 
   nsIURI* docURI = aWindow->GetDocumentURI();
   if (!docURI) {
     return NS_ERROR_UNEXPECTED;
@@ -2647,30 +2742,30 @@ MediaManager::GetUserMedia(nsPIDOMWindow
         if (!Preferences::GetBool(((videoType == MediaSourceEnum::Browser)?
                                    "media.getusermedia.browser.enabled" :
                                    "media.getusermedia.screensharing.enabled"),
                                   false) ||
             (!privileged && !aWindow->IsSecureContext())) {
           RefPtr<MediaStreamError> error =
               new MediaStreamError(aWindow,
                                    MediaStreamError::Name::NotAllowedError);
-          onFailure->OnError(error);
+          CallOnError(onFailure, *error);
           return NS_OK;
         }
         break;
 
       case MediaSourceEnum::Microphone:
       case MediaSourceEnum::Other:
       default: {
         RefPtr<MediaStreamError> error =
             new MediaStreamError(aWindow,
                                  MediaStreamError::Name::OverconstrainedError,
                                  NS_LITERAL_STRING(""),
                                  NS_LITERAL_STRING("mediaSource"));
-        onFailure->OnError(error);
+        CallOnError(onFailure, *error);
         return NS_OK;
       }
     }
 
     if (vc.mAdvanced.WasPassed() && videoType != MediaSourceEnum::Camera) {
       // iterate through advanced, forcing all unset mediaSources to match "root"
       const char *unset = EnumToASCII(dom::MediaSourceEnumValues::strings,
                                       MediaSourceEnum::Camera);
@@ -2727,29 +2822,29 @@ MediaManager::GetUserMedia(nsPIDOMWindow
 
       case MediaSourceEnum::AudioCapture:
         // Only enable AudioCapture if the pref is enabled. If it's not, we can
         // deny right away.
         if (!Preferences::GetBool("media.getusermedia.audiocapture.enabled")) {
           RefPtr<MediaStreamError> error =
             new MediaStreamError(aWindow,
                                  MediaStreamError::Name::NotAllowedError);
-          onFailure->OnError(error);
+          CallOnError(onFailure, *error);
           return NS_OK;
         }
         break;
 
       case MediaSourceEnum::Other:
       default: {
         RefPtr<MediaStreamError> error =
             new MediaStreamError(aWindow,
                                  MediaStreamError::Name::OverconstrainedError,
                                  NS_LITERAL_STRING(""),
                                  NS_LITERAL_STRING("mediaSource"));
-        onFailure->OnError(error);
+        CallOnError(onFailure, *error);
         return NS_OK;
       }
     }
     if (ac.mAdvanced.WasPassed()) {
       // iterate through advanced, forcing all unset mediaSources to match "root"
       const char *unset = EnumToASCII(dom::MediaSourceEnumValues::strings,
                                       MediaSourceEnum::Camera);
       for (MediaTrackConstraintSet& cs : ac.mAdvanced.Value()) {
@@ -2810,17 +2905,17 @@ MediaManager::GetUserMedia(nsPIDOMWindow
       }
     }
 
     if ((!IsOn(c.mAudio) && !IsOn(c.mVideo)) ||
         (IsOn(c.mAudio) && audioPerm == nsIPermissionManager::DENY_ACTION) ||
         (IsOn(c.mVideo) && videoPerm == nsIPermissionManager::DENY_ACTION)) {
       RefPtr<MediaStreamError> error =
           new MediaStreamError(aWindow, MediaStreamError::Name::NotAllowedError);
-      onFailure->OnError(error);
+      CallOnError(onFailure, *error);
       windowListener->Remove(sourceListener);
       return NS_OK;
     }
   }
 
   // Get list of all devices, with origin-specific device ids.
 
   MediaEnginePrefs prefs = mPrefs;
@@ -2920,31 +3015,31 @@ MediaManager::GetUserMedia(nsPIDOMWindow
              "success callback! Calling error handler!"));
         nsString constraint;
         constraint.AssignASCII(badConstraint);
         RefPtr<MediaStreamError> error =
             new MediaStreamError(window,
                                  MediaStreamError::Name::OverconstrainedError,
                                  NS_LITERAL_STRING(""),
                                  constraint);
-        onFailure->OnError(error);
+        CallOnError(onFailure, *error);
         return;
       }
       if (!(*devices)->Length()) {
         LOG(("GetUserMedia: no devices found in post enumeration pledge2 "
              "success callback! Calling error handler!"));
         RefPtr<MediaStreamError> error =
             new MediaStreamError(
                 window,
                 // When privacy.resistFingerprinting = true, no available
                 // device implies content script is requesting a fake
                 // device, so report NotAllowedError.
                 resistFingerprinting ? MediaStreamError::Name::NotAllowedError
                                      : MediaStreamError::Name::NotFoundError);
-        onFailure->OnError(error);
+        CallOnError(onFailure, *error);
         return;
       }
 
       nsCOMPtr<nsIMutableArray> devicesCopy = nsArray::Create(); // before we give up devices below
       if (!askPermission) {
         for (auto& device : **devices) {
           nsresult rv = devicesCopy->AppendElement(device);
           if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -2995,21 +3090,21 @@ MediaManager::GetUserMedia(nsPIDOMWindow
         }
       }
 
 #ifdef MOZ_WEBRTC
       EnableWebRtcLog();
 #endif
     }, [onFailure](MediaStreamError*& reason) mutable {
       LOG(("GetUserMedia: post enumeration pledge2 failure callback called!"));
-      onFailure->OnError(reason);
+      CallOnError(onFailure, *reason);
     });
   }, [onFailure](MediaStreamError*& reason) mutable {
     LOG(("GetUserMedia: post enumeration pledge failure callback called!"));
-    onFailure->OnError(reason);
+    CallOnError(onFailure, *reason);
   });
   return NS_OK;
 }
 
 /* static */ void
 MediaManager::AnonymizeDevices(MediaDeviceSet& aDevices, const nsACString& aOriginKey)
 {
   if (!aOriginKey.IsEmpty()) {
@@ -3288,41 +3383,38 @@ MediaManager::EnumerateDevices(nsPIDOMWi
 
 /*
  * GetUserMediaDevices - called by the UI-part of getUserMedia from chrome JS.
  */
 
 nsresult
 MediaManager::GetUserMediaDevices(nsPIDOMWindowInner* aWindow,
                                   const MediaStreamConstraints& aConstraints,
-                                  nsIGetUserMediaDevicesSuccessCallback* aOnSuccess,
-                                  nsIDOMGetUserMediaErrorCallback* aOnFailure,
+                                  dom::MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
                                   uint64_t aWindowId,
                                   const nsAString& aCallID)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess(aOnSuccess);
-  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure(aOnFailure);
   if (!aWindowId) {
     aWindowId = aWindow->WindowID();
   }
 
   // Ignore passed-in constraints, instead locate + return already-constrained list.
 
   nsTArray<nsString>* callIDs;
   if (!mCallIds.Get(aWindowId, &callIDs)) {
     return NS_ERROR_UNEXPECTED;
   }
 
   for (auto& callID : *callIDs) {
     RefPtr<GetUserMediaTask> task;
     if (!aCallID.Length() || aCallID == callID) {
       if (mActiveCallbacks.Get(callID, getter_AddRefs(task))) {
         nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*task->mMediaDeviceSet);
-        onSuccess->OnSuccess(array);
+        aOnSuccess.Call(array);
         return NS_OK;
       }
     }
   }
   return NS_ERROR_UNEXPECTED;
 }
 
 MediaEngine*
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -24,16 +24,17 @@
 #include "nsIDOMNavigatorUserMedia.h"
 #include "nsXULAppAPI.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/dom/MediaStreamBinding.h"
 #include "mozilla/dom/MediaStreamTrackBinding.h"
 #include "mozilla/dom/MediaStreamError.h"
+#include "mozilla/dom/NavigatorBinding.h"
 #include "mozilla/media/MediaChild.h"
 #include "mozilla/media/MediaParent.h"
 #include "mozilla/Logging.h"
 #include "mozilla/UniquePtr.h"
 #include "DOMMediaStream.h"
 
 #ifdef MOZ_WEBRTC
 #include "mtransport/runnable_utils.h"
@@ -197,27 +198,31 @@ public:
   bool IsWindowStillActive(uint64_t aWindowId) {
     return !!GetWindowListener(aWindowId);
   }
   bool IsWindowListenerStillActive(GetUserMediaWindowListener* aListener);
   // Note: also calls aListener->Remove(), even if inactive
   void RemoveFromWindowList(uint64_t aWindowID,
     GetUserMediaWindowListener *aListener);
 
+  typedef dom::CallbackObjectHolder<dom::NavigatorUserMediaSuccessCallback,
+    nsIDOMGetUserMediaSuccessCallback> GetUserMediaSuccessCallback;
+  typedef dom::CallbackObjectHolder<dom::NavigatorUserMediaErrorCallback,
+    nsIDOMGetUserMediaErrorCallback> GetUserMediaErrorCallback;
+
   nsresult GetUserMedia(
     nsPIDOMWindowInner* aWindow,
     const dom::MediaStreamConstraints& aConstraints,
-    nsIDOMGetUserMediaSuccessCallback* onSuccess,
-    nsIDOMGetUserMediaErrorCallback* onError,
+    GetUserMediaSuccessCallback&& onSuccess,
+    GetUserMediaErrorCallback&& onError,
     dom::CallerType aCallerType);
 
   nsresult GetUserMediaDevices(nsPIDOMWindowInner* aWindow,
                                const dom::MediaStreamConstraints& aConstraints,
-                               nsIGetUserMediaDevicesSuccessCallback* onSuccess,
-                               nsIDOMGetUserMediaErrorCallback* onError,
+                               dom::MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
                                uint64_t aInnerWindowID = 0,
                                const nsAString& aCallID = nsString());
 
   nsresult EnumerateDevices(nsPIDOMWindowInner* aWindow,
                             nsIGetUserMediaDevicesSuccessCallback* aOnSuccess,
                             nsIDOMGetUserMediaErrorCallback* aOnFailure,
                             dom::CallerType aCallerType);
 
--- a/dom/media/webspeech/recognition/SpeechRecognition.cpp
+++ b/dom/media/webspeech/recognition/SpeechRecognition.cpp
@@ -750,20 +750,24 @@ SpeechRecognition::Start(const Optional<
   MediaStreamConstraints constraints;
   constraints.mAudio.SetAsBoolean() = true;
 
   if (aStream.WasPassed()) {
     StartRecording(&aStream.Value());
   } else {
     AutoNoJSAPI();
     MediaManager* manager = MediaManager::Get();
+    MediaManager::GetUserMediaSuccessCallback onsuccess(
+      new GetUserMediaSuccessCallback(this));
+    MediaManager::GetUserMediaErrorCallback onerror(
+      new GetUserMediaErrorCallback(this));
     manager->GetUserMedia(GetOwner(),
                           constraints,
-                          new GetUserMediaSuccessCallback(this),
-                          new GetUserMediaErrorCallback(this),
+                          std::move(onsuccess),
+                          std::move(onerror),
                           aCallerType);
   }
 
   RefPtr<SpeechEvent> event = new SpeechEvent(this, EVENT_START);
   NS_DispatchToMainThread(event);
 }
 
 bool
--- a/dom/webidl/AccessibleNode.webidl
+++ b/dom/webidl/AccessibleNode.webidl
@@ -56,16 +56,21 @@ interface AccessibleNode {
   attribute boolean? selected;
 
   // Live regions
   attribute boolean? atomic;
   attribute boolean? busy;
   attribute DOMString? live;
   attribute DOMString? relevant;
 
+  // Other relationships
+  attribute AccessibleNode? activeDescendant;
+  attribute AccessibleNode? details;
+  attribute AccessibleNode? errorMessage;
+
   // Collections.
   attribute long? colCount;
   attribute unsigned long? colIndex;
   attribute unsigned long? colSpan;
   attribute unsigned long? level;
   attribute unsigned long? posInSet;
   attribute long? rowCount;
   attribute unsigned long? rowIndex;
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -508,17 +508,17 @@ public:
 
   // If the frame is in vertical-RTL writing mode(E.g. "writing-mode:
   // vertical-rl" in CSS), or if it's in horizontal-RTL writing-mode(E.g.
   // "writing-mode: horizontal-tb; direction: rtl;" in CSS), then this function
   // returns true. From the representation perspective, frames whose horizontal
   // contents start at rightside also cause their horizontal scrollbars, if any,
   // initially start at rightside. So we can also learn about the initial side
   // of the horizontal scrollbar for the frame by calling this function.
-  bool IsHorizontalContentRightToLeft() {
+  bool IsHorizontalContentRightToLeft() const {
     return mScrollableRect.x < 0;
   }
 
   void SetPaintRequestTime(const TimeStamp& aTime) {
     mPaintRequestTime = aTime;
   }
   const TimeStamp& GetPaintRequestTime() const {
     return mPaintRequestTime;
--- a/gfx/layers/LayerTreeInvalidation.cpp
+++ b/gfx/layers/LayerTreeInvalidation.cpp
@@ -8,16 +8,17 @@
 
 #include <stdint.h>                     // for uint32_t
 #include "ImageContainer.h"             // for ImageContainer
 #include "ImageLayers.h"                // for ImageLayer, etc
 #include "Layers.h"                     // for Layer, ContainerLayer, etc
 #include "Units.h"                      // for ParentLayerIntRect
 #include "gfxRect.h"                    // for gfxRect
 #include "gfxUtils.h"                   // for gfxUtils
+#include "mozilla/ArrayUtils.h"         // for ArrayEqual
 #include "mozilla/gfx/BaseSize.h"       // for BaseSize
 #include "mozilla/gfx/Point.h"          // for IntSize
 #include "mozilla/mozalloc.h"           // for operator new, etc
 #include "nsDataHashtable.h"            // for nsDataHashtable
 #include "nsDebug.h"                    // for NS_ASSERTION
 #include "nsHashKeys.h"                 // for nsPtrHashKey
 #include "nsISupportsImpl.h"            // for Layer::AddRef, etc
 #include "nsRect.h"                     // for IntRect
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -816,26 +816,25 @@ AsyncPanZoomController::AsyncPanZoomCont
                                                const RefPtr<InputQueue>& aInputQueue,
                                                GeckoContentController* aGeckoContentController,
                                                GestureBehavior aGestures)
   :  mLayersId(aLayersId),
      mGeckoContentController(aGeckoContentController),
      mRefPtrMonitor("RefPtrMonitor"),
      // mTreeManager must be initialized before GetFrameTime() is called
      mTreeManager(aTreeManager),
-     mFrameMetrics(mScrollMetadata.GetMetrics()),
      mRecursiveMutex("AsyncPanZoomController"),
      mLastContentPaintMetrics(mLastContentPaintMetadata.GetMetrics()),
      mX(this),
      mY(this),
      mPanDirRestricted(false),
      mPinchLocked(false),
      mZoomConstraints(false, false,
-        mFrameMetrics.GetDevPixelsPerCSSPixel() * kViewportMinScale / ParentLayerToScreenScale(1),
-        mFrameMetrics.GetDevPixelsPerCSSPixel() * kViewportMaxScale / ParentLayerToScreenScale(1)),
+        Metrics().GetDevPixelsPerCSSPixel() * kViewportMinScale / ParentLayerToScreenScale(1),
+        Metrics().GetDevPixelsPerCSSPixel() * kViewportMaxScale / ParentLayerToScreenScale(1)),
      mLastSampleTime(GetFrameTime()),
      mLastCheckerboardReport(GetFrameTime()),
      mOverscrollEffect(MakeUnique<OverscrollEffect>(*this)),
      mState(NOTHING),
      mNotificationBlockers(0),
      mInputQueue(aInputQueue),
      mPinchPaintTimerSet(false),
      mAPZCId(sAsyncPanZoomControllerCount++),
@@ -894,17 +893,17 @@ AsyncPanZoomController::Destroy()
     mGeckoContentController = nullptr;
     mGestureEventListener = nullptr;
   }
   mParent = nullptr;
   mTreeManager = nullptr;
 
   // Only send the release message if the SharedFrameMetrics has been created.
   if (mMetricsSharingController && mSharedFrameMetricsBuffer) {
-    Unused << mMetricsSharingController->StopSharingMetrics(mFrameMetrics.GetScrollId(), mAPZCId);
+    Unused << mMetricsSharingController->StopSharingMetrics(Metrics().GetScrollId(), mAPZCId);
   }
 
   { // scope the lock
     RecursiveMutexAutoLock lock(mRecursiveMutex);
     mSharedFrameMetricsBuffer = nullptr;
     delete mSharedLock;
     mSharedLock = nullptr;
   }
@@ -1058,26 +1057,26 @@ nsEventStatus AsyncPanZoomController::Ha
 
   CSSCoord maxThumbPos = scrollbarData.mScrollTrackLength;
   maxThumbPos -= scrollbarData.mThumbLength;
 
   float scrollPercent = thumbPosition / maxThumbPos;
   APZC_LOG("%p scrollbar dragged to %f percent\n", this, scrollPercent);
 
   CSSCoord minScrollPosition =
-    GetAxisStart(direction, mFrameMetrics.GetScrollableRect().TopLeft());
+    GetAxisStart(direction, Metrics().GetScrollableRect().TopLeft());
   CSSCoord maxScrollPosition =
-    GetAxisStart(direction, mFrameMetrics.GetScrollableRect().BottomRight()) -
-    GetAxisLength(direction, mFrameMetrics.CalculateCompositionBoundsInCssPixelsOfSurroundingContent());
+    GetAxisStart(direction, Metrics().GetScrollableRect().BottomRight()) -
+    GetAxisLength(direction, Metrics().CalculateCompositionBoundsInCssPixelsOfSurroundingContent());
   CSSCoord scrollPosition = minScrollPosition + (scrollPercent * (maxScrollPosition - minScrollPosition));
 
   scrollPosition = std::max(scrollPosition, minScrollPosition);
   scrollPosition = std::min(scrollPosition, maxScrollPosition);
 
-  CSSPoint scrollOffset = mFrameMetrics.GetScrollOffset();
+  CSSPoint scrollOffset = Metrics().GetScrollOffset();
   if (direction == ScrollDirection::eHorizontal) {
     scrollOffset.x = scrollPosition;
   } else {
     scrollOffset.y = scrollPosition;
   }
   APZC_LOG("%p set scroll offset to %s from scrollbar drag\n", this, Stringify(scrollOffset).c_str());
   SetScrollOffset(scrollOffset);
   ScheduleCompositeAndMaybeRepaint();
@@ -1458,17 +1457,17 @@ nsEventStatus AsyncPanZoomController::On
     if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
       controller->NotifyPinchGesture(aEvent.mType, GetGuid(), 0, aEvent.modifiers);
     }
   }
 
   SetState(PINCHING);
   mX.SetVelocity(0);
   mY.SetVelocity(0);
-  mLastZoomFocus = aEvent.mLocalFocusPoint - mFrameMetrics.GetCompositionBounds().TopLeft();
+  mLastZoomFocus = aEvent.mLocalFocusPoint - Metrics().GetCompositionBounds().TopLeft();
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) {
   APZC_LOG("%p got a scale in state %d\n", this, mState);
 
   if (HasReadyTouchBlock() && !GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
@@ -1479,17 +1478,17 @@ nsEventStatus AsyncPanZoomController::On
     return nsEventStatus_eConsumeNoDefault;
   }
 
   ParentLayerCoord spanDistance = fabsf(aEvent.mPreviousSpan - aEvent.mCurrentSpan);
   ParentLayerPoint focusPoint, focusChange;
   {
     RecursiveMutexAutoLock lock(mRecursiveMutex);
 
-    focusPoint = aEvent.mLocalFocusPoint - mFrameMetrics.GetCompositionBounds().TopLeft();
+    focusPoint = aEvent.mLocalFocusPoint - Metrics().GetCompositionBounds().TopLeft();
     focusChange = mLastZoomFocus - focusPoint;
     mLastZoomFocus = focusPoint;
   }
 
   HandlePinchLocking(
     ToScreenCoordinates(ParentLayerPoint(0, spanDistance), focusPoint).Length(),
     ToScreenCoordinates(focusChange, focusPoint));
   bool allowZoom = mZoomConstraints.mAllowZoom && !mPinchLocked;
@@ -1512,24 +1511,24 @@ nsEventStatus AsyncPanZoomController::On
     }
   }
 
   // Only the root APZC is zoomable, and the root APZC is not allowed to have
   // different x and y scales. If it did, the calculations in this function
   // would have to be adjusted (as e.g. it would no longer be valid to take
   // the minimum or maximum of the ratios of the widths and heights of the
   // page rect and the composition bounds).
-  MOZ_ASSERT(mFrameMetrics.IsRootContent());
-  MOZ_ASSERT(mFrameMetrics.GetZoom().AreScalesSame());
+  MOZ_ASSERT(Metrics().IsRootContent());
+  MOZ_ASSERT(Metrics().GetZoom().AreScalesSame());
 
   {
     RecursiveMutexAutoLock lock(mRecursiveMutex);
 
-    CSSToParentLayerScale userZoom = mFrameMetrics.GetZoom().ToScaleFactor();
-    CSSPoint cssFocusPoint = focusPoint / mFrameMetrics.GetZoom();
+    CSSToParentLayerScale userZoom = Metrics().GetZoom().ToScaleFactor();
+    CSSPoint cssFocusPoint = focusPoint / Metrics().GetZoom();
 
     // If displacing by the change in focus point will take us off page bounds,
     // then reduce the displacement such that it doesn't.
     focusChange.x -= mX.DisplacementWillOverscrollAmount(focusChange.x);
     focusChange.y -= mY.DisplacementWillOverscrollAmount(focusChange.y);
     ScrollBy(focusChange / userZoom);
 
     // If the span is zero or close to it, we don't want to process this zoom
@@ -1551,19 +1550,19 @@ nsEventStatus AsyncPanZoomController::On
     // When we zoom in with focus, we can zoom too much towards the boundaries
     // that we actually go over them. These are the needed displacements along
     // either axis such that we don't overscroll the boundaries when zooming.
     CSSPoint neededDisplacement;
 
     CSSToParentLayerScale realMinZoom = mZoomConstraints.mMinZoom;
     CSSToParentLayerScale realMaxZoom = mZoomConstraints.mMaxZoom;
     realMinZoom.scale = std::max(realMinZoom.scale,
-                                 mFrameMetrics.GetCompositionBounds().Width() / mFrameMetrics.GetScrollableRect().Width());
+                                 Metrics().GetCompositionBounds().Width() / Metrics().GetScrollableRect().Width());
     realMinZoom.scale = std::max(realMinZoom.scale,
-                                 mFrameMetrics.GetCompositionBounds().Height() / mFrameMetrics.GetScrollableRect().Height());
+                                 Metrics().GetCompositionBounds().Height() / Metrics().GetScrollableRect().Height());
     if (realMaxZoom < realMinZoom) {
       realMaxZoom = realMinZoom;
     }
 
     bool doScale = allowZoom && (
                    (spanRatio > 1.0 && userZoom < realMaxZoom) ||
                    (spanRatio < 1.0 && userZoom > realMinZoom));
 
@@ -1758,23 +1757,23 @@ AsyncPanZoomController::ConvertToGecko(c
 
 CSSCoord
 AsyncPanZoomController::ConvertScrollbarPoint(const ParentLayerPoint& aScrollbarPoint,
                                               const ScrollbarData& aThumbData) const
 {
   RecursiveMutexAutoLock lock(mRecursiveMutex);
 
   // First, get it into the right coordinate space.
-  CSSPoint scrollbarPoint = aScrollbarPoint / mFrameMetrics.GetZoom();
+  CSSPoint scrollbarPoint = aScrollbarPoint / Metrics().GetZoom();
   // The scrollbar can be transformed with the frame but the pres shell
   // resolution is only applied to the scroll frame.
-  scrollbarPoint = scrollbarPoint * mFrameMetrics.GetPresShellResolution();
+  scrollbarPoint = scrollbarPoint * Metrics().GetPresShellResolution();
 
   // Now, get it to be relative to the beginning of the scroll track.
-  CSSRect cssCompositionBound = mFrameMetrics.CalculateCompositionBoundsInCssPixelsOfSurroundingContent();
+  CSSRect cssCompositionBound = Metrics().CalculateCompositionBoundsInCssPixelsOfSurroundingContent();
   return GetAxisStart(*aThumbData.mDirection, scrollbarPoint)
       - GetAxisStart(*aThumbData.mDirection, cssCompositionBound)
       - aThumbData.mScrollTrackStart;
 }
 
 static bool
 AllowsScrollingMoreThanOnePage(double aMultiplier)
 {
@@ -1803,19 +1802,19 @@ AsyncPanZoomController::GetScrollWheelDe
   ParentLayerSize pageScrollSize;
 
   {
     // Grab the lock to access the frame metrics.
     RecursiveMutexAutoLock lock(mRecursiveMutex);
     LayoutDeviceIntSize scrollAmountLD = mScrollMetadata.GetLineScrollAmount();
     LayoutDeviceIntSize pageScrollSizeLD = mScrollMetadata.GetPageScrollAmount();
     scrollAmount = scrollAmountLD /
-      mFrameMetrics.GetDevPixelsPerCSSPixel() * mFrameMetrics.GetZoom();
+      Metrics().GetDevPixelsPerCSSPixel() * Metrics().GetZoom();
     pageScrollSize = pageScrollSizeLD /
-      mFrameMetrics.GetDevPixelsPerCSSPixel() * mFrameMetrics.GetZoom();
+      Metrics().GetDevPixelsPerCSSPixel() * Metrics().GetZoom();
   }
 
   ParentLayerPoint delta;
   switch (aEvent.mDeltaType) {
     case ScrollWheelInput::SCROLLDELTA_LINE: {
       delta.x = aDeltaX * scrollAmount.width;
       delta.y = aDeltaY * scrollAmount.height;
       break;
@@ -1892,18 +1891,18 @@ AsyncPanZoomController::OnKeyboard(const
   bool scrollSnapped = MaybeAdjustDestinationForScrollSnapping(aEvent, destination);
 
   // If smooth scrolling is disabled, then scroll immediately to the destination
   if (!gfxPrefs::SmoothScrollEnabled()) {
     CancelAnimation();
 
     // CallDispatchScroll interprets the start and end points as the start and
     // end of a touch scroll so they need to be reversed.
-    ParentLayerPoint startPoint = destination * mFrameMetrics.GetZoom();
-    ParentLayerPoint endPoint = mFrameMetrics.GetScrollOffset() * mFrameMetrics.GetZoom();
+    ParentLayerPoint startPoint = destination * Metrics().GetZoom();
+    ParentLayerPoint endPoint = Metrics().GetScrollOffset() * Metrics().GetZoom();
     ParentLayerPoint delta = endPoint - startPoint;
 
     ScreenPoint distance = ToScreenCoordinates(
         ParentLayerPoint(fabs(delta.x), fabs(delta.y)), startPoint);
 
     OverscrollHandoffState handoffState(
         *mInputQueue->GetCurrentKeyboardBlock()->GetOverscrollHandoffChain(),
         distance,
@@ -1930,26 +1929,26 @@ AsyncPanZoomController::OnKeyboard(const
     return nsEventStatus_eConsumeDoDefault;
   }
 
   // Use a keyboard scroll animation to scroll, reusing an existing one if it exists
   if (mState != KEYBOARD_SCROLL) {
     CancelAnimation();
     SetState(KEYBOARD_SCROLL);
 
-    nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
+    nsPoint initialPosition = CSSPoint::ToAppUnits(Metrics().GetScrollOffset());
     StartAnimation(new KeyboardScrollAnimation(*this, initialPosition, aEvent.mAction.mType));
   }
 
   // Convert velocity from ParentLayerPoints/ms to ParentLayerPoints/s and then
   // to appunits/second.
   nsPoint velocity =
     CSSPoint::ToAppUnits(
       ParentLayerPoint(mX.GetVelocity() * 1000.0f, mY.GetVelocity() * 1000.0f) /
-      mFrameMetrics.GetZoom());
+      Metrics().GetZoom());
 
   KeyboardScrollAnimation* animation = mAnimation->AsKeyboardScrollAnimation();
   MOZ_ASSERT(animation);
 
   animation->UpdateDestination(aEvent.mTimeStamp,
                                CSSPixel::ToAppUnits(destination),
                                nsSize(velocity.x, velocity.y));
 
@@ -1964,31 +1963,31 @@ AsyncPanZoomController::GetKeyboardDesti
   CSSPoint scrollOffset;
   CSSRect scrollRect;
 
   {
     // Grab the lock to access the frame metrics.
     RecursiveMutexAutoLock lock(mRecursiveMutex);
 
     lineScrollSize = mScrollMetadata.GetLineScrollAmount() /
-      mFrameMetrics.GetDevPixelsPerCSSPixel();
+      Metrics().GetDevPixelsPerCSSPixel();
     pageScrollSize = mScrollMetadata.GetPageScrollAmount() /
-      mFrameMetrics.GetDevPixelsPerCSSPixel();
+      Metrics().GetDevPixelsPerCSSPixel();
 
     if (mState == WHEEL_SCROLL) {
       scrollOffset = mAnimation->AsWheelScrollAnimation()->GetDestination();
     } else if (mState == SMOOTH_SCROLL) {
       scrollOffset = mAnimation->AsSmoothScrollAnimation()->GetDestination();
     } else if (mState == KEYBOARD_SCROLL) {
       scrollOffset = mAnimation->AsKeyboardScrollAnimation()->GetDestination();
     } else {
-      scrollOffset = mFrameMetrics.GetScrollOffset();
+      scrollOffset = Metrics().GetScrollOffset();
     }
 
-    scrollRect = mFrameMetrics.GetScrollableRect();
+    scrollRect = Metrics().GetScrollableRect();
   }
 
   // Calculate the scroll destination based off of the scroll type and direction
   CSSPoint scrollDestination = scrollOffset;
 
   switch (aAction.mType) {
     case KeyboardScrollAction::eScrollCharacter: {
       int32_t scrollDistance = gfxPrefs::ToolkitHorizontalScrollDistance();
@@ -2135,17 +2134,17 @@ AsyncPanZoomController::CanScroll(Scroll
 bool
 AsyncPanZoomController::IsContentOfHonouredTargetRightToLeft(
                           bool aHonoursRoot) const
 {
   if (aHonoursRoot) {
     return mScrollMetadata.IsAutoDirRootContentRTL();
   }
   RecursiveMutexAutoLock lock(mRecursiveMutex);
-  return mFrameMetrics.IsHorizontalContentRightToLeft();
+  return Metrics().IsHorizontalContentRightToLeft();
 }
 
 bool
 AsyncPanZoomController::AllowScrollHandoffInCurrentBlock() const
 {
   bool result = mInputQueue->AllowScrollHandoff();
   if (!gfxPrefs::APZAllowImmediateHandoff()) {
     if (InputBlockState* currentBlock = GetCurrentInputBlock()) {
@@ -2232,17 +2231,17 @@ nsEventStatus AsyncPanZoomController::On
     APZC_LOG("%p got a scroll-wheel with adjusted auto-dir delta values\n",
              this);
   } else if ((delta.x || delta.y) && !CanScrollWithWheel(delta)) {
     // We can't scroll this apz anymore, so we simply drop the event.
     if (mInputQueue->GetActiveWheelTransaction() &&
         gfxPrefs::MouseScrollTestingEnabled()) {
       if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
         controller->NotifyMozMouseScrollEvent(
-          mFrameMetrics.GetScrollId(),
+          Metrics().GetScrollId(),
           NS_LITERAL_STRING("MozMouseScrollFailed"));
       }
     }
     return nsEventStatus_eConsumeNoDefault;
   }
 
   MOZ_ASSERT(mInputQueue->GetCurrentWheelBlock());
   AdjustDeltaForAllowedScrollDirections(delta,
@@ -2254,17 +2253,17 @@ nsEventStatus AsyncPanZoomController::On
   }
 
   switch (aEvent.mScrollMode) {
     case ScrollWheelInput::SCROLLMODE_INSTANT: {
 
       // Wheel events from "clicky" mouse wheels trigger scroll snapping to the
       // next snap point. Check for this, and adjust the delta to take into
       // account the snap point.
-      CSSPoint startPosition = mFrameMetrics.GetScrollOffset();
+      CSSPoint startPosition = Metrics().GetScrollOffset();
       MaybeAdjustDeltaForScrollSnapping(aEvent, delta, startPosition);
 
       ScreenPoint distance = ToScreenCoordinates(
         ParentLayerPoint(fabs(delta.x), fabs(delta.y)), aEvent.mLocalOrigin);
 
       CancelAnimation();
 
       OverscrollHandoffState handoffState(
@@ -2287,17 +2286,17 @@ nsEventStatus AsyncPanZoomController::On
 
     case ScrollWheelInput::SCROLLMODE_SMOOTH: {
       // The lock must be held across the entire update operation, so the
       // compositor doesn't end the animation before we get a chance to
       // update it.
       RecursiveMutexAutoLock lock(mRecursiveMutex);
 
       // Perform scroll snapping if appropriate.
-      CSSPoint startPosition = mFrameMetrics.GetScrollOffset();
+      CSSPoint startPosition = Metrics().GetScrollOffset();
       // If we're already in a wheel scroll or smooth scroll animation,
       // the delta is applied to its destination, not to the current
       // scroll position. Take this into account when finding a snap point.
       if (mState == WHEEL_SCROLL) {
         startPosition = mAnimation->AsWheelScrollAnimation()->GetDestination();
       } else if (mState == SMOOTH_SCROLL) {
         startPosition = mAnimation->AsSmoothScrollAnimation()->GetDestination();
       } else if (mState == KEYBOARD_SCROLL) {
@@ -2312,31 +2311,31 @@ nsEventStatus AsyncPanZoomController::On
         break;
       }
 
       // Otherwise, use a wheel scroll animation, also reusing one if possible.
       if (mState != WHEEL_SCROLL) {
         CancelAnimation();
         SetState(WHEEL_SCROLL);
 
-        nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
+        nsPoint initialPosition = CSSPoint::ToAppUnits(Metrics().GetScrollOffset());
         StartAnimation(new WheelScrollAnimation(
           *this, initialPosition, aEvent.mDeltaType));
       }
 
       nsPoint deltaInAppUnits =
-        CSSPoint::ToAppUnits(delta / mFrameMetrics.GetZoom());
+        CSSPoint::ToAppUnits(delta / Metrics().GetZoom());
 
       // Convert velocity from ParentLayerPoints/ms to ParentLayerPoints/s and
       // then to appunits/second.
       nsPoint velocity =
         CSSPoint::ToAppUnits(
           ParentLayerPoint(mX.GetVelocity() * 1000.0f,
                            mY.GetVelocity() * 1000.0f) /
-          mFrameMetrics.GetZoom());
+          Metrics().GetZoom());
 
       WheelScrollAnimation* animation = mAnimation->AsWheelScrollAnimation();
       animation->UpdateDelta(aEvent.mTimeStamp, deltaInAppUnits, nsSize(velocity.x, velocity.y));
       break;
     }
   }
 
   return nsEventStatus_eConsumeNoDefault;
@@ -2345,17 +2344,17 @@ nsEventStatus AsyncPanZoomController::On
 void
 AsyncPanZoomController::NotifyMozMouseScrollEvent(const nsString& aString) const
 {
   RefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (!controller) {
     return;
   }
 
-  controller->NotifyMozMouseScrollEvent(mFrameMetrics.GetScrollId(), aString);
+  controller->NotifyMozMouseScrollEvent(Metrics().GetScrollId(), aString);
 }
 
 nsEventStatus AsyncPanZoomController::OnPanMayBegin(const PanGestureInput& aEvent) {
   APZC_LOG("%p got a pan-maybegin in state %d\n", this, mState);
 
   mX.StartTouch(aEvent.mLocalPanStartPoint.x, aEvent.mTime);
   mY.StartTouch(aEvent.mLocalPanStartPoint.y, aEvent.mTime);
   MOZ_ASSERT(GetCurrentPanGestureBlock());
@@ -2930,17 +2929,17 @@ bool AsyncPanZoomController::AttemptScro
     bool xChanged = mX.AdjustDisplacement(displacement.x, adjustedDisplacement.x,
                                           overscroll.x,
                                           forcesHorizontalOverscroll);
     if (xChanged || yChanged) {
       ScheduleComposite();
     }
 
     if (!IsZero(adjustedDisplacement)) {
-      ScrollBy(adjustedDisplacement / mFrameMetrics.GetZoom());
+      ScrollBy(adjustedDisplacement / Metrics().GetZoom());
       if (InputBlockState* block = GetCurrentInputBlock()) {
 #if defined(MOZ_WIDGET_ANDROID)
         if (block->AsTouchBlock() && (block->GetScrolledApzc() != this) && IsRootContent()) {
           if (APZCTreeManager* manager = GetApzcTreeManager()) {
             AndroidDynamicToolbarAnimator* animator = manager->GetAndroidDynamicToolbarAnimator();
             MOZ_ASSERT(animator);
             animator->SetScrollingRootContent();
           }
@@ -3150,24 +3149,24 @@ void AsyncPanZoomController::SmoothScrol
   if (mState == SMOOTH_SCROLL && mAnimation) {
     APZC_LOG("%p updating destination on existing animation\n", this);
     RefPtr<SmoothScrollAnimation> animation(
       static_cast<SmoothScrollAnimation*>(mAnimation.get()));
     animation->SetDestination(CSSPoint::ToAppUnits(aDestination));
   } else {
     CancelAnimation();
     SetState(SMOOTH_SCROLL);
-    nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
+    nsPoint initialPosition = CSSPoint::ToAppUnits(Metrics().GetScrollOffset());
     // Convert velocity from ParentLayerPoints/ms to ParentLayerPoints/s and
     // then to appunits/second.
     nsPoint initialVelocity =
       CSSPoint::ToAppUnits(
         ParentLayerPoint(mX.GetVelocity() * 1000.0f,
                           mY.GetVelocity() * 1000.0f) /
-        mFrameMetrics.GetZoom());
+        Metrics().GetZoom());
 
     nsPoint destination = CSSPoint::ToAppUnits(aDestination);
 
     StartAnimation(new SmoothScrollAnimation(*this,
                                              initialPosition, initialVelocity,
                                              destination,
                                              gfxPrefs::ScrollBehaviorSpringConstant(),
                                              gfxPrefs::ScrollBehaviorDampingRatio()));
@@ -3304,62 +3303,62 @@ void AsyncPanZoomController::SetMetricsS
   mMetricsSharingController = aMetricsSharingController;
 }
 
 void AsyncPanZoomController::AdjustScrollForSurfaceShift(const ScreenPoint& aShift)
 {
   RecursiveMutexAutoLock lock(mRecursiveMutex);
   CSSPoint adjustment =
     ViewAs<ParentLayerPixel>(aShift, PixelCastJustification::ScreenIsParentLayerForRoot)
-    / mFrameMetrics.GetZoom();
+    / Metrics().GetZoom();
   APZC_LOG("%p adjusting scroll position by %s for surface shift\n",
     this, Stringify(adjustment).c_str());
-  CSSRect scrollRange = mFrameMetrics.CalculateScrollRange();
-  // Apply shift to mFrameMetrics.mScrollOffset.
+  CSSRect scrollRange = Metrics().CalculateScrollRange();
+  // Apply shift to Metrics().mScrollOffset.
   SetScrollOffset(scrollRange.ClampPoint(
-      mFrameMetrics.GetScrollOffset() + adjustment));
+      Metrics().GetScrollOffset() + adjustment));
   // Apply shift to mCompositedScrollOffset, since the dynamic toolbar expects
   // the shift to take effect right away, without the usual frame delay.
   mCompositedScrollOffset = scrollRange.ClampPoint(
       mCompositedScrollOffset + adjustment);
   RequestContentRepaint();
   UpdateSharedCompositorFrameMetrics();
 }
 
 void AsyncPanZoomController::SetScrollOffset(const CSSPoint& aOffset) {
-  mFrameMetrics.SetScrollOffset(aOffset);
-  mFrameMetrics.RecalculateViewportOffset();
+  Metrics().SetScrollOffset(aOffset);
+  Metrics().RecalculateViewportOffset();
 }
 
 void AsyncPanZoomController::ClampAndSetScrollOffset(const CSSPoint& aOffset) {
-  mFrameMetrics.ClampAndSetScrollOffset(aOffset);
-  mFrameMetrics.RecalculateViewportOffset();
+  Metrics().ClampAndSetScrollOffset(aOffset);
+  Metrics().RecalculateViewportOffset();
 }
 
 void AsyncPanZoomController::ScrollBy(const CSSPoint& aOffset) {
-  SetScrollOffset(mFrameMetrics.GetScrollOffset() + aOffset);
+  SetScrollOffset(Metrics().GetScrollOffset() + aOffset);
 }
 
 void AsyncPanZoomController::ScrollByAndClamp(const CSSPoint& aOffset) {
-  ClampAndSetScrollOffset(mFrameMetrics.GetScrollOffset() + aOffset);
+  ClampAndSetScrollOffset(Metrics().GetScrollOffset() + aOffset);
 }
 
 void AsyncPanZoomController::CopyScrollInfoFrom(const FrameMetrics& aFrameMetrics) {
-  mFrameMetrics.CopyScrollInfoFrom(aFrameMetrics);
-  mFrameMetrics.RecalculateViewportOffset();
+  Metrics().CopyScrollInfoFrom(aFrameMetrics);
+  Metrics().RecalculateViewportOffset();
 }
 
 void AsyncPanZoomController::ScaleWithFocus(float aScale,
                                             const CSSPoint& aFocus) {
-  mFrameMetrics.ZoomBy(aScale);
+  Metrics().ZoomBy(aScale);
   // We want to adjust the scroll offset such that the CSS point represented by aFocus remains
   // at the same position on the screen before and after the change in zoom. The below code
   // accomplishes this; see https://bugzilla.mozilla.org/show_bug.cgi?id=923431#c6 for an
   // in-depth explanation of how.
-  SetScrollOffset((mFrameMetrics.GetScrollOffset() + aFocus) - (aFocus / aScale));
+  SetScrollOffset((Metrics().GetScrollOffset() + aFocus) - (aFocus / aScale));
 }
 
 /**
  * Enlarges the displayport along both axes based on the velocity.
  */
 static CSSSize
 CalculateDisplayPortSize(const CSSSize& aCompositionSize,
                          const CSSPoint& aVelocity)
@@ -3544,29 +3543,29 @@ bool AsyncPanZoomController::IsFlingingF
 
 bool AsyncPanZoomController::IsPannable() const {
   RecursiveMutexAutoLock lock(mRecursiveMutex);
   return mX.CanScroll() || mY.CanScroll();
 }
 
 bool AsyncPanZoomController::IsScrollInfoLayer() const {
   RecursiveMutexAutoLock lock(mRecursiveMutex);
-  return mFrameMetrics.IsScrollInfoLayer();
+  return Metrics().IsScrollInfoLayer();
 }
 
 int32_t AsyncPanZoomController::GetLastTouchIdentifier() const {
   RefPtr<GestureEventListener> listener = GetGestureEventListener();
   return listener ? listener->GetLastTouchIdentifier() : -1;
 }
 
 void AsyncPanZoomController::RequestContentRepaint(bool aUserAction) {
   // Reinvoke this method on the repaint thread if it's not there already. It's
   // important to do this before the call to CalculatePendingDisplayPort, so
   // that CalculatePendingDisplayPort uses the most recent available version of
-  // mFrameMetrics, just before the paint request is dispatched to content.
+  // Metrics(). just before the paint request is dispatched to content.
   RefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (!controller) {
     return;
   }
   if (!controller->IsRepaintThread()) {
     // use the local variable to resolve the function overload.
     auto func = static_cast<void (AsyncPanZoomController::*)(bool)>
         (&AsyncPanZoomController::RequestContentRepaint);
@@ -3577,21 +3576,21 @@ void AsyncPanZoomController::RequestCont
       aUserAction));
     return;
   }
 
   MOZ_ASSERT(controller->IsRepaintThread());
 
   RecursiveMutexAutoLock lock(mRecursiveMutex);
   ParentLayerPoint velocity = GetVelocityVector();
-  mFrameMetrics.SetDisplayPortMargins(CalculatePendingDisplayPort(mFrameMetrics, velocity));
-  mFrameMetrics.SetUseDisplayPortMargins(true);
-  mFrameMetrics.SetPaintRequestTime(TimeStamp::Now());
-  mFrameMetrics.SetRepaintDrivenByUserAction(aUserAction);
-  RequestContentRepaint(mFrameMetrics, velocity);
+  Metrics().SetDisplayPortMargins(CalculatePendingDisplayPort(Metrics(), velocity));
+  Metrics().SetUseDisplayPortMargins(true);
+  Metrics().SetPaintRequestTime(TimeStamp::Now());
+  Metrics().SetRepaintDrivenByUserAction(aUserAction);
+  RequestContentRepaint(Metrics(), velocity);
 }
 
 /*static*/ CSSRect
 GetDisplayPortRect(const FrameMetrics& aFrameMetrics)
 {
   // This computation is based on what happens in CalculatePendingDisplayPort. If that
   // changes then this might need to change too
   CSSRect baseRect(aFrameMetrics.GetScrollOffset(),
@@ -3678,17 +3677,17 @@ bool AsyncPanZoomController::UpdateAnima
   // call this after the |mLastSampleTime == aSampleTime| check, to ensure
   // it's only called once per APZC on each composite.
   bool needComposite = SampleCompositedAsyncTransform();
 
   TimeDuration sampleTimeDelta = aSampleTime - mLastSampleTime;
   mLastSampleTime = aSampleTime;
 
   if (mAnimation) {
-    bool continueAnimation = mAnimation->Sample(mFrameMetrics, sampleTimeDelta);
+    bool continueAnimation = mAnimation->Sample(Metrics(), sampleTimeDelta);
     bool wantsRepaints = mAnimation->WantsRepaints();
     *aOutDeferredTasks = mAnimation->TakeDeferredTasks();
     if (!continueAnimation) {
       mAnimation = nullptr;
       SetState(NOTHING);
     }
     // Request a repaint at the end of the animation in case something such as a
     // call to NotifyLayersUpdated was invoked during the animation and Gecko's
@@ -3744,18 +3743,18 @@ bool AsyncPanZoomController::AdvanceAnim
 
     requestAnimationFrame = UpdateAnimation(aSampleTime, &deferredTasks);
 
     { // scope lock
       MutexAutoLock lock(mCheckerboardEventLock);
       if (mCheckerboardEvent) {
         mCheckerboardEvent->UpdateRendertraceProperty(
             CheckerboardEvent::UserVisible,
-            CSSRect(mFrameMetrics.GetScrollOffset(),
-                    mFrameMetrics.CalculateCompositedSizeInCssPixels()));
+            CSSRect(Metrics().GetScrollOffset(),
+                    Metrics().CalculateCompositedSizeInCssPixels()));
       }
     }
   }
 
   // Execute any deferred tasks queued up by mAnimation's Sample() (called by
   // UpdateAnimation()). This needs to be done after the monitor is released
   // since the tasks are allowed to call APZCTreeManager methods which can grab
   // the tree lock.
@@ -3766,17 +3765,17 @@ bool AsyncPanZoomController::AdvanceAnim
   // If any of the deferred tasks starts a new animation, it will request a
   // new composite directly, so we can just return requestAnimationFrame here.
   return requestAnimationFrame;
 }
 
 CSSRect
 AsyncPanZoomController::GetCurrentAsyncLayoutViewport(AsyncTransformConsumer aMode) const {
   RecursiveMutexAutoLock lock(mRecursiveMutex);
-  MOZ_ASSERT(mFrameMetrics.IsRootContent(),
+  MOZ_ASSERT(Metrics().IsRootContent(),
       "Only the root content APZC has a layout viewport");
   if (aMode == eForCompositing && mScrollMetadata.IsApzForceDisabled()) {
     return mLastContentPaintMetrics.GetViewport();
   }
   return GetEffectiveLayoutViewport(aMode);
 }
 
 ParentLayerPoint
@@ -3839,58 +3838,58 @@ AsyncPanZoomController::GetCurrentAsyncT
   }
 
   CSSToParentLayerScale2D effectiveZoom = GetEffectiveZoom(aMode);
 
   ParentLayerPoint translation = (currentScrollOffset - lastPaintScrollOffset)
                                * effectiveZoom * mTestAsyncZoom.scale;
 
   LayerToParentLayerScale compositedAsyncZoom =
-      (effectiveZoom / mFrameMetrics.LayersPixelsPerCSSPixel()).ToScaleFactor();
+      (effectiveZoom / Metrics().LayersPixelsPerCSSPixel()).ToScaleFactor();
   return AsyncTransform(
     LayerToParentLayerScale(compositedAsyncZoom.scale * mTestAsyncZoom.scale),
     -translation);
 }
 
 CSSRect
 AsyncPanZoomController::GetEffectiveLayoutViewport(AsyncTransformConsumer aMode) const
 {
   if (gfxPrefs::APZFrameDelayEnabled() && aMode == eForCompositing) {
     return mCompositedLayoutViewport;
   }
-  return mFrameMetrics.GetViewport();
+  return Metrics().GetViewport();
 }
 
 CSSPoint
 AsyncPanZoomController::GetEffectiveScrollOffset(AsyncTransformConsumer aMode) const
 {
   if (gfxPrefs::APZFrameDelayEnabled() && aMode == eForCompositing) {
     return mCompositedScrollOffset;
   }
-  return mFrameMetrics.GetScrollOffset();
+  return Metrics().GetScrollOffset();
 }
 
 CSSToParentLayerScale2D
 AsyncPanZoomController::GetEffectiveZoom(AsyncTransformConsumer aMode) const
 {
   if (gfxPrefs::APZFrameDelayEnabled() && aMode == eForCompositing) {
     return mCompositedZoom;
   }
-  return mFrameMetrics.GetZoom();
+  return Metrics().GetZoom();
 }
 
 bool
 AsyncPanZoomController::SampleCompositedAsyncTransform()
 {
   RecursiveMutexAutoLock lock(mRecursiveMutex);
-  if (mCompositedScrollOffset != mFrameMetrics.GetScrollOffset() ||
-      mCompositedZoom != mFrameMetrics.GetZoom()) {
-    mCompositedLayoutViewport = mFrameMetrics.GetViewport();
-    mCompositedScrollOffset = mFrameMetrics.GetScrollOffset();
-    mCompositedZoom = mFrameMetrics.GetZoom();
+  if (mCompositedScrollOffset != Metrics().GetScrollOffset() ||
+      mCompositedZoom != Metrics().GetZoom()) {
+    mCompositedLayoutViewport = Metrics().GetViewport();
+    mCompositedScrollOffset = Metrics().GetScrollOffset();
+    mCompositedZoom = Metrics().GetZoom();
     return true;
   }
   return false;
 }
 
 AsyncTransformComponentMatrix
 AsyncPanZoomController::GetCurrentAsyncTransformWithOverscroll(AsyncTransformConsumer aMode) const
 {
@@ -3919,19 +3918,19 @@ Matrix4x4 AsyncPanZoomController::GetTra
            PostScale(zoomChange.width, zoomChange.height, 1);
 }
 
 uint32_t
 AsyncPanZoomController::GetCheckerboardMagnitude() const
 {
   RecursiveMutexAutoLock lock(mRecursiveMutex);
 
-  CSSPoint currentScrollOffset = mFrameMetrics.GetScrollOffset() + mTestAsyncScrollOffset;
+  CSSPoint currentScrollOffset = Metrics().GetScrollOffset() + mTestAsyncScrollOffset;
   CSSRect painted = mLastContentPaintMetrics.GetDisplayPort() + mLastContentPaintMetrics.GetScrollOffset();
-  CSSRect visible = CSSRect(currentScrollOffset, mFrameMetrics.CalculateCompositedSizeInCssPixels());
+  CSSRect visible = CSSRect(currentScrollOffset, Metrics().CalculateCompositedSizeInCssPixels());
 
   CSSIntRegion checkerboard;
   // Round so as to minimize checkerboarding; if we're only showing fractional
   // pixels of checkerboarding it's not really worth counting
   checkerboard.Sub(RoundedIn(visible), RoundedOut(painted));
   return checkerboard.Area();
 }
 
@@ -3999,24 +3998,24 @@ AsyncPanZoomController::FlushActiveCheck
 
 bool AsyncPanZoomController::IsCurrentlyCheckerboarding() const {
   RecursiveMutexAutoLock lock(mRecursiveMutex);
 
   if (!gfxPrefs::APZAllowCheckerboarding() || mScrollMetadata.IsApzForceDisabled()) {
     return false;
   }
 
-  CSSPoint currentScrollOffset = mFrameMetrics.GetScrollOffset() + mTestAsyncScrollOffset;
+  CSSPoint currentScrollOffset = Metrics().GetScrollOffset() + mTestAsyncScrollOffset;
   CSSRect painted = mLastContentPaintMetrics.GetDisplayPort() + mLastContentPaintMetrics.GetScrollOffset();
   painted.Inflate(CSSMargin::FromAppUnits(nsMargin(1, 1, 1, 1)));   // fuzz for rounding error
-  CSSRect visible = CSSRect(currentScrollOffset, mFrameMetrics.CalculateCompositedSizeInCssPixels());
+  CSSRect visible = CSSRect(currentScrollOffset, Metrics().CalculateCompositedSizeInCssPixels());
   if (painted.Contains(visible)) {
     return false;
   }
-  APZC_LOG_FM(mFrameMetrics, "%p is currently checkerboarding (painted %s visble %s)",
+  APZC_LOG_FM(Metrics(), "%p is currently checkerboarding (painted %s visble %s)",
     this, Stringify(painted).c_str(), Stringify(visible).c_str());
   return true;
 }
 
 void AsyncPanZoomController::NotifyLayersUpdated(const ScrollMetadata& aScrollMetadata,
                                                  bool aIsFirstPaint,
                                                  bool aThisLayerTreeUpdated)
 {
@@ -4028,28 +4027,28 @@ void AsyncPanZoomController::NotifyLayer
   const FrameMetrics& aLayerMetrics = aScrollMetadata.GetMetrics();
 
   if ((aScrollMetadata == mLastContentPaintMetadata) && !isDefault) {
     // No new information here, skip it.
     APZC_LOG("%p NotifyLayersUpdated short-circuit\n", this);
     return;
   }
 
-  // If the mFrameMetrics scroll offset is different from the last scroll offset
+  // If the Metrics scroll offset is different from the last scroll offset
   // that the main-thread sent us, then we know that the user has been doing
   // something that triggers a scroll. This check is the APZ equivalent of the
   // check on the main-thread at
   // https://hg.mozilla.org/mozilla-central/file/97a52326b06a/layout/generic/nsGfxScrollFrame.cpp#l4050
   // There is code below (the use site of userScrolled) that prevents a restored-
   // scroll-position update from overwriting a user scroll, again equivalent to
   // how the main thread code does the same thing.
   CSSPoint lastScrollOffset = mLastContentPaintMetadata.GetMetrics().GetScrollOffset();
   bool userScrolled =
-    !FuzzyEqualsAdditive(mFrameMetrics.GetScrollOffset().x, lastScrollOffset.x) ||
-    !FuzzyEqualsAdditive(mFrameMetrics.GetScrollOffset().y, lastScrollOffset.y);
+    !FuzzyEqualsAdditive(Metrics().GetScrollOffset().x, lastScrollOffset.x) ||
+    !FuzzyEqualsAdditive(Metrics().GetScrollOffset().y, lastScrollOffset.y);
 
   if (aLayerMetrics.GetScrollUpdateType() != FrameMetrics::ScrollOffsetUpdateType::ePending) {
     mLastContentPaintMetadata = aScrollMetadata;
   }
 
   mScrollMetadata.SetScrollParentId(aScrollMetadata.GetScrollParentId());
   APZC_LOG_FM(aLayerMetrics, "%p got a NotifyLayersUpdated with aIsFirstPaint=%d, aThisLayerTreeUpdated=%d",
     this, aIsFirstPaint, aThisLayerTreeUpdated);
@@ -4090,43 +4089,43 @@ void AsyncPanZoomController::NotifyLayer
     }
   }
 
   // If the layers update was not triggered by our own repaint request, then
   // we want to take the new scroll offset. Check the scroll generation as well
   // to filter duplicate calls to NotifyLayersUpdated with the same scroll offset
   // update message.
   bool scrollOffsetUpdated = aLayerMetrics.GetScrollOffsetUpdated()
-        && (aLayerMetrics.GetScrollGeneration() != mFrameMetrics.GetScrollGeneration());
+        && (aLayerMetrics.GetScrollGeneration() != Metrics().GetScrollGeneration());
 
   if (scrollOffsetUpdated && userScrolled &&
       aLayerMetrics.GetScrollUpdateType() == FrameMetrics::ScrollOffsetUpdateType::eRestore) {
     APZC_LOG("%p dropping scroll update of type eRestore because of user scroll\n", this);
     scrollOffsetUpdated = false;
   }
 
   bool smoothScrollRequested = aLayerMetrics.GetDoSmoothScroll()
-       && (aLayerMetrics.GetScrollGeneration() != mFrameMetrics.GetScrollGeneration());
+       && (aLayerMetrics.GetScrollGeneration() != Metrics().GetScrollGeneration());
 
   // TODO if we're in a drag and scrollOffsetUpdated is set then we want to
   // ignore it
 
   bool needContentRepaint = false;
   bool viewportUpdated = false;
-  if (FuzzyEqualsAdditive(aLayerMetrics.GetCompositionBounds().Width(), mFrameMetrics.GetCompositionBounds().Width()) &&
-      FuzzyEqualsAdditive(aLayerMetrics.GetCompositionBounds().Height(), mFrameMetrics.GetCompositionBounds().Height())) {
+  if (FuzzyEqualsAdditive(aLayerMetrics.GetCompositionBounds().Width(), Metrics().GetCompositionBounds().Width()) &&
+      FuzzyEqualsAdditive(aLayerMetrics.GetCompositionBounds().Height(), Metrics().GetCompositionBounds().Height())) {
     // Remote content has sync'd up to the composition geometry
     // change, so we can accept the viewport it's calculated.
-    if (mFrameMetrics.GetViewport().Width() != aLayerMetrics.GetViewport().Width() ||
-        mFrameMetrics.GetViewport().Height() != aLayerMetrics.GetViewport().Height()) {
+    if (Metrics().GetViewport().Width() != aLayerMetrics.GetViewport().Width() ||
+        Metrics().GetViewport().Height() != aLayerMetrics.GetViewport().Height()) {
       needContentRepaint = true;
       viewportUpdated = true;
     }
     if (viewportUpdated || scrollOffsetUpdated) {
-      mFrameMetrics.SetViewport(aLayerMetrics.GetViewport());
+      Metrics().SetViewport(aLayerMetrics.GetViewport());
     }
   }
 
 #if defined(MOZ_WIDGET_ANDROID)
   if (aLayerMetrics.IsRootContent()) {
     if (APZCTreeManager* manager = GetApzcTreeManager()) {
       AndroidDynamicToolbarAnimator* animator = manager->GetAndroidDynamicToolbarAnimator();
       MOZ_ASSERT(animator);
@@ -4139,103 +4138,103 @@ void AsyncPanZoomController::NotifyLayer
     // Initialize our internal state to something sane when the content
     // that was just painted is something we knew nothing about previously
     CancelAnimation();
 
     mScrollMetadata = aScrollMetadata;
     mExpectedGeckoMetrics = aLayerMetrics;
     ShareCompositorFrameMetrics();
 
-    mCompositedLayoutViewport = mFrameMetrics.GetViewport();
-    mCompositedScrollOffset = mFrameMetrics.GetScrollOffset();
-    mCompositedZoom = mFrameMetrics.GetZoom();
-
-    if (mFrameMetrics.GetDisplayPortMargins() != ScreenMargin()) {
+    mCompositedLayoutViewport = Metrics().GetViewport();
+    mCompositedScrollOffset = Metrics().GetScrollOffset();
+    mCompositedZoom = Metrics().GetZoom();
+
+    if (Metrics().GetDisplayPortMargins() != ScreenMargin()) {
       // A non-zero display port margin here indicates a displayport has
       // been set by a previous APZC for the content at this guid. The
       // scrollable rect may have changed since then, making the margins
       // wrong, so we need to calculate a new display port.
       APZC_LOG("%p detected non-empty margins which probably need updating\n", this);
       needContentRepaint = true;
     }
   } else {
     // If we're not taking the aLayerMetrics wholesale we still need to pull
-    // in some things into our local mFrameMetrics because these things are
-    // determined by Gecko and our copy in mFrameMetrics may be stale.
-
-    if (FuzzyEqualsAdditive(mFrameMetrics.GetCompositionBounds().Width(), aLayerMetrics.GetCompositionBounds().Width()) &&
-        mFrameMetrics.GetDevPixelsPerCSSPixel() == aLayerMetrics.GetDevPixelsPerCSSPixel() &&
+    // in some things into our local Metrics() because these things are
+    // determined by Gecko and our copy in Metrics() may be stale.
+
+    if (FuzzyEqualsAdditive(Metrics().GetCompositionBounds().Width(), aLayerMetrics.GetCompositionBounds().Width()) &&
+        Metrics().GetDevPixelsPerCSSPixel() == aLayerMetrics.GetDevPixelsPerCSSPixel() &&
         !viewportUpdated) {
       // Any change to the pres shell resolution was requested by APZ and is
       // already included in our zoom; however, other components of the
       // cumulative resolution (a parent document's pres-shell resolution, or
       // the css-driven resolution) may have changed, and we need to update
       // our zoom to reflect that. Note that we can't just take
       // aLayerMetrics.mZoom because the APZ may have additional async zoom
       // since the repaint request.
       gfxSize totalResolutionChange = aLayerMetrics.GetCumulativeResolution()
-                                    / mFrameMetrics.GetCumulativeResolution();
+                                    / Metrics().GetCumulativeResolution();
       float presShellResolutionChange = aLayerMetrics.GetPresShellResolution()
-                                      / mFrameMetrics.GetPresShellResolution();
+                                      / Metrics().GetPresShellResolution();
       if (presShellResolutionChange != 1.0f) {
         needContentRepaint = true;
       }
-      mFrameMetrics.ZoomBy(totalResolutionChange / presShellResolutionChange);
+      Metrics().ZoomBy(totalResolutionChange / presShellResolutionChange);
       mCompositedZoom.xScale *= (totalResolutionChange / presShellResolutionChange).width;
       mCompositedZoom.yScale *= (totalResolutionChange / presShellResolutionChange).height;
     } else {
       // Take the new zoom as either device scale or composition width or
       // viewport size got changed (e.g. due to orientation change, or content
       // changing the meta-viewport tag).
-      mFrameMetrics.SetZoom(aLayerMetrics.GetZoom());
+      Metrics().SetZoom(aLayerMetrics.GetZoom());
       mCompositedZoom = aLayerMetrics.GetZoom();
-      mFrameMetrics.SetDevPixelsPerCSSPixel(aLayerMetrics.GetDevPixelsPerCSSPixel());
+      Metrics().SetDevPixelsPerCSSPixel(aLayerMetrics.GetDevPixelsPerCSSPixel());
     }
     bool scrollableRectChanged = false;
-    if (!mFrameMetrics.GetScrollableRect().IsEqualEdges(aLayerMetrics.GetScrollableRect())) {
-      mFrameMetrics.SetScrollableRect(aLayerMetrics.GetScrollableRect());
+    if (!Metrics().GetScrollableRect().IsEqualEdges(aLayerMetrics.GetScrollableRect())) {
+      Metrics().SetScrollableRect(aLayerMetrics.GetScrollableRect());
       needContentRepaint = true;
       scrollableRectChanged = true;
     }
-    mFrameMetrics.SetCompositionBounds(aLayerMetrics.GetCompositionBounds());
-    mFrameMetrics.SetRootCompositionSize(aLayerMetrics.GetRootCompositionSize());
-    mFrameMetrics.SetPresShellResolution(aLayerMetrics.GetPresShellResolution());
-    mFrameMetrics.SetCumulativeResolution(aLayerMetrics.GetCumulativeResolution());
+    Metrics().SetCompositionBounds(aLayerMetrics.GetCompositionBounds());
+    Metrics().SetRootCompositionSize(aLayerMetrics.GetRootCompositionSize());
+    Metrics().SetPresShellResolution(aLayerMetrics.GetPresShellResolution());
+    Metrics().SetCumulativeResolution(aLayerMetrics.GetCumulativeResolution());
     mScrollMetadata.SetHasScrollgrab(aScrollMetadata.GetHasScrollgrab());
     mScrollMetadata.SetLineScrollAmount(aScrollMetadata.GetLineScrollAmount());
     mScrollMetadata.SetPageScrollAmount(aScrollMetadata.GetPageScrollAmount());
     mScrollMetadata.SetSnapInfo(ScrollSnapInfo(aScrollMetadata.GetSnapInfo()));
     // The scroll clip can differ between layers associated a given scroll frame,
     // so APZC (which keeps a single copy of ScrollMetadata per scroll frame)
     // has no business using it.
     mScrollMetadata.SetScrollClip(Nothing());
     mScrollMetadata.SetIsLayersIdRoot(aScrollMetadata.IsLayersIdRoot());
     mScrollMetadata.SetIsAutoDirRootContentRTL(
                       aScrollMetadata.IsAutoDirRootContentRTL());
     mScrollMetadata.SetUsesContainerScrolling(aScrollMetadata.UsesContainerScrolling());
-    mFrameMetrics.SetIsScrollInfoLayer(aLayerMetrics.IsScrollInfoLayer());
+    Metrics().SetIsScrollInfoLayer(aLayerMetrics.IsScrollInfoLayer());
     mScrollMetadata.SetForceDisableApz(aScrollMetadata.IsApzForceDisabled());
     mScrollMetadata.SetDisregardedDirection(aScrollMetadata.GetDisregardedDirection());
     mScrollMetadata.SetOverscrollBehavior(aScrollMetadata.GetOverscrollBehavior());
 
     if (scrollOffsetUpdated) {
       APZC_LOG("%p updating scroll offset from %s to %s\n", this,
-        ToString(mFrameMetrics.GetScrollOffset()).c_str(),
+        ToString(Metrics().GetScrollOffset()).c_str(),
         ToString(aLayerMetrics.GetScrollOffset()).c_str());
 
       // Send an acknowledgement with the new scroll generation so that any
       // repaint requests later in this function go through.
       // Because of the scroll generation update, any inflight paint requests are
       // going to be ignored by layout, and so mExpectedGeckoMetrics
       // becomes incorrect for the purposes of calculating the LD transform. To
       // correct this we need to update mExpectedGeckoMetrics to be the
       // last thing we know was painted by Gecko.
       CopyScrollInfoFrom(aLayerMetrics);
-      mCompositedLayoutViewport = mFrameMetrics.GetViewport();
-      mCompositedScrollOffset = mFrameMetrics.GetScrollOffset();
+      mCompositedLayoutViewport = Metrics().GetViewport();
+      mCompositedScrollOffset = Metrics().GetScrollOffset();
       mExpectedGeckoMetrics = aLayerMetrics;
 
       // Cancel the animation (which might also trigger a repaint request)
       // after we update the scroll offset above. Otherwise we can be left
       // in a state where things are out of sync.
       CancelAnimation();
 
       // Since the scroll offset has changed, we need to recompute the
@@ -4248,49 +4247,57 @@ void AsyncPanZoomController::NotifyLayer
       needContentRepaint = true;
       // Since the main-thread scroll offset changed we should trigger a
       // recomposite to make sure it becomes user-visible.
       ScheduleComposite();
     } else if (scrollableRectChanged) {
       // Even if we didn't accept a new scroll offset from content, the
       // scrollable rect may have changed in a way that makes our local
       // scroll offset out of bounds, so re-clamp it.
-      ClampAndSetScrollOffset(mFrameMetrics.GetScrollOffset());
+      ClampAndSetScrollOffset(Metrics().GetScrollOffset());
     }
   }
 
   if (smoothScrollRequested) {
     // A smooth scroll has been requested for animation on the compositor
     // thread.  This flag will be reset by the main thread when it receives
     // the scroll update acknowledgement.
 
     APZC_LOG("%p smooth scrolling from %s to %s in state %d\n", this,
-      Stringify(mFrameMetrics.GetScrollOffset()).c_str(),
+      Stringify(Metrics().GetScrollOffset()).c_str(),
       Stringify(aLayerMetrics.GetSmoothScrollOffset()).c_str(),
       mState);
 
     // See comment on the similar code in the |if (scrollOffsetUpdated)| block
     // above.
-    mFrameMetrics.CopySmoothScrollInfoFrom(aLayerMetrics);
+    Metrics().CopySmoothScrollInfoFrom(aLayerMetrics);
     needContentRepaint = true;
     mExpectedGeckoMetrics = aLayerMetrics;
 
-    SmoothScrollTo(mFrameMetrics.GetSmoothScrollOffset());
+    SmoothScrollTo(Metrics().GetSmoothScrollOffset());
   }
 
   if (needContentRepaint) {
     // This repaint request is not driven by a user action on the APZ side
     RequestContentRepaint(false);
   }
   UpdateSharedCompositorFrameMetrics();
 }
 
+FrameMetrics& AsyncPanZoomController::Metrics() {
+  return mScrollMetadata.GetMetrics();
+}
+
+const FrameMetrics& AsyncPanZoomController::Metrics() const {
+  return mScrollMetadata.GetMetrics();;
+}
+
 const FrameMetrics& AsyncPanZoomController::GetFrameMetrics() const {
   mRecursiveMutex.AssertCurrentThreadIn();
-  return mFrameMetrics;
+  return mScrollMetadata.GetMetrics();;
 }
 
 const ScrollMetadata& AsyncPanZoomController::GetScrollMetadata() const {
   mRecursiveMutex.AssertCurrentThreadIn();
   return mScrollMetadata;
 }
 
 void
@@ -4326,28 +4333,28 @@ void AsyncPanZoomController::ZoomToRect(
     return;
   }
 
   // Only the root APZC is zoomable, and the root APZC is not allowed to have
   // different x and y scales. If it did, the calculations in this function
   // would have to be adjusted (as e.g. it would no longer be valid to take
   // the minimum or maximum of the ratios of the widths and heights of the
   // page rect and the composition bounds).
-  MOZ_ASSERT(mFrameMetrics.IsRootContent());
-  MOZ_ASSERT(mFrameMetrics.GetZoom().AreScalesSame());
+  MOZ_ASSERT(Metrics().IsRootContent());
+  MOZ_ASSERT(Metrics().GetZoom().AreScalesSame());
 
   SetState(ANIMATING_ZOOM);
 
   {
     RecursiveMutexAutoLock lock(mRecursiveMutex);
 
-    ParentLayerRect compositionBounds = mFrameMetrics.GetCompositionBounds();
-    CSSRect cssPageRect = mFrameMetrics.GetScrollableRect();
-    CSSPoint scrollOffset = mFrameMetrics.GetScrollOffset();
-    CSSToParentLayerScale currentZoom = mFrameMetrics.GetZoom().ToScaleFactor();
+    ParentLayerRect compositionBounds = Metrics().GetCompositionBounds();
+    CSSRect cssPageRect = Metrics().GetScrollableRect();
+    CSSPoint scrollOffset = Metrics().GetScrollOffset();
+    CSSToParentLayerScale currentZoom = Metrics().GetZoom().ToScaleFactor();
     CSSToParentLayerScale targetZoom;
 
     // The minimum zoom to prevent over-zoom-out.
     // If the zoom factor is lower than this (i.e. we are zoomed more into the page),
     // then the CSS content rect, in layers pixels, will be smaller than the
     // composition bounds. If this happens, we can't fill the target composited
     // area with this frame.
     CSSToParentLayerScale localMinZoom(std::max(mZoomConstraints.mMinZoom.scale,
@@ -4372,38 +4379,38 @@ void AsyncPanZoomController::ZoomToRect(
       zoomOut = false;
     } else {
       zoomOut = aRect.IsEmpty() ||
         (currentZoom == localMaxZoom && targetZoom >= localMaxZoom) ||
         (currentZoom == localMinZoom && targetZoom <= localMinZoom);
     }
 
     if (zoomOut) {
-      CSSSize compositedSize = mFrameMetrics.CalculateCompositedSizeInCssPixels();
+      CSSSize compositedSize = Metrics().CalculateCompositedSizeInCssPixels();
       float y = scrollOffset.y;
       float newHeight =
         cssPageRect.Width() * (compositedSize.height / compositedSize.width);
       float dh = compositedSize.height - newHeight;
 
       aRect = CSSRect(0.0f,
                       y + dh/2,
                       cssPageRect.Width(),
                       newHeight);
       aRect = aRect.Intersect(cssPageRect);
       targetZoom = CSSToParentLayerScale(std::min(compositionBounds.Width() / aRect.Width(),
                                                   compositionBounds.Height() / aRect.Height()));
     }
 
     targetZoom.scale = clamped(targetZoom.scale, localMinZoom.scale, localMaxZoom.scale);
-    FrameMetrics endZoomToMetrics = mFrameMetrics;
+    FrameMetrics endZoomToMetrics = Metrics();
     if (aFlags & PAN_INTO_VIEW_ONLY) {
       targetZoom = currentZoom;
     } else if(aFlags & ONLY_ZOOM_TO_DEFAULT_SCALE) {
       CSSToParentLayerScale zoomAtDefaultScale =
-        mFrameMetrics.GetDevPixelsPerCSSPixel() * LayoutDeviceToParentLayerScale(1.0);
+        Metrics().GetDevPixelsPerCSSPixel() * LayoutDeviceToParentLayerScale(1.0);
       if (targetZoom.scale > zoomAtDefaultScale.scale) {
         // Only change the zoom if we are less than the default zoom
         if (currentZoom.scale < zoomAtDefaultScale.scale) {
           targetZoom = zoomAtDefaultScale;
         } else {
           targetZoom = currentZoom;
         }
       }
@@ -4430,18 +4437,18 @@ void AsyncPanZoomController::ZoomToRect(
       aRect.MoveToX(std::max(0.f, cssPageRect.Width() - sizeAfterZoom.width));
     }
 
     endZoomToMetrics.SetScrollOffset(aRect.TopLeft());
     endZoomToMetrics.RecalculateViewportOffset();
 
     StartAnimation(new ZoomAnimation(
         *this,
-        mFrameMetrics.GetScrollOffset(),
-        mFrameMetrics.GetZoom(),
+        Metrics().GetScrollOffset(),
+        Metrics().GetZoom(),
         endZoomToMetrics.GetScrollOffset(),
         endZoomToMetrics.GetZoom()));
 
     // Schedule a repaint now, so the new displayport will be painted before the
     // animation finishes.
     ParentLayerPoint velocity(0, 0);
     endZoomToMetrics.SetDisplayPortMargins(
       CalculatePendingDisplayPort(endZoomToMetrics, velocity));
@@ -4557,17 +4564,17 @@ void AsyncPanZoomController::DispatchSta
     } else if (IsTransformingState(aOldState) && !IsTransformingState(aNewState)) {
 #if defined(MOZ_WIDGET_ANDROID)
       // The Android UI thread only shows overlay UI elements when the content is not being
       // panned or zoomed and it is in a steady state. So the FrameMetrics only need to be
       // updated when the transform ends.
       if (APZCTreeManager* manager = GetApzcTreeManager()) {
         AndroidDynamicToolbarAnimator* animator = manager->GetAndroidDynamicToolbarAnimator();
         MOZ_ASSERT(animator);
-        animator->UpdateRootFrameMetrics(mFrameMetrics);
+        animator->UpdateRootFrameMetrics(Metrics());
       }
 #endif
 
       controller->NotifyAPZStateChange(
           GetGuid(), APZStateChange::eTransformEnd);
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
       if (gfxPrefs::HidePluginsForScroll() && mCompositorController) {
         mCompositorController->ScheduleShowAllPluginWindows();
@@ -4588,19 +4595,19 @@ bool AsyncPanZoomController::IsInPanning
 void AsyncPanZoomController::UpdateZoomConstraints(const ZoomConstraints& aConstraints) {
   APZC_LOG("%p updating zoom constraints to %d %d %f %f\n", this, aConstraints.mAllowZoom,
     aConstraints.mAllowDoubleTapZoom, aConstraints.mMinZoom.scale, aConstraints.mMaxZoom.scale);
   if (IsNaN(aConstraints.mMinZoom.scale) || IsNaN(aConstraints.mMaxZoom.scale)) {
     NS_WARNING("APZC received zoom constraints with NaN values; dropping...");
     return;
   }
 
-  CSSToParentLayerScale min = mFrameMetrics.GetDevPixelsPerCSSPixel()
+  CSSToParentLayerScale min = Metrics().GetDevPixelsPerCSSPixel()
     * kViewportMinScale / ParentLayerToScreenScale(1);
-  CSSToParentLayerScale max = mFrameMetrics.GetDevPixelsPerCSSPixel()
+  CSSToParentLayerScale max = Metrics().GetDevPixelsPerCSSPixel()
     * kViewportMaxScale / ParentLayerToScreenScale(1);
 
   // inf float values and other bad cases should be sanitized by the code below.
   mZoomConstraints.mAllowZoom = aConstraints.mAllowZoom;
   mZoomConstraints.mAllowDoubleTapZoom = aConstraints.mAllowDoubleTapZoom;
   mZoomConstraints.mMinZoom = (min > aConstraints.mMinZoom ? min : aConstraints.mMinZoom);
   mZoomConstraints.mMaxZoom = (max > aConstraints.mMaxZoom ? aConstraints.mMaxZoom : max);
   if (mZoomConstraints.mMaxZoom < mZoomConstraints.mMinZoom) {
@@ -4641,29 +4648,29 @@ void AsyncPanZoomController::GetGuid(Scr
 {
   if (aGuidOut) {
     *aGuidOut = GetGuid();
   }
 }
 
 ScrollableLayerGuid AsyncPanZoomController::GetGuid() const
 {
-  return ScrollableLayerGuid(mLayersId, mFrameMetrics);
+  return ScrollableLayerGuid(mLayersId, Metrics());
 }
 
 void AsyncPanZoomController::UpdateSharedCompositorFrameMetrics()
 {
   mRecursiveMutex.AssertCurrentThreadIn();
 
   FrameMetrics* frame = mSharedFrameMetricsBuffer ?
       static_cast<FrameMetrics*>(mSharedFrameMetricsBuffer->memory()) : nullptr;
 
   if (frame && mSharedLock && gfxPrefs::ProgressivePaint()) {
     mSharedLock->Lock();
-    *frame = mFrameMetrics;
+    *frame = Metrics();
     mSharedLock->Unlock();
   }
 }
 
 void AsyncPanZoomController::ShareCompositorFrameMetrics()
 {
   AssertOnUpdaterThread();
 
@@ -4678,17 +4685,17 @@ void AsyncPanZoomController::ShareCompos
     mSharedFrameMetricsBuffer->Create(sizeof(FrameMetrics));
     mSharedFrameMetricsBuffer->Map(sizeof(FrameMetrics));
     frame = static_cast<FrameMetrics*>(mSharedFrameMetricsBuffer->memory());
 
     if (frame) {
 
       { // scope the monitor, only needed to copy the FrameMetrics.
         RecursiveMutexAutoLock lock(mRecursiveMutex);
-        *frame = mFrameMetrics;
+        *frame = Metrics();
       }
 
       // Get the process id of the content process
       base::ProcessId otherPid = mMetricsSharingController->RemotePid();
       ipc::SharedMemoryBasic::Handle mem = ipc::SharedMemoryBasic::NULLHandle();
 
       // Get the shared memory handle to share with the content process
       mSharedFrameMetricsBuffer->ShareToProcess(otherPid, &mem);
@@ -4720,76 +4727,76 @@ AsyncPanZoomController::SetTestAsyncZoom
   mTestAsyncZoom = aZoom;
   ScheduleComposite();
 }
 
 Maybe<CSSPoint> AsyncPanZoomController::FindSnapPointNear(
     const CSSPoint& aDestination, nsIScrollableFrame::ScrollUnit aUnit) {
   mRecursiveMutex.AssertCurrentThreadIn();
   APZC_LOG("%p scroll snapping near %s\n", this, Stringify(aDestination).c_str());
-  CSSRect scrollRange = mFrameMetrics.CalculateScrollRange();
+  CSSRect scrollRange = Metrics().CalculateScrollRange();
   if (Maybe<nsPoint> snapPoint = ScrollSnapUtils::GetSnapPointForDestination(
           mScrollMetadata.GetSnapInfo(),
           aUnit,
-          CSSSize::ToAppUnits(mFrameMetrics.CalculateCompositedSizeInCssPixels()),
+          CSSSize::ToAppUnits(Metrics().CalculateCompositedSizeInCssPixels()),
           CSSRect::ToAppUnits(scrollRange),
-          CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset()),
+          CSSPoint::ToAppUnits(Metrics().GetScrollOffset()),
           CSSPoint::ToAppUnits(aDestination))) {
     CSSPoint cssSnapPoint = CSSPoint::FromAppUnits(snapPoint.ref());
     // GetSnapPointForDestination() can produce a destination that's outside
     // of the scroll frame's scroll range. Clamp it here (this matches the
     // behaviour of the main-thread code path, which clamps it in
     // nsGfxScrollFrame::ScrollTo()).
     return Some(scrollRange.ClampPoint(cssSnapPoint));
   }
   return Nothing();
 }
 
 void AsyncPanZoomController::ScrollSnapNear(const CSSPoint& aDestination) {
   if (Maybe<CSSPoint> snapPoint =
         FindSnapPointNear(aDestination, nsIScrollableFrame::DEVICE_PIXELS)) {
-    if (*snapPoint != mFrameMetrics.GetScrollOffset()) {
+    if (*snapPoint != Metrics().GetScrollOffset()) {
       APZC_LOG("%p smooth scrolling to snap point %s\n", this, Stringify(*snapPoint).c_str());
       SmoothScrollTo(*snapPoint);
     }
   }
 }
 
 void AsyncPanZoomController::ScrollSnap() {
   RecursiveMutexAutoLock lock(mRecursiveMutex);
-  ScrollSnapNear(mFrameMetrics.GetScrollOffset());
+  ScrollSnapNear(Metrics().GetScrollOffset());
 }
 
 void AsyncPanZoomController::ScrollSnapToDestination() {
   RecursiveMutexAutoLock lock(mRecursiveMutex);
 
   float friction = gfxPrefs::APZFlingFriction();
   ParentLayerPoint velocity(mX.GetVelocity(), mY.GetVelocity());
   ParentLayerPoint predictedDelta;
   // "-velocity / log(1.0 - friction)" is the integral of the deceleration
   // curve modeled for flings in the "Axis" class.
   if (velocity.x != 0.0f) {
     predictedDelta.x = -velocity.x / log(1.0 - friction);
   }
   if (velocity.y != 0.0f) {
     predictedDelta.y = -velocity.y / log(1.0 - friction);
   }
-  CSSPoint predictedDestination = mFrameMetrics.GetScrollOffset() + predictedDelta / mFrameMetrics.GetZoom();
+  CSSPoint predictedDestination = Metrics().GetScrollOffset() + predictedDelta / Metrics().GetZoom();
 
   // If the fling will overscroll, don't scroll snap, because then the user
   // user would not see any overscroll animation.
   bool flingWillOverscroll = IsOverscrolled() && ((velocity.x * mX.GetOverscroll() >= 0) ||
                                                   (velocity.y * mY.GetOverscroll() >= 0));
   if (!flingWillOverscroll) {
     APZC_LOG("%p fling snapping.  friction: %f velocity: %f, %f "
              "predictedDelta: %f, %f position: %f, %f "
              "predictedDestination: %f, %f\n",
              this, friction, velocity.x, velocity.y, (float)predictedDelta.x,
-             (float)predictedDelta.y, (float)mFrameMetrics.GetScrollOffset().x,
-             (float)mFrameMetrics.GetScrollOffset().y,
+             (float)predictedDelta.y, (float)Metrics().GetScrollOffset().x,
+             (float)Metrics().GetScrollOffset().y,
              (float)predictedDestination.x, (float)predictedDestination.y);
 
     ScrollSnapNear(predictedDestination);
   }
 }
 
 bool AsyncPanZoomController::MaybeAdjustDeltaForScrollSnapping(
     const ScrollWheelInput& aEvent,
@@ -4798,18 +4805,18 @@ bool AsyncPanZoomController::MaybeAdjust
 {
   // Don't scroll snap for pixel scrolls. This matches the main thread
   // behaviour in EventStateManager::DoScrollText().
   if (aEvent.mDeltaType == ScrollWheelInput::SCROLLDELTA_PIXEL) {
     return false;
   }
 
   RecursiveMutexAutoLock lock(mRecursiveMutex);
-  CSSToParentLayerScale2D zoom = mFrameMetrics.GetZoom();
-  CSSPoint destination = mFrameMetrics.CalculateScrollRange().ClampPoint(
+  CSSToParentLayerScale2D zoom = Metrics().GetZoom();
+  CSSPoint destination = Metrics().CalculateScrollRange().ClampPoint(
       aStartPosition + (aDelta / zoom));
   nsIScrollableFrame::ScrollUnit unit =
       ScrollWheelInput::ScrollUnitForDeltaType(aEvent.mDeltaType);
 
   if (Maybe<CSSPoint> snapPoint = FindSnapPointNear(destination, unit)) {
     aDelta = (*snapPoint - aStartPosition) * zoom;
     aStartPosition = *snapPoint;
     return true;
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -512,16 +512,23 @@ public:
    */
   CSSCoord ConvertScrollbarPoint(const ParentLayerPoint& aScrollbarPoint,
                                  const ScrollbarData& aThumbData) const;
 
   void NotifyMozMouseScrollEvent(const nsString& aString) const;
 
   bool OverscrollBehaviorAllowsSwipe() const;
 
+  //|Metrics()| and |Metrics() const| are getter functions that both return
+  //mScrollMetadata.mMetrics
+
+  const FrameMetrics& Metrics() const;
+  FrameMetrics& Metrics();
+
+
 private:
   // Get whether the horizontal content of the honoured target of auto-dir
   // scrolling starts from right to left. If you don't know of auto-dir
   // scrolling or what a honoured target means,
   // @see mozilla::WheelDeltaAdjustmentStrategy
   bool IsContentOfHonouredTargetRightToLeft(bool aHonoursRoot) const;
 
 protected:
@@ -882,23 +889,22 @@ protected:
 
   /* Utility functions that return a addrefed pointer to the corresponding fields. */
   already_AddRefed<GeckoContentController> GetGeckoContentController() const;
   already_AddRefed<GestureEventListener> GetGestureEventListener() const;
 
   PlatformSpecificStateBase* GetPlatformSpecificState();
 
 protected:
-  // Both |mFrameMetrics| and |mLastContentPaintMetrics| are protected by the
-  // monitor. Do not read from or modify either of them without locking.
+  // Both |mScrollMetadata| and |mLastContentPaintMetrics| are protected by the
+  // monitor. Do not read from or modify them without locking.
   ScrollMetadata mScrollMetadata;
-  FrameMetrics& mFrameMetrics;  // for convenience, refers to mScrollMetadata.mMetrics
 
-  // Protects |mFrameMetrics|, |mLastContentPaintMetrics|, and |mState|.
-  // Before manipulating |mFrameMetrics| or |mLastContentPaintMetrics|, the
+  // Protects |mScrollMetadata|, |mLastContentPaintMetrics| and |mState|.
+  // Before manipulating |mScrollMetadata| or |mLastContentPaintMetrics| the
   // monitor should be held. When setting |mState|, either the SetState()
   // function can be used, or the monitor can be held and then |mState| updated.
   // IMPORTANT: See the note about lock ordering at the top of APZCTreeManager.h.
   // This is mutable to allow entering it from 'const' methods; doing otherwise
   // would significantly limit what methods could be 'const'.
   // FIXME: Please keep in mind that due to some existing coupled relationships
   // among the class members, we should be aware of indirect usage of the
   // monitor-protected members. That is, although this monitor isn't required to
@@ -926,17 +932,17 @@ private:
   // The last metrics used for a content repaint request.
   FrameMetrics mLastPaintRequestMetrics;
   // The metrics that we expect content to have. This is updated when we
   // request a content repaint, and when we receive a shadow layers update.
   // This allows us to transform events into Gecko's coordinate space.
   FrameMetrics mExpectedGeckoMetrics;
 
   // These variables cache the layout viewport, scroll offset, and zoom stored
-  // in |mFrameMetrics| the last time SampleCompositedAsyncTransform() was
+  // in |Metrics()| the last time SampleCompositedAsyncTransform() was
   // called.
   CSSRect mCompositedLayoutViewport;
   CSSPoint mCompositedScrollOffset;
   CSSToParentLayerScale2D mCompositedZoom;
 
   AxisX mX;
   AxisY mY;
 
@@ -1054,30 +1060,30 @@ public:
    * any transform due to axis over-scroll.
    */
   AsyncTransformComponentMatrix GetCurrentAsyncTransformWithOverscroll(AsyncTransformConsumer aMode) const;
 
 private:
   /**
    * Samples the composited async transform, making the result of
    * |GetCurrentAsyncTransform(eForCompositing)| and similar functions reflect
-   * the async scroll offset and zoom stored in |mFrameMetrics|.
+   * the async scroll offset and zoom stored in |Metrics()|.
    *
    * Returns true if the newly sampled value is different from the previously
    * sampled value.
    *
    * (This is only relevant when |gfxPrefs::APZFrameDelayEnabled() == true|.
    * Otherwise, GetCurrentAsyncTransform() always reflects what's stored in
-   * |mFrameMetrics| immediately, without any delay.)
+   * |Metrics()| immediately, without any delay.)
    */
   bool SampleCompositedAsyncTransform();
 
   /*
    * Helper functions to query the async layout viewport, scroll offset, and
-   * zoom either directly from |mFrameMetrics|, or from cached variables that
+   * zoom either directly from |Metrics()|, or from cached variables that
    * store the required value from the last time it was sampled by calling
    * SampleCompositedAsyncTransform(), depending on who is asking.
    */
   CSSRect GetEffectiveLayoutViewport(AsyncTransformConsumer aMode) const;
   CSSPoint GetEffectiveScrollOffset(AsyncTransformConsumer aMode) const;
   CSSToParentLayerScale2D GetEffectiveZoom(AsyncTransformConsumer aMode) const;
 
   /* ===================================================================
@@ -1268,17 +1274,17 @@ public:
 
   bool IsRootForLayersId() const {
     RecursiveMutexAutoLock lock(mRecursiveMutex);
     return mScrollMetadata.IsLayersIdRoot();
   }
 
   bool IsRootContent() const {
     RecursiveMutexAutoLock lock(mRecursiveMutex);
-    return mFrameMetrics.IsRootContent();
+    return Metrics().IsRootContent();
   }
 
 private:
   // |mTreeManager| belongs in this section but it's declaration is a bit
   // further above due to initialization-order constraints.
 
   RefPtr<AsyncPanZoomController> mParent;
 
@@ -1422,17 +1428,17 @@ private:
 private:
   /* Unique id assigned to each APZC. Used with ViewID to uniquely identify
    * shared FrameMeterics used in progressive tile painting. */
   const uint32_t mAPZCId;
 
   RefPtr<ipc::SharedMemoryBasic> mSharedFrameMetricsBuffer;
   CrossProcessMutex* mSharedLock;
   /**
-   * Called when ever mFrameMetrics is updated so that if it is being
+   * Called when ever Metrics() is updated so that if it is being
    * shared with the content process the shared FrameMetrics may be updated.
    */
   void UpdateSharedCompositorFrameMetrics();
   /**
    * Create a shared memory buffer for containing the FrameMetrics and
    * a CrossProcessMutex that may be shared with the content process
    * for use in progressive tiled update calculations.
    */
--- a/gfx/layers/apz/src/Axis.h
+++ b/gfx/layers/apz/src/Axis.h
@@ -99,17 +99,17 @@ public:
    * The axis must be at the end of its scroll range in this direction.
    */
   void OverscrollBy(ParentLayerCoord aOverscroll);
 
   /**
    * Return the amount of overscroll on this axis, in ParentLayer pixels.
    *
    * If this amount is nonzero, the relevant component of
-   * mAsyncPanZoomController->mFrameMetrics.mScrollOffset must be at its
+   * mAsyncPanZoomController->Metrics().mScrollOffset must be at its
    * extreme allowed value in the relevant direction (that is, it must be at
    * its maximum value if we are overscrolled at our composition length, and
    * at its minimum value if we are overscrolled at the origin).
    */
   ParentLayerCoord GetOverscroll() const;
 
   /**
    * Start an overscroll animation with the given initial velocity.
--- a/gfx/layers/apz/test/gtest/APZTestCommon.h
+++ b/gfx/layers/apz/test/gtest/APZTestCommon.h
@@ -222,16 +222,17 @@ public:
 protected:
   AsyncPanZoomController* NewAPZCInstance(LayersId aLayersId,
                                           GeckoContentController* aController) override;
 
   TimeStamp GetFrameTime() override {
     return mcc->Time();
   }
 
+
 private:
   RefPtr<MockContentControllerDelayed> mcc;
 };
 
 class TestAsyncPanZoomController : public AsyncPanZoomController {
 public:
   TestAsyncPanZoomController(LayersId aLayersId, MockContentControllerDelayed* aMcc,
                              TestAPZCTreeManager* aTreeManager,
@@ -264,32 +265,32 @@ public:
   }
 
   void SetAllowedTouchBehavior(uint64_t aInputBlockId, const nsTArray<TouchBehaviorFlags>& aBehaviors) {
     GetInputQueue()->SetAllowedTouchBehavior(aInputBlockId, aBehaviors);
   }
 
   void SetFrameMetrics(const FrameMetrics& metrics) {
     RecursiveMutexAutoLock lock(mRecursiveMutex);
-    mFrameMetrics = metrics;
+    Metrics() = metrics;
   }
 
   FrameMetrics& GetFrameMetrics() {
     RecursiveMutexAutoLock lock(mRecursiveMutex);
-    return mFrameMetrics;
+    return mScrollMetadata.GetMetrics();
   }
 
   ScrollMetadata& GetScrollMetadata() {
     RecursiveMutexAutoLock lock(mRecursiveMutex);
     return mScrollMetadata;
   }
 
   const FrameMetrics& GetFrameMetrics() const {
     RecursiveMutexAutoLock lock(mRecursiveMutex);
-    return mFrameMetrics;
+    return mScrollMetadata.GetMetrics();
   }
 
   using AsyncPanZoomController::GetVelocityVector;
 
   void AssertStateIsReset() const {
     RecursiveMutexAutoLock lock(mRecursiveMutex);
     EXPECT_EQ(NOTHING, mState);
   }
--- a/gfx/src/FilterSupport.cpp
+++ b/gfx/src/FilterSupport.cpp
@@ -4,16 +4,17 @@
  * 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 "FilterSupport.h"
 
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Filters.h"
 #include "mozilla/gfx/Logging.h"
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/PodOperations.h"
 
 #include "gfxContext.h"
 #include "gfxPattern.h"
 #include "gfxPlatform.h"
 #include "gfx2DGlue.h"
 
 #include "nsMargin.h"
@@ -758,17 +759,17 @@ FilterNodeFromPrimitiveDescription(const
     }
 
     case PrimitiveType::ColorMatrix:
     {
       float colorMatrix[20];
       uint32_t type = atts.GetUint(eColorMatrixType);
       const nsTArray<float>& values = atts.GetFloats(eColorMatrixValues);
       if (NS_FAILED(ComputeColorMatrix(type, values, colorMatrix)) ||
-          PodEqual(colorMatrix, identityMatrix)) {
+          ArrayEqual(colorMatrix, identityMatrix)) {
         RefPtr<FilterNode> filter(aSources[0]);
         return filter.forget();
       }
       Matrix5x4 matrix(colorMatrix[0], colorMatrix[5], colorMatrix[10],  colorMatrix[15],
                        colorMatrix[1], colorMatrix[6], colorMatrix[11],  colorMatrix[16],
                        colorMatrix[2], colorMatrix[7], colorMatrix[12],  colorMatrix[17],
                        colorMatrix[3], colorMatrix[8], colorMatrix[13],  colorMatrix[18],
                        colorMatrix[4], colorMatrix[9], colorMatrix[14],  colorMatrix[19]);
@@ -969,17 +970,17 @@ FilterNodeFromPrimitiveDescription(const
       uint32_t op = atts.GetUint(eCompositeOperator);
       if (op == SVG_FECOMPOSITE_OPERATOR_ARITHMETIC) {
         const nsTArray<float>& coefficients = atts.GetFloats(eCompositeCoefficients);
         static const float allZero[4] = { 0, 0, 0, 0 };
         filter = aDT->CreateFilter(FilterType::ARITHMETIC_COMBINE);
         // All-zero coefficients sometimes occur in junk filters.
         if (!filter ||
             (coefficients.Length() == ArrayLength(allZero) &&
-             PodEqual(coefficients.Elements(), allZero, ArrayLength(allZero)))) {
+             ArrayEqual(coefficients.Elements(), allZero, ArrayLength(allZero)))) {
           return nullptr;
         }
         filter->SetAttribute(ATT_ARITHMETIC_COMBINE_COEFFICIENTS,
                              coefficients.Elements(), coefficients.Length());
         filter->SetInput(IN_ARITHMETIC_COMBINE_IN, aSources[0]);
         filter->SetInput(IN_ARITHMETIC_COMBINE_IN2, aSources[1]);
       } else {
         filter = aDT->CreateFilter(FilterType::COMPOSITE);
--- a/gfx/tests/gtest/TestSwizzle.cpp
+++ b/gfx/tests/gtest/TestSwizzle.cpp
@@ -1,17 +1,17 @@
 /* -*- 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/. */
 
 #include "gtest/gtest.h"
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/gfx/Swizzle.h"
-#include "mozilla/PodOperations.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
 TEST(Moz2D, PremultiplyData) {
   const uint8_t in_bgra[5*4] =
   {
     255, 255,   0, 255, // verify 255 alpha leaves RGB unchanged
@@ -45,27 +45,27 @@ TEST(Moz2D, PremultiplyData) {
       0,   0,   0,   0,
       0,   0,   0,   0,
     128,   0,   0, 128,
   };
 
   PremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8,
                   out, sizeof(in_bgra), SurfaceFormat::B8G8R8A8,
                   IntSize(5, 1));
-  EXPECT_TRUE(PodEqual(out, check_bgra));
+  EXPECT_TRUE(ArrayEqual(out, check_bgra));
 
   PremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8,
                   out, sizeof(in_bgra), SurfaceFormat::R8G8B8A8,
                   IntSize(5, 1));
-  EXPECT_TRUE(PodEqual(out, check_rgba));
+  EXPECT_TRUE(ArrayEqual(out, check_rgba));
 
   PremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8,
                   out, sizeof(in_bgra), SurfaceFormat::A8R8G8B8,
                   IntSize(5, 1));
-  EXPECT_TRUE(PodEqual(out, check_argb));
+  EXPECT_TRUE(ArrayEqual(out, check_argb));
 }
 
 TEST(Moz2D, UnpremultiplyData) {
   const uint8_t in_bgra[5*4] =
   {
     255, 255,   0, 255, // verify 255 alpha leaves RGB unchanged
       0,   0, 255, 255,
       0,   0,   0,   0, // verify 0 alpha leaves RGB at 0
@@ -98,27 +98,27 @@ TEST(Moz2D, UnpremultiplyData) {
       0,   0,   0,   0,
      64,   0,   0,   0,
     128,   0,   0, 255,
   };
 
   UnpremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8,
                     out, sizeof(in_bgra), SurfaceFormat::B8G8R8A8,
                     IntSize(5, 1));
-  EXPECT_TRUE(PodEqual(out, check_bgra));
+  EXPECT_TRUE(ArrayEqual(out, check_bgra));
 
   UnpremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8,
                     out, sizeof(in_bgra), SurfaceFormat::R8G8B8A8,
                     IntSize(5, 1));
-  EXPECT_TRUE(PodEqual(out, check_rgba));
+  EXPECT_TRUE(ArrayEqual(out, check_rgba));
 
   UnpremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8,
                     out, sizeof(in_bgra), SurfaceFormat::A8R8G8B8,
                     IntSize(5, 1));
-  EXPECT_TRUE(PodEqual(out, check_argb));
+  EXPECT_TRUE(ArrayEqual(out, check_argb));
 }
 
 TEST(Moz2D, SwizzleData) {
   const uint8_t in_bgra[5*4] =
   {
     253, 254,   0, 255,
       0,   0, 255, 255,
       0,   0,   0,   0,
@@ -194,47 +194,47 @@ TEST(Moz2D, SwizzleData) {
     PACK_RGB565(0, 0, 0),
     PACK_RGB565(1, 2, 3),
     PACK_RGB565(127, 0, 9),
   };
 
   SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8,
               out, sizeof(out), SurfaceFormat::B8G8R8A8,
               IntSize(5, 1));
-  EXPECT_TRUE(PodEqual(out, check_bgra));
+  EXPECT_TRUE(ArrayEqual(out, check_bgra));
 
   SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8,
               out, sizeof(out), SurfaceFormat::R8G8B8A8,
               IntSize(5, 1));
-  EXPECT_TRUE(PodEqual(out, check_rgba));
+  EXPECT_TRUE(ArrayEqual(out, check_rgba));
 
   SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8,
               out, sizeof(out), SurfaceFormat::A8R8G8B8,
               IntSize(5, 1));
-  EXPECT_TRUE(PodEqual(out, check_argb));
+  EXPECT_TRUE(ArrayEqual(out, check_argb));
 
   SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8,
               out, sizeof(out), SurfaceFormat::R8G8B8X8,
               IntSize(5, 1));
-  EXPECT_TRUE(PodEqual(out, check_rgbx));
+  EXPECT_TRUE(ArrayEqual(out, check_rgbx));
 
   SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8,
               out24, sizeof(out24), SurfaceFormat::B8G8R8,
               IntSize(5, 1));
-  EXPECT_TRUE(PodEqual(out24, check_bgr));
+  EXPECT_TRUE(ArrayEqual(out24, check_bgr));
 
   SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8,
               out24, sizeof(out24), SurfaceFormat::R8G8B8,
               IntSize(5, 1));
-  EXPECT_TRUE(PodEqual(out24, check_rgb));
+  EXPECT_TRUE(ArrayEqual(out24, check_rgb));
 
   SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8,
               out8, sizeof(out8), SurfaceFormat::A8,
               IntSize(5, 1));
-  EXPECT_TRUE(PodEqual(out8, check_a));
+  EXPECT_TRUE(ArrayEqual(out8, check_a));
 
   SwizzleData(SurfaceFormat::A8R8G8B8_UINT32 == SurfaceFormat::A8R8G8B8 ? check_argb : check_bgra,
               sizeof(in_bgra), SurfaceFormat::A8R8G8B8_UINT32,
               reinterpret_cast<uint8_t*>(out16), sizeof(out16), SurfaceFormat::R5G6B5_UINT16,
               IntSize(5, 1));
-  EXPECT_TRUE(PodEqual(out16, check_16));
+  EXPECT_TRUE(ArrayEqual(out16, check_16));
 }
 
--- a/js/public/UbiNode.h
+++ b/js/public/UbiNode.h
@@ -479,23 +479,24 @@ ConstructSavedFrameStackSlow(JSContext* 
 // if a concrete specialization does not provide its own mapping to a CoarseType
 // variant, is "Other".
 //
 // NB: the values associated with a particular enum variant must not change or
 // be reused for new variants. Doing so will cause inspecting ubi::Nodes backed
 // by an offline heap snapshot from an older SpiderMonkey/Firefox version to
 // break. Consider this enum append only.
 enum class CoarseType: uint32_t {
-    Other  = 0,
-    Object = 1,
-    Script = 2,
-    String = 3,
+    Other   = 0,
+    Object  = 1,
+    Script  = 2,
+    String  = 3,
+    DOMNode = 4,
 
-    FIRST  = Other,
-    LAST   = String
+    FIRST   = Other,
+    LAST    = DOMNode
 };
 
 inline uint32_t
 CoarseTypeToUint32(CoarseType type)
 {
     return static_cast<uint32_t>(type);
 }
 
@@ -614,16 +615,23 @@ class JS_PUBLIC_API(Base) {
 
     // Get the stack recorded at the time this node's referent was
     // allocated. This must only be called when hasAllocationStack() is true.
     virtual StackFrame allocationStack() const {
         MOZ_CRASH("Concrete classes that have an allocation stack must override both "
                   "hasAllocationStack and allocationStack.");
     }
 
+    // In some cases, Concrete<T> can return a more descriptive
+    // referent type name than simply `T`. This method returns an
+    // identifier as specific as is efficiently available.
+    // The string returned is borrowed from the ubi::Node's referent.
+    // If nothing more specific than typeName() is available, return nullptr.
+    virtual const char16_t* descriptiveTypeName() const { return nullptr; }
+
     // Methods for JSObject Referents
     //
     // These methods are only semantically valid if the referent is either a
     // JSObject in the live heap, or represents a previously existing JSObject
     // from some deserialized heap snapshot.
 
     // Return the object's [[Class]]'s name.
     virtual const char* jsObjectClassName() const { return nullptr; }
@@ -773,22 +781,23 @@ class Node {
     }
 
     // If this node refers to something that can be represented as a JavaScript
     // value that is safe to expose to JavaScript code, return that value.
     // Otherwise return UndefinedValue(). JSStrings, JS::Symbols, and some (but
     // not all!) JSObjects can be exposed.
     JS::Value exposeToJS() const;
 
-    CoarseType coarseType()         const { return base()->coarseType(); }
-    const char16_t* typeName()      const { return base()->typeName(); }
-    JS::Zone* zone()                const { return base()->zone(); }
-    JS::Compartment* compartment()  const { return base()->compartment(); }
-    JS::Realm* realm()              const { return base()->realm(); }
-    const char* jsObjectClassName() const { return base()->jsObjectClassName(); }
+    CoarseType coarseType()               const { return base()->coarseType(); }
+    const char16_t* typeName()            const { return base()->typeName(); }
+    JS::Zone* zone()                      const { return base()->zone(); }
+    JS::Compartment* compartment()        const { return base()->compartment(); }
+    JS::Realm* realm()                    const { return base()->realm(); }
+    const char* jsObjectClassName()       const { return base()->jsObjectClassName(); }
+    const char16_t* descriptiveTypeName() const { return base()->descriptiveTypeName(); }
     MOZ_MUST_USE bool jsObjectConstructorName(JSContext* cx, UniqueTwoByteChars& outName) const {
         return base()->jsObjectConstructorName(cx, outName);
     }
 
     const char* scriptFilename() const { return base()->scriptFilename(); }
 
     using Size = Base::Size;
     Size size(mozilla::MallocSizeOf mallocSizeof) const {
--- a/js/public/UbiNodeCensus.h
+++ b/js/public/UbiNodeCensus.h
@@ -25,16 +25,18 @@
 // example, the following breakdown might give an interesting overview of the
 // heap:
 //
 //   - all nodes
 //     - objects
 //       - objects with a specific [[Class]] *
 //     - strings
 //     - scripts
+//     - DOM nodes
+//       - nsINodes with a specific name (found in nsINode::NodeName()) *
 //     - all other Node types
 //       - nodes with a specific ubi::Node::typeName *
 //
 // Obviously, the parts of this tree marked with * represent many separate
 // counts, depending on how many distinct [[Class]] values and ubi::Node type
 // names we encounter.
 //
 // The supported types of breakdowns are documented in
@@ -42,28 +44,31 @@
 //
 // When we parse the 'breakdown' argument to takeCensus, we build a tree of
 // CountType nodes. For example, for the breakdown shown in the
 // Debugger.Memory.prototype.takeCensus, documentation:
 //
 //    {
 //      by: "coarseType",
 //      objects: { by: "objectClass" },
-//      other:    { by: "internalType" }
+//      other:    { by: "internalType" },
+//      domNode: { by: "descriptiveType" }
 //    }
 //
 // we would build the following tree of CountType subclasses:
 //
 //    ByCoarseType
 //      objects: ByObjectClass
 //        each class: SimpleCount
 //      scripts: SimpleCount
 //      strings: SimpleCount
 //      other: ByUbinodeType
 //        each type: SimpleCount
+//      domNode: ByDomObjectClass
+//        each type: SimpleCount
 //
 // The interior nodes are all breakdown types that categorize nodes according to
 // one characteristic or another; and the leaf nodes are all SimpleType.
 //
 // Each CountType has its own concrete C++ type that holds the counts it
 // produces. SimpleCount::Count just holds totals. ByObjectClass::Count has a
 // hash table whose keys are object class names and whose values are counts of
 // some other type (in the example above, SimpleCount).
--- a/js/src/doc/Debugger/Debugger.Memory.md
+++ b/js/src/doc/Debugger/Debugger.Memory.md
@@ -270,23 +270,25 @@ Function Properties of the `Debugger.Mem
     count of debuggee items:
 
         dbg.memory.takeCensus({ breakdown: { by: 'count' } })
 
     That might produce a result like:
 
         { "count": 1616, "bytes": 93240 }
 
-    Here is a breakdown that groups JavaScript objects by their class name, and
-    non-string, non-script items by their C++ type name:
+    Here is a breakdown that groups JavaScript objects by their class name,
+    non-string, non-script items by their C++ type name, and DOM nodes with
+    their node name:
 
         {
           by: "coarseType",
           objects: { by: "objectClass" },
-          other:   { by: "internalType" }
+          other:   { by: "internalType" },
+          domNode: { by: "descriptiveType" }
         }
 
     which produces a result like this:
 
         {
           "objects": {
             "Function":         { "count": 404, "bytes": 37328 },
             "Object":           { "count": 11,  "bytes": 1264 },
@@ -295,16 +297,19 @@ Function Properties of the `Debugger.Mem
             // ... omitted for brevity...
           },
           "scripts":            { "count": 1,   "bytes": 0 },
           "strings":            { "count": 701, "bytes": 49080 },
           "other": {
             "js::Shape":        { "count": 450, "bytes": 0 },
             "js::BaseShape":    { "count": 21,  "bytes": 0 },
             "js::ObjectGroup":  { "count": 17,  "bytes": 0 }
+          },
+          "domNode": {
+            "#text":            { "count": 1,   "bytes": 12 }
           }
         }
 
     In general, a `breakdown` value has one of the following forms:
 
     <code>{ by: "count", count:<i>count<i>, bytes:<i>bytes</i> }</code>
     :   The trivial categorization: none whatsoever. Simply tally up the items
         visited. If <i>count</i> is true, count the number of items visited; if
@@ -365,39 +370,42 @@ Function Properties of the `Debugger.Mem
         objects using <i>otherBreakdown</i>.
 
         In the result of the census, this breakdown produces a JavaScript object
         with no prototype whose own property names are strings naming classes,
         and whose values are whatever sort of result <i>breakdown</i> produces.
         The results for non-object items appear as the value of the property
         named `"other"`.
 
-    <code>{ by: "coarseType", objects:<i>objects</i>, scripts:<i>scripts</i>, strings:<i>strings</i>, other:<i>other</i> }</code>
+    <code>{ by: "coarseType", objects:<i>objects</i>, scripts:<i>scripts</i>, strings:<i>strings</i>, domNode:<i>domNode</i>, other:<i>other</i> }</code>
     :   Group items by their coarse type.
 
         Use the breakdown value <i>objects</i> for items that are JavaScript
         objects.
 
         Use the breakdown value <i>scripts</i> for items that are
         representations of JavaScript code. This includes bytecode, compiled
         machine code, and saved source code.
 
         Use the breakdown value <i>strings</i> for JavaScript strings.
 
+        Use the breakdown value <i>domNode</i> for DOM nodes.
+
         Use the breakdown value <i>other</i> for items that don't fit into any of
         the above categories.
 
         In the result of the census, this breakdown produces a JavaScript object
         of the form:
 
         <pre class='language-js'><code>
         {
           "objects": <i>result</i>,
           "scripts": <i>result</i>,
           "strings": <i>result</i>,
+          "domNode:" <i>result</i>,
           "other": <i>result</i>
         }
         </code></pre>
 
         where each <i>result</i> is a value of whatever sort the corresponding
         breakdown value produces. All breakdown values are optional, and default
         to `{ type: "count" }`.
 
@@ -433,27 +441,29 @@ Function Properties of the `Debugger.Mem
 
     If the `options` argument has no `breakdown` property, `takeCensus` defaults
     to the following:
 
     <pre class='language-js'><code>
     {
       by: "coarseType",
       objects: { by: "objectClass" },
+      domNode: { by: "descriptiveType" },
       other:   { by: "internalType" }
     }
     </code></pre>
 
     which produces results of the form:
 
     <pre class='language-js'><code>
     {
       objects: { <i>class</i>: <i>count</i>, ... },
       scripts: <i>count</i>,
       strings: <i>count</i>,
+      domNode: { <i>node name</i>: <i>count</i>, ... },
       other:   { <i>type name</i>: <i>count</i>, ... }
     }
     </code></pre>
 
     where each <i>count</i> has the form:
 
     <pre class='language-js'><code>
     { "count": <i>count</i>, bytes:<i>bytes</i> }
--- a/js/src/jit/CacheIRCompiler.cpp
+++ b/js/src/jit/CacheIRCompiler.cpp
@@ -1,16 +1,17 @@
 /* -*- 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/. */
 
 #include "jit/CacheIRCompiler.h"
 
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/ScopeExit.h"
 
 #include <utility>
 
 #include "jslibmath.h"
 #include "jit/IonIC.h"
 #include "jit/SharedICHelpers.h"
 
@@ -1160,17 +1161,17 @@ CacheIRStubKey::match(const CacheIRStubK
         return false;
 
     if (entry.stubInfo->engine() != l.engine)
         return false;
 
     if (entry.stubInfo->codeLength() != l.length)
         return false;
 
-    if (!mozilla::PodEqual(entry.stubInfo->code(), l.code, l.length))
+    if (!mozilla::ArrayEqual(entry.stubInfo->code(), l.code, l.length))
         return false;
 
     return true;
 }
 
 CacheIRReader::CacheIRReader(const CacheIRStubInfo* stubInfo)
   : CacheIRReader(stubInfo->code(), stubInfo->code() + stubInfo->codeLength())
 {}
--- a/js/src/jsapi-tests/testExternalStrings.cpp
+++ b/js/src/jsapi-tests/testExternalStrings.cpp
@@ -1,19 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/ArrayUtils.h"
-#include "mozilla/PodOperations.h"
 
 #include "jsapi-tests/tests.h"
 
+using mozilla::ArrayEqual;
 using mozilla::ArrayLength;
-using mozilla::PodEqual;
 
 static const char16_t arr[] = {
     'h', 'i', ',', 'd', 'o', 'n', '\'', 't', ' ', 'd', 'e', 'l', 'e', 't', 'e', ' ', 'm', 'e', '\0'
 };
 static const size_t arrlen = ArrayLength(arr) - 1;
 
 static int finalized1 = 0;
 static int finalized2 = 0;
@@ -22,17 +21,17 @@ static void
 finalize_str(const JSStringFinalizer* fin, char16_t* chars);
 
 static const JSStringFinalizer finalizer1 = { finalize_str };
 static const JSStringFinalizer finalizer2 = { finalize_str };
 
 static void
 finalize_str(const JSStringFinalizer* fin, char16_t* chars)
 {
-    if (chars && PodEqual(const_cast<const char16_t*>(chars), arr, arrlen)) {
+    if (chars && ArrayEqual(chars, arr, arrlen)) {
         if (fin == &finalizer1) {
             ++finalized1;
         } else if (fin == &finalizer2) {
             ++finalized2;
         }
     }
 }
 
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -122,24 +122,23 @@
 #include "vm/Stack-inl.h"
 
 using namespace js;
 using namespace js::cli;
 using namespace js::shell;
 
 using js::shell::RCFile;
 
+using mozilla::ArrayEqual;
 using mozilla::ArrayLength;
 using mozilla::Atomic;
 using mozilla::MakeScopeExit;
 using mozilla::Maybe;
 using mozilla::Nothing;
 using mozilla::NumberEqualsInt32;
-using mozilla::PodCopy;
-using mozilla::PodEqual;
 using mozilla::TimeDuration;
 using mozilla::TimeStamp;
 
 // Avoid an unnecessary NSPR dependency on Linux and OS X just for the shell.
 #ifdef JS_POSIX_NSPR
 
 enum PRLibSpecType { PR_LibSpec_Pathname };
 
@@ -2069,17 +2068,17 @@ Evaluate(JSContext* cx, unsigned argc, V
                 char saveLengthStr[16];
                 SprintfLiteral(saveLengthStr,"%zu", saveBuffer.length());
 
                 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_CACHE_EQ_SIZE_FAILED,
                                           loadLengthStr, saveLengthStr);
                 return false;
             }
 
-            if (!PodEqual(loadBuffer.begin(), saveBuffer.begin(), loadBuffer.length())) {
+            if (!ArrayEqual(loadBuffer.begin(), saveBuffer.begin(), loadBuffer.length())) {
                 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
                                           JSSMSG_CACHE_EQ_CONTENT_FAILED);
                 return false;
             }
         }
 
         size_t saveLength = saveBuffer.length();
         if (saveLength >= INT32_MAX) {
@@ -5577,17 +5576,17 @@ SingleStepCallback(void* arg, jit::Simul
         }
     }
 
     ShellContext* sc = GetShellContext(cx);
 
     // Only append the stack if it differs from the last stack.
     if (sc->stacks.empty() ||
         sc->stacks.back().length() != stack.length() ||
-        !PodEqual(sc->stacks.back().begin(), stack.begin(), stack.length()))
+        !ArrayEqual(sc->stacks.back().begin(), stack.begin(), stack.length()))
     {
         if (!sc->stacks.append(std::move(stack)))
             oomUnsafe.crash("stacks.append");
     }
 }
 #endif
 
 static bool
--- a/js/src/util/Text.h
+++ b/js/src/util/Text.h
@@ -2,16 +2,17 @@
  * 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 util_Text_h
 #define util_Text_h
 
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/TextUtils.h"
 
 #include <ctype.h>
 #include <stddef.h>
 #include <stdint.h>
 #include <stdio.h>
@@ -51,34 +52,19 @@ extern int32_t
 js_fputs(const char16_t* s, FILE* f);
 
 namespace js {
 
 class StringBuffer;
 
 template <typename Char1, typename Char2>
 inline bool
-EqualChars(const Char1* s1, const Char2* s2, size_t len);
-
-template <typename Char1>
-inline bool
-EqualChars(const Char1* s1, const Char1* s2, size_t len)
-{
-    return mozilla::PodEqual(s1, s2, len);
-}
-
-template <typename Char1, typename Char2>
-inline bool
 EqualChars(const Char1* s1, const Char2* s2, size_t len)
 {
-    for (const Char1* s1end = s1 + len; s1 < s1end; s1++, s2++) {
-        if (*s1 != *s2)
-            return false;
-    }
-    return true;
+    return mozilla::ArrayEqual(s1, s2, len);
 }
 
 // Return less than, equal to, or greater than zero depending on whether
 // s1 is less than, equal to, or greater than s2.
 template <typename Char1, typename Char2>
 inline int32_t
 CompareChars(const Char1* s1, size_t len1, const Char2* s2, size_t len2)
 {
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -97,16 +97,17 @@
     macro(defineGetter, defineGetter, "__defineGetter__") \
     macro(defineProperty, defineProperty, "defineProperty") \
     macro(defineSetter, defineSetter, "__defineSetter__") \
     macro(delete, delete_, "delete") \
     macro(deleteProperty, deleteProperty, "deleteProperty") \
     macro(direction, direction, "direction") \
     macro(displayURL, displayURL, "displayURL") \
     macro(do, do_, "do") \
+    macro(domNode, domNode, "domNode") \
     macro(done, done, "done") \
     macro(dotGenerator, dotGenerator, ".generator") \
     macro(dotThis, dotThis, ".this") \
     macro(each, each, "each") \
     macro(elementType, elementType, "elementType") \
     macro(else, else_, "else") \
     macro(empty, empty, "") \
     macro(emptyRegExp, emptyRegExp, "(?:)") \
--- a/js/src/vm/Iteration.cpp
+++ b/js/src/vm/Iteration.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/. */
 
 /* JavaScript iterators. */
 
 #include "vm/Iteration.h"
 
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Likely.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Unused.h"
 
 #include <algorithm>
@@ -42,20 +43,20 @@
 #include "vm/NativeObject-inl.h"
 #include "vm/ReceiverGuard-inl.h"
 #include "vm/Stack-inl.h"
 #include "vm/StringType-inl.h"
 
 using namespace js;
 using namespace js::gc;
 
+using mozilla::ArrayEqual;
 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.
@@ -788,18 +789,18 @@ js::NewEmptyPropertyIterator(JSContext* 
 
 /* static */ bool
 IteratorHashPolicy::match(PropertyIteratorObject* obj, const Lookup& lookup)
 {
     NativeIterator* ni = obj->getNativeIterator();
     if (ni->guardKey() != lookup.key || ni->guardCount() != lookup.numGuards)
         return false;
 
-    return PodEqual(reinterpret_cast<ReceiverGuard*>(ni->guardsBegin()), lookup.guards,
-                    ni->guardCount());
+    return ArrayEqual(reinterpret_cast<ReceiverGuard*>(ni->guardsBegin()), lookup.guards,
+                      ni->guardCount());
 }
 
 static inline bool
 CanCompareIterableObjectToCache(JSObject* obj)
 {
     if (obj->isNative())
         return obj->as<NativeObject>().hasEmptyElements();
     if (obj->is<UnboxedPlainObject>()) {
--- a/js/src/vm/JSAtom.cpp
+++ b/js/src/vm/JSAtom.cpp
@@ -91,24 +91,24 @@ js::AtomHasher::match(const AtomStateEnt
     if (lookup.atom)
         return lookup.atom == key;
     if (key->length() != lookup.length || key->hash() != lookup.hash)
         return false;
 
     if (key->hasLatin1Chars()) {
         const Latin1Char* keyChars = key->latin1Chars(lookup.nogc);
         if (lookup.isLatin1)
-            return mozilla::PodEqual(keyChars, lookup.latin1Chars, lookup.length);
+            return mozilla::ArrayEqual(keyChars, lookup.latin1Chars, lookup.length);
         return EqualChars(keyChars, lookup.twoByteChars, lookup.length);
     }
 
     const char16_t* keyChars = key->twoByteChars(lookup.nogc);
     if (lookup.isLatin1)
         return EqualChars(lookup.latin1Chars, keyChars, lookup.length);
-    return mozilla::PodEqual(keyChars, lookup.twoByteChars, lookup.length);
+    return mozilla::ArrayEqual(keyChars, lookup.twoByteChars, lookup.length);
 }
 
 inline JSAtom*
 js::AtomStateEntry::asPtr(JSContext* cx) const
 {
     JSAtom* atom = asPtrUnbarriered();
     if (!cx->helperThread())
         JSString::readBarrier(atom);
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -4,20 +4,20 @@
  * 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/. */
 
 /* JS script descriptor. */
 
 #ifndef vm_JSScript_h
 #define vm_JSScript_h
 
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
-#include "mozilla/PodOperations.h"
 #include "mozilla/Variant.h"
 
 #include "jstypes.h"
 
 #include "frontend/NameAnalysisTypes.h"
 #include "gc/Barrier.h"
 #include "gc/Rooting.h"
 #include "jit/IonCode.h"
@@ -881,17 +881,17 @@ struct ScriptBytecodeHasher
     static bool match(SharedScriptData* entry, const Lookup& lookup) {
         const SharedScriptData* data = lookup.scriptData;
         if (entry->natoms() != data->natoms())
             return false;
         if (entry->codeLength() != data->codeLength())
             return false;
         if (entry->numNotes() != data->numNotes())
             return false;
-        return mozilla::PodEqual<uint8_t>(entry->data(), data->data(), data->dataLength());
+        return mozilla::ArrayEqual<uint8_t>(entry->data(), data->data(), data->dataLength());
     }
 };
 
 class AutoLockScriptData;
 
 using ScriptDataTable = HashSet<SharedScriptData*,
                                 ScriptBytecodeHasher,
                                 SystemAllocPolicy>;
--- a/js/src/vm/StringType.cpp
+++ b/js/src/vm/StringType.cpp
@@ -1,16 +1,17 @@
 /* -*- 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/. */
 
 #include "vm/StringType-inl.h"
 
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/HashFunctions.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/RangedPtr.h"
 #include "mozilla/TextUtils.h"
 #include "mozilla/TypeTraits.h"
@@ -25,21 +26,21 @@
 
 #include "vm/GeckoProfiler-inl.h"
 #include "vm/JSContext-inl.h"
 #include "vm/JSObject-inl.h"
 #include "vm/Realm-inl.h"
 
 using namespace js;
 
+using mozilla::ArrayEqual;
 using mozilla::IsAsciiDigit;
 using mozilla::IsNegativeZero;
 using mozilla::IsSame;
 using mozilla::PodCopy;
-using mozilla::PodEqual;
 using mozilla::RangedPtr;
 using mozilla::RoundUpPow2;
 using mozilla::Unused;
 
 using JS::AutoCheckCannotGC;
 
 using UniqueLatin1Chars = UniquePtr<Latin1Char[], JS::FreePolicy>;
 
@@ -841,46 +842,46 @@ js::EqualChars(JSLinearString* str1, JSL
 {
     MOZ_ASSERT(str1->length() == str2->length());
 
     size_t len = str1->length();
 
     AutoCheckCannotGC nogc;
     if (str1->hasTwoByteChars()) {
         if (str2->hasTwoByteChars())
-            return PodEqual(str1->twoByteChars(nogc), str2->twoByteChars(nogc), len);
+            return ArrayEqual(str1->twoByteChars(nogc), str2->twoByteChars(nogc), len);
 
         return EqualChars(str2->latin1Chars(nogc), str1->twoByteChars(nogc), len);
     }
 
     if (str2->hasLatin1Chars())
-        return PodEqual(str1->latin1Chars(nogc), str2->latin1Chars(nogc), len);
+        return ArrayEqual(str1->latin1Chars(nogc), str2->latin1Chars(nogc), len);
 
     return EqualChars(str1->latin1Chars(nogc), str2->twoByteChars(nogc), len);
 }
 
 bool
 js::HasSubstringAt(JSLinearString* text, JSLinearString* pat, size_t start)
 {
     MOZ_ASSERT(start + pat->length() <= text->length());
 
     size_t patLen = pat->length();
 
     AutoCheckCannotGC nogc;
     if (text->hasLatin1Chars()) {
         const Latin1Char* textChars = text->latin1Chars(nogc) + start;
         if (pat->hasLatin1Chars())
-            return PodEqual(textChars, pat->latin1Chars(nogc), patLen);
+            return ArrayEqual(textChars, pat->latin1Chars(nogc), patLen);
 
         return EqualChars(textChars, pat->twoByteChars(nogc), patLen);
     }
 
     const char16_t* textChars = text->twoByteChars(nogc) + start;
     if (pat->hasTwoByteChars())
-        return PodEqual(textChars, pat->twoByteChars(nogc), patLen);
+        return ArrayEqual(textChars, pat->twoByteChars(nogc), patLen);
 
     return EqualChars(pat->latin1Chars(nogc), textChars, patLen);
 }
 
 bool
 js::EqualStrings(JSContext* cx, JSString* str1, JSString* str2, bool* result)
 {
     if (str1 == str2) {
@@ -986,17 +987,17 @@ js::StringEqualsAscii(JSLinearString* st
 #endif
     if (length != str->length())
         return false;
 
     const Latin1Char* latin1 = reinterpret_cast<const Latin1Char*>(asciiBytes);
 
     AutoCheckCannotGC nogc;
     return str->hasLatin1Chars()
-           ? PodEqual(latin1, str->latin1Chars(nogc), length)
+           ? ArrayEqual(latin1, str->latin1Chars(nogc), length)
            : EqualChars(latin1, str->twoByteChars(nogc), length);
 }
 
 template <typename CharT>
 /* static */ bool
 JSFlatString::isIndexSlow(const CharT* s, size_t length, uint32_t* indexp)
 {
     CharT ch = *s;
@@ -1752,17 +1753,17 @@ ExternalStringCache::lookup(const char16
             // The cache is purged on GC so any string we get from the cache
             // must have been allocated after the GC started.
             return str;
         }
 
         // Compare the chars. Don't do this for long strings as it will be
         // faster to allocate a new external string.
         static const size_t MaxLengthForCharComparison = 100;
-        if (len <= MaxLengthForCharComparison && PodEqual(chars, strChars, len))
+        if (len <= MaxLengthForCharComparison && ArrayEqual(chars, strChars, len))
             return str;
     }
 
     return nullptr;
 }
 
 MOZ_ALWAYS_INLINE void
 ExternalStringCache::put(JSString* str)
--- a/js/src/vm/UbiNodeCensus.cpp
+++ b/js/src/vm/UbiNodeCensus.cpp
@@ -172,56 +172,62 @@ BucketCount::report(JSContext* cx, Count
         arr->setDenseElement(i, NumberValue(count.ids_[i]));
 
     report.setObject(*arr);
     return true;
 }
 
 
 // A type that categorizes nodes by their JavaScript type -- 'objects',
-// 'strings', 'scripts', and 'other' -- and then passes the nodes to child
+// 'strings', 'scripts', 'domNode', and 'other' -- and then passes the nodes to child
 // types.
 //
 // Implementation details of scripts like jitted code are counted under
 // 'scripts'.
 class ByCoarseType : public CountType {
     CountTypePtr objects;
     CountTypePtr scripts;
     CountTypePtr strings;
     CountTypePtr other;
+    CountTypePtr domNode;
 
     struct Count : CountBase {
         Count(CountType& type,
               CountBasePtr& objects,
               CountBasePtr& scripts,
               CountBasePtr& strings,
-              CountBasePtr& other)
+              CountBasePtr& other,
+              CountBasePtr& domNode)
           : CountBase(type),
             objects(std::move(objects)),
             scripts(std::move(scripts)),
             strings(std::move(strings)),
-            other(std::move(other))
+            other(std::move(other)),
+            domNode(std::move(domNode))
         { }
 
         CountBasePtr objects;
         CountBasePtr scripts;
         CountBasePtr strings;
         CountBasePtr other;
+        CountBasePtr domNode;
     };
 
   public:
     ByCoarseType(CountTypePtr& objects,
                  CountTypePtr& scripts,
                  CountTypePtr& strings,
-                 CountTypePtr& other)
+                 CountTypePtr& other,
+                 CountTypePtr& domNode)
       : CountType(),
         objects(std::move(objects)),
         scripts(std::move(scripts)),
         strings(std::move(strings)),
-        other(std::move(other))
+        other(std::move(other)),
+        domNode(std::move(domNode))
     { }
 
     void destructCount(CountBase& countBase) override {
         Count& count = static_cast<Count&>(countBase);
         count.~Count();
     }
 
     CountBasePtr makeCount() override;
@@ -232,51 +238,56 @@ class ByCoarseType : public CountType {
 
 CountBasePtr
 ByCoarseType::makeCount()
 {
     CountBasePtr objectsCount(objects->makeCount());
     CountBasePtr scriptsCount(scripts->makeCount());
     CountBasePtr stringsCount(strings->makeCount());
     CountBasePtr otherCount(other->makeCount());
+    CountBasePtr domNodeCount(domNode->makeCount());
 
-    if (!objectsCount || !scriptsCount || !stringsCount || !otherCount)
+    if (!objectsCount || !scriptsCount || !stringsCount || !otherCount || !domNodeCount)
         return CountBasePtr(nullptr);
 
     return CountBasePtr(js_new<Count>(*this,
                                       objectsCount,
                                       scriptsCount,
                                       stringsCount,
-                                      otherCount));
+                                      otherCount,
+                                      domNodeCount));
 }
 
 void
 ByCoarseType::traceCount(CountBase& countBase, JSTracer* trc)
 {
     Count& count = static_cast<Count&>(countBase);
     count.objects->trace(trc);
     count.scripts->trace(trc);
     count.strings->trace(trc);
     count.other->trace(trc);
+    count.domNode->trace(trc);
 }
 
 bool
 ByCoarseType::count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf, const Node& node)
 {
     Count& count = static_cast<Count&>(countBase);
 
     switch (node.coarseType()) {
       case JS::ubi::CoarseType::Object:
         return count.objects->count(mallocSizeOf, node);
       case JS::ubi::CoarseType::Script:
         return count.scripts->count(mallocSizeOf, node);
       case JS::ubi::CoarseType::String:
         return count.strings->count(mallocSizeOf, node);
       case JS::ubi::CoarseType::Other:
         return count.other->count(mallocSizeOf, node);
+      case JS::ubi::CoarseType::DOMNode:
+        return count.domNode->count(mallocSizeOf, node);
       default:
         MOZ_CRASH("bad JS::ubi::CoarseType in JS::ubi::ByCoarseType::count");
         return false;
     }
 }
 
 bool
 ByCoarseType::report(JSContext* cx, CountBase& countBase, MutableHandleValue report)
@@ -301,16 +312,20 @@ ByCoarseType::report(JSContext* cx, Coun
     if (!count.strings->report(cx, &stringsReport) ||
         !DefineDataProperty(cx, obj, cx->names().strings, stringsReport))
         return false;
 
     RootedValue otherReport(cx);
     if (!count.other->report(cx, &otherReport) ||
         !DefineDataProperty(cx, obj, cx->names().other, otherReport))
         return false;
+    RootedValue domReport(cx);
+    if (!count.domNode->report(cx, &domReport) ||
+        !DefineDataProperty(cx, obj, cx->names().domNode, domReport))
+        return false;
 
     report.setObject(*obj);
     return true;
 }
 
 
 // Comparison function for sorting hash table entries by the smallest node ID
 // they counted. Node IDs are stable and unique, which ensures ordering of
@@ -381,16 +396,61 @@ countMapToObject(JSContext* cx, Map& map
         RootedId entryId(cx, AtomToId(atom));
         if (!DefineDataProperty(cx, obj, entryId, thenReport))
             return nullptr;
     }
 
     return obj;
 }
 
+template <class Map, class GetName>
+static PlainObject*
+countMap16ToObject(JSContext* cx, Map& map, GetName getName) {
+    // Build a vector of pointers to entries; sort by total; and then use
+    // that to build the result object. This makes the ordering of entries
+    // more interesting, and a little less non-deterministic.
+
+    JS::ubi::Vector<typename Map::Entry*> entries;
+    if (!entries.reserve(map.count())) {
+        ReportOutOfMemory(cx);
+        return nullptr;
+    }
+
+    for (auto r = map.all(); !r.empty(); r.popFront())
+        entries.infallibleAppend(&r.front());
+
+    if (entries.length()) {
+        qsort(entries.begin(), entries.length(), sizeof(*entries.begin()),
+              compareEntries<typename Map::Entry>);
+    }
+
+    RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
+    if (!obj)
+        return nullptr;
+
+    for (auto& entry : entries) {
+        CountBasePtr& thenCount = entry->value();
+        RootedValue thenReport(cx);
+        if (!thenCount->report(cx, &thenReport))
+            return nullptr;
+
+        const char16_t* name = getName(entry->key());
+        MOZ_ASSERT(name);
+        JSAtom* atom = AtomizeChars(cx, name, js_strlen(name));
+        if (!atom)
+            return nullptr;
+
+        RootedId entryId(cx, AtomToId(atom));
+        if (!DefineDataProperty(cx, obj, entryId, thenReport))
+            return nullptr;
+    }
+
+    return obj;
+}
+
 
 // A type that categorizes nodes that are JSObjects by their class name,
 // and places all other nodes in an 'other' category.
 class ByObjectClass : public CountType {
     // A table mapping class names to their counts. Note that we treat js::Class
     // instances with the same name as equal keys. If you have several
     // js::Classes with equal names (and we do; as of this writing there were
     // six named "Object"), you will get several different js::Classes being
@@ -487,16 +547,121 @@ ByObjectClass::report(JSContext* cx, Cou
     if (!count.other->report(cx, &otherReport) ||
         !DefineDataProperty(cx, obj, cx->names().other, otherReport))
         return false;
 
     report.setObject(*obj);
     return true;
 }
 
+class ByDomObjectClass : public CountType {
+    // A table mapping descriptive names to their counts.
+    using UniqueC16String = JS::UniqueTwoByteChars;
+
+    struct UniqueC16StringHasher
+    {
+        using Lookup = UniqueC16String;
+
+        static js::HashNumber hash(const Lookup& lookup) {
+            return mozilla::HashString(lookup.get());
+        }
+
+        static bool match(const UniqueC16String& key, const Lookup& lookup) {
+            return CompareChars(key.get(), js_strlen(key.get()), lookup.get(),
+                js_strlen(lookup.get())) == 0;
+        }
+    };
+
+    using Table = HashMap<UniqueC16String,
+                          CountBasePtr,
+                          UniqueC16StringHasher,
+                          SystemAllocPolicy>;
+    using Entry = Table::Entry;
+
+    struct Count : public CountBase {
+        Table table;
+
+        explicit Count(CountType& type) : CountBase(type) { }
+
+        bool init() { return table.init(); }
+    };
+
+    CountTypePtr classesType;
+
+  public:
+    explicit ByDomObjectClass(CountTypePtr& classesType)
+      : CountType(),
+        classesType(std::move(classesType))
+    { }
+
+    void destructCount(CountBase& countBase) override {
+        Count& count = static_cast<Count&>(countBase);
+        count.~Count();
+    }
+
+    CountBasePtr makeCount() override;
+    void traceCount(CountBase& countBase, JSTracer* trc) override;
+    bool count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf, const Node& node) override;
+    bool report(JSContext* cx, CountBase& countBase, MutableHandleValue report) override;
+};
+
+CountBasePtr
+ByDomObjectClass::makeCount()
+{
+    auto count = js::MakeUnique<Count>(*this);
+    if (!count || !count->init())
+        return nullptr;
+
+    return CountBasePtr(count.release());
+}
+
+void
+ByDomObjectClass::traceCount(CountBase& countBase, JSTracer* trc)
+{
+    Count& count = static_cast<Count&>(countBase);
+    for (Table::Range r = count.table.all(); !r.empty(); r.popFront())
+        r.front().value()->trace(trc);
+}
+
+bool
+ByDomObjectClass::count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf, const Node& node)
+{
+    Count& count = static_cast<Count&>(countBase);
+
+    const char16_t* nodeName = node.descriptiveTypeName();
+    if (!nodeName)
+        return false;
+
+    UniqueC16String name = DuplicateString(nodeName);
+    if (!name)
+        return false;
+
+    Table::AddPtr p = count.table.lookupForAdd(name);
+    if (!p) {
+        CountBasePtr classesCount(classesType->makeCount());
+        if (!classesCount || !count.table.add(p, std::move(name), std::move(classesCount)))
+            return false;
+    }
+    return p->value()->count(mallocSizeOf, node);
+}
+
+bool
+ByDomObjectClass::report(JSContext* cx, CountBase& countBase, MutableHandleValue report)
+{
+    Count& count = static_cast<Count&>(countBase);
+
+    RootedPlainObject obj(cx, countMap16ToObject(cx, count.table, [](const UniqueC16String& key) {
+        return key.get();
+    }));
+    if (!obj)
+        return false;
+
+    report.setObject(*obj);
+    return true;
+}
 
 // A count type that categorizes nodes by their ubi::Node::typeName.
 class ByUbinodeType : public CountType {
     // Note that, because ubi::Node::typeName promises to return a specific
     // pointer, not just any string whose contents are correct, we can use their
     // addresses as hash table keys.
     using Table = HashMap<const char16_t*, CountBasePtr, DefaultHasher<const char16_t*>,
                           SystemAllocPolicy>;
@@ -1053,31 +1218,42 @@ ParseBreakdown(JSContext* cx, HandleValu
         if (!scriptsType)
             return nullptr;
         CountTypePtr stringsType(ParseChildBreakdown(cx, breakdown, cx->names().strings));
         if (!stringsType)
             return nullptr;
         CountTypePtr otherType(ParseChildBreakdown(cx, breakdown, cx->names().other));
         if (!otherType)
             return nullptr;
+        CountTypePtr domNodeType(ParseChildBreakdown(cx, breakdown, cx->names().domNode));
+        if (!domNodeType)
+            return nullptr;
 
         return CountTypePtr(cx->new_<ByCoarseType>(objectsType,
                                                    scriptsType,
                                                    stringsType,
-                                                   otherType));
+                                                   otherType,
+                                                   domNodeType));
     }
 
     if (StringEqualsAscii(by, "internalType")) {
         CountTypePtr thenType(ParseChildBreakdown(cx, breakdown, cx->names().then));
         if (!thenType)
             return nullptr;
 
         return CountTypePtr(cx->new_<ByUbinodeType>(thenType));
     }
 
+    if (StringEqualsAscii(by, "descriptiveType")) {
+        CountTypePtr thenType(ParseChildBreakdown(cx, breakdown, cx->names().then));
+        if (!thenType)
+            return nullptr;
+        return CountTypePtr(cx->new_<ByDomObjectClass>(thenType));
+    }
+
     if (StringEqualsAscii(by, "allocationStack")) {
         CountTypePtr thenType(ParseChildBreakdown(cx, breakdown, cx->names().then));
         if (!thenType)
             return nullptr;
         CountTypePtr noStackType(ParseChildBreakdown(cx, breakdown, cx->names().noStack));
         if (!noStackType)
             return nullptr;
 
@@ -1109,21 +1285,25 @@ ParseBreakdown(JSContext* cx, HandleValu
                                byBytes.ptr());
     return nullptr;
 }
 
 // Get the default census breakdown:
 //
 // { by: "coarseType",
 //   objects: { by: "objectClass" },
-//   other:   { by: "internalType" }
+//   other:   { by: "internalType" },
+//   domNode: { by: "descriptiveType" }
 // }
 static CountTypePtr
 GetDefaultBreakdown(JSContext* cx)
 {
+    CountTypePtr byDomClass(cx->new_<SimpleCount>());
+    if (!byDomClass)
+        return nullptr;
     CountTypePtr byClass(cx->new_<SimpleCount>());
     if (!byClass)
         return nullptr;
 
     CountTypePtr byClassElse(cx->new_<SimpleCount>());
     if (!byClassElse)
         return nullptr;
 
@@ -1141,21 +1321,25 @@ GetDefaultBreakdown(JSContext* cx)
 
     CountTypePtr byType(cx->new_<SimpleCount>());
     if (!byType)
         return nullptr;
 
     CountTypePtr other(cx->new_<ByUbinodeType>(byType));
     if (!other)
         return nullptr;
+    CountTypePtr domNode(cx->new_<ByDomObjectClass>(byDomClass));
+    if (!domNode)
+        return nullptr;
 
     return CountTypePtr(cx->new_<ByCoarseType>(objects,
                                                scripts,
                                                strings,
-                                               other));
+                                               other,
+                                               domNode));
 }
 
 JS_PUBLIC_API(bool)
 ParseCensusOptions(JSContext* cx, Census& census, HandleObject options, CountTypePtr& outResult)
 {
     RootedValue breakdown(cx, UndefinedValue());
     if (options && !GetProperty(cx, options, options, cx->names().breakdown, &breakdown))
         return false;
--- a/js/src/vm/Xdr.cpp
+++ b/js/src/vm/Xdr.cpp
@@ -1,32 +1,33 @@
 /* -*- 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/. */
 
 #include "vm/Xdr.h"
 
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/ScopeExit.h"
 
 #include <string.h>
 
 #include "jsapi.h"
 #include "jsutil.h"
 
 #include "vm/Debugger.h"
 #include "vm/EnvironmentObject.h"
 #include "vm/JSContext.h"
 #include "vm/JSScript.h"
 #include "vm/TraceLogging.h"
 
 using namespace js;
-using mozilla::PodEqual;
+using mozilla::ArrayEqual;
 
 template<XDRMode mode>
 LifoAlloc&
 XDRState<mode>::lifoAlloc() const {
     return buf.cx()->tempLifoAlloc();
 }
 
 #ifdef DEBUG
@@ -111,17 +112,17 @@ VersionCheck(XDRState<mode>* xdr)
         if (!decodedBuildId.resize(buildIdLength)) {
             ReportOutOfMemory(xdr->cx());
             return xdr->fail(JS::TranscodeResult_Throw);
         }
 
         MOZ_TRY(xdr->codeBytes(decodedBuildId.begin(), buildIdLength));
 
         // We do not provide binary compatibility with older scripts.
-        if (!PodEqual(decodedBuildId.begin(), buildId.begin(), buildIdLength))
+        if (!ArrayEqual(decodedBuildId.begin(), buildId.begin(), buildIdLength))
             return xdr->fail(JS::TranscodeResult_Failure_BadBuildId);
     }
 
     return Ok();
 }
 
 template<XDRMode mode>
 XDRResult
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -13,16 +13,17 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "wasm/AsmJS.h"
 
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Compression.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/Unused.h"
 
 #include <new>
 
 #include "jsmath.h"
@@ -53,24 +54,24 @@
 #include "vm/ArrayBufferObject-inl.h"
 #include "vm/JSObject-inl.h"
 
 using namespace js;
 using namespace js::frontend;
 using namespace js::jit;
 using namespace js::wasm;
 
+using mozilla::ArrayEqual;
 using mozilla::CeilingLog2;
 using mozilla::Compression::LZ4;
 using mozilla::HashGeneric;
 using mozilla::IsNaN;
 using mozilla::IsNegativeZero;
 using mozilla::IsPositiveZero;
 using mozilla::IsPowerOfTwo;
-using mozilla::PodEqual;
 using mozilla::PodZero;
 using mozilla::PositiveInfinity;
 using mozilla::Unused;
 using JS::AsmJSOption;
 using JS::GenericNaN;
 
 /*****************************************************************************/
 
@@ -6822,17 +6823,17 @@ class ModuleCharsForLookup : ModuleChars
     }
 
     bool match(AsmJSParser& parser) const {
         const char16_t* parseBegin = parser.tokenStream.codeUnitPtrAt(beginOffset(parser));
         const char16_t* parseLimit = parser.tokenStream.rawLimit();
         MOZ_ASSERT(parseLimit >= parseBegin);
         if (uint32_t(parseLimit - parseBegin) < chars_.length())
             return false;
-        if (!PodEqual(chars_.begin(), parseBegin, chars_.length()))
+        if (!ArrayEqual(chars_.begin(), parseBegin, chars_.length()))
             return false;
         if (isFunCtor_ != parser.pc->isStandaloneFunctionBody())
             return false;
         if (isFunCtor_) {
             // For function statements, the closing } is included as the last
             // character of the matched source. For Function constructor,
             // parsing terminates with EOF which we must explicitly check. This
             // prevents
--- a/js/src/wasm/WasmTypes.cpp
+++ b/js/src/wasm/WasmTypes.cpp
@@ -536,17 +536,17 @@ Assumptions::clone(const Assumptions& ot
     return buildId.appendAll(other.buildId);
 }
 
 bool
 Assumptions::operator==(const Assumptions& rhs) const
 {
     return cpuId == rhs.cpuId &&
            buildId.length() == rhs.buildId.length() &&
-           PodEqual(buildId.begin(), rhs.buildId.begin(), buildId.length());
+           ArrayEqual(buildId.begin(), rhs.buildId.begin(), buildId.length());
 }
 
 size_t
 Assumptions::serializedSize() const
 {
     return sizeof(uint32_t) +
            SerializedPodVectorSize(buildId);
 }
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -15,16 +15,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #ifndef wasm_types_h
 #define wasm_types_h
 
 #include "mozilla/Alignment.h"
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/HashFunctions.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/Unused.h"
 
 #include "NamespaceImports.h"
@@ -70,25 +71,25 @@ typedef Handle<WasmTableObject*> HandleW
 typedef MutableHandle<WasmTableObject*> MutableHandleWasmTableObject;
 
 class WasmGlobalObject;
 typedef GCVector<WasmGlobalObject*, 0, SystemAllocPolicy> WasmGlobalObjectVector;
 typedef Rooted<WasmGlobalObject*> RootedWasmGlobalObject;
 
 namespace wasm {
 
+using mozilla::ArrayEqual;
 using mozilla::Atomic;
 using mozilla::DebugOnly;
 using mozilla::EnumeratedArray;
 using mozilla::Maybe;
 using mozilla::MallocSizeOf;
 using mozilla::Nothing;
 using mozilla::PodZero;
 using mozilla::PodCopy;
-using mozilla::PodEqual;
 using mozilla::Some;
 using mozilla::Unused;
 
 class Code;
 class DebugState;
 class GeneratedSourceMap;
 class Memory;
 class Module;
--- a/layout/style/nsCSSPropertyIDSet.h
+++ b/layout/style/nsCSSPropertyIDSet.h
@@ -3,17 +3,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* bit vectors for sets of CSS properties */
 
 #ifndef nsCSSPropertyIDSet_h__
 #define nsCSSPropertyIDSet_h__
 
 #include "mozilla/ArrayUtils.h"
-#include "mozilla/PodOperations.h"
 
 #include "nsCSSPropertyID.h"
 #include <limits.h> // for CHAR_BIT
 
 /**
  * nsCSSPropertyIDSet maintains a set of non-shorthand CSS properties.  In
  * other words, for each longhand CSS property we support, it has a bit
  * for whether that property is in the set.
@@ -59,17 +58,17 @@ public:
 
     void AssertIsEmpty(const char* aText) const {
         for (size_t i = 0; i < mozilla::ArrayLength(mProperties); ++i) {
             NS_ASSERTION(mProperties[i] == 0, aText);
         }
     }
 
     bool Equals(const nsCSSPropertyIDSet& aOther) const {
-      return mozilla::PodEqual(mProperties, aOther.mProperties);
+      return mozilla::ArrayEqual(mProperties, aOther.mProperties);
     }
 
     bool IsEmpty() const {
       for (size_t i = 0; i < mozilla::ArrayLength(mProperties); ++i) {
           if (mProperties[i] != 0) {
             return false;
           }
       }
--- a/memory/gtest/TestJemalloc.cpp
+++ b/memory/gtest/TestJemalloc.cpp
@@ -431,16 +431,18 @@ TEST(Jemalloc, InPlace)
       moz_arena_free(arena, ptr2);
     }
   }
 
   // Until Bug 1364359 is fixed it is unsafe to call moz_dispose_arena.
   // moz_dispose_arena(arena);
 }
 
+// Bug 1474254: disable this test for windows ccov builds because it leads to timeout.
+#if !defined(XP_WIN) || !defined(MOZ_CODE_COVERAGE)
 TEST(Jemalloc, JunkPoison)
 {
   jemalloc_stats_t stats;
   jemalloc_stats(&stats);
 
   // Create buffers in a separate arena, for faster comparisons with
   // bulk_compare.
   arena_id_t buf_arena = moz_create_arena();
@@ -617,8 +619,9 @@ TEST(Jemalloc, JunkPoison)
   // Until Bug 1364359 is fixed it is unsafe to call moz_dispose_arena.
   // moz_dispose_arena(arena);
 
   moz_arena_free(buf_arena, poison_buf);
   moz_arena_free(buf_arena, junk_buf);
   // Until Bug 1364359 is fixed it is unsafe to call moz_dispose_arena.
   // moz_dispose_arena(buf_arena);
 }
+#endif
--- a/mfbt/ArrayUtils.h
+++ b/mfbt/ArrayUtils.h
@@ -9,16 +9,17 @@
  */
 
 #ifndef mozilla_ArrayUtils_h
 #define mozilla_ArrayUtils_h
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 
+#include <algorithm>
 #include <stddef.h>
 
 #ifdef __cplusplus
 
 #include "mozilla/Alignment.h"
 #include "mozilla/Array.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/TypeTraits.h"
@@ -93,16 +94,34 @@ ArrayEnd(Array<T, N>& aArr)
 
 template<typename T, size_t N>
 constexpr const T*
 ArrayEnd(const Array<T, N>& aArr)
 {
   return &aArr[0] + ArrayLength(aArr);
 }
 
+/**
+ * std::equal has subpar ergonomics.
+ */
+
+template<typename T, typename U, size_t N>
+bool
+ArrayEqual(const T (&a)[N], const U (&b)[N])
+{
+  return std::equal(a, a + N, b);
+}
+
+template<typename T, typename U>
+bool
+ArrayEqual(const T* const a, const U* const b, const size_t n)
+{
+  return std::equal(a, a + n, b);
+}
+
 namespace detail {
 
 template<typename AlignType, typename Pointee,
          typename = EnableIf<!IsVoid<AlignType>::value>>
 struct AlignedChecker
 {
   static void
   test(const Pointee* aPtr)
--- a/mfbt/LinkedList.h
+++ b/mfbt/LinkedList.h
@@ -94,28 +94,34 @@ struct LinkedListElementTraits
 
   // These static methods are called when an element is added to or removed from
   // a linked list. It can be used to keep track ownership in lists that are
   // supposed to own their elements. If elements are transferred from one list
   // to another, no enter or exit calls happen since the elements still belong
   // to a list.
   static void enterList(LinkedListElement<T>* elt) {}
   static void exitList(LinkedListElement<T>* elt) {}
+
+  // This method is called when AutoCleanLinkedList cleans itself
+  // during destruction. It can be used to call delete on elements if
+  // the list is the sole owner.
+  static void cleanElement(LinkedListElement<T>* elt) { delete elt->asT(); }
 };
 
 template<typename T>
 struct LinkedListElementTraits<RefPtr<T>>
 {
   typedef T* RawType;
   typedef const T* ConstRawType;
   typedef RefPtr<T> ClientType;
   typedef RefPtr<const T> ConstClientType;
 
   static void enterList(LinkedListElement<RefPtr<T>>* elt) { elt->asT()->AddRef(); }
   static void exitList(LinkedListElement<RefPtr<T>>* elt) { elt->asT()->Release(); }
+  static void cleanElement(LinkedListElement<RefPtr<T>>* elt) {}
 };
 
 } /* namespace detail */
 
 template<typename T>
 class LinkedList;
 
 template<typename T>
@@ -650,32 +656,35 @@ private:
 
   LinkedList& operator=(const LinkedList<T>& aOther) = delete;
   LinkedList(const LinkedList<T>& aOther) = delete;
 };
 
 template <typename T>
 class AutoCleanLinkedList : public LinkedList<T>
 {
+private:
+  using Traits = detail::LinkedListElementTraits<T>;
+  using ClientType = typename detail::LinkedListElementTraits<T>::ClientType;
 public:
   ~AutoCleanLinkedList()
   {
     clear();
   }
 
   AutoCleanLinkedList& operator=(AutoCleanLinkedList&& aOther)
   {
     LinkedList<T>::operator=(std::forward<LinkedList<T>>(aOther));
     return *this;
   }
 
   void clear()
   {
-    while (T* element = this->popFirst()) {
-      delete element;
+    while (ClientType element = this->popFirst()) {
+      Traits::cleanElement(element);
     }
   }
 };
 
 } /* namespace mozilla */
 
 #endif /* __cplusplus */
 
--- a/mfbt/PodOperations.h
+++ b/mfbt/PodOperations.h
@@ -14,16 +14,17 @@
 
 #ifndef mozilla_PodOperations_h
 #define mozilla_PodOperations_h
 
 #include "mozilla/Array.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
 
+#include <algorithm>
 #include <stdint.h>
 #include <string.h>
 
 namespace mozilla {
 
 /** Set the contents of |aT| to 0. */
 template<typename T>
 static MOZ_ALWAYS_INLINE void
@@ -153,44 +154,15 @@ static MOZ_ALWAYS_INLINE void
 PodMove(T* aDst, const T* aSrc, size_t aNElem)
 {
   MOZ_ASSERT(aNElem <= SIZE_MAX / sizeof(T),
              "trying to move an impossible number of elements");
   memmove(aDst, aSrc, aNElem * sizeof(T));
 }
 
 /**
- * Determine whether the |len| elements at |one| are memory-identical to the
- * |len| elements at |two|.
+ * Looking for a PodEqual? Use ArrayEqual from ArrayUtils.h.
+ * Note that we *cannot* use memcmp for this, due to padding bytes, etc..
  */
-template<typename T>
-static MOZ_ALWAYS_INLINE bool
-PodEqual(const T* one, const T* two, size_t len)
-{
-  if (len < 128) {
-    const T* p1end = one + len;
-    const T* p1 = one;
-    const T* p2 = two;
-    for (; p1 < p1end; p1++, p2++) {
-      if (*p1 != *p2) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  return !memcmp(one, two, len * sizeof(T));
-}
-
-/*
- * Determine whether the |N| elements at |one| are memory-identical to the
- * |N| elements at |two|.
- */
-template <class T, size_t N>
-static MOZ_ALWAYS_INLINE bool
-PodEqual(const T (&one)[N], const T (&two)[N])
-{
-  return PodEqual(one, two, N);
-}
 
 } // namespace mozilla
 
 #endif /* mozilla_PodOperations_h */
new file mode 100644
--- /dev/null
+++ b/mfbt/tests/gtest/TestLinkedList.cpp
@@ -0,0 +1,93 @@
+/* -*- 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/. */
+
+#include "gtest/gtest.h"
+
+#include "mozilla/LinkedList.h"
+#include "mozilla/RefPtr.h"
+
+using mozilla::AutoCleanLinkedList;
+using mozilla::LinkedList;
+using mozilla::LinkedListElement;
+
+class PtrClass : public LinkedListElement<PtrClass>
+{
+public:
+  bool* mResult;
+
+  explicit PtrClass(bool* result)
+    : mResult(result)
+  {
+    EXPECT_TRUE(!*mResult);
+  }
+
+  virtual ~PtrClass() {
+    *mResult = true;
+  }
+};
+
+class InheritedPtrClass : public PtrClass {
+public:
+  bool* mInheritedResult;
+
+  InheritedPtrClass(bool* result, bool* inheritedResult)
+    : PtrClass(result)
+    , mInheritedResult(inheritedResult)
+    {
+      EXPECT_TRUE(!*mInheritedResult);
+    }
+
+  virtual ~InheritedPtrClass() {
+    *mInheritedResult = true;
+  }
+};
+
+TEST(LinkedList, AutoCleanLinkedList)
+{
+    bool rv1 = false;
+    bool rv2 = false;
+    bool rv3 = false;
+    {
+        AutoCleanLinkedList<PtrClass> list;
+        list.insertBack(new PtrClass(&rv1));
+        list.insertBack(new InheritedPtrClass(&rv2, &rv3));
+    }
+
+    EXPECT_TRUE(rv1);
+    EXPECT_TRUE(rv2);
+    EXPECT_TRUE(rv3);
+}
+
+class CountedClass final : public LinkedListElement<RefPtr<CountedClass>>
+{
+public:
+  int mCount;
+  void AddRef() { mCount++; }
+  void Release() { mCount--; }
+
+  CountedClass()
+    : mCount(0)
+    {
+    }
+  ~CountedClass() { EXPECT_TRUE(mCount == 0); }
+};
+
+TEST(LinkedList, AutoCleanLinkedListRefPtr)
+{
+    RefPtr<CountedClass> elt1 = new CountedClass;
+    CountedClass* elt2 = new CountedClass;
+    {
+        AutoCleanLinkedList<RefPtr<CountedClass>> list;
+        list.insertBack(elt1);
+        list.insertBack(elt2);
+
+        EXPECT_TRUE(elt1->mCount == 2);
+        EXPECT_TRUE(elt2->mCount == 1);
+    }
+
+    EXPECT_TRUE(elt1->mCount == 1);
+    EXPECT_TRUE(elt2->mCount == 0);
+}
--- a/mfbt/tests/gtest/moz.build
+++ b/mfbt/tests/gtest/moz.build
@@ -1,15 +1,16 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 UNIFIED_SOURCES += [
+    'TestLinkedList.cpp',
     'TestSpan.cpp',
 ]
 
 #LOCAL_INCLUDES += [
 #    '../../base',
 #]
 
 FINAL_LIBRARY = 'xul-gtest'
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
@@ -101,16 +101,23 @@ class AccessibilityTest : BaseSessionTes
         { (view.parent as View).setAccessibilityDelegate(null) },
         object : EventDelegate { })
     }
 
     @After fun teardown() {
         sessionRule.session.accessibility.view = null
     }
 
+    private fun waitForInitialFocus() {
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            @AssertCalled(count = 1)
+            override fun onFocused(event: AccessibilityEvent) { }
+        })
+    }
+
     @Test fun testRootNode() {
         assertThat("provider is not null", provider, notNullValue())
         val node = provider.createAccessibilityNodeInfo(AccessibilityNodeProvider.HOST_VIEW_ID)
         assertThat("Root node should have WebView class name",
             node.className.toString(), equalTo("android.webkit.WebView"))
     }
 
     @Test fun testPageLoad() {
@@ -120,17 +127,17 @@ class AccessibilityTest : BaseSessionTes
             @AssertCalled(count = 1)
             override fun onFocused(event: AccessibilityEvent) { }
         })
     }
 
     @Test fun testAccessibilityFocus() {
         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
         sessionRule.session.loadTestPath(INPUTS_PATH)
-        sessionRule.waitForPageStop()
+        waitForInitialFocus()
 
         provider.performAction(AccessibilityNodeProvider.HOST_VIEW_ID,
             AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
 
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
@@ -149,17 +156,17 @@ class AccessibilityTest : BaseSessionTes
                 val node = provider.createAccessibilityNodeInfo(nodeId)
                 assertThat("Entry node should be focusable", node.isFocusable, equalTo(true))
             }
         })
     }
 
     @Test fun testTextEntryNode() {
         sessionRule.session.loadString("<input aria-label='Name' value='Tobias'>", "text/html")
-        sessionRule.waitForPageStop()
+        waitForInitialFocus()
 
         mainSession.evaluateJS("$('input').focus()")
 
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 val nodeId = getSourceId(event)
                 val node = provider.createAccessibilityNodeInfo(nodeId)
@@ -228,17 +235,17 @@ class AccessibilityTest : BaseSessionTes
         arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, granularity)
         arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, extendSelection)
         return arguments
     }
 
     @Test fun testClipboard() {
         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID;
         sessionRule.session.loadString("<input value='hello cruel world' id='input'>", "text/html")
-        sessionRule.waitForPageStop()
+        waitForInitialFocus()
 
         mainSession.evaluateJS("$('input').focus()")
 
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
                 val node = provider.createAccessibilityNodeInfo(nodeId)
@@ -279,17 +286,17 @@ class AccessibilityTest : BaseSessionTes
                 assertThat("text should be pasted", event.text[0].toString(), equalTo("hello cruel cruel cruel"))
             }
         })
     }
 
     @Test fun testMoveByCharacter() {
         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
         sessionRule.session.loadTestPath(LOREM_IPSUM_HTML_PATH)
-        sessionRule.waitForPageStop()
+        waitForInitialFocus()
 
         provider.performAction(AccessibilityNodeProvider.HOST_VIEW_ID,
                 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
 
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
@@ -312,17 +319,17 @@ class AccessibilityTest : BaseSessionTes
                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER))
         waitUntilTextTraversed(0, 1) // "L"
     }
 
     @Test fun testMoveByWord() {
         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
         sessionRule.session.loadTestPath(LOREM_IPSUM_HTML_PATH)
-        sessionRule.waitForPageStop()
+        waitForInitialFocus()
 
         provider.performAction(AccessibilityNodeProvider.HOST_VIEW_ID,
                 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
 
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
@@ -345,17 +352,17 @@ class AccessibilityTest : BaseSessionTes
                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD))
         waitUntilTextTraversed(0, 5) // "Lorem"
     }
 
     @Test fun testMoveByLine() {
         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
         sessionRule.session.loadTestPath(LOREM_IPSUM_HTML_PATH)
-        sessionRule.waitForPageStop()
+        waitForInitialFocus()
 
         provider.performAction(AccessibilityNodeProvider.HOST_VIEW_ID,
                 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
 
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
@@ -378,17 +385,17 @@ class AccessibilityTest : BaseSessionTes
                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE))
         waitUntilTextTraversed(0, 18) // "Lorem ipsum dolor "
     }
 
     @Test fun testCheckbox() {
         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID;
         sessionRule.session.loadString("<label><input id='checkbox' type='checkbox'>many option</label>", "text/html")
-        sessionRule.waitForPageStop()
+        waitForInitialFocus()
 
         mainSession.evaluateJS("$('#checkbox').focus()")
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
                 var node = provider.createAccessibilityNodeInfo(nodeId)
                 assertThat("Checkbox node is checkable", node.isCheckable, equalTo(true))
@@ -408,17 +415,17 @@ class AccessibilityTest : BaseSessionTes
 
     @Test fun testSelectable() {
         var nodeId = View.NO_ID
         sessionRule.session.loadString(
                 """<ul style="list-style-type: none;" role="listbox">
                         <li id="li" role="option" onclick="this.setAttribute('aria-selected',
                             this.getAttribute('aria-selected') == 'true' ? 'false' : 'true')">1</li>
                 </ul>""","text/html")
-        sessionRule.waitForPageStop()
+        waitForInitialFocus()
 
         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
                 var node = provider.createAccessibilityNodeInfo(nodeId)
                 assertThat("Selectable node is clickable", node.isClickable, equalTo(true))
--- a/security/certverifier/ExtendedValidation.cpp
+++ b/security/certverifier/ExtendedValidation.cpp
@@ -1097,27 +1097,27 @@ CertIsAuthoritativeForEVPolicy(const Uni
 
   const SECOidData* cabforumOIDData = SECOID_FindOIDByTag(sCABForumEVOIDTag);
   for (size_t i = 0; i < ArrayLength(kEVInfos); ++i) {
     const EVInfo& entry = kEVInfos[i];
 
     // This check ensures that only the specific roots we approve for EV get
     // that status, and not certs (roots or otherwise) that happen to have an
     // OID that's already been approved for EV.
-    if (!PodEqual(fingerprint, entry.sha256Fingerprint)) {
+    if (!ArrayEqual(fingerprint, entry.sha256Fingerprint)) {
       continue;
     }
 
     if (cabforumOIDData && cabforumOIDData->oid.len == policy.numBytes &&
-        PodEqual(cabforumOIDData->oid.data, policy.bytes, policy.numBytes)) {
+        ArrayEqual(cabforumOIDData->oid.data, policy.bytes, policy.numBytes)) {
       return true;
     }
     const SECOidData* oidData = SECOID_FindOIDByTag(sEVInfoOIDTags[i]);
     if (oidData && oidData->oid.len == policy.numBytes &&
-        PodEqual(oidData->oid.data, policy.bytes, policy.numBytes)) {
+        ArrayEqual(oidData->oid.data, policy.bytes, policy.numBytes)) {
       return true;
     }
   }
 
   return false;
 }
 
 nsresult
@@ -1183,17 +1183,17 @@ LoadExtendedValidationInfo()
     } else {
       unsigned char certFingerprint[SHA256_LENGTH];
       srv = PK11_HashBuf(SEC_OID_SHA256, certFingerprint, cert->derCert.data,
                          AssertedCast<int32_t>(cert->derCert.len));
       MOZ_ASSERT(srv == SECSuccess, "Could not hash EV root");
       if (srv != SECSuccess) {
         return NS_ERROR_FAILURE;
       }
-      bool same = PodEqual(certFingerprint, entry.sha256Fingerprint);
+      bool same = ArrayEqual(certFingerprint, entry.sha256Fingerprint);
       MOZ_ASSERT(same, "EV root fingerprint mismatch");
       if (!same) {
         return NS_ERROR_FAILURE;
       }
     }
 #endif
     // This is the code that actually enables these roots for EV.
     ScopedAutoSECItem evOIDItem;
--- a/security/certverifier/TrustOverrideUtils.h
+++ b/security/certverifier/TrustOverrideUtils.h
@@ -4,17 +4,17 @@
  * 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 TrustOverrides_h
 #define TrustOverrides_h
 
 #include "nsNSSCertificate.h"
 #include "nsNSSCertValidity.h"
-#include "mozilla/PodOperations.h"
+#include "mozilla/ArrayUtils.h"
 
 using namespace mozilla;
 
 struct DataAndLength {
   const uint8_t* data;
   uint32_t len;
 };
 
@@ -24,17 +24,17 @@ CertDNIsInList(const CERTCertificate* aC
 {
   MOZ_ASSERT(aCert);
   if (!aCert) {
     return false;
   }
 
   for (auto& dn: aDnList) {
     if (aCert->derSubject.len == dn.len &&
-        mozilla::PodEqual(aCert->derSubject.data, dn.data, dn.len)) {
+        mozilla::ArrayEqual(aCert->derSubject.data, dn.data, dn.len)) {
       return true;
     }
   }
   return false;
 }
 
 template<size_t T>
 static bool
@@ -42,36 +42,36 @@ CertSPKIIsInList(const CERTCertificate* 
 {
   MOZ_ASSERT(aCert);
   if (!aCert) {
     return false;
   }
 
   for (auto& spki: aSpkiList) {
     if (aCert->derPublicKey.len == spki.len &&
-        mozilla::PodEqual(aCert->derPublicKey.data, spki.data, spki.len)) {
+        mozilla::ArrayEqual(aCert->derPublicKey.data, spki.data, spki.len)) {
       return true;
     }
   }
   return false;
 }
 
 template<size_t T, size_t R>
 static bool
 CertMatchesStaticData(const CERTCertificate* cert,
                       const unsigned char (&subject)[T],
                       const unsigned char (&spki)[R]) {
   MOZ_ASSERT(cert);
   if (!cert) {
     return false;
   }
   return cert->derSubject.len == T &&
-         mozilla::PodEqual(cert->derSubject.data, subject, T) &&
+         mozilla::ArrayEqual(cert->derSubject.data, subject, T) &&
          cert->derPublicKey.len == R &&
-         mozilla::PodEqual(cert->derPublicKey.data, spki, R);
+         mozilla::ArrayEqual(cert->derPublicKey.data, spki, R);
 }
 
 // Implements the graduated Symantec distrust algorithm from Bug 1409257.
 // This accepts a pre-segmented certificate chain (e.g. SegmentCertificateChain)
 // as |intCerts| and |eeCert|, and pre-assumes that the root has been identified
 // as being affected (this is to avoid duplicate Segment operations in the
 // NSSCertDBTrustDomain). If |permitAfterDate| is non-zero, this algorithm
 // returns "not distrusted" if the NotBefore date of |eeCert| is after
--- a/security/manager/ssl/tests/gtest/MD4Test.cpp
+++ b/security/manager/ssl/tests/gtest/MD4Test.cpp
@@ -3,18 +3,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // This file tests the md4.c implementation.
 
 #include "gtest/gtest.h"
 #include "md4.h"
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/Casting.h"
-#include "mozilla/PodOperations.h"
 
 struct RFC1320TestParams
 {
   const char* data;
   const uint8_t expectedHash[16];
 };
 
 static const RFC1320TestParams RFC1320_TEST_PARAMS[] =
@@ -63,14 +63,14 @@ class psm_MD4
 };
 
 TEST_P(psm_MD4, RFC1320TestValues)
 {
   const RFC1320TestParams& params(GetParam());
   uint8_t actualHash[16];
   md4sum(mozilla::BitwiseCast<const uint8_t*, const char*>(params.data),
          strlen(params.data), actualHash);
-  EXPECT_TRUE(mozilla::PodEqual(actualHash, params.expectedHash))
+  EXPECT_TRUE(mozilla::ArrayEqual(actualHash, params.expectedHash))
     << "MD4 hashes aren't equal for input: '" << params.data << "'";
 }
 
 INSTANTIATE_TEST_CASE_P(psm_MD4, psm_MD4,
                         testing::ValuesIn(RFC1320_TEST_PARAMS));
--- a/toolkit/components/aboutperformance/content/aboutPerformance.js
+++ b/toolkit/components/aboutperformance/content/aboutPerformance.js
@@ -598,17 +598,17 @@ var State = {
 
       return ({windowId: id, name, image,
                totalDispatches: dispatches,
                totalDuration: tab.duration,
                durationSincePrevious: prev ? tab.duration - prev.duration : NaN,
                dispatchesSincePrevious: prev ? dispatches - prev.dispatchCount : NaN,
                dispatchesSinceStartOfBuffer: oldest ? dispatches - oldest.dispatchCount : NaN,
                children: tab.children});
-    }).sort((a, b) => b.dispatchesSinceStartOfBuffer - a.dispatchesSinceStartOfBuffer);
+    });
   }
 };
 
 var View = {
   /**
    * A cache for all the per-item DOM elements that are reused across refreshes.
    *
    * Reusing the same elements means that elements that were hidden (respectively
@@ -949,17 +949,17 @@ var Control = {
         await wait(0);
         await View.updateCategory(state[category], category, category, mode);
       }
       await wait(0);
 
       // Make sure that we do not keep obsolete stuff around.
       View.DOMCache.trimTo(state.deltas);
     } else {
-      let counters = State.getCounters();
+      let counters = this._sortCounters(State.getCounters());
       for (let {name, image, totalDispatches, dispatchesSincePrevious,
                 totalDuration, durationSincePrevious, children} of counters) {
         function dispatchesAndDuration(dispatches, duration) {
           let result = dispatches;
           if (duration) {
             duration /= 1000;
             duration = Math.round(duration);
             if (duration)
@@ -990,16 +990,25 @@ var Control = {
       View.commit();
     }
 
     await wait(0);
 
     // Inform watchers
     Services.obs.notifyObservers(null, UPDATE_COMPLETE_TOPIC, mode);
   },
+  _sortCounters(counters) {
+    return counters.sort((a, b) => {
+      if (a.dispatchesSinceStartOfBuffer != b.dispatchesSinceStartOfBuffer)
+        return b.dispatchesSinceStartOfBuffer - a.dispatchesSinceStartOfBuffer;
+      if (a.totalDispatches != b.totalDispatches)
+        return b.totalDispatches - a.totalDispatches;
+      return a.name.localeCompare(b.name);
+    });
+  },
   _setOptions(options) {
     dump(`about:performance _setOptions ${JSON.stringify(options)}\n`);
     let eltRefresh = document.getElementById("check-autorefresh");
     if ((options.autoRefresh > 0) != eltRefresh.checked) {
       eltRefresh.click();
     }
     let eltCheckRecent = document.getElementById("check-display-recent");
     if (!!options.displayRecent != eltCheckRecent.checked) {
--- a/toolkit/components/browser/nsWebBrowser.cpp
+++ b/toolkit/components/browser/nsWebBrowser.cpp
@@ -1245,16 +1245,17 @@ nsWebBrowser::Create()
 
   mDocShell->SetName(mInitInfo->name);
   if (mContentType == typeChromeWrapper) {
     mDocShell->SetItemType(nsIDocShellTreeItem::typeChrome);
   } else {
     mDocShell->SetItemType(nsIDocShellTreeItem::typeContent);
   }
   mDocShell->SetTreeOwner(mDocShellTreeOwner);
+  mDocShell->AttachBrowsingContext(nullptr);
 
   // If the webbrowser is a content docshell item then we won't hear any
   // events from subframes. To solve that we install our own chrome event
   // handler that always gets called (even for subframes) for any bubbling
   // event.
 
   mDocShell->InitSessionHistory();
 
--- a/xpfe/appshell/nsWebShellWindow.cpp
+++ b/xpfe/appshell/nsWebShellWindow.cpp
@@ -194,16 +194,18 @@ nsresult nsWebShellWindow::Initialize(ns
   // Create() so it knows what type it is.
   nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(do_QueryInterface(mDocShell));
   NS_ENSURE_TRUE(docShellAsItem, NS_ERROR_FAILURE);
   NS_ENSURE_SUCCESS(EnsureChromeTreeOwner(), NS_ERROR_FAILURE);
 
   docShellAsItem->SetTreeOwner(mChromeTreeOwner);
   docShellAsItem->SetItemType(nsIDocShellTreeItem::typeChrome);
 
+  mDocShell->AttachBrowsingContext(nullptr);
+
   r.MoveTo(0, 0);
   nsCOMPtr<nsIBaseWindow> docShellAsWin(do_QueryInterface(mDocShell));
   NS_ENSURE_SUCCESS(docShellAsWin->InitWindow(nullptr, mWindow,
    r.X(), r.Y(), r.Width(), r.Height()), NS_ERROR_FAILURE);
   NS_ENSURE_SUCCESS(docShellAsWin->Create(), NS_ERROR_FAILURE);
 
   // Attach a WebProgress listener.during initialization...
   nsCOMPtr<nsIWebProgress> webProgress(do_GetInterface(mDocShell, &rv));