Merge mozilla-inbound to mozilla-central. a=merge
authorDorel Luca <dluca@mozilla.com>
Fri, 02 Mar 2018 00:09:29 +0200
changeset 461150 2667f0b010c959940d7a12b4311d54a6abd74ac5
parent 461149 36806a2e73253732351b9d6079de744c484a64a5 (current diff)
parent 461135 2e915e01f05d02c2e39161733615af6f16982f29 (diff)
child 461151 2a4819d9193f2f4951e0e73f58e5094e3129ed3b
child 461171 5410375734d6ca8be3ff06acdc1c951d87d644ee
child 461196 a1330e4089ad73ec22d89b3162bf50bfa534feb8
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone60.0a1
first release with
nightly linux32
2667f0b010c9 / 60.0a1 / 20180301223350 / files
nightly linux64
2667f0b010c9 / 60.0a1 / 20180301223350 / files
nightly mac
2667f0b010c9 / 60.0a1 / 20180301223350 / files
nightly win32
2667f0b010c9 / 60.0a1 / 20180301223350 / files
nightly win64
2667f0b010c9 / 60.0a1 / 20180301223350 / 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 mozilla-inbound to mozilla-central. a=merge
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1042,21 +1042,17 @@ pref("dom.ipc.plugins.sandbox-level.flas
 #endif
 
 #if defined(MOZ_CONTENT_SANDBOX)
 // This controls the strength of the Windows content process sandbox for testing
 // purposes. This will require a restart.
 // On windows these levels are:
 // See - security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
 // SetSecurityLevelForContentProcess() for what the different settings mean.
-#if defined(NIGHTLY_BUILD)
 pref("security.sandbox.content.level", 5);
-#else
-pref("security.sandbox.content.level", 4);
-#endif
 
 // This controls the depth of stack trace that is logged when Windows sandbox
 // logging is turned on.  This is only currently available for the content
 // process because the only other sandbox (for GMP) has too strict a policy to
 // allow stack tracing.  This does not require a restart to take effect.
 pref("security.sandbox.windows.log.stackTraceDepth", 0);
 #endif
 
--- a/devtools/client/inspector/fonts/fonts.js
+++ b/devtools/client/inspector/fonts/fonts.js
@@ -1,23 +1,21 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=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/. */
 
 "use strict";
 
+const { gDevTools } = require("devtools/client/framework/devtools");
 const { getColor } = require("devtools/client/shared/theme");
-
 const { createFactory, createElement } = require("devtools/client/shared/vendor/react");
 const { Provider } = require("devtools/client/shared/vendor/react-redux");
 
-const { gDevTools } = require("devtools/client/framework/devtools");
-
 const FontsApp = createFactory(require("./components/FontsApp"));
 
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const INSPECTOR_L10N =
   new LocalizationHelper("devtools/client/locales/inspector.properties");
 
 const { updateFonts } = require("./actions/fonts");
 const { updatePreviewText } = require("./actions/font-options");
@@ -188,16 +186,21 @@ class FontInspector {
     }
 
     let options = {
       includePreviews: true,
       previewText,
       previewFillStyle: getColor("body-color")
     };
 
+    // Add the includeVariations argument into the option to get font variation data.
+    if (this.pageStyle.supportsFontVariations) {
+      options.includeVariations = true;
+    }
+
     fonts = await this.getFontsForNode(node, options);
     otherFonts = await this.getFontsNotInNode(fonts, options);
 
     if (!fonts.length && !otherFonts.length) {
       // No fonts to display. Clear the previously shown fonts.
       if (this.store) {
         this.store.dispatch(updateFonts(fonts, otherFonts));
       }
--- a/devtools/client/inspector/fonts/types.js
+++ b/devtools/client/inspector/fonts/types.js
@@ -2,16 +2,55 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
 /**
+ * A font variation axis.
+ */
+const fontVariationAxis = exports.fontVariationAxis = {
+  // The OpenType tag name of the variation axis
+  tag: PropTypes.string,
+
+  // The axis name of the variation axis
+  name: PropTypes.string,
+
+  // The minimum value of the variation axis
+  minValue: PropTypes.number,
+
+  // The maximum value of the variation axis
+  maxValue: PropTypes.number,
+
+  // The default value of the variation axis
+  defaultValue: PropTypes.number,
+};
+
+const fontVariationInstanceValue = exports.fontVariationInstanceValue = {
+  // The axis name of the variation axis
+  axis: PropTypes.string,
+
+  // The value of the variation axis
+  value: PropTypes.number,
+};
+
+/**
+ * A font variation instance.
+ */
+const fontVariationInstance = exports.fontVariationInstance = {
+  // The variation instance name of the font
+  axis: PropTypes.string,
+
+  // The font variation values for the variation instance of the font
+  values: PropTypes.arrayOf(PropTypes.shape(fontVariationInstanceValue)),
+};
+
+/**
  * A single font.
  */
 const font = exports.font = {
   // The format of the font
   format: PropTypes.string,
 
   // The name of the font
   name: PropTypes.string,
@@ -22,25 +61,31 @@ const font = exports.font = {
   // Object containing the CSS rule for the font
   rule: PropTypes.object,
 
   // The text of the CSS rule
   ruleText: PropTypes.string,
 
   // The URI of the font file
   URI: PropTypes.string,
+
+  // The variation axes of the font
+  variationAxes: PropTypes.arrayOf(PropTypes.shape(fontVariationAxis)),
+
+  // The variation instances of the font
+  variationInstances: PropTypes.arrayOf(PropTypes.shape(fontVariationInstance))
 };
 
 exports.fontOptions = {
   // The current preview text
   previewText: PropTypes.string,
 };
 
 /**
- * Font data
+ * Font data.
  */
 exports.fontData = {
   // The fonts used in the current element.
   fonts: PropTypes.arrayOf(PropTypes.shape(font)),
 
   // Fonts used elsewhere.
   otherFonts: PropTypes.arrayOf(PropTypes.shape(font)),
 };
--- a/devtools/client/memory/components/SnapshotListItem.js
+++ b/devtools/client/memory/components/SnapshotListItem.js
@@ -86,17 +86,17 @@ class SnapshotListItem extends Component
       details = dom.span({ className: "snapshot-state" }, statusText);
     }
 
     let saveLink = !snapshot.path ? void 0 : dom.a({
       onClick: () => onSave(snapshot),
       className: "save",
     }, L10N.getStr("snapshot.io.save"));
 
-    let deleteButton = !snapshot.path ? void 0 : dom.div({
+    let deleteButton = !snapshot.path ? void 0 : dom.button({
       onClick: () => onDelete(snapshot),
       className: "delete",
       title: L10N.getStr("snapshot.io.delete")
     });
 
     return (
       dom.li({ className, onClick },
         dom.span({
--- a/devtools/client/shared/components/VirtualizedTree.js
+++ b/devtools/client/shared/components/VirtualizedTree.js
@@ -806,17 +806,17 @@ class TreeNodeClass extends Component {
       ariaExpanded = true;
     }
 
     return dom.div(
       {
         id: this.props.id,
         className: classList.join(" "),
         role: "treeitem",
-        "aria-level": this.props.depth,
+        "aria-level": this.props.depth + 1,
         onClick: this.props.onClick,
         "aria-expanded": ariaExpanded,
         "data-expanded": this.props.expanded ? "" : undefined,
         "data-depth": this.props.depth,
         style: {
           padding: 0,
           margin: 0
         }
--- a/devtools/client/shared/components/test/mochitest/head.js
+++ b/devtools/client/shared/components/test/mochitest/head.js
@@ -91,17 +91,19 @@ function isAccessibleTree(tree, options 
     ok(treeNode.hasAttribute("aria-activedescendant"),
        "Tree has an active descendant set");
   }
 
   const treeNodes = [...treeNode.querySelectorAll(".tree-node")];
   for (let node of treeNodes) {
     ok(node.id, "TreeNode has an id");
     is(node.getAttribute("role"), "treeitem", "Tree item semantics is present");
-    ok(node.hasAttribute("aria-level"), "Aria level attribute is set");
+    is(parseInt(node.getAttribute("aria-level"), 10),
+       parseInt(node.getAttribute("data-depth"), 10) + 1,
+       "Aria level attribute is set correctly");
   }
 }
 
 // Encoding of the following tree/forest:
 //
 // A
 // |-- B
 // |   |-- E
--- a/devtools/client/themes/memory.css
+++ b/devtools/client/themes/memory.css
@@ -204,19 +204,23 @@ html, body, #app, #memory-tool {
 
 .snapshot-list-item .save {
   text-decoration: underline;
   cursor: pointer;
 }
 
 .snapshot-list-item .delete {
   cursor: pointer;
+  background-color: transparent;
+  border: 0;
+  padding: 0;
   position: relative;
   min-height: 1em;
   min-width: 1.3em;
+  color: currentColor;
 }
 
 .snapshot-list-item .delete::before {
   display: block;
   width: 16px;
   height: 16px;
   content: "";
   background-image: url("chrome://devtools/skin/images/close.svg");
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/accessibility-parent.js
@@ -0,0 +1,232 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci } = require("chrome");
+const Services = require("Services");
+const PREF_ACCESSIBILITY_FORCE_DISABLED = "accessibility.force_disabled";
+
+/**
+ * A helper class that does all the work related to accessibility service
+ * lifecycle (initialization, shutdown, consumer changes, etc) in parent
+ * parent process. It is not guaranteed that the AccessibilityActor starts in
+ * parent process and thus triggering these lifecycle functions directly is
+ * extremely unreliable.
+ */
+class AccessibilityParent {
+  constructor(mm, prefix) {
+    this._msgName = `debug:${prefix}accessibility`;
+    this.onAccessibilityMessage = this.onAccessibilityMessage.bind(this);
+    this.setMessageManager(mm);
+
+    this.userPref = Services.prefs.getIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED);
+    Services.obs.addObserver(this, "a11y-consumers-changed");
+    Services.prefs.addObserver(PREF_ACCESSIBILITY_FORCE_DISABLED, this);
+
+    if (this.enabled && !this.accService) {
+      // Set a local reference to an accessibility service if accessibility was
+      // started elsewhere to ensure that parent process a11y service does not
+      // get GC'ed away.
+      this.accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+        Ci.nsIAccessibilityService);
+    }
+
+    this.messageManager.sendAsyncMessage(`${this._msgName}:event`, {
+      topic: "initialized",
+      data: {
+        canBeDisabled: this.canBeDisabled,
+        canBeEnabled: this.canBeEnabled
+      }
+    });
+  }
+
+  /**
+   * Set up message manager listener to listen for messages coming from the
+   * AccessibilityActor when it is instantiated in the child process.
+   *
+   * @param {Object} mm
+   *        Message manager that corresponds to the current content tab.
+   */
+  setMessageManager(mm) {
+    if (this.messageManager === mm) {
+      return;
+    }
+
+    if (this.messageManager) {
+      // If the browser was swapped we need to reset the message manager.
+      let oldMM = this.messageManager;
+      oldMM.removeMessageListener(this._msgName, this.onAccessibilityMessage);
+    }
+
+    this.messageManager = mm;
+    if (mm) {
+      mm.addMessageListener(this._msgName, this.onAccessibilityMessage);
+    }
+  }
+
+  /**
+   * Content AccessibilityActor message listener.
+   *
+   * @param  {String} msg
+   *         Name of the action to perform.
+   */
+  onAccessibilityMessage(msg) {
+    let { action } = msg.json;
+    switch (action) {
+      case "enable":
+        this.enable();
+        break;
+
+      case "disable":
+        this.disable();
+        break;
+
+      case "disconnect":
+        this.destroy();
+        break;
+
+      default:
+        break;
+    }
+  }
+
+  observe(subject, topic, data) {
+    if (topic === "a11y-consumers-changed") {
+      // This event is fired when accessibility service consumers change. Since
+      // this observer lives in parent process there are 2 possible consumers of
+      // a11y service: XPCOM and PlatformAPI (e.g. screen readers). We only care
+      // about PlatformAPI consumer changes because when set, we can no longer
+      // disable accessibility service.
+      let { PlatformAPI } = JSON.parse(data);
+      this.messageManager.sendAsyncMessage(`${this._msgName}:event`, {
+        topic: "can-be-disabled-change",
+        data: !PlatformAPI
+      });
+    } else if (!this.disabling && topic === "nsPref:changed" &&
+               data === PREF_ACCESSIBILITY_FORCE_DISABLED) {
+      // PREF_ACCESSIBILITY_FORCE_DISABLED preference change event. When set to
+      // >=1, it means that the user wants to disable accessibility service and
+      // prevent it from starting in the future. Note: we also check
+      // this.disabling state when handling this pref change because this is how
+      // we disable the accessibility inspector itself.
+      this.messageManager.sendAsyncMessage(`${this._msgName}:event`, {
+        topic: "can-be-enabled-change",
+        data: Services.prefs.getIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED) < 1
+      });
+    }
+  }
+
+  /**
+   * A getter that indicates if accessibility service is enabled.
+   *
+   * @return {Boolean}
+   *         True if accessibility service is on.
+   */
+  get enabled() {
+    return Services.appinfo.accessibilityEnabled;
+  }
+
+  /**
+   * A getter that indicates if the accessibility service can be disabled.
+   *
+   * @return {Boolean}
+   *         True if accessibility service can be disabled.
+   */
+  get canBeDisabled() {
+    if (this.enabled) {
+      let a11yService = Cc["@mozilla.org/accessibilityService;1"].getService(
+        Ci.nsIAccessibilityService);
+      let { PlatformAPI } = JSON.parse(a11yService.getConsumers());
+      return !PlatformAPI;
+    }
+
+    return true;
+  }
+
+  /**
+   * A getter that indicates if the accessibility service can be enabled.
+   *
+   * @return {Boolean}
+   *         True if accessibility service can be enabled.
+   */
+  get canBeEnabled() {
+    return Services.prefs.getIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED) < 1;
+  }
+
+  /**
+   * Enable accessibility service (via XPCOM service).
+   */
+  enable() {
+    if (this.enabled || !this.canBeEnabled) {
+      return;
+    }
+
+    this.accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+      Ci.nsIAccessibilityService);
+  }
+
+  /**
+   * Force disable accessibility service. This method removes the reference to
+   * the XPCOM a11y service object and flips the
+   * PREF_ACCESSIBILITY_FORCE_DISABLED preference on and off to shutdown a11y
+   * service.
+   */
+  disable() {
+    if (!this.enabled || !this.canBeDisabled) {
+      return;
+    }
+
+    this.disabling = true;
+    this.accService = null;
+    // Set PREF_ACCESSIBILITY_FORCE_DISABLED to 1 to force disable
+    // accessibility service. This is the only way to guarantee an immediate
+    // accessibility service shutdown in all processes. This also prevents
+    // accessibility service from starting up in the future.
+    Services.prefs.setIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED, 1);
+    // Set PREF_ACCESSIBILITY_FORCE_DISABLED back to previous default or user
+    // set value. This will not start accessibility service until the user
+    // activates it again. It simply ensures that accessibility service can
+    // start again (when value is below 1).
+    Services.prefs.setIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED, this.userPref);
+    delete this.disabling;
+  }
+
+  /**
+   * Destroy thie helper class, remove all listeners and if possible disable
+   * accessibility service in the parent process.
+   */
+  destroy() {
+    Services.obs.removeObserver(this, "a11y-consumers-changed");
+    Services.prefs.removeObserver(PREF_ACCESSIBILITY_FORCE_DISABLED, this);
+    this.setMessageManager(null);
+    this.accService = null;
+  }
+}
+
+/**
+ * Setup function that runs in parent process and setups AccessibleActor bits
+ * that must always run in parent process.
+ *
+ * @param  {Object} options.mm
+ *         Message manager that corresponds to the current content tab.
+ * @param  {String} options.prefix
+ *         Unique prefix for message manager messages.
+ * @return {Object}
+ *         Defines event listeners for when client disconnects or browser gets
+ *         swapped.
+ */
+function setupParentProcess({ mm, prefix }) {
+  let accessibility = new AccessibilityParent(mm, prefix);
+
+  return {
+    onBrowserSwap: newMM => accessibility.setMessageManager(newMM),
+    onDisconnected: () => {
+      accessibility.destroy();
+      accessibility = null;
+    }
+  };
+}
+
+exports.setupParentProcess = setupParentProcess;
--- a/devtools/server/actors/accessibility.js
+++ b/devtools/server/actors/accessibility.js
@@ -1,29 +1,37 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { Cc, Ci, Cu } = require("chrome");
+const { Cc, Ci } = require("chrome");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const { DebuggerServer } = require("devtools/server/main");
 const Services = require("Services");
 const { Actor, ActorClassWithSpec } = require("devtools/shared/protocol");
 const defer = require("devtools/shared/defer");
 const events = require("devtools/shared/event-emitter");
 const {
   accessibleSpec,
   accessibleWalkerSpec,
   accessibilitySpec
 } = require("devtools/shared/specs/accessibility");
 
+const { isXUL } = require("devtools/server/actors/highlighters/utils/markup");
+const { isWindowIncluded } = require("devtools/shared/layout/utils");
+const { CustomHighlighterActor, register } =
+  require("devtools/server/actors/highlighters");
+const PREF_ACCESSIBILITY_FORCE_DISABLED = "accessibility.force_disabled";
+
 const nsIAccessibleEvent = Ci.nsIAccessibleEvent;
 const nsIAccessibleStateChangeEvent = Ci.nsIAccessibleStateChangeEvent;
 const nsIPropertyElement = Ci.nsIPropertyElement;
+const nsIAccessibleRole = Ci.nsIAccessibleRole;
 
 const {
   EVENT_TEXT_CHANGED,
   EVENT_TEXT_INSERTED,
   EVENT_TEXT_REMOVED,
   EVENT_ACCELERATOR_CHANGE,
   EVENT_ACTION_CHANGE,
   EVENT_DEFACTION_CHANGE,
@@ -34,27 +42,104 @@ const {
   EVENT_NAME_CHANGE,
   EVENT_OBJECT_ATTRIBUTE_CHANGED,
   EVENT_REORDER,
   EVENT_STATE_CHANGE,
   EVENT_TEXT_ATTRIBUTE_CHANGED,
   EVENT_VALUE_CHANGE
 } = nsIAccessibleEvent;
 
-const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+// TODO: We do not need this once bug 1422913 is fixed. We also would not need
+// to fire a name change event for an accessible that has an updated subtree and
+// that has its name calculated from the said subtree.
+const NAME_FROM_SUBTREE_RULE_ROLES = new Set([
+  nsIAccessibleRole.ROLE_BUTTONDROPDOWN,
+  nsIAccessibleRole.ROLE_BUTTONDROPDOWNGRID,
+  nsIAccessibleRole.ROLE_BUTTONMENU,
+  nsIAccessibleRole.ROLE_CELL,
+  nsIAccessibleRole.ROLE_CHECKBUTTON,
+  nsIAccessibleRole.ROLE_CHECK_MENU_ITEM,
+  nsIAccessibleRole.ROLE_CHECK_RICH_OPTION,
+  nsIAccessibleRole.ROLE_COLUMN,
+  nsIAccessibleRole.ROLE_COLUMNHEADER,
+  nsIAccessibleRole.ROLE_COMBOBOX_OPTION,
+  nsIAccessibleRole.ROLE_DEFINITION,
+  nsIAccessibleRole.ROLE_GRID_CELL,
+  nsIAccessibleRole.ROLE_HEADING,
+  nsIAccessibleRole.ROLE_HELPBALLOON,
+  nsIAccessibleRole.ROLE_HTML_CONTAINER,
+  nsIAccessibleRole.ROLE_KEY,
+  nsIAccessibleRole.ROLE_LABEL,
+  nsIAccessibleRole.ROLE_LINK,
+  nsIAccessibleRole.ROLE_LISTITEM,
+  nsIAccessibleRole.ROLE_MATHML_IDENTIFIER,
+  nsIAccessibleRole.ROLE_MATHML_NUMBER,
+  nsIAccessibleRole.ROLE_MATHML_OPERATOR,
+  nsIAccessibleRole.ROLE_MATHML_TEXT,
+  nsIAccessibleRole.ROLE_MATHML_STRING_LITERAL,
+  nsIAccessibleRole.ROLE_MATHML_GLYPH,
+  nsIAccessibleRole.ROLE_MENUITEM,
+  nsIAccessibleRole.ROLE_OPTION,
+  nsIAccessibleRole.ROLE_OUTLINEITEM,
+  nsIAccessibleRole.ROLE_PAGETAB,
+  nsIAccessibleRole.ROLE_PARENT_MENUITEM,
+  nsIAccessibleRole.ROLE_PUSHBUTTON,
+  nsIAccessibleRole.ROLE_RADIOBUTTON,
+  nsIAccessibleRole.ROLE_RADIO_MENU_ITEM,
+  nsIAccessibleRole.ROLE_RICH_OPTION,
+  nsIAccessibleRole.ROLE_ROW,
+  nsIAccessibleRole.ROLE_ROWHEADER,
+  nsIAccessibleRole.ROLE_SUMMARY,
+  nsIAccessibleRole.ROLE_SWITCH,
+  nsIAccessibleRole.ROLE_TABLE_COLUMN_HEADER,
+  nsIAccessibleRole.ROLE_TABLE_ROW_HEADER,
+  nsIAccessibleRole.ROLE_TEAR_OFF_MENU_ITEM,
+  nsIAccessibleRole.ROLE_TERM,
+  nsIAccessibleRole.ROLE_TOGGLE_BUTTON,
+  nsIAccessibleRole.ROLE_TOOLTIP
+]);
+
+const IS_OSX = Services.appinfo.OS === "Darwin";
+
+register("AccessibleHighlighter", "accessible");
+register("XULWindowAccessibleHighlighter", "xul-accessible");
+
+/**
+ * Helper function that determines if nsIAccessible object is in defunct state.
+ *
+ * @param  {nsIAccessible}  accessible
+ *         object to be tested.
+ * @return {Boolean}
+ *         True if accessible object is defunct, false otherwise.
+ */
+function isDefunct(accessible) {
+  let defunct = false;
+
+  try {
+    let extState = {};
+    accessible.getState({}, extState);
+    // extState.value is a bitmask. We are applying bitwise AND to mask out
+    // irrelevant states.
+    defunct = !!(extState.value & Ci.nsIAccessibleStates.EXT_STATE_DEFUNCT);
+  } catch (e) {
+    defunct = true;
+  }
+
+  return defunct;
+}
 
 /**
  * Set of actors that expose accessibility tree information to the
  * devtools protocol clients.
  *
  * The |Accessibility| actor is the main entry point. It is used to request
  * an AccessibleWalker actor that caches the tree of Accessible actors.
  *
  * The |AccessibleWalker| actor is used to cache all seen Accessible actors as
- * well as observe all relevant accesible events.
+ * well as observe all relevant accessible events.
  *
  * The |Accessible| actor provides information about a particular accessible
  * object, its properties, , attributes, states, relations, etc.
  */
 
 /**
  * The AccessibleActor provides information about a given accessible object: its
  * role, name, states, etc.
@@ -67,28 +152,17 @@ const AccessibleActor = ActorClassWithSp
 
     /**
      * Indicates if the raw accessible is no longer alive.
      *
      * @return Boolean
      */
     Object.defineProperty(this, "isDefunct", {
       get() {
-        let defunct = false;
-
-        try {
-          let extState = {};
-          this.rawAccessible.getState({}, extState);
-          // extState.value is a bitmask. We are applying bitwise AND to mask out
-          // irrelelvant states.
-          defunct = !!(extState.value & Ci.nsIAccessibleStates.EXT_STATE_DEFUNCT);
-        } catch (e) {
-          defunct = true;
-        }
-
+        let defunct = isDefunct(this.rawAccessible);
         if (defunct) {
           delete this.isDefunct;
           this.isDefunct = true;
           return this.isDefunct;
         }
 
         return defunct;
       },
@@ -160,189 +234,201 @@ const AccessibleActor = ActorClassWithSp
 
   get domNodeType() {
     if (this.isDefunct) {
       return 0;
     }
     return this.rawAccessible.DOMNode ? this.rawAccessible.DOMNode.nodeType : 0;
   },
 
+  get parentAcc() {
+    if (this.isDefunct) {
+      return null;
+    }
+    return this.walker.addRef(this.rawAccessible.parent);
+  },
+
   children() {
     let children = [];
     if (this.isDefunct) {
       return children;
     }
 
     for (let child = this.rawAccessible.firstChild; child; child = child.nextSibling) {
       children.push(this.walker.addRef(child));
     }
     return children;
   },
 
-  getIndexInParent() {
+  get indexInParent() {
     if (this.isDefunct) {
       return -1;
     }
-    return this.rawAccessible.indexInParent;
+
+    try {
+      return this.rawAccessible.indexInParent;
+    } catch (e) {
+      // Accessible is dead.
+      return -1;
+    }
   },
 
-  getActions() {
+  get actions() {
     let actions = [];
     if (this.isDefunct) {
       return actions;
     }
 
     for (let i = 0; i < this.rawAccessible.actionCount; i++) {
       actions.push(this.rawAccessible.getActionDescription(i));
     }
     return actions;
   },
 
-  getState() {
+  get states() {
     if (this.isDefunct) {
       return [];
     }
 
     let state = {};
     let extState = {};
     this.rawAccessible.getState(state, extState);
     return [
       ...this.walker.a11yService.getStringStates(state.value, extState.value)
     ];
   },
 
-  getAttributes() {
+  get attributes() {
     if (this.isDefunct || !this.rawAccessible.attributes) {
       return {};
     }
 
     let attributes = {};
     let attrsEnum = this.rawAccessible.attributes.enumerate();
     while (attrsEnum.hasMoreElements()) {
       let { key, value } = attrsEnum.getNext().QueryInterface(
         nsIPropertyElement);
       attributes[key] = value;
     }
 
     return attributes;
   },
 
+  get bounds() {
+    if (this.isDefunct) {
+      return null;
+    }
+
+    let x = {}, y = {}, w = {}, h = {};
+    try {
+      this.rawAccessible.getBounds(x, y, w, h);
+      x = x.value;
+      y = y.value;
+      w = w.value;
+      h = h.value;
+    } catch (e) {
+      return null;
+    }
+
+    return { x, y, w, h };
+  },
+
   form() {
     return {
       actor: this.actorID,
       role: this.role,
       name: this.name,
       value: this.value,
       description: this.description,
       help: this.help,
       keyboardShortcut: this.keyboardShortcut,
       childCount: this.childCount,
       domNodeType: this.domNodeType,
-      walker: this.walker.form()
+      indexInParent: this.indexInParent,
+      states: this.states,
+      actions: this.actions,
+      attributes: this.attributes
     };
   }
 });
 
 /**
  * The AccessibleWalkerActor stores a cache of AccessibleActors that represent
  * accessible objects in a given document.
  *
  * It is also responsible for implicitely initializing and shutting down
  * accessibility engine by storing a reference to the XPCOM accessibility
  * service.
  */
 const AccessibleWalkerActor = ActorClassWithSpec(accessibleWalkerSpec, {
   initialize(conn, tabActor) {
     Actor.prototype.initialize.call(this, conn);
     this.tabActor = tabActor;
-    this.rootWin = tabActor.window;
-    this.rootDoc = tabActor.window.document;
     this.refMap = new Map();
-    // Accessibility Walker should only be considered ready, when raw accessible
-    // object for root document is fully initialized (e.g. does not have a
-    // 'busy' state)
-    this.readyDeferred = defer();
+    this.setA11yServiceGetter();
+    this.onPick = this.onPick.bind(this);
+    this.onHovered = this.onHovered.bind(this);
+    this.onKey = this.onKey.bind(this);
 
+    this.highlighter = CustomHighlighterActor(this, isXUL(this.rootWin) ?
+      "XULWindowAccessibleHighlighter" : "AccessibleHighlighter");
+  },
+
+  setA11yServiceGetter() {
     DevToolsUtils.defineLazyGetter(this, "a11yService", () => {
       Services.obs.addObserver(this, "accessible-event");
       return Cc["@mozilla.org/accessibilityService;1"].getService(
         Ci.nsIAccessibilityService);
     });
+  },
 
-    this.onLoad = this.onLoad.bind(this);
-    this.onUnload = this.onUnload.bind(this);
-
-    events.on(tabActor, "will-navigate", this.onUnload);
-    events.on(tabActor, "window-ready", this.onLoad);
+  get rootWin() {
+    return this.tabActor && this.tabActor.window;
   },
 
-  onUnload({ window }) {
-    let doc = window.document;
-    let actor = this.getRef(doc);
-
-    // If an accessible actor was never created for document, then there's
-    // nothing to clean up.
-    if (!actor) {
-      return;
-    }
-
-    // Purge document's subtree from accessible actors cache.
-    this.purgeSubtree(this.a11yService.getAccessibleFor(this.doc));
-    // If document is a root document, clear it's reference and cache.
-    if (this.rootDoc === doc) {
-      this.rootDoc = null;
-      this.refMap.clear();
-      this.readyDeferred = defer();
-    }
+  get rootDoc() {
+    return this.tabActor && this.tabActor.window.document;
   },
 
-  onLoad({ window, isTopLevel }) {
-    if (isTopLevel) {
-      // If root document is dead, unload it and clean up.
-      if (this.rootDoc && !Cu.isDeadWrapper(this.rootDoc) &&
-          this.rootDoc.defaultView) {
-        this.onUnload({ window: this.rootDoc.defaultView });
-      }
-
-      this.rootWin = window;
-      this.rootDoc = window.document;
-    }
-  },
-
-  destroy() {
-    if (this._destroyed) {
-      return;
-    }
-
-    this._destroyed = true;
-
+  reset() {
     try {
       Services.obs.removeObserver(this, "accessible-event");
     } catch (e) {
       // Accessible event observer might not have been initialized if a11y
       // service was never used.
     }
 
+    this.cancelPick();
+
     // Clean up accessible actors cache.
     if (this.refMap.size > 0) {
-      this.purgeSubtree(this.a11yService.getAccessibleFor(this.rootDoc));
-      this.refMap.clear();
+      try {
+        if (this.rootDoc) {
+          this.purgeSubtree(this.a11yService.getAccessibleFor(this.rootDoc),
+                            this.rootDoc);
+        }
+      } catch (e) {
+        // Accessibility service might be already destroyed.
+      }
     }
 
-    events.off(this.tabActor, "will-navigate", this.onUnload);
-    events.off(this.tabActor, "window-ready", this.onLoad);
+    delete this.a11yService;
+    this.setA11yServiceGetter();
+  },
+
+  destroy() {
+    Actor.prototype.destroy.call(this);
 
-    this.onLoad = null;
-    this.onUnload = null;
-    delete this.a11yService;
+    this.reset();
+
+    this.highlighter.destroy();
+    this.highlighter = null;
+
     this.tabActor = null;
-    this.rootDoc = null;
     this.refMap = null;
-
-    Actor.prototype.destroy.call(this);
   },
 
   getRef(rawAccessible) {
     return this.refMap.get(rawAccessible);
   },
 
   addRef(rawAccessible) {
     let actor = this.refMap.get(rawAccessible);
@@ -356,31 +442,37 @@ const AccessibleWalkerActor = ActorClass
 
     return actor;
   },
 
   /**
    * Clean up accessible actors cache for a given accessible's subtree.
    *
    * @param  {nsIAccessible} rawAccessible
+   * @param  {null|Object}   rawNode
    */
-  purgeSubtree(rawAccessible) {
+  purgeSubtree(rawAccessible, rawNode) {
     let actor = this.getRef(rawAccessible);
     if (actor && rawAccessible && !actor.isDefunct) {
       for (let child = rawAccessible.firstChild; child; child = child.nextSibling) {
         this.purgeSubtree(child);
       }
     }
 
     this.refMap.delete(rawAccessible);
 
     if (actor) {
       events.emit(this, "accessible-destroy", actor);
       actor.destroy();
     }
+
+    // If corresponding DOMNode is a top level document, clear entire cache.
+    if (rawNode && rawNode === this.rootDoc) {
+      this.refMap.clear();
+    }
   },
 
   /**
    * A helper method. Accessibility walker is assumed to have only 1 child which
    * is the top level document.
    */
   children() {
     return Promise.all([this.getDocument()]);
@@ -388,74 +480,105 @@ const AccessibleWalkerActor = ActorClass
 
   /**
    * A promise for a root document accessible actor that only resolves when its
    * corresponding document accessible object is fully loaded.
    *
    * @return {Promise}
    */
   getDocument() {
-    let doc = this.addRef(this.a11yService.getAccessibleFor(this.rootDoc));
-    let states = doc.getState();
+    if (!this.rootDoc || !this.rootDoc.documentElement) {
+      return this.once("document-ready").then(docAcc => this.addRef(docAcc));
+    }
 
-    if (states.includes("busy")) {
-      return this.readyDeferred.promise.then(() => doc);
+    if (isXUL(this.rootWin)) {
+      let doc = this.addRef(this.a11yService.getAccessibleFor(this.rootDoc));
+      return Promise.resolve(doc);
     }
 
-    this.readyDeferred.resolve();
-    return Promise.resolve(doc);
+    let doc = this.a11yService.getAccessibleFor(this.rootDoc);
+    let state = {};
+    doc.getState(state, {});
+    if (state.value & Ci.nsIAccessibleStates.STATE_BUSY) {
+      return this.once("document-ready").then(docAcc => this.addRef(docAcc));
+    }
+
+    return Promise.resolve(this.addRef(doc));
   },
 
   getAccessibleFor(domNode) {
     // We need to make sure that the document is loaded processed by a11y first.
     return this.getDocument().then(() =>
       this.addRef(this.a11yService.getAccessibleFor(domNode.rawNode)));
   },
 
+  async getAncestry(accessible) {
+    if (accessible.indexInParent === -1) {
+      return [];
+    }
+    const doc = await this.getDocument();
+    let ancestry = [];
+    try {
+      let parent = accessible;
+      while (parent && (parent = parent.parentAcc) && parent != doc) {
+        ancestry.push(parent);
+      }
+      ancestry.push(doc);
+    } catch (error) {
+      throw new Error(`Failed to get ancestor for ${accessible}: ${error}`);
+    }
+
+    return ancestry.map(parent => (
+      { accessible: parent, children: parent.children() }));
+  },
+
   /**
    * Accessible event observer function.
    *
    * @param {nsIAccessibleEvent} subject
    *                                      accessible event object.
    */
   observe(subject) {
     let event = subject.QueryInterface(nsIAccessibleEvent);
     let rawAccessible = event.accessible;
     let accessible = this.getRef(rawAccessible);
 
     switch (event.eventType) {
       case EVENT_STATE_CHANGE:
         let { state, isEnabled } = event.QueryInterface(nsIAccessibleStateChangeEvent);
-        let states = [...this.a11yService.getStringStates(state, 0)];
-
-        if (states.includes("busy") && !isEnabled) {
-          let { DOMNode } = event;
-          // If debugging chrome, wait for top level content document loaded,
-          // otherwise wait for root document loaded.
-          if (DOMNode == this.rootDoc || (
-            this.rootDoc.documentElement.namespaceURI === XUL_NS &&
-            this.rootWin.gBrowser.selectedBrowser.contentDocument == DOMNode)) {
-            this.readyDeferred.resolve();
+        let isBusy = state & Ci.nsIAccessibleStates.STATE_BUSY;
+        // Accessible document is recreated.
+        if (isBusy && !isEnabled && rawAccessible instanceof Ci.nsIAccessibleDocument) {
+          // Remove its existing cache from tree.
+          this.purgeSubtree(rawAccessible, event.DOMNode);
+          // If it's a top level document notify listeners about the document
+          // being ready.
+          if (event.DOMNode == this.rootDoc) {
+            events.emit(this, "document-ready", rawAccessible);
           }
         }
 
         if (accessible) {
           // Only propagate state change events for active accessibles.
-          if (states.includes("busy") && isEnabled) {
+          if (isBusy && isEnabled) {
+            if (rawAccessible instanceof Ci.nsIAccessibleDocument) {
+              // Remove its existing cache from tree.
+              this.purgeSubtree(rawAccessible, event.DOMNode);
+            }
             return;
           }
-          events.emit(accessible, "state-change", accessible.getState());
+          events.emit(accessible, "states-change", accessible.states);
         }
 
         break;
       case EVENT_NAME_CHANGE:
         if (accessible) {
           events.emit(accessible, "name-change", rawAccessible.name,
             event.DOMNode == this.rootDoc ?
-              undefined : this.getRef(rawAccessible.parent));
+              undefined : this.getRef(rawAccessible.parent), this);
         }
         break;
       case EVENT_VALUE_CHANGE:
         if (accessible) {
           events.emit(accessible, "value-change", rawAccessible.value);
         }
         break;
       case EVENT_DESCRIPTION_CHANGE:
@@ -465,74 +588,565 @@ const AccessibleWalkerActor = ActorClass
         break;
       case EVENT_HELP_CHANGE:
         if (accessible) {
           events.emit(accessible, "help-change", rawAccessible.help);
         }
         break;
       case EVENT_REORDER:
         if (accessible) {
-          events.emit(accessible, "reorder", rawAccessible.childCount);
+          accessible.children().forEach(child =>
+            events.emit(child, "index-in-parent-change", child.indexInParent));
+          events.emit(accessible, "reorder", rawAccessible.childCount, this);
         }
         break;
       case EVENT_HIDE:
         this.purgeSubtree(rawAccessible);
         break;
       case EVENT_DEFACTION_CHANGE:
       case EVENT_ACTION_CHANGE:
         if (accessible) {
-          events.emit(accessible, "actions-change", accessible.getActions());
+          events.emit(accessible, "actions-change", accessible.actions);
         }
         break;
       case EVENT_TEXT_CHANGED:
       case EVENT_TEXT_INSERTED:
       case EVENT_TEXT_REMOVED:
         if (accessible) {
-          events.emit(accessible, "text-change");
+          events.emit(accessible, "text-change", this);
+          if (NAME_FROM_SUBTREE_RULE_ROLES.has(rawAccessible.role)) {
+            events.emit(accessible, "name-change", rawAccessible.name,
+              event.DOMNode == this.rootDoc ?
+                undefined : this.getRef(rawAccessible.parent), this);
+          }
         }
         break;
       case EVENT_DOCUMENT_ATTRIBUTES_CHANGED:
       case EVENT_OBJECT_ATTRIBUTE_CHANGED:
       case EVENT_TEXT_ATTRIBUTE_CHANGED:
         if (accessible) {
-          events.emit(accessible, "attributes-change", accessible.getAttributes());
+          events.emit(accessible, "attributes-change", accessible.attributes);
         }
         break;
       case EVENT_ACCELERATOR_CHANGE:
         if (accessible) {
           events.emit(accessible, "shortcut-change", rawAccessible.keyboardShortcut);
         }
         break;
       default:
         break;
     }
+  },
+
+  /**
+   * Public method used to show an accessible object highlighter on the client
+   * side.
+   *
+   * @param  {Object} accessible
+   *         AccessibleActor to be highlighted.
+   * @param  {Object} options
+   *         Object used for passing options. Available options:
+   *         - duration {Number}
+   *                    Duration of time that the highlighter should be shown.
+   * @return {Boolean}
+   *         True if highlighter shows the accessible object.
+   */
+  highlightAccessible(accessible, options = {}) {
+    let bounds = accessible.bounds;
+    if (!bounds) {
+      return false;
+    }
+
+    return this.highlighter.show({ rawNode: accessible.rawAccessible.DOMNode },
+                                 { ...options, ...bounds });
+  },
+
+  /**
+   * Public method used to hide an accessible object highlighter on the client
+   * side.
+   */
+  unhighlight() {
+    this.highlighter.hide();
+  },
+
+  /**
+   * Picking state that indicates if picking is currently enabled and, if so,
+   * what the current and hovered accessible objects are.
+   */
+  _isPicking: false,
+  _currentAccessible: null,
+
+  /**
+   * Check is event handling is allowed.
+   */
+  _isEventAllowed: function ({ view }) {
+    return this.rootWin instanceof Ci.nsIDOMChromeWindow ||
+           isWindowIncluded(this.rootWin, view);
+  },
+
+  _preventContentEvent(event) {
+    event.stopPropagation();
+    event.preventDefault();
+  },
+
+  /**
+   * Click event handler for when picking is enabled.
+   *
+   * @param  {Object} event
+   *         Current click event.
+   */
+  async onPick(event) {
+    if (!this._isPicking) {
+      return;
+    }
+
+    this._preventContentEvent(event);
+    if (!this._isEventAllowed(event)) {
+      return;
+    }
+
+    // If shift is pressed, this is only a preview click, send the event to
+    // the client, but don't stop picking.
+    if (event.shiftKey) {
+      if (!this._currentAccessible) {
+        this._currentAccessible = await this._findAndAttachAccessible(event);
+      }
+      events.emit(this, "picker-accessible-previewed", this._currentAccessible);
+      return;
+    }
+
+    this._stopPickerListeners();
+    this._isPicking = false;
+    if (!this._currentAccessible) {
+      this._currentAccessible = await this._findAndAttachAccessible(event);
+    }
+    events.emit(this, "picker-accessible-picked", this._currentAccessible);
+  },
+
+  /**
+   * Hover event handler for when picking is enabled.
+   *
+   * @param  {Object} event
+   *         Current hover event.
+   */
+  async onHovered(event) {
+    if (!this._isPicking) {
+      return;
+    }
+
+    this._preventContentEvent(event);
+    if (!this._isEventAllowed(event)) {
+      return;
+    }
+
+    let accessible = await this._findAndAttachAccessible(event);
+    if (!accessible) {
+      return;
+    }
+
+    if (this._currentAccessible !== accessible) {
+      let { bounds } = accessible;
+      if (bounds) {
+        this.highlighter.show({ rawNode: event.originalTarget || event.target }, bounds);
+      }
+
+      events.emit(this, "picker-accessible-hovered", accessible);
+      this._currentAccessible = accessible;
+    }
+  },
+
+  /**
+   * Keyboard event handler for when picking is enabled.
+   *
+   * @param  {Object} event
+   *         Current keyboard event.
+   */
+  onKey(event) {
+    if (!this._currentAccessible || !this._isPicking) {
+      return;
+    }
+
+    this._preventContentEvent(event);
+    if (!this._isEventAllowed(event)) {
+      return;
+    }
+
+    /**
+     * KEY: Action/scope
+     * ENTER/CARRIAGE_RETURN: Picks current accessible
+     * ESC/CTRL+SHIFT+C: Cancels picker
+     */
+    switch (event.keyCode) {
+      // Select the element.
+      case event.DOM_VK_RETURN:
+        this._onPick(event);
+        break;
+      // Cancel pick mode.
+      case event.DOM_VK_ESCAPE:
+        this.cancelPick();
+        events.emit(this, "picker-accessible-canceled");
+        break;
+      case event.DOM_VK_C:
+        if ((IS_OSX && event.metaKey && event.altKey) ||
+          (!IS_OSX && event.ctrlKey && event.shiftKey)) {
+          this.cancelPick();
+          events.emit(this, "picker-accessible-canceled");
+        }
+        break;
+      default:
+        break;
+    }
+  },
+
+  /**
+   * Picker method that starts picker content listeners.
+   */
+  pick: function () {
+    if (!this._isPicking) {
+      this._isPicking = true;
+      this._startPickerListeners();
+    }
+  },
+
+  /**
+   * This pick method also focuses the highlighter's target window.
+   */
+  pickAndFocus: function () {
+    this.pick();
+    this.rootWin.focus();
+  },
+
+  /**
+   * Find accessible object that corresponds to a DOMNode and attach (lookup its
+   * ancestry to the root doc) to the AccessibilityWalker tree.
+   *
+   * @param  {Object} event
+   *         Correspoinding content event.
+   * @return {null|Object}
+   *         Accessible object, if available, that corresponds to a DOM node.
+   */
+  async _findAndAttachAccessible(event) {
+    let target = event.originalTarget || event.target;
+    let rawAccessible = this.a11yService.getAccessibleFor(target);
+    // If raw accessible object is defunct or detached, no need to cache it and
+    // its ancestry.
+    if (!rawAccessible || isDefunct(rawAccessible) || rawAccessible.indexInParent < 0) {
+      return {};
+    }
+
+    const doc = await this.getDocument();
+    let accessible = this.addRef(rawAccessible);
+    // There is a chance that ancestry lookup can fail if the accessible is in
+    // the detached subtree. At that point the root accessible object would be
+    // defunct and accessing it via parent property will throw.
+    try {
+      let parent = accessible;
+      while (parent && parent != doc) {
+        parent = parent.parentAcc;
+      }
+    } catch (error) {
+      throw new Error(`Failed to get ancestor for ${accessible}: ${error}`);
+    }
+
+    return accessible;
+  },
+
+  /**
+   * Start picker content listeners.
+   */
+  _startPickerListeners: function () {
+    let target = this.tabActor.chromeEventHandler;
+    target.addEventListener("mousemove", this.onHovered, true);
+    target.addEventListener("click", this.onPick, true);
+    target.addEventListener("mousedown", this._preventContentEvent, true);
+    target.addEventListener("mouseup", this._preventContentEvent, true);
+    target.addEventListener("dblclick", this._preventContentEvent, true);
+    target.addEventListener("keydown", this.onKey, true);
+    target.addEventListener("keyup", this._preventContentEvent, true);
+  },
+
+  /**
+   * If content is still alive, stop picker content listeners.
+   */
+  _stopPickerListeners: function () {
+    let target = this.tabActor.chromeEventHandler;
+
+    if (!target) {
+      return;
+    }
+
+    target.removeEventListener("mousemove", this.onHovered, true);
+    target.removeEventListener("click", this.onPick, true);
+    target.removeEventListener("mousedown", this._preventContentEvent, true);
+    target.removeEventListener("mouseup", this._preventContentEvent, true);
+    target.removeEventListener("dblclick", this._preventContentEvent, true);
+    target.removeEventListener("keydown", this.onKey, true);
+    target.removeEventListener("keyup", this._preventContentEvent, true);
+  },
+
+  /**
+   * Cacncel picker pick. Remvoe all content listeners and hide the highlighter.
+   */
+  cancelPick: function () {
+    this.highlighter.hide();
+
+    if (this._isPicking) {
+      this._stopPickerListeners();
+      this._isPicking = false;
+      this._currentAccessible = null;
+    }
   }
 });
 
 /**
  * The AccessibilityActor is a top level container actor that initializes
  * accessible walker and is the top-most point of interaction for accessibility
  * tools UI.
  */
 const AccessibilityActor = ActorClassWithSpec(accessibilitySpec, {
   initialize(conn, tabActor) {
     Actor.prototype.initialize.call(this, conn);
+
+    this.initializedDeferred = defer();
+
+    if (DebuggerServer.isInChildProcess) {
+      this._msgName = `debug:${this.conn.prefix}accessibility`;
+      this.conn.setupInParent({
+        module: "devtools/server/actors/accessibility-parent",
+        setupParent: "setupParentProcess"
+      });
+
+      this.onMessage = this.onMessage.bind(this);
+      this.messageManager.addMessageListener(`${this._msgName}:event`, this.onMessage);
+    } else {
+      this.userPref = Services.prefs.getIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED);
+      Services.obs.addObserver(this, "a11y-consumers-changed");
+      Services.prefs.addObserver(PREF_ACCESSIBILITY_FORCE_DISABLED, this);
+      this.initializedDeferred.resolve();
+    }
+
+    Services.obs.addObserver(this, "a11y-init-or-shutdown");
     this.tabActor = tabActor;
   },
 
+  bootstrap() {
+    return this.initializedDeferred.promise.then(() => ({
+      enabled: this.enabled,
+      canBeEnabled: this.canBeEnabled,
+      canBeDisabled: this.canBeDisabled
+    }));
+  },
+
+  get enabled() {
+    return Services.appinfo.accessibilityEnabled;
+  },
+
+  get canBeEnabled() {
+    if (DebuggerServer.isInChildProcess) {
+      return this._canBeEnabled;
+    }
+
+    return Services.prefs.getIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED) < 1;
+  },
+
+  get canBeDisabled() {
+    if (DebuggerServer.isInChildProcess) {
+      return this._canBeDisabled;
+    } else if (!this.enabled) {
+      return true;
+    }
+
+    let { PlatformAPI } = JSON.parse(this.walker.a11yService.getConsumers());
+    return !PlatformAPI;
+  },
+
+  /**
+   * Getter for a message manager that corresponds to a current tab. It is onyl
+   * used if the AccessibilityActor runs in the child process.
+   *
+   * @return {Object}
+   *         Message manager that corresponds to the current content tab.
+   */
+  get messageManager() {
+    if (!DebuggerServer.isInChildProcess) {
+      throw new Error(
+        "Message manager should only be used when actor is in child process.");
+    }
+
+    return this.conn.parentMessageManager;
+  },
+
+  onMessage(msg) {
+    let { topic, data } = msg.data;
+
+    switch (topic) {
+      case "initialized":
+        this._canBeEnabled = data.canBeEnabled;
+        this._canBeDisabled = data.canBeDisabled;
+        this.initializedDeferred.resolve();
+        break;
+      case "can-be-disabled-change":
+        this._canBeDisabled = data;
+        events.emit(this, "can-be-disabled-change", this.canBeDisabled);
+        break;
+
+      case "can-be-enabled-change":
+        this._canBeEnabled = data;
+        events.emit(this, "can-be-enabled-change", this.canBeEnabled);
+        break;
+
+      default:
+        break;
+    }
+  },
+
+  /**
+   * Enable acessibility service in the given process.
+   */
+  async enable() {
+    if (this.enabled || !this.canBeEnabled) {
+      return;
+    }
+
+    let initPromise = this.once("init");
+
+    if (DebuggerServer.isInChildProcess) {
+      this.messageManager.sendAsyncMessage(this._msgName, { action: "enable" });
+    } else {
+      // This executes accessibility service lazy getter and adds accessible
+      // events observer.
+      this.walker.a11yService;
+    }
+
+    await initPromise;
+  },
+
+  /**
+   * Disable acessibility service in the given process.
+   */
+  async disable() {
+    if (!this.enabled || !this.canBeDisabled) {
+      return;
+    }
+
+    this.disabling = true;
+    let shutdownPromise = this.once("shutdown");
+    if (DebuggerServer.isInChildProcess) {
+      this.messageManager.sendAsyncMessage(this._msgName, { action: "disable" });
+    } else {
+      // Set PREF_ACCESSIBILITY_FORCE_DISABLED to 1 to force disable
+      // accessibility service. This is the only way to guarantee an immediate
+      // accessibility service shutdown in all processes. This also prevents
+      // accessibility service from starting up in the future.
+      //
+      // TODO: Introduce a shutdown method that is exposed via XPCOM on
+      // accessibility service.
+      Services.prefs.setIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED, 1);
+      // Set PREF_ACCESSIBILITY_FORCE_DISABLED back to previous default or user
+      // set value. This will not start accessibility service until the user
+      // activates it again. It simply ensures that accessibility service can
+      // start again (when value is below 1).
+      Services.prefs.setIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED, this.userPref);
+    }
+
+    await shutdownPromise;
+    delete this.disabling;
+  },
+
+  /**
+   * Observe Accessibility service init and shutdown events. It relays these
+   * events to AccessibilityFront iff the event is fired for the a11y service
+   * that lives in the same process.
+   *
+   * @param  {null} subject
+   *         Not used.
+   * @param  {String} topic
+   *         Name of the a11y service event: "a11y-init-or-shutdown".
+   * @param  {String} data
+   *         "0" corresponds to shutdown and "1" to init.
+   */
+  observe(subject, topic, data) {
+    if (topic === "a11y-init-or-shutdown") {
+      // This event is fired when accessibility service is initialized or shut
+      // down. "init" and "shutdown" events are only relayed when the enabled
+      // state matches the event (e.g. the event came from the same process as
+      // the actor).
+      const enabled = data === "1";
+      if (enabled && this.enabled) {
+        events.emit(this, "init");
+      } else if (!enabled && !this.enabled) {
+        if (this.walker) {
+          this.walker.reset();
+        }
+
+        events.emit(this, "shutdown");
+      }
+    } else if (topic === "a11y-consumers-changed") {
+      // This event is fired when accessibility service consumers change. There
+      // are 3 possible consumers of a11y service: XPCOM, PlatformAPI (e.g.
+      // screen readers) and MainProcess. PlatformAPI consumer can only be set
+      // in parent process, and MainProcess consumer can only be set in child
+      // process. We only care about PlatformAPI consumer changes because when
+      // set, we can no longer disable accessibility service.
+      let { PlatformAPI } = JSON.parse(data);
+      events.emit(this, "can-be-disabled-change", !PlatformAPI);
+    } else if (!this.disabling && topic === "nsPref:changed" &&
+               data === PREF_ACCESSIBILITY_FORCE_DISABLED) {
+      // PREF_ACCESSIBILITY_FORCE_DISABLED preference change event. When set to
+      // >=1, it means that the user wants to disable accessibility service and
+      // prevent it from starting in the future. Note: we also check
+      // this.disabling state when handling this pref change because this is how
+      // we disable the accessibility inspector itself.
+      events.emit(this, "can-be-enabled-change", this.canBeEnabled);
+    }
+  },
+
+  /**
+   * Get or create AccessibilityWalker actor, similar to WalkerActor.
+   *
+   * @return {Object}
+   *         AccessibleWalkerActor for the current tab.
+   */
   getWalker() {
     if (!this.walker) {
       this.walker = new AccessibleWalkerActor(this.conn, this.tabActor);
     }
     return this.walker;
   },
 
-  destroy() {
+  /**
+   * Destroy accessibility service actor. This method also shutsdown
+   * accessibility service if possible.
+   */
+  async destroy() {
+    if (this.destroyed) {
+      await this.destroyed;
+      return;
+    }
+
+    let resolver;
+    this.destroyed = new Promise(resolve => {
+      resolver = resolve;
+    });
+
+    if (this.walker) {
+      this.walker.reset();
+    }
+
+    Services.obs.removeObserver(this, "a11y-init-or-shutdown");
+    if (DebuggerServer.isInChildProcess) {
+      this.messageManager.removeMessageListener(`${this._msgName}:event`,
+                                                this.onMessage);
+    } else {
+      Services.obs.removeObserver(this, "a11y-consumers-changed");
+      Services.prefs.removeObserver(PREF_ACCESSIBILITY_FORCE_DISABLED, this);
+    }
+
     Actor.prototype.destroy.call(this);
-    this.walker.destroy();
     this.walker = null;
     this.tabActor = null;
+    resolver();
   }
 });
 
 exports.AccessibleActor = AccessibleActor;
 exports.AccessibleWalkerActor = AccessibleWalkerActor;
 exports.AccessibilityActor = AccessibilityActor;
--- a/devtools/server/actors/moz.build
+++ b/devtools/server/actors/moz.build
@@ -8,16 +8,17 @@ DIRS += [
     'emulation',
     'highlighters',
     'inspector',
     'utils',
     'webconsole',
 ]
 
 DevToolsModules(
+    'accessibility-parent.js',
     'accessibility.js',
     'actor-registry.js',
     'addon.js',
     'addons.js',
     'animation.js',
     'breakpoint.js',
     'call-watcher.js',
     'canvas.js',
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {Ci} = require("chrome");
+const Services = require("Services");
 const protocol = require("devtools/shared/protocol");
 const {LongStringActor} = require("devtools/server/actors/string");
 const InspectorUtils = require("InspectorUtils");
 
 // This will also add the "stylesheet" actor type for protocol.js to recognize
 
 const {pageStyleSpec, styleRuleSpec, ELEMENT_STYLE} = require("devtools/shared/specs/styles");
 
@@ -24,16 +25,19 @@ loader.lazyRequireGetter(this, "parseNam
 loader.lazyRequireGetter(this, "UPDATE_PRESERVING_RULES",
   "devtools/server/actors/stylesheets", true);
 loader.lazyRequireGetter(this, "UPDATE_GENERAL",
   "devtools/server/actors/stylesheets", true);
 
 loader.lazyGetter(this, "PSEUDO_ELEMENTS", () => {
   return InspectorUtils.getCSSPseudoElementNames();
 });
+loader.lazyGetter(this, "FONT_VARIATIONS_ENABLED", () => {
+  return Services.prefs.getBoolPref("layout.css.font-variations.enabled");
+});
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 const FONT_PREVIEW_TEXT = "Abc";
 const FONT_PREVIEW_FONT_SIZE = 40;
 const FONT_PREVIEW_FILLSTYLE = "black";
 const NORMAL_FONT_WEIGHT = 400;
 const BOLD_FONT_WEIGHT = 700;
 // Offset (in px) to avoid cutting off text edges of italic fonts.
@@ -110,17 +114,20 @@ var PageStyleActor = protocol.ActorClass
       actor: this.actorID,
       traits: {
         // Whether the actor has had bug 1103993 fixed, which means that the
         // getApplied method calls cssLogic.highlight(node) to recreate the
         // style cache. Clients requesting getApplied from actors that have not
         // been fixed must make sure cssLogic.highlight(node) was called before.
         getAppliedCreatesStyleCache: true,
         // Whether addNewRule accepts the editAuthored argument.
-        authoredStyles: true
+        authoredStyles: true,
+        // Whether getAllUsedFontFaces/getUsedFontFaces accepts the includeVariations
+        // argument.
+        fontVariations: FONT_VARIATIONS_ENABLED,
       }
     };
   },
 
   /**
    * Called when a style sheet is updated.
    */
   _styleApplied: function (kind, styleSheet) {
@@ -315,16 +322,22 @@ var PageStyleActor = protocol.ActorClass
         };
         let { dataURL, size } = getFontPreviewData(font.CSSFamilyName,
                                                    contentDocument, opts);
         fontFace.preview = {
           data: LongStringActor(this.conn, dataURL),
           size: size
         };
       }
+
+      if (options.includeVariations && FONT_VARIATIONS_ENABLED) {
+        fontFace.variationAxes = font.getVariationAxes();
+        fontFace.variationInstances = font.getVariationInstances();
+      }
+
       fontsArray.push(fontFace);
     }
 
     // @font-face fonts at the top, then alphabetically, then by weight
     fontsArray.sort(function (a, b) {
       return a.weight > b.weight ? 1 : -1;
     });
     fontsArray.sort(function (a, b) {
--- a/devtools/server/tests/browser/browser.ini
+++ b/devtools/server/tests/browser/browser.ini
@@ -22,18 +22,18 @@ support-files =
   storage-secured-iframe.html
   stylesheets-nested-iframes.html
   timeline-iframe-child.html
   timeline-iframe-parent.html
   storage-helpers.js
   !/devtools/server/tests/mochitest/hello-actor.js
   !/devtools/client/framework/test/shared-head.js
 
+[browser_accessibility_node.js]
 [browser_accessibility_node_events.js]
-[browser_accessibility_node.js]
 [browser_accessibility_simple.js]
 [browser_accessibility_walker.js]
 [browser_animation_emitMutations.js]
 [browser_animation_getFrames.js]
 [browser_animation_getProperties.js]
 [browser_animation_getMultipleStates.js]
 [browser_animation_getPlayers.js]
 [browser_animation_getStateAfterFinished.js]
--- a/devtools/server/tests/browser/browser_accessibility_node.js
+++ b/devtools/server/tests/browser/browser_accessibility_node.js
@@ -5,70 +5,51 @@
 "use strict";
 
 // Checks for the AccessibleActor
 
 add_task(async function () {
   let {client, walker, accessibility} =
     await initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility.html");
 
-  let a11yWalker = await accessibility.getWalker(walker);
+  let a11yWalker = await accessibility.getWalker();
+  await accessibility.enable();
   let buttonNode = await walker.querySelector(walker.rootNode, "#button");
   let accessibleFront = await a11yWalker.getAccessibleFor(buttonNode);
 
   checkA11yFront(accessibleFront, {
     name: "Accessible Button",
     role: "pushbutton",
     value: "",
     description: "Accessibility Test",
     help: "",
     keyboardShortcut: "",
     childCount: 1,
-    domNodeType: 1
+    domNodeType: 1,
+    indexInParent: 1,
+    states: ["focusable", "selectable text", "opaque", "enabled", "sensitive"],
+    actions: [ "Press" ],
+    attributes: {
+      "margin-top": "0px",
+      display: "inline-block",
+      "text-align": "center",
+      "text-indent": "0px",
+      "margin-left": "0px",
+      tag: "button",
+      "margin-right": "0px",
+      id: "button",
+      "margin-bottom": "0px"
+    }
   });
 
-  info("Actions");
-  let actions = await accessibleFront.getActions();
-  is(actions.length, 1, "Accessible Front has correct number of actions");
-  is(actions[0], "Press", "Accessible Front default action is correct");
-
-  info("Index in parent");
-  let index = await accessibleFront.getIndexInParent();
-  is(index, 1, "Accessible Front has correct index in parent");
-
-  info("State");
-  let state = await accessibleFront.getState();
-  SimpleTest.isDeeply(state,
-    ["focusable", "selectable text", "opaque", "enabled", "sensitive"],
-    "Accessible Front has correct states");
-
-  info("Attributes");
-  let attributes = await accessibleFront.getAttributes();
-  SimpleTest.isDeeply(attributes, {
-    "margin-top": "0px",
-    display: "inline-block",
-    "text-align": "center",
-    "text-indent": "0px",
-    "margin-left": "0px",
-    tag: "button",
-    "margin-right": "0px",
-    id: "button",
-    "margin-bottom": "0px"
-  }, "Accessible Front has correct attributes");
-
   info("Children");
   let children = await accessibleFront.children();
   is(children.length, 1, "Accessible Front has correct number of children");
   checkA11yFront(children[0], {
     name: "Accessible Button",
     role: "text leaf"
   });
 
-  info("DOM Node");
-  let node = await accessibleFront.getDOMNode(walker);
-  is(node, buttonNode, "Accessible Front has correct DOM node");
-
-  let a11yShutdown = waitForA11yShutdown();
+  await accessibility.disable();
+  await waitForA11yShutdown();
   await client.close();
-  forceCollections();
-  await a11yShutdown;
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_accessibility_node_events.js
+++ b/devtools/server/tests/browser/browser_accessibility_node_events.js
@@ -5,33 +5,49 @@
 "use strict";
 
 // Checks for the AccessibleActor events
 
 add_task(async function () {
   let {client, walker, accessibility} =
     await initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility.html");
 
-  let a11yWalker = await accessibility.getWalker(walker);
-  let a11yDoc = await a11yWalker.getDocument();
+  let a11yWalker = await accessibility.getWalker();
+  await accessibility.enable();
+  let rootNode = await walker.getRootNode();
+  let a11yDoc = await a11yWalker.getAccessibleFor(rootNode);
   let buttonNode = await walker.querySelector(walker.rootNode, "#button");
   let accessibleFront = await a11yWalker.getAccessibleFor(buttonNode);
   let sliderNode = await walker.querySelector(walker.rootNode, "#slider");
   let accessibleSliderFront = await a11yWalker.getAccessibleFor(sliderNode);
   let browser = gBrowser.selectedBrowser;
 
   checkA11yFront(accessibleFront, {
     name: "Accessible Button",
     role: "pushbutton",
     value: "",
     description: "Accessibility Test",
     help: "",
     keyboardShortcut: "",
     childCount: 1,
-    domNodeType: 1
+    domNodeType: 1,
+    indexInParent: 1,
+    states: ["focusable", "selectable text", "opaque", "enabled", "sensitive"],
+    actions: [ "Press" ],
+    attributes: {
+      "margin-top": "0px",
+      display: "inline-block",
+      "text-align": "center",
+      "text-indent": "0px",
+      "margin-left": "0px",
+      tag: "button",
+      "margin-right": "0px",
+      id: "button",
+      "margin-bottom": "0px"
+    }
   });
 
   info("Name change event");
   await emitA11yEvent(accessibleFront, "name-change",
     (name, parent) => {
       checkA11yFront(accessibleFront, { name: "Renamed" });
       checkA11yFront(parent, { }, a11yDoc);
     }, () => ContentTask.spawn(browser, null, () =>
@@ -40,54 +56,73 @@ add_task(async function () {
 
   info("Description change event");
   await emitA11yEvent(accessibleFront, "description-change",
     () => checkA11yFront(accessibleFront, { description: "" }),
     () => ContentTask.spawn(browser, null, () =>
       content.document.getElementById("button").removeAttribute("aria-describedby")));
 
   info("State change event");
-  let states = await accessibleFront.getState();
   let expectedStates = ["unavailable", "selectable text", "opaque"];
-  SimpleTest.isDeeply(states, ["focusable", "selectable text", "opaque",
-                               "enabled", "sensitive"], "States are correct");
-  await emitA11yEvent(accessibleFront, "state-change",
-    newStates => SimpleTest.isDeeply(newStates, expectedStates,
-                                     "States are updated"),
-    () => ContentTask.spawn(browser, null, () =>
+  await emitA11yEvent(accessibleFront, "states-change",
+    newStates => {
+      checkA11yFront(accessibleFront, { states: expectedStates });
+      SimpleTest.isDeeply(newStates, expectedStates, "States are updated");
+    }, () => ContentTask.spawn(browser, null, () =>
       content.document.getElementById("button").setAttribute("disabled", true)));
-  states = await accessibleFront.getState();
-  SimpleTest.isDeeply(states, expectedStates, "States are updated");
 
   info("Attributes change event");
-  let attrs = await accessibleFront.getAttributes();
-  ok(!attrs.live, "Attribute is not present");
   await emitA11yEvent(accessibleFront, "attributes-change",
-    newAttrs => is(newAttrs.live, "polite", "Attributes are updated"),
-    () => ContentTask.spawn(browser, null, () =>
+    newAttrs => {
+      checkA11yFront(accessibleFront, { attributes: {
+        "container-live": "polite",
+        display: "inline-block",
+        "event-from-input": "false",
+        "explicit-name": "true",
+        id: "button",
+        live: "polite",
+        "margin-bottom": "0px",
+        "margin-left": "0px",
+        "margin-right": "0px",
+        "margin-top": "0px",
+        tag: "button",
+        "text-align": "center",
+        "text-indent": "0px"
+      }});
+      is(newAttrs.live, "polite", "Attributes are updated");
+    }, () => ContentTask.spawn(browser, null, () =>
       content.document.getElementById("button").setAttribute("aria-live", "polite")));
-  attrs = await accessibleFront.getAttributes();
-  is(attrs.live, "polite", "Attributes are updated");
 
   info("Value change event");
   checkA11yFront(accessibleSliderFront, { value: "5" });
   await emitA11yEvent(accessibleSliderFront, "value-change",
     () => checkA11yFront(accessibleSliderFront, { value: "6" }),
     () => ContentTask.spawn(browser, null, () =>
       content.document.getElementById("slider").setAttribute("aria-valuenow", "6")));
 
   info("Reorder event");
   is(accessibleSliderFront.childCount, 1, "Slider has only 1 child");
+  let [firstChild, ] = await accessibleSliderFront.children();
+  is(firstChild.indexInParent, 0, "Slider's first child has correct index in parent");
   await emitA11yEvent(accessibleSliderFront, "reorder",
-    childCount => is(childCount, 2, "Child count is updated"),
-    () => ContentTask.spawn(browser, null, () => {
-      let button = content.document.createElement("button");
+    childCount => {
+      is(childCount, 2, "Child count is updated");
+      is(accessibleSliderFront.childCount, 2, "Child count is updated");
+      is(firstChild.indexInParent, 1,
+        "Slider's first child has an updated index in parent");
+    }, () => ContentTask.spawn(browser, null, () => {
+      let doc = content.document;
+      let slider = doc.getElementById("slider");
+      let button = doc.createElement("button");
       button.innerText = "Slider button";
-      content.document.getElementById("slider").appendChild(button);
+      content.document.getElementById("slider").insertBefore(button, slider.firstChild);
     }));
-  is(accessibleSliderFront.childCount, 2, "Child count is updated");
 
-  let a11yShutdown = waitForA11yShutdown();
+  await emitA11yEvent(firstChild, "index-in-parent-change", indexInParent =>
+    is(indexInParent, 0, "Slider's first child has an updated index in parent"), () =>
+    ContentTask.spawn(browser, null, () =>
+      content.document.getElementById("slider").firstChild.remove()));
+
+  await accessibility.disable();
+  await waitForA11yShutdown();
   await client.close();
-  forceCollections();
-  await a11yShutdown;
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_accessibility_simple.js
+++ b/devtools/server/tests/browser/browser_accessibility_simple.js
@@ -1,21 +1,68 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+const PREF_ACCESSIBILITY_FORCE_DISABLED = "accessibility.force_disabled";
+
+function checkAccessibilityState(accessibility, expected) {
+  let { enabled, canBeDisabled, canBeEnabled } = accessibility;
+  is(enabled, expected.enabled, "Enabled state is correct.");
+  is(canBeDisabled, expected.canBeDisabled, "canBeDisabled state is correct.");
+  is(canBeEnabled, expected.canBeEnabled, "canBeEnabled state is correct.");
+}
+
 // Simple checks for the AccessibilityActor and AccessibleWalkerActor
 
 add_task(async function () {
-  let {client, accessibility} = await initAccessibilityFrontForUrl(
+  let { walker: domWalker, client, accessibility} = await initAccessibilityFrontForUrl(
     "data:text/html;charset=utf-8,<title>test</title><div></div>");
 
   ok(accessibility, "The AccessibilityFront was created");
   ok(accessibility.getWalker, "The getWalker method exists");
 
   let a11yWalker = await accessibility.getWalker();
   ok(a11yWalker, "The AccessibleWalkerFront was returned");
 
+  checkAccessibilityState(accessibility,
+    { enabled: false, canBeDisabled: true, canBeEnabled: true });
+
+  info("Force disable accessibility service: updates canBeEnabled flag");
+  let onEvent = accessibility.once("can-be-enabled-change");
+  Services.prefs.setIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED, 1);
+  await onEvent;
+  checkAccessibilityState(accessibility,
+    { enabled: false, canBeDisabled: true, canBeEnabled: false });
+
+  info("Clear force disable accessibility service: updates canBeEnabled flag");
+  onEvent = accessibility.once("can-be-enabled-change");
+  Services.prefs.clearUserPref(PREF_ACCESSIBILITY_FORCE_DISABLED);
+  await onEvent;
+  checkAccessibilityState(accessibility,
+    { enabled: false, canBeDisabled: true, canBeEnabled: true });
+
+  info("Initialize accessibility service");
+  let initEvent = accessibility.once("init");
+  await accessibility.enable();
+  await waitForA11yInit();
+  await initEvent;
+  checkAccessibilityState(accessibility,
+    { enabled: true, canBeDisabled: true, canBeEnabled: true });
+
+  a11yWalker = await accessibility.getWalker();
+  let rootNode = await domWalker.getRootNode();
+  let a11yDoc = await a11yWalker.getAccessibleFor(rootNode);
+  ok(a11yDoc, "Accessible document actor is created");
+
+  info("Shutdown accessibility service");
+  let shutdownEvent = accessibility.once("shutdown");
+  await accessibility.disable();
+  await waitForA11yShutdown();
+  await shutdownEvent;
+  checkAccessibilityState(accessibility,
+    { enabled: false, canBeDisabled: true, canBeEnabled: true });
+
   await client.close();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/browser_accessibility_walker.js
+++ b/devtools/server/tests/browser/browser_accessibility_walker.js
@@ -5,36 +5,47 @@
 "use strict";
 
 // Checks for the AccessibleWalkerActor
 
 add_task(async function () {
   let {client, walker, accessibility} =
     await initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility.html");
 
-  let a11yWalker = await accessibility.getWalker(walker);
+  let a11yWalker = await accessibility.getWalker();
   ok(a11yWalker, "The AccessibleWalkerFront was returned");
 
-  let a11yDoc = await a11yWalker.getDocument();
+  await accessibility.enable();
+  let rootNode = await walker.getRootNode();
+  let a11yDoc = await a11yWalker.getAccessibleFor(rootNode);
   ok(a11yDoc, "The AccessibleFront for root doc is created");
 
   let children = await a11yWalker.children();
   is(children.length, 1,
     "AccessibleWalker only has 1 child - root doc accessible");
   is(a11yDoc, children[0],
     "Root accessible must be AccessibleWalker's only child");
 
   let buttonNode = await walker.querySelector(walker.rootNode, "#button");
   let accessibleFront = await a11yWalker.getAccessibleFor(buttonNode);
 
   checkA11yFront(accessibleFront, {
     name: "Accessible Button",
     role: "pushbutton"
   });
 
+  let ancestry = await a11yWalker.getAncestry(accessibleFront);
+  is(ancestry.length, 1, "Button is a direct child of a root document.");
+  is(ancestry[0].accessible, a11yDoc,
+    "Button's only ancestor is a root document");
+  is(ancestry[0].children.length, 3,
+    "Root doc should have correct number of children");
+  ok(ancestry[0].children.includes(accessibleFront),
+    "Button accessible front is in root doc's children");
+
   let browser = gBrowser.selectedBrowser;
 
   // Ensure name-change event is emitted by walker when cached accessible's name
   // gets updated (via DOM manipularion).
   await emitA11yEvent(a11yWalker, "name-change",
     (front, parent) => {
       checkA11yFront(front, { name: "Renamed" }, accessibleFront);
       checkA11yFront(parent, { }, a11yDoc);
@@ -62,14 +73,60 @@ add_task(async function () {
 
   // Ensure destory event is emitted by walker when cached accessible's raw
   // accessible gets destroyed.
   await emitA11yEvent(a11yWalker, "accessible-destroy",
     destroyedFront => checkA11yFront(destroyedFront, { }, accessibleFront),
     () => ContentTask.spawn(browser, null, () =>
       content.document.getElementById("button").remove()));
 
-  let a11yShutdown = waitForA11yShutdown();
+  let shown = await a11yWalker.highlightAccessible(docChildren[0]);
+  ok(shown, "AccessibleHighlighter highlighted the node");
+
+  shown = await a11yWalker.highlightAccessible(a11yDoc);
+  ok(!shown, "AccessibleHighlighter does not highlight an accessible with no bounds");
+  await a11yWalker.unhighlight();
+
+  info("Checking AccessibleWalker picker functionality");
+  ok(a11yWalker.pick, "AccessibleWalker pick method exists");
+  ok(a11yWalker.pickAndFocus, "AccessibleWalker pickAndFocus method exists");
+  ok(a11yWalker.cancelPick, "AccessibleWalker cancelPick method exists");
+
+  let onPickerEvent = a11yWalker.once("picker-accessible-hovered");
+  await a11yWalker.pick();
+  await BrowserTestUtils.synthesizeMouseAtCenter("#h1", { type: "mousemove" }, browser);
+  let acc = await onPickerEvent;
+  checkA11yFront(acc, { name: "Accessibility Test" }, docChildren[0]);
+
+  onPickerEvent = a11yWalker.once("picker-accessible-previewed");
+  await BrowserTestUtils.synthesizeMouseAtCenter("#h1", { shiftKey: true }, browser);
+  acc = await onPickerEvent;
+  checkA11yFront(acc, { name: "Accessibility Test" }, docChildren[0]);
+
+  onPickerEvent = a11yWalker.once("picker-accessible-canceled");
+  await BrowserTestUtils.synthesizeKey("VK_ESCAPE", { type: "keydown" }, browser);
+  await onPickerEvent;
+
+  onPickerEvent = a11yWalker.once("picker-accessible-hovered");
+  await a11yWalker.pick();
+  await BrowserTestUtils.synthesizeMouseAtCenter("#h1", { type: "mousemove" }, browser);
+  await onPickerEvent;
+
+  onPickerEvent = a11yWalker.once("picker-accessible-picked");
+  await BrowserTestUtils.synthesizeMouseAtCenter("#h1", { }, browser);
+  acc = await onPickerEvent;
+  checkA11yFront(acc, { name: "Accessibility Test" }, docChildren[0]);
+
+  await a11yWalker.cancelPick();
+
+  info("Checking document-ready event fired by walker when top level accessible " +
+       "document is recreated.");
+  let reloaded = BrowserTestUtils.browserLoaded(browser);
+  let documentReady = a11yWalker.once("document-ready");
+  browser.reload();
+  await reloaded;
+  await documentReady;
+
+  await accessibility.disable();
+  await waitForA11yShutdown();
   await client.close();
-  forceCollections();
-  await a11yShutdown;
   gBrowser.removeCurrentTab();
 });
--- a/devtools/server/tests/browser/head.js
+++ b/devtools/server/tests/browser/head.js
@@ -84,16 +84,18 @@ async function initAccessibilityFrontFor
 
   initDebuggerServer();
   let client = new DebuggerClient(DebuggerServer.connectPipe());
   let form = await connectDebuggerClient(client);
   let inspector = InspectorFront(client, form);
   let walker = await inspector.getWalker();
   let accessibility = AccessibilityFront(client, form);
 
+  await accessibility.bootstrap();
+
   return {inspector, walker, accessibility, client};
 }
 
 function initDebuggerServer() {
   try {
     // Sometimes debugger server does not get destroyed correctly by previous
     // tests.
     DebuggerServer.destroy();
@@ -303,29 +305,52 @@ async function emitA11yEvent(emitter, na
 function checkA11yFront(front, expected, expectedFront) {
   ok(front, "The accessibility front is created");
 
   if (expectedFront) {
     is(front, expectedFront, "Matching accessibility front");
   }
 
   for (let key in expected) {
-    is(front[key], expected[key], `accessibility front has correct ${key}`);
+    if (["actions", "states", "attributes"].includes(key)) {
+      SimpleTest.isDeeply(front[key], expected[key],
+        `Accessible Front has correct ${key}`);
+    } else {
+      is(front[key], expected[key], `accessibility front has correct ${key}`);
+    }
   }
 }
 
+function getA11yInitOrShutdownPromise() {
+  return new Promise(resolve => {
+    let observe = (subject, topic, data) => {
+      Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
+      resolve(data);
+    };
+    Services.obs.addObserver(observe, "a11y-init-or-shutdown");
+  });
+}
+
 /**
  * Wait for accessibility service to shut down. We consider it shut down when
  * an "a11y-init-or-shutdown" event is received with a value of "0".
  */
 async function waitForA11yShutdown() {
-  await ContentTask.spawn(gBrowser.selectedBrowser, {}, () =>
-    new Promise(resolve => {
-      let observe = (subject, topic, data) => {
-        Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
+  if (!Services.appinfo.accessibilityEnabled) {
+    return;
+  }
+
+  await getA11yInitOrShutdownPromise().then(data =>
+    data === "0" ? Promise.resolve() : Promise.reject());
+}
 
-        if (data === "0") {
-          resolve();
-        }
-      };
-      Services.obs.addObserver(observe, "a11y-init-or-shutdown");
-    }));
+/**
+ * Wait for accessibility service to initialize. We consider it initialized when
+ * an "a11y-init-or-shutdown" event is received with a value of "1".
+ */
+async function waitForA11yInit() {
+  if (Services.appinfo.accessibilityEnabled) {
+    return;
+  }
+
+  await getA11yInitOrShutdownPromise().then(data =>
+    data === "1" ? Promise.resolve() : Promise.reject());
 }
--- a/devtools/shared/fronts/accessibility.js
+++ b/devtools/shared/fronts/accessibility.js
@@ -1,86 +1,99 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const {
+  custom,
   Front,
   FrontClassWithSpec,
-  preEvent,
-  types
+  preEvent
 } = require("devtools/shared/protocol.js");
 const {
   accessibleSpec,
   accessibleWalkerSpec,
   accessibilitySpec
 } = require("devtools/shared/specs/accessibility");
-
 const events = require("devtools/shared/event-emitter");
-const ACCESSIBLE_PROPERTIES = [
-  "role",
-  "name",
-  "value",
-  "description",
-  "help",
-  "keyboardShortcut",
-  "childCount",
-  "domNodeType"
-];
 
 const AccessibleFront = FrontClassWithSpec(accessibleSpec, {
   initialize(client, form) {
     Front.prototype.initialize.call(this, client, form);
-
-    // Define getters for accesible properties that are received from the actor.
-    // Note: we would like accessible properties to be iterable for a11y
-    // clients.
-    for (let key of ACCESSIBLE_PROPERTIES) {
-      Object.defineProperty(this, key, {
-        get() {
-          return this._form[key];
-        },
-        enumerable: true
-      });
-    }
   },
 
   marshallPool() {
-    return this.walker;
+    return this.parent();
+  },
+
+  get role() {
+    return this._form.role;
+  },
+
+  get name() {
+    return this._form.name;
+  },
+
+  get value() {
+    return this._form.value;
+  },
+
+  get description() {
+    return this._form.description;
+  },
+
+  get help() {
+    return this._form.help;
+  },
+
+  get keyboardShortcut() {
+    return this._form.keyboardShortcut;
+  },
+
+  get childCount() {
+    return this._form.childCount;
+  },
+
+  get domNodeType() {
+    return this._form.domNodeType;
+  },
+
+  get indexInParent() {
+    return this._form.indexInParent;
+  },
+
+  get states() {
+    return this._form.states;
+  },
+
+  get actions() {
+    return this._form.actions;
+  },
+
+  get attributes() {
+    return this._form.attributes;
   },
 
   form(form, detail) {
     if (detail === "actorid") {
       this.actorID = form;
       return;
     }
 
     this.actorID = form.actor;
     this._form = form;
-    DevToolsUtils.defineLazyGetter(this, "walker", () =>
-      types.getType("accessiblewalker").read(this._form.walker, this));
   },
 
-  /**
-   * Get a dom node front from accessible actor's raw accessible object's
-   * DONNode property.
-   */
-  getDOMNode(domWalker) {
-    return domWalker.getNodeFromActor(this.actorID,
-                                      ["rawAccessible", "DOMNode"]);
-  },
-
-  nameChange: preEvent("name-change", function (name, parent) {
+  nameChange: preEvent("name-change", function (name, parent, walker) {
     this._form.name = name;
     // Name change event affects the tree rendering, we fire this event on
     // accessibility walker as the point of interaction for UI.
-    if (this.walker) {
-      events.emit(this.walker, "name-change", this, parent);
+    if (walker) {
+      events.emit(walker, "name-change", this, parent);
     }
   }),
 
   valueChange: preEvent("value-change", function (value) {
     this._form.value = value;
   }),
 
   descriptionChange: preEvent("description-change", function (description) {
@@ -90,47 +103,99 @@ const AccessibleFront = FrontClassWithSp
   helpChange: preEvent("help-change", function (help) {
     this._form.help = help;
   }),
 
   shortcutChange: preEvent("shortcut-change", function (keyboardShortcut) {
     this._form.keyboardShortcut = keyboardShortcut;
   }),
 
-  reorder: preEvent("reorder", function (childCount) {
+  reorder: preEvent("reorder", function (childCount, walker) {
     this._form.childCount = childCount;
     // Reorder event affects the tree rendering, we fire this event on
     // accessibility walker as the point of interaction for UI.
-    if (this.walker) {
-      events.emit(this.walker, "reorder", this);
+    if (walker) {
+      events.emit(walker, "reorder", this);
+    }
+  }),
+
+  textChange: preEvent("text-change", function (walker) {
+    // Text event affects the tree rendering, we fire this event on
+    // accessibility walker as the point of interaction for UI.
+    if (walker) {
+      events.emit(walker, "text-change", this);
     }
   }),
 
-  textChange: preEvent("text-change", function () {
-    // Text event affects the tree rendering, we fire this event on
-    // accessibility walker as the point of interaction for UI.
-    if (this.walker) {
-      events.emit(this.walker, "text-change", this);
-    }
+  indexInParentChange: preEvent("index-in-parent-change", function (indexInParent) {
+    this._form.indexInParent = indexInParent;
+  }),
+
+  statesChange: preEvent("states-change", function (states) {
+    this._form.states = states;
+  }),
+
+  actionsChange: preEvent("actions-change", function (actions) {
+    this._form.actions = actions;
+  }),
+
+  attributesChange: preEvent("attributes-change", function (attributes) {
+    this._form.attributes = attributes;
   })
 });
 
 const AccessibleWalkerFront = FrontClassWithSpec(accessibleWalkerSpec, {
   accessibleDestroy: preEvent("accessible-destroy", function (accessible) {
     accessible.destroy();
   }),
 
   form(json) {
     this.actorID = json.actor;
-  }
+  },
+
+  pick: custom(function (doFocus) {
+    if (doFocus) {
+      return this.pickAndFocus();
+    }
+
+    return this._pick();
+  }, {
+    impl: "_pick"
+  })
 });
 
 const AccessibilityFront = FrontClassWithSpec(accessibilitySpec, {
   initialize(client, form) {
     Front.prototype.initialize.call(this, client, form);
     this.actorID = form.accessibilityActor;
     this.manage(this);
-  }
+  },
+
+  bootstrap: custom(function () {
+    return this._bootstrap().then(state => {
+      this.enabled = state.enabled;
+      this.canBeEnabled = state.canBeEnabled;
+      this.canBeDisabled = state.canBeDisabled;
+    });
+  }, {
+    impl: "_bootstrap"
+  }),
+
+  init: preEvent("init", function () {
+    this.enabled = true;
+  }),
+
+  shutdown: preEvent("shutdown", function () {
+    this.enabled = false;
+  }),
+
+  canBeEnabled: preEvent("can-be-enabled-change", function (canBeEnabled) {
+    this.canBeEnabled = canBeEnabled;
+  }),
+
+  canBeDisabled: preEvent("can-be-disabled-change", function (canBeDisabled) {
+    this.canBeDisabled = canBeDisabled;
+  })
 });
 
 exports.AccessibleFront = AccessibleFront;
 exports.AccessibleWalkerFront = AccessibleWalkerFront;
 exports.AccessibilityFront = AccessibilityFront;
--- a/devtools/shared/fronts/styles.js
+++ b/devtools/shared/fronts/styles.js
@@ -42,16 +42,20 @@ const PageStyleFront = FrontClassWithSpe
   get walker() {
     return this.inspector.walker;
   },
 
   get supportsAuthoredStyles() {
     return this._form.traits && this._form.traits.authoredStyles;
   },
 
+  get supportsFontVariations() {
+    return this._form.traits && this._form.traits.fontVariations;
+  },
+
   getMatchedSelectors: custom(function (node, property, options) {
     return this._getMatchedSelectors(node, property, options).then(ret => {
       return ret.matched;
     });
   }, {
     impl: "_getMatchedSelectors"
   }),
 
--- a/devtools/shared/specs/accessibility.js
+++ b/devtools/shared/specs/accessibility.js
@@ -4,136 +4,194 @@
 
 "use strict";
 
 const protocol = require("devtools/shared/protocol");
 const { Arg, generateActorSpec, RetVal, types } = protocol;
 
 types.addActorType("accessible");
 
+/**
+ * Accessible with children listed in the ancestry structure calculated by the
+ * walker.
+ */
+types.addDictType("accessibleWithChildren", {
+  // Accessible
+  accessible: "accessible",
+  // Accessible's children
+  children: "array:accessible"
+});
+
 const accessibleSpec = generateActorSpec({
   typeName: "accessible",
 
   events: {
     "actions-change": {
       type: "actionsChange",
       actions: Arg(0, "array:string")
     },
     "name-change": {
       type: "nameChange",
       name: Arg(0, "string"),
-      parent: Arg(1, "nullable:accessible")
+      parent: Arg(1, "nullable:accessible"),
+      walker: Arg(2, "nullable:accessiblewalker")
     },
     "value-change": {
       type: "valueChange",
       value: Arg(0, "string")
     },
     "description-change": {
       type: "descriptionChange",
       description: Arg(0, "string")
     },
-    "state-change": {
-      type: "stateChange",
+    "states-change": {
+      type: "statesChange",
       states: Arg(0, "array:string")
     },
     "attributes-change": {
       type: "attributesChange",
-      states: Arg(0, "json")
+      attributes: Arg(0, "json")
     },
     "help-change": {
       type: "helpChange",
       help: Arg(0, "string")
     },
     "shortcut-change": {
       type: "shortcutChange",
       shortcut: Arg(0, "string")
     },
     "reorder": {
       type: "reorder",
-      childCount: Arg(0, "number")
+      childCount: Arg(0, "number"),
+      walker: Arg(1, "nullable:accessiblewalker")
     },
     "text-change": {
-      type: "textChange"
+      type: "textChange",
+      walker: Arg(0, "nullable:accessiblewalker")
+    },
+    "index-in-parent-change": {
+      type: "indexInParentChange",
+      indexInParent: Arg(0, "number")
     }
   },
 
   methods: {
-    getActions: {
-      request: {},
-      response: {
-        actions: RetVal("array:string")
-      }
-    },
-    getIndexInParent: {
-      request: {},
-      response: {
-        indexInParent: RetVal("number")
-      }
-    },
-    getState: {
-      request: {},
-      response: {
-        states: RetVal("array:string")
-      }
-    },
-    getAttributes: {
-      request: {},
-      response: {
-        attributes: RetVal("json")
-      }
-    },
     children: {
       request: {},
       response: {
         children: RetVal("array:accessible")
       }
     }
   }
 });
 
 const accessibleWalkerSpec = generateActorSpec({
   typeName: "accessiblewalker",
 
   events: {
     "accessible-destroy": {
       type: "accessibleDestroy",
       accessible: Arg(0, "accessible")
+    },
+    "document-ready": {
+      type: "documentReady",
+    },
+    "picker-accessible-picked": {
+      type: "pickerAccessiblePicked",
+      accessible: Arg(0, "nullable:accessible")
+    },
+    "picker-accessible-previewed": {
+      type: "pickerAccessiblePreviewed",
+      accessible: Arg(0, "nullable:accessible")
+    },
+    "picker-accessible-hovered": {
+      type: "pickerAccessibleHovered",
+      accessible: Arg(0, "nullable:accessible")
+    },
+    "picker-accessible-canceled": {
+      type: "pickerAccessibleCanceled"
     }
   },
 
   methods: {
     children: {
       request: {},
       response: {
         children: RetVal("array:accessible")
       }
     },
-    getDocument: {
-      request: {},
-      response: {
-        document: RetVal("accessible")
-      }
-    },
     getAccessibleFor: {
       request: { node: Arg(0, "domnode") },
       response: {
         accessible: RetVal("accessible")
       }
-    }
+    },
+    getAncestry: {
+      request: { accessible: Arg(0, "accessible") },
+      response: {
+        ancestry: RetVal("array:accessibleWithChildren")
+      }
+    },
+    highlightAccessible: {
+      request: {
+        accessible: Arg(0, "accessible"),
+        options: Arg(1, "nullable:json")
+      },
+      response: {
+        value: RetVal("nullable:boolean")
+      }
+    },
+    unhighlight: {
+      request: {}
+    },
+    pick: {},
+    pickAndFocus: {},
+    cancelPick: {}
   }
 });
 
 const accessibilitySpec = generateActorSpec({
   typeName: "accessibility",
 
+  events: {
+    "init": {
+      type: "init"
+    },
+    "shutdown": {
+      type: "shutdown"
+    },
+    "can-be-disabled-change": {
+      type: "canBeDisabledChange",
+      canBeDisabled: Arg(0, "boolean")
+    },
+    "can-be-enabled-change": {
+      type: "canBeEnabledChange",
+      canBeEnabled: Arg(0, "boolean")
+    }
+  },
+
   methods: {
+    bootstrap: {
+      request: {},
+      response: {
+        state: RetVal("json")
+      }
+    },
     getWalker: {
       request: {},
       response: {
         walker: RetVal("accessiblewalker")
       }
+    },
+    enable: {
+      request: {},
+      response: {}
+    },
+    disable: {
+      request: {},
+      response: {}
     }
   }
 });
 
 exports.accessibleSpec = accessibleSpec;
 exports.accessibleWalkerSpec = accessibleWalkerSpec;
 exports.accessibilitySpec = accessibilitySpec;
--- a/devtools/shared/specs/styles.js
+++ b/devtools/shared/specs/styles.js
@@ -53,26 +53,46 @@ types.addDictType("modifiedStylesReturn"
   ruleProps: RetVal("nullable:appliedStylesReturn")
 });
 
 types.addDictType("fontpreview", {
   data: "nullable:longstring",
   size: "json"
 });
 
+types.addDictType("fontvariationaxis", {
+  tag: "string",
+  name: "string",
+  minValue: "number",
+  maxValue: "number",
+  defaultValue: "number"
+});
+
+types.addDictType("fontvariationinstancevalue", {
+  axis: "string",
+  value: "number"
+});
+
+types.addDictType("fontvariationinstance", {
+  name: "string",
+  values: "array:fontvariationinstancevalue"
+});
+
 types.addDictType("fontface", {
   name: "string",
   CSSFamilyName: "string",
   rule: "nullable:domstylerule",
   srcIndex: "number",
   URI: "string",
   format: "string",
   preview: "nullable:fontpreview",
   localName: "string",
-  metadata: "string"
+  metadata: "string",
+  variationAxes: "array:fontvariationaxis",
+  variationInstances: "array:fontvariationinstance"
 });
 
 const pageStyleSpec = generateActorSpec({
   typeName: "pagestyle",
 
   events: {
     "stylesheet-updated": {
       type: "styleSheetUpdated",
@@ -90,28 +110,30 @@ const pageStyleSpec = generateActorSpec(
       },
       response: {
         computed: RetVal("json")
       }
     },
     getAllUsedFontFaces: {
       request: {
         includePreviews: Option(0, "boolean"),
+        includeVariations: Option(1, "boolean"),
         previewText: Option(0, "string"),
         previewFontSize: Option(0, "string"),
         previewFillStyle: Option(0, "string")
       },
       response: {
         fontFaces: RetVal("array:fontface")
       }
     },
     getUsedFontFaces: {
       request: {
         node: Arg(0, "domnode"),
         includePreviews: Option(1, "boolean"),
+        includeVariations: Option(1, "boolean"),
         previewText: Option(1, "string"),
         previewFontSize: Option(1, "string"),
         previewFillStyle: Option(1, "string")
       },
       response: {
         fontFaces: RetVal("array:fontface")
       }
     },
--- a/dom/base/CustomElementRegistry.cpp
+++ b/dom/base/CustomElementRegistry.cpp
@@ -418,16 +418,21 @@ CustomElementRegistry::EnqueueLifecycleC
 {
   CustomElementDefinition* definition = aDefinition;
   if (!definition) {
     definition = aCustomElement->GetCustomElementDefinition();
     if (!definition ||
         definition->mLocalName != aCustomElement->NodeInfo()->NameAtom()) {
       return;
     }
+
+    if (!definition->mCallbacks) {
+      // definition has been unlinked.  Don't try to mess with it.
+      return;
+    }
   }
 
   auto callback =
     CreateCustomElementCallback(aType, aCustomElement, aArgs,
                                 aAdoptedCallbackArgs, definition);
   if (!callback) {
     return;
   }
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -1097,17 +1097,22 @@ nsIContent::GetEventTargetParent(EventCh
           // Step 11.3.
           // "Let relatedTarget be the result of retargeting event's
           // relatedTarget against parent if event's relatedTarget is non-null,
           // and null otherwise.".
           nsINode* retargetedRelatedTarget =
             nsContentUtils::Retarget(relatedTargetAsNode, this);
           nsCOMPtr<nsINode> targetInKnownToBeHandledScope =
             FindChromeAccessOnlySubtreeOwner(aVisitor.mTargetInKnownToBeHandledScope);
-          if (nsContentUtils::ContentIsShadowIncludingDescendantOf(
+          // If aVisitor.mTargetInKnownToBeHandledScope wasn't nsINode,
+          // targetInKnownToBeHandledScope will be null. This may happen when
+          // dispatching event to Window object in a content page and
+          // propagating the event to a chrome Element.
+          if (targetInKnownToBeHandledScope &&
+              nsContentUtils::ContentIsShadowIncludingDescendantOf(
                 this, targetInKnownToBeHandledScope->SubtreeRoot())) {
             // Part of step 11.4.
             // "If target's root is a shadow-including inclusive ancestor of
             //  parent, then"
             // "...Append to an event path with event, parent, null, relatedTarget,
             // "   and slot-in-closed-tree."
             aVisitor.mRetargetedRelatedTarget = retargetedRelatedTarget;
           } else if (this == retargetedRelatedTarget) {
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1440,18 +1440,18 @@ nsIDocument::nsIDocument()
     // &&-ed in, this is safe.
     mAllowDNSPrefetch(true),
     mIsStaticDocument(false),
     mCreatingStaticClone(false),
     mInUnlinkOrDeletion(false),
     mHasHadScriptHandlingObject(false),
     mIsBeingUsedAsImage(false),
     mIsSyntheticDocument(false),
-    mHasLinksToUpdate(false),
     mHasLinksToUpdateRunnable(false),
+    mFlushingPendingLinkUpdates(false),
     mMayHaveDOMMutationObservers(false),
     mMayHaveAnimationObservers(false),
     mHasMixedActiveContentLoaded(false),
     mHasMixedActiveContentBlocked(false),
     mHasMixedDisplayContentLoaded(false),
     mHasMixedDisplayContentBlocked(false),
     mHasMixedContentObjectSubrequest(false),
     mHasCSP(false),
@@ -1486,17 +1486,16 @@ nsIDocument::nsIDocument()
 #ifdef MOZILLA_INTERNAL_API
     mVisibilityState(dom::VisibilityState::Hidden),
 #else
     mDummy(0),
 #endif
     mType(eUnknown),
     mDefaultElementType(0),
     mAllowXULXBL(eTriUnset),
-    mIsLinkUpdateRegistrationsForbidden(false),
     mBidiOptions(IBMBIDI_DEFAULT_BIDI_OPTIONS),
     mSandboxFlags(0),
     mPartID(0),
     mMarkedCCGeneration(0),
     mPresShell(nullptr),
     mSubtreeModifiedDepth(0),
     mEventsSuppressed(0),
     mIgnoreDestructiveWritesCounter(0),
@@ -9607,73 +9606,72 @@ nsIDocument::EnumerateActivityObservers(
        iter.Next()) {
     aEnumerator(iter.Get()->GetKey(), aData);
   }
 }
 
 void
 nsIDocument::RegisterPendingLinkUpdate(Link* aLink)
 {
-  MOZ_RELEASE_ASSERT(!mIsLinkUpdateRegistrationsForbidden);
-
   if (aLink->HasPendingLinkUpdate()) {
     return;
   }
 
   aLink->SetHasPendingLinkUpdate();
 
-  if (!mHasLinksToUpdateRunnable) {
+  if (!mHasLinksToUpdateRunnable && !mFlushingPendingLinkUpdates) {
     nsCOMPtr<nsIRunnable> event =
       NewRunnableMethod("nsIDocument::FlushPendingLinkUpdatesFromRunnable",
                         this,
                         &nsIDocument::FlushPendingLinkUpdatesFromRunnable);
     // Do this work in a second in the worst case.
     nsresult rv =
       NS_IdleDispatchToCurrentThread(event.forget(), 1000);
     if (NS_FAILED(rv)) {
       // If during shutdown posting a runnable doesn't succeed, we probably
       // don't need to update link states.
       return;
     }
     mHasLinksToUpdateRunnable = true;
   }
 
   mLinksToUpdate.InfallibleAppend(aLink);
-  mHasLinksToUpdate = true;
 }
 
 void
 nsIDocument::FlushPendingLinkUpdatesFromRunnable()
 {
   MOZ_ASSERT(mHasLinksToUpdateRunnable);
   mHasLinksToUpdateRunnable = false;
   FlushPendingLinkUpdates();
 }
 
 void
 nsIDocument::FlushPendingLinkUpdates()
 {
-  MOZ_RELEASE_ASSERT(!mIsLinkUpdateRegistrationsForbidden);
-  if (!mHasLinksToUpdate)
-    return;
-
-  AutoRestore<bool> saved(mIsLinkUpdateRegistrationsForbidden);
-  mIsLinkUpdateRegistrationsForbidden = true;
-  for (auto iter = mLinksToUpdate.Iter(); !iter.Done(); iter.Next()) {
-    Link* link = iter.Get();
-    Element* element = link->GetElement();
-    if (element->OwnerDoc() == this) {
-      link->ClearHasPendingLinkUpdate();
-      if (element->IsInComposedDoc()) {
-        element->UpdateLinkState(link->LinkState());
-      }
-    }
-  }
-  mLinksToUpdate.Clear();
-  mHasLinksToUpdate = false;
+  if (mFlushingPendingLinkUpdates) {
+    return;
+  }
+
+  auto restore = MakeScopeExit([&] { mFlushingPendingLinkUpdates = false; });
+  mFlushingPendingLinkUpdates = true;
+
+  while (!mLinksToUpdate.IsEmpty()) {
+    LinksToUpdateList links(Move(mLinksToUpdate));
+    for (auto iter = links.Iter(); !iter.Done(); iter.Next()) {
+      Link* link = iter.Get();
+      Element* element = link->GetElement();
+      if (element->OwnerDoc() == this) {
+        link->ClearHasPendingLinkUpdate();
+        if (element->IsInComposedDoc()) {
+          element->UpdateLinkState(link->LinkState());
+        }
+      }
+    }
+  }
 }
 
 already_AddRefed<nsIDocument>
 nsIDocument::CreateStaticClone(nsIDocShell* aCloneContainer)
 {
   nsDocument* thisAsDoc = static_cast<nsDocument*>(this);
   mCreatingStaticClone = true;
 
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -3339,20 +3339,23 @@ protected:
   //
   // These are non-owning pointers, the elements are responsible for removing
   // themselves when they go away.
   nsAutoPtr<nsTHashtable<nsPtrHashKey<nsISupports> > > mActivityObservers;
 
   // The array of all links that need their status resolved.  Links must add themselves
   // to this set by calling RegisterPendingLinkUpdate when added to a document.
   static const size_t kSegmentSize = 128;
-  mozilla::SegmentedVector<nsCOMPtr<mozilla::dom::Link>,
-                           kSegmentSize,
-                           InfallibleAllocPolicy>
-    mLinksToUpdate;
+
+  typedef mozilla::SegmentedVector<nsCOMPtr<mozilla::dom::Link>,
+                                   kSegmentSize,
+                                   InfallibleAllocPolicy>
+    LinksToUpdateList;
+
+  LinksToUpdateList mLinksToUpdate;
 
   // SMIL Animation Controller, lazily-initialized in GetAnimationController
   RefPtr<nsSMILAnimationController> mAnimationController;
 
   // Table of element properties for this document.
   nsPropertyTable mPropertyTable;
   nsTArray<nsAutoPtr<nsPropertyTable> > mExtraPropertyTables;
 
@@ -3436,22 +3439,22 @@ protected:
 
   // True if we're an SVG document being used as an image.
   bool mIsBeingUsedAsImage : 1;
 
   // True is this document is synthetic : stand alone image, video, audio
   // file, etc.
   bool mIsSyntheticDocument : 1;
 
-  // True if this document has links whose state needs updating
-  bool mHasLinksToUpdate : 1;
-
   // True is there is a pending runnable which will call FlushPendingLinkUpdates().
   bool mHasLinksToUpdateRunnable : 1;
 
+  // True if we're flushing pending link updates.
+  bool mFlushingPendingLinkUpdates : 1;
+
   // True if a DOMMutationObserver is perhaps attached to a node in the document.
   bool mMayHaveDOMMutationObservers : 1;
 
   // True if an nsIAnimationObserver is perhaps attached to a node in the document.
   bool mMayHaveAnimationObservers : 1;
 
   // True if a document has loaded Mixed Active Script (see nsMixedContentBlocker.cpp)
   bool mHasMixedActiveContentLoaded : 1;
@@ -3590,22 +3593,16 @@ protected:
   enum Tri {
     eTriUnset = 0,
     eTriFalse,
     eTriTrue
   };
 
   Tri mAllowXULXBL;
 
-  /**
-   * This is true while FlushPendingLinkUpdates executes.  Calls to
-   * [Un]RegisterPendingLinkUpdate will assert when this is true.
-   */
-  bool mIsLinkUpdateRegistrationsForbidden;
-
   // The document's script global object, the object from which the
   // document can get its script context and scope. This is the
   // *inner* window object.
   nsCOMPtr<nsIScriptGlobalObject> mScriptGlobalObject;
 
   // If mIsStaticDocument is true, mOriginalDocument points to the original
   // document.
   nsCOMPtr<nsIDocument> mOriginalDocument;
--- a/dom/base/nsJSEnvironment.h
+++ b/dom/base/nsJSEnvironment.h
@@ -18,20 +18,16 @@
 #include "mozilla/TimeStamp.h"
 #include "nsThreadUtils.h"
 #include "xpcpublic.h"
 
 class nsICycleCollectorListener;
 class nsScriptNameSpaceManager;
 class nsIDocShell;
 
-namespace JS {
-class AutoValueVector;
-} // namespace JS
-
 namespace mozilla {
 template <class> class Maybe;
 struct CycleCollectorResults;
 } // namespace mozilla
 
 // The amount of time we wait between a request to GC (due to leaving
 // a page) and doing the actual GC.
 #define NS_GC_DELAY                 4000 // ms
--- a/dom/clients/manager/ClientSource.cpp
+++ b/dom/clients/manager/ClientSource.cpp
@@ -203,17 +203,19 @@ ClientSource::WorkerExecutionReady(Worke
     return;
   }
 
   // A client without access to storage should never be controlled by
   // a service worker.  Check this here in case we were controlled before
   // execution ready.  We can't reliably determine what our storage policy
   // is before execution ready, unfortunately.
   if (mController.isSome()) {
-    MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate->IsStorageAllowed());
+    MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate->IsStorageAllowed() ||
+                          StringBeginsWith(aWorkerPrivate->ScriptURL(),
+                                           NS_LITERAL_STRING("blob:")));
   }
 
   // Its safe to store the WorkerPrivate* here because the ClientSource
   // is explicitly destroyed by WorkerPrivate before exiting its run loop.
   MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>());
   mOwner = AsVariant(aWorkerPrivate);
 
   ClientSourceExecutionReadyArgs args(
@@ -231,44 +233,46 @@ ClientSource::WindowExecutionReady(nsPID
   MOZ_DIAGNOSTIC_ASSERT(aInnerWindow->IsCurrentInnerWindow());
   MOZ_DIAGNOSTIC_ASSERT(aInnerWindow->HasActiveDocument());
 
   if (IsShutdown()) {
     return NS_OK;
   }
 
   nsIDocument* doc = aInnerWindow->GetExtantDoc();
-  if (NS_WARN_IF(!doc)) {
-    return NS_ERROR_UNEXPECTED;
-  }
+  NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
+
+  nsIURI* uri = doc->GetOriginalURI();
+  NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED);
+
+  // Don't use nsAutoCString here since IPC requires a full nsCString anyway.
+  nsCString spec;
+  nsresult rv = uri->GetSpec(spec);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   // A client without access to storage should never be controlled by
   // a service worker.  Check this here in case we were controlled before
   // execution ready.  We can't reliably determine what our storage policy
   // is before execution ready, unfortunately.
+  //
+  // Note, explicitly avoid checking storage policy for windows that inherit
+  // service workers from their parent.  If a user opens a controlled window
+  // and then blocks storage, that window will continue to be controlled by
+  // the SW until the window is closed.  Any about:blank or blob URL should
+  // continue to inherit the SW as well.  We need to avoid triggering the
+  // assertion in this corner case.
   if (mController.isSome()) {
-    MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::StorageAllowedForWindow(aInnerWindow) ==
+    MOZ_DIAGNOSTIC_ASSERT(spec.LowerCaseEqualsLiteral("about:blank") ||
+                          StringBeginsWith(spec, NS_LITERAL_CSTRING("blob:")) ||
+                          nsContentUtils::StorageAllowedForWindow(aInnerWindow) ==
                           nsContentUtils::StorageAccess::eAllow);
   }
 
-  // Don't use nsAutoCString here since IPC requires a full nsCString anyway.
-  nsCString spec;
-
-  nsIURI* uri = doc->GetOriginalURI();
-  if (uri) {
-    nsresult rv = uri->GetSpec(spec);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-
   nsPIDOMWindowOuter* outer = aInnerWindow->GetOuterWindow();
-  if (NS_WARN_IF(!outer)) {
-    return NS_ERROR_UNEXPECTED;
-  }
+  NS_ENSURE_TRUE(outer, NS_ERROR_UNEXPECTED);
 
   FrameType frameType = FrameType::Top_level;
   if (!outer->IsTopLevelWindow()) {
     frameType = FrameType::Nested;
   } else if(outer->HadOriginalOpener()) {
     frameType = FrameType::Auxiliary;
   }
 
@@ -381,21 +385,29 @@ ClientSource::SetController(const Servic
   // A client in private browsing mode should never be controlled by
   // a service worker.  The principal origin attributes should guarantee
   // this invariant.
   MOZ_DIAGNOSTIC_ASSERT(!mClientInfo.IsPrivateBrowsing());
 
   // A client without access to storage should never be controlled a
   // a service worker.  If we are already execution ready with a real
   // window or worker, then verify assert the storage policy is correct.
+  //
+  // Note, explicitly avoid checking storage policy for clients that inherit
+  // service workers from their parent.  This basically means blob: URLs
+  // and about:blank windows.
   if (GetInnerWindow()) {
-    MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::StorageAllowedForWindow(GetInnerWindow()) ==
+    MOZ_DIAGNOSTIC_ASSERT(Info().URL().LowerCaseEqualsLiteral("about:blank") ||
+                          StringBeginsWith(Info().URL(), NS_LITERAL_CSTRING("blob:")) ||
+                          nsContentUtils::StorageAllowedForWindow(GetInnerWindow()) ==
                           nsContentUtils::StorageAccess::eAllow);
   } else if (GetWorkerPrivate()) {
-    MOZ_DIAGNOSTIC_ASSERT(GetWorkerPrivate()->IsStorageAllowed());
+    MOZ_DIAGNOSTIC_ASSERT(GetWorkerPrivate()->IsStorageAllowed() ||
+                          StringBeginsWith(GetWorkerPrivate()->ScriptURL(),
+                                           NS_LITERAL_STRING("blob:")));
   }
 
   if (mController.isSome() && mController.ref() == aServiceWorker) {
     return;
   }
 
   mController.reset();
   mController.emplace(aServiceWorker);
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -2069,17 +2069,19 @@ ScriptLoader::FillCompileOptionsForReque
     aOptions->setSourceMapURL(aRequest->mSourceMapURL.get());
   }
   if (aRequest->mOriginPrincipal) {
     nsIPrincipal* scriptPrin = nsContentUtils::ObjectPrincipal(aScopeChain);
     bool subsumes = scriptPrin->Subsumes(aRequest->mOriginPrincipal);
     aOptions->setMutedErrors(!subsumes);
   }
 
-  if (!aRequest->IsModuleRequest()) {
+  if (aRequest->IsModuleRequest()) {
+    aOptions->hideScriptFromDebugger = true;
+  } else {
     JSContext* cx = jsapi.cx();
     JS::Rooted<JS::Value> elementVal(cx);
     MOZ_ASSERT(aRequest->mElement);
     if (NS_SUCCEEDED(nsContentUtils::WrapNative(cx, aRequest->mElement,
                                                 &elementVal,
                                                 /* aAllowWrapping = */ true))) {
       MOZ_ASSERT(elementVal.isObject());
       aOptions->setElement(&elementVal.toObject());
@@ -2240,16 +2242,20 @@ ScriptLoader::EvaluateScript(ScriptLoadR
 
       JS::Rooted<JSObject*> module(cx, moduleScript->ModuleRecord());
       MOZ_ASSERT(module);
 
       if (!moduleScript->SourceElementAssociated()) {
         rv = nsJSUtils::InitModuleSourceElement(cx, module, aRequest->mElement);
         NS_ENSURE_SUCCESS(rv, rv);
         moduleScript->SetSourceElementAssociated();
+
+        // The script is now ready to be exposed to the debugger.
+        JS::Rooted<JSScript*> script(cx, JS::GetModuleScript(module));
+        JS::ExposeScriptToDebugger(cx, script);
       }
 
       rv = nsJSUtils::ModuleEvaluate(cx, module);
       MOZ_ASSERT(NS_FAILED(rv) == aes.HasException());
       if (NS_FAILED(rv)) {
         LOG(("ScriptLoadRequest (%p):   evaluation failed", aRequest));
         rv = NS_OK; // An error is reported by AutoEntryScript.
       }
--- a/dom/security/nsCSPService.cpp
+++ b/dom/security/nsCSPService.cpp
@@ -38,23 +38,27 @@ CSPService::~CSPService()
   mAppStatusCache.Clear();
 }
 
 NS_IMPL_ISUPPORTS(CSPService, nsIContentPolicy, nsIChannelEventSink)
 
 // Helper function to identify protocols and content types not subject to CSP.
 bool
 subjectToCSP(nsIURI* aURI, nsContentPolicyType aContentType) {
+
+  nsContentPolicyType contentType =
+    nsContentUtils::InternalContentPolicyTypeToExternal(aContentType);
+
   // These content types are not subject to CSP content policy checks:
   // TYPE_CSP_REPORT -- csp can't block csp reports
   // TYPE_REFRESH    -- never passed to ShouldLoad (see nsIContentPolicy.idl)
   // TYPE_DOCUMENT   -- used for frame-ancestors
-  if (aContentType == nsIContentPolicy::TYPE_CSP_REPORT ||
-      aContentType == nsIContentPolicy::TYPE_REFRESH ||
-      aContentType == nsIContentPolicy::TYPE_DOCUMENT) {
+  if (contentType == nsIContentPolicy::TYPE_CSP_REPORT ||
+      contentType == nsIContentPolicy::TYPE_REFRESH ||
+      contentType == nsIContentPolicy::TYPE_DOCUMENT) {
     return false;
   }
 
   // The three protocols: data:, blob: and filesystem: share the same
   // protocol flag (URI_IS_LOCAL_RESOURCE) with other protocols,
   // but those three protocols get special attention in CSP and
   // are subject to CSP, hence we have to make sure those
   // protocols are subject to CSP, see:
@@ -85,22 +89,26 @@ subjectToCSP(nsIURI* aURI, nsContentPoli
     return false;
   }
 
   // Please note that it should be possible for websites to
   // whitelist their own protocol handlers with respect to CSP,
   // hence we use protocol flags to accomplish that, but we also
   // want resource:, chrome: and moz-icon to be subject to CSP
   // (which also use URI_IS_LOCAL_RESOURCE).
+  // Exception to the rule are images and styles using a scheme
+  // of resource: or chrome:
+  bool isImgOrStyle = contentType == nsIContentPolicy::TYPE_IMAGE ||
+                      contentType == nsIContentPolicy::TYPE_STYLESHEET;
   rv = aURI->SchemeIs("resource", &match);
-  if (NS_SUCCEEDED(rv) && match) {
+  if (NS_SUCCEEDED(rv) && match && !isImgOrStyle) {
     return true;
   }
   rv = aURI->SchemeIs("chrome", &match);
-  if (NS_SUCCEEDED(rv) && match) {
+  if (NS_SUCCEEDED(rv) && match && !isImgOrStyle) {
     return true;
   }
   rv = aURI->SchemeIs("moz-icon", &match);
   if (NS_SUCCEEDED(rv) && match) {
     return true;
   }
   rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &match);
   if (NS_SUCCEEDED(rv) && match) {
--- a/dom/serviceworkers/test/browser_storage_permission.js
+++ b/dom/serviceworkers/test/browser_storage_permission.js
@@ -90,16 +90,158 @@ add_task(async function test_session_per
   });
 
   is(controller, null, "page should be not controlled with session storage");
 
   await BrowserTestUtils.removeTab(tab);
   Services.perms.remove(Services.io.newURI(PAGE_URI), "cookie");
 });
 
+// Test to verify an about:blank iframe successfully inherits the
+// parent's controller when storage is blocked between opening the
+// parent page and creating the iframe.
+add_task(async function test_block_storage_before_blank_iframe() {
+  Services.perms.remove(Services.io.newURI(PAGE_URI), "cookie");
+
+  let tab = BrowserTestUtils.addTab(gBrowser, SCOPE);
+  let browser = gBrowser.getBrowserForTab(tab);
+  await BrowserTestUtils.browserLoaded(browser);
+
+  let controller = await ContentTask.spawn(browser, null, async function() {
+    return content.navigator.serviceWorker.controller;
+  });
+
+  ok(!!controller, "page should be controlled with storage allowed");
+
+  let controller2 = await ContentTask.spawn(browser, null, async function() {
+    let f = content.document.createElement("iframe");
+    content.document.body.appendChild(f);
+    await new Promise(resolve => f.onload = resolve);
+    return !!f.contentWindow.navigator.serviceWorker.controller;
+  });
+
+  ok(!!controller2, "page should be controlled with storage allowed");
+
+  await SpecialPowers.pushPrefEnv({"set": [
+    ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT],
+  ]});
+
+  let controller3 = await ContentTask.spawn(browser, null, async function() {
+    let f = content.document.createElement("iframe");
+    content.document.body.appendChild(f);
+    await new Promise(resolve => f.onload = resolve);
+    return !!f.contentWindow.navigator.serviceWorker.controller;
+  });
+
+  ok(!!controller3, "page should be controlled with storage allowed");
+
+  await SpecialPowers.popPrefEnv();
+  await BrowserTestUtils.removeTab(tab);
+});
+
+// Test to verify a blob URL iframe successfully inherits the
+// parent's controller when storage is blocked between opening the
+// parent page and creating the iframe.
+add_task(async function test_block_storage_before_blob_iframe() {
+  Services.perms.remove(Services.io.newURI(PAGE_URI), "cookie");
+
+  let tab = BrowserTestUtils.addTab(gBrowser, SCOPE);
+  let browser = gBrowser.getBrowserForTab(tab);
+  await BrowserTestUtils.browserLoaded(browser);
+
+  let controller = await ContentTask.spawn(browser, null, async function() {
+    return content.navigator.serviceWorker.controller;
+  });
+
+  ok(!!controller, "page should be controlled with storage allowed");
+
+  let controller2 = await ContentTask.spawn(browser, null, async function() {
+    let b = new content.Blob(["<!DOCTYPE html><html></html>"], { type: "text/html" });
+    let f = content.document.createElement("iframe");
+    // No need to call revokeObjectURL() since the window will be closed shortly.
+    f.src = content.URL.createObjectURL(b);
+    content.document.body.appendChild(f);
+    await new Promise(resolve => f.onload = resolve);
+    return !!f.contentWindow.navigator.serviceWorker.controller;
+  });
+
+  ok(!!controller2, "page should be controlled with storage allowed");
+
+  await SpecialPowers.pushPrefEnv({"set": [
+    ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT],
+  ]});
+
+  let controller3 = await ContentTask.spawn(browser, null, async function() {
+    let b = new content.Blob(["<!DOCTYPE html><html></html>"], { type: "text/html" });
+    let f = content.document.createElement("iframe");
+    // No need to call revokeObjectURL() since the window will be closed shortly.
+    f.src = content.URL.createObjectURL(b);
+    content.document.body.appendChild(f);
+    await new Promise(resolve => f.onload = resolve);
+    return !!f.contentWindow.navigator.serviceWorker.controller;
+  });
+
+  ok(!!controller3, "page should be controlled with storage allowed");
+
+  await SpecialPowers.popPrefEnv();
+  await BrowserTestUtils.removeTab(tab);
+});
+
+// Test to verify a blob worker script does not hit our service
+// worker storage assertions when storage is blocked between opening
+// the parent page and creating the worker.  Note, we cannot
+// explicitly check if the worker is controlled since we don't expose
+// WorkerNavigator.serviceWorkers.controller yet.
+add_task(async function test_block_storage_before_blob_worker() {
+  Services.perms.remove(Services.io.newURI(PAGE_URI), "cookie");
+
+  let tab = BrowserTestUtils.addTab(gBrowser, SCOPE);
+  let browser = gBrowser.getBrowserForTab(tab);
+  await BrowserTestUtils.browserLoaded(browser);
+
+  let controller = await ContentTask.spawn(browser, null, async function() {
+    return content.navigator.serviceWorker.controller;
+  });
+
+  ok(!!controller, "page should be controlled with storage allowed");
+
+  let scriptURL = await ContentTask.spawn(browser, null, async function() {
+    let b = new content.Blob(["self.postMessage(self.location.href);self.close()"],
+                             { type: "application/javascript" });
+    // No need to call revokeObjectURL() since the window will be closed shortly.
+    let u = content.URL.createObjectURL(b);
+    let w = new content.Worker(u);
+    return await new Promise(resolve => {
+      w.onmessage = e => resolve(e.data);
+    });
+  });
+
+  ok(scriptURL.startsWith("blob:"), "blob URL worker should run");
+
+  await SpecialPowers.pushPrefEnv({"set": [
+    ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT],
+  ]});
+
+  let scriptURL2 = await ContentTask.spawn(browser, null, async function() {
+    let b = new content.Blob(["self.postMessage(self.location.href);self.close()"],
+                             { type: "application/javascript" });
+    // No need to call revokeObjectURL() since the window will be closed shortly.
+    let u = content.URL.createObjectURL(b);
+    let w = new content.Worker(u);
+    return await new Promise(resolve => {
+      w.onmessage = e => resolve(e.data);
+    });
+  });
+
+  ok(scriptURL2.startsWith("blob:"), "blob URL worker should run");
+
+  await SpecialPowers.popPrefEnv();
+  await BrowserTestUtils.removeTab(tab);
+});
+
 add_task(async function cleanup() {
   Services.perms.remove(Services.io.newURI(PAGE_URI), "cookie");
 
   let tab = BrowserTestUtils.addTab(gBrowser, PAGE_URI);
   let browser = gBrowser.getBrowserForTab(tab);
   await BrowserTestUtils.browserLoaded(browser);
 
   await ContentTask.spawn(browser, SCOPE, async function(uri) {
--- a/dom/tests/mochitest/general/test_interfaces.js
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -15,27 +15,43 @@
 //   {name: "ExperimentalThing", release: false},
 //   {name: "ReallyExperimentalThing", nightly: true},
 //   {name: "DesktopOnlyThing", desktop: true},
 //   {name: "FancyControl", xbl: true},
 //   {name: "DisabledEverywhere", disabled: true},
 // ];
 //
 // See createInterfaceMap() below for a complete list of properties.
+//
+// The values of the properties need to be either literal true/false
+// (e.g. indicating whether something is enabled on a particular
+// channel/OS) or one of the is* constants below (in cases when
+// exposure is affected by channel or OS in a nontrivial way).
+
+const version = SpecialPowers.Cc["@mozilla.org/xre/app-info;1"].getService(SpecialPowers.Ci.nsIXULAppInfo).version;
+const isNightly = version.endsWith("a1");
+const isEarlyBetaOrEarlier = SpecialPowers.EARLY_BETA_OR_EARLIER;
+const isRelease = !version.includes("a");
+const isDesktop = !/Mobile|Tablet/.test(navigator.userAgent);
+const isMac = /Mac OS/.test(navigator.oscpu);
+const isWindows = /Windows/.test(navigator.oscpu);
+const isAndroid = navigator.userAgent.includes("Android");
+const isLinux = /Linux/.test(navigator.oscpu) && !isAndroid;
+const isInsecureContext = !window.isSecureContext;
 
 // IMPORTANT: Do not change this list without review from
 //            a JavaScript Engine peer!
 var ecmaGlobals =
   [
     {name: "Array", insecureContext: true},
     {name: "ArrayBuffer", insecureContext: true},
     {name: "Atomics", insecureContext: true, disabled: true},
     {name: "Boolean", insecureContext: true},
-    {name: "ByteLengthQueuingStrategy", insecureContext: true, disabled: !SpecialPowers.Cu.getJSTestingFunctions().streamsAreEnabled()},
-    {name: "CountQueuingStrategy", insecureContext: true, disabled: !SpecialPowers.Cu.getJSTestingFunctions().streamsAreEnabled()},
+    {name: "ByteLengthQueuingStrategy", insecureContext: true, disabled: true},
+    {name: "CountQueuingStrategy", insecureContext: true, disabled: true},
     {name: "DataView", insecureContext: true},
     {name: "Date", insecureContext: true},
     {name: "Error", insecureContext: true},
     {name: "EvalError", insecureContext: true},
     {name: "Float32Array", insecureContext: true},
     {name: "Float64Array", insecureContext: true},
     {name: "Function", insecureContext: true},
     // NB: We haven't bothered to resolve constants like Infinity and NaN on
@@ -51,17 +67,17 @@ var ecmaGlobals =
     {name: "Map", insecureContext: true},
     {name: "Math", insecureContext: true},
     {name: "NaN", insecureContext: true, xbl: false},
     {name: "Number", insecureContext: true},
     {name: "Object", insecureContext: true},
     {name: "Promise", insecureContext: true},
     {name: "Proxy", insecureContext: true},
     {name: "RangeError", insecureContext: true},
-    {name: "ReadableStream", insecureContext: true, disabled: !SpecialPowers.Cu.getJSTestingFunctions().streamsAreEnabled()},
+    {name: "ReadableStream", insecureContext: true, disabled: true},
     {name: "ReferenceError", insecureContext: true},
     {name: "Reflect", insecureContext: true},
     {name: "RegExp", insecureContext: true},
     {name: "Set", insecureContext: true},
     {name: "SharedArrayBuffer", insecureContext: true, disabled: true},
     {name: "SIMD", insecureContext: true, nightly: true},
     {name: "String", insecureContext: true},
     {name: "Symbol", insecureContext: true},
@@ -70,17 +86,17 @@ var ecmaGlobals =
     {name: "TypeError", insecureContext: true},
     {name: "Uint16Array", insecureContext: true},
     {name: "Uint32Array", insecureContext: true},
     {name: "Uint8Array", insecureContext: true},
     {name: "Uint8ClampedArray", insecureContext: true},
     {name: "URIError", insecureContext: true},
     {name: "WeakMap", insecureContext: true},
     {name: "WeakSet", insecureContext: true},
-    {name: "WebAssembly", insecureContext: true, disabled: !SpecialPowers.Cu.getJSTestingFunctions().wasmIsSupported()}
+    {name: "WebAssembly", insecureContext: true, disabled: !SpecialPowers.Cu.getJSTestingFunctions().wasmIsSupportedByHardware()},
   ];
 // IMPORTANT: Do not change the list above without review from
 //            a JavaScript Engine peer!
 
 // IMPORTANT: Do not change the list below without review from a DOM peer,
 //            except to remove items from it!
 //
 // This is a list of interfaces that were prefixed with 'moz' instead of 'Moz'.
@@ -718,17 +734,17 @@ var interfaceNamesInGlobalScope =
     {name: "Notification", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "OffscreenCanvas", insecureContext: true, disabled: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "OfflineAudioCompletionEvent", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "OfflineAudioContext", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "OfflineResourceList", insecureContext: SpecialPowers.getBoolPref("browser.cache.offline.insecure.enable")},
+    {name: "OfflineResourceList", insecureContext: !isEarlyBetaOrEarlier},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "Option", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "OscillatorNode", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "PageTransitionEvent", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "PaintRequest", insecureContext: true},
@@ -1262,26 +1278,16 @@ var interfaceNamesInGlobalScope =
     {name: "XULElement", insecureContext: true, xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "XULLabeledControlElement", insecureContext: true, xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
   ];
 // IMPORTANT: Do not change the list above without review from a DOM peer!
 
 function createInterfaceMap(isXBLScope) {
-  var version = SpecialPowers.Cc["@mozilla.org/xre/app-info;1"].getService(SpecialPowers.Ci.nsIXULAppInfo).version;
-  var isNightly = version.endsWith("a1");
-  var isRelease = !version.includes("a");
-  var isDesktop = !/Mobile|Tablet/.test(navigator.userAgent);
-  var isMac = /Mac OS/.test(navigator.oscpu);
-  var isWindows = /Windows/.test(navigator.oscpu);
-  var isAndroid = navigator.userAgent.includes("Android");
-  var isLinux = /Linux/.test(navigator.oscpu) && !isAndroid;
-  var isInsecureContext = !window.isSecureContext;
-
   var interfaceMap = {};
 
   function addInterfaces(interfaces)
   {
     for (var entry of interfaces) {
       if (typeof(entry) === "string") {
         interfaceMap[entry] = !isInsecureContext;
       } else {
--- a/dom/workers/test/test_worker_interfaces.js
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -13,27 +13,33 @@
 //   { name: "ExperimentalThing", release: false },
 //   { name: "ReallyExperimentalThing", nightly: true },
 //   { name: "DesktopOnlyThing", desktop: true },
 //   { name: "FancyControl", xbl: true },
 //   { name: "DisabledEverywhere", disabled: true },
 // ];
 //
 // See createInterfaceMap() below for a complete list of properties.
+//
+// The values of the properties need to be literal true/false
+// (e.g. indicating whether something is enabled on a particular
+// channel/OS).  If we ever end up in a situation where a propert
+// value needs to depend on channel or OS, we will need to make sure
+// we have that information before setting up the property lists.
 
 // IMPORTANT: Do not change this list without review from
 //            a JavaScript Engine peer!
 var ecmaGlobals =
   [
     {name: "Array", insecureContext: true},
     {name: "ArrayBuffer", insecureContext: true},
     {name: "Atomics", insecureContext: true, disabled: true},
     {name: "Boolean", insecureContext: true},
-    {name: "ByteLengthQueuingStrategy", insecureContext: true, optional: true},
-    {name: "CountQueuingStrategy", insecureContext: true, optional: true},
+    {name: "ByteLengthQueuingStrategy", insecureContext: true, disabled: true},
+    {name: "CountQueuingStrategy", insecureContext: true, disabled: true},
     {name: "DataView", insecureContext: true},
     {name: "Date", insecureContext: true},
     {name: "Error", insecureContext: true},
     {name: "EvalError", insecureContext: true},
     {name: "Float32Array", insecureContext: true},
     {name: "Float64Array", insecureContext: true},
     {name: "Function", insecureContext: true},
     {name: "Infinity", insecureContext: true},
@@ -46,17 +52,17 @@ var ecmaGlobals =
     {name: "Map", insecureContext: true},
     {name: "Math", insecureContext: true},
     {name: "NaN", insecureContext: true},
     {name: "Number", insecureContext: true},
     {name: "Object", insecureContext: true},
     {name: "Promise", insecureContext: true},
     {name: "Proxy", insecureContext: true},
     {name: "RangeError", insecureContext: true},
-    {name: "ReadableStream", insecureContext: true, optional: true},
+    {name: "ReadableStream", insecureContext: true, disabled: true},
     {name: "ReferenceError", insecureContext: true},
     {name: "Reflect", insecureContext: true},
     {name: "RegExp", insecureContext: true},
     {name: "Set", insecureContext: true},
     {name: "SharedArrayBuffer", insecureContext: true, disabled: true},
     {name: "SIMD", insecureContext: true, nightly: true},
     {name: "String", insecureContext: true},
     {name: "Symbol", insecureContext: true},
@@ -65,17 +71,21 @@ var ecmaGlobals =
     {name: "TypeError", insecureContext: true},
     {name: "Uint16Array", insecureContext: true},
     {name: "Uint32Array", insecureContext: true},
     {name: "Uint8Array", insecureContext: true},
     {name: "Uint8ClampedArray", insecureContext: true},
     {name: "URIError", insecureContext: true},
     {name: "WeakMap", insecureContext: true},
     {name: "WeakSet", insecureContext: true},
-    {name: "WebAssembly", insecureContext: true, optional: true}
+    // WebAssembly is not supported on some hardware configurations,
+    // but we have no way to check that from here.  Just give up for
+    // now and don't check for it at all.  Do NOT add any other uses
+    // of "optional"!
+    {name: "WebAssembly", insecureContext: true, optional: true},
   ];
 // IMPORTANT: Do not change the list above without review from
 //            a JavaScript Engine peer!
 
 // IMPORTANT: Do not change the list below without review from a DOM peer!
 var interfaceNamesInGlobalScope =
   [
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/dom/xslt/xpath/txExprLexer.cpp
+++ b/dom/xslt/xpath/txExprLexer.cpp
@@ -89,18 +89,18 @@ txExprLexer::nextIsOperatorToken(Token* 
 }
 
 /**
  * Parses the given string into a sequence of Tokens
  */
 nsresult
 txExprLexer::parse(const nsAString& aPattern)
 {
-  iterator start, end;
-  start = aPattern.BeginReading(mPosition);
+  iterator end;
+  aPattern.BeginReading(mPosition);
   aPattern.EndReading(end);
 
   //-- initialize previous token, this will automatically get
   //-- deleted when it goes out of scope
   Token nullToken(nullptr, nullptr, Token::NULL_TOKEN);
 
   Token::Type defType;
   Token* newToken = nullptr;
@@ -120,17 +120,17 @@ txExprLexer::parse(const nsAString& aPat
     }
     // just reuse the QName parsing, which will use defType
     // the token to construct
 
     if (XMLUtils::isLetter(*mPosition)) {
       // NCName, can get QName or OperatorName;
       //  FunctionName, NodeName, and AxisSpecifier may want whitespace,
       //  and are dealt with below
-      start = mPosition;
+      iterator start = mPosition;
       while (++mPosition < end && XMLUtils::isNCNameChar(*mPosition)) {
         /* just go */
       }
       if (mPosition < end && *mPosition == COLON) {
         // try QName or wildcard, might need to step back for axis
         if (++mPosition == end) {
           return NS_ERROR_XPATH_UNEXPECTED_END;
         }
@@ -165,17 +165,17 @@ txExprLexer::parse(const nsAString& aPat
           // XXX QUESTION: spec is not too precise
           // badops is sure an error, but is bad:ops, too? We say yes!
           return NS_ERROR_XPATH_OPERATOR_EXPECTED;
         }
       }
       newToken = new Token(start, mPosition, defType);
     }
     else if (isXPathDigit(*mPosition)) {
-      start = mPosition;
+      iterator start = mPosition;
       while (++mPosition < end && isXPathDigit(*mPosition)) {
         /* just go */
       }
       if (mPosition < end && *mPosition == '.') {
         while (++mPosition < end && isXPathDigit(*mPosition)) {
           /* just go */
         }
       }
@@ -188,34 +188,36 @@ txExprLexer::parse(const nsAString& aPat
       case TX_TAB:
       case TX_CR:
       case TX_LF:
         ++mPosition;
         isToken = false;
         break;
       case S_QUOTE :
       case D_QUOTE :
-        start = mPosition;
+      {
+        iterator start = mPosition;
         while (++mPosition < end && *mPosition != *start) {
           // eat literal
         }
         if (mPosition == end) {
           mPosition = start;
           return NS_ERROR_XPATH_UNCLOSED_LITERAL;
         }
         newToken = new Token(start + 1, mPosition, Token::LITERAL);
         ++mPosition;
-        break;
+      }
+      break;
       case PERIOD:
         // period can be .., .(DIGITS)+ or ., check next
         if (++mPosition == end) {
           newToken = new Token(mPosition - 1, Token::SELF_NODE);
         }
         else if (isXPathDigit(*mPosition)) {
-          start = mPosition - 1;
+          iterator start = mPosition - 1;
           while (++mPosition < end && isXPathDigit(*mPosition)) {
             /* just go */
           }
           newToken = new Token(start, mPosition, Token::NUMBER);
         }
         else if (*mPosition == PERIOD) {
           ++mPosition;
           newToken = new Token(mPosition - 2, mPosition, Token::PARENT_NODE);
--- a/gfx/thebes/gfxFT2FontBase.cpp
+++ b/gfx/thebes/gfxFT2FontBase.cpp
@@ -169,17 +169,23 @@ gfxFT2FontBase::GetCharExtents(char aCha
  * exists.  aWidth is only set when this returns a non-zero glyph id.
  * This is just for use during initialization, and doesn't use the width cache.
  */
 uint32_t
 gfxFT2FontBase::GetCharWidth(char aChar, gfxFloat* aWidth)
 {
     FT_UInt gid = GetGlyph(aChar);
     if (gid) {
-        *aWidth = FLOAT_FROM_16_16(GetFTGlyphAdvance(gid));
+        int32_t width;
+        if (!GetFTGlyphAdvance(gid, &width)) {
+            cairo_text_extents_t extents;
+            GetGlyphExtents(gid, &extents);
+            width = NS_lround(0x10000 * extents.x_advance);
+        }
+        *aWidth = FLOAT_FROM_16_16(width);
     }
     return gid;
 }
 
 void
 gfxFT2FontBase::InitMetrics()
 {
     mFUnitsConvFactor = 0.0;
@@ -521,78 +527,89 @@ gfxFT2FontBase::GetGlyph(uint32_t unicod
             return GetGlyph(unicode);
         }
         return 0;
     }
 
     return GetGlyph(unicode);
 }
 
-FT_Fixed
-gfxFT2FontBase::GetFTGlyphAdvance(uint16_t aGID)
+bool
+gfxFT2FontBase::GetFTGlyphAdvance(uint16_t aGID, int32_t* aAdvance)
 {
     gfxFT2LockedFace face(this);
     MOZ_ASSERT(face.get());
     if (!face.get()) {
         // Failed to get the FT_Face? Give up already.
-        return 0;
+        NS_WARNING("failed to get FT_Face!");
+        return false;
     }
+
+    // Due to bugs like 1435234 and 1440938, we currently prefer to fall back
+    // to reading the advance from cairo extents, unless we're dealing with
+    // a variation font (for which cairo metrics may be wrong, due to FreeType
+    // bug 52683).
+    if (!(face.get()->face_flags & FT_FACE_FLAG_SCALABLE) ||
+        !(face.get()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS)) {
+        return false;
+    }
+
     bool hinting = gfxPlatform::GetPlatform()->FontHintingEnabled();
     int32_t flags =
         hinting ? FT_LOAD_ADVANCE_ONLY
                 : FT_LOAD_ADVANCE_ONLY | FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING;
     FT_Error ftError = FT_Load_Glyph(face.get(), aGID, flags);
-    MOZ_ASSERT(!ftError);
     if (ftError != FT_Err_Ok) {
         // FT_Face was somehow broken/invalid? Don't try to access glyph slot.
-        return 0;
+        // This probably shouldn't happen, but does: see bug 1440938.
+        NS_WARNING("failed to load glyph!");
+        return false;
     }
-    FT_Fixed advance = 0;
+
     // Due to freetype bug 52683 we MUST use the linearHoriAdvance field when
-    // dealing with a variation font; also use it for scalable fonts when not
-    // applying hinting. Otherwise, prefer hinted width from glyph->advance.x.
-    if ((face.get()->face_flags & FT_FACE_FLAG_SCALABLE) &&
-        (!hinting || (face.get()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS))) {
-        advance = face.get()->glyph->linearHoriAdvance;
-    } else {
-        advance = face.get()->glyph->advance.x << 10; // convert 26.6 to 16.16
-    }
+    // dealing with a variation font. (And other fonts would have returned
+    // earlier, so only variation fonts currently reach here.)
+    FT_Fixed advance = face.get()->glyph->linearHoriAdvance;
 
     // If freetype emboldening is being used, and it's not a zero-width glyph,
     // adjust the advance to account for the increased width.
     if (mEmbolden && advance > 0) {
         // This is the embolden "strength" used by FT_GlyphSlot_Embolden,
         // converted from 26.6 to 16.16
         FT_Fixed strength = 1024 *
             FT_MulFix(face.get()->units_per_EM,
                       face.get()->size->metrics.y_scale) / 24;
         advance += strength;
     }
 
     // Round the 16.16 fixed-point value to whole pixels for better consistency
     // with how cairo renders the glyphs.
-    advance = (advance + 0x8000) & 0xffff0000u;
+    *aAdvance = (advance + 0x8000) & 0xffff0000u;
 
-    return advance;
+    return true;
 }
 
 int32_t
 gfxFT2FontBase::GetGlyphWidth(DrawTarget& aDrawTarget, uint16_t aGID)
 {
     if (!mGlyphWidths) {
         mGlyphWidths =
             mozilla::MakeUnique<nsDataHashtable<nsUint32HashKey,int32_t>>(128);
     }
 
     int32_t width;
     if (mGlyphWidths->Get(aGID, &width)) {
         return width;
     }
 
-    width = GetFTGlyphAdvance(aGID);
+    if (!GetFTGlyphAdvance(aGID, &width)) {
+        cairo_text_extents_t extents;
+        GetGlyphExtents(aGID, &extents);
+        width = NS_lround(0x10000 * extents.x_advance);
+    }
     mGlyphWidths->Put(aGID, width);
 
     return width;
 }
 
 bool
 gfxFT2FontBase::SetupCairoFont(DrawTarget* aDrawTarget)
 {
--- a/gfx/thebes/gfxFT2FontBase.h
+++ b/gfx/thebes/gfxFT2FontBase.h
@@ -40,17 +40,22 @@ public:
 
     static void SetupVarCoords(FT_Face aFace,
                                const nsTArray<gfxFontVariation>& aVariations,
                                nsTArray<FT_Fixed>* aCoords);
 
 private:
     uint32_t GetCharExtents(char aChar, cairo_text_extents_t* aExtents);
     uint32_t GetCharWidth(char aChar, gfxFloat* aWidth);
-    FT_Fixed GetFTGlyphAdvance(uint16_t aGID);
+
+    // Get advance of a single glyph from FreeType, and return true;
+    // or return false if we should fall back to getting the glyph
+    // extents from cairo instead.
+    bool GetFTGlyphAdvance(uint16_t aGID, int32_t* aWidth);
+
     void InitMetrics();
 
 protected:
     virtual const Metrics& GetHorizontalMetrics() override;
 
     uint32_t mSpaceGlyph;
     Metrics mMetrics;
     bool    mEmbolden;
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -696,17 +696,40 @@ MessageChannel::Clear()
     //
     // In practice, mListener owns the channel, so the channel gets deleted
     // before mListener.  But just to be safe, mListener is a weak pointer.
 
 #if !defined(ANDROID)
     if (!Unsound_IsClosed()) {
         CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("ProtocolName"),
                                            nsDependentCString(mName));
-        MOZ_CRASH("MessageChannel destroyed without being closed");
+        switch (mChannelState) {
+            case ChannelOpening:
+                MOZ_CRASH("MessageChannel destroyed without being closed " \
+                          "(mChannelState == ChannelOpening).");
+                break;
+            case ChannelConnected:
+                MOZ_CRASH("MessageChannel destroyed without being closed " \
+                          "(mChannelState == ChannelConnected).");
+                break;
+            case ChannelTimeout:
+                MOZ_CRASH("MessageChannel destroyed without being closed " \
+                          "(mChannelState == ChannelTimeout).");
+                break;
+            case ChannelClosing:
+                MOZ_CRASH("MessageChannel destroyed without being closed " \
+                          "(mChannelState == ChannelClosing).");
+                break;
+            case ChannelError:
+                MOZ_CRASH("MessageChannel destroyed without being closed " \
+                          "(mChannelState == ChannelError).");
+                break;
+            default:
+                MOZ_CRASH("MessageChannel destroyed without being closed.");
+        }
     }
 #endif
 
     if (gParentProcessBlocker == this) {
         gParentProcessBlocker = nullptr;
     }
 
     if (mWorkerLoop) {
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -35,18 +35,16 @@ class Shape;
 // This is equal to JSFunction::class_.  Use it in places where you don't want
 // to #include jsfun.h.
 extern JS_FRIEND_DATA(const js::Class* const) FunctionClassPtr;
 
 } // namespace js
 
 namespace JS {
 
-class AutoIdVector;
-
 /**
  * The answer to a successful query as to whether an object is an Array per
  * ES6's internal |IsArray| operation (as exposed by |Array.isArray|).
  */
 enum class IsArrayAnswer
 {
     Array,
     NotArray,
--- a/js/public/GCVector.h
+++ b/js/public/GCVector.h
@@ -94,24 +94,24 @@ class GCVector
     template<typename U> void
     infallibleAppend(const U* aBegin, const U* aEnd) {
         return vector.infallibleAppend(aBegin, aEnd);
     }
     template<typename U> void infallibleAppend(const U* aBegin, size_t aLength) {
         return vector.infallibleAppend(aBegin, aLength);
     }
 
-    template<typename U, size_t O, class BP>
-    MOZ_MUST_USE bool appendAll(const mozilla::Vector<U, O, BP>& aU) { return vector.appendAll(aU); }
-    template<typename U, size_t O, class BP>
-    MOZ_MUST_USE bool appendAll(const GCVector<U, O, BP>& aU) {
-        return vector.append(aU.begin(), aU.length());
+    template<typename U>
+    MOZ_MUST_USE bool appendAll(const U& aU) {
+        return vector.append(aU.begin(), aU.end());
     }
 
-    MOZ_MUST_USE bool appendN(const T& val, size_t count) { return vector.appendN(val, count); }
+    MOZ_MUST_USE bool appendN(const T& val, size_t count) {
+        return vector.appendN(val, count);
+    }
 
     template<typename U>
     MOZ_MUST_USE bool append(const U* aBegin, const U* aEnd) {
         return vector.append(aBegin, aEnd);
     }
     template<typename U>
     MOZ_MUST_USE bool append(const U* aBegin, size_t aLength) {
         return vector.append(aBegin, aLength);
@@ -214,20 +214,18 @@ class MutableWrappedPtrOperations<JS::GC
     void clear() { vec().clear(); }
     void clearAndFree() { vec().clearAndFree(); }
     template<typename U>
     MOZ_MUST_USE bool append(U&& aU) { return vec().append(mozilla::Forward<U>(aU)); }
     template<typename... Args>
     MOZ_MUST_USE bool emplaceBack(Args&&... aArgs) {
         return vec().emplaceBack(mozilla::Forward<Args...>(aArgs...));
     }
-    template<typename U, size_t O, class BP>
-    MOZ_MUST_USE bool appendAll(const mozilla::Vector<U, O, BP>& aU) { return vec().appendAll(aU); }
-    template<typename U, size_t O, class BP>
-    MOZ_MUST_USE bool appendAll(const JS::GCVector<U, O, BP>& aU) { return vec().appendAll(aU); }
+    template<typename U>
+    MOZ_MUST_USE bool appendAll(const U& aU) { return vec().appendAll(aU); }
     MOZ_MUST_USE bool appendN(const T& aT, size_t aN) { return vec().appendN(aT, aN); }
     template<typename U>
     MOZ_MUST_USE bool append(const U* aBegin, const U* aEnd) {
         return vec().append(aBegin, aEnd);
     }
     template<typename U>
     MOZ_MUST_USE bool append(const U* aBegin, size_t aLength) {
         return vec().append(aBegin, aLength);
@@ -248,9 +246,22 @@ class MutableWrappedPtrOperations<JS::GC
         return vec().insert(aP, mozilla::Forward<U>(aVal));
     }
     void erase(T* aT) { vec().erase(aT); }
     void erase(T* aBegin, T* aEnd) { vec().erase(aBegin, aEnd); }
 };
 
 } // namespace js
 
+namespace JS {
+
+// An automatically rooted vector for stack use.
+template <typename T>
+class AutoVector : public Rooted<GCVector<T, 8>> {
+    using Vec = GCVector<T, 8>;
+    using Base = Rooted<Vec>;
+  public:
+    explicit AutoVector(JSContext* cx) : Base(cx, Vec(cx)) {}
+};
+
+} // namespace JS
+
 #endif // js_GCVector_h
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -890,29 +890,22 @@ class JS_PUBLIC_API(AutoGCRooter)
     ptrdiff_t tag_;
 
     enum {
         VALARRAY =     -2, /* js::AutoValueArray */
         PARSER =       -3, /* js::frontend::Parser */
 #if defined(JS_BUILD_BINAST)
         BINPARSER =    -4, /* js::frontend::BinSource */
 #endif // defined(JS_BUILD_BINAST)
-        VALVECTOR =   -10, /* js::AutoValueVector */
-        IDVECTOR =    -11, /* js::AutoIdVector */
-        OBJVECTOR =   -14, /* js::AutoObjectVector */
         IONMASM =     -19, /* js::jit::MacroAssembler */
         WRAPVECTOR =  -20, /* js::AutoWrapperVector */
         WRAPPER =     -21, /* js::AutoWrapperRooter */
         CUSTOM =      -26  /* js::CustomAutoRooter */
     };
 
-    static ptrdiff_t GetTag(const Value& value) { return VALVECTOR; }
-    static ptrdiff_t GetTag(const jsid& id) { return IDVECTOR; }
-    static ptrdiff_t GetTag(JSObject* obj) { return OBJVECTOR; }
-
   private:
     AutoGCRooter ** const stackTop;
 
     /* No copy or assignment semantics. */
     AutoGCRooter(AutoGCRooter& ida) = delete;
     void operator=(AutoGCRooter& ida) = delete;
 };
 
--- a/js/rust/build.rs
+++ b/js/rust/build.rs
@@ -470,16 +470,17 @@ const WHITELIST_FUNCTIONS: &'static [&'s
 /// These are types which are too tricky for bindgen to handle, and/or use C++
 /// features that don't have an equivalent in rust, such as partial template
 /// specialization.
 const OPAQUE_TYPES: &'static [&'static str] = &[
     "JS::ReadOnlyCompileOptions",
     "mozilla::BufferList",
     "mozilla::UniquePtr.*",
     "JS::Rooted<JS::Auto.*Vector.*>",
+    "JS::Auto.*Vector"
 ];
 
 /// Types for which we should NEVER generate bindings, even if it is used within
 /// a type or function signature that we are generating bindings for.
 const BLACKLIST_TYPES: &'static [&'static str] = &[
     // We provide our own definition because we need to express trait bounds in
     // the definition of the struct to make our Drop implementation correct.
     "JS::Heap",
--- a/js/src/NamespaceImports.h
+++ b/js/src/NamespaceImports.h
@@ -25,19 +25,19 @@ namespace JS {
 class Latin1Chars;
 class Latin1CharsZ;
 class ConstTwoByteChars;
 class TwoByteChars;
 class TwoByteCharsZ;
 class UTF8Chars;
 class UTF8CharsZ;
 
-class AutoValueVector;
-class AutoIdVector;
-class AutoObjectVector;
+using AutoValueVector = AutoVector<Value>;
+using AutoIdVector = AutoVector<jsid>;
+using AutoObjectVector = AutoVector<JSObject*>;
 
 using ValueVector = JS::GCVector<JS::Value>;
 using IdVector = JS::GCVector<jsid>;
 using ScriptVector = JS::GCVector<JSScript*>;
 
 template<typename K, typename V> class AutoHashMapRooter;
 template<typename T> class AutoHashSetRooter;
 
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -525,16 +525,24 @@ static bool
 WasmIsSupported(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setBoolean(wasm::HasSupport(cx));
     return true;
 }
 
 static bool
+WasmIsSupportedByHardware(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    args.rval().setBoolean(wasm::HasCompilerSupport(cx));
+    return true;
+}
+
+static bool
 WasmDebuggingIsSupported(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setBoolean(wasm::HasSupport(cx) && cx->options().wasmBaseline());
     return true;
 }
 
 static bool
@@ -5370,16 +5378,20 @@ gc::ZealModeHelpText),
 "isAsmJSFunction(fn)",
 "  Returns whether the given value is a nested function in an asm.js module that has been\n"
 "  both compile- and link-time validated."),
 
     JS_FN_HELP("wasmIsSupported", WasmIsSupported, 0, 0,
 "wasmIsSupported()",
 "  Returns a boolean indicating whether WebAssembly is supported on the current device."),
 
+    JS_FN_HELP("wasmIsSupportedByHardware", WasmIsSupportedByHardware, 0, 0,
+"wasmIsSupportedByHardware()",
+"  Returns a boolean indicating whether WebAssembly is supported on the current hardware (regardless of whether we've enabled support)."),
+
     JS_FN_HELP("wasmDebuggingIsSupported", WasmDebuggingIsSupported, 0, 0,
 "wasmDebuggingIsSupported()",
 "  Returns a boolean indicating whether WebAssembly debugging is supported on the current device;\n"
 "  returns false also if WebAssembly is not supported"),
 
     JS_FN_HELP("wasmThreadsSupported", WasmThreadsSupported, 0, 0,
 "wasmThreadsSupported()",
 "  Returns a boolean indicating whether the WebAssembly threads proposal is\n"
--- a/js/src/gc/Allocator.cpp
+++ b/js/src/gc/Allocator.cpp
@@ -178,17 +178,21 @@ js::AllocateString(JSContext* cx, Initia
             ReportOutOfMemory(cx);
         return str;
     }
 
     JSRuntime* rt = cx->runtime();
     if (!rt->gc.checkAllocatorState<allowGC>(cx, kind))
         return nullptr;
 
-    if (cx->nursery().isEnabled() && heap != TenuredHeap && cx->nursery().canAllocateStrings()) {
+    if (cx->nursery().isEnabled() &&
+        heap != TenuredHeap &&
+        cx->nursery().canAllocateStrings() &&
+        cx->zone()->allocNurseryStrings)
+    {
         auto str = static_cast<StringAllocT*>(rt->gc.tryNewNurseryString<allowGC>(cx, size, kind));
         if (str)
             return str;
 
         // Our most common non-jit allocation path is NoGC; thus, if we fail the
         // alloc and cannot GC, we *must* return nullptr here so that the caller
         // will do a CanGC allocation to clear the nursery. Failing to do so will
         // cause all allocations on this path to land in Tenured, and we will not
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -2772,28 +2772,31 @@ js::gc::StoreBuffer::SlotsEdge::trace(Te
     // Beware JSObject::swap exchanging a native object for a non-native one.
     if (!obj->isNative())
         return;
 
     if (IsInsideNursery(obj))
         return;
 
     if (kind() == ElementKind) {
-        int32_t initLen = obj->getDenseInitializedLength();
-        int32_t numShifted = obj->getElementsHeader()->numShiftedElements();
-        int32_t clampedStart = Min(Max(0, start_ - numShifted), initLen);
-        int32_t clampedEnd = Min(Max(0, start_ + count_ - numShifted), initLen);
-        MOZ_ASSERT(clampedStart >= 0);
+        uint32_t initLen = obj->getDenseInitializedLength();
+        uint32_t numShifted = obj->getElementsHeader()->numShiftedElements();
+        uint32_t clampedStart = start_;
+        clampedStart = numShifted < clampedStart ? clampedStart - numShifted : 0;
+        clampedStart = Min(clampedStart, initLen);
+        uint32_t clampedEnd = start_ + count_;
+        clampedEnd = numShifted < clampedEnd ? clampedEnd - numShifted : 0;
+        clampedEnd = Min(clampedEnd, initLen);
         MOZ_ASSERT(clampedStart <= clampedEnd);
         mover.traceSlots(static_cast<HeapSlot*>(obj->getDenseElements() + clampedStart)
                             ->unsafeUnbarrieredForTracing(), clampedEnd - clampedStart);
     } else {
-        int32_t start = Min(uint32_t(start_), obj->slotSpan());
-        int32_t end = Min(uint32_t(start_) + count_, obj->slotSpan());
-        MOZ_ASSERT(end >= start);
+        uint32_t start = Min(start_, obj->slotSpan());
+        uint32_t end = Min(start_ + count_, obj->slotSpan());
+        MOZ_ASSERT(start <= end);
         mover.traceObjectSlots(obj, start, end - start);
     }
 }
 
 static inline void
 TraceWholeCell(TenuringTracer& mover, JSObject* object)
 {
     mover.traceObject(object);
@@ -3195,16 +3198,17 @@ js::TenuringTracer::insertIntoStringFixu
 JSString*
 js::TenuringTracer::moveToTenured(JSString* src)
 {
     MOZ_ASSERT(IsInsideNursery(src));
     MOZ_ASSERT(!src->zone()->usedByHelperThread());
 
     AllocKind dstKind = src->getAllocKind();
     Zone* zone = src->zone();
+    zone->tenuredStrings++;
 
     TenuredCell* t = zone->arenas.allocateFromFreeList(dstKind, Arena::thingSize(dstKind));
     if (!t) {
         AutoEnterOOMUnsafeRegion oomUnsafe;
         t = runtime()->gc.refillFreeListInGC(zone, dstKind);
         if (!t)
             oomUnsafe.crash(ChunkSize, "Failed to allocate string while tenuring.");
     }
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -127,19 +127,19 @@ js::Nursery::Nursery(JSRuntime* rt)
   , reportTenurings_(0)
   , minorGCTriggerReason_(JS::gcreason::NO_REASON)
   , minorGcCount_(0)
   , freeMallocedBuffersTask(nullptr)
 #ifdef JS_GC_ZEAL
   , lastCanary_(nullptr)
 #endif
 {
-    const char* env = getenv("MOZ_ENABLE_NURSERY_STRINGS");
+    const char* env = getenv("MOZ_NURSERY_STRINGS");
     if (env && *env)
-        canAllocateStrings_ = true;
+        canAllocateStrings_ = (*env == '1');
 }
 
 bool
 js::Nursery::init(uint32_t maxNurseryBytes, AutoLockGCBgAlloc& lock)
 {
     if (!mallocedBuffers.init())
         return false;
 
@@ -733,31 +733,50 @@ js::Nursery::collect(JS::gcreason::Reaso
     // If we are promoting the nursery, or exhausted the store buffer with
     // pointers to nursery things, which will force a collection well before
     // the nursery is full, look for object groups that are getting promoted
     // excessively and try to pretenure them.
     startProfile(ProfileKey::Pretenure);
     bool validPromotionRate;
     const float promotionRate = calcPromotionRate(&validPromotionRate);
     uint32_t pretenureCount = 0;
-    if (validPromotionRate) {
-        if (promotionRate > 0.8 || IsFullStoreBufferReason(reason)) {
-            JSContext* cx = TlsContext.get();
-            for (auto& entry : tenureCounts.entries) {
-                if (entry.count >= 3000) {
-                    ObjectGroup* group = entry.group;
-                    if (group->canPreTenure() && group->zone()->group()->canEnterWithoutYielding(cx)) {
-                        AutoCompartment ac(cx, group);
-                        group->setShouldPreTenure(cx);
-                        pretenureCount++;
-                    }
+    bool shouldPretenure = (validPromotionRate && promotionRate > 0.6) ||
+        IsFullStoreBufferReason(reason);
+
+    if (shouldPretenure) {
+        JSContext* cx = TlsContext.get();
+        for (auto& entry : tenureCounts.entries) {
+            if (entry.count >= 3000) {
+                ObjectGroup* group = entry.group;
+                if (group->canPreTenure() && group->zone()->group()->canEnterWithoutYielding(cx)) {
+                    AutoCompartment ac(cx, group);
+                    group->setShouldPreTenure(cx);
+                    pretenureCount++;
                 }
             }
         }
     }
+    for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
+        if (shouldPretenure && zone->allocNurseryStrings && zone->tenuredStrings >= 30 * 1000) {
+            JSRuntime::AutoProhibitActiveContextChange apacc(rt);
+            CancelOffThreadIonCompile(zone);
+            bool preserving = zone->isPreservingCode();
+            zone->setPreservingCode(false);
+            zone->discardJitCode(rt->defaultFreeOp());
+            zone->setPreservingCode(preserving);
+            for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) {
+                if (jit::JitCompartment* jitComp = c->jitCompartment()) {
+                    jitComp->discardStubs();
+                    jitComp->stringsCanBeInNursery = false;
+                }
+            }
+            zone->allocNurseryStrings = false;
+        }
+        zone->tenuredStrings = 0;
+    }
     endProfile(ProfileKey::Pretenure);
 
     // We ignore gcMaxBytes when allocating for minor collection. However, if we
     // overflowed, we disable the nursery. The next time we allocate, we'll fail
     // because gcBytes >= gcMaxBytes.
     if (rt->gc.usage.gcBytes() >= rt->gc.tunables.gcMaxBytes())
         disable();
     // Disable the nursery if the user changed the configuration setting.  The
@@ -1081,17 +1100,17 @@ js::Nursery::setStartPosition()
 {
     currentStartChunk_ = currentChunk_;
     currentStartPosition_ = position();
 }
 
 void
 js::Nursery::maybeResizeNursery(JS::gcreason::Reason reason)
 {
-    static const double GrowThreshold   = 0.05;
+    static const double GrowThreshold   = 0.03;
     static const double ShrinkThreshold = 0.01;
     unsigned newMaxNurseryChunks;
 
     // Shrink the nursery to its minimum size of we ran out of memory or
     // received a memory pressure event.
     if (gc::IsOOMReason(reason)) {
         minimizeAllocableSpace();
         return;
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -174,23 +174,23 @@ AutoGCRooter::trace(JSTracer* trc)
          * in RemapAllWrappersForObject; see comment there.
          */
         TraceManuallyBarrieredEdge(trc, &static_cast<AutoWrapperRooter*>(this)->value.get(),
                                    "JS::AutoWrapperRooter.value");
         return;
       }
 
       case WRAPVECTOR: {
-        AutoWrapperVector::VectorImpl& vector = static_cast<AutoWrapperVector*>(this)->vector;
+        auto vector = static_cast<AutoWrapperVector*>(this);
         /*
          * We need to use TraceManuallyBarrieredEdge here because we trace
          * wrapper roots in every slice. This is because of some rule-breaking
          * in RemapAllWrappersForObject; see comment there.
          */
-        for (WrapperValue* p = vector.begin(); p < vector.end(); p++)
+        for (WrapperValue* p = vector->begin(); p < vector->end(); p++)
             TraceManuallyBarrieredEdge(trc, &p->get(), "js::AutoWrapperVector.vector");
         return;
       }
 
       case CUSTOM:
         static_cast<JS::CustomAutoRooter*>(this)->trace(trc);
         return;
     }
--- a/js/src/gc/StoreBuffer.h
+++ b/js/src/gc/StoreBuffer.h
@@ -271,27 +271,27 @@ class StoreBuffer
 
     struct SlotsEdge
     {
         // These definitions must match those in HeapSlot::Kind.
         const static int SlotKind = 0;
         const static int ElementKind = 1;
 
         uintptr_t objectAndKind_; // NativeObject* | Kind
-        int32_t start_;
-        int32_t count_;
+        uint32_t start_;
+        uint32_t count_;
 
         SlotsEdge() : objectAndKind_(0), start_(0), count_(0) {}
-        SlotsEdge(NativeObject* object, int kind, int32_t start, int32_t count)
+        SlotsEdge(NativeObject* object, int kind, uint32_t start, uint32_t count)
           : objectAndKind_(uintptr_t(object) | kind), start_(start), count_(count)
         {
             MOZ_ASSERT((uintptr_t(object) & 1) == 0);
             MOZ_ASSERT(kind <= 1);
-            MOZ_ASSERT(start >= 0);
             MOZ_ASSERT(count > 0);
+            MOZ_ASSERT(start + count > start);
         }
 
         NativeObject* object() const { return reinterpret_cast<NativeObject*>(objectAndKind_ & ~1); }
         int kind() const { return (int)(objectAndKind_ & 1); }
 
         bool operator==(const SlotsEdge& other) const {
             return objectAndKind_ == other.objectAndKind_ &&
                    start_ == other.start_ &&
@@ -308,30 +308,32 @@ class StoreBuffer
             if (objectAndKind_ != other.objectAndKind_)
                 return false;
 
             // Widen our range by one on each side so that we consider
             // adjacent-but-not-actually-overlapping ranges as overlapping. This
             // is particularly useful for coalescing a series of increasing or
             // decreasing single index writes 0, 1, 2, ..., N into a SlotsEdge
             // range of elements [0, N].
-            auto end = start_ + count_ + 1;
-            auto start = start_ - 1;
+            uint32_t end = start_ + count_ + 1;
+            uint32_t start = start_ > 0 ? start_ - 1 : 0;
+            MOZ_ASSERT(start < end);
 
-            auto otherEnd = other.start_ + other.count_;
+            uint32_t otherEnd = other.start_ + other.count_;
+            MOZ_ASSERT(other.start_ <= otherEnd);
             return (start <= other.start_ && other.start_ <= end) ||
                    (start <= otherEnd && otherEnd <= end);
         }
 
         // Destructively make this SlotsEdge range the union of the other
         // SlotsEdge range and this one. A precondition is that the ranges must
         // overlap.
         void merge(const SlotsEdge& other) {
             MOZ_ASSERT(overlaps(other));
-            auto end = Max(start_ + count_, other.start_ + other.count_);
+            uint32_t end = Max(start_ + count_, other.start_ + other.count_);
             start_ = Min(start_, other.start_);
             count_ = end - start_;
         }
 
         bool maybeInRememberedSet(const Nursery& n) const {
             return !IsInsideNursery(reinterpret_cast<Cell*>(object()));
         }
 
@@ -411,17 +413,17 @@ class StoreBuffer
 
     bool cancelIonCompilations() const { return cancelIonCompilations_; }
 
     /* Insert a single edge into the buffer/remembered set. */
     void putValue(JS::Value* vp) { put(bufferVal, ValueEdge(vp)); }
     void unputValue(JS::Value* vp) { unput(bufferVal, ValueEdge(vp)); }
     void putCell(Cell** cellp) { put(bufferCell, CellPtrEdge(cellp)); }
     void unputCell(Cell** cellp) { unput(bufferCell, CellPtrEdge(cellp)); }
-    void putSlot(NativeObject* obj, int kind, int32_t start, int32_t count) {
+    void putSlot(NativeObject* obj, int kind, uint32_t start, uint32_t count) {
         SlotsEdge edge(obj, kind, start, count);
         if (bufferSlot.last_.overlaps(edge))
             bufferSlot.last_.merge(edge);
         else
             put(bufferSlot, edge);
     }
     inline void putWholeCell(Cell* cell);
 
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -41,16 +41,18 @@ JS::Zone::Zone(JSRuntime* rt, ZoneGroup*
     regExps(this),
     markedAtoms_(group),
     atomCache_(group),
     externalStringCache_(group),
     functionToStringCache_(group),
     usage(&rt->gc.usage),
     threshold(),
     gcDelayBytes(0),
+    tenuredStrings(group, 0),
+    allocNurseryStrings(group, true),
     propertyTree_(group, this),
     baseShapes_(group, this),
     initialShapes_(group, this),
     nurseryShapes_(group),
     data(group, nullptr),
     isSystem(group, false),
 #ifdef DEBUG
     gcLastSweepGroupIndex(group, 0),
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -477,16 +477,19 @@ struct Zone : public JS::shadow::Zone,
 
     // Thresholds used to trigger GC.
     js::gc::ZoneHeapThreshold threshold;
 
     // Amount of data to allocate before triggering a new incremental slice for
     // the current GC.
     js::UnprotectedData<size_t> gcDelayBytes;
 
+    js::ZoneGroupData<uint32_t> tenuredStrings;
+    js::ZoneGroupData<bool> allocNurseryStrings;
+
   private:
     // Shared Shape property tree.
     js::ZoneGroupData<js::PropertyTree> propertyTree_;
   public:
     js::PropertyTree& propertyTree() { return propertyTree_.ref(); }
 
   private:
     // Set of all unowned base shapes in the Zone.
--- a/js/src/jit/BaselineCacheIRCompiler.cpp
+++ b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -278,37 +278,32 @@ BaselineCacheIRCompiler::emitGuardCompar
     reader.stubOffset(); // Read global wrapper.
     AutoScratchRegister scratch(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
     Address addr(stubAddress(reader.stubOffset()));
-    masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
-    masm.loadPtr(Address(scratch, ObjectGroup::offsetOfCompartment()), scratch);
-    masm.branchPtr(Assembler::NotEqual, addr, scratch, failure->label());
+    masm.branchTestObjCompartment(Assembler::NotEqual, obj, addr, scratch, failure->label());
     return true;
 }
 
 bool
 BaselineCacheIRCompiler::emitGuardAnyClass()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     AutoScratchRegister scratch(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
     Address testAddr(stubAddress(reader.stubOffset()));
-
-    masm.loadObjGroup(obj, scratch);
-    masm.loadPtr(Address(scratch, ObjectGroup::offsetOfClasp()), scratch);
-    masm.branchPtr(Assembler::NotEqual, testAddr, scratch, failure->label());
+    masm.branchTestObjClass(Assembler::NotEqual, obj, scratch, testAddr, failure->label());
     return true;
 }
 
 bool
 BaselineCacheIRCompiler::emitGuardHasProxyHandler()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     AutoScratchRegister scratch(allocator, masm);
@@ -523,19 +518,17 @@ BaselineCacheIRCompiler::emitMegamorphic
     AutoScratchRegister scratch2(allocator, masm);
     AutoScratchRegister scratch3(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
     // The object must be Native.
-    masm.loadObjClass(obj, scratch3);
-    masm.branchTest32(Assembler::NonZero, Address(scratch3, Class::offsetOfFlags()),
-                      Imm32(Class::NON_NATIVE), failure->label());
+    masm.branchIfNonNativeObj(obj, scratch3, failure->label());
 
     masm.Push(UndefinedValue());
     masm.moveStackPtrTo(scratch3.get());
 
     LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs());
     volatileRegs.takeUnchecked(scratch1);
     volatileRegs.takeUnchecked(scratch2);
     volatileRegs.takeUnchecked(scratch3);
@@ -1171,37 +1164,32 @@ BaselineCacheIRCompiler::emitAddAndStore
     if (!callTypeUpdateIC(obj, val, scratch1, saveRegs))
         return false;
 
     if (changeGroup) {
         // Changing object's group from a partially to fully initialized group,
         // per the acquired properties analysis. Only change the group if the
         // old group still has a newScript. This only applies to PlainObjects.
         Label noGroupChange;
-        masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch1);
-        masm.branchPtr(Assembler::Equal,
-                       Address(scratch1, ObjectGroup::offsetOfAddendum()),
-                       ImmWord(0),
-                       &noGroupChange);
-
-        // Reload the new group from the cache.
+        masm.branchIfObjGroupHasNoAddendum(obj, scratch1, &noGroupChange);
+
+        // Update the object's group.
         masm.loadPtr(newGroupAddr, scratch1);
-
-        Address groupAddr(obj, JSObject::offsetOfGroup());
-        EmitPreBarrier(masm, groupAddr, MIRType::ObjectGroup);
-        masm.storePtr(scratch1, groupAddr);
+        masm.storeObjGroup(scratch1, obj, [](MacroAssembler& masm, const Address& addr) {
+            EmitPreBarrier(masm, addr, MIRType::ObjectGroup);
+        });
 
         masm.bind(&noGroupChange);
     }
 
     // Update the object's shape.
-    Address shapeAddr(obj, ShapedObject::offsetOfShape());
     masm.loadPtr(newShapeAddr, scratch1);
-    EmitPreBarrier(masm, shapeAddr, MIRType::Shape);
-    masm.storePtr(scratch1, shapeAddr);
+    masm.storeObjShape(scratch1, obj, [](MacroAssembler& masm, const Address& addr) {
+        EmitPreBarrier(masm, addr, MIRType::Shape);
+    });
 
     // Perform the store. No pre-barrier required since this is a new
     // initialization.
     masm.load32(offsetAddr, scratch1);
     if (op == CacheOp::AddAndStoreFixedSlot) {
         BaseIndex slot(obj, scratch1, TimesOne);
         masm.storeValue(val, slot);
     } else {
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -421,21 +421,20 @@ bool
 ICTypeUpdate_ObjectGroup::Compiler::generateStubCode(MacroAssembler& masm)
 {
     MOZ_ASSERT(engine_ == Engine::Baseline);
 
     Label failure;
     masm.branchTestObject(Assembler::NotEqual, R0, &failure);
 
     // Guard on the object's ObjectGroup.
-    Register obj = masm.extractObject(R0, R1.scratchReg());
-    masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), R1.scratchReg());
-
+    Register scratch = R1.scratchReg();
+    Register obj = masm.extractObject(R0, scratch);
     Address expectedGroup(ICStubReg, ICTypeUpdate_ObjectGroup::offsetOfGroup());
-    masm.branchPtr(Assembler::NotEqual, expectedGroup, R1.scratchReg(), &failure);
+    masm.branchTestObjGroup(Assembler::NotEqual, obj, expectedGroup, scratch, &failure);
 
     // Group matches, load true into R1.scratchReg() and return.
     masm.mov(ImmWord(1), R1.scratchReg());
     EmitReturnFromIC(masm);
 
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
@@ -770,19 +769,17 @@ void
 LoadTypedThingLength(MacroAssembler& masm, TypedThingLayout layout, Register obj, Register result)
 {
     switch (layout) {
       case Layout_TypedArray:
         masm.unboxInt32(Address(obj, TypedArrayObject::lengthOffset()), result);
         break;
       case Layout_OutlineTypedObject:
       case Layout_InlineTypedObject:
-        masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), result);
-        masm.loadPtr(Address(result, ObjectGroup::offsetOfAddendum()), result);
-        masm.unboxInt32(Address(result, ArrayTypeDescr::offsetOfLength()), result);
+        masm.loadTypedObjectLength(obj, result);
         break;
       default:
         MOZ_CRASH();
     }
 }
 
 static void
 SetUpdateStubData(ICCacheIR_Updated* stub, const PropertyTypeCheckInfo* info)
@@ -3427,21 +3424,19 @@ ICCall_ClassHook::Compiler::generateStub
     masm.loadValue(calleeSlot, R1);
     regs.take(R1);
 
     masm.branchTestObject(Assembler::NotEqual, R1, &failure);
 
     // Ensure the callee's class matches the one in this stub.
     Register callee = masm.extractObject(R1, ExtractTemp0);
     Register scratch = regs.takeAny();
-    masm.loadObjClass(callee, scratch);
-    masm.branchPtr(Assembler::NotEqual,
-                   Address(ICStubReg, ICCall_ClassHook::offsetOfClass()),
-                   scratch, &failure);
-
+    masm.branchTestObjClass(Assembler::NotEqual, callee, scratch,
+                            Address(ICStubReg, ICCall_ClassHook::offsetOfClass()),
+                            &failure);
     regs.add(R1);
     regs.takeUnchecked(callee);
 
     // Push a stub frame so that we can perform a non-tail call.
     // Note that this leaves the return address in TailCallReg.
     enterStubFrame(masm, regs.getAny());
 
     regs.add(scratch);
--- a/js/src/jit/CacheIRCompiler.cpp
+++ b/js/src/jit/CacheIRCompiler.cpp
@@ -1467,19 +1467,17 @@ CacheIRCompiler::emitGuardIsNativeObject
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     AutoScratchRegister scratch(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
-    masm.loadObjClass(obj, scratch);
-    masm.branchTest32(Assembler::NonZero, Address(scratch, Class::offsetOfFlags()),
-                      Imm32(Class::NON_NATIVE), failure->label());
+    masm.branchIfNonNativeObj(obj, scratch, failure->label());
     return true;
 }
 
 bool
 CacheIRCompiler::emitGuardIsProxy()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     AutoScratchRegister scratch(allocator, masm);
@@ -2663,19 +2661,17 @@ CacheIRCompiler::emitMegamorphicLoadSlot
 
     AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
     // The object must be Native.
-    masm.loadObjClass(obj, scratch);
-    masm.branchTest32(Assembler::NonZero, Address(scratch, Class::offsetOfFlags()),
-                      Imm32(Class::NON_NATIVE), failure->label());
+    masm.branchIfNonNativeObj(obj, scratch, failure->label());
 
     // idVal will be in vp[0], result will be stored in vp[1].
     masm.reserveStack(sizeof(Value));
     masm.Push(idVal);
     masm.moveStackPtrTo(idVal.scratchReg());
 
     LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs());
     volatileRegs.takeUnchecked(scratch);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -954,18 +954,17 @@ CodeGenerator::visitFunctionDispatch(LFu
         lastLabel = skipTrivialBlocks(mir->getFallback())->lir()->label();
     }
 
     // Compare function pointers, except for the last case.
     for (size_t i = 0; i < casesWithFallback - 1; i++) {
         MOZ_ASSERT(i < mir->numCases());
         LBlock* target = skipTrivialBlocks(mir->getCaseBlock(i))->lir();
         if (ObjectGroup* funcGroup = mir->getCaseObjectGroup(i)) {
-            masm.branchPtr(Assembler::Equal, Address(input, JSObject::offsetOfGroup()),
-                           ImmGCPtr(funcGroup), target->label());
+            masm.branchTestObjGroup(Assembler::Equal, input, funcGroup, target->label());
         } else {
             JSFunction* func = mir->getCase(i);
             masm.branchPtr(Assembler::Equal, input, ImmGCPtr(func), target->label());
         }
     }
 
     // Jump to the last case.
     masm.jump(lastLabel);
@@ -974,17 +973,17 @@ CodeGenerator::visitFunctionDispatch(LFu
 void
 CodeGenerator::visitObjectGroupDispatch(LObjectGroupDispatch* lir)
 {
     MObjectGroupDispatch* mir = lir->mir();
     Register input = ToRegister(lir->input());
     Register temp = ToRegister(lir->temp());
 
     // Load the incoming ObjectGroup in temp.
-    masm.loadPtr(Address(input, JSObject::offsetOfGroup()), temp);
+    masm.loadObjGroupUnsafe(input, temp);
 
     // Compare ObjectGroups.
     MacroAssembler::BranchGCPtr lastBranch;
     LBlock* lastBlock = nullptr;
     InlinePropertyTable* propTable = mir->propTable();
     for (size_t i = 0; i < mir->numCases(); i++) {
         JSFunction* func = mir->getCase(i);
         LBlock* target = skipTrivialBlocks(mir->getCaseBlock(i))->lir();
@@ -2513,18 +2512,17 @@ CodeGenerator::visitRegExpPrototypeOptim
     addOutOfLineCode(ool, ins->mir());
 
     masm.loadJSContext(temp);
     masm.loadPtr(Address(temp, JSContext::offsetOfCompartment()), temp);
     size_t offset = JSCompartment::offsetOfRegExps() +
                     RegExpCompartment::offsetOfOptimizableRegExpPrototypeShape();
     masm.loadPtr(Address(temp, offset), temp);
 
-    masm.loadPtr(Address(object, ShapedObject::offsetOfShape()), output);
-    masm.branchPtr(Assembler::NotEqual, output, temp, ool->entry());
+    masm.branchTestObjShape(Assembler::NotEqual, object, temp, ool->entry());
     masm.move32(Imm32(0x1), output);
 
     masm.bind(ool->rejoin());
 }
 
 void
 CodeGenerator::visitOutOfLineRegExpPrototypeOptimizable(OutOfLineRegExpPrototypeOptimizable* ool)
 {
@@ -2574,18 +2572,17 @@ CodeGenerator::visitRegExpInstanceOptimi
     addOutOfLineCode(ool, ins->mir());
 
     masm.loadJSContext(temp);
     masm.loadPtr(Address(temp, JSContext::offsetOfCompartment()), temp);
     size_t offset = JSCompartment::offsetOfRegExps() +
                     RegExpCompartment::offsetOfOptimizableRegExpInstanceShape();
     masm.loadPtr(Address(temp, offset), temp);
 
-    masm.loadPtr(Address(object, ShapedObject::offsetOfShape()), output);
-    masm.branchPtr(Assembler::NotEqual, output, temp, ool->entry());
+    masm.branchTestObjShape(Assembler::NotEqual, object, temp, ool->entry());
     masm.move32(Imm32(0x1), output);
 
     masm.bind(ool->rejoin());
 }
 
 void
 CodeGenerator::visitOutOfLineRegExpInstanceOptimizable(OutOfLineRegExpInstanceOptimizable* ool)
 {
@@ -3711,21 +3708,18 @@ void
 CodeGenerator::visitMaybeCopyElementsForWrite(LMaybeCopyElementsForWrite* lir)
 {
     Register object = ToRegister(lir->object());
     Register temp = ToRegister(lir->temp());
 
     OutOfLineCode* ool = oolCallVM(CopyElementsForWriteInfo, lir,
                                    ArgList(object), StoreNothing());
 
-    if (lir->mir()->checkNative()) {
-        masm.loadObjClass(object, temp);
-        masm.branchTest32(Assembler::NonZero, Address(temp, Class::offsetOfFlags()),
-                          Imm32(Class::NON_NATIVE), ool->rejoin());
-    }
+    if (lir->mir()->checkNative())
+        masm.branchIfNonNativeObj(object, temp, ool->rejoin());
 
     masm.loadPtr(Address(object, NativeObject::offsetOfElements()), temp);
     masm.branchTest32(Assembler::NonZero,
                       Address(temp, ObjectElements::offsetOfFlags()),
                       Imm32(ObjectElements::COPY_ON_WRITE),
                       ool->entry());
     masm.bind(ool->rejoin());
 }
@@ -4297,27 +4291,18 @@ LoadDOMPrivate(MacroAssembler& masm, Reg
     MOZ_ASSERT(obj != priv);
 
     // Check if it's a proxy.
     Label isProxy, done;
     if (kind == DOMObjectKind::Unknown)
         masm.branchTestObjectIsProxy(true, obj, priv, &isProxy);
 
     if (kind != DOMObjectKind::Proxy) {
-#ifdef DEBUG
         // If it's a native object, the value must be in a fixed slot.
-        Label hasFixedSlots;
-        masm.loadPtr(Address(obj, ShapedObject::offsetOfShape()), priv);
-        masm.branchTest32(Assembler::NonZero,
-                          Address(priv, Shape::offsetOfSlotInfo()),
-                          Imm32(Shape::fixedSlotsMask()),
-                          &hasFixedSlots);
-        masm.assumeUnreachable("Expected a fixed slot");
-        masm.bind(&hasFixedSlots);
-#endif
+        masm.debugAssertObjHasFixedSlots(obj, priv);
         masm.loadPrivate(Address(obj, NativeObject::getFixedSlotOffset(0)), priv);
         if (kind == DOMObjectKind::Unknown)
             masm.jump(&done);
     }
 
     if (kind != DOMObjectKind::Native) {
         masm.bind(&isProxy);
 #ifdef DEBUG
@@ -4883,20 +4868,20 @@ CodeGenerator::emitApplyGeneric(T* apply
     Register extraStackSpace = ToRegister(apply->getTempStackCounter());
 
     // Holds the function nargs, computed in the invoker or (for
     // ApplyArray) in the argument pusher.
     Register argcreg = ToRegister(apply->getArgc());
 
     // Unless already known, guard that calleereg is actually a function object.
     if (!apply->hasSingleTarget()) {
-        masm.loadObjClass(calleereg, objreg);
-
-        ImmPtr ptr = ImmPtr(&JSFunction::class_);
-        bailoutCmpPtr(Assembler::NotEqual, objreg, ptr, apply->snapshot());
+        Label bail;
+        masm.branchTestObjClass(Assembler::NotEqual, calleereg, objreg, &JSFunction::class_,
+                                &bail);
+        bailoutFrom(&bail, apply->snapshot());
     }
 
     // Copy the arguments of the current function.
     //
     // In the case of ApplyArray, also compute argc: the argc register
     // and the elements register are the same; argc must not be
     // referenced before the call to emitPushArguments() and elements
     // must not be referenced after it returns.
@@ -6373,48 +6358,17 @@ CodeGenerator::addSimdTemplateToReadBarr
 void
 CodeGenerator::visitSimdUnbox(LSimdUnbox* lir)
 {
     Register object = ToRegister(lir->input());
     FloatRegister simd = ToFloatRegister(lir->output());
     Register temp = ToRegister(lir->temp());
     Label bail;
 
-    // obj->group()
-    masm.loadPtr(Address(object, JSObject::offsetOfGroup()), temp);
-
-    // Guard that the object has the same representation as the one produced for
-    // SIMD value-type.
-    Address clasp(temp, ObjectGroup::offsetOfClasp());
-    static_assert(!SimdTypeDescr::Opaque, "SIMD objects are transparent");
-    masm.branchPtr(Assembler::NotEqual, clasp, ImmPtr(&InlineTransparentTypedObject::class_),
-                   &bail);
-
-    // obj->type()->typeDescr()
-    // The previous class pointer comparison implies that the addendumKind is
-    // Addendum_TypeDescr.
-    masm.loadPtr(Address(temp, ObjectGroup::offsetOfAddendum()), temp);
-
-    // Check for the /Kind/ reserved slot of the TypeDescr.  This is an Int32
-    // Value which is equivalent to the object class check.
-    static_assert(JS_DESCR_SLOT_KIND < NativeObject::MAX_FIXED_SLOTS, "Load from fixed slots");
-    Address typeDescrKind(temp, NativeObject::getFixedSlotOffset(JS_DESCR_SLOT_KIND));
-    masm.assertTestInt32(Assembler::Equal, typeDescrKind,
-      "MOZ_ASSERT(obj->type()->typeDescr()->getReservedSlot(JS_DESCR_SLOT_KIND).isInt32())");
-    masm.branch32(Assembler::NotEqual, masm.ToPayload(typeDescrKind), Imm32(js::type::Simd), &bail);
-
-    SimdType type = lir->mir()->simdType();
-
-    // Check if the SimdTypeDescr /Type/ match the specialization of this
-    // MSimdUnbox instruction.
-    static_assert(JS_DESCR_SLOT_TYPE < NativeObject::MAX_FIXED_SLOTS, "Load from fixed slots");
-    Address typeDescrType(temp, NativeObject::getFixedSlotOffset(JS_DESCR_SLOT_TYPE));
-    masm.assertTestInt32(Assembler::Equal, typeDescrType,
-      "MOZ_ASSERT(obj->type()->typeDescr()->getReservedSlot(JS_DESCR_SLOT_TYPE).isInt32())");
-    masm.branch32(Assembler::NotEqual, masm.ToPayload(typeDescrType), Imm32(int32_t(type)), &bail);
+    masm.branchIfNotSimdObject(object, temp, lir->mir()->simdType(), &bail);
 
     // Load the value from the data of the InlineTypedObject.
     Address objectData(object, InlineTypedObject::offsetOfDataStart());
     switch (lir->mir()->type()) {
       case MIRType::Int8x16:
       case MIRType::Int16x8:
       case MIRType::Int32x4:
       case MIRType::Bool8x16:
@@ -7090,34 +7044,30 @@ CodeGenerator::visitSetDisjointTypedElem
     masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, js::SetDisjointTypedElements));
 }
 
 void
 CodeGenerator::visitTypedObjectDescr(LTypedObjectDescr* lir)
 {
     Register obj = ToRegister(lir->object());
     Register out = ToRegister(lir->output());
-
-    masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), out);
-    masm.loadPtr(Address(out, ObjectGroup::offsetOfAddendum()), out);
+    masm.loadTypedObjectDescr(obj, out);
 }
 
 void
 CodeGenerator::visitTypedObjectElements(LTypedObjectElements* lir)
 {
     Register obj = ToRegister(lir->object());
     Register out = ToRegister(lir->output());
 
     if (lir->mir()->definitelyOutline()) {
         masm.loadPtr(Address(obj, OutlineTypedObject::offsetOfData()), out);
     } else {
         Label inlineObject, done;
-        masm.loadObjClass(obj, out);
-        masm.branchPtr(Assembler::Equal, out, ImmPtr(&InlineOpaqueTypedObject::class_), &inlineObject);
-        masm.branchPtr(Assembler::Equal, out, ImmPtr(&InlineTransparentTypedObject::class_), &inlineObject);
+        masm.branchIfInlineTypedObject(obj, out, &inlineObject);
 
         masm.loadPtr(Address(obj, OutlineTypedObject::offsetOfData()), out);
         masm.jump(&done);
 
         masm.bind(&inlineObject);
         masm.computeEffectiveAddress(Address(obj, InlineTypedObject::offsetOfDataStart()), out);
         masm.bind(&done);
     }
@@ -7130,19 +7080,17 @@ CodeGenerator::visitSetTypedObjectOffset
     Register offset = ToRegister(lir->offset());
     Register temp0 = ToRegister(lir->temp0());
     Register temp1 = ToRegister(lir->temp1());
 
     // Compute the base pointer for the typed object's owner.
     masm.loadPtr(Address(object, OutlineTypedObject::offsetOfOwner()), temp0);
 
     Label inlineObject, done;
-    masm.loadObjClass(temp0, temp1);
-    masm.branchPtr(Assembler::Equal, temp1, ImmPtr(&InlineOpaqueTypedObject::class_), &inlineObject);
-    masm.branchPtr(Assembler::Equal, temp1, ImmPtr(&InlineTransparentTypedObject::class_), &inlineObject);
+    masm.branchIfInlineTypedObject(temp0, temp1, &inlineObject);
 
     masm.loadPrivate(Address(temp0, ArrayBufferObject::offsetOfDataSlot()), temp0);
     masm.jump(&done);
 
     masm.bind(&inlineObject);
     masm.addPtr(ImmWord(InlineTypedObject::offsetOfDataStart()), temp0);
 
     masm.bind(&done);
@@ -9318,18 +9266,17 @@ static const VMFunction ConvertUnboxedPl
 void
 CodeGenerator::visitConvertUnboxedObjectToNative(LConvertUnboxedObjectToNative* lir)
 {
     Register object = ToRegister(lir->getOperand(0));
 
     OutOfLineCode* ool = oolCallVM(ConvertUnboxedPlainObjectToNativeInfo,
                                    lir, ArgList(object), StoreNothing());
 
-    masm.branchPtr(Assembler::Equal, Address(object, JSObject::offsetOfGroup()),
-                   ImmGCPtr(lir->mir()->group()), ool->entry());
+    masm.branchTestObjGroup(Assembler::Equal, object, lir->mir()->group(), ool->entry());
     masm.bind(ool->rejoin());
 }
 
 typedef bool (*ArrayPopShiftFn)(JSContext*, HandleObject, MutableHandleValue);
 static const VMFunction ArrayPopDenseInfo =
     FunctionInfo<ArrayPopShiftFn>(jit::ArrayPopDense, "ArrayPopDense");
 static const VMFunction ArrayShiftDenseInfo =
     FunctionInfo<ArrayPopShiftFn>(jit::ArrayShiftDense, "ArrayShiftDense");
@@ -9516,18 +9463,17 @@ CodeGenerator::visitArraySlice(LArraySli
     Register temp2 = ToRegister(lir->temp2());
 
     Label call, fail;
 
     // Try to allocate an object.
     masm.createGCObject(temp1, temp2, lir->mir()->templateObj(), lir->mir()->initialHeap(), &fail);
 
     // Fixup the group of the result in case it doesn't match the template object.
-    masm.loadPtr(Address(object, JSObject::offsetOfGroup()), temp2);
-    masm.storePtr(temp2, Address(temp1, JSObject::offsetOfGroup()));
+    masm.copyObjGroupNoPreBarrier(object, temp1, temp2);
 
     masm.jump(&call);
     {
         masm.bind(&fail);
         masm.movePtr(ImmPtr(nullptr), temp1);
     }
     masm.bind(&call);
 
@@ -12135,17 +12081,17 @@ class OutOfLineIsCallable : public OutOf
     }
 };
 
 template <CodeGenerator::CallableOrConstructor mode>
 void
 CodeGenerator::emitIsCallableOrConstructor(Register object, Register output, Label* failure)
 {
     Label notFunction, hasCOps, done;
-    masm.loadObjClass(object, output);
+    masm.loadObjClassUnsafe(object, output);
 
     // Just skim proxies off. Their notion of isCallable()/isConstructor() is
     // more complicated.
     masm.branchTestClassIsProxy(true, output, failure);
 
     // An object is callable iff:
     //   is<JSFunction>() || (getClass()->cOps && getClass()->cOps->call).
     // An object is constructor iff:
@@ -12314,17 +12260,17 @@ CodeGenerator::visitOutOfLineIsConstruct
 
 typedef bool (*IsArrayFn)(JSContext*, HandleObject, bool*);
 static const VMFunction IsArrayInfo = FunctionInfo<IsArrayFn>(JS::IsArray, "IsArray");
 
 static void
 EmitObjectIsArray(MacroAssembler& masm, OutOfLineCode* ool, Register obj, Register output,
                   Label* notArray = nullptr)
 {
-    masm.loadObjClass(obj, output);
+    masm.loadObjClassUnsafe(obj, output);
 
     Label isArray;
     masm.branchPtr(Assembler::Equal, output, ImmPtr(&ArrayObject::class_), &isArray);
 
     // Branch to OOL path if it's a proxy.
     masm.branchTestClassIsProxy(true, output, ool->entry());
 
     if (notArray)
@@ -12375,17 +12321,17 @@ CodeGenerator::visitIsTypedArray(LIsType
     Label done;
 
     static_assert(Scalar::Int8 == 0, "Int8 is the first typed array class");
     static_assert((Scalar::Uint8Clamped - Scalar::Int8) == Scalar::MaxTypedArrayViewType - 1,
                   "Uint8Clamped is the last typed array class");
     const Class* firstTypedArrayClass = TypedArrayObject::classForType(Scalar::Int8);
     const Class* lastTypedArrayClass = TypedArrayObject::classForType(Scalar::Uint8Clamped);
 
-    masm.loadObjClass(object, output);
+    masm.loadObjClassUnsafe(object, output);
     masm.branchPtr(Assembler::Below, output, ImmPtr(firstTypedArrayClass), &notTypedArray);
     masm.branchPtr(Assembler::Above, output, ImmPtr(lastTypedArrayClass), &notTypedArray);
 
     masm.move32(Imm32(1), output);
     masm.jump(&done);
     masm.bind(&notTypedArray);
     masm.move32(Imm32(0), output);
     masm.bind(&done);
@@ -12429,17 +12375,17 @@ CodeGenerator::loadJSScriptForBlock(MBas
 }
 
 void
 CodeGenerator::visitHasClass(LHasClass* ins)
 {
     Register lhs = ToRegister(ins->lhs());
     Register output = ToRegister(ins->output());
 
-    masm.loadObjClass(lhs, output);
+    masm.loadObjClassUnsafe(lhs, output);
     masm.cmpPtrSet(Assembler::Equal, output, ImmPtr(ins->mir()->getClass()), output);
 }
 
 typedef JSString* (*ObjectClassToStringFn)(JSContext*, HandleObject);
 static const VMFunction ObjectClassToStringInfo =
     FunctionInfo<ObjectClassToStringFn>(js::ObjectClassToString, "ObjectClassToString");
 
 void
--- a/js/src/jit/CompileWrappers.cpp
+++ b/js/src/jit/CompileWrappers.cpp
@@ -200,17 +200,19 @@ const void*
 CompileZone::addressOfStringNurseryCurrentEnd()
 {
     return zone()->runtimeFromAnyThread()->gc.addressOfStringNurseryCurrentEnd();
 }
 
 bool
 CompileZone::canNurseryAllocateStrings()
 {
-    return nurseryExists() && zone()->group()->nursery().canAllocateStrings();
+    return nurseryExists() &&
+        zone()->group()->nursery().canAllocateStrings() &&
+        zone()->allocNurseryStrings;
 }
 
 bool
 CompileZone::nurseryExists()
 {
     return zone()->group()->nursery().exists();
 }
 
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -2224,16 +2224,22 @@ IsRegExpHoistable(MIRGenerator* mir, MDe
     worklist.clear();
     *hoistable = true;
     return true;
 }
 
 bool
 jit::MakeMRegExpHoistable(MIRGenerator* mir, MIRGraph& graph)
 {
+    // If we are compiling try blocks, regular expressions may be observable
+    // from catch blocks (which Ion does not compile). For now just disable the
+    // pass in this case.
+    if (graph.hasTryBlock())
+        return true;
+
     MDefinitionVector worklist(graph.alloc());
 
     for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); block++) {
         if (mir->shouldCancel("MakeMRegExpHoistable outer loop"))
             return false;
 
         for (MDefinitionIterator iter(*block); iter; iter++) {
             if (!*iter)
--- a/js/src/jit/IonCacheIRCompiler.cpp
+++ b/js/src/jit/IonCacheIRCompiler.cpp
@@ -694,19 +694,18 @@ IonCacheIRCompiler::emitGuardCompartment
     JSCompartment* compartment = compartmentStubField(reader.stubOffset());
 
     AutoScratchRegister scratch(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
-    masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
-    masm.loadPtr(Address(scratch, ObjectGroup::offsetOfCompartment()), scratch);
-    masm.branchPtr(Assembler::NotEqual, scratch, ImmPtr(compartment), failure->label());
+    masm.branchTestObjCompartment(Assembler::NotEqual, obj, compartment, scratch,
+                                  failure->label());
     return true;
 }
 
 bool
 IonCacheIRCompiler::emitGuardAnyClass()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     AutoScratchRegister scratch(allocator, masm);
@@ -930,19 +929,17 @@ IonCacheIRCompiler::emitMegamorphicLoadS
     AutoScratchRegister scratch2(allocator, masm);
     AutoScratchRegister scratch3(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
     // The object must be Native.
-    masm.loadObjClass(obj, scratch3);
-    masm.branchTest32(Assembler::NonZero, Address(scratch3, Class::offsetOfFlags()),
-                      Imm32(Class::NON_NATIVE), failure->label());
+    masm.branchIfNonNativeObj(obj, scratch3, failure->label());
 
     masm.Push(UndefinedValue());
     masm.moveStackPtrTo(scratch3.get());
 
     LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs());
     volatileRegs.takeUnchecked(scratch1);
     volatileRegs.takeUnchecked(scratch2);
     volatileRegs.takeUnchecked(scratch3);
@@ -1647,33 +1644,30 @@ IonCacheIRCompiler::emitAddAndStoreSlotS
         masm.branchIfFalseBool(scratch1, failure->label());
     }
 
     if (changeGroup) {
         // Changing object's group from a partially to fully initialized group,
         // per the acquired properties analysis. Only change the group if the
         // old group still has a newScript. This only applies to PlainObjects.
         Label noGroupChange;
-        masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch1);
-        masm.branchPtr(Assembler::Equal,
-                       Address(scratch1, ObjectGroup::offsetOfAddendum()),
-                       ImmWord(0),
-                       &noGroupChange);
-
-        Address groupAddr(obj, JSObject::offsetOfGroup());
-        EmitPreBarrier(masm, groupAddr, MIRType::ObjectGroup);
-        masm.storePtr(ImmGCPtr(newGroup), groupAddr);
+        masm.branchIfObjGroupHasNoAddendum(obj, scratch1, &noGroupChange);
+
+        // Update the object's group.
+        masm.storeObjGroup(newGroup, obj, [](MacroAssembler& masm, const Address& addr) {
+            EmitPreBarrier(masm, addr, MIRType::ObjectGroup);
+        });
 
         masm.bind(&noGroupChange);
     }
 
     // Update the object's shape.
-    Address shapeAddr(obj, ShapedObject::offsetOfShape());
-    EmitPreBarrier(masm, shapeAddr, MIRType::Shape);
-    masm.storePtr(ImmGCPtr(newShape), shapeAddr);
+    masm.storeObjShape(newShape, obj, [](MacroAssembler& masm, const Address& addr) {
+        EmitPreBarrier(masm, addr, MIRType::Shape);
+    });
 
     // Perform the store. No pre-barrier required since this is a new
     // initialization.
     if (op == CacheOp::AddAndStoreFixedSlot) {
         Address slot(obj, offset);
         masm.storeConstantOrRegister(val, slot);
     } else {
         MOZ_ASSERT(op == CacheOp::AddAndStoreDynamicSlot ||
--- a/js/src/jit/JitCompartment.h
+++ b/js/src/jit/JitCompartment.h
@@ -616,16 +616,21 @@ class JitCompartment
         if (stubs_[StringConcat])
             return true;
         stubs_[StringConcat] = generateStringConcatStub(cx);
         return stubs_[StringConcat];
     }
 
     void sweep(JSCompartment* compartment);
 
+    void discardStubs() {
+        for (ReadBarrieredJitCode& stubRef : stubs_)
+            stubRef = nullptr;
+    }
+
     JitCode* stringConcatStubNoBarrier(uint32_t* requiredBarriersOut) const {
         return getStubNoBarrier(StringConcat, requiredBarriersOut);
     }
 
     JitCode* regExpMatcherStubNoBarrier(uint32_t* requiredBarriersOut) const {
         return getStubNoBarrier(RegExpMatcher, requiredBarriersOut);
     }
 
--- a/js/src/jit/MacroAssembler-inl.h
+++ b/js/src/jit/MacroAssembler-inl.h
@@ -462,17 +462,17 @@ MacroAssembler::branchIfInterpreted(Regi
 }
 
 void
 MacroAssembler::branchIfObjectEmulatesUndefined(Register objReg, Register scratch,
                                                 Label* slowCheck, Label* label)
 {
     // The branches to out-of-line code here implement a conservative version
     // of the JSObject::isWrapper test performed in EmulatesUndefined.
-    loadObjClass(objReg, scratch);
+    loadObjClassUnsafe(objReg, scratch);
 
     branchTestClassIsProxy(true, scratch, slowCheck);
 
     Address flags(scratch, Class::offsetOfFlags());
     branchTest32(Assembler::NonZero, flags, Imm32(JSCLASS_EMULATES_UNDEFINED), label);
 }
 
 void
@@ -487,37 +487,47 @@ MacroAssembler::branchFunctionKind(Condi
     int32_t mask = IMM32_16ADJ(JSFunction::FUNCTION_KIND_MASK);
     int32_t bit = IMM32_16ADJ(kind << JSFunction::FUNCTION_KIND_SHIFT);
     load32(address, scratch);
     and32(Imm32(mask), scratch);
     branch32(cond, scratch, Imm32(bit), label);
 }
 
 void
-MacroAssembler::branchTestObjClass(Condition cond, Register obj, Register scratch, const js::Class* clasp,
-                                   Label* label)
+MacroAssembler::branchTestObjClass(Condition cond, Register obj, Register scratch,
+                                   const js::Class* clasp, Label* label)
 {
-    loadObjGroup(obj, scratch);
+    loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
     branchPtr(cond, Address(scratch, ObjectGroup::offsetOfClasp()), ImmPtr(clasp), label);
 }
 
 void
+MacroAssembler::branchTestObjClass(Condition cond, Register obj, Register scratch,
+                                   const Address& clasp, Label* label)
+{
+    loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
+    loadPtr(Address(scratch, ObjectGroup::offsetOfClasp()), scratch);
+    branchPtr(cond, clasp, scratch, label);
+}
+
+void
 MacroAssembler::branchTestObjShape(Condition cond, Register obj, const Shape* shape, Label* label)
 {
     branchPtr(cond, Address(obj, ShapedObject::offsetOfShape()), ImmGCPtr(shape), label);
 }
 
 void
 MacroAssembler::branchTestObjShape(Condition cond, Register obj, Register shape, Label* label)
 {
     branchPtr(cond, Address(obj, ShapedObject::offsetOfShape()), shape, label);
 }
 
 void
-MacroAssembler::branchTestObjGroup(Condition cond, Register obj, ObjectGroup* group, Label* label)
+MacroAssembler::branchTestObjGroup(Condition cond, Register obj, const ObjectGroup* group,
+                                   Label* label)
 {
     branchPtr(cond, Address(obj, JSObject::offsetOfGroup()), ImmGCPtr(group), label);
 }
 
 void
 MacroAssembler::branchTestObjGroup(Condition cond, Register obj, Register group, Label* label)
 {
     branchPtr(cond, Address(obj, JSObject::offsetOfGroup()), group, label);
@@ -529,17 +539,17 @@ MacroAssembler::branchTestClassIsProxy(b
     branchTest32(proxy ? Assembler::NonZero : Assembler::Zero,
                  Address(clasp, Class::offsetOfFlags()),
                  Imm32(JSCLASS_IS_PROXY), label);
 }
 
 void
 MacroAssembler::branchTestObjectIsProxy(bool proxy, Register object, Register scratch, Label* label)
 {
-    loadObjClass(object, scratch);
+    loadObjClassUnsafe(object, scratch);
     branchTestClassIsProxy(proxy, scratch, label);
 }
 
 void
 MacroAssembler::branchTestProxyHandlerFamily(Condition cond, Register proxy, Register scratch,
                                              const void* handlerp, Label* label)
 {
     Address handlerAddr(proxy, ProxyObject::offsetOfHandler());
@@ -728,16 +738,54 @@ MacroAssembler::addStackPtrTo(T t)
 void
 MacroAssembler::reserveStack(uint32_t amount)
 {
     subFromStackPtr(Imm32(amount));
     adjustFrame(amount);
 }
 #endif // !JS_CODEGEN_ARM64
 
+template <typename EmitPreBarrier>
+void
+MacroAssembler::storeObjGroup(Register group, Register obj, EmitPreBarrier emitPreBarrier)
+{
+    MOZ_ASSERT(group != obj);
+    Address groupAddr(obj, JSObject::offsetOfGroup());
+    emitPreBarrier(*this, groupAddr);
+    storePtr(group, groupAddr);
+}
+
+template <typename EmitPreBarrier>
+void
+MacroAssembler::storeObjGroup(ObjectGroup* group, Register obj, EmitPreBarrier emitPreBarrier)
+{
+    Address groupAddr(obj, JSObject::offsetOfGroup());
+    emitPreBarrier(*this, groupAddr);
+    storePtr(ImmGCPtr(group), groupAddr);
+}
+
+template <typename EmitPreBarrier>
+void
+MacroAssembler::storeObjShape(Register shape, Register obj, EmitPreBarrier emitPreBarrier)
+{
+    MOZ_ASSERT(shape != obj);
+    Address shapeAddr(obj, ShapedObject::offsetOfShape());
+    emitPreBarrier(*this, shapeAddr);
+    storePtr(shape, shapeAddr);
+}
+
+template <typename EmitPreBarrier>
+void
+MacroAssembler::storeObjShape(Shape* shape, Register obj, EmitPreBarrier emitPreBarrier)
+{
+    Address shapeAddr(obj, ShapedObject::offsetOfShape());
+    emitPreBarrier(*this, shapeAddr);
+    storePtr(ImmGCPtr(shape), shapeAddr);
+}
+
 template <typename T>
 void
 MacroAssembler::storeObjectOrNull(Register src, const T& dest)
 {
     Label notNull, done;
     branchTestPtr(Assembler::NonZero, src, src, &notNull);
     storeValue(NullValue(), dest);
     jump(&done);
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -1746,17 +1746,17 @@ MacroAssembler::loadStringIndexValue(Reg
     // Extract the index.
     rshift32(Imm32(JSString::INDEX_VALUE_SHIFT), dest);
 }
 
 void
 MacroAssembler::typeOfObject(Register obj, Register scratch, Label* slow,
                              Label* isObject, Label* isCallable, Label* isUndefined)
 {
-    loadObjClass(obj, scratch);
+    loadObjClassUnsafe(obj, scratch);
 
     // Proxies can emulate undefined and have complex isCallable behavior.
     branchTestClassIsProxy(true, scratch, slow);
 
     // JSFunctions are always callable.
     branchPtr(Assembler::Equal, scratch, ImmPtr(&JSFunction::class_), isCallable);
 
     // Objects that emulate undefined.
@@ -3196,16 +3196,138 @@ MacroAssembler::branchIfNotInterpretedCo
     branchTest32(Assembler::Zero, scratch, Imm32(bits), label);
 
     // Check if the CONSTRUCTOR bit is set.
     bits = IMM32_16ADJ(JSFunction::CONSTRUCTOR);
     branchTest32(Assembler::Zero, scratch, Imm32(bits), label);
 }
 
 void
+MacroAssembler::branchTestObjGroup(Condition cond, Register obj, const Address& group,
+                                   Register scratch, Label* label)
+{
+    // Note: obj and scratch registers may alias.
+
+    loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
+    branchPtr(cond, group, scratch, label);
+}
+
+void
+MacroAssembler::branchTestObjCompartment(Condition cond, Register obj, const Address& compartment,
+                                         Register scratch, Label* label)
+{
+    MOZ_ASSERT(obj != scratch);
+    loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
+    loadPtr(Address(scratch, ObjectGroup::offsetOfCompartment()), scratch);
+    branchPtr(cond, compartment, scratch, label);
+}
+
+void
+MacroAssembler::branchTestObjCompartment(Condition cond, Register obj,
+                                         const JSCompartment* compartment, Register scratch,
+                                         Label* label)
+{
+    MOZ_ASSERT(obj != scratch);
+    loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
+    loadPtr(Address(scratch, ObjectGroup::offsetOfCompartment()), scratch);
+    branchPtr(cond, scratch, ImmPtr(compartment), label);
+}
+
+void
+MacroAssembler::branchIfObjGroupHasNoAddendum(Register obj, Register scratch, Label* label)
+{
+    MOZ_ASSERT(obj != scratch);
+    loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
+    branchPtr(Assembler::Equal,
+              Address(scratch, ObjectGroup::offsetOfAddendum()),
+              ImmWord(0),
+              label);
+}
+
+void
+MacroAssembler::branchIfPretenuredGroup(const ObjectGroup* group, Register scratch, Label* label)
+{
+    movePtr(ImmGCPtr(group), scratch);
+    branchTest32(Assembler::NonZero, Address(scratch, ObjectGroup::offsetOfFlags()),
+                 Imm32(OBJECT_FLAG_PRE_TENURE), label);
+}
+
+void
+MacroAssembler::branchIfNonNativeObj(Register obj, Register scratch, Label* label)
+{
+    loadObjClassUnsafe(obj, scratch);
+    branchTest32(Assembler::NonZero, Address(scratch, Class::offsetOfFlags()),
+                 Imm32(Class::NON_NATIVE), label);
+}
+
+void
+MacroAssembler::branchIfInlineTypedObject(Register obj, Register scratch, Label* label)
+{
+    loadObjClassUnsafe(obj, scratch);
+    branchPtr(Assembler::Equal, scratch, ImmPtr(&InlineOpaqueTypedObject::class_), label);
+    branchPtr(Assembler::Equal, scratch, ImmPtr(&InlineTransparentTypedObject::class_), label);
+}
+
+void
+MacroAssembler::branchIfNotSimdObject(Register obj, Register scratch, SimdType simdType,
+                                      Label* label)
+{
+    loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
+
+    // Guard that the object has the same representation as the one produced for
+    // SIMD value-type.
+    Address clasp(scratch, ObjectGroup::offsetOfClasp());
+    static_assert(!SimdTypeDescr::Opaque, "SIMD objects are transparent");
+    branchPtr(Assembler::NotEqual, clasp, ImmPtr(&InlineTransparentTypedObject::class_),
+              label);
+
+    // obj->type()->typeDescr()
+    // The previous class pointer comparison implies that the addendumKind is
+    // Addendum_TypeDescr.
+    loadPtr(Address(scratch, ObjectGroup::offsetOfAddendum()), scratch);
+
+    // Check for the /Kind/ reserved slot of the TypeDescr.  This is an Int32
+    // Value which is equivalent to the object class check.
+    static_assert(JS_DESCR_SLOT_KIND < NativeObject::MAX_FIXED_SLOTS, "Load from fixed slots");
+    Address typeDescrKind(scratch, NativeObject::getFixedSlotOffset(JS_DESCR_SLOT_KIND));
+    assertTestInt32(Assembler::Equal, typeDescrKind,
+      "MOZ_ASSERT(obj->type()->typeDescr()->getReservedSlot(JS_DESCR_SLOT_KIND).isInt32())");
+    branch32(Assembler::NotEqual, ToPayload(typeDescrKind), Imm32(js::type::Simd), label);
+
+    // Check if the SimdTypeDescr /Type/ matches the specialization of this
+    // MSimdUnbox instruction.
+    static_assert(JS_DESCR_SLOT_TYPE < NativeObject::MAX_FIXED_SLOTS, "Load from fixed slots");
+    Address typeDescrType(scratch, NativeObject::getFixedSlotOffset(JS_DESCR_SLOT_TYPE));
+    assertTestInt32(Assembler::Equal, typeDescrType,
+      "MOZ_ASSERT(obj->type()->typeDescr()->getReservedSlot(JS_DESCR_SLOT_TYPE).isInt32())");
+    branch32(Assembler::NotEqual, ToPayload(typeDescrType), Imm32(int32_t(simdType)), label);
+}
+
+void
+MacroAssembler::copyObjGroupNoPreBarrier(Register sourceObj, Register destObj, Register scratch)
+{
+    loadPtr(Address(sourceObj, JSObject::offsetOfGroup()), scratch);
+    storePtr(scratch, Address(destObj, JSObject::offsetOfGroup()));
+}
+
+void
+MacroAssembler::loadTypedObjectDescr(Register obj, Register dest)
+{
+    loadPtr(Address(obj, JSObject::offsetOfGroup()), dest);
+    loadPtr(Address(dest, ObjectGroup::offsetOfAddendum()), dest);
+}
+
+void
+MacroAssembler::loadTypedObjectLength(Register obj, Register dest)
+{
+    loadTypedObjectDescr(obj, dest);
+    unboxInt32(Address(dest, ArrayTypeDescr::offsetOfLength()), dest);
+}
+
+void
 MacroAssembler::maybeBranchTestType(MIRType type, MDefinition* maybeDef, Register tag, Label* label)
 {
     if (!maybeDef || maybeDef->mightBeType(type)) {
         switch (type) {
           case MIRType::Null:
             branchTestNull(Equal, tag, label);
             break;
           case MIRType::Boolean:
@@ -3566,16 +3688,31 @@ MacroAssembler::debugAssertIsObject(cons
     Label ok;
     branchTestObject(Assembler::Equal, val, &ok);
     assumeUnreachable("Expected an object!");
     bind(&ok);
 #endif
 }
 
 void
+MacroAssembler::debugAssertObjHasFixedSlots(Register obj, Register scratch)
+{
+#ifdef DEBUG
+    Label hasFixedSlots;
+    loadPtr(Address(obj, ShapedObject::offsetOfShape()), scratch);
+    branchTest32(Assembler::NonZero,
+                 Address(scratch, Shape::offsetOfSlotInfo()),
+                 Imm32(Shape::fixedSlotsMask()),
+                 &hasFixedSlots);
+    assumeUnreachable("Expected a fixed slot");
+    bind(&hasFixedSlots);
+#endif
+}
+
+void
 MacroAssembler::spectreMaskIndex(Register index, Register length, Register output)
 {
     MOZ_ASSERT(JitOptions.spectreIndexMasking);
     MOZ_ASSERT(length != output);
     MOZ_ASSERT(index != output);
 
     move32(Imm32(0), output);
     cmp32Move32(Assembler::Below, index, length, index, output);
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -1172,30 +1172,54 @@ class MacroAssembler : public MacroAssem
     inline void branchFunctionKind(Condition cond, JSFunction::FunctionKind kind, Register fun,
                                    Register scratch, Label* label);
 
     void branchIfNotInterpretedConstructor(Register fun, Register scratch, Label* label);
 
     inline void branchIfObjectEmulatesUndefined(Register objReg, Register scratch, Label* slowCheck,
                                                 Label* label);
 
-    inline void branchTestObjClass(Condition cond, Register obj, Register scratch, const js::Class* clasp,
-                                   Label* label);
+    inline void branchTestObjClass(Condition cond, Register obj, Register scratch,
+                                   const js::Class* clasp, Label* label);
+    inline void branchTestObjClass(Condition cond, Register obj, Register scratch,
+                                   const Address& clasp, Label* label);
     inline void branchTestObjShape(Condition cond, Register obj, const Shape* shape, Label* label);
     inline void branchTestObjShape(Condition cond, Register obj, Register shape, Label* label);
-    inline void branchTestObjGroup(Condition cond, Register obj, ObjectGroup* group, Label* label);
+    inline void branchTestObjGroup(Condition cond, Register obj, const ObjectGroup* group,
+                                   Label* label);
     inline void branchTestObjGroup(Condition cond, Register obj, Register group, Label* label);
 
+    void branchTestObjGroup(Condition cond, Register obj, const Address& group, Register scratch,
+                            Label* label);
+
+    void branchTestObjCompartment(Condition cond, Register obj, const Address& compartment,
+                                  Register scratch, Label* label);
+    void branchTestObjCompartment(Condition cond, Register obj, const JSCompartment* compartment,
+                                  Register scratch, Label* label);
+    void branchIfObjGroupHasNoAddendum(Register obj, Register scratch, Label* label);
+    void branchIfPretenuredGroup(const ObjectGroup* group, Register scratch, Label* label);
+
+    void branchIfNonNativeObj(Register obj, Register scratch, Label* label);
+
+    void branchIfInlineTypedObject(Register obj, Register scratch, Label* label);
+
+    void branchIfNotSimdObject(Register obj, Register scratch, SimdType simdType, Label* label);
+
     inline void branchTestClassIsProxy(bool proxy, Register clasp, Label* label);
 
     inline void branchTestObjectIsProxy(bool proxy, Register object, Register scratch, Label* label);
 
     inline void branchTestProxyHandlerFamily(Condition cond, Register proxy, Register scratch,
                                              const void* handlerp, Label* label);
 
+    void copyObjGroupNoPreBarrier(Register sourceObj, Register destObj, Register scratch);
+
+    void loadTypedObjectDescr(Register obj, Register dest);
+    void loadTypedObjectLength(Register obj, Register dest);
+
     // Emit type case branch on tag matching if the type tag in the definition
     // might actually be that type.
     void maybeBranchTestType(MIRType type, MDefinition* maybeDef, Register tag, Label* label);
 
     inline void branchTestNeedsIncrementalBarrier(Condition cond, Label* label);
 
     // Perform a type-test on a tag of a Value (32bits boxing), or the tagged
     // value (64bits boxing).
@@ -1946,31 +1970,35 @@ class MacroAssembler : public MacroAssem
     void guardObjectType(Register obj, const TypeSet* types, Register scratch,
                          Register spectreRegToZero, Label* miss);
 
 #ifdef DEBUG
     void guardTypeSetMightBeIncomplete(const TypeSet* types, Register obj, Register scratch,
                                        Label* label);
 #endif
 
-    void loadObjShape(Register objReg, Register dest) {
-        loadPtr(Address(objReg, ShapedObject::offsetOfShape()), dest);
-    }
-    void loadObjGroup(Register objReg, Register dest) {
-        loadPtr(Address(objReg, JSObject::offsetOfGroup()), dest);
+    // Unsafe here means the caller is responsible for Spectre mitigations if
+    // needed. Prefer branchTestObjGroup or one of the other masm helpers!
+    void loadObjGroupUnsafe(Register obj, Register dest) {
+        loadPtr(Address(obj, JSObject::offsetOfGroup()), dest);
     }
-    void loadBaseShape(Register objReg, Register dest) {
-        loadObjShape(objReg, dest);
-        loadPtr(Address(dest, Shape::offsetOfBase()), dest);
-    }
-    void loadObjClass(Register objReg, Register dest) {
-        loadObjGroup(objReg, dest);
+    void loadObjClassUnsafe(Register obj, Register dest) {
+        loadPtr(Address(obj, JSObject::offsetOfGroup()), dest);
         loadPtr(Address(dest, ObjectGroup::offsetOfClasp()), dest);
     }
 
+    template <typename EmitPreBarrier>
+    inline void storeObjGroup(Register group, Register obj, EmitPreBarrier emitPreBarrier);
+    template <typename EmitPreBarrier>
+    inline void storeObjGroup(ObjectGroup* group, Register obj, EmitPreBarrier emitPreBarrier);
+    template <typename EmitPreBarrier>
+    inline void storeObjShape(Register shape, Register obj, EmitPreBarrier emitPreBarrier);
+    template <typename EmitPreBarrier>
+    inline void storeObjShape(Shape* shape, Register obj, EmitPreBarrier emitPreBarrier);
+
     void loadObjPrivate(Register obj, uint32_t nfixed, Register dest) {
         loadPtr(Address(obj, NativeObject::getPrivateDataOffset(nfixed)), dest);
     }
 
     void loadObjProto(Register obj, Register dest) {
         loadPtr(Address(obj, JSObject::offsetOfGroup()), dest);
         loadPtr(Address(dest, ObjectGroup::offsetOfProto()), dest);
     }
@@ -2187,16 +2215,17 @@ class MacroAssembler : public MacroAssem
     // Store a property to an UnboxedPlainObject, without triggering barriers.
     // If failure is null, the value definitely has a type suitable for storing
     // in the property.
     template <typename T>
     void storeUnboxedProperty(T address, JSValueType type,
                               const ConstantOrRegister& value, Label* failure);
 
     void debugAssertIsObject(const ValueOperand& val);
+    void debugAssertObjHasFixedSlots(Register obj, Register scratch);
 
     using MacroAssemblerSpecific::extractTag;
     Register extractTag(const TypedOrValueRegister& reg, Register scratch) {
         if (reg.hasValue())
             return extractTag(reg.valueReg(), scratch);
         mov(ImmWord(MIRTypeToTag(reg.type())), scratch);
         return scratch;
     }
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -2535,20 +2535,19 @@ bool
 ICTypeMonitor_ObjectGroup::Compiler::generateStubCode(MacroAssembler& masm)
 {
     Label failure;
     masm.branchTestObject(Assembler::NotEqual, R0, &failure);
     MaybeWorkAroundAmdBug(masm);
 
     // Guard on the object's ObjectGroup.
     Register obj = masm.extractObject(R0, ExtractTemp0);
-    masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), R1.scratchReg());
-
     Address expectedGroup(ICStubReg, ICTypeMonitor_ObjectGroup::offsetOfGroup());
-    masm.branchPtr(Assembler::NotEqual, expectedGroup, R1.scratchReg(), &failure);
+    masm.branchTestObjGroup(Assembler::NotEqual, obj, expectedGroup, R1.scratchReg(),
+                            &failure);
     MaybeWorkAroundAmdBug(masm);
 
     EmitReturnFromIC(masm);
     MaybeWorkAroundAmdBug(masm);
 
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
@@ -2760,19 +2759,17 @@ GenerateNewObjectWithTemplateCode(JSCont
     MacroAssembler masm;
 #ifdef JS_CODEGEN_ARM
     masm.setSecondScratchReg(BaselineSecondScratchReg);
 #endif
 
     Label failure;
     Register objReg = R0.scratchReg();
     Register tempReg = R1.scratchReg();
-    masm.movePtr(ImmGCPtr(templateObject->group()), tempReg);
-    masm.branchTest32(Assembler::NonZero, Address(tempReg, ObjectGroup::offsetOfFlags()),
-                      Imm32(OBJECT_FLAG_PRE_TENURE), &failure);
+    masm.branchIfPretenuredGroup(templateObject->group(), tempReg, &failure);
     masm.branchPtr(Assembler::NotEqual, AbsoluteAddress(cx->compartment()->addressOfMetadataBuilder()),
                    ImmWord(0), &failure);
     masm.createGCObject(objReg, tempReg, templateObject, gc::DefaultHeap, &failure);
     masm.tagValue(JSVAL_TYPE_OBJECT, objReg, R0);
 
     EmitReturnFromIC(masm);
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -716,17 +716,20 @@ PostWriteElementBarrier(JSRuntime* rt, J
 {
     AutoUnsafeCallWithABI unsafe;
 
     MOZ_ASSERT(!IsInsideNursery(obj));
 
     if (InBounds == IndexInBounds::Yes) {
         MOZ_ASSERT(uint32_t(index) < obj->as<NativeObject>().getDenseInitializedLength());
     } else {
-        if (MOZ_UNLIKELY(!obj->is<NativeObject>() || index < 0)) {
+        if (MOZ_UNLIKELY(!obj->is<NativeObject>() ||
+                         index < 0 ||
+                         uint32_t(index) >= NativeObject::MAX_DENSE_ELEMENTS_COUNT))
+        {
             rt->gc.storeBuffer().putWholeCell(obj);
             return;
         }
     }
 
     NativeObject* nobj = &obj->as<NativeObject>();
     if (nobj->isInWholeCellBuffer())
         return;
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -1741,52 +1741,40 @@ CodeGeneratorARM::visitNotF(LNotF* ins)
         masm.ma_mov(Imm32(1), dest, Assembler::Overflow);
     }
 }
 
 void
 CodeGeneratorARM::visitGuardShape(LGuardShape* guard)
 {
     Register obj = ToRegister(guard->input());
-    Register tmp = ToRegister(guard->tempInt());
-
-    ScratchRegisterScope scratch(masm);
-    masm.ma_ldr(DTRAddr(obj, DtrOffImm(ShapedObject::offsetOfShape())), tmp);
-    masm.ma_cmp(tmp, ImmGCPtr(guard->mir()->shape()), scratch);
-
-    bailoutIf(Assembler::NotEqual, guard->snapshot());
+    Label bail;
+    masm.branchTestObjShape(Assembler::NotEqual, obj, guard->mir()->shape(), &bail);
+    bailoutFrom(&bail, guard->snapshot());
 }
 
 void
 CodeGeneratorARM::visitGuardObjectGroup(LGuardObjectGroup* guard)
 {
     Register obj = ToRegister(guard->input());
-    Register tmp = ToRegister(guard->tempInt());
-    MOZ_ASSERT(obj != tmp);
-
-    ScratchRegisterScope scratch(masm);
-    masm.ma_ldr(DTRAddr(obj, DtrOffImm(JSObject::offsetOfGroup())), tmp);
-    masm.ma_cmp(tmp, ImmGCPtr(guard->mir()->group()), scratch);
-
     Assembler::Condition cond =
         guard->mir()->bailOnEquality() ? Assembler::Equal : Assembler::NotEqual;
-    bailoutIf(cond, guard->snapshot());
+    Label bail;
+    masm.branchTestObjGroup(cond, obj, guard->mir()->group(), &bail);
+    bailoutFrom(&bail, guard->snapshot());
 }
 
 void
 CodeGeneratorARM::visitGuardClass(LGuardClass* guard)
 {
     Register obj = ToRegister(guard->input());
     Register tmp = ToRegister(guard->tempInt());
-
-    ScratchRegisterScope scratch(masm);
-
-    masm.loadObjClass(obj, tmp);
-    masm.ma_cmp(tmp, Imm32((uint32_t)guard->mir()->getClass()), scratch);
-    bailoutIf(Assembler::NotEqual, guard->snapshot());
+    Label bail;
+    masm.branchTestObjClass(Assembler::NotEqual, obj, tmp, guard->mir()->getClass(), &bail);
+    bailoutFrom(&bail, guard->snapshot());
 }
 
 void
 CodeGeneratorARM::generateInvalidateEpilogue()
 {
     // Ensure that there is enough space in the buffer for the OsiPoint patching
     // to occur. Otherwise, we could overwrite the invalidation epilogue.
     for (size_t i = 0; i < sizeof(void*); i += Assembler::NopSize())
--- a/js/src/jit/arm/LIR-arm.h
+++ b/js/src/jit/arm/LIR-arm.h
@@ -378,41 +378,39 @@ class LTableSwitchV : public LInstructio
     const LDefinition* tempFloat() {
         return getTemp(1);
     }
     const LDefinition* tempPointer() {
         return nullptr;
     }
 };
 
-class LGuardShape : public LInstructionHelper<0, 1, 1>
+class LGuardShape : public LInstructionHelper<0, 1, 0>
 {
   public:
     LIR_HEADER(GuardShape);
 
-    LGuardShape(const LAllocation& in, const LDefinition& temp) {
+    explicit LGuardShape(const LAllocation& in) {
         setOperand(0, in);
-        setTemp(0, temp);
     }
     const MGuardShape* mir() const {
         return mir_->toGuardShape();
     }
     const LDefinition* tempInt() {
         return getTemp(0);
     }
 };
 
-class LGuardObjectGroup : public LInstructionHelper<0, 1, 1>
+class LGuardObjectGroup : public LInstructionHelper<0, 1, 0>
 {
   public:
     LIR_HEADER(GuardObjectGroup);
 
-    LGuardObjectGroup(const LAllocation& in, const LDefinition& temp) {
+    explicit LGuardObjectGroup(const LAllocation& in) {
         setOperand(0, in);
-        setTemp(0, temp);
     }
     const MGuardObjectGroup* mir() const {
         return mir_->toGuardObjectGroup();
     }
     const LDefinition* tempInt() {
         return getTemp(0);
     }
 };
--- a/js/src/jit/arm/Lowering-arm.cpp
+++ b/js/src/jit/arm/Lowering-arm.cpp
@@ -480,30 +480,28 @@ LIRGeneratorARM::newLTableSwitchV(MTable
                                       temp(), tempDouble(), tableswitch);
 }
 
 void
 LIRGeneratorARM::visitGuardShape(MGuardShape* ins)
 {
     MOZ_ASSERT(ins->object()->type() == MIRType::Object);
 
-    LDefinition tempObj = temp(LDefinition::OBJECT);
-    LGuardShape* guard = new(alloc()) LGuardShape(useRegister(ins->object()), tempObj);
+    LGuardShape* guard = new(alloc()) LGuardShape(useRegister(ins->object()));
     assignSnapshot(guard, ins->bailoutKind());
     add(guard, ins);
     redefine(ins, ins->object());
 }
 
 void
 LIRGeneratorARM::visitGuardObjectGroup(MGuardObjectGroup* ins)
 {
     MOZ_ASSERT(ins->object()->type() == MIRType::Object);
 
-    LDefinition tempObj = temp(LDefinition::OBJECT);
-    LGuardObjectGroup* guard = new(alloc()) LGuardObjectGroup(useRegister(ins->object()), tempObj);
+    LGuardObjectGroup* guard = new(alloc()) LGuardObjectGroup(useRegister(ins->object()));
     assignSnapshot(guard, ins->bailoutKind());
     add(guard, ins);
     redefine(ins, ins->object());
 }
 
 void
 LIRGeneratorARM::lowerUrshD(MUrsh* mir)
 {
--- a/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
+++ b/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
@@ -1767,46 +1767,40 @@ CodeGeneratorMIPSShared::visitNotF(LNotF
     masm.loadConstantFloat32(0.0f, ScratchFloat32Reg);
     masm.ma_cmp_set_float32(dest, in, ScratchFloat32Reg, Assembler::DoubleEqualOrUnordered);
 }
 
 void
 CodeGeneratorMIPSShared::visitGuardShape(LGuardShape* guard)
 {
     Register obj = ToRegister(guard->input());
-    Register tmp = ToRegister(guard->tempInt());
-
-    masm.loadPtr(Address(obj, ShapedObject::offsetOfShape()), tmp);
-    bailoutCmpPtr(Assembler::NotEqual, tmp, ImmGCPtr(guard->mir()->shape()),
-                  guard->snapshot());
+    Label bail;
+    masm.branchTestObjShape(Assembler::NotEqual, obj, guard->mir()->shape(), &bail);
+    bailoutFrom(&bail, guard->snapshot());
 }
 
 void
 CodeGeneratorMIPSShared::visitGuardObjectGroup(LGuardObjectGroup* guard)
 {
     Register obj = ToRegister(guard->input());
-    Register tmp = ToRegister(guard->tempInt());
-    MOZ_ASSERT(obj != tmp);
-
-    masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), tmp);
-    Assembler::Condition cond = guard->mir()->bailOnEquality()
-                                ? Assembler::Equal
-                                : Assembler::NotEqual;
-    bailoutCmpPtr(cond, tmp, ImmGCPtr(guard->mir()->group()), guard->snapshot());
+    Assembler::Condition cond =
+        guard->mir()->bailOnEquality() ? Assembler::Equal : Assembler::NotEqual;
+    Label bail;
+    masm.branchTestObjGroup(cond, obj, guard->mir()->group(), &bail);
+    bailoutFrom(&bail, guard->snapshot());
 }
 
 void
 CodeGeneratorMIPSShared::visitGuardClass(LGuardClass* guard)
 {
     Register obj = ToRegister(guard->input());
     Register tmp = ToRegister(guard->tempInt());
-
-    masm.loadObjClass(obj, tmp);
-    bailoutCmpPtr(Assembler::NotEqual, tmp, ImmPtr(guard->mir()->getClass()),
-                  guard->snapshot());
+    Label bail;
+    masm.branchTestObjClass(Assembler::NotEqual, obj, tmp, guard->mir()->getClass(), &bail);
+    bailoutFrom(&bail, guard->snapshot());
 }
 
 void
 CodeGeneratorMIPSShared::visitMemoryBarrier(LMemoryBarrier* ins)
 {
     masm.memoryBarrier(ins->type());
 }
 
--- a/js/src/jit/mips-shared/LIR-mips-shared.h
+++ b/js/src/jit/mips-shared/LIR-mips-shared.h
@@ -205,41 +205,39 @@ class LTableSwitchV : public LInstructio
     const LDefinition* tempFloat() {
         return getTemp(1);
     }
     const LDefinition* tempPointer() {
         return getTemp(2);
     }
 };
 
-class LGuardShape : public LInstructionHelper<0, 1, 1>
+class LGuardShape : public LInstructionHelper<0, 1, 0>
 {
   public:
     LIR_HEADER(GuardShape);
 
-    LGuardShape(const LAllocation& in, const LDefinition& temp) {
+    explicit LGuardShape(const LAllocation& in) {
         setOperand(0, in);
-        setTemp(0, temp);
     }
     const MGuardShape* mir() const {
         return mir_->toGuardShape();
     }
     const LDefinition* tempInt() {
         return getTemp(0);
     }
 };
 
-class LGuardObjectGroup : public LInstructionHelper<0, 1, 1>
+class LGuardObjectGroup : public LInstructionHelper<0, 1, 0>
 {
   public:
     LIR_HEADER(GuardObjectGroup);
 
-    LGuardObjectGroup(const LAllocation& in, const LDefinition& temp) {
+    explicit LGuardObjectGroup(const LAllocation& in) {
         setOperand(0, in);
-        setTemp(0, temp);
     }
     const MGuardObjectGroup* mir() const {
         return mir_->toGuardObjectGroup();
     }
     const LDefinition* tempInt() {
         return getTemp(0);
     }
 };
--- a/js/src/jit/mips-shared/Lowering-mips-shared.cpp
+++ b/js/src/jit/mips-shared/Lowering-mips-shared.cpp
@@ -287,30 +287,28 @@ LIRGeneratorMIPSShared::newLTableSwitchV
                                       temp(), tempDouble(), temp(), tableswitch);
 }
 
 void
 LIRGeneratorMIPSShared::visitGuardShape(MGuardShape* ins)
 {
     MOZ_ASSERT(ins->object()->type() == MIRType::Object);
 
-    LDefinition tempObj = temp(LDefinition::OBJECT);
-    LGuardShape* guard = new(alloc()) LGuardShape(useRegister(ins->object()), tempObj);
+    LGuardShape* guard = new(alloc()) LGuardShape(useRegister(ins->object()));
     assignSnapshot(guard, ins->bailoutKind());
     add(guard, ins);
     redefine(ins, ins->object());
 }
 
 void
 LIRGeneratorMIPSShared::visitGuardObjectGroup(MGuardObjectGroup* ins)
 {
     MOZ_ASSERT(ins->object()->type() == MIRType::Object);
 
-    LDefinition tempObj = temp(LDefinition::OBJECT);
-    LGuardObjectGroup* guard = new(alloc()) LGuardObjectGroup(useRegister(ins->object()), tempObj);
+    LGuardObjectGroup* guard = new(alloc()) LGuardObjectGroup(useRegister(ins->object()));
     assignSnapshot(guard, ins->bailoutKind());
     add(guard, ins);
     redefine(ins, ins->object());
 }
 
 void
 LIRGeneratorMIPSShared::lowerUrshD(MUrsh* mir)
 {
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
@@ -2371,42 +2371,40 @@ CodeGeneratorX86Shared::visitNearbyIntF(
     RoundingMode roundingMode = lir->mir()->roundingMode();
     masm.vroundss(Assembler::ToX86RoundingMode(roundingMode), input, output, output);
 }
 
 void
 CodeGeneratorX86Shared::visitGuardShape(LGuardShape* guard)
 {
     Register obj = ToRegister(guard->input());
-    masm.cmpPtr(Operand(obj, ShapedObject::offsetOfShape()), ImmGCPtr(guard->mir()->shape()));
-
-    bailoutIf(Assembler::NotEqual, guard->snapshot());
+    Label bail;
+    masm.branchTestObjShape(Assembler::NotEqual, obj, guard->mir()->shape(), &bail);
+    bailoutFrom(&bail, guard->snapshot());
 }
 
 void
 CodeGeneratorX86Shared::visitGuardObjectGroup(LGuardObjectGroup* guard)
 {
     Register obj = ToRegister(guard->input());
-
-    masm.cmpPtr(Operand(obj, JSObject::offsetOfGroup()), ImmGCPtr(guard->mir()->group()));
-
     Assembler::Condition cond =
         guard->mir()->bailOnEquality() ? Assembler::Equal : Assembler::NotEqual;
-    bailoutIf(cond, guard->snapshot());
+    Label bail;
+    masm.branchTestObjGroup(cond, obj, guard->mir()->group(), &bail);
+    bailoutFrom(&bail, guard->snapshot());
 }
 
 void
 CodeGeneratorX86Shared::visitGuardClass(LGuardClass* guard)
 {
     Register obj = ToRegister(guard->input());
     Register tmp = ToRegister(guard->tempInt());
-
-    masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), tmp);
-    masm.cmpPtr(Operand(tmp, ObjectGroup::offsetOfClasp()), ImmPtr(guard->mir()->getClass()));
-    bailoutIf(Assembler::NotEqual, guard->snapshot());
+    Label bail;
+    masm.branchTestObjClass(Assembler::NotEqual, obj, tmp, guard->mir()->getClass(), &bail);
+    bailoutFrom(&bail, guard->snapshot());
 }
 
 void
 CodeGeneratorX86Shared::visitEffectiveAddress(LEffectiveAddress* ins)
 {
     const MEffectiveAddress* mir = ins->mir();
     Register base = ToRegister(ins->base());
     Register index = ToRegister(ins->index());
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3988,16 +3988,17 @@ JS::TransitiveCompileOptions::copyPODTra
     throwOnAsmJSValidationFailureOption = rhs.throwOnAsmJSValidationFailureOption;
     forceAsync = rhs.forceAsync;
     sourceIsLazy = rhs.sourceIsLazy;
     introductionType = rhs.introductionType;
     introductionLineno = rhs.introductionLineno;
     introductionOffset = rhs.introductionOffset;
     hasIntroductionInfo = rhs.hasIntroductionInfo;
     isProbablySystemOrAddonCode = rhs.isProbablySystemOrAddonCode;
+    hideScriptFromDebugger = rhs.hideScriptFromDebugger;
 };
 
 void
 JS::ReadOnlyCompileOptions::copyPODOptions(const ReadOnlyCompileOptions& rhs)
 {
     copyPODTransitiveOptions(rhs);
     lineno = rhs.lineno;
     column = rhs.column;
@@ -4673,16 +4674,27 @@ JS::InitScriptSourceElement(JSContext* c
 {
     MOZ_ASSERT(cx);
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
 
     RootedScriptSource sso(cx, &script->sourceObject()->as<ScriptSourceObject>());
     return ScriptSourceObject::initElementProperties(cx, sso, element, elementAttrName);
 }
 
+JS_PUBLIC_API(void)
+JS::ExposeScriptToDebugger(JSContext* cx, HandleScript script)
+{
+    MOZ_ASSERT(cx);
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
+
+    MOZ_ASSERT(script->hideScriptFromDebugger());
+    script->clearHideScriptFromDebugger();
+    Debugger::onNewScript(cx, script);
+}
+
 JS_PUBLIC_API(JSString*)
 JS_DecompileScript(JSContext* cx, HandleScript script)
 {
     MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
 
     AssertHeapIsIdle();
     CHECK_REQUEST(cx);
     script->ensureNonLazyCanonicalFunction();
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -97,143 +97,16 @@ class MOZ_RAII AutoValueArray : public A
     MutableHandleValue operator[](unsigned i) {
         MOZ_ASSERT(i < N);
         return MutableHandleValue::fromMarkedLocation(&elements_[i]);
     }
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
-template<class T>
-class MOZ_RAII AutoVectorRooterBase : protected AutoGCRooter
-{
-    typedef js::Vector<T, 8> VectorImpl;
-    VectorImpl vector;
-
-  public:
-    explicit AutoVectorRooterBase(JSContext* cx, ptrdiff_t tag
-                              MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
-      : AutoGCRooter(cx, tag), vector(cx)
-    {
-        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-    }
-
-    typedef T ElementType;
-    typedef typename VectorImpl::Range Range;
-
-    size_t length() const { return vector.length(); }
-    bool empty() const { return vector.empty(); }
-
-    MOZ_MUST_USE bool append(const T& v) { return vector.append(v); }
-    MOZ_MUST_USE bool appendN(const T& v, size_t len) { return vector.appendN(v, len); }
-    MOZ_MUST_USE bool append(const T* ptr, size_t len) { return vector.append(ptr, len); }
-    MOZ_MUST_USE bool appendAll(const AutoVectorRooterBase<T>& other) {
-        return vector.appendAll(other.vector);
-    }
-
-    MOZ_MUST_USE bool insert(T* p, const T& val) { return vector.insert(p, val); }
-
-    /* For use when space has already been reserved. */
-    void infallibleAppend(const T& v) { vector.infallibleAppend(v); }
-
-    void popBack() { vector.popBack(); }
-    T popCopy() { return vector.popCopy(); }
-
-    MOZ_MUST_USE bool growBy(size_t inc) {
-        size_t oldLength = vector.length();
-        if (!vector.growByUninitialized(inc))
-            return false;
-        makeRangeGCSafe(oldLength);
-        return true;
-    }
-
-    MOZ_MUST_USE bool resize(size_t newLength) {
-        size_t oldLength = vector.length();
-        if (newLength <= oldLength) {
-            vector.shrinkBy(oldLength - newLength);
-            return true;
-        }
-        if (!vector.growByUninitialized(newLength - oldLength))
-            return false;
-        makeRangeGCSafe(oldLength);
-        return true;
-    }
-
-    void clear() { vector.clear(); }
-
-    MOZ_MUST_USE bool reserve(size_t newLength) {
-        return vector.reserve(newLength);
-    }
-
-    JS::MutableHandle<T> operator[](size_t i) {
-        return JS::MutableHandle<T>::fromMarkedLocation(&vector[i]);
-    }
-    JS::Handle<T> operator[](size_t i) const {
-        return JS::Handle<T>::fromMarkedLocation(&vector[i]);
-    }
-
-    const T* begin() const { return vector.begin(); }
-    T* begin() { return vector.begin(); }
-
-    const T* end() const { return vector.end(); }
-    T* end() { return vector.end(); }
-
-    Range all() { return vector.all(); }
-
-    const T& back() const { return vector.back(); }
-
-    friend void AutoGCRooter::trace(JSTracer* trc);
-
-  private:
-    void makeRangeGCSafe(size_t oldLength) {
-        T* t = vector.begin() + oldLength;
-        for (size_t i = oldLength; i < vector.length(); ++i, ++t)
-            memset(t, 0, sizeof(T));
-    }
-
-    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
-};
-
-template <typename T>
-class MOZ_RAII AutoVectorRooter : public AutoVectorRooterBase<T>
-{
-  public:
-    explicit AutoVectorRooter(JSContext* cx
-                             MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
-        : AutoVectorRooterBase<T>(cx, this->GetTag(T()))
-    {
-        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-    }
-
-    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
-};
-
-class AutoValueVector : public Rooted<GCVector<Value, 8>> {
-    using Vec = GCVector<Value, 8>;
-    using Base = Rooted<Vec>;
-  public:
-    explicit AutoValueVector(JSContext* cx) : Base(cx, Vec(cx)) {}
-};
-
-class AutoIdVector : public Rooted<GCVector<jsid, 8>> {
-    using Vec = GCVector<jsid, 8>;
-    using Base = Rooted<Vec>;
-  public:
-    explicit AutoIdVector(JSContext* cx) : Base(cx, Vec(cx)) {}
-
-    bool appendAll(const AutoIdVector& other) { return this->Base::appendAll(other.get()); }
-};
-
-class AutoObjectVector : public Rooted<GCVector<JSObject*, 8>> {
-    using Vec = GCVector<JSObject*, 8>;
-    using Base = Rooted<Vec>;
-  public:
-    explicit AutoObjectVector(JSContext* cx) : Base(cx, Vec(cx)) {}
-};
-
 using ValueVector = JS::GCVector<JS::Value>;
 using IdVector = JS::GCVector<jsid>;
 using ScriptVector = JS::GCVector<JSScript*>;
 using StringVector = JS::GCVector<JSString*>;
 
 template<class Key, class Value>
 class MOZ_RAII AutoHashMapRooter : protected AutoGCRooter
 {
@@ -3687,16 +3560,17 @@ class JS_FRIEND_API(TransitiveCompileOpt
         expressionClosuresOption(false),
         werrorOption(false),
         asmJSOption(AsmJSOption::Disabled),
         throwOnAsmJSValidationFailureOption(false),
         forceAsync(false),
         sourceIsLazy(false),
         allowHTMLComments(true),
         isProbablySystemOrAddonCode(false),
+        hideScriptFromDebugger(false),
         introductionType(nullptr),
         introductionLineno(0),
         introductionOffset(0),
         hasIntroductionInfo(false)
     { }
 
     // Set all POD options (those not requiring reference counts, copies,
     // rooting, or other hand-holding) to their values in |rhs|.
@@ -3722,16 +3596,17 @@ class JS_FRIEND_API(TransitiveCompileOpt
     bool expressionClosuresOption;
     bool werrorOption;
     AsmJSOption asmJSOption;
     bool throwOnAsmJSValidationFailureOption;
     bool forceAsync;
     bool sourceIsLazy;
     bool allowHTMLComments;
     bool isProbablySystemOrAddonCode;
+    bool hideScriptFromDebugger;
 
     // |introductionType| is a statically allocated C string:
     // one of "eval", "Function", or "GeneratorFunction".
     const char* introductionType;
     unsigned introductionLineno;
     uint32_t introductionOffset;
     bool hasIntroductionInfo;
 
@@ -4129,16 +4004,23 @@ CompileFunction(JSContext* cx, AutoObjec
  * Associate an element wrapper and attribute name with a previously compiled
  * script, for debugging purposes. Calling this function is optional, but should
  * be done before script execution if it is required.
  */
 extern JS_PUBLIC_API(bool)
 InitScriptSourceElement(JSContext* cx, HandleScript script,
                         HandleObject element, HandleString elementAttrName = nullptr);
 
+/*
+ * For a script compiled with the hideScriptFromDebugger option, expose the
+ * script to the debugger by calling the debugger's onNewScript hook.
+ */
+extern JS_PUBLIC_API(void)
+ExposeScriptToDebugger(JSContext* cx, HandleScript script);
+
 } /* namespace JS */
 
 extern JS_PUBLIC_API(JSString*)
 JS_DecompileScript(JSContext* cx, JS::Handle<JSScript*> script);
 
 extern JS_PUBLIC_API(JSString*)
 JS_DecompileFunction(JSContext* cx, JS::Handle<JSFunction*> fun);
 
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -24,17 +24,21 @@
 #include "js/TypeDecls.h"
 
 #if defined(JS_GC_ZEAL) || defined(DEBUG)
 # define JSGC_HASH_TABLE_CHECKS
 #endif
 
 namespace JS {
 
-class AutoIdVector;
+template <typename T> class AutoVector;
+using AutoIdVector = AutoVector<jsid>;
+using AutoValueVector = AutoVector<Value>;
+using AutoObjectVector = AutoVector<JSObject*>;
+
 class CallArgs;
 
 class JS_FRIEND_API(CompileOptions);
 class JS_FRIEND_API(ReadOnlyCompileOptions);
 class JS_FRIEND_API(OwningCompileOptions);
 class JS_FRIEND_API(TransitiveCompileOptions);
 class JS_PUBLIC_API(CompartmentOptions);
 
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -9080,17 +9080,18 @@ main(int argc, char** argv, char** envp)
         || !op.addBoolOption('\0', "no-threads", "Disable helper threads")
 #ifdef DEBUG
         || !op.addBoolOption('\0', "dump-entrained-variables", "Print variables which are "
                              "unnecessarily entrained by inner functions")
 #endif
         || !op.addBoolOption('\0', "no-ggc", "Disable Generational GC")
         || !op.addBoolOption('\0', "no-cgc", "Disable Compacting GC")
         || !op.addBoolOption('\0', "no-incremental-gc", "Disable Incremental GC")
-        || !op.addBoolOption('\0', "nursery-strings", "Allocate strings in the nursery")
+        || !op.addStringOption('\0', "nursery-strings", "on/off",
+                               "Allocate strings in the nursery")
         || !op.addIntOption('\0', "available-memory", "SIZE",
                             "Select GC settings based on available memory (MB)", 0)
         || !op.addStringOption('\0', "arm-hwcap", "[features]",
                                "Specify ARM code generation features, or 'help' to list all features.")
         || !op.addIntOption('\0', "arm-asm-nop-fill", "SIZE",
                             "Insert the given number of NOP instructions at all possible pool locations.", 0)
         || !op.addIntOption('\0', "asm-pool-max-offset", "OFFSET",
                             "The maximum pc relative OFFSET permitted in pool reference instructions.", 1024)
@@ -9228,18 +9229,24 @@ main(int argc, char** argv, char** envp)
     JS::InitConsumeStreamCallback(cx, ConsumeBufferSource);
 
     JS_SetNativeStackQuota(cx, gMaxStackSize);
 
     JS::dbg::SetDebuggerMallocSizeOf(cx, moz_malloc_size_of);
 
     js::UseInternalJobQueues(cx);
 
-    if (op.getBoolOption("nursery-strings"))
-        cx->runtime()->gc.nursery().enableStrings();
+    if (const char* opt = op.getStringOption("nursery-strings")) {
+        if (strcmp(opt, "on") == 0)
+            cx->runtime()->gc.nursery().enableStrings();
+        else if (strcmp(opt, "off") == 0)
+            cx->runtime()->gc.nursery().disableStrings();
+        else
+            MOZ_CRASH("invalid option value for --nursery-strings, must be on/off");
+    }
 
     if (!JS::InitSelfHostedCode(cx))
         return 1;
 
     EnvironmentPreparer environmentPreparer(cx);
 
     JS_SetGCParameter(cx, JSGC_MODE, JSGC_MODE_INCREMENTAL);
 
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -1795,16 +1795,21 @@ Debugger::observesGlobal(GlobalObject* g
 /* static */ void
 Debugger::onNewScript(JSContext* cx, HandleScript script)
 {
     // We early return in slowPathOnNewScript for self-hosted scripts, so we can
     // ignore those in our assertion here.
     MOZ_ASSERT_IF(!script->compartment()->creationOptions().invisibleToDebugger() &&
                   !script->selfHosted(),
                   script->compartment()->firedOnNewGlobalObject);
+
+    // The script may not be ready to be interrogated by the debugger.
+    if (script->hideScriptFromDebugger())
+        return;
+
     if (script->compartment()->isDebuggee())
         slowPathOnNewScript(cx, script);
 }
 
 /* static */ void
 Debugger::onNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global)
 {
     MOZ_ASSERT(!global->compartment()->firedOnNewGlobalObject);
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -213,32 +213,34 @@ FinishOffThreadIonCompile(jit::IonBuilde
 
 static JSRuntime*
 GetSelectorRuntime(const CompilationSelector& selector)
 {
     struct Matcher
     {
         JSRuntime* match(JSScript* script)    { return script->runtimeFromActiveCooperatingThread(); }
         JSRuntime* match(JSCompartment* comp) { return comp->runtimeFromActiveCooperatingThread(); }
+        JSRuntime* match(Zone* zone)          { return zone->runtimeFromActiveCooperatingThread(); }
         JSRuntime* match(ZonesInState zbs)    { return zbs.runtime; }
         JSRuntime* match(JSRuntime* runtime)  { return runtime; }
         JSRuntime* match(AllCompilations all) { return nullptr; }
         JSRuntime* match(CompilationsUsingNursery cun) { return cun.runtime; }
     };
 
     return selector.match(Matcher());
 }
 
 static bool
 JitDataStructuresExist(const CompilationSelector& selector)
 {
     struct Matcher
     {
         bool match(JSScript* script)    { return !!script->compartment()->jitCompartment(); }
         bool match(JSCompartment* comp) { return !!comp->jitCompartment(); }
+        bool match(Zone* zone)          { return !!zone->jitZone(); }
         bool match(ZonesInState zbs)    { return zbs.runtime->hasJitRuntime(); }
         bool match(JSRuntime* runtime)  { return runtime->hasJitRuntime(); }
         bool match(AllCompilations all) { return true; }
         bool match(CompilationsUsingNursery cun) { return cun.runtime->hasJitRuntime(); }
     };
 
     return selector.match(Matcher());
 }
@@ -247,16 +249,17 @@ static bool
 IonBuilderMatches(const CompilationSelector& selector, jit::IonBuilder* builder)
 {
     struct BuilderMatches
     {
         jit::IonBuilder* builder_;
 
         bool match(JSScript* script)    { return script == builder_->script(); }
         bool match(JSCompartment* comp) { return comp == builder_->script()->compartment(); }
+        bool match(Zone* zone)          { return zone == builder_->script()->zone(); }
         bool match(JSRuntime* runtime)  { return runtime == builder_->script()->runtimeFromAnyThread(); }
         bool match(AllCompilations all) { return true; }
         bool match(ZonesInState zbs)    {
             return zbs.runtime == builder_->script()->runtimeFromAnyThread() &&
                    zbs.state == builder_->script()->zoneFromAnyThread()->gcState();
         }
         bool match(CompilationsUsingNursery cun) {
             return cun.runtime == builder_->script()->runtimeFromAnyThread() &&
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -511,16 +511,17 @@ bool
 StartOffThreadIonFree(jit::IonBuilder* builder, const AutoLockHelperThreadState& lock);
 
 struct AllCompilations {};
 struct ZonesInState { JSRuntime* runtime; JS::Zone::GCState state; };
 struct CompilationsUsingNursery { JSRuntime* runtime; };
 
 using CompilationSelector = mozilla::Variant<JSScript*,
                                              JSCompartment*,
+                                             Zone*,
                                              ZonesInState,
                                              JSRuntime*,
                                              CompilationsUsingNursery,
                                              AllCompilations>;
 
 /*
  * Cancel scheduled or in progress Ion compilations.
  */
@@ -535,16 +536,22 @@ CancelOffThreadIonCompile(JSScript* scri
 
 inline void
 CancelOffThreadIonCompile(JSCompartment* comp)
 {
     CancelOffThreadIonCompile(CompilationSelector(comp), true);
 }
 
 inline void
+CancelOffThreadIonCompile(Zone* zone)
+{
+    CancelOffThreadIonCompile(CompilationSelector(zone), true);
+}
+
+inline void
 CancelOffThreadIonCompile(JSRuntime* runtime, JS::Zone::GCState state)
 {
     CancelOffThreadIonCompile(CompilationSelector(ZonesInState{runtime, state}), true);
 }
 
 inline void
 CancelOffThreadIonCompile(JSRuntime* runtime)
 {
--- a/js/src/vm/JSCompartment.h
+++ b/js/src/vm/JSCompartment.h
@@ -1379,26 +1379,30 @@ struct WrapperValue
     Value get() const { return value; }
     operator const Value&() const { return value; }
     JSObject& toObject() const { return value.toObject(); }
 
   private:
     Value value;
 };
 
-class MOZ_RAII AutoWrapperVector : public JS::AutoVectorRooterBase<WrapperValue>
+class MOZ_RAII AutoWrapperVector : public JS::GCVector<WrapperValue, 8>,
+                                   private JS::AutoGCRooter
 {
   public:
     explicit AutoWrapperVector(JSContext* cx
                                MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
-        : AutoVectorRooterBase<WrapperValue>(cx, WRAPVECTOR)
+      : JS::GCVector<WrapperValue, 8>(cx),
+        JS::AutoGCRooter(cx, WRAPVECTOR)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     }
 
+    friend void AutoGCRooter::trace(JSTracer* trc);
+
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 class MOZ_RAII AutoWrapperRooter : private JS::AutoGCRooter {
   public:
     AutoWrapperRooter(JSContext* cx, const WrapperValue& v
                       MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : JS::AutoGCRooter(cx, WRAPPER), value(v)
--- a/js/src/vm/JSObject.h
+++ b/js/src/vm/JSObject.h
@@ -536,28 +536,33 @@ class JSObject : public js::gc::Cell
         return *static_cast<const T*>(this);
     }
 
 #ifdef DEBUG
     void dump(js::GenericPrinter& fp) const;
     void dump() const;
 #endif
 
-    /* JIT Accessors */
+    // Maximum size in bytes of a JSObject.
+    static const size_t MAX_BYTE_SIZE = 4 * sizeof(void*) + 16 * sizeof(JS::Value);
+
+  protected:
+    // JIT Accessors.
+    //
+    // To help avoid writing Spectre-unsafe code, we only allow MacroAssembler
+    // to call the method below.
+    friend class js::jit::MacroAssembler;
 
     static constexpr size_t offsetOfGroup() {
         return offsetof(JSObject, group_);
     }
     static constexpr size_t offsetOfShapeOrExpando() {
         return offsetof(JSObject, shapeOrExpando_);
     }
 
-    // Maximum size in bytes of a JSObject.
-    static const size_t MAX_BYTE_SIZE = 4 * sizeof(void*) + 16 * sizeof(JS::Value);
-
   private:
     JSObject() = delete;
     JSObject(const JSObject& other) = delete;
     void operator=(const JSObject& other) = delete;
 };
 
 template <typename Wrapper>
 template <typename U>
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -2727,16 +2727,18 @@ JSScript::Create(JSContext* cx, const Re
     script->setSourceObject(sourceObject);
     if (cx->runtime()->lcovOutput().isEnabled() && !script->initScriptName(cx))
         return nullptr;
     script->sourceStart_ = bufStart;
     script->sourceEnd_ = bufEnd;
     script->toStringStart_ = toStringStart;
     script->toStringEnd_ = toStringEnd;
 
+    script->hideScriptFromDebugger_ = options.hideScriptFromDebugger;
+
 #ifdef MOZ_VTUNE
     script->vtuneMethodId_ = vtune::GenerateUniqueMethodID();
 #endif
 
     return script;
 }
 
 bool
@@ -3620,16 +3622,17 @@ js::detail::CopyScript(JSContext* cx, Ha
     dst->hasInnerFunctions_ = src->hasInnerFunctions();
     dst->setGeneratorKind(src->generatorKind());
     dst->isDerivedClassConstructor_ = src->isDerivedClassConstructor();
     dst->needsHomeObject_ = src->needsHomeObject();
     dst->isDefaultClassConstructor_ = src->isDefaultClassConstructor();
     dst->isAsync_ = src->isAsync_;
     dst->hasRest_ = src->hasRest_;
     dst->isExprBody_ = src->isExprBody_;
+    dst->hideScriptFromDebugger_ = src->hideScriptFromDebugger_;
 
     if (nconsts != 0) {
         GCPtrValue* vector = Rebase<GCPtrValue>(dst, src, src->consts()->vector);
         dst->consts()->vector = vector;
         for (unsigned i = 0; i < nconsts; ++i)
             MOZ_ASSERT_IF(vector[i].isGCThing(), vector[i].toString()->isAtom());
     }
     if (nobjects != 0) {
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -1137,16 +1137,19 @@ class JSScript : public js::gc::TenuredC
     bool isGenerator_:1;
 
     // True if this function is an async function or async generator.
     bool isAsync_:1;
 
     bool hasRest_:1;
     bool isExprBody_:1;
 
+    // True if the debugger's onNewScript hook has not yet been called.
+    bool hideScriptFromDebugger_:1;
+
     // Add padding so JSScript is gc::Cell aligned. Make padding protected
     // instead of private to suppress -Wunused-private-field compiler warnings.
   protected:
 #if JS_BITS_PER_WORD == 32
     uint32_t padding_;
 #endif
 
     //
@@ -1458,16 +1461,23 @@ class JSScript : public js::gc::TenuredC
 
     bool isExprBody() const {
         return isExprBody_;
     }
     void setIsExprBody() {
         isExprBody_ = true;
     }
 
+    bool hideScriptFromDebugger() const {
+        return hideScriptFromDebugger_;
+    }
+    void clearHideScriptFromDebugger() {
+        hideScriptFromDebugger_ = false;
+    }
+
     void setNeedsHomeObject() {
         needsHomeObject_ = true;
     }
     bool needsHomeObject() const {
         return needsHomeObject_;
     }
 
     bool isDerivedClassConstructor() const {
--- a/js/src/vm/ObjectGroup.h
+++ b/js/src/vm/ObjectGroup.h
@@ -454,16 +454,20 @@ class ObjectGroup : public gc::TenuredCe
     }
 
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
     void finalize(FreeOp* fop);
 
     static const JS::TraceKind TraceKind = JS::TraceKind::ObjectGroup;
 
+  private:
+    // See JSObject::offsetOfGroup() comment.
+    friend class js::jit::MacroAssembler;
+
     static inline uint32_t offsetOfClasp() {
         return offsetof(ObjectGroup, clasp_);
     }
 
     static inline uint32_t offsetOfProto() {
         return offsetof(ObjectGroup, proto_);
     }
 
@@ -474,16 +478,17 @@ class ObjectGroup : public gc::TenuredCe
     static inline uint32_t offsetOfAddendum() {
         return offsetof(ObjectGroup, addendum_);
     }
 
     static inline uint32_t offsetOfFlags() {
         return offsetof(ObjectGroup, flags_);
     }
 
+  public:
     const ObjectGroupFlags* addressOfFlags() const {
         return &flags_;
     }
 
     // Get the bit pattern stored in an object's addendum when it has an
     // original unboxed group.
     static inline int32_t addendumOriginalUnboxedGroupValue() {
         return Addendum_OriginalUnboxedGroup << OBJECT_FLAG_ADDENDUM_SHIFT;
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -1795,22 +1795,22 @@ Shape::fixupDictionaryShapeAfterMovingGC
     Cell* cell = reinterpret_cast<Cell*>(uintptr_t(listp) & ~CellAlignMask);
     AllocKind kind = TenuredCell::fromPointer(cell)->getAllocKind();
     MOZ_ASSERT_IF(listpPointsIntoShape, IsShapeAllocKind(kind));
     MOZ_ASSERT_IF(!listpPointsIntoShape, IsObjectAllocKind(kind));
 #endif
 
     if (listpPointsIntoShape) {
         // listp points to the parent field of the next shape.
-        Shape* next = reinterpret_cast<Shape*>(uintptr_t(listp) - offsetof(Shape, parent));
+        Shape* next = Shape::fromParentFieldPointer(uintptr_t(listp));
         if (gc::IsForwarded(next))
             listp = &gc::Forwarded(next)->parent;
     } else {
         // listp points to the shape_ field of an object.
-        JSObject* last = reinterpret_cast<JSObject*>(uintptr_t(listp) - ShapedObject::offsetOfShape());
+        JSObject* last = ShapedObject::fromShapeFieldPointer(uintptr_t(listp));
         if (gc::IsForwarded(last))
             listp = gc::Forwarded(last)->as<NativeObject>().shapePtr();
     }
 }
 
 void
 Shape::fixupShapeTreeAfterMovingGC()
 {
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -1164,25 +1164,30 @@ class Shape : public gc::TenuredCell
 
     MOZ_ALWAYS_INLINE Shape* search(JSContext* cx, jsid id);
     MOZ_ALWAYS_INLINE Shape* searchLinear(jsid id);
 
     void fixupAfterMovingGC();
     void fixupGetterSetterForBarrier(JSTracer* trc);
     void updateBaseShapeAfterMovingGC();
 
-    /* For JIT usage */
-    static inline size_t offsetOfBase() { return offsetof(Shape, base_); }
+#ifdef DEBUG
+    // For JIT usage.
     static inline size_t offsetOfSlotInfo() { return offsetof(Shape, slotInfo); }
     static inline uint32_t fixedSlotsMask() { return FIXED_SLOTS_MASK; }
+#endif
 
   private:
     void fixupDictionaryShapeAfterMovingGC();
     void fixupShapeTreeAfterMovingGC();
 
+    static Shape* fromParentFieldPointer(uintptr_t p) {
+        return reinterpret_cast<Shape*>(p - offsetof(Shape, parent));
+    }
+
     static void staticAsserts() {
         JS_STATIC_ASSERT(offsetof(Shape, base_) == offsetof(js::shadow::Shape, base));
         JS_STATIC_ASSERT(offsetof(Shape, slotInfo) == offsetof(js::shadow::Shape, slotInfo));
         JS_STATIC_ASSERT(FIXED_SLOTS_SHIFT == js::shadow::Shape::FIXED_SLOTS_SHIFT);
     }
 };
 
 /* Fat Shape used for accessor properties. */
--- a/js/src/vm/ShapedObject.h
+++ b/js/src/vm/ShapedObject.h
@@ -45,16 +45,24 @@ class ShapedObject : public JSObject
 
     void setShape(Shape* shape) { shapeRef() = shape; }
     Shape* shape() const { return shapeRef(); }
 
     void traceShape(JSTracer* trc) {
         TraceEdge(trc, shapePtr(), "shape");
     }
 
+    static JSObject* fromShapeFieldPointer(uintptr_t p) {
+        return reinterpret_cast<JSObject*>(p - ShapedObject::offsetOfShape());
+    }
+
+  private:
+    // See JSObject::offsetOfGroup() comment.
+    friend class js::jit::MacroAssembler;
+
     static constexpr size_t offsetOfShape() {
         static_assert(offsetOfShapeOrExpando() == offsetof(shadow::Object, shape),
                       "shadow shape must match actual shape");
         return offsetOfShapeOrExpando();
     }
 };
 
 } // namespace js
--- a/js/src/vm/String.cpp
+++ b/js/src/vm/String.cpp
@@ -520,17 +520,18 @@ JSRope::flattenInternal(JSContext* maybe
             ReportOutOfMemory(maybecx);
         return nullptr;
     }
 
     if (!isTenured()) {
         Nursery& nursery = zone()->group()->nursery();
         if (!nursery.registerMallocedBuffer(wholeChars)) {
             js_free(wholeChars);
-            ReportOutOfMemory(maybecx);
+            if (maybecx)
+                ReportOutOfMemory(maybecx);
             return nullptr;
         }
     }
 
     pos = wholeChars;
     first_visit_node: {
         if (b == WithIncrementalBarrier) {
             JSString::writeBarrierPre(str->d.s.u2.left);
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -82,25 +82,35 @@ wasm::HasCompilerSupport(JSContext* cx)
 #ifdef JS_SIMULATOR
     if (!Simulator::supportsAtomics())
         return false;
 #endif
 
 #if defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_ARM64)
     return false;
 #else
+    return BaselineCanCompile() || IonCanCompile();
+#endif
+}
+
+// Return whether wasm compilation is allowed by prefs.  This check
+// only makes sense if HasCompilerSupport() is true.
+static bool
+HasAvailableCompilerTier(JSContext* cx)
+{
     return (cx->options().wasmBaseline() && BaselineCanCompile()) ||
            (cx->options().wasmIon() && IonCanCompile());
-#endif
 }
 
 bool
 wasm::HasSupport(JSContext* cx)
 {
-    return cx->options().wasm() && HasCompilerSupport(cx);
+    return cx->options().wasm() &&
+           HasCompilerSupport(cx) &&
+           HasAvailableCompilerTier(cx);
 }
 
 bool
 wasm::ToWebAssemblyValue(JSContext* cx, ValType targetType, HandleValue v, Val* val)
 {
     switch (targetType) {
       case ValType::I32: {
         int32_t i32;
--- a/js/xpconnect/wrappers/FilteringWrapper.h
+++ b/js/xpconnect/wrappers/FilteringWrapper.h
@@ -7,20 +7,16 @@
 #ifndef __FilteringWrapper_h__
 #define __FilteringWrapper_h__
 
 #include "XrayWrapper.h"
 #include "mozilla/Attributes.h"
 #include "js/CallNonGenericMethod.h"
 #include "js/Wrapper.h"
 
-namespace JS {
-class AutoIdVector;
-} // namespace JS
-
 namespace xpc {
 
 template <typename Base, typename Policy>
 class FilteringWrapper : public Base {
   public:
     constexpr explicit FilteringWrapper(unsigned flags) : Base(flags) {}
 
     virtual bool enter(JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -195,22 +195,16 @@ struct nsContentAndOffset
 static bool
 IsReversedDirectionFrame(nsIFrame* aFrame)
 {
   FrameBidiData bidiData = aFrame->GetBidiData();
   return !IS_SAME_DIRECTION(bidiData.embeddingLevel, bidiData.baseLevel);
 }
 
 #include "nsILineIterator.h"
-
-//non Hack prototypes
-#if 0
-static void RefreshContentFrames(nsPresContext* aPresContext, nsIContent * aStartContent, nsIContent * aEndContent);
-#endif
-
 #include "prenv.h"
 
 NS_DECLARE_FRAME_PROPERTY_DELETABLE(BoxMetricsProperty, nsBoxLayoutMetrics)
 
 static void
 InitBoxMetrics(nsIFrame* aFrame, bool aClear)
 {
   if (aClear) {
--- a/layout/reftests/w3c-css/received/reftest.list
+++ b/layout/reftests/w3c-css/received/reftest.list
@@ -916,47 +916,47 @@ fuzzy-if(OSX||winWidget,110,1200) == css
 fuzzy-if(OSX||winWidget,110,1200) == css-writing-modes/row-progression-vrl-004.xht css-writing-modes/block-flow-direction-001-ref.xht
 fuzzy-if(OSX||winWidget,110,1200) == css-writing-modes/row-progression-vrl-006.xht css-writing-modes/block-flow-direction-001-ref.xht
 fuzzy-if(OSX||winWidget,110,1200) == css-writing-modes/row-progression-vrl-008.xht css-writing-modes/block-flow-direction-001-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vlr-001.xht css-writing-modes/sizing-orthog-htb-in-vlr-001-ref.xht
 fails-if(Android) == css-writing-modes/sizing-orthog-htb-in-vlr-003.xht css-writing-modes/sizing-orthog-htb-in-vlr-003-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vlr-004.xht css-writing-modes/sizing-orthog-htb-in-vlr-004-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vlr-006.xht css-writing-modes/sizing-orthog-htb-in-vlr-006-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vlr-007.xht css-writing-modes/sizing-orthog-htb-in-vlr-007-ref.xht
-fails-if(OSX||winWidget) == css-writing-modes/sizing-orthog-htb-in-vlr-008.xht css-writing-modes/sizing-orthog-htb-in-vlr-008-ref.xht
+fails-if(OSX||winWidget||Android) == css-writing-modes/sizing-orthog-htb-in-vlr-008.xht css-writing-modes/sizing-orthog-htb-in-vlr-008-ref.xht
 fails-if(Android) == css-writing-modes/sizing-orthog-htb-in-vlr-009.xht css-writing-modes/sizing-orthog-htb-in-vlr-003-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vlr-010.xht css-writing-modes/sizing-orthog-htb-in-vlr-010-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vlr-011.xht css-writing-modes/sizing-orthog-htb-in-vlr-011-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vlr-012.xht css-writing-modes/sizing-orthog-htb-in-vlr-006-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vlr-013.xht css-writing-modes/sizing-orthog-htb-in-vlr-013-ref.xht
 fails-if(Android) == css-writing-modes/sizing-orthog-htb-in-vlr-015.xht css-writing-modes/sizing-orthog-htb-in-vlr-015-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vlr-016.xht css-writing-modes/sizing-orthog-htb-in-vlr-016-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vlr-018.xht css-writing-modes/sizing-orthog-htb-in-vlr-018-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vlr-019.xht css-writing-modes/sizing-orthog-htb-in-vlr-019-ref.xht
-fails-if(OSX||winWidget) == css-writing-modes/sizing-orthog-htb-in-vlr-020.xht css-writing-modes/sizing-orthog-htb-in-vlr-020-ref.xht
+fails-if(OSX||winWidget||Android) == css-writing-modes/sizing-orthog-htb-in-vlr-020.xht css-writing-modes/sizing-orthog-htb-in-vlr-020-ref.xht
 fails-if(Android) == css-writing-modes/sizing-orthog-htb-in-vlr-021.xht css-writing-modes/sizing-orthog-htb-in-vlr-015-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vlr-022.xht css-writing-modes/sizing-orthog-htb-in-vlr-022-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vlr-023.xht css-writing-modes/sizing-orthog-htb-in-vlr-023-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vlr-024.xht css-writing-modes/sizing-orthog-htb-in-vlr-018-ref.xht
 fails == css-writing-modes/sizing-orthog-htb-in-vrl-001.xht css-writing-modes/sizing-orthog-htb-in-vrl-001-ref.xht
 fails-if(Android) == css-writing-modes/sizing-orthog-htb-in-vrl-003.xht css-writing-modes/sizing-orthog-htb-in-vrl-003-ref.xht
 fails == css-writing-modes/sizing-orthog-htb-in-vrl-004.xht css-writing-modes/sizing-orthog-htb-in-vlr-004-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vrl-006.xht css-writing-modes/sizing-orthog-htb-in-vrl-006-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vrl-007.xht css-writing-modes/sizing-orthog-htb-in-vrl-007-ref.xht
-fails-if(OSX||winWidget) == css-writing-modes/sizing-orthog-htb-in-vrl-008.xht css-writing-modes/sizing-orthog-htb-in-vrl-008-ref.xht
+fails-if(OSX||winWidget||Android) == css-writing-modes/sizing-orthog-htb-in-vrl-008.xht css-writing-modes/sizing-orthog-htb-in-vrl-008-ref.xht
 fails-if(Android) == css-writing-modes/sizing-orthog-htb-in-vrl-009.xht css-writing-modes/sizing-orthog-htb-in-vrl-003-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vrl-010.xht css-writing-modes/sizing-orthog-htb-in-vrl-010-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vrl-011.xht css-writing-modes/sizing-orthog-htb-in-vrl-011-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vrl-012.xht css-writing-modes/sizing-orthog-htb-in-vrl-006-ref.xht
 fails == css-writing-modes/sizing-orthog-htb-in-vrl-013.xht css-writing-modes/sizing-orthog-htb-in-vrl-013-ref.xht
 fails-if(Android) == css-writing-modes/sizing-orthog-htb-in-vrl-015.xht css-writing-modes/sizing-orthog-htb-in-vrl-015-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vrl-016.xht css-writing-modes/sizing-orthog-htb-in-vlr-016-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vrl-018.xht css-writing-modes/sizing-orthog-htb-in-vrl-018-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vrl-019.xht css-writing-modes/sizing-orthog-htb-in-vrl-019-ref.xht
-fails-if(OSX||winWidget) == css-writing-modes/sizing-orthog-htb-in-vrl-020.xht css-writing-modes/sizing-orthog-htb-in-vrl-020-ref.xht
+fails-if(OSX||winWidget||Android) == css-writing-modes/sizing-orthog-htb-in-vrl-020.xht css-writing-modes/sizing-orthog-htb-in-vrl-020-ref.xht
 fails-if(Android) == css-writing-modes/sizing-orthog-htb-in-vrl-021.xht css-writing-modes/sizing-orthog-htb-in-vrl-015-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vrl-022.xht css-writing-modes/sizing-orthog-htb-in-vrl-022-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vrl-023.xht css-writing-modes/sizing-orthog-htb-in-vrl-023-ref.xht
 == css-writing-modes/sizing-orthog-htb-in-vrl-024.xht css-writing-modes/sizing-orthog-htb-in-vrl-018-ref.xht
 == css-writing-modes/sizing-orthog-prct-htb-in-vlr-001.xht css-writing-modes/sizing-orthog-prct-htb-in-vlr-001-ref.xht
 == css-writing-modes/sizing-orthog-prct-htb-in-vlr-002.xht css-writing-modes/sizing-orthog-prct-htb-in-vlr-002-ref.xht
 == css-writing-modes/sizing-orthog-prct-htb-in-vlr-003.xht css-writing-modes/sizing-orthog-prct-htb-in-vlr-003-ref.xht
 == css-writing-modes/sizing-orthog-prct-htb-in-vlr-004.xht css-writing-modes/sizing-orthog-prct-htb-in-vlr-004-ref.xht
--- a/mfbt/SegmentedVector.h
+++ b/mfbt/SegmentedVector.h
@@ -134,16 +134,21 @@ public:
     // size should be less than the size of a single element... unless the
     // ideal size was too small, in which case the capacity should be one.
     MOZ_ASSERT_IF(
       aIdealSegmentSize != 0,
       (sizeof(Segment) > aIdealSegmentSize && kSegmentCapacity == 1) ||
       aIdealSegmentSize - sizeof(Segment) < sizeof(T));
   }
 
+  SegmentedVector(SegmentedVector&& aOther)
+    : mSegments(mozilla::Move(aOther.mSegments))
+  {
+  }
+
   ~SegmentedVector() { Clear(); }
 
   bool IsEmpty() const { return !mSegments.getFirst(); }
 
   // Note that this is O(n) rather than O(1), but the constant factor is very
   // small because it only has to do one addition per segment.
   size_t Length() const
   {
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -3591,17 +3591,17 @@ public class BrowserApp extends GeckoApp
     }
 
     @Override
     public void closeOptionsMenu() {
         if (!mBrowserToolbar.closeOptionsMenu())
             super.closeOptionsMenu();
     }
 
-    @Override // GeckoView.ContentListener
+    @Override // GeckoView.ContentDelegate
     public void onFullScreen(final GeckoSession session, final boolean fullscreen) {
         super.onFullScreen(session, fullscreen);
 
         if (fullscreen) {
             mDynamicToolbar.setVisible(false, VisibilityTransition.IMMEDIATE);
             mDynamicToolbar.setPinned(true, PinReason.FULL_SCREEN);
         } else {
             mDynamicToolbar.setPinned(false, PinReason.FULL_SCREEN);
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -106,17 +106,17 @@ import static org.mozilla.gecko.Tabs.INV
 import static org.mozilla.gecko.mma.MmaDelegate.DOWNLOAD_MEDIA_SAVED_IMAGE;
 import static org.mozilla.gecko.mma.MmaDelegate.READER_AVAILABLE;
 
 public abstract class GeckoApp extends GeckoActivity
                                implements AnchoredPopup.OnVisibilityChangeListener,
                                           BundleEventListener,
                                           GeckoMenu.Callback,
                                           GeckoMenu.MenuPresenter,
-                                          GeckoSession.ContentListener,
+                                          GeckoSession.ContentDelegate,
                                           ScreenOrientationDelegate,
                                           Tabs.OnTabsChangedListener,
                                           ViewTreeObserver.OnGlobalLayoutListener {
 
     private static final String LOGTAG = "GeckoApp";
     private static final long ONE_DAY_MS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS);
 
     public static final String ACTION_ALERT_CALLBACK       = "org.mozilla.gecko.ALERT_CALLBACK";
@@ -868,29 +868,29 @@ public abstract class GeckoApp extends G
                 inSampleSize = Math.round((float)height / idealHeight);
             } else {
                 inSampleSize = Math.round((float)width / idealWidth);
             }
         }
         return inSampleSize;
     }
 
-    @Override // GeckoSession.ContentListener
+    @Override // GeckoSession.ContentDelegate
     public void onTitleChange(final GeckoSession session, final String title) {
     }
 
-    @Override // GeckoSession.ContentListener
+    @Override // GeckoSession.ContentDelegate
     public void onFocusRequest(final GeckoSession session) {
     }
 
-    @Override // GeckoSession.ContentListener
+    @Override // GeckoSession.ContentDelegate
     public void onCloseRequest(final GeckoSession session) {
     }
 
-    @Override // GeckoSession.ContentListener
+    @Override // GeckoSession.ContentDelegate
     public void onFullScreen(final GeckoSession session, final boolean fullScreen) {
         if (fullScreen) {
             SnackbarBuilder.builder(this)
                     .message(R.string.fullscreen_warning)
                     .duration(Snackbar.LENGTH_LONG).buildAndShow();
         }
         ThreadUtils.assertOnUiThread();
         ActivityUtils.setFullScreen(this, fullScreen);
@@ -1082,17 +1082,17 @@ public abstract class GeckoApp extends G
         if (mLayerView.getSession() != null) {
             mLayerView.getSession().closeWindow();
         }
         mLayerView.setSession(session);
         mLayerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
 
         session.getSettings().setString(GeckoSessionSettings.CHROME_URI,
                                         "chrome://browser/content/browser.xul");
-        session.setContentListener(this);
+        session.setContentDelegate(this);
 
         GeckoAccessibility.setDelegate(mLayerView);
 
         getAppEventDispatcher().registerGeckoThreadListener(this,
             "Locale:Set",
             "PrivateBrowsing:Data",
             null);
 
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/ActionBarPresenter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/ActionBarPresenter.java
@@ -29,17 +29,17 @@ import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.TextView;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SiteIdentity;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.toolbar.SecurityModeUtil;
 import org.mozilla.gecko.util.ColorUtil;
-import org.mozilla.geckoview.GeckoSession.ProgressListener.SecurityInformation;
+import org.mozilla.geckoview.GeckoSession.ProgressDelegate.SecurityInformation;
 import org.mozilla.geckoview.GeckoView;
 
 /**
  * This class is used to maintain appearance of ActionBar of CustomTabsActivity, includes background
  * color, custom-view and so on.
  */
 public class ActionBarPresenter {
 
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -68,19 +68,19 @@ import org.mozilla.geckoview.GeckoSessio
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.GeckoView;
 
 import java.util.List;
 
 public class CustomTabsActivity extends AppCompatActivity
                                 implements ActionModePresenter,
                                            GeckoMenu.Callback,
-                                           GeckoSession.ContentListener,
-                                           GeckoSession.NavigationListener,
-                                           GeckoSession.ProgressListener {
+                                           GeckoSession.ContentDelegate,
+                                           GeckoSession.NavigationDelegate,
+                                           GeckoSession.ProgressDelegate {
 
     private static final String LOGTAG = "CustomTabsActivity";
 
     private final SparseArrayCompat<PendingIntent> menuItemsIntent = new SparseArrayCompat<>();
     private GeckoPopupMenu popupMenu;
     private View doorhangerOverlay;
     private ActionBarPresenter actionBarPresenter;
     private ProgressBar mProgressView;
@@ -129,19 +129,19 @@ public class CustomTabsActivity extends 
 
         mGeckoView = (GeckoView) findViewById(R.id.gecko_view);
 
         GeckoAccessibility.setDelegate(mGeckoView);
 
         mGeckoSession = new GeckoSession();
         mGeckoView.setSession(mGeckoSession);
 
-        mGeckoSession.setNavigationListener(this);
-        mGeckoSession.setProgressListener(this);
-        mGeckoSession.setContentListener(this);
+        mGeckoSession.setNavigationDelegate(this);
+        mGeckoSession.setProgressDelegate(this);
+        mGeckoSession.setContentDelegate(this);
 
         mPromptService = new PromptService(this, mGeckoView.getEventDispatcher());
         mDoorHangerPopup = new DoorHangerPopup(this, mGeckoView.getEventDispatcher());
 
         mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup);
         mFormAssistPopup.create(mGeckoView);
 
         mTextSelection = TextSelection.Factory.create(mGeckoView, this);
@@ -586,17 +586,17 @@ public class CustomTabsActivity extends 
         }
         String referrerName = intent.getStringExtra("android.intent.extra.REFERRER_NAME");
         if (referrerName != null) {
             return Uri.parse(referrerName).getHost();
         }
         return null;
     }
 
-    /* GeckoSession.NavigationListener */
+    /* GeckoSession.NavigationDelegate */
     @Override
     public void onLocationChange(GeckoSession session, String url) {
         mCurrentUrl = url;
         updateActionBar();
         updateProgress(60);
     }
 
     @Override
@@ -651,17 +651,17 @@ public class CustomTabsActivity extends 
 
     @Override
     public void onNewSession(final GeckoSession session, final String uri,
                              final GeckoSession.Response<GeckoSession> response) {
         // We should never get here because we abort loads that need a new session in onLoadUri()
         throw new IllegalStateException("Unexpected new session");
     }
 
-    /* GeckoSession.ProgressListener */
+    /* GeckoSession.ProgressDelegate */
     @Override
     public void onPageStart(GeckoSession session, String url) {
         mCurrentUrl = url;
         mCanStop = true;
         updateActionBar();
         updateCanStop();
         updateProgress(20);
     }
@@ -674,17 +674,17 @@ public class CustomTabsActivity extends 
     }
 
     @Override
     public void onSecurityChange(GeckoSession session, SecurityInformation securityInfo) {
         mSecurityInformation = securityInfo;
         updateActionBar();
     }
 
-    /* GeckoSession.ContentListener */
+    /* GeckoSession.ContentDelegate */
     @Override
     public void onTitleChange(GeckoSession session, String title) {
         mCurrentTitle = title;
         updateActionBar();
     }
 
     @Override
     public void onFocusRequest(GeckoSession session) {
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsSecurityPopup.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsSecurityPopup.java
@@ -28,17 +28,17 @@ import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.AnchoredPopup;
 import org.mozilla.gecko.widget.DoorHanger;
 import org.mozilla.gecko.widget.DoorHanger.OnButtonClickListener;
-import org.mozilla.geckoview.GeckoSession.ProgressListener.SecurityInformation;
+import org.mozilla.geckoview.GeckoSession.ProgressDelegate.SecurityInformation;
 import org.mozilla.geckoview.GeckoView;
 
 import android.app.Activity;
 import android.content.Context;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
--- a/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
@@ -48,18 +48,18 @@ import org.mozilla.gecko.util.ActivityUt
 import org.mozilla.gecko.util.ColorUtil;
 import org.mozilla.gecko.widget.ActionModePresenter;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.GeckoView;
 
 public class WebAppActivity extends AppCompatActivity
                             implements ActionModePresenter,
-                                       GeckoSession.ContentListener,
-                                       GeckoSession.NavigationListener {
+                                       GeckoSession.ContentDelegate,
+                                       GeckoSession.NavigationDelegate {
     private static final String LOGTAG = "WebAppActivity";
 
     public static final String MANIFEST_PATH = "MANIFEST_PATH";
     public static final String MANIFEST_URL = "MANIFEST_URL";
     private static final String SAVED_INTENT = "savedIntent";
 
     private GeckoSession mGeckoSession;
     private GeckoView mGeckoView;
@@ -98,19 +98,19 @@ public class WebAppActivity extends AppC
 
         super.onCreate(savedInstanceState);
         setContentView(R.layout.webapp_activity);
         mGeckoView = (GeckoView) findViewById(R.id.pwa_gecko_view);
 
         mGeckoSession = new GeckoSession();
         mGeckoView.setSession(mGeckoSession);
 
-        mGeckoSession.setNavigationListener(this);
-        mGeckoSession.setContentListener(this);
-        mGeckoSession.setProgressListener(new GeckoSession.ProgressListener() {
+        mGeckoSession.setNavigationDelegate(this);
+        mGeckoSession.setContentDelegate(this);
+        mGeckoSession.setProgressDelegate(new GeckoSession.ProgressDelegate() {
             @Override
             public void onPageStart(GeckoSession session, String url) {
 
             }
 
             @Override
             public void onPageStop(GeckoSession session, boolean success) {
 
@@ -327,58 +327,58 @@ public class WebAppActivity extends AppC
             default:
                 mode = GeckoSessionSettings.DISPLAY_MODE_BROWSER;
                 break;
         }
 
         mGeckoView.getSettings().setInt(GeckoSessionSettings.DISPLAY_MODE, mode);
     }
 
-    @Override // GeckoSession.NavigationListener
+    @Override // GeckoSession.NavigationDelegate
     public void onLocationChange(GeckoSession session, String url) {
     }
 
-    @Override // GeckoSession.NavigationListener
+    @Override // GeckoSession.NavigationDelegate
     public void onCanGoBack(GeckoSession session, boolean canGoBack) {
         mCanGoBack = canGoBack;
     }
 
-    @Override // GeckoSession.NavigationListener
+    @Override // GeckoSession.NavigationDelegate
     public void onCanGoForward(GeckoSession session, boolean canGoForward) {
     }
 
-    @Override // GeckoSession.ContentListener
+    @Override // GeckoSession.ContentDelegate
     public void onTitleChange(GeckoSession session, String title) {
     }
 
-    @Override // GeckoSession.ContentListener
+    @Override // GeckoSession.ContentDelegate
     public void onFocusRequest(GeckoSession session) {
         Intent intent = new Intent(getIntent());
         intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
         startActivity(intent);
     }
 
-    @Override // GeckoSession.ContentListener
+    @Override // GeckoSession.ContentDelegate
     public void onCloseRequest(GeckoSession session) {
         // Ignore
     }
 
-    @Override // GeckoSession.ContentListener
+    @Override // GeckoSession.ContentDelegate
     public void onContextMenu(GeckoSession session, int screenX, int screenY,
                               String uri, String elementSrc) {
         final String content = uri != null ? uri : elementSrc != null ? elementSrc : "";
         final Uri validUri = WebApps.getValidURL(content);
         if (validUri == null) {
             return;
         }
 
         WebApps.openInFennec(validUri, WebAppActivity.this);
     }
 
-    @Override // GeckoSession.ContentListener
+    @Override // GeckoSession.ContentDelegate
     public void onFullScreen(GeckoSession session, boolean fullScreen) {
         updateFullScreenContent(fullScreen);
     }
 
     @Override
     public boolean onLoadUri(final GeckoSession session, final String urlStr,
                              final TargetWindow where) {
         final Uri uri = Uri.parse(urlStr);
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseGeckoViewTest.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseGeckoViewTest.java
@@ -58,26 +58,26 @@ public class BaseGeckoViewTest {
     }
 
     protected void loadTestPath(final String path, Runnable finished) {
         loadTestPage(buildAssetUrl(path), finished);
     }
 
     protected void loadTestPage(final String url, final Runnable finished) {
         final String path = Uri.parse(url).getPath();
-        mSession.setProgressListener(new GeckoSession.ProgressListener() {
+        mSession.setProgressDelegate(new GeckoSession.ProgressDelegate() {
             @Override
             public void onPageStart(GeckoSession session, String loadingUrl) {
                 assertTrue("Loaded url should end with " + path, loadingUrl.endsWith(path));
             }
 
             @Override
             public void onPageStop(GeckoSession session, boolean success) {
                 assertTrue("Load should succeed", success);
-                mSession.setProgressListener(null);
+                mSession.setProgressDelegate(null);
                 finished.run();
             }
 
             @Override
             public void onSecurityChange(GeckoSession session, SecurityInformation securityInfo) {
             }
         });
 
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoSessionTestRuleTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoSessionTestRuleTest.kt
@@ -22,17 +22,17 @@ import org.junit.Test
 import org.junit.rules.ErrorCollector
 import org.junit.runner.RunWith
 
 inline fun GeckoSession.loadTestPath(path: String) =
         this.loadUri(GeckoSessionTestRule.APK_URI_PREFIX + path.removePrefix("/"))
 
 /**
  * Test for the GeckoSessionTestRule class, to ensure it properly sets up a session for
- * each test, and to ensure it can properly wait for and assert listener/delegate
+ * each test, and to ensure it can properly wait for and assert delegate
  * callbacks.
  */
 @RunWith(AndroidJUnit4::class)
 @MediumTest
 class GeckoSessionTestRuleTest {
     companion object {
         const val HELLO_HTML_PATH = "assets/www/hello.html"
     }
@@ -70,96 +70,94 @@ class GeckoSessionTestRuleTest {
     @LargeTest
     fun noPendingCallbacks() {
         // Make sure we don't have unexpected pending callbacks at the start of a test.
         sessionRule.waitUntilCalled(object : Callbacks.All {})
     }
 
     @Test fun includesAllCallbacks() {
         for (ifce in GeckoSession::class.java.classes) {
-            if (!ifce.isInterface || (
-                    !ifce.simpleName.endsWith("Listener") &&
-                    !ifce.simpleName.endsWith("Delegate"))) {
+            if (!ifce.isInterface || !ifce.simpleName.endsWith("Delegate")) {
                 continue
             }
             assertThat("Callbacks.All should include interface " + ifce.simpleName,
                        ifce.isInstance(Callbacks.Default), equalTo(true))
         }
     }
 
     @Test fun waitForPageStop() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
         var counter = 0
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 counter++
             }
         })
 
         assertThat("Callback count should be correct", counter, equalTo(1))
     }
 
     @Test(expected = AssertionError::class)
     fun waitForPageStop_throwOnChangedCallback() {
-        sessionRule.session.progressListener = Callbacks.Default
+        sessionRule.session.progressDelegate = Callbacks.Default
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
     }
 
     @Test fun waitForPageStops() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.session.reload()
         sessionRule.waitForPageStops(2)
 
         var counter = 0
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 counter++
             }
         })
 
         assertThat("Callback count should be correct", counter, equalTo(2))
     }
 
     @Test fun waitUntilCalled_anyInterfaceMethod() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
-        sessionRule.waitUntilCalled(GeckoSession.ProgressListener::class)
+        sessionRule.waitUntilCalled(GeckoSession.ProgressDelegate::class)
 
         var counter = 0
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             override fun onPageStart(session: GeckoSession, url: String) {
                 counter++
             }
 
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 counter++
             }
 
             override fun onSecurityChange(session: GeckoSession,
-                                          securityInfo: GeckoSession.ProgressListener.SecurityInformation) {
+                                          securityInfo: GeckoSession.ProgressDelegate.SecurityInformation) {
                 counter++
             }
         })
 
         assertThat("Callback count should be correct", counter, equalTo(1))
     }
 
     @Test fun waitUntilCalled_specificInterfaceMethod() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
-        sessionRule.waitUntilCalled(GeckoSession.ProgressListener::class,
+        sessionRule.waitUntilCalled(GeckoSession.ProgressDelegate::class,
                                      "onPageStart", "onPageStop")
 
         var counter = 0
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             override fun onPageStart(session: GeckoSession, url: String) {
                 counter++
             }
 
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 counter++
             }
         })
@@ -173,40 +171,40 @@ class GeckoSessionTestRuleTest {
         sessionRule.waitUntilCalled(CharSequence::class)
     }
 
     @Test fun waitUntilCalled_anyObjectMethod() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
 
         var counter = 0
 
-        sessionRule.waitUntilCalled(object : Callbacks.ProgressListener {
+        sessionRule.waitUntilCalled(object : Callbacks.ProgressDelegate {
             override fun onPageStart(session: GeckoSession, url: String) {
                 counter++
             }
 
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 counter++
             }
 
             override fun onSecurityChange(session: GeckoSession,
-                                          securityInfo: GeckoSession.ProgressListener.SecurityInformation) {
+                                          securityInfo: GeckoSession.ProgressDelegate.SecurityInformation) {
                 counter++
             }
         })
 
         assertThat("Callback count should be correct", counter, equalTo(1))
     }
 
     @Test fun waitUntilCalled_specificObjectMethod() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
 
         var counter = 0
 
-        sessionRule.waitUntilCalled(object : Callbacks.ProgressListener {
+        sessionRule.waitUntilCalled(object : Callbacks.ProgressDelegate {
             @AssertCalled
             override fun onPageStart(session: GeckoSession, url: String) {
                 counter++
             }
 
             @AssertCalled
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 counter++
@@ -217,17 +215,17 @@ class GeckoSessionTestRuleTest {
     }
 
     @Test fun waitUntilCalled_multipleCount() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.session.reload()
 
         var counter = 0
 
-        sessionRule.waitUntilCalled(object : Callbacks.ProgressListener {
+        sessionRule.waitUntilCalled(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 2)
             override fun onPageStart(session: GeckoSession, url: String) {
                 counter++
             }
 
             @AssertCalled(count = 2)
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 counter++
@@ -238,17 +236,17 @@ class GeckoSessionTestRuleTest {
     }
 
     @Test fun waitUntilCalled_currentCall() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.session.reload()
 
         var counter = 0
 
-        sessionRule.waitUntilCalled(object : Callbacks.ProgressListener {
+        sessionRule.waitUntilCalled(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 2, order = intArrayOf(1, 2))
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 val info = sessionRule.currentCall
                 assertThat("Method info should be valid", info, notNullValue())
                 assertThat("Counter should be correct",
                            info.counter, isOneOf(1, 2))
                 assertThat("Order should equal counter",
                            info.order, equalTo(info.counter))
@@ -260,40 +258,40 @@ class GeckoSessionTestRuleTest {
     }
 
     @Test fun forCallbacksDuringWait_anyMethod() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
         var counter = 0
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 counter++
             }
         })
 
         assertThat("Callback count should be correct", counter, equalTo(1))
     }
 
     @Test(expected = AssertionError::class)
     fun forCallbacksDuringWait_throwOnAnyMethodNotCalled() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
-        sessionRule.forCallbacksDuringWait(GeckoSession.ScrollListener { _, _, _ -> })
+        sessionRule.forCallbacksDuringWait(GeckoSession.ScrollDelegate { _, _, _ -> })
     }
 
     @Test fun forCallbacksDuringWait_specificMethod() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
         var counter = 0
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             @AssertCalled
             override fun onPageStart(session: GeckoSession, url: String) {
                 counter++
             }
 
             @AssertCalled
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 counter++
@@ -305,17 +303,17 @@ class GeckoSessionTestRuleTest {
 
     @Test fun forCallbacksDuringWait_specificMethodMultipleTimes() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.session.reload()
         sessionRule.waitForPageStops(2)
 
         var counter = 0
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             @AssertCalled
             override fun onPageStart(session: GeckoSession, url: String) {
                 counter++
             }
 
             @AssertCalled
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 counter++
@@ -326,27 +324,27 @@ class GeckoSessionTestRuleTest {
     }
 
     @Test(expected = AssertionError::class)
     fun forCallbacksDuringWait_throwOnSpecificMethodNotCalled() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(
-                GeckoSession.ScrollListener @AssertCalled { _, _, _ -> })
+                GeckoSession.ScrollDelegate @AssertCalled { _, _, _ -> })
     }
 
     @Test fun forCallbacksDuringWait_specificCount() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.session.reload()
         sessionRule.waitForPageStops(2)
 
         var counter = 0
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 2)
             override fun onPageStart(session: GeckoSession, url: String) {
                 counter++
             }
 
             @AssertCalled(count = 2)
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 counter++
@@ -357,105 +355,105 @@ class GeckoSessionTestRuleTest {
     }
 
     @Test(expected = AssertionError::class)
     fun forCallbacksDuringWait_throwOnWrongCount() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.session.reload()
         sessionRule.waitForPageStops(2)
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 1)
             override fun onPageStart(session: GeckoSession, url: String) {
             }
 
             @AssertCalled(count = 1)
             override fun onPageStop(session: GeckoSession, success: Boolean) {
             }
         })
     }
 
     @Test fun forCallbacksDuringWait_specificOrder() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             @AssertCalled(order = intArrayOf(1))
             override fun onPageStart(session: GeckoSession, url: String) {
             }
 
             @AssertCalled(order = intArrayOf(2))
             override fun onPageStop(session: GeckoSession, success: Boolean) {
             }
         })
     }
 
     @Test(expected = AssertionError::class)
     fun forCallbacksDuringWait_throwOnWrongOrder() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             @AssertCalled(order = intArrayOf(2))
             override fun onPageStart(session: GeckoSession, url: String) {
             }
 
             @AssertCalled(order = intArrayOf(1))
             override fun onPageStop(session: GeckoSession, success: Boolean) {
             }
         })
     }
 
     @Test fun forCallbacksDuringWait_multipleOrder() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.session.reload()
         sessionRule.waitForPageStops(2)
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             @AssertCalled(order = intArrayOf(1, 3, 1))
             override fun onPageStart(session: GeckoSession, url: String) {
             }
 
             @AssertCalled(order = intArrayOf(2, 4, 1))
             override fun onPageStop(session: GeckoSession, success: Boolean) {
             }
         })
     }
 
     @Test(expected = AssertionError::class)
     fun forCallbacksDuringWait_throwOnWrongMultipleOrder() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.session.reload()
         sessionRule.waitForPageStops(2)
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             @AssertCalled(order = intArrayOf(1, 2, 1))
             override fun onPageStart(session: GeckoSession, url: String) {
             }
 
             @AssertCalled(order = intArrayOf(3, 4, 1))
             override fun onPageStop(session: GeckoSession, success: Boolean) {
             }
         })
     }
 
     @Test fun forCallbacksDuringWait_notCalled() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(
-                GeckoSession.ScrollListener @AssertCalled(false) { _, _, _ -> })
+                GeckoSession.ScrollDelegate @AssertCalled(false) { _, _, _ -> })
     }
 
     @Test(expected = AssertionError::class)
     fun forCallbacksDuringWait_throwOnCallingNoCall() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             @AssertCalled(false)
             override fun onPageStop(session: GeckoSession, success: Boolean) {
             }
         })
     }
 
     @Test fun forCallbacksDuringWait_limitedToLastWait() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
@@ -467,17 +465,17 @@ class GeckoSessionTestRuleTest {
         Thread.sleep(100)
 
         sessionRule.waitForPageStop() // Wait for loadUri.
         sessionRule.waitForPageStop() // Wait for first reload.
 
         var counter = 0
 
         // assert should only apply to callbacks within range (loadUri, first reload].
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 1)
             override fun onPageStart(session: GeckoSession, url: String) {
                 counter++
             }
 
             @AssertCalled(count = 1)
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 counter++
@@ -486,17 +484,17 @@ class GeckoSessionTestRuleTest {
 
         assertThat("Callback count should be correct", counter, equalTo(2))
     }
 
     @Test fun forCallbacksDuringWait_currentCall() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 1)
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 val info = sessionRule.currentCall
                 assertThat("Method info should be valid", info, notNullValue())
                 assertThat("Counter should be correct",
                            info.counter, equalTo(1))
                 assertThat("Order should equal counter",
                            info.order, equalTo(0))
@@ -507,17 +505,17 @@ class GeckoSessionTestRuleTest {
     @Test(expected = AssertionError::class)
     fun getCurrentCall_throwOnNoCurrentCall() {
         sessionRule.currentCall
     }
 
     @Test fun delegateUntilTestEnd() {
         var counter = 0
 
-        sessionRule.delegateUntilTestEnd(object : Callbacks.ProgressListener {
+        sessionRule.delegateUntilTestEnd(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 1, order = intArrayOf(1))
             override fun onPageStart(session: GeckoSession, url: String) {
                 counter++
             }
 
             @AssertCalled(count = 1, order = intArrayOf(2))
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 counter++
@@ -527,56 +525,56 @@ class GeckoSessionTestRuleTest {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
         assertThat("Callback count should be correct", counter, equalTo(2))
     }
 
     @Test fun delegateUntilTestEnd_notCalled() {
         sessionRule.delegateUntilTestEnd(
-                GeckoSession.ScrollListener @AssertCalled(false) { _, _, _ -> })
+                GeckoSession.ScrollDelegate @AssertCalled(false) { _, _, _ -> })
     }
 
     @Test(expected = AssertionError::class)
     fun delegateUntilTestEnd_throwOnNotCalled() {
         sessionRule.delegateUntilTestEnd(
-                GeckoSession.ScrollListener @AssertCalled(count = 1) { _, _, _ -> })
+                GeckoSession.ScrollDelegate @AssertCalled(count = 1) { _, _, _ -> })
         sessionRule.performTestEndCheck()
     }
 
     @Test(expected = AssertionError::class)
     fun delegateUntilTestEnd_throwOnCallingNoCall() {
-        sessionRule.delegateUntilTestEnd(object : Callbacks.ProgressListener {
+        sessionRule.delegateUntilTestEnd(object : Callbacks.ProgressDelegate {
             @AssertCalled(false)
             override fun onPageStop(session: GeckoSession, success: Boolean) {
             }
         })
 
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
     }
 
     @Test(expected = AssertionError::class)
     fun delegateUntilTestEnd_throwOnWrongOrder() {
-        sessionRule.delegateUntilTestEnd(object : Callbacks.ProgressListener {
+        sessionRule.delegateUntilTestEnd(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 1, order = intArrayOf(2))
             override fun onPageStart(session: GeckoSession, url: String) {
             }
 
             @AssertCalled(count = 1, order = intArrayOf(1))
             override fun onPageStop(session: GeckoSession, success: Boolean) {
             }
         })
 
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
     }
 
     @Test fun delegateUntilTestEnd_currentCall() {
-        sessionRule.delegateUntilTestEnd(object : Callbacks.ProgressListener {
+        sessionRule.delegateUntilTestEnd(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 1)
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 val info = sessionRule.currentCall
                 assertThat("Method info should be valid", info, notNullValue())
                 assertThat("Counter should be correct",
                            info.counter, equalTo(1))
                 assertThat("Order should equal counter",
                            info.order, equalTo(0))
@@ -585,17 +583,17 @@ class GeckoSessionTestRuleTest {
 
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
     }
 
     @Test fun delegateDuringNextWait() {
         var counter = 0
 
-        sessionRule.delegateDuringNextWait(object : Callbacks.ProgressListener {
+        sessionRule.delegateDuringNextWait(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 1, order = intArrayOf(1))
             override fun onPageStart(session: GeckoSession, url: String) {
                 counter++
             }
 
             @AssertCalled(count = 1, order = intArrayOf(2))
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 counter++
@@ -611,34 +609,34 @@ class GeckoSessionTestRuleTest {
         sessionRule.waitForPageStop()
 
         assertThat("Delegate should be cleared", counter, equalTo(2))
     }
 
     @Test(expected = AssertionError::class)
     fun delegateDuringNextWait_throwOnNotCalled() {
         sessionRule.delegateDuringNextWait(
-                GeckoSession.ScrollListener @AssertCalled(count = 1) { _, _, _ -> })
+                GeckoSession.ScrollDelegate @AssertCalled(count = 1) { _, _, _ -> })
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
     }
 
     @Test(expected = AssertionError::class)
     fun delegateDuringNextWait_throwOnNotCalledAtTestEnd() {
         sessionRule.delegateDuringNextWait(
-                GeckoSession.ScrollListener @AssertCalled(count = 1) { _, _, _ -> })
+                GeckoSession.ScrollDelegate @AssertCalled(count = 1) { _, _, _ -> })
         sessionRule.performTestEndCheck()
     }
 
     @Test fun delegateDuringNextWait_hasPrecedence() {
         var testCounter = 0
         var waitCounter = 0
 
-        sessionRule.delegateUntilTestEnd(object : Callbacks.ProgressListener,
-                                                  Callbacks.NavigationListener {
+        sessionRule.delegateUntilTestEnd(object : Callbacks.ProgressDelegate,
+                                                  Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = intArrayOf(2))
             override fun onPageStart(session: GeckoSession, url: String) {
                 testCounter++
             }
 
             @AssertCalled(count = 1, order = intArrayOf(4))
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 testCounter++
@@ -650,17 +648,17 @@ class GeckoSessionTestRuleTest {
             }
 
             @AssertCalled(count = 2, order = intArrayOf(1, 3))
             override fun onCanGoForward(session: GeckoSession, canGoForward: Boolean) {
                 testCounter++
             }
         })
 
-        sessionRule.delegateDuringNextWait(object : Callbacks.ProgressListener {
+        sessionRule.delegateDuringNextWait(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 1, order = intArrayOf(1))
             override fun onPageStart(session: GeckoSession, url: String) {
                 waitCounter++
             }
 
             @AssertCalled(count = 1, order = intArrayOf(2))
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 waitCounter++
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationListenerTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationListenerTest.kt
@@ -15,17 +15,17 @@ import org.hamcrest.Matchers.*
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.rules.ErrorCollector
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
 @MediumTest
-class NavigationListenerTest {
+class NavigationDelegateTest {
     companion object {
         const val HELLO_HTML_PATH = "/assets/www/hello.html";
         const val HELLO2_HTML_PATH = "/assets/www/hello2.html";
     }
 
     @get:Rule val sessionRule = GeckoSessionTestRule()
 
     @get:Rule val errors = ErrorCollector()
@@ -34,26 +34,26 @@ class NavigationListenerTest {
     @Before fun setUp() {
         sessionRule.errorCollector = errors
     }
 
     @Test fun load() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = intArrayOf(1))
             override fun onLoadUri(session: GeckoSession, uri: String,
-                                   where: GeckoSession.NavigationListener.TargetWindow): Boolean {
+                                   where: GeckoSession.NavigationDelegate.TargetWindow): Boolean {
                 assertThat("Session should not be null", session, notNullValue())
                 assertThat("URI should not be null", uri, notNullValue())
                 assertThat("URI should match", uri, endsWith(HELLO_HTML_PATH))
                 assertThat("Where should not be null", where, notNullValue())
                 assertThat("Where should match", where,
-                           equalTo(GeckoSession.NavigationListener.TargetWindow.CURRENT))
+                           equalTo(GeckoSession.NavigationDelegate.TargetWindow.CURRENT))
                 return false
             }
 
             @AssertCalled(count = 1, order = intArrayOf(2))
             override fun onLocationChange(session: GeckoSession, url: String) {
                 assertThat("Session should not be null", session, notNullValue())
                 assertThat("URL should not be null", url, notNullValue())
                 assertThat("URL should match", url, endsWith(HELLO_HTML_PATH))
@@ -80,23 +80,23 @@ class NavigationListenerTest {
 
     @Test fun reload() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
         sessionRule.session.reload()
         sessionRule.waitForPageStop()
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = intArrayOf(1))
             override fun onLoadUri(session: GeckoSession, uri: String,
-                                   where: GeckoSession.NavigationListener.TargetWindow): Boolean {
+                                   where: GeckoSession.NavigationDelegate.TargetWindow): Boolean {
                 assertThat("URI should match", uri, endsWith(HELLO_HTML_PATH))
                 assertThat("Where should match", where,
-                           equalTo(GeckoSession.NavigationListener.TargetWindow.CURRENT))
+                           equalTo(GeckoSession.NavigationDelegate.TargetWindow.CURRENT))
                 return false
             }
 
             @AssertCalled(count = 1, order = intArrayOf(2))
             override fun onLocationChange(session: GeckoSession, url: String) {
                 assertThat("URL should match", url, endsWith(HELLO_HTML_PATH))
             }
 
@@ -119,33 +119,33 @@ class NavigationListenerTest {
 
     @Test fun goBackAndForward() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
         sessionRule.session.loadTestPath(HELLO2_HTML_PATH)
         sessionRule.waitForPageStop()
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1)
             override fun onLocationChange(session: GeckoSession, url: String) {
                 assertThat("URL should match", url, endsWith(HELLO2_HTML_PATH))
             }
         })
 
         sessionRule.session.goBack()
         sessionRule.waitForPageStop()
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = intArrayOf(1))
             override fun onLoadUri(session: GeckoSession, uri: String,
-                                   where: GeckoSession.NavigationListener.TargetWindow): Boolean {
+                                   where: GeckoSession.NavigationDelegate.TargetWindow): Boolean {
                 assertThat("URI should match", uri, endsWith(HELLO_HTML_PATH))
                 assertThat("Where should match", where,
-                           equalTo(GeckoSession.NavigationListener.TargetWindow.CURRENT))
+                           equalTo(GeckoSession.NavigationDelegate.TargetWindow.CURRENT))
                 return false
             }
 
             @AssertCalled(count = 1, order = intArrayOf(2))
             override fun onLocationChange(session: GeckoSession, url: String) {
                 assertThat("URL should match", url, endsWith(HELLO_HTML_PATH))
             }
 
@@ -163,23 +163,23 @@ class NavigationListenerTest {
             override fun onNewSession(session: GeckoSession, uri: String,
                                       response: GeckoSession.Response<GeckoSession>) {
             }
         })
 
         sessionRule.session.goForward()
         sessionRule.waitForPageStop()
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = intArrayOf(1))
             override fun onLoadUri(session: GeckoSession, uri: String,
-                                   where: GeckoSession.NavigationListener.TargetWindow): Boolean {
+                                   where: GeckoSession.NavigationDelegate.TargetWindow): Boolean {
                 assertThat("URI should match", uri, endsWith(HELLO2_HTML_PATH))
                 assertThat("Where should match", where,
-                           equalTo(GeckoSession.NavigationListener.TargetWindow.CURRENT))
+                           equalTo(GeckoSession.NavigationDelegate.TargetWindow.CURRENT))
                 return false
             }
 
             @AssertCalled(count = 1, order = intArrayOf(2))
             override fun onLocationChange(session: GeckoSession, url: String) {
                 assertThat("URL should match", url, endsWith(HELLO2_HTML_PATH))
             }
 
@@ -196,29 +196,29 @@ class NavigationListenerTest {
             @AssertCalled(false)
             override fun onNewSession(session: GeckoSession, uri: String,
                                       response: GeckoSession.Response<GeckoSession>) {
             }
         })
     }
 
     @Test fun onLoadUri_returnTrueCancelsLoad() {
-        sessionRule.delegateDuringNextWait(object : Callbacks.NavigationListener {
+        sessionRule.delegateDuringNextWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 2)
             override fun onLoadUri(session: GeckoSession, uri: String,
-                                   where: GeckoSession.NavigationListener.TargetWindow): Boolean {
+                                   where: GeckoSession.NavigationDelegate.TargetWindow): Boolean {
                 return uri.endsWith(HELLO_HTML_PATH)
             }
         })
 
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.session.loadTestPath(HELLO2_HTML_PATH)
         sessionRule.waitForPageStop()
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 1, order = intArrayOf(1))
             override fun onPageStart(session: GeckoSession, url: String) {
                 assertThat("URL should match", url, endsWith(HELLO2_HTML_PATH))
             }
 
             @AssertCalled(count = 1, order = intArrayOf(2))
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 assertThat("Load should succeed", success, equalTo(true))
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationTests.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationTests.java
@@ -32,17 +32,17 @@ public class NavigationTests extends Bas
 
     @Test
     public void testGoBack() {
         final String startPath = "hello.html";
         loadTestPath(startPath, new Runnable() {
             @Override public void run() {
                 loadTestPath("hello2.html", new Runnable() {
                     @Override public void run() {
-                        mSession.setNavigationListener(new GeckoSession.NavigationListener() {
+                        mSession.setNavigationDelegate(new GeckoSession.NavigationDelegate() {
                             @Override
                             public void onLocationChange(GeckoSession session, String url) {
                                 assertTrue("URL should end with " + startPath + ", got " + url, url.endsWith(startPath));
                                 done();
                             }
 
                             @Override
                             public void onCanGoBack(GeckoSession session, boolean canGoBack) {
@@ -73,17 +73,17 @@ public class NavigationTests extends Bas
 
         waitUntilDone();
     }
 
     @Test
     public void testReload() {
         loadTestPath("hello.html", new Runnable() {
             @Override public void run() {
-                mSession.setProgressListener(new GeckoSession.ProgressListener() {
+                mSession.setProgressDelegate(new GeckoSession.ProgressDelegate() {
                     @Override
                     public void onPageStart(GeckoSession session, String url) {
                     }
 
                     @Override
                     public void onPageStop(GeckoSession session, boolean success) {
                         assertTrue(success);
                         done();
@@ -99,17 +99,17 @@ public class NavigationTests extends Bas
             }
         });
 
         waitUntilDone();
     }
 
     @Test
     public void testExpiredCert() {
-        mSession.setProgressListener(new GeckoSession.ProgressListener() {
+        mSession.setProgressDelegate(new GeckoSession.ProgressDelegate() {
             private boolean mNotBlank;
 
             @Override
             public void onPageStart(GeckoSession session, String url) {
                 mNotBlank = !url.equals("about:blank");
             }
 
             @Override
@@ -128,17 +128,17 @@ public class NavigationTests extends Bas
         });
 
         mSession.loadUri("https://expired.badssl.com/");
         waitUntilDone();
     }
 
     @Test
     public void testValidTLS() {
-        mSession.setProgressListener(new GeckoSession.ProgressListener() {
+        mSession.setProgressDelegate(new GeckoSession.ProgressDelegate() {
             private boolean mNotBlank;
 
             @Override
             public void onPageStart(GeckoSession session, String url) {
                 mNotBlank = !url.equals("about:blank");
             }
 
             @Override
@@ -157,17 +157,17 @@ public class NavigationTests extends Bas
         });
 
         mSession.loadUri("https://mozilla-modern.badssl.com/");
         waitUntilDone();
     }
 
     @Test
     public void testOnNewSession() {
-        mSession.setNavigationListener(new GeckoSession.NavigationListener() {
+        mSession.setNavigationDelegate(new GeckoSession.NavigationDelegate() {
             @Override
             public void onLocationChange(GeckoSession session, String url) {
             }
 
             @Override
             public void onCanGoBack(GeckoSession session, boolean canGoBack) {
 
             }
@@ -180,17 +180,17 @@ public class NavigationTests extends Bas
             @Override
             public boolean onLoadUri(GeckoSession session, String uri, TargetWindow where) {
                 return false;
             }
 
             @Override
             public void onNewSession(GeckoSession session, String uri, GeckoSession.Response<GeckoSession> response) {
                 final GeckoSession newSession = new GeckoSession(session.getSettings());
-                newSession.setContentListener(new GeckoSession.ContentListener() {
+                newSession.setContentDelegate(new GeckoSession.ContentDelegate() {
                     @Override
                     public void onTitleChange(GeckoSession session, String title) {
 
                     }
 
                     @Override
                     public void onFocusRequest(GeckoSession session) {
 
@@ -213,17 +213,17 @@ public class NavigationTests extends Bas
                     }
                 });
 
                 newSession.openWindow(InstrumentationRegistry.getTargetContext());
                 response.respond(newSession);
             }
         });
 
-        mSession.setProgressListener(new GeckoSession.ProgressListener() {
+        mSession.setProgressDelegate(new GeckoSession.ProgressDelegate() {
             @Override
             public void onPageStart(GeckoSession session, String url) {
 
             }
 
             @Override
             public void onPageStop(GeckoSession session, boolean success) {
                 // Send a click to open the window
@@ -240,20 +240,20 @@ public class NavigationTests extends Bas
         mSession.loadUri(buildAssetUrl("newSession.html"));
 
         waitUntilDone();
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void testOnNewSessionNoExisting() {
         // This makes sure that we get an exception if you try to return
-        // an existing GeckoSession instance from the NavigationListener.onNewSession()
+        // an existing GeckoSession instance from the NavigationDelegate.onNewSession()
         // implementation.
 
-        mSession.setNavigationListener(new GeckoSession.NavigationListener() {
+        mSession.setNavigationDelegate(new GeckoSession.NavigationDelegate() {
             @Override
             public void onLocationChange(GeckoSession session, String url) {
             }
 
             @Override
             public void onCanGoBack(GeckoSession session, boolean canGoBack) {
 
             }
@@ -270,17 +270,17 @@ public class NavigationTests extends Bas
 
             @Override
             public void onNewSession(GeckoSession session, String uri, GeckoSession.Response<GeckoSession> response) {
                 // This is where the throw should occur
                 response.respond(mSession);
             }
         });
 
-        mSession.setProgressListener(new GeckoSession.ProgressListener() {
+        mSession.setProgressDelegate(new GeckoSession.ProgressDelegate() {
             @Override
             public void onPageStart(GeckoSession session, String url) {
 
             }
 
             @Override
             public void onPageStop(GeckoSession session, boolean success) {
                 sendClick(100, 100);
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ProgressListenerTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ProgressListenerTest.kt
@@ -17,17 +17,17 @@ import org.hamcrest.Matchers.*
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.rules.ErrorCollector
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
 @MediumTest
-class ProgressListenerTest {
+class ProgressDelegateTest {
     companion object {
         const val INVALID_URI = "http://www.test.invalid/"
         const val HELLO_HTML_PATH = "/assets/www/hello.html";
         const val HELLO2_HTML_PATH = "/assets/www/hello2.html";
     }
 
     @get:Rule val sessionRule = GeckoSessionTestRule()
 
@@ -37,50 +37,50 @@ class ProgressListenerTest {
     @Before fun setUp() {
         sessionRule.errorCollector = errors
     }
 
     @Test fun load() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 1, order = intArrayOf(1))
             override fun onPageStart(session: GeckoSession, url: String) {
                 assertThat("Session should not be null", session, notNullValue())
                 assertThat("URL should not be null", url, notNullValue())
                 assertThat("URL should match", url, endsWith(HELLO_HTML_PATH))
             }
 
             @AssertCalled(count = 1, order = intArrayOf(2))
             override fun onSecurityChange(session: GeckoSession,
-                                          securityInfo: GeckoSession.ProgressListener.SecurityInformation) {
+                                          securityInfo: GeckoSession.ProgressDelegate.SecurityInformation) {
                 assertThat("Session should not be null", session, notNullValue())
                 assertThat("Security info should not be null", securityInfo, notNullValue())
 
                 assertThat("Should not be secure", securityInfo.isSecure, equalTo(false))
                 assertThat("Tracking mode should match",
                            securityInfo.trackingMode,
-                           equalTo(GeckoSession.ProgressListener.SecurityInformation.CONTENT_UNKNOWN))
+                           equalTo(GeckoSession.ProgressDelegate.SecurityInformation.CONTENT_UNKNOWN))
             }
 
             @AssertCalled(count = 1, order = intArrayOf(3))
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 assertThat("Session should not be null", session, notNullValue())
                 assertThat("Load should succeed", success, equalTo(true))
             }
         })
     }
 
     @Test fun multipleLoads() {
         sessionRule.session.loadUri(INVALID_URI)
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStops(2)
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 2, order = intArrayOf(1, 3))
             override fun onPageStart(session: GeckoSession, url: String) {
                 assertThat("URL should match", url,
                            endsWith(if (sessionRule.currentCall.counter == 1)
                                         INVALID_URI else HELLO_HTML_PATH))
             }
 
             @AssertCalled(count = 2, order = intArrayOf(2, 4))
@@ -95,25 +95,25 @@ class ProgressListenerTest {
 
     @Test fun reload() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
         sessionRule.session.reload()
         sessionRule.waitForPageStop()
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 1, order = intArrayOf(1))
             override fun onPageStart(session: GeckoSession, url: String) {
                 assertThat("URL should match", url, endsWith(HELLO_HTML_PATH))
             }
 
             @AssertCalled(count = 1, order = intArrayOf(2))
             override fun onSecurityChange(session: GeckoSession,
-                                          securityInfo: GeckoSession.ProgressListener.SecurityInformation) {
+                                          securityInfo: GeckoSession.ProgressDelegate.SecurityInformation) {
             }
 
             @AssertCalled(count = 1, order = intArrayOf(3))
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 assertThat("Load should succeed", success, equalTo(true))
             }
         })
     }
@@ -122,63 +122,63 @@ class ProgressListenerTest {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
         sessionRule.session.loadTestPath(HELLO2_HTML_PATH)
         sessionRule.waitForPageStop()
 
         sessionRule.session.goBack()
         sessionRule.waitForPageStop()
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 1, order = intArrayOf(1))
             override fun onPageStart(session: GeckoSession, url: String) {
                 assertThat("URL should match", url, endsWith(HELLO_HTML_PATH))
             }
 
             @AssertCalled(count = 1, order = intArrayOf(2))
             override fun onSecurityChange(session: GeckoSession,
-                                          securityInfo: GeckoSession.ProgressListener.SecurityInformation) {
+                                          securityInfo: GeckoSession.ProgressDelegate.SecurityInformation) {
             }
 
             @AssertCalled(count = 1, order = intArrayOf(3))
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 assertThat("Load should succeed", success, equalTo(true))
             }
         })
 
         sessionRule.session.goForward()
         sessionRule.waitForPageStop()
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 1, order = intArrayOf(1))
             override fun onPageStart(session: GeckoSession, url: String) {
                 assertThat("URL should match", url, endsWith(HELLO2_HTML_PATH))
             }
 
             @AssertCalled(count = 1, order = intArrayOf(2))
             override fun onSecurityChange(session: GeckoSession,
-                                          securityInfo: GeckoSession.ProgressListener.SecurityInformation) {
+                                          securityInfo: GeckoSession.ProgressDelegate.SecurityInformation) {
             }
 
             @AssertCalled(count = 1, order = intArrayOf(3))
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 assertThat("Load should succeed", success, equalTo(true))
             }
         })
     }
 
     @LargeTest
     @Test fun correctSecurityInfoForValidTLS() {
         sessionRule.session.loadUri("https://mozilla-modern.badssl.com")
         sessionRule.waitForPageStop()
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 1)
             override fun onSecurityChange(session: GeckoSession,
-                                          securityInfo: GeckoSession.ProgressListener.SecurityInformation) {
+                                          securityInfo: GeckoSession.ProgressDelegate.SecurityInformation) {
                 assertThat("Should be secure",
                            securityInfo.isSecure, equalTo(true))
                 assertThat("Should not be exception",
                            securityInfo.isException, equalTo(false))
                 assertThat("Origin should match",
                            securityInfo.origin,
                            equalTo("https://mozilla-modern.badssl.com"))
                 assertThat("Host should match",
@@ -193,40 +193,40 @@ class ProgressListenerTest {
                 assertThat("Issuer common name should match",
                            securityInfo.issuerCommonName,
                            equalTo("DigiCert SHA2 Secure Server CA"))
                 assertThat("Issuer organization should match",
                            securityInfo.issuerOrganization,
                            equalTo("DigiCert Inc"))
                 assertThat("Security mode should match",
                            securityInfo.securityMode,
-                           equalTo(GeckoSession.ProgressListener.SecurityInformation.SECURITY_MODE_IDENTIFIED))
+                           equalTo(GeckoSession.ProgressDelegate.SecurityInformation.SECURITY_MODE_IDENTIFIED))
                 assertThat("Active mixed mode should match",
                            securityInfo.mixedModeActive,
-                           equalTo(GeckoSession.ProgressListener.SecurityInformation.CONTENT_UNKNOWN))
+                           equalTo(GeckoSession.ProgressDelegate.SecurityInformation.CONTENT_UNKNOWN))
                 assertThat("Passive mixed mode should match",
                            securityInfo.mixedModePassive,
-                           equalTo(GeckoSession.ProgressListener.SecurityInformation.CONTENT_UNKNOWN))
+                           equalTo(GeckoSession.ProgressDelegate.SecurityInformation.CONTENT_UNKNOWN))
                 assertThat("Tracking mode should match",
                            securityInfo.trackingMode,
-                           equalTo(GeckoSession.ProgressListener.SecurityInformation.CONTENT_UNKNOWN))
+                           equalTo(GeckoSession.ProgressDelegate.SecurityInformation.CONTENT_UNKNOWN))
             }
         })
     }
 
     @LargeTest
     @Test fun noSecurityInfoForExpiredTLS() {
         sessionRule.session.loadUri("https://expired.badssl.com")
         sessionRule.waitForPageStop()
 
-        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressListener {
+        sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 1)
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 assertThat("Load should fail", success, equalTo(false))
             }
 
             @AssertCalled(false)
             override fun onSecurityChange(session: GeckoSession,
-                                          securityInfo: GeckoSession.ProgressListener.SecurityInformation) {
+                                          securityInfo: GeckoSession.ProgressDelegate.SecurityInformation) {
             }
         })
     }
 }
\ No newline at end of file
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java
@@ -18,17 +18,17 @@ import java.io.InputStream;
 import java.io.OutputStream;
 
 public class TestRunnerActivity extends Activity {
     private static final String LOGTAG = "TestRunnerActivity";
 
     GeckoSession mSession;
     GeckoView mView;
 
-    private GeckoSession.NavigationListener mNavigationListener = new GeckoSession.NavigationListener() {
+    private GeckoSession.NavigationDelegate mNavigationDelegate = new GeckoSession.NavigationDelegate() {
         @Override
         public void onLocationChange(GeckoSession session, String url) {
             getActionBar().setSubtitle(url);
         }
 
         @Override
         public void onCanGoBack(GeckoSession session, boolean canGoBack) {
 
@@ -46,17 +46,17 @@ public class TestRunnerActivity extends 
         }
 
         @Override
         public void onNewSession(GeckoSession session, String uri, GeckoSession.Response<GeckoSession> response) {
             response.respond(createSession(session.getSettings()));
         }
     };
 
-    private GeckoSession.ContentListener mContentListener = new GeckoSession.ContentListener() {
+    private GeckoSession.ContentDelegate mContentDelegate = new GeckoSession.ContentDelegate() {
         @Override
         public void onTitleChange(GeckoSession session, String title) {
 
         }
 
         @Override
         public void onFocusRequest(GeckoSession session) {
 
@@ -87,17 +87,17 @@ public class TestRunnerActivity extends 
             settings = new GeckoSessionSettings();
 
             // We can't use e10s because we get deadlocked when quickly creating and
             // destroying sessions. Bug 1348361.
             settings.setBoolean(GeckoSessionSettings.USE_MULTIPROCESS, false);
         }
 
         final GeckoSession session = new GeckoSession(settings);
-        session.setNavigationListener(mNavigationListener);
+        session.setNavigationDelegate(mNavigationDelegate);
         session.openWindow(this);
         return session;
     }
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
@@ -716,27 +716,27 @@ public class GeckoSessionTestRule extend
      * Wait until a page load has finished. The session must have started a page load since
      * the last wait, or this method will wait indefinitely.
      *
      * @param count Number of page loads to wait for.
      */
     public void waitForPageStops(final int count) {
         final Method onPageStop;
         try {
-            onPageStop = GeckoSession.ProgressListener.class.getMethod(
+            onPageStop = GeckoSession.ProgressDelegate.class.getMethod(
                     "onPageStop", GeckoSession.class, boolean.class);
         } catch (final NoSuchMethodException e) {
             throw new RuntimeException(e);
         }
 
         final List<MethodCall> methodCalls = new ArrayList<>(1);
         methodCalls.add(new MethodCall(onPageStop,
                 new CallRequirement(/* allowed */ true, count, null)));
 
-        waitUntilCalled(GeckoSession.ProgressListener.class, methodCalls);
+        waitUntilCalled(GeckoSession.ProgressDelegate.class, methodCalls);
     }
 
     /**
      * Wait until the specified methods have been called on the specified callback
      * interface. If no methods are specified, wait until any method has been called.
      *
      * @param callback Target callback interface; must be an interface under GeckoSession.
      * @param methods List of methods to wait on; use empty or null or wait on any method.
@@ -810,17 +810,17 @@ public class GeckoSessionTestRule extend
                 }
             }
         }
 
         waitUntilCalled(callback.getClass(), methodCalls);
         forCallbacksDuringWait(callback);
     }
 
-    protected void waitUntilCalled(final @NonNull Class<?> listener,
+    protected void waitUntilCalled(final @NonNull Class<?> delegate,
                                    final @NonNull List<MethodCall> methodCalls) {
         // Make sure all handlers are set though #delegateUntilTestEnd or #delegateDuringNextWait,
         // instead of through GeckoSession directly, so that we can still record calls even with
         // custom handlers set.
         for (final Class<?> ifce : CALLBACK_CLASSES) {
             try {
                 assertThat("Callbacks should be set through" +
                            " GeckoSessionTestRule delegate methods",
@@ -835,17 +835,17 @@ public class GeckoSessionTestRule extend
         int index = mLastWaitStart = mLastWaitEnd;
 
         while (!calledAny || !methodCalls.isEmpty()) {
             while (index >= mCallRecords.size()) {
                 loopUntilIdle(mTimeoutMillis);
             }
 
             final MethodCall recorded = mCallRecords.get(index).methodCall;
-            calledAny |= recorded.method.getDeclaringClass().isAssignableFrom(listener);
+            calledAny |= recorded.method.getDeclaringClass().isAssignableFrom(delegate);
             index++;
 
             final int i = methodCalls.indexOf(recorded);
             if (i < 0) {
                 continue;
             }
 
             final MethodCall methodCall = methodCalls.get(i);
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Callbacks.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Callbacks.kt
@@ -4,116 +4,116 @@
 package org.mozilla.geckoview.test.util
 
 import org.mozilla.geckoview.GeckoSession
 
 class Callbacks private constructor() {
     object Default : All {
     }
 
-    interface All : ContentListener, NavigationListener, PermissionDelegate, ProgressListener,
-                    PromptDelegate, ScrollListener, TrackingProtectionDelegate {
+    interface All : ContentDelegate, NavigationDelegate, PermissionDelegate, ProgressDelegate,
+                    PromptDelegate, ScrollDelegate, TrackingProtectionDelegate {
     }
 
-    interface ContentListener : GeckoSession.ContentListener {
+    interface ContentDelegate : GeckoSession.ContentDelegate {
         override fun onTitleChange(session: GeckoSession, title: String) {
         }
 
         override fun onFocusRequest(session: GeckoSession) {
         }
 
         override fun onCloseRequest(session: GeckoSession) {
         }
 
         override fun onFullScreen(session: GeckoSession, fullScreen: Boolean) {
         }
 
         override fun onContextMenu(session: GeckoSession, screenX: Int, screenY: Int, uri: String, elementSrc: String) {
         }
     }
 
-    interface NavigationListener : GeckoSession.NavigationListener {
+    interface NavigationDelegate : GeckoSession.NavigationDelegate {
         override fun onLocationChange(session: GeckoSession, url: String) {
         }
 
         override fun onCanGoBack(session: GeckoSession, canGoBack: Boolean) {
         }
 
         override fun onCanGoForward(session: GeckoSession, canGoForward: Boolean) {
         }
 
-        override fun onLoadUri(session: GeckoSession, uri: String, where: GeckoSession.NavigationListener.TargetWindow): Boolean {
+        override fun onLoadUri(session: GeckoSession, uri: String, where: GeckoSession.NavigationDelegate.TargetWindow): Boolean {
             return false;
         }
 
         override fun onNewSession(session: GeckoSession, uri: String, response: GeckoSession.Response<GeckoSession>) {
             response.respond(null)
         }
     }
 
     interface PermissionDelegate : GeckoSession.PermissionDelegate {
-        override fun requestAndroidPermissions(session: GeckoSession, permissions: Array<out String>, callback: GeckoSession.PermissionDelegate.Callback) {
+        override fun onAndroidPermissionsRequest(session: GeckoSession, permissions: Array<out String>, callback: GeckoSession.PermissionDelegate.Callback) {
             callback.reject()
         }
 
-        override fun requestContentPermission(session: GeckoSession, uri: String, type: String, access: String, callback: GeckoSession.PermissionDelegate.Callback) {
+        override fun onContentPermissionRequest(session: GeckoSession, uri: String, type: Int, access: String, callback: GeckoSession.PermissionDelegate.Callback) {
             callback.reject()
         }
 
-        override fun requestMediaPermission(session: GeckoSession, uri: String, video: Array<out GeckoSession.PermissionDelegate.MediaSource>, audio: Array<out GeckoSession.PermissionDelegate.MediaSource>, callback: GeckoSession.PermissionDelegate.MediaCallback) {
+        override fun onMediaPermissionRequest(session: GeckoSession, uri: String, video: Array<out GeckoSession.PermissionDelegate.MediaSource>, audio: Array<out GeckoSession.PermissionDelegate.MediaSource>, callback: GeckoSession.PermissionDelegate.MediaCallback) {
             callback.reject()
         }
     }
 
-    interface ProgressListener : GeckoSession.ProgressListener {
+    interface ProgressDelegate : GeckoSession.ProgressDelegate {
         override fun onPageStart(session: GeckoSession, url: String) {
         }
 
         override fun onPageStop(session: GeckoSession, success: Boolean) {
         }
 
-        override fun onSecurityChange(session: GeckoSession, securityInfo: GeckoSession.ProgressListener.SecurityInformation) {
+        override fun onSecurityChange(session: GeckoSession, securityInfo: GeckoSession.ProgressDelegate.SecurityInformation) {
         }
     }
 
     interface PromptDelegate : GeckoSession.PromptDelegate {
-        override fun alert(session: GeckoSession, title: String, msg: String, callback: GeckoSession.PromptDelegate.AlertCallback) {
+        override fun onAlert(session: GeckoSession, title: String, msg: String, callback: GeckoSession.PromptDelegate.AlertCallback) {
             callback.dismiss()
         }
 
-        override fun promptForButton(session: GeckoSession, title: String, msg: String, btnMsg: Array<out String>, callback: GeckoSession.PromptDelegate.ButtonCallback) {
+        override fun onButtonPrompt(session: GeckoSession, title: String, msg: String, btnMsg: Array<out String>, callback: GeckoSession.PromptDelegate.ButtonCallback) {
             callback.dismiss()
         }
 
-        override fun promptForText(session: GeckoSession, title: String, msg: String, value: String, callback: GeckoSession.PromptDelegate.TextCallback) {
+        override fun onTextPrompt(session: GeckoSession, title: String, msg: String, value: String, callback: GeckoSession.PromptDelegate.TextCallback) {
             callback.dismiss()
         }
 
-        override fun promptForAuth(session: GeckoSession, title: String, msg: String, options: GeckoSession.PromptDelegate.AuthenticationOptions, callback: GeckoSession.PromptDelegate.AuthCallback) {
+        override fun onAuthPrompt(session: GeckoSession, title: String, msg: String, options: GeckoSession.PromptDelegate.AuthOptions, callback: GeckoSession.PromptDelegate.AuthCallback) {
             callback.dismiss()
         }
 
-        override fun promptForChoice(session: GeckoSession, title: String, msg: String, type: Int, choices: Array<out GeckoSession.PromptDelegate.Choice>, callback: GeckoSession.PromptDelegate.ChoiceCallback) {
+        override fun onChoicePrompt(session: GeckoSession, title: String, msg: String, type: Int, choices: Array<out GeckoSession.PromptDelegate.Choice>, callback: GeckoSession.PromptDelegate.ChoiceCallback) {
             callback.dismiss()
         }
 
-        override fun promptForColor(session: GeckoSession, title: String, value: String, callback: GeckoSession.PromptDelegate.TextCallback) {
+        override fun onColorPrompt(session: GeckoSession, title: String, value: String, callback: GeckoSession.PromptDelegate.TextCallback) {
             callback.dismiss()
         }
 
-        override fun promptForDateTime(session: GeckoSession, title: String, type: Int, value: String, min: String, max: String, callback: GeckoSession.PromptDelegate.TextCallback) {
+        override fun onDateTimePrompt(session: GeckoSession, title: String, type: Int, value: String, min: String, max: String, callback: GeckoSession.PromptDelegate.TextCallback) {
             callback.dismiss()
         }
 
-        override fun promptForFile(session: GeckoSession, title: String, type: Int, mimeTypes: Array<out String>, callback: GeckoSession.PromptDelegate.FileCallback) {
+        override fun onFilePrompt(session: GeckoSession, title: String, type: Int, mimeTypes: Array<out String>, callback: GeckoSession.PromptDelegate.FileCallback) {
             callback.dismiss()
         }
     }
 
-    interface ScrollListener : GeckoSession.ScrollListener {
+    interface ScrollDelegate : GeckoSession.ScrollDelegate {
         override fun onScrollChanged(session: GeckoSession, scrollX: Int, scrollY: Int) {
         }
     }
 
     interface TrackingProtectionDelegate : GeckoSession.TrackingProtectionDelegate {
         override fun onTrackerBlocked(session: GeckoSession, uri: String, categories: Int) {
         }
     }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -72,141 +72,141 @@ public class GeckoSession extends LayerS
     private final EventDispatcher mEventDispatcher =
         new EventDispatcher(mNativeQueue);
 
     private final TextInputController mTextInput = new TextInputController(this, mNativeQueue);
 
     private String mId = UUID.randomUUID().toString().replace("-", "");
     /* package */ String getId() { return mId; }
 
-    private final GeckoSessionHandler<ContentListener> mContentHandler =
-        new GeckoSessionHandler<ContentListener>(
+    private final GeckoSessionHandler<ContentDelegate> mContentHandler =
+        new GeckoSessionHandler<ContentDelegate>(
             "GeckoViewContent", this,
             new String[]{
                 "GeckoView:ContextMenu",
                 "GeckoView:DOMTitleChanged",
                 "GeckoView:DOMWindowFocus",
                 "GeckoView:DOMWindowClose",
                 "GeckoView:FullScreenEnter",
                 "GeckoView:FullScreenExit"
             }
         ) {
             @Override
-            public void handleMessage(final ContentListener listener,
+            public void handleMessage(final ContentDelegate delegate,
                                       final String event,
                                       final GeckoBundle message,
                                       final EventCallback callback) {
 
                 if ("GeckoView:ContextMenu".equals(event)) {
-                    listener.onContextMenu(GeckoSession.this,
+                    delegate.onContextMenu(GeckoSession.this,
                                            message.getInt("screenX"),
                                            message.getInt("screenY"),
                                            message.getString("uri"),
                                            message.getString("elementSrc"));
                 } else if ("GeckoView:DOMTitleChanged".equals(event)) {
-                    listener.onTitleChange(GeckoSession.this,
+                    delegate.onTitleChange(GeckoSession.this,
                                            message.getString("title"));
                 } else if ("GeckoView:DOMWindowFocus".equals(event)) {
-                    listener.onFocusRequest(GeckoSession.this);
+                    delegate.onFocusRequest(GeckoSession.this);
                 } else if ("GeckoView:DOMWindowClose".equals(event)) {
-                    listener.onCloseRequest(GeckoSession.this);
+                    delegate.onCloseRequest(GeckoSession.this);
                 } else if ("GeckoView:FullScreenEnter".equals(event)) {
-                    listener.onFullScreen(GeckoSession.this, true);
+                    delegate.onFullScreen(GeckoSession.this, true);
                 } else if ("GeckoView:FullScreenExit".equals(event)) {
-                    listener.onFullScreen(GeckoSession.this, false);
+                    delegate.onFullScreen(GeckoSession.this, false);
                 }
             }
         };
 
-    private final GeckoSessionHandler<NavigationListener> mNavigationHandler =
-        new GeckoSessionHandler<NavigationListener>(
+    private final GeckoSessionHandler<NavigationDelegate> mNavigationHandler =
+        new GeckoSessionHandler<NavigationDelegate>(
             "GeckoViewNavigation", this,
             new String[]{
                 "GeckoView:LocationChange",
                 "GeckoView:OnLoadUri",
                 "GeckoView:OnNewSession"
             }
         ) {
             @Override
-            public void handleMessage(final NavigationListener listener,
+            public void handleMessage(final NavigationDelegate delegate,
                                       final String event,
                                       final GeckoBundle message,
                                       final EventCallback callback) {
                 if ("GeckoView:LocationChange".equals(event)) {
-                    listener.onLocationChange(GeckoSession.this,
+                    delegate.onLocationChange(GeckoSession.this,
                                               message.getString("uri"));
-                    listener.onCanGoBack(GeckoSession.this,
+                    delegate.onCanGoBack(GeckoSession.this,
                                          message.getBoolean("canGoBack"));
-                    listener.onCanGoForward(GeckoSession.this,
+                    delegate.onCanGoForward(GeckoSession.this,
                                             message.getBoolean("canGoForward"));
                 } else if ("GeckoView:OnLoadUri".equals(event)) {
                     final String uri = message.getString("uri");
-                    final NavigationListener.TargetWindow where =
-                        NavigationListener.TargetWindow.forGeckoValue(
+                    final NavigationDelegate.TargetWindow where =
+                        NavigationDelegate.TargetWindow.forGeckoValue(
                             message.getInt("where"));
                     final boolean result =
-                        listener.onLoadUri(GeckoSession.this, uri, where);
+                        delegate.onLoadUri(GeckoSession.this, uri, where);
                     callback.sendSuccess(result);
                 } else if ("GeckoView:OnNewSession".equals(event)) {
                     final String uri = message.getString("uri");
-                    listener.onNewSession(GeckoSession.this, uri,
+                    delegate.onNewSession(GeckoSession.this, uri,
                         new Response<GeckoSession>() {
                             @Override
                             public void respond(GeckoSession session) {
                                 if (session != null && session.isOpen() && session.isReady()) {
                                     throw new IllegalArgumentException("Must use a new GeckoSession instance");
                                 }
 
                                 callback.sendSuccess(session != null ? session.getId() : null);
                             }
                         });
                 }
             }
         };
 
-    private final GeckoSessionHandler<ProgressListener> mProgressHandler =
-        new GeckoSessionHandler<ProgressListener>(
+    private final GeckoSessionHandler<ProgressDelegate> mProgressHandler =
+        new GeckoSessionHandler<ProgressDelegate>(
             "GeckoViewProgress", this,
             new String[]{
                 "GeckoView:PageStart",
                 "GeckoView:PageStop",
                 "GeckoView:SecurityChanged"
             }
         ) {
             @Override
-            public void handleMessage(final ProgressListener listener,
+            public void handleMessage(final ProgressDelegate delegate,
                                       final String event,
                                       final GeckoBundle message,
                                       final EventCallback callback) {
                 if ("GeckoView:PageStart".equals(event)) {
-                    listener.onPageStart(GeckoSession.this,
+                    delegate.onPageStart(GeckoSession.this,
                                          message.getString("uri"));
                 } else if ("GeckoView:PageStop".equals(event)) {
-                    listener.onPageStop(GeckoSession.this,
+                    delegate.onPageStop(GeckoSession.this,
                                         message.getBoolean("success"));
                 } else if ("GeckoView:SecurityChanged".equals(event)) {
                     final GeckoBundle identity = message.getBundle("identity");
-                    listener.onSecurityChange(GeckoSession.this, new ProgressListener.SecurityInformation(identity));
+                    delegate.onSecurityChange(GeckoSession.this, new ProgressDelegate.SecurityInformation(identity));
                 }
             }
         };
 
-    private final GeckoSessionHandler<ScrollListener> mScrollHandler =
-        new GeckoSessionHandler<ScrollListener>(
+    private final GeckoSessionHandler<ScrollDelegate> mScrollHandler =
+        new GeckoSessionHandler<ScrollDelegate>(
             "GeckoViewScroll", this,
             new String[]{ "GeckoView:ScrollChanged" }
         ) {
             @Override
-            public void handleMessage(final ScrollListener listener,
+            public void handleMessage(final ScrollDelegate delegate,
                                       final String event,
                                       final GeckoBundle message,
                                       final EventCallback callback) {
 
                 if ("GeckoView:ScrollChanged".equals(event)) {
-                    listener.onScrollChanged(GeckoSession.this,
+                    delegate.onScrollChanged(GeckoSession.this,
                                              message.getInt("scrollX"),
                                              message.getInt("scrollY"));
                 }
             }
         };
 
     private final GeckoSessionHandler<TrackingProtectionDelegate> mTrackingProtectionHandler =
         new GeckoSessionHandler<TrackingProtectionDelegate>(
@@ -233,40 +233,40 @@ public class GeckoSession extends LayerS
             "GeckoViewPermission", this,
             new String[] {
                 "GeckoView:AndroidPermission",
                 "GeckoView:ContentPermission",
                 "GeckoView:MediaPermission"
             }, /* alwaysListen */ true
         ) {
             @Override
-            public void handleMessage(final PermissionDelegate listener,
+            public void handleMessage(final PermissionDelegate delegate,
                                       final String event,
                                       final GeckoBundle message,
                                       final EventCallback callback) {
 
-                if (listener == null) {
+                if (delegate == null) {
                     callback.sendSuccess(/* granted */ false);
                     return;
                 }
                 if ("GeckoView:AndroidPermission".equals(event)) {
-                    listener.requestAndroidPermissions(
+                    delegate.onAndroidPermissionsRequest(
                             GeckoSession.this, message.getStringArray("perms"),
                             new PermissionCallback("android", callback));
                 } else if ("GeckoView:ContentPermission".equals(event)) {
                     final String typeString = message.getString("perm");
                     final int type;
                     if ("geolocation".equals(typeString)) {
                         type = PermissionDelegate.PERMISSION_GEOLOCATION;
                     } else if ("desktop_notification".equals(typeString)) {
                         type = PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION;
                     } else {
                         throw new IllegalArgumentException("Unknown permission request: " + typeString);
                     }
-                    listener.requestContentPermission(
+                    delegate.onContentPermissionRequest(
                             GeckoSession.this, message.getString("uri"),
                             type, message.getString("access"),
                             new PermissionCallback(typeString, callback));
                 } else if ("GeckoView:MediaPermission".equals(event)) {
                     GeckoBundle[] videoBundles = message.getBundleArray("video");
                     GeckoBundle[] audioBundles = message.getBundleArray("audio");
                     PermissionDelegate.MediaSource[] videos = null;
                     PermissionDelegate.MediaSource[] audios = null;
@@ -280,17 +280,17 @@ public class GeckoSession extends LayerS
 
                     if (audioBundles != null) {
                         audios = new PermissionDelegate.MediaSource[audioBundles.length];
                         for (int i = 0; i < audioBundles.length; i++) {
                             audios[i] = new PermissionDelegate.MediaSource(audioBundles[i]);
                         }
                     }
 
-                    listener.requestMediaPermission(
+                    delegate.onMediaPermissionRequest(
                             GeckoSession.this, message.getString("uri"),
                             videos, audios, new PermissionCallback("media", callback));
                 }
             }
         };
 
     private static class PermissionCallback implements
         PermissionDelegate.Callback, PermissionDelegate.MediaCallback {
@@ -341,25 +341,25 @@ public class GeckoSession extends LayerS
         }
     }
 
     /**
      * Get the current prompt delegate for this GeckoSession.
      * @return PromptDelegate instance or null if using default delegate.
      */
     public PermissionDelegate getPermissionDelegate() {
-        return mPermissionHandler.getListener();
+        return mPermissionHandler.getDelegate();
     }
 
     /**
      * Set the current permission delegate for this GeckoSession.
      * @param delegate PermissionDelegate instance or null to use the default delegate.
      */
     public void setPermissionDelegate(final PermissionDelegate delegate) {
-        mPermissionHandler.setListener(delegate, this);
+        mPermissionHandler.setDelegate(delegate, this);
     }
 
     private PromptDelegate mPromptDelegate;
 
     private final Listener mListener = new Listener();
 
     /* package */ static final class Window extends JNIObject implements IInterface {
         private NativeQueue mNativeQueue;
@@ -729,92 +729,92 @@ public class GeckoSession extends LayerS
     */
     public void exitFullScreen() {
         mEventDispatcher.dispatch("GeckoViewContent:ExitFullScreen", null);
     }
 
     /**
     * Set the content callback handler.
     * This will replace the current handler.
-    * @param listener An implementation of ContentListener.
+    * @param delegate An implementation of ContentDelegate.
     */
-    public void setContentListener(ContentListener listener) {
-        mContentHandler.setListener(listener, this);
+    public void setContentDelegate(ContentDelegate delegate) {
+        mContentHandler.setDelegate(delegate, this);
     }
 
     /**
     * Get the content callback handler.
     * @return The current content callback handler.
     */
-    public ContentListener getContentListener() {
-        return mContentHandler.getListener();
+    public ContentDelegate getContentDelegate() {
+        return mContentHandler.getDelegate();
     }
 
     /**
     * Set the progress callback handler.
     * This will replace the current handler.
-    * @param listener An implementation of ProgressListener.
+    * @param delegate An implementation of ProgressDelegate.
     */
-    public void setProgressListener(ProgressListener listener) {
-        mProgressHandler.setListener(listener, this);
+    public void setProgressDelegate(ProgressDelegate delegate) {
+        mProgressHandler.setDelegate(delegate, this);
     }
 
     /**
     * Get the progress callback handler.
     * @return The current progress callback handler.
     */
-    public ProgressListener getProgressListener() {
-        return mProgressHandler.getListener();
+    public ProgressDelegate getProgressDelegate() {
+        return mProgressHandler.getDelegate();
     }
 
     /**
     * Set the navigation callback handler.
     * This will replace the current handler.
-    * @param listener An implementation of NavigationListener.
+    * @param delegate An implementation of NavigationDelegate.
     */
-    public void setNavigationListener(NavigationListener listener) {
-        mNavigationHandler.setListener(listener, this);
+    public void setNavigationDelegate(NavigationDelegate delegate) {
+        mNavigationHandler.setDelegate(delegate, this);
     }
 
     /**
     * Get the navigation callback handler.
     * @return The current navigation callback handler.
     */
-    public NavigationListener getNavigationListener() {
-        return mNavigationHandler.getListener();
+    public NavigationDelegate getNavigationDelegate() {
+        return mNavigationHandler.getDelegate();
     }
 
     /**
     * Set the content scroll callback handler.
     * This will replace the current handler.
-    * @param listener An implementation of ScrollListener.
+    * @param delegate An implementation of ScrollDelegate.
     */
-    public void setScrollListener(ScrollListener listener) {
-        mScrollHandler.setListener(listener, this);
+    public void setScrollDelegate(ScrollDelegate delegate) {
+        mScrollHandler.setDelegate(delegate, this);
     }
 
-    public ScrollListener getScrollListener() {
-        return mScrollHandler.getListener();
+    public ScrollDelegate getScrollDelegate() {
+        return mScrollHandler.getDelegate();
     }
 
     /**
     * Set the tracking protection callback handler.
     * This will replace the current handler.
     * @param delegate An implementation of TrackingProtectionDelegate.
     */
     public void setTrackingProtectionDelegate(TrackingProtectionDelegate delegate) {
-        mTrackingProtectionHandler.setListener(delegate, this);
+        mTrackingProtectionHandler.setDelegate(delegate, this);
     }
 
     /**
     * Get the tracking protection callback handler.
     * @return The current tracking protection callback handler.
     */
     public TrackingProtectionDelegate getTrackingProtectionDelegate() {
-        return mTrackingProtectionHandler.getListener();
+        return mTrackingProtectionHandler.getDelegate();
     }
 
     /**
      * Set the current prompt delegate for this GeckoSession.
      * @param delegate PromptDelegate instance or null to use the built-in delegate.
      */
     public void setPromptDelegate(PromptDelegate delegate) {
         mPromptDelegate = delegate;
@@ -1061,17 +1061,17 @@ public class GeckoSession extends LayerS
 
         final String type = message.getString("type");
         final String mode = message.getString("mode");
         final PromptCallback cb = new PromptCallback(type, mode, message, callback);
         final String title = message.getString("title");
         final String msg = message.getString("msg");
         switch (type) {
             case "alert": {
-                delegate.alert(session, title, msg, cb);
+                delegate.onAlert(session, title, msg, cb);
                 break;
             }
             case "button": {
                 final String[] btnTitle = message.getStringArray("btnTitle");
                 final String[] btnCustomTitle = message.getStringArray("btnCustomTitle");
                 for (int i = 0; i < btnCustomTitle.length; i++) {
                     final int resId;
                     if ("ok".equals(btnTitle[i])) {
@@ -1082,25 +1082,25 @@ public class GeckoSession extends LayerS
                         resId = android.R.string.yes;
                     } else if ("no".equals(btnTitle[i])) {
                         resId = android.R.string.no;
                     } else {
                         continue;
                     }
                     btnCustomTitle[i] = Resources.getSystem().getString(resId);
                 }
-                delegate.promptForButton(session, title, msg, btnCustomTitle, cb);
+                delegate.onButtonPrompt(session, title, msg, btnCustomTitle, cb);
                 break;
             }
             case "text": {
-                delegate.promptForText(session, title, msg, message.getString("value"), cb);
+                delegate.onTextPrompt(session, title, msg, message.getString("value"), cb);
                 break;
             }
             case "auth": {
-                delegate.promptForAuth(session, title, msg, new PromptDelegate.AuthenticationOptions(message.getBundle("options")), cb);
+                delegate.onAuthPrompt(session, title, msg, new PromptDelegate.AuthOptions(message.getBundle("options")), cb);
                 break;
             }
             case "choice": {
                 final int intMode;
                 if ("menu".equals(mode)) {
                     intMode = PromptDelegate.Choice.CHOICE_TYPE_MENU;
                 } else if ("single".equals(mode)) {
                     intMode = PromptDelegate.Choice.CHOICE_TYPE_SINGLE;
@@ -1116,22 +1116,22 @@ public class GeckoSession extends LayerS
                 if (choiceBundles == null || choiceBundles.length == 0) {
                     choices = null;
                 } else {
                     choices = new PromptDelegate.Choice[choiceBundles.length];
                     for (int i = 0; i < choiceBundles.length; i++) {
                         choices[i] = new PromptDelegate.Choice(choiceBundles[i]);
                     }
                 }
-                delegate.promptForChoice(session, title, msg, intMode,
+                delegate.onChoicePrompt(session, title, msg, intMode,
                                          choices, cb);
                 break;
             }
             case "color": {
-                delegate.promptForColor(session, title, message.getString("value"), cb);
+                delegate.onColorPrompt(session, title, message.getString("value"), cb);
                 break;
             }
             case "datetime": {
                 final int intMode;
                 if ("date".equals(mode)) {
                     intMode = PromptDelegate.DATETIME_TYPE_DATE;
                 } else if ("month".equals(mode)) {
                     intMode = PromptDelegate.DATETIME_TYPE_MONTH;
@@ -1140,17 +1140,17 @@ public class GeckoSession extends LayerS
                 } else if ("time".equals(mode)) {
                     intMode = PromptDelegate.DATETIME_TYPE_TIME;
                 } else if ("datetime-local".equals(mode)) {
                     intMode = PromptDelegate.DATETIME_TYPE_DATETIME_LOCAL;
                 } else {
                     callback.sendError("Invalid mode");
                     return;
                 }
-                delegate.promptForDateTime(session, title, intMode,
+                delegate.onDateTimePrompt(session, title, intMode,
                                            message.getString("value"),
                                            message.getString("min"),
                                            message.getString("max"), cb);
                 break;
             }
             case "file": {
                 final int intMode;
                 if ("single".equals(mode)) {
@@ -1171,31 +1171,31 @@ public class GeckoSession extends LayerS
                         final String mimeType =
                                 URLConnection.guessContentTypeFromName(extension);
                         if (mimeType != null) {
                             combined.add(mimeType);
                         }
                     }
                     mimeTypes = combined.toArray(new String[combined.size()]);
                 }
-                delegate.promptForFile(session, title, intMode, mimeTypes, cb);
+                delegate.onFilePrompt(session, title, intMode, mimeTypes, cb);
                 break;
             }
             default: {
                 callback.sendError("Invalid type");
                 break;
             }
         }
     }
 
     public EventDispatcher getEventDispatcher() {
         return mEventDispatcher;
     }
 
-    public interface ProgressListener {
+    public interface ProgressDelegate {
         /**
          * Class representing security information for a site.
          */
         public class SecurityInformation {
             public static final int SECURITY_MODE_UNKNOWN = 0;
             public static final int SECURITY_MODE_IDENTIFIED = 1;
             public static final int SECURITY_MODE_VERIFIED = 2;
 
@@ -1293,17 +1293,17 @@ public class GeckoSession extends LayerS
         /**
         * The security status has been updated.
         * @param session GeckoSession that initiated the callback.
         * @param securityInfo The new security information.
         */
         void onSecurityChange(GeckoSession session, SecurityInformation securityInfo);
     }
 
-    public interface ContentListener {
+    public interface ContentDelegate {
         /**
         * A page title was discovered in the content or updated after the content
         * loaded.
         * @param session The GeckoSession that initiated the callback.
         * @param title The title sent from the content.
         */
         void onTitleChange(GeckoSession session, String title);
 
@@ -1353,17 +1353,17 @@ public class GeckoSession extends LayerS
      */
     public interface Response<T> {
         /**
          * @param val The value contained in the response
          */
         void respond(T val);
     }
 
-    public interface NavigationListener {
+    public interface NavigationDelegate {
         /**
         * A view has started loading content from the network.
         * @param session The GeckoSession that initiated the callback.
         * @param url The resource being loaded.
         */
         void onLocationChange(GeckoSession session, String url);
 
         /**
@@ -1491,17 +1491,17 @@ public class GeckoSession extends LayerS
         /**
          * Display a simple message prompt.
          *
          * @param session GeckoSession that triggered the prompt
          * @param title Title for the prompt dialog.
          * @param msg Message for the prompt dialog.
          * @param callback Callback interface.
          */
-        void alert(GeckoSession session, String title, String msg, AlertCallback callback);
+        void onAlert(GeckoSession session, String title, String msg, AlertCallback callback);
 
         /**
          * Callback interface for notifying the result of a button prompt.
          */
         interface ButtonCallback extends AlertCallback {
             /**
              * Called by the prompt implementation when the button prompt is dismissed by
              * the user pressing one of the buttons.
@@ -1523,17 +1523,17 @@ public class GeckoSession extends LayerS
          * @param msg Message for the prompt dialog.
          * @param btnMsg Array of 3 elements indicating labels for the individual buttons.
          *               btnMsg[BUTTON_TYPE_POSITIVE] is the label for the "positive" button.
          *               btnMsg[BUTTON_TYPE_NEUTRAL] is the label for the "neutral" button.
          *               btnMsg[BUTTON_TYPE_NEGATIVE] is the label for the "negative" button.
          *               The button is hidden if the corresponding label is null.
          * @param callback Callback interface.
          */
-        void promptForButton(GeckoSession session, String title, String msg,
+        void onButtonPrompt(GeckoSession session, String title, String msg,
                              String[] btnMsg, ButtonCallback callback);
 
         /**
          * Callback interface for notifying the result of prompts that have text results,
          * including color and date/time pickers.
          */
         interface TextCallback extends AlertCallback {
             /**
@@ -1549,17 +1549,17 @@ public class GeckoSession extends LayerS
          * Display a prompt for inputting text.
          *
          * @param session GeckoSession that triggered the prompt
          * @param title Title for the prompt dialog.
          * @param msg Message for the prompt dialog.
          * @param value Default input text for the prompt.
          * @param callback Callback interface.
          */
-        void promptForText(GeckoSession session, String title, String msg,
+        void onTextPrompt(GeckoSession session, String title, String msg,
                            String value, TextCallback callback);
 
         /**
          * Callback interface for notifying the result of authentication prompts.
          */
         interface AuthCallback extends AlertCallback {
             /**
              * Called by the prompt implementation when a password-only prompt is
@@ -1574,17 +1574,17 @@ public class GeckoSession extends LayerS
              * confirmed by the user.
              *
              * @param username Entered username.
              * @param password Entered password.
              */
             void confirm(String username, String password);
         }
 
-        class AuthenticationOptions {
+        class AuthOptions {
             /**
              * The auth prompt is for a network host.
              */
             public static final int AUTH_FLAG_HOST = 1;
             /**
              * The auth prompt is for a proxy.
              */
             public static final int AUTH_FLAG_PROXY = 2;
@@ -1634,36 +1634,36 @@ public class GeckoSession extends LayerS
              */
             public String username;
 
             /**
              * A string containing the initial password.
              */
             public String password;
 
-            /* package */ AuthenticationOptions(GeckoBundle options) {
+            /* package */ AuthOptions(GeckoBundle options) {
                 flags = options.getInt("flags");
                 uri = options.getString("uri");
                 level = options.getInt("level");
                 username = options.getString("username");
                 password = options.getString("password");
             }
         }
 
         /**
          * Display a prompt for authentication credentials.
          *
          * @param session GeckoSession that triggered the prompt
          * @param title Title for the prompt dialog.
          * @param msg Message for the prompt dialog.
-         * @param options AuthenticationOptions containing options for the prompt
+         * @param options AuthOptions containing options for the prompt
          * @param callback Callback interface.
          */
-        void promptForAuth(GeckoSession session, String title, String msg,
-                           AuthenticationOptions options, AuthCallback callback);
+        void onAuthPrompt(GeckoSession session, String title, String msg,
+                           AuthOptions options, AuthCallback callback);
 
         class Choice {
             /**
              * Display choices in a menu that dismisses as soon as an item is chosen.
              */
             public static final int CHOICE_TYPE_MENU = 1;
 
             /**
@@ -1780,29 +1780,29 @@ public class GeckoSession extends LayerS
          *
          * @param session GeckoSession that triggered the prompt
          * @param title Title for the prompt dialog, or null for no title.
          * @param msg Message for the prompt dialog, or null for no message.
          * @param type One of CHOICE_TYPE_* indicating the type of prompt.
          * @param choices Array of Choices each representing an item or group.
          * @param callback Callback interface.
          */
-        void promptForChoice(GeckoSession session, String title, String msg, int type,
+        void onChoicePrompt(GeckoSession session, String title, String msg, int type,
                              Choice[] choices, ChoiceCallback callback);
 
         /**
          * Display a color prompt.
          *
          * @param session GeckoSession that triggered the prompt
          * @param title Title for the prompt dialog.
          * @param value Initial color value in HTML color format.
          * @param callback Callback interface; the result passed to confirm() must be in
          *                 HTML color format.
          */
-        void promptForColor(GeckoSession session, String title, String value,
+        void onColorPrompt(GeckoSession session, String title, String value,
                             TextCallback callback);
 
         /**
          * Prompt for year, month, and day.
          */
         static final int DATETIME_TYPE_DATE = 1;
 
         /**
@@ -1832,17 +1832,17 @@ public class GeckoSession extends LayerS
          * @param title Title for the prompt dialog; currently always null.
          * @param type One of DATETIME_TYPE_* indicating the type of prompt.
          * @param value Initial date/time value in HTML date/time format.
          * @param min Minimum date/time value in HTML date/time format.
          * @param max Maximum date/time value in HTML date/time format.
          * @param callback Callback interface; the result passed to confirm() must be in
          *                 HTML date/time format.
          */
-        void promptForDateTime(GeckoSession session, String title, int type,
+        void onDateTimePrompt(GeckoSession session, String title, int type,
                                String value, String min, String max, TextCallback callback);
 
         /**
          * Callback interface for notifying the result of file prompts.
          */
         interface FileCallback extends AlertCallback {
             /**
              * Called by the prompt implementation when the user makes a file selection in
@@ -1872,25 +1872,25 @@ public class GeckoSession extends LayerS
          * @param session GeckoSession that triggered the prompt
          * @param title Title for the prompt dialog.
          * @param type One of FILE_TYPE_* indicating the prompt type.
          * @param mimeTypes Array of permissible MIME types for the selected files, in
          *                  the form "type/subtype", where "type" and/or "subtype" can be
          *                  "*" to indicate any value.
          * @param callback Callback interface.
          */
-        void promptForFile(GeckoSession session, String title, int type,
+        void onFilePrompt(GeckoSession session, String title, int type,
                            String[] mimeTypes, FileCallback callback);
     }
 
     /**
      * GeckoSession applications implement this interface to handle content scroll
      * events.
      **/
-    public interface ScrollListener {
+    public interface ScrollDelegate {
         /**
          * The scroll position of the content has changed.
          *
         * @param session GeckoSession that initiated the callback.
         * @param scrollX The new horizontal scroll position in pixels.
         * @param scrollY The new vertical scroll position in pixels.
         */
         public void onScrollChanged(GeckoSession session, int scrollX, int scrollY);
@@ -1982,31 +1982,31 @@ public class GeckoSession extends LayerS
          * @param session GeckoSession instance requesting the permissions.
          * @param permissions List of permissions to request; possible values are,
          *                    android.Manifest.permission.ACCESS_COARSE_LOCATION
          *                    android.Manifest.permission.ACCESS_FINE_LOCATION
          *                    android.Manifest.permission.CAMERA
          *                    android.Manifest.permission.RECORD_AUDIO
          * @param callback Callback interface.
          */
-        void requestAndroidPermissions(GeckoSession session, String[] permissions,
+        void onAndroidPermissionsRequest(GeckoSession session, String[] permissions,
                                        Callback callback);
 
         /**
          * Request content permission.
          *
          * @param session GeckoSession instance requesting the permission.
          * @param uri The URI of the content requesting the permission.
          * @param type The type of the requested permission; possible values are,
          *             PERMISSION_GEOLOCATION
          *             PERMISSION_DESKTOP_NOTIFICATION
          * @param access Not used.
          * @param callback Callback interface.
          */
-        void requestContentPermission(GeckoSession session, String uri, int type,
+        void onContentPermissionRequest(GeckoSession session, String uri, int type,
                                       String access, Callback callback);
 
         class MediaSource {
             /**
              * The media source is a camera.
              */
             public static final int SOURCE_CAMERA = 0;
 
@@ -2170,12 +2170,12 @@ public class GeckoSession extends LayerS
          * audio source to use.
          *
          * @param session GeckoSession instance requesting the permission.
          * @param uri The URI of the content requesting the permission.
          * @param video List of video sources, or null if not requesting video.
          * @param audio List of audio sources, or null if not requesting audio.
          * @param callback Callback interface.
          */
-        void requestMediaPermission(GeckoSession session, String uri, MediaSource[] video,
+        void onMediaPermissionRequest(GeckoSession session, String uri, MediaSource[] video,
                                     MediaSource[] audio, MediaCallback callback);
     }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionHandler.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionHandler.java
@@ -9,23 +9,23 @@ package org.mozilla.geckoview;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.geckoview.GeckoSession;
 
 import android.util.Log;
 
-/* package */ abstract class GeckoSessionHandler<Listener>
+/* package */ abstract class GeckoSessionHandler<Delegate>
     implements BundleEventListener {
 
     private static final String LOGTAG = "GeckoSessionHandler";
     private static final boolean DEBUG = false;
 
-    private Listener mListener;
+    private Delegate mDelegate;
     private final boolean mAlwaysListen;
     private final String mModuleName;
     private final String[] mEvents;
 
 
     /* package */ GeckoSessionHandler(final String module,
                                       final GeckoSession session,
                                       final String[] events) {
@@ -40,33 +40,33 @@ import android.util.Log;
         mModuleName = module;
         mEvents = events;
 
         if (alwaysListen) {
             register(session.getEventDispatcher());
         }
     }
 
-    public Listener getListener() {
-        return mListener;
+    public Delegate getDelegate() {
+        return mDelegate;
     }
 
-    public void setListener(final Listener listener, final GeckoSession session) {
+    public void setDelegate(final Delegate delegate, final GeckoSession session) {
         final EventDispatcher eventDispatcher = session.getEventDispatcher();
-        if (mListener == listener) {
+        if (mDelegate == delegate) {
             return;
         }
 
-        if (!mAlwaysListen && mListener != null) {
+        if (!mAlwaysListen && mDelegate != null) {
             unregister(eventDispatcher);
         }
 
-        mListener = listener;
+        mDelegate = delegate;
 
-        if (!mAlwaysListen && mListener != null) {
+        if (!mAlwaysListen && mDelegate != null) {
             register(eventDispatcher);
         }
     }
 
     private void unregister(final EventDispatcher eventDispatcher) {
         final GeckoBundle msg = new GeckoBundle(1);
         msg.putString("module", mModuleName);
         eventDispatcher.dispatch("GeckoView:Unregister", msg);
@@ -82,20 +82,20 @@ import android.util.Log;
 
     @Override
     public void handleMessage(final String event, final GeckoBundle message,
                               final EventCallback callback) {
         if (DEBUG) {
             Log.d(LOGTAG, mModuleName + " handleMessage: event = " + event);
         }
 
-        if (mListener != null) {
-            handleMessage(mListener, event, message, callback);
+        if (mDelegate != null) {
+            handleMessage(mDelegate, event, message, callback);
         } else {
             callback.sendError("No listener registered");
         }
     }
 
-    protected abstract void handleMessage(final Listener listener,
+    protected abstract void handleMessage(final Delegate delegate,
                                           final String event,
                                           final GeckoBundle message,
                                           final EventCallback callback);
 }
--- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/BasicGeckoViewPrompt.java
+++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/BasicGeckoViewPrompt.java
@@ -87,32 +87,32 @@ final class BasicGeckoViewPrompt impleme
             parent.setPadding(/* left */ padding, /* top */ 0,
                               /* right */ padding, /* bottom */ 0);
             builder.setView(parent);
         }
         parent.addView(checkbox);
         return builder;
     }
 
-    public void alert(final GeckoSession session, final String title, final String msg,
+    public void onAlert(final GeckoSession session, final String title, final String msg,
                       final AlertCallback callback) {
         final Activity activity = mActivity;
         if (activity == null) {
             callback.dismiss();
             return;
         }
         final AlertDialog.Builder builder = new AlertDialog.Builder(activity)
                 .setTitle(title)
                 .setMessage(msg)
                 .setPositiveButton(android.R.string.ok, /* onClickListener */ null);
         createStandardDialog(addCheckbox(builder, /* parent */ null, callback),
                              callback).show();
     }
 
-    public void promptForButton(final GeckoSession session, final String title,
+    public void onButtonPrompt(final GeckoSession session, final String title,
                                 final String msg, final String[] btnMsg,
                                 final ButtonCallback callback) {
         final Activity activity = mActivity;
         if (activity == null) {
             callback.dismiss();
             return;
         }
         final AlertDialog.Builder builder = new AlertDialog.Builder(activity)
@@ -177,17 +177,17 @@ final class BasicGeckoViewPrompt impleme
                     @Override
                     public void onDismiss(final DialogInterface dialog) {
                         callback.dismiss();
                     }
                 });
         return dialog;
     }
 
-    public void promptForText(final GeckoSession session, final String title,
+    public void onTextPrompt(final GeckoSession session, final String title,
                               final String msg, final String value,
                               final TextCallback callback) {
         final Activity activity = mActivity;
         if (activity == null) {
             callback.dismiss();
             return;
         }
         final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
@@ -203,58 +203,58 @@ final class BasicGeckoViewPrompt impleme
                     public void onClick(final DialogInterface dialog, final int which) {
                         callback.confirm(editText.getText().toString());
                     }
                 });
 
         createStandardDialog(addCheckbox(builder, container, callback), callback).show();
     }
 
-    public void promptForAuth(final GeckoSession session, final String title,
-                              final String msg, final AuthenticationOptions options,
+    public void onAuthPrompt(final GeckoSession session, final String title,
+                              final String msg, final AuthOptions options,
                               final AuthCallback callback) {
         final Activity activity = mActivity;
         if (activity == null) {
             callback.dismiss();
             return;
         }
         final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
         final LinearLayout container = addStandardLayout(builder, title, msg);
 
         final int flags = options.flags;
         final int level = options.level;
         final EditText username;
-        if ((flags & AuthenticationOptions.AUTH_FLAG_ONLY_PASSWORD) == 0) {
+        if ((flags & AuthOptions.AUTH_FLAG_ONLY_PASSWORD) == 0) {
             username = new EditText(builder.getContext());
             username.setHint(R.string.username);
             username.setText(options.username);
             container.addView(username);
         } else {
             username = null;
         }
 
         final EditText password = new EditText(builder.getContext());
         password.setHint(R.string.password);
         password.setText(options.password);
         password.setInputType(InputType.TYPE_CLASS_TEXT |
                               InputType.TYPE_TEXT_VARIATION_PASSWORD);
         container.addView(password);
 
-        if (level != AuthenticationOptions.AUTH_LEVEL_NONE) {
+        if (level != AuthOptions.AUTH_LEVEL_NONE) {
             final ImageView secure = new ImageView(builder.getContext());
             secure.setImageResource(android.R.drawable.ic_lock_lock);
             container.addView(secure);
         }
 
         builder.setNegativeButton(android.R.string.cancel, /* listener */ null)
                .setPositiveButton(android.R.string.ok,
                                   new DialogInterface.OnClickListener() {
                     @Override
                     public void onClick(final DialogInterface dialog, final int which) {
-                        if ((flags & AuthenticationOptions.AUTH_FLAG_ONLY_PASSWORD) == 0) {
+                        if ((flags & AuthOptions.AUTH_FLAG_ONLY_PASSWORD) == 0) {
                             callback.confirm(username.getText().toString(),
                                              password.getText().toString());
                         } else {
                             callback.confirm(password.getText().toString());
                         }
                     }
                 });
         createStandardDialog(addCheckbox(builder, container, callback), callback).show();
@@ -281,17 +281,17 @@ final class BasicGeckoViewPrompt impleme
                 } else {
                     newIndent = null;
                 }
                 addChoiceItems(type, list, children, newIndent);
             }
         }
     }
 
-    public void promptForChoice(final GeckoSession session, final String title,
+    public void onChoicePrompt(final GeckoSession session, final String title,
                                 final String msg, final int type,
                                 final Choice[] choices, final ChoiceCallback callback) {
         final Activity activity = mActivity;
         if (activity == null) {
             callback.dismiss();
             return;
         }
         final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
@@ -412,17 +412,17 @@ final class BasicGeckoViewPrompt impleme
                                         final int position, final long id) {
                     final Choice item = adapter.getItem(position);
                     if (type == Choice.CHOICE_TYPE_MENU) {
                         final Choice[] children = item.items;
                         if (children != null) {
                             // Show sub-menu.
                             dialog.setOnDismissListener(null);
                             dialog.dismiss();
-                            promptForChoice(session, item.label, /* msg */ null,
+                            onChoicePrompt(session, item.label, /* msg */ null,
                                             type, children, callback);
                             return;
                         }
                     }
                     callback.confirm(item);
                     dialog.dismiss();
                 }
             });
@@ -462,17 +462,17 @@ final class BasicGeckoViewPrompt impleme
     private static int parseColor(final String value, final int def) {
         try {
             return Color.parseColor(value);
         } catch (final IllegalArgumentException e) {
             return def;
         }
     }
 
-    public void promptForColor(final GeckoSession session, final String title,
+    public void onColorPrompt(final GeckoSession session, final String title,
                                final String value, final TextCallback callback)
     {
         final Activity activity = mActivity;
         if (activity == null) {
             callback.dismiss();
             return;
         }
         final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
@@ -570,17 +570,17 @@ final class BasicGeckoViewPrompt impleme
             cal.set(Calendar.HOUR_OF_DAY, picker.getHour());
             cal.set(Calendar.MINUTE, picker.getMinute());
         } else {
             cal.set(Calendar.HOUR_OF_DAY, picker.getCurrentHour());
             cal.set(Calendar.MINUTE, picker.getCurrentMinute());
         }
     }
 
-    public void promptForDateTime(final GeckoSession session, final String title,
+    public void onDateTimePrompt(final GeckoSession session, final String title,
                                   final int type, final String value, final String min,
                                   final String max, final TextCallback callback) {
         final Activity activity = mActivity;
         if (activity == null) {
             callback.dismiss();
             return;
         }
         final String format;
@@ -686,17 +686,17 @@ final class BasicGeckoViewPrompt impleme
         };
         builder.setNegativeButton(android.R.string.cancel, /* listener */ null)
                .setNeutralButton(R.string.clear_field, listener)
                .setPositiveButton(android.R.string.ok, listener);
         createStandardDialog(builder, callback).show();
     }
 
     @TargetApi(19)
-    public void promptForFile(GeckoSession session, String title, int type,
+    public void onFilePrompt(GeckoSession session, String title, int type,
                               String[] mimeTypes, FileCallback callback)
     {
         final Activity activity = mActivity;
         if (activity == null) {
             callback.dismiss();
             return;
         }
 
@@ -776,17 +776,17 @@ final class BasicGeckoViewPrompt impleme
             final ArrayList<Uri> uris = new ArrayList<>(count);
             for (int i = 0; i < count; i++) {
                 uris.add(clip.getItemAt(i).getUri());
             }
             callback.confirm(mActivity, uris.toArray(new Uri[uris.size()]));
         }
     }
 
-    public void promptForPermission(final GeckoSession session, final String title,
+    public void onPermissionPrompt(final GeckoSession session, final String title,
                                     final GeckoSession.PermissionDelegate.Callback callback) {
         final Activity activity = mActivity;
         if (activity == null) {
             callback.reject();
             return;
         }
         final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
         builder.setTitle(title)
@@ -837,17 +837,17 @@ final class BasicGeckoViewPrompt impleme
 
         final Spinner spinner = new Spinner(context);
         spinner.setAdapter(adapter);
         spinner.setSelection(0);
         container.addView(spinner);
         return spinner;
     }
 
-    public void promptForMedia(final GeckoSession session, final String title,
+    public void onMediaPrompt(final GeckoSession session, final String title,
                                final MediaSource[] video, final MediaSource[] audio,
                                final GeckoSession.PermissionDelegate.MediaCallback callback) {
         final Activity activity = mActivity;
         if (activity == null || (video == null && audio == null)) {
             callback.reject();
             return;
         }
         final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
--- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
+++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
@@ -69,21 +69,21 @@ public class GeckoViewActivity extends A
         GeckoSession.preload(this, geckoArgs, useMultiprocess);
 
         setContentView(R.layout.geckoview_activity);
 
         mGeckoView = (GeckoView) findViewById(R.id.gecko_view);
         mGeckoSession = new GeckoSession();
         mGeckoView.setSession(mGeckoSession);
 
-        mGeckoSession.setContentListener(new MyGeckoViewContent());
+        mGeckoSession.setContentDelegate(new MyGeckoViewContent());
         final MyTrackingProtection tp = new MyTrackingProtection();
         mGeckoSession.setTrackingProtectionDelegate(tp);
-        mGeckoSession.setProgressListener(new MyGeckoViewProgress(tp));
-        mGeckoSession.setNavigationListener(new Navigation());
+        mGeckoSession.setProgressDelegate(new MyGeckoViewProgress(tp));
+        mGeckoSession.setNavigationDelegate(new Navigation());
 
         final BasicGeckoViewPrompt prompt = new BasicGeckoViewPrompt(this);
         prompt.filePickerRequestCode = REQUEST_FILE_PICKER;
         mGeckoSession.setPromptDelegate(prompt);
 
         final MyGeckoViewPermission permission = new MyGeckoViewPermission();
         permission.androidPermissionRequestCode = REQUEST_PERMISSIONS;
         mGeckoSession.setPermissionDelegate(permission);
@@ -163,17 +163,17 @@ public class GeckoViewActivity extends A
             final MyGeckoViewPermission permission = (MyGeckoViewPermission)
                     mGeckoSession.getPermissionDelegate();
             permission.onRequestPermissionsResult(permissions, grantResults);
         } else {
             super.onRequestPermissionsResult(requestCode, permissions, grantResults);
         }
     }
 
-    private class MyGeckoViewContent implements GeckoSession.ContentListener {
+    private class MyGeckoViewContent implements GeckoSession.ContentDelegate {
         @Override
         public void onTitleChange(GeckoSession session, String title) {
             Log.i(LOGTAG, "Content title changed to " + title);
         }
 
         @Override
         public void onFullScreen(final GeckoSession session, final boolean fullScreen) {
             getWindow().setFlags(fullScreen ? WindowManager.LayoutParams.FLAG_FULLSCREEN : 0,
@@ -201,17 +201,17 @@ public class GeckoViewActivity extends A
         public void onContextMenu(GeckoSession session, int screenX, int screenY,
                                   String uri, String elementSrc) {
             Log.d(LOGTAG, "onContextMenu screenX=" + screenX +
                           " screenY=" + screenY + " uri=" + uri +
                           " elementSrc=" + elementSrc);
         }
     }
 
-    private class MyGeckoViewProgress implements GeckoSession.ProgressListener {
+    private class MyGeckoViewProgress implements GeckoSession.ProgressDelegate {
         private MyTrackingProtection mTp;
 
         private MyGeckoViewProgress(final MyTrackingProtection tp) {
             mTp = tp;
         }
 
         @Override
         public void onPageStart(GeckoSession session, String url) {
@@ -254,46 +254,46 @@ public class GeckoViewActivity extends A
                     cb.reject();
                     return;
                 }
             }
             cb.grant();
         }
 
         @Override
-        public void requestAndroidPermissions(final GeckoSession session, final String[] permissions,
+        public void onAndroidPermissionsRequest(final GeckoSession session, final String[] permissions,
                                               final Callback callback) {
             if (Build.VERSION.SDK_INT < 23) {
                 // requestPermissions was introduced in API 23.
                 callback.grant();
                 return;
             }
             mCallback = callback;
             requestPermissions(permissions, androidPermissionRequestCode);
         }
 
         @Override
-        public void requestContentPermission(final GeckoSession session, final String uri,
+        public void onContentPermissionRequest(final GeckoSession session, final String uri,
                                              final int type, final String access,
                                              final Callback callback) {
             final int resId;
             if (PERMISSION_GEOLOCATION == type) {
                 resId = R.string.request_geolocation;
             } else if (PERMISSION_DESKTOP_NOTIFICATION == type) {
                 resId = R.string.request_notification;
             } else {
                 Log.w(LOGTAG, "Unknown permission: " + type);
                 callback.reject();
                 return;
             }
 
             final String title = getString(resId, Uri.parse(uri).getAuthority());
             final BasicGeckoViewPrompt prompt = (BasicGeckoViewPrompt)
                     mGeckoSession.getPromptDelegate();
-            prompt.promptForPermission(session, title, callback);
+            prompt.onPermissionPrompt(session, title, callback);
         }
 
         private void normalizeMediaName(final MediaSource[] sources) {
             if (sources == null) {
                 return;
             }
             for (final MediaSource source : sources) {
                 final int mediaSource = source.source;
@@ -311,17 +311,17 @@ public class GeckoViewActivity extends A
                 } else {
                     name = getString(R.string.media_other);
                 }
                 source.name = name;
             }
         }
 
         @Override
-        public void requestMediaPermission(final GeckoSession session, final String uri,
+        public void onMediaPermissionRequest(final GeckoSession session, final String uri,
                                            final MediaSource[] video, final MediaSource[] audio,
                                            final MediaCallback callback) {
             final String host = Uri.parse(uri).getAuthority();
             final String title;
             if (audio == null) {
                 title = getString(R.string.request_video, host);
             } else if (video == null) {
                 title = getString(R.string.request_audio, host);
@@ -329,21 +329,21 @@ public class GeckoViewActivity extends A
                 title = getString(R.string.request_media, host);
             }
 
             normalizeMediaName(video);
             normalizeMediaName(audio);
 
             final BasicGeckoViewPrompt prompt = (BasicGeckoViewPrompt)
                     mGeckoSession.getPromptDelegate();
-            prompt.promptForMedia(session, title, video, audio, callback);
+            prompt.onMediaPrompt(session, title, video, audio, callback);
         }
     }
 
-    private class Navigation implements GeckoSession.NavigationListener {
+    private class Navigation implements GeckoSession.NavigationDelegate {
         @Override
         public void onLocationChange(GeckoSession session, final String url) {
         }
 
         @Override
         public void onCanGoBack(GeckoSession session, boolean canGoBack) {
         }
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3389,17 +3389,17 @@ pref("dom.ipc.processCount", 4);
 
 // Default to allow only one file:// URL content process.
 pref("dom.ipc.processCount.file", 1);
 
 // WebExtensions only support a single extension process.
 pref("dom.ipc.processCount.extension", 1);
 
 // Whether a native event loop should be used in the content process.
-#if defined(XP_WIN) && defined(NIGHTLY_BUILD)
+#if defined(XP_WIN)
 pref("dom.ipc.useNativeEventProcessing.content", false);
 #else
 pref("dom.ipc.useNativeEventProcessing.content", true);
 #endif
 
 // Quantum DOM scheduling:
 pref("dom.ipc.scheduler", false);
 pref("dom.ipc.scheduler.useMultipleQueues", true);
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -2850,20 +2850,20 @@ HttpBaseChannel::GetFetchCacheMode(uint3
 
   // Otherwise try to guess an appropriate cache mode from the load flags.
   if (ContainsAllFlags(mLoadFlags, INHIBIT_CACHING | LOAD_BYPASS_CACHE)) {
     *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE;
   } else if (ContainsAllFlags(mLoadFlags, LOAD_BYPASS_CACHE)) {
     *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD;
   } else if (ContainsAllFlags(mLoadFlags, VALIDATE_ALWAYS)) {
     *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE;
-  } else if (ContainsAllFlags(mLoadFlags, LOAD_FROM_CACHE |
+  } else if (ContainsAllFlags(mLoadFlags, VALIDATE_NEVER |
                                           nsICachingChannel::LOAD_ONLY_FROM_CACHE)) {
     *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED;
-  } else if (ContainsAllFlags(mLoadFlags, LOAD_FROM_CACHE)) {
+  } else if (ContainsAllFlags(mLoadFlags, VALIDATE_NEVER)) {
     *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE;
   } else {
     *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT;
   }
 
   return NS_OK;
 }
 
@@ -2910,26 +2910,26 @@ HttpBaseChannel::SetFetchCacheMode(uint3
     SetCacheFlags(mLoadFlags, LOAD_BYPASS_CACHE);
     break;
   case nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE:
     // no-cache means always validate what's in the cache.
     SetCacheFlags(mLoadFlags, VALIDATE_ALWAYS);
     break;
   case nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE:
     // force-cache means don't validate unless if the response would vary.
-    SetCacheFlags(mLoadFlags, LOAD_FROM_CACHE);
+    SetCacheFlags(mLoadFlags, VALIDATE_NEVER);
     break;
   case nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED:
     // only-if-cached means only from cache, no network, no validation, generate
     // a network error if the document was't in the cache.
     // The privacy implications of these flags (making it fast/easy to check if
     // the user has things in their cache without any network traffic side
     // effects) are addressed in the Request constructor which enforces/requires
     // same-origin request mode.
-    SetCacheFlags(mLoadFlags, LOAD_FROM_CACHE |
+    SetCacheFlags(mLoadFlags, VALIDATE_NEVER |
                               nsICachingChannel::LOAD_ONLY_FROM_CACHE);
     break;
   }
 
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
   uint32_t finalMode = 0;
   MOZ_ALWAYS_SUCCEEDS(GetFetchCacheMode(&finalMode));
   MOZ_DIAGNOSTIC_ASSERT(finalMode == aFetchCacheMode);
--- a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
+++ b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
@@ -2,19 +2,16 @@
 /* vim: set ts=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 "sandboxBroker.h"
 
 #include <string>
-#if defined(NIGHTLY_BUILD)
-#include <vector>
-#endif
 
 #include "base/win/windows_version.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Logging.h"
 #include "mozilla/NSPRLogModulesParser.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Telemetry.h"
@@ -26,42 +23,16 @@
 #include "nsIProperties.h"
 #include "nsServiceManagerUtils.h"
 #include "nsString.h"
 #include "nsTHashtable.h"
 #include "sandbox/win/src/sandbox.h"
 #include "sandbox/win/src/security_level.h"
 #include "WinUtils.h"
 
-#if defined(NIGHTLY_BUILD)
-
-// This list of DLLs have been found to cause instability in sandboxed child
-// processes and so they will be unloaded if they attempt to load.
-const std::vector<std::wstring> kDllsToUnload = {
-  // Symantec Corporation (bug 1400637)
-  L"ffm64.dll",
-  L"ffm.dll",
-  L"prntm64.dll",
-
-  // HitmanPro - SurfRight now part of Sophos (bug 1400637)
-  L"hmpalert.dll",
-
-  // Avast Antivirus (bug 1400637)
-  L"snxhk64.dll",
-  L"snxhk.dll",
-
-  // Webroot SecureAnywhere (bug 1400637)
-  L"wrusr.dll",
-
-  // Comodo Internet Security (bug 1400637)
-  L"guard32.dll",
-};
-
-#endif
-
 namespace mozilla
 {
 
 sandbox::BrokerServices *SandboxBroker::sBrokerService = nullptr;
 
 // This is set to true in Initialize when our exe file name has a drive type of
 // DRIVE_REMOTE, so that we can tailor the sandbox policy as some settings break
 // fundamental things when running from a network drive. We default to false in
@@ -253,40 +224,19 @@ SandboxBroker::LaunchApp(const wchar_t *
   if (it != aEnvironment.end()) {
     logFileName = (it->second).c_str();
   }
   if (logFileName) {
     mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES,
                      sandbox::TargetPolicy::FILES_ALLOW_ANY, logFileName);
   }
 
-  sandbox::ResultCode result;
-#if defined(NIGHTLY_BUILD)
-
-  // Add DLLs to the policy that have been found to cause instability with the
-  // sandbox, so that they will be unloaded when they attempt to load.
-  for (std::wstring dllToUnload : kDllsToUnload) {
-    // Similar to Chromium, we only add a DLL if it is loaded in this process.
-    if (::GetModuleHandleW(dllToUnload.c_str())) {
-      result = mPolicy->AddDllToUnload(dllToUnload.c_str());
-      MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result,
-                         "AddDllToUnload should never fail, what happened?");
-    }
-  }
-
-  // Add K7 Computing DLL to be blocked even if not loaded in the parent, as we
-  // are still getting crash reports for it.
-  result = mPolicy->AddDllToUnload(L"k7pswsen.dll");
-  MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result,
-                     "AddDllToUnload should never fail, what happened?");
-
-#endif
-
   // Ceate the sandboxed process
   PROCESS_INFORMATION targetInfo = {0};
+  sandbox::ResultCode result;
   sandbox::ResultCode last_warning = sandbox::SBOX_ALL_OK;
   DWORD last_error = ERROR_SUCCESS;
   result = sBrokerService->SpawnTarget(aPath, aArguments, aEnvironment, mPolicy,
                                        &last_warning, &last_error, &targetInfo);
   if (sandbox::SBOX_ALL_OK != result) {
     nsAutoCString key;
     key.AppendASCII(XRE_ChildProcessTypeToString(aProcessType));
     key.AppendLiteral("/0x");
@@ -772,36 +722,36 @@ SandboxBroker::SetSecurityLevelForPlugin
                          "SetDelayedIntegrityLevel should never fail, what happened?");
 
   sandbox::MitigationFlags mitigations =
     sandbox::MITIGATION_BOTTOM_UP_ASLR |
     sandbox::MITIGATION_HEAP_TERMINATE |
     sandbox::MITIGATION_SEHOP |
     sandbox::MITIGATION_DEP_NO_ATL_THUNK |
     sandbox::MITIGATION_DEP |
-    sandbox::MITIGATION_HARDEN_TOKEN_IL_POLICY |
-    sandbox::MITIGATION_EXTENSION_POINT_DISABLE |
-    sandbox::MITIGATION_NONSYSTEM_FONT_DISABLE |
-    sandbox::MITIGATION_IMAGE_LOAD_PREFER_SYS32