Merge mozilla-inbound to mozilla-central. a=merge
authorAndreea Pavel <apavel@mozilla.com>
Tue, 10 Apr 2018 00:55:35 +0300
changeset 468530 83de58ddda2057f1cb949537f6b111e3b115ea3d
parent 468506 bbaa0e5f393c280e23d78d360fb29b5b0d06e150 (current diff)
parent 468529 abb2fafe775b3e58cdff89164a229883755a08df (diff)
child 468545 d42b6284aba7e1cb7e266f2799432c5f8b2375cb
child 468591 b86fa87d995a07da5039d689e08640559118c460
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.0a1
first release with
nightly linux32
83de58ddda20 / 61.0a1 / 20180409220118 / files
nightly linux64
83de58ddda20 / 61.0a1 / 20180409220118 / files
nightly mac
83de58ddda20 / 61.0a1 / 20180409220118 / files
nightly win32
83de58ddda20 / 61.0a1 / 20180409220118 / files
nightly win64
83de58ddda20 / 61.0a1 / 20180409220118 / 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
dom/serviceworkers/test/fetch/context/beacon.sjs
dom/serviceworkers/test/fetch/context/context_test.js
dom/serviceworkers/test/fetch/context/csp-violate.sjs
dom/serviceworkers/test/fetch/context/index.html
dom/serviceworkers/test/fetch/context/parentsharedworker.js
dom/serviceworkers/test/fetch/context/parentworker.js
dom/serviceworkers/test/fetch/context/ping.html
dom/serviceworkers/test/fetch/context/realaudio.ogg
dom/serviceworkers/test/fetch/context/realimg.jpg
dom/serviceworkers/test/fetch/context/register.html
dom/serviceworkers/test/fetch/context/sharedworker.js
dom/serviceworkers/test/fetch/context/unregister.html
dom/serviceworkers/test/fetch/context/worker.js
dom/serviceworkers/test/fetch/context/xml.xml
dom/serviceworkers/test/test_request_context.js
dom/serviceworkers/test/test_request_context_audio.html
dom/serviceworkers/test/test_request_context_beacon.html
dom/serviceworkers/test/test_request_context_cache.html
dom/serviceworkers/test/test_request_context_cspreport.html
dom/serviceworkers/test/test_request_context_embed.html
dom/serviceworkers/test/test_request_context_fetch.html
dom/serviceworkers/test/test_request_context_font.html
dom/serviceworkers/test/test_request_context_frame.html
dom/serviceworkers/test/test_request_context_iframe.html
dom/serviceworkers/test/test_request_context_image.html
dom/serviceworkers/test/test_request_context_imagesrcset.html
dom/serviceworkers/test/test_request_context_internal.html
dom/serviceworkers/test/test_request_context_nestedworker.html
dom/serviceworkers/test/test_request_context_nestedworkerinsharedworker.html
dom/serviceworkers/test/test_request_context_object.html
dom/serviceworkers/test/test_request_context_picture.html
dom/serviceworkers/test/test_request_context_ping.html
dom/serviceworkers/test/test_request_context_plugin.html
dom/serviceworkers/test/test_request_context_script.html
dom/serviceworkers/test/test_request_context_sharedworker.html
dom/serviceworkers/test/test_request_context_style.html
dom/serviceworkers/test/test_request_context_track.html
dom/serviceworkers/test/test_request_context_video.html
dom/serviceworkers/test/test_request_context_worker.html
dom/serviceworkers/test/test_request_context_xhr.html
dom/serviceworkers/test/test_request_context_xslt.html
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoSessionTestRuleTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ProgressDelegateTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
modules/libpref/init/all.js
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -353,17 +353,22 @@ nsBrowserContentHandler.prototype = {
           if (uri instanceof Ci.nsINestedURI) {
             uri = uri.QueryInterface(Ci.nsINestedURI).innerMostURI;
           }
           return localSchemes.has(uri.scheme);
         };
         if (isLocal(resolvedURI)) {
           // If the URI is local, we are sure it won't wrongly inherit chrome privs
           let features = "chrome,dialog=no,all" + this.getFeatures(cmdLine);
-          Services.ww.openWindow(null, resolvedURI.spec, "_blank", features, null);
+          // Provide 1 null argument, as openWindow has a different behavior
+          // when the arg count is 0.
+          let argArray = Cc["@mozilla.org/array;1"]
+                           .createInstance(Ci.nsIMutableArray);
+          argArray.appendElement(null);
+          Services.ww.openWindow(null, resolvedURI.spec, "_blank", features, argArray);
           cmdLine.preventDefault = true;
         } else {
           dump("*** Preventing load of web URI as chrome\n");
           dump("    If you're trying to load a webpage, do not pass --chrome.\n");
         }
       } catch (e) {
         Cu.reportError(e);
       }
--- a/build/autoconf/toolchain.m4
+++ b/build/autoconf/toolchain.m4
@@ -93,17 +93,17 @@ dnl Updates to the test below should be 
 dnl cross-compiling case.
 AC_LANG_CPLUSPLUS
 if test "$GNU_CXX"; then
     AC_CACHE_CHECK([whether 64-bits std::atomic requires -latomic],
         ac_cv_needs_atomic,
         dnl x86 with clang is a little peculiar.  std::atomic does not require
         dnl linking with libatomic, but using atomic intrinsics does, so we
         dnl force the setting on for such systems.
-        if test "$CC_TYPE" = "clang" -a "$CPU_ARCH" = "x86"; then
+        if test "$CC_TYPE" = "clang" -a "$CPU_ARCH" = "x86" -a "$OS_ARCH" = "Linux"; then
             ac_cv_needs_atomic=yes
         else
             AC_TRY_LINK(
                 [#include <cstdint>
                  #include <atomic>],
                 [ std::atomic<uint64_t> foo; foo = 1; ],
                 ac_cv_needs_atomic=no,
                 _SAVE_LIBS="$LIBS"
--- a/devtools/client/accessibility/accessibility-panel.js
+++ b/devtools/client/accessibility/accessibility-panel.js
@@ -136,16 +136,20 @@ AccessibilityPanel.prototype = {
     this.shouldRefresh = false;
     this.postContentMessage("initialize", this._front, this._walker, this._isOldVersion);
   },
 
   selectAccessible(accessibleFront) {
     this.postContentMessage("selectAccessible", this._walker, accessibleFront);
   },
 
+  selectAccessibleForNode(nodeFront) {
+    this.postContentMessage("selectNodeAccessible", this._walker, nodeFront);
+  },
+
   highlightAccessible(accessibleFront) {
     this.postContentMessage("highlightAccessible", this._walker, accessibleFront);
   },
 
   postContentMessage(type, ...args) {
     const event = new this.panelWin.MessageEvent("devtools/chrome/message", {
       bubbles: true,
       cancelable: true,
--- a/devtools/client/accessibility/test/browser.ini
+++ b/devtools/client/accessibility/test/browser.ini
@@ -2,13 +2,14 @@
 tags = devtools
 subsuite = devtools
 support-files =
   head.js
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-redux-head.js
 
+[browser_accessibility_context_menu_inspector.js]
 [browser_accessibility_mutations.js]
 [browser_accessibility_reload.js]
 [browser_accessibility_sidebar.js]
 [browser_accessibility_tree.js]
 [browser_accessibility_tree_nagivation.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/test/browser_accessibility_context_menu_inspector.js
@@ -0,0 +1,43 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const TEST_URI = "<h1 id=\"h1\">header</h1><p id=\"p\">paragraph</p>";
+
+addA11YPanelTask("Test show accessibility properties context menu.", TEST_URI,
+  async function testShowAccessibilityPropertiesContextMenu({ panel, toolbox }) {
+    let inspector = await toolbox.selectTool("inspector");
+
+    ok(inspector.selection.isBodyNode(), "Default selection is a body node.");
+    let menuUpdated = inspector.once("node-menu-updated");
+    let allMenuItems = openContextMenuAndGetAllItems(inspector);
+    let showA11YPropertiesNode = allMenuItems.find(item =>
+      item.id === "node-menu-showaccessibilityproperties");
+    ok(showA11YPropertiesNode,
+      "the popup menu now has a show accessibility properties item");
+    await menuUpdated;
+    ok(showA11YPropertiesNode.disabled, "Body node does not have accessible");
+
+    await selectNode("#h1", inspector, "test");
+    menuUpdated = inspector.once("node-menu-updated");
+    allMenuItems = openContextMenuAndGetAllItems(inspector);
+    showA11YPropertiesNode = allMenuItems.find(item =>
+      item.id === "node-menu-showaccessibilityproperties");
+    ok(showA11YPropertiesNode,
+      "the popup menu now has a show accessibility properties item");
+    await menuUpdated;
+    ok(!showA11YPropertiesNode.disabled, "Body node has an accessible");
+
+    info("Triggering 'Show Accessibility Properties' and waiting for " +
+         "accessibility panel to open");
+    let panelSelected = toolbox.once("accessibility-selected");
+    let objectSelected = panel.once("new-accessible-front-selected");
+    showA11YPropertiesNode.click();
+    await panelSelected;
+    let selected = await objectSelected;
+
+    let expectedSelected = await panel.walker.getAccessibleFor(
+      inspector.selection.nodeFront);
+    is(selected, expectedSelected, "Accessible front selected correctly");
+  });
--- a/devtools/client/accessibility/test/head.js
+++ b/devtools/client/accessibility/test/head.js
@@ -94,29 +94,34 @@ async function addTestTab(url) {
   // accessibility panel test can be too fast.
   await win.gToolbox.loadTool("inspector");
 
   return {
     tab,
     browser: tab.linkedBrowser,
     panel,
     win,
+    toolbox: panel._toolbox,
     doc,
     store
   };
 }
 
 /**
  * Turn off accessibility features from within the panel. We call it before the
  * cleanup function to make sure that the panel is still present.
  */
-function disableAccessibilityInspector(env) {
-  let { doc, win } = env;
+async function disableAccessibilityInspector(env) {
+  let { doc, win, panel } = env;
+  // Disable accessibility service through the panel and wait for the shutdown
+  // event.
+  let shutdown = panel._front.once("shutdown");
   EventUtils.sendMouseEvent({ type: "click" },
     doc.getElementById("accessibility-disable-button"), win);
+  await shutdown;
 }
 
 /**
  * Open the Accessibility panel for the given tab.
  *
  * @param {nsIDOMElement} tab
  *        Optional tab element for which you want open the Accessibility panel.
  *        The default tab is taken from the global variable |tab|.
@@ -237,17 +242,17 @@ async function runA11yPanelTests(tests, 
 }
 
 /**
  * Build a valid URL from an HTML snippet.
  * @param  {String} uri HTML snippet
  * @return {String}     built URL
  */
 function buildURL(uri) {
-  return `data:text/html,${encodeURIComponent(uri)}`;
+  return `data:text/html;charset=UTF-8,${encodeURIComponent(uri)}`;
 }
 
 /**
  * Add a test task based on the test structure and a test URL.
  * @param  {JSON}   tests  test data that has the format of:
  *                    {
  *                      desc     {String}    description for better logging
  *                      action   {Function}  An optional action that needs to be
@@ -255,25 +260,32 @@ function buildURL(uri) {
  *                                           tree and the sidebar can be checked
  *                      expected {JSON}      An expected states for the tree and
  *                                           the sidebar
  *                    }
  * @param {String}  uri    test URL
  * @param {String}  msg    a message that is printed for the test
  */
 function addA11yPanelTestsTask(tests, uri, msg) {
-  tests.push({
-    desc: "Disable accessibility inspector.",
-    action: env => disableAccessibilityInspector(env),
-    expected: {}
-  });
-  add_task(async function a11yPanelTests() {
+  addA11YPanelTask(msg, uri, env => runA11yPanelTests(tests, env));
+}
+
+/**
+ * A wrapper function around add_task that sets up the test environment, runs
+ * the test and then disables accessibility tools.
+ * @param {String}   msg    a message that is printed for the test
+ * @param {String}   uri    test URL
+ * @param {Function} task   task function containing the tests.
+ */
+function addA11YPanelTask(msg, uri, task) {
+  add_task(async function a11YPanelTask() {
     info(msg);
     let env = await addTestTab(buildURL(uri));
-    await runA11yPanelTests(tests, env);
+    await task(env);
+    await disableAccessibilityInspector(env);
   });
 }
 
 /**
  * Reload panel target.
  * @param  {Object} target             Panel target.
  * @param  {String} waitForTargetEvent Event to wait for after reload.
  */
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -1561,32 +1561,66 @@ Inspector.prototype = {
       click: () => this.useInConsole(),
     }));
     menu.append(new MenuItem({
       id: "node-menu-showdomproperties",
       label: INSPECTOR_L10N.getStr("inspectorShowDOMProperties.label"),
       click: () => this.showDOMProperties(),
     }));
 
+    this.buildA11YMenuItem(menu);
+
     let nodeLinkMenuItems = this._getNodeLinkMenuItems();
     if (nodeLinkMenuItems.filter(item => item.visible).length > 0) {
       menu.append(new MenuItem({
         id: "node-menu-link-separator",
         type: "separator",
       }));
     }
 
     for (let menuitem of nodeLinkMenuItems) {
       menu.append(menuitem);
     }
 
     menu.popup(screenX, screenY, this._toolbox);
     return menu;
   },
 
+  buildA11YMenuItem: function(menu) {
+    if (!this.selection.isElementNode() ||
+        !Services.prefs.getBoolPref("devtools.accessibility.enabled")) {
+      return;
+    }
+
+    const showA11YPropsItem = new MenuItem({
+      id: "node-menu-showaccessibilityproperties",
+      label: INSPECTOR_L10N.getStr("inspectorShowAccessibilityProperties.label"),
+      click: () => this.showAccessibilityProperties(),
+      disabled: true
+    });
+    this._updateA11YMenuItem(showA11YPropsItem);
+    menu.append(showA11YPropsItem);
+  },
+
+  _updateA11YMenuItem: async function(menuItem) {
+    const hasMethod = await this.target.actorHasMethod("domwalker",
+                                                       "hasAccessibilityProperties");
+    if (!hasMethod) {
+      return;
+    }
+
+    const hasA11YProps = await this.walker.hasAccessibilityProperties(
+      this.selection.nodeFront);
+    if (hasA11YProps) {
+      this._toolbox.doc.getElementById(menuItem.id).disabled = menuItem.disabled = false;
+    }
+
+    this.emit("node-menu-updated");
+  },
+
   _getCopySubmenu: function(markupContainer, isSelectionElement) {
     let copySubmenu = new Menu();
     copySubmenu.append(new MenuItem({
       id: "node-menu-copyinner",
       label: INSPECTOR_L10N.getStr("inspectorCopyInnerHTML.label"),
       accesskey: INSPECTOR_L10N.getStr("inspectorCopyInnerHTML.accesskey"),
       disabled: !isSelectionElement,
       click: () => this.copyInnerHTML(),
@@ -1949,16 +1983,28 @@ Inspector.prototype = {
       let jsterm = panel.hud.jsterm;
 
       jsterm.execute("inspect($0)");
       jsterm.focus();
     });
   },
 
   /**
+   * Show Accessibility properties for currently selected node
+   */
+  async showAccessibilityProperties() {
+    let a11yPanel = await this._toolbox.selectTool("accessibility");
+    // Select the accessible object in the panel and wait for the event that
+    // tells us it has been done.
+    let onSelected = a11yPanel.once("new-accessible-front-selected");
+    a11yPanel.selectAccessibleForNode(this.selection.nodeFront);
+    await onSelected;
+  },
+
+  /**
    * Use in Console.
    *
    * Takes the currently selected node in the inspector and assigns it to a
    * temp variable on the content window.  Also opens the split console and
    * autofills it with the temp variable.
    */
   useInConsole: function() {
     this._toolbox.openSplitConsole().then(() => {
--- a/devtools/client/inspector/test/head.js
+++ b/devtools/client/inspector/test/head.js
@@ -745,36 +745,16 @@ async function assertTooltipHiddenOnMous
   target.parentNode.dispatchEvent(mouseEvent);
 
   await tooltip.once("hidden");
 
   ok(!tooltip.isVisible(), "The tooltip is hidden on mouseout");
 }
 
 /**
- * Open the inspector menu and return all of it's items in a flat array
- * @param {InspectorPanel} inspector
- * @param {Object} options to pass into openMenu
- * @return An array of MenuItems
- */
-function openContextMenuAndGetAllItems(inspector, options) {
-  let menu = inspector._openMenu(options);
-
-  // Flatten all menu items into a single array to make searching through it easier
-  let allItems = [].concat.apply([], menu.items.map(function addItem(item) {
-    if (item.submenu) {
-      return addItem(item.submenu.items);
-    }
-    return item;
-  }));
-
-  return allItems;
-}
-
-/**
  * Get the rule editor from the rule-view given its index
  *
  * @param {CssRuleView} view
  *        The instance of the rule-view panel
  * @param {Number} childrenIndex
  *        The children index of the element to get
  * @param {Number} nodeIndex
  *        The child node index of the element to get
--- a/devtools/client/inspector/test/shared-head.js
+++ b/devtools/client/inspector/test/shared-head.js
@@ -568,26 +568,43 @@ var setSearchFilter = async function(vie
   for (let key of searchValue.split("")) {
     EventUtils.synthesizeKey(key, {}, view.styleWindow);
   }
 
   await view.inspector.once("ruleview-filtered");
 };
 
 /**
- * Open the style editor context menu and return all of it's items in a flat array
- * @param {CssRuleView} view
- *        The instance of the rule-view panel
- * @return An array of MenuItems
+ * Flatten all context menu items into a single array to make searching through
+ * it easier.
  */
-function openStyleContextMenuAndGetAllItems(view, target) {
-  let menu = view._contextmenu._openMenu({target: target});
-
-  // Flatten all menu items into a single array to make searching through it easier
-  let allItems = [].concat.apply([], menu.items.map(function addItem(item) {
+function buildContextMenuItems(menu) {
+  const allItems = [].concat.apply([], menu.items.map(function addItem(item) {
     if (item.submenu) {
       return addItem(item.submenu.items);
     }
     return item;
   }));
 
   return allItems;
 }
+
+/**
+ * Open the style editor context menu and return all of it's items in a flat array
+ * @param {CssRuleView} view
+ *        The instance of the rule-view panel
+ * @return An array of MenuItems
+ */
+function openStyleContextMenuAndGetAllItems(view, target) {
+  const menu = view._contextmenu._openMenu({target: target});
+  return buildContextMenuItems(menu);
+}
+
+/**
+ * Open the inspector menu and return all of it's items in a flat array
+ * @param {InspectorPanel} inspector
+ * @param {Object} options to pass into openMenu
+ * @return An array of MenuItems
+ */
+function openContextMenuAndGetAllItems(inspector, options) {
+  const menu = inspector._openMenu(options);
+  return buildContextMenuItems(menu);
+}
--- a/devtools/client/locales/en-US/inspector.properties
+++ b/devtools/client/locales/en-US/inspector.properties
@@ -296,16 +296,23 @@ inspectorSearchHTML.label3=Search HTML
 inspectorImageDataUri.label=Image Data-URL
 
 # LOCALIZATION NOTE (inspectorShowDOMProperties.label): This is the label
 # shown in the inspector contextual-menu for the item that lets users see
 # the DOM properties of the current node. When triggered, this item
 # opens the split Console and displays the properties in its side panel.
 inspectorShowDOMProperties.label=Show DOM Properties
 
+# LOCALIZATION NOTE (inspectorShowAccessibilityProperties.label): This is the
+# label shown in the inspector contextual-menu for the item that lets users see
+# the accessibility tree and accessibility properties of the current node.
+# When triggered, this item opens accessibility panel and selects an accessible
+# object for the given node.
+inspectorShowAccessibilityProperties.label=Show Accessibility Properties
+
 # LOCALIZATION NOTE (inspectorUseInConsole.label): This is the label
 # shown in the inspector contextual-menu for the item that outputs a
 # variable for the current node to the console. When triggered,
 # this item opens the split Console.
 inspectorUseInConsole.label=Use in Console
 
 # LOCALIZATION NOTE (inspectorExpandNode.label): This is the label
 # shown in the inspector contextual-menu for recursively expanding
--- a/devtools/server/actors/inspector/walker.js
+++ b/devtools/server/actors/inspector/walker.js
@@ -1,15 +1,15 @@
 /* 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, Cu} = require("chrome");
+const {Cc, Ci, Cu} = require("chrome");
 
 const Services = require("Services");
 const protocol = require("devtools/shared/protocol");
 const {walkerSpec} = require("devtools/shared/specs/inspector");
 const {LongStringActor} = require("devtools/server/actors/string");
 const InspectorUtils = require("InspectorUtils");
 
 loader.lazyRequireGetter(this, "getFrameElement", "devtools/shared/layout/utils", true);
@@ -2009,11 +2009,26 @@ var WalkerActor = protocol.ActorClassWit
     let offsetParent = node.rawNode.offsetParent;
 
     if (!offsetParent) {
       return null;
     }
 
     return this._ref(offsetParent);
   },
+
+  /**
+   * Returns true if accessibility service is running and the node has a
+   * corresponding valid accessible object.
+   */
+  hasAccessibilityProperties: async function(node) {
+    if (isNodeDead(node) || !Services.appinfo.accessibilityEnabled) {
+      return false;
+    }
+
+    const accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+      Ci.nsIAccessibilityService);
+    const acc = accService.getAccessibleFor(node.rawNode);
+    return acc && acc.indexInParent > -1;
+  },
 });
 
 exports.WalkerActor = WalkerActor;
--- a/devtools/server/actors/thread.js
+++ b/devtools/server/actors/thread.js
@@ -343,95 +343,97 @@ const ThreadActor = ActorClassWithSpec(t
    * @param Debugger.Frame frame
    *        The newest debuggee frame in the stack.
    * @param object reason
    *        An object with a 'type' property containing the reason for the pause.
    * @param function onPacket
    *        Hook to modify the packet before it is sent. Feel free to return a
    *        promise.
    */
-  _pauseAndRespond: function(frame, reason, onPacket = function(k) {
-    return k;
-  }) {
+  _pauseAndRespond: async function(frame, reason, onPacket = k => k) {
     try {
       let packet = this._paused(frame);
       if (!packet) {
         return undefined;
       }
       packet.why = reason;
 
       let generatedLocation = this.sources.getFrameLocation(frame);
-      this.sources.getOriginalLocation(generatedLocation)
-                  .then((originalLocation) => {
-                    if (!originalLocation.originalSourceActor) {
+      this.sources.getOriginalLocation(generatedLocation).then((originalLocation) => {
+        if (!originalLocation.originalSourceActor) {
           // The only time the source actor will be null is if there
           // was a sourcemap and it tried to look up the original
           // location but there was no original URL. This is a strange
           // scenario so we simply don't pause.
-                      DevToolsUtils.reportException(
+          DevToolsUtils.reportException(
             "ThreadActor",
             new Error("Attempted to pause in a script with a sourcemap but " +
                       "could not find original location.")
           );
-
-                      return undefined;
-                    }
+          return undefined;
+        }
 
-                    packet.frame.where = {
-                      source: originalLocation.originalSourceActor.form(),
-                      line: originalLocation.originalLine,
-                      column: originalLocation.originalColumn
-                    };
-                    Promise.resolve(onPacket(packet))
+        packet.frame.where = {
+          source: originalLocation.originalSourceActor.form(),
+          line: originalLocation.originalLine,
+          column: originalLocation.originalColumn
+        };
+
+        Promise.resolve(onPacket(packet))
           .catch(error => {
             reportError(error);
             return {
               error: "unknownError",
               message: error.message + "\n" + error.stack
             };
           })
           .then(pkt => {
             this.conn.send(pkt);
           });
 
-                    return undefined;
-                  });
+        return undefined;
+      });
 
       this._pushThreadPause();
     } catch (e) {
       reportError(e, "Got an exception during TA__pauseAndRespond: ");
     }
 
     // If the browser tab has been closed, terminate the debuggee script
     // instead of continuing. Executing JS after the content window is gone is
     // a bad idea.
     return this._tabClosed ? null : undefined;
   },
 
   _makeOnEnterFrame: function({ pauseAndRespond }) {
     return frame => {
       const generatedLocation = this.sources.getFrameLocation(frame);
       let { originalSourceActor } = this.unsafeSynchronize(
-        this.sources.getOriginalLocation(generatedLocation));
+        this.sources.getOriginalLocation(generatedLocation)
+      );
+
       let url = originalSourceActor.url;
 
-      return this.sources.isBlackBoxed(url)
-        ? undefined
-        : pauseAndRespond(frame);
+      if (this.sources.isBlackBoxed(url)) {
+        return undefined;
+      }
+
+      return pauseAndRespond(frame);
     };
   },
 
   _makeOnPop: function({ thread, pauseAndRespond, createValueGrip: createValueGripHook,
                           startLocation }) {
     const result = function(completion) {
       // onPop is called with 'this' set to the current frame.
-
       const generatedLocation = thread.sources.getFrameLocation(this);
       const { originalSourceActor } = thread.unsafeSynchronize(
-        thread.sources.getOriginalLocation(generatedLocation));
+        thread.sources.getOriginalLocation(generatedLocation)
+      );
+
       const url = originalSourceActor.url;
 
       if (thread.sources.isBlackBoxed(url)) {
         return undefined;
       }
 
       // Note that we're popping this frame; we need to watch for
       // subsequent step events on its caller.
@@ -460,22 +462,20 @@ const ThreadActor = ActorClassWithSpec(t
     // frame, if we did we'd also have to find the appropriate spot to
     // clear it.
     result.originalLocation = startLocation;
 
     return result;
   },
 
   _makeOnStep: function({ thread, pauseAndRespond, startFrame,
-                           startLocation, steppingType }) {
+                          startLocation, steppingType }) {
     // Breaking in place: we should always pause.
     if (steppingType === "break") {
-      return function() {
-        return pauseAndRespond(this);
-      };
+      return () => pauseAndRespond(this);
     }
 
     // Otherwise take what a "step" means into consideration.
     return function() {
       // onStep is called with 'this' set to the current frame.
 
       // Only allow stepping stops at entry points for the line, when
       // the stepping occurs in a single frame.  The "same frame"
@@ -484,18 +484,19 @@ const ThreadActor = ActorClassWithSpec(t
       // is a call "a(b())" and the user steps into b, then this
       // condition makes it possible to step out of b and into a.
       if (this === startFrame &&
           !this.script.getOffsetLocation(this.offset).isEntryPoint) {
         return undefined;
       }
 
       const generatedLocation = thread.sources.getFrameLocation(this);
-      const newLocation = thread.unsafeSynchronize(thread.sources.getOriginalLocation(
-        generatedLocation));
+      const newLocation = thread.unsafeSynchronize(
+        thread.sources.getOriginalLocation(generatedLocation)
+      );
 
       // Cases when we should pause because we have executed enough to consider
       // a "step" to have occured:
       //
       // 1.1. We change frames.
       // 1.2. We change URLs (can happen without changing frames thanks to
       //      source mapping).
       // 1.3. The source has pause points and we change locations.
@@ -554,21 +555,22 @@ const ThreadActor = ActorClassWithSpec(t
    * Define the JS hook functions for stepping.
    */
   _makeSteppingHooks: function(startLocation, steppingType) {
     // Bind these methods and state because some of the hooks are called
     // with 'this' set to the current frame. Rather than repeating the
     // binding in each _makeOnX method, just do it once here and pass it
     // in to each function.
     const steppingHookState = {
-      pauseAndRespond: (frame, onPacket = k=>k) => {
-        return this._pauseAndRespond(frame, { type: "resumeLimit" }, onPacket);
-      },
-      createValueGrip: v => createValueGrip(v, this._pausePool,
-        this.objectGrip),
+      pauseAndRespond: (frame, onPacket = k=>k) => this._pauseAndRespond(
+        frame,
+        { type: "resumeLimit" },
+        onPacket
+      ),
+      createValueGrip: v => createValueGrip(v, this._pausePool, this.objectGrip),
       thread: this,
       startFrame: this.youngestFrame,
       startLocation: startLocation,
       steppingType: steppingType
     };
 
     return {
       onEnterFrame: this._makeOnEnterFrame(steppingHookState),
@@ -581,51 +583,52 @@ const ThreadActor = ActorClassWithSpec(t
    * Handle attaching the various stepping hooks we need to attach when we
    * receive a resume request with a resumeLimit property.
    *
    * @param Object request
    *        The request packet received over the RDP.
    * @returns A promise that resolves to true once the hooks are attached, or is
    *          rejected with an error packet.
    */
-  _handleResumeLimit: function(request) {
+  _handleResumeLimit: async function(request) {
     let steppingType = request.resumeLimit.type;
     if (!["break", "step", "next", "finish"].includes(steppingType)) {
-      return Promise.reject({ error: "badParameterType",
-                              message: "Unknown resumeLimit type" });
+      return Promise.reject({
+        error: "badParameterType",
+        message: "Unknown resumeLimit type"
+      });
     }
 
     const generatedLocation = this.sources.getFrameLocation(this.youngestFrame);
-    return this.sources.getOriginalLocation(generatedLocation)
-      .then(originalLocation => {
-        const { onEnterFrame, onPop, onStep } = this._makeSteppingHooks(originalLocation,
-                                                                        steppingType);
+    const originalLocation = await this.sources.getOriginalLocation(generatedLocation);
+    const { onEnterFrame, onPop, onStep } = this._makeSteppingHooks(
+      originalLocation,
+      steppingType
+    );
 
-        // Make sure there is still a frame on the stack if we are to continue
-        // stepping.
-        let stepFrame = this._getNextStepFrame(this.youngestFrame);
-        if (stepFrame) {
-          switch (steppingType) {
-            case "step":
-              this.dbg.onEnterFrame = onEnterFrame;
-              // Fall through.
-            case "break":
-            case "next":
-              if (stepFrame.script) {
-                stepFrame.onStep = onStep;
-              }
-              stepFrame.onPop = onPop;
-              break;
-            case "finish":
-              stepFrame.onPop = onPop;
+    // Make sure there is still a frame on the stack if we are to continue stepping.
+    let stepFrame = this._getNextStepFrame(this.youngestFrame);
+    if (stepFrame) {
+      switch (steppingType) {
+        case "step":
+          this.dbg.onEnterFrame = onEnterFrame;
+          // Fall through.
+        case "break":
+        case "next":
+          if (stepFrame.script) {
+            stepFrame.onStep = onStep;
           }
-        }
+          stepFrame.onPop = onPop;
+          break;
+        case "finish":
+          stepFrame.onPop = onPop;
+      }
+    }
 
-        return true;
-      });
+    return true;
   },
 
   /**
    * Clear the onStep and onPop hooks from the given frame and all of the frames
    * below it.
    *
    * @param Debugger.Frame aFrame
    *        The frame we want to clear the stepping hooks from.
@@ -1340,24 +1343,33 @@ const ThreadActor = ActorClassWithSpec(t
    * Return a protocol completion value representing the given
    * Debugger-provided completion value.
    */
   createProtocolCompletionValue: function(completion) {
     let protoValue = {};
     if (completion == null) {
       protoValue.terminated = true;
     } else if ("return" in completion) {
-      protoValue.return = createValueGrip(completion.return,
-        this._pausePool, this.objectGrip);
+      protoValue.return = createValueGrip(
+        completion.return,
+        this._pausePool,
+        this.objectGrip
+      );
     } else if ("throw" in completion) {
-      protoValue.throw = createValueGrip(completion.throw,
-        this._pausePool, this.objectGrip);
+      protoValue.throw = createValueGrip(
+        completion.throw,
+        this._pausePool,
+        this.objectGrip
+      );
     } else {
-      protoValue.return = createValueGrip(completion.yield,
-        this._pausePool, this.objectGrip);
+      protoValue.return = createValueGrip(
+        completion.yield,
+        this._pausePool,
+        this.objectGrip
+      );
     }
     return protoValue;
   },
 
   /**
    * Create a grip for the given debuggee object.
    *
    * @param value Debugger.Object
--- a/devtools/shared/specs/inspector.js
+++ b/devtools/shared/specs/inspector.js
@@ -325,16 +325,24 @@ const walkerSpec = generateActorSpec({
     getOffsetParent: {
       request: {
         node: Arg(0, "nullable:domnode")
       },
       response: {
         node: RetVal("nullable:domnode")
       }
     },
+    hasAccessibilityProperties: {
+      request: {
+        node: Arg(0, "nullable:domnode")
+      },
+      response: {
+        value: RetVal("boolean")
+      }
+    }
   }
 });
 
 exports.walkerSpec = walkerSpec;
 
 const inspectorSpec = generateActorSpec({
   typeName: "inspector",
 
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -10135,25 +10135,25 @@ nsContentUtils::RegisterUnresolvedElemen
   registry->RegisterUnresolvedElement(aElement, aTypeName);
 }
 
 /* static */ void
 nsContentUtils::UnregisterUnresolvedElement(Element* aElement)
 {
   MOZ_ASSERT(aElement);
 
-  RefPtr<nsAtom> typeAtom =
+  nsAtom* typeAtom =
     aElement->GetCustomElementData()->GetCustomElementType();
   nsIDocument* doc = aElement->OwnerDoc();
   nsPIDOMWindowInner* window(doc->GetInnerWindow());
   if (!window) {
     return;
   }
 
-  RefPtr<CustomElementRegistry> registry(window->CustomElements());
+  CustomElementRegistry* registry = window->CustomElements();
   if (!registry) {
     return;
   }
 
   registry->UnregisterUnresolvedElement(aElement, typeAtom);
 }
 
 /* static */ void
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -541,37 +541,30 @@ StaticAutoPtr<ContentChild::ShutdownCana
 ContentChild::ContentChild()
  : mID(uint64_t(-1))
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
  , mMainChromeTid(0)
  , mMsaaID(0)
 #endif
  , mIsAlive(true)
  , mShuttingDown(false)
- , mShutdownTimeout(0)
 {
   // This process is a content process, so it's clearly running in
   // multiprocess mode!
   nsDebugImpl::SetMultiprocessMode("Child");
 
   // When ContentChild is created, the observer service does not even exist.
   // When ContentChild::RecvSetXPCOMProcessAttributes is called (the first
   // IPDL call made on this object), shutdown may have already happened. Thus
   // we create a canary here that relies upon getting cleared if shutdown
   // happens without requiring the observer service at this time.
   if (!sShutdownCanary) {
     sShutdownCanary = new ShutdownCanary();
     ClearOnShutdown(&sShutdownCanary, ShutdownPhase::Shutdown);
   }
-  // If a shutdown message is received from within a nested event loop, we set
-  // the timeout for the nested event loop to half the ForceKillTimer timeout
-  // (in ms) to leave enough time to send the FinishShutdown message to the
-  // parent.
-  mShutdownTimeout =
-    Preferences::GetInt("dom.ipc.tabs.shutdownTimeoutSecs", 5) * 1000 / 2;
 }
 
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(disable:4722) /* Silence "destructor never returns" warning */
 #endif
 
 ContentChild::~ContentChild()
@@ -3015,42 +3008,38 @@ ContentChild::RecvShutdown()
 
   ShutdownInternal();
   return IPC_OK();
 }
 
 void
 ContentChild::ShutdownInternal()
 {
-  CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("IPCShutdownState"),
-                                     NS_LITERAL_CSTRING("RecvShutdown"));
-
   // If we receive the shutdown message from within a nested event loop, we want
   // to wait for that event loop to finish. Otherwise we could prematurely
   // terminate an "unload" or "pagehide" event handler (which might be doing a
-  // sync XHR, for example). However, we need to strike a balance and shut down
-  // within a reasonable amount of time (mShutdownTimeout) or the ForceKillTimer
-  // in the parent will execute and kill us hard.
+  // sync XHR, for example).
+  CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("IPCShutdownState"),
+                                     NS_LITERAL_CSTRING("RecvShutdown"));
+
+  MOZ_ASSERT(NS_IsMainThread());
+  RefPtr<nsThread> mainThread = nsThreadManager::get().GetCurrentThread();
   // Note that we only have to check the recursion count for the current
   // cooperative thread. Since the Shutdown message is not labeled with a
   // SchedulerGroup, there can be no other cooperative threads doing work while
   // we're running.
-  MOZ_ASSERT(NS_IsMainThread());
-  RefPtr<nsThread> mainThread = nsThreadManager::get().GetCurrentThread();
-  if (mainThread && mainThread->RecursionDepth() > 1 && mShutdownTimeout > 0) {
+  if (mainThread && mainThread->RecursionDepth() > 1) {
     // We're in a nested event loop. Let's delay for an arbitrary period of
     // time (100ms) in the hopes that the event loop will have finished by
     // then.
-    int32_t delay = 100;
     MessageLoop::current()->PostDelayedTask(
       NewRunnableMethod(
         "dom::ContentChild::RecvShutdown", this,
         &ContentChild::ShutdownInternal),
-      delay);
-    mShutdownTimeout -= delay;
+      100);
     return;
   }
 
   mShuttingDown = true;
 
 #ifdef NIGHTLY_BUILD
   HangMonitor::UnregisterAnnotator(PendingInputEventHangAnnotator::sSingleton);
 #endif
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -823,17 +823,16 @@ private:
   // Hashtable to keep track of the pending file creation.
   // These items are removed when RecvFileCreationResponse is received.
   nsRefPtrHashtable<nsIDHashKey, FileCreatorHelper> mFileCreationPending;
 
 
   nsClassHashtable<nsUint64HashKey, AnonymousTemporaryFileCallback> mPendingAnonymousTemporaryFiles;
 
   mozilla::Atomic<bool> mShuttingDown;
-  int32_t mShutdownTimeout;
 
 #ifdef NIGHTLY_BUILD
   // NOTE: This member is atomic because it can be accessed from off-main-thread.
   mozilla::Atomic<uint32_t> mPendingInputEvents;
 #endif
 
   DISALLOW_EVIL_CONSTRUCTORS(ContentChild);
 };
--- a/dom/serviceworkers/ServiceWorkerManager.cpp
+++ b/dom/serviceworkers/ServiceWorkerManager.cpp
@@ -82,16 +82,17 @@
 #include "ServiceWorkerRegistrar.h"
 #include "ServiceWorkerRegistration.h"
 #include "ServiceWorkerRegistrationListener.h"
 #include "ServiceWorkerScriptCache.h"
 #include "ServiceWorkerEvents.h"
 #include "ServiceWorkerUnregisterJob.h"
 #include "ServiceWorkerUpdateJob.h"
 #include "ServiceWorkerUpdaterChild.h"
+#include "ServiceWorkerUtils.h"
 
 #ifdef PostMessage
 #undef PostMessage
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
@@ -191,37 +192,38 @@ PopulateRegistrationData(nsIPrincipal* a
 
   nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &aData.principal());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   aData.scope() = aRegistration->Scope();
 
-  RefPtr<ServiceWorkerInfo> newest = aRegistration->Newest();
-  if (NS_WARN_IF(!newest)) {
+  // TODO: When bug 1426401 is implemented we will need to handle more
+  //       than just the active worker here.
+  RefPtr<ServiceWorkerInfo> active = aRegistration->GetActive();
+  MOZ_ASSERT(active);
+  if (NS_WARN_IF(!active)) {
     return NS_ERROR_FAILURE;
   }
 
-  if (aRegistration->GetActive()) {
-    aData.currentWorkerURL() = aRegistration->GetActive()->ScriptSpec();
-    aData.cacheName() = aRegistration->GetActive()->CacheName();
-    aData.currentWorkerHandlesFetch() = aRegistration->GetActive()->HandlesFetch();
-
-    aData.currentWorkerInstalledTime() =
-      aRegistration->GetActive()->GetInstalledTime();
-    aData.currentWorkerActivatedTime() =
-      aRegistration->GetActive()->GetActivatedTime();
-  }
+  aData.currentWorkerURL() = active->ScriptSpec();
+  aData.cacheName() = active->CacheName();
+  aData.currentWorkerHandlesFetch() = active->HandlesFetch();
+
+  aData.currentWorkerInstalledTime() = active->GetInstalledTime();
+  aData.currentWorkerActivatedTime() = active->GetActivatedTime();
 
   aData.updateViaCache() =
     static_cast<uint32_t>(aRegistration->GetUpdateViaCache());
 
   aData.lastUpdateTime() = aRegistration->GetLastUpdateTime();
 
+  MOZ_ASSERT(ServiceWorkerRegistrationDataIsValid(aData));
+
   return NS_OK;
 }
 
 class TeardownRunnable final : public Runnable
 {
 public:
   explicit TeardownRunnable(ServiceWorkerManagerChild* aActor)
     : Runnable("dom::ServiceWorkerManager::TeardownRunnable")
--- a/dom/serviceworkers/ServiceWorkerRegisterJob.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegisterJob.cpp
@@ -35,23 +35,18 @@ ServiceWorkerRegisterJob::AsyncExecute()
 
   RefPtr<ServiceWorkerRegistrationInfo> registration =
     swm->GetRegistration(mPrincipal, mScope);
 
   if (registration) {
     bool sameUVC = GetUpdateViaCache() == registration->GetUpdateViaCache();
     registration->SetUpdateViaCache(GetUpdateViaCache());
 
-    // If we are resurrecting an uninstalling registration, then persist
-    // it to disk again.  We preemptively removed it earlier during
-    // unregister so that closing the window by shutting down the browser
-    // results in the registration being gone on restart.
     if (registration->IsPendingUninstall()) {
       registration->ClearPendingUninstall();
-      swm->StoreRegistration(mPrincipal, registration);
       // Its possible that a ready promise is created between when the
       // uninstalling flag is set and when we resurrect the registration
       // here.  In that case we might need to fire the ready promise
       // now.
       swm->CheckPendingReadyPromises();
     }
     RefPtr<ServiceWorkerInfo> newest = registration->Newest();
     if (newest && mScriptSpec.Equals(newest->ScriptSpec()) && sameUVC) {
--- a/dom/serviceworkers/ServiceWorkerRegistrar.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistrar.cpp
@@ -684,16 +684,22 @@ ServiceWorkerRegistrar::ReadData()
       return NS_ERROR_FAILURE;
     }
   }
 
   stream->Close();
 
   // Copy data over to mData.
   for (uint32_t i = 0; i < tmpData.Length(); ++i) {
+    // Older versions could sometimes write out empty, useless entries.
+    // Prune those here.
+    if (!ServiceWorkerRegistrationDataIsValid(tmpData[i])) {
+      continue;
+    }
+
     bool match = false;
     if (dedupe) {
       MOZ_ASSERT(overwrite);
       // If this is an old profile, then we might need to deduplicate.  In
       // theory this can be removed in the future (Bug 1248449)
       for (uint32_t j = 0; j < mData.Length(); ++j) {
         // Use same comparison as RegisterServiceWorker. Scope contains
         // basic origin information.  Combine with any principal attributes.
@@ -774,16 +780,17 @@ ServiceWorkerRegistrar::RegisterServiceW
     if (Equivalent(aData, mData[i])) {
       mData[i] = aData;
       found = true;
       break;
     }
   }
 
   if (!found) {
+    MOZ_ASSERT(ServiceWorkerRegistrationDataIsValid(aData));
     mData.AppendElement(aData);
   }
 }
 
 class ServiceWorkerRegistrarSaveDataRunnable final : public Runnable
 {
 public:
   ServiceWorkerRegistrarSaveDataRunnable()
@@ -951,16 +958,22 @@ ServiceWorkerRegistrar::WriteData()
     return rv;
   }
 
   if (count != buffer.Length()) {
     return NS_ERROR_UNEXPECTED;
   }
 
   for (uint32_t i = 0, len = data.Length(); i < len; ++i) {
+    // We have an assertion further up the stack, but as a last
+    // resort avoid writing out broken entries here.
+    if (!ServiceWorkerRegistrationDataIsValid(data[i])) {
+      continue;
+    }
+
     const mozilla::ipc::PrincipalInfo& info = data[i].principal();
 
     MOZ_ASSERT(info.type() == mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
 
     const mozilla::ipc::ContentPrincipalInfo& cInfo =
       info.get_ContentPrincipalInfo();
 
     nsAutoCString suffix;
--- a/dom/serviceworkers/ServiceWorkerRegistrationInfo.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistrationInfo.cpp
@@ -136,16 +136,26 @@ void
 ServiceWorkerRegistrationInfo::SetPendingUninstall()
 {
   mPendingUninstall = true;
 }
 
 void
 ServiceWorkerRegistrationInfo::ClearPendingUninstall()
 {
+  // If we are resurrecting an uninstalling registration, then persist
+  // it to disk again.  We preemptively removed it earlier during
+  // unregister so that closing the window by shutting down the browser
+  // results in the registration being gone on restart.
+  if (mPendingUninstall && mActiveWorker) {
+    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+    if (swm) {
+      swm->StoreRegistration(mPrincipal, this);
+    }
+  }
   mPendingUninstall = false;
 }
 
 NS_IMPL_ISUPPORTS(ServiceWorkerRegistrationInfo, nsIServiceWorkerRegistrationInfo)
 
 NS_IMETHODIMP
 ServiceWorkerRegistrationInfo::GetPrincipal(nsIPrincipal** aPrincipal)
 {
@@ -602,22 +612,18 @@ ServiceWorkerRegistrationInfo::Transitio
 
   UpdateRegistrationState();
 
   mWaitingWorker->UpdateState(ServiceWorkerState::Installed);
   mWaitingWorker->UpdateInstalledTime();
 
   NotifyChromeRegistrationListeners();
 
-  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
-  if (!swm) {
-    // browser shutdown began
-    return;
-  }
-  swm->StoreRegistration(mPrincipal, this);
+  // TODO: When bug 1426401 is implemented we will need to call
+  //       StoreRegistration() here to persist the waiting worker.
 }
 
 void
 ServiceWorkerRegistrationInfo::SetActive(ServiceWorkerInfo* aServiceWorker)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aServiceWorker);
 
--- a/dom/serviceworkers/ServiceWorkerUtils.cpp
+++ b/dom/serviceworkers/ServiceWorkerUtils.cpp
@@ -27,10 +27,18 @@ ServiceWorkerParentInterceptEnabled()
                                  "dom.serviceWorkers.parent_intercept",
                                  false);
     sInit = true;
   }
 
   return sEnabled;
 }
 
+bool
+ServiceWorkerRegistrationDataIsValid(const ServiceWorkerRegistrationData& aData)
+{
+  return !aData.scope().IsEmpty() &&
+         !aData.currentWorkerURL().IsEmpty() &&
+         !aData.cacheName().IsEmpty();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/serviceworkers/ServiceWorkerUtils.h
+++ b/dom/serviceworkers/ServiceWorkerUtils.h
@@ -7,20 +7,24 @@
 #define _mozilla_dom_ServiceWorkerUtils_h
 
 #include "mozilla/MozPromise.h"
 #include "mozilla/dom/ServiceWorkerRegistrationDescriptor.h"
 
 namespace mozilla {
 namespace dom {
 
+class ServiceWorkerRegistrationData;
 class ServiceWorkerRegistrationDescriptor;
 
 typedef MozPromise<ServiceWorkerRegistrationDescriptor, nsresult, false>
         ServiceWorkerRegistrationPromise;
 
 bool
 ServiceWorkerParentInterceptEnabled();
 
+bool
+ServiceWorkerRegistrationDataIsValid(const ServiceWorkerRegistrationData& aData);
+
 } // namespace dom
 } // namespace mozilla
 
 #endif // _mozilla_dom_ServiceWorkerUtils_h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1450796.js
@@ -0,0 +1,7 @@
+function f() {
+    var t = new Float32Array(1);
+    t[t.length] = 1;
+    return t[t.length];
+}
+for (var i = 0; i < 5; i++)
+    assertEq(f(), undefined);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -10232,24 +10232,21 @@ CodeGenerator::link(JSContext* cx, Compi
 
     if (!linkSharedStubs(cx))
         return false;
 
     // Check to make sure we didn't have a mid-build invalidation. If so, we
     // will trickle to jit::Compile() and return Method_Skipped.
     uint32_t warmUpCount = script->getWarmUpCount();
 
-    JitRuntime* jrt = cx->runtime()->jitRuntime();
-    IonCompilationId compilationId = jrt->nextCompilationId();
-#ifdef DEBUG
-    jrt->currentCompilationId().emplace(compilationId);
-    auto resetCurrentId = mozilla::MakeScopeExit([jrt] {
-        jrt->currentCompilationId().reset();
+    IonCompilationId compilationId = cx->runtime()->jitRuntime()->nextCompilationId();
+    cx->zone()->types.currentCompilationIdRef().emplace(compilationId);
+    auto resetCurrentId = mozilla::MakeScopeExit([cx] {
+        cx->zone()->types.currentCompilationIdRef().reset();
     });
-#endif
 
     // Record constraints. If an error occured, returns false and potentially
     // prevent future compilations. Otherwise, if an invalidation occured, then
     // skip the current compilation.
     bool isValid = false;
     if (!FinishCompilation(cx, script, constraints, compilationId, &isValid))
         return false;
     if (!isValid)
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -2865,17 +2865,17 @@ jit::Invalidate(TypeZone& types, FreeOp*
 
     // Add an invalidation reference to all invalidated IonScripts to indicate
     // to the traversal which frames have been invalidated.
     size_t numInvalidations = 0;
     for (const RecompileInfo& info : invalid) {
         if (cancelOffThread)
             CancelOffThreadIonCompile(info.script());
 
-        IonScript* ionScript = info.maybeIonScriptToInvalidate();
+        IonScript* ionScript = info.maybeIonScriptToInvalidate(types);
         if (!ionScript)
             continue;
 
         JitSpew(JitSpew_IonInvalidate, " Invalidate %s:%zu, IonScript %p",
                 info.script()->filename(), info.script()->lineno(), ionScript);
 
         // Keep the ion script alive during the invalidation and flag this
         // ionScript as being invalidated.  This increment is removed by the
@@ -2892,17 +2892,17 @@ jit::Invalidate(TypeZone& types, FreeOp*
     JSContext* cx = TlsContext.get();
     for (JitActivationIterator iter(cx); !iter.done(); ++iter)
         InvalidateActivation(fop, iter, false);
 
     // Drop the references added above. If a script was never active, its
     // IonScript will be immediately destroyed. Otherwise, it will be held live
     // until its last invalidated frame is destroyed.
     for (const RecompileInfo& info : invalid) {
-        IonScript* ionScript = info.maybeIonScriptToInvalidate();
+        IonScript* ionScript = info.maybeIonScriptToInvalidate(types);
         if (!ionScript)
             continue;
 
         if (ionScript->invalidationCount() == 1) {
             // decrementInvalidationCount will destroy the IonScript so null out
             // script->ion now. We don't want to do this unconditionally because
             // maybeIonScriptToInvalidate depends on script->ion (we would leak
             // the IonScript if |invalid| contains duplicates).
@@ -2914,17 +2914,17 @@ jit::Invalidate(TypeZone& types, FreeOp*
     }
 
     // Make sure we didn't leak references by invalidating the same IonScript
     // multiple times in the above loop.
     MOZ_ASSERT(!numInvalidations);
 
     // Finally, null out script->ion for IonScripts that are still on the stack.
     for (const RecompileInfo& info : invalid) {
-        if (info.maybeIonScriptToInvalidate())
+        if (info.maybeIonScriptToInvalidate(types))
             ClearIonScriptAfterInvalidation(cx, info.script(), resetUses);
     }
 }
 
 void
 jit::Invalidate(JSContext* cx, const RecompileInfoVector& invalid, bool resetUses,
                 bool cancelOffThread)
 {
--- a/js/src/jit/JitCompartment.h
+++ b/js/src/jit/JitCompartment.h
@@ -60,19 +60,16 @@ class JitRuntime
 {
   private:
     friend class JitCompartment;
 
     // Executable allocator for all code except wasm code.
     ActiveThreadData<ExecutableAllocator> execAlloc_;
 
     ActiveThreadData<uint64_t> nextCompilationId_;
-#ifdef DEBUG
-    ActiveThreadData<mozilla::Maybe<IonCompilationId>> currentCompilationId_;
-#endif
 
     // Shared exception-handler tail.
     ExclusiveAccessLockWriteOnceData<uint32_t> exceptionTailOffset_;
 
     // Shared post-bailout-handler tail.
     ExclusiveAccessLockWriteOnceData<uint32_t> bailoutTailOffset_;
 
     // Shared profiler exit frame tail.
@@ -185,21 +182,16 @@ class JitRuntime
 
     ExecutableAllocator& execAlloc() {
         return execAlloc_.ref();
     }
 
     IonCompilationId nextCompilationId() {
         return IonCompilationId(nextCompilationId_++);
     }
-#ifdef DEBUG
-    mozilla::Maybe<IonCompilationId>& currentCompilationId() {
-        return currentCompilationId_.ref();
-    }
-#endif
 
     TrampolinePtr getVMWrapper(const VMFunction& f) const;
     JitCode* debugTrapHandler(JSContext* cx);
     JitCode* getBaselineDebugModeOSRHandler(JSContext* cx);
     void* getBaselineDebugModeOSRHandlerAddress(JSContext* cx, bool popFrameReg);
 
     TrampolinePtr getGenericBailoutHandler() const {
         return trampolineCode(bailoutHandlerOffset_);
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -1425,16 +1425,18 @@ CodeGenerator::visitBoxFloatingPoint(LBo
 
 void
 CodeGenerator::visitUnbox(LUnbox* unbox)
 {
     // Note that for unbox, the type and payload indexes are switched on the
     // inputs.
     MUnbox* mir = unbox->mir();
     Register type = ToRegister(unbox->type());
+    Register payload = ToRegister(unbox->payload());
+    Register output = ToRegister(unbox->output());
 
     mozilla::Maybe<ScratchRegisterScope> scratch;
     scratch.emplace(masm);
 
     JSValueTag tag = MIRTypeToTag(mir->type());
     if (mir->fallible()) {
         masm.ma_cmp(type, Imm32(tag), *scratch);
         bailoutIf(Assembler::NotEqual, unbox->snapshot());
@@ -1443,16 +1445,21 @@ CodeGenerator::visitUnbox(LUnbox* unbox)
         Label ok;
         masm.ma_cmp(type, Imm32(tag), *scratch);
         masm.ma_b(&ok, Assembler::Equal);
         scratch.reset();
         masm.assumeUnreachable("Infallible unbox type mismatch");
         masm.bind(&ok);
 #endif
     }
+
+    // Note: If spectreValueMasking is disabled, then this instruction will
+    // default to a no-op as long as the lowering allocate the same register for
+    // the output and the payload.
+    masm.unboxNonDouble(ValueOperand(type, payload), output, ValueTypeFromMIRType(mir->type()));
 }
 
 void
 CodeGenerator::visitDouble(LDouble* ins)
 {
     const LDefinition* out = ins->getDef(0);
     masm.loadConstantDouble(ins->getDouble(), ToFloatRegister(out));
 }
--- a/js/src/jit/arm/MacroAssembler-arm-inl.h
+++ b/js/src/jit/arm/MacroAssembler-arm-inl.h
@@ -2171,17 +2171,16 @@ MacroAssembler::spectreZeroRegister(Cond
 {
     ma_mov(Imm32(0), dest, cond);
 }
 
 void
 MacroAssembler::spectreBoundsCheck32(Register index, Register length, Register maybeScratch,
                                      Label* failure)
 {
-    MOZ_ASSERT(index != length);
     MOZ_ASSERT(length != maybeScratch);
     MOZ_ASSERT(index != maybeScratch);
 
     branch32(Assembler::BelowOrEqual, length, index, failure);
 
     if (JitOptions.spectreIndexMasking)
         ma_mov(Imm32(0), index, Assembler::BelowOrEqual);
 }
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -2995,36 +2995,72 @@ MacroAssemblerARMCompat::testGCThing(Con
     ScratchRegisterScope scratch(asMasm());
     extractTag(address, scratch);
     ma_cmp(scratch, ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET));
     return cond == Equal ? AboveOrEqual : Below;
 }
 
 // Unboxing code.
 void
-MacroAssemblerARMCompat::unboxNonDouble(const ValueOperand& operand, Register dest, JSValueType)
-{
-    if (operand.payloadReg() != dest)
-        ma_mov(operand.payloadReg(), dest);
-}
-
-void
-MacroAssemblerARMCompat::unboxNonDouble(const Address& src, Register dest, JSValueType)
+MacroAssemblerARMCompat::unboxNonDouble(const ValueOperand& operand, Register dest, JSValueType type)
+{
+    auto movPayloadToDest = [&]() {
+        if (operand.payloadReg() != dest)
+            ma_mov(operand.payloadReg(), dest, LeaveCC);
+    };
+    if (!JitOptions.spectreValueMasking) {
+        movPayloadToDest();
+        return;
+    }
+
+    // Spectre mitigation: We zero the payload if the tag does not match the
+    // expected type and if this is a pointer type.
+    if (type == JSVAL_TYPE_INT32 || type == JSVAL_TYPE_BOOLEAN) {
+        movPayloadToDest();
+        return;
+    }
+
+    // We zero the destination register and move the payload into it if
+    // the tag corresponds to the given type.
+    ma_cmp(operand.typeReg(), ImmType(type));
+    movPayloadToDest();
+    ma_mov(Imm32(0), dest, NotEqual);
+}
+
+void
+MacroAssemblerARMCompat::unboxNonDouble(const Address& src, Register dest, JSValueType type)
 {
     ScratchRegisterScope scratch(asMasm());
-    ma_ldr(ToPayload(src), dest, scratch);
-}
-
-void
-MacroAssemblerARMCompat::unboxNonDouble(const BaseIndex& src, Register dest, JSValueType)
-{
-    ScratchRegisterScope scratch(asMasm());
+    if (!JitOptions.spectreValueMasking) {
+        ma_ldr(ToPayload(src), dest, scratch);
+        return;
+    }
+
+    // Spectre mitigation: We zero the payload if the tag does not match the
+    // expected type and if this is a pointer type.
+    if (type == JSVAL_TYPE_INT32 || type == JSVAL_TYPE_BOOLEAN) {
+        ma_ldr(ToPayload(src), dest, scratch);
+        return;
+    }
+
+    // We zero the destination register and move the payload into it if
+    // the tag corresponds to the given type.
+    ma_ldr(ToType(src), scratch, scratch);
+    ma_cmp(scratch, ImmType(type));
+    ma_ldr(ToPayload(src), dest, scratch, Offset, Equal);
+    ma_mov(Imm32(0), dest, NotEqual);
+}
+
+void
+MacroAssemblerARMCompat::unboxNonDouble(const BaseIndex& src, Register dest, JSValueType type)
+{
     SecondScratchRegisterScope scratch2(asMasm());
-    ma_alu(src.base, lsl(src.index, src.scale), scratch, OpAdd);
-    ma_ldr(Address(scratch, src.offset), dest, scratch2);
+    ma_alu(src.base, lsl(src.index, src.scale), scratch2, OpAdd);
+    Address value(scratch2, src.offset);
+    unboxNonDouble(value, dest, type);
 }
 
 void
 MacroAssemblerARMCompat::unboxDouble(const ValueOperand& operand, FloatRegister dest)
 {
     MOZ_ASSERT(dest.isDouble());
     as_vxfer(operand.payloadReg(), operand.typeReg(), VFPRegister(dest), CoreToFloat);
 }
@@ -3043,18 +3079,18 @@ MacroAssemblerARMCompat::unboxValue(cons
     if (dest.isFloat()) {
         Label notInt32, end;
         asMasm().branchTestInt32(Assembler::NotEqual, src, &notInt32);
         convertInt32ToDouble(src.payloadReg(), dest.fpu());
         ma_b(&end);
         bind(&notInt32);
         unboxDouble(src, dest.fpu());
         bind(&end);
-    } else if (src.payloadReg() != dest.gpr()) {
-        as_mov(dest.gpr(), O2Reg(src.payloadReg()));
+    } else {
+        unboxNonDouble(src, dest.gpr(), type);
     }
 }
 
 void
 MacroAssemblerARMCompat::unboxPrivate(const ValueOperand& src, Register dest)
 {
     ma_mov(src.payloadReg(), dest);
 }
--- a/js/src/jit/arm/MacroAssembler-arm.h
+++ b/js/src/jit/arm/MacroAssembler-arm.h
@@ -824,22 +824,25 @@ class MacroAssemblerARMCompat : public M
     void boxDouble(FloatRegister src, const ValueOperand& dest, FloatRegister);
     void boxNonDouble(JSValueType type, Register src, const ValueOperand& dest);
 
     // Extended unboxing API. If the payload is already in a register, returns
     // that register. Otherwise, provides a move to the given scratch register,
     // and returns that.
     Register extractObject(const Address& address, Register scratch);
     Register extractObject(const ValueOperand& value, Register scratch) {
+        unboxNonDouble(value, value.payloadReg(), JSVAL_TYPE_OBJECT);
         return value.payloadReg();
     }
     Register extractString(const ValueOperand& value, Register scratch) {
+        unboxNonDouble(value, value.payloadReg(), JSVAL_TYPE_STRING);
         return value.payloadReg();
     }
     Register extractSymbol(const ValueOperand& value, Register scratch) {
+        unboxNonDouble(value, value.payloadReg(), JSVAL_TYPE_SYMBOL);
         return value.payloadReg();
     }
     Register extractInt32(const ValueOperand& value, Register scratch) {
         return value.payloadReg();
     }
     Register extractBoolean(const ValueOperand& value, Register scratch) {
         return value.payloadReg();
     }
--- a/js/src/jit/arm64/MacroAssembler-arm64-inl.h
+++ b/js/src/jit/arm64/MacroAssembler-arm64-inl.h
@@ -1850,17 +1850,16 @@ MacroAssembler::spectreZeroRegister(Cond
     Csel(ARMRegister(dest, 64), ARMRegister(dest, 64), vixl::xzr,
          Assembler::InvertCondition(cond));
 }
 
 void
 MacroAssembler::spectreBoundsCheck32(Register index, Register length, Register maybeScratch,
                                      Label* failure)
 {
-    MOZ_ASSERT(index != length);
     MOZ_ASSERT(length != maybeScratch);
     MOZ_ASSERT(index != maybeScratch);
 
     branch32(Assembler::BelowOrEqual, length, index, failure);
 
     if (JitOptions.spectreIndexMasking)
         Csel(ARMRegister(index, 32), ARMRegister(index, 32), vixl::wzr, Assembler::Above);
 }
--- a/js/src/jit/x64/MacroAssembler-x64-inl.h
+++ b/js/src/jit/x64/MacroAssembler-x64-inl.h
@@ -851,17 +851,16 @@ MacroAssembler::spectreMovePtr(Condition
 {
     cmovCCq(cond, Operand(src), dest);
 }
 
 void
 MacroAssembler::spectreBoundsCheck32(Register index, Register length, Register maybeScratch,
                                      Label* failure)
 {
-    MOZ_ASSERT(index != length);
     MOZ_ASSERT(length != maybeScratch);
     MOZ_ASSERT(index != maybeScratch);
 
     ScratchRegisterScope scratch(*this);
     MOZ_ASSERT(index != scratch);
     MOZ_ASSERT(length != scratch);
 
     if (JitOptions.spectreIndexMasking)
--- a/js/src/jit/x86/MacroAssembler-x86-inl.h
+++ b/js/src/jit/x86/MacroAssembler-x86-inl.h
@@ -1076,17 +1076,16 @@ MacroAssembler::spectreBoundsCheck32(Reg
         }
     }
 }
 
 void
 MacroAssembler::spectreBoundsCheck32(Register index, Register length, Register maybeScratch,
                                      Label* failure)
 {
-    MOZ_ASSERT(index != length);
     MOZ_ASSERT(length != maybeScratch);
     MOZ_ASSERT(index != maybeScratch);
 
     spectreBoundsCheck32(index, Operand(length), maybeScratch, failure);
 }
 
 void
 MacroAssembler::spectreBoundsCheck32(Register index, const Address& length, Register maybeScratch,
--- a/js/src/vm/TypeInference-inl.h
+++ b/js/src/vm/TypeInference-inl.h
@@ -31,34 +31,44 @@
 
 namespace js {
 
 /////////////////////////////////////////////////////////////////////
 // RecompileInfo
 /////////////////////////////////////////////////////////////////////
 
 jit::IonScript*
-RecompileInfo::maybeIonScriptToInvalidate() const
+RecompileInfo::maybeIonScriptToInvalidate(const TypeZone& zone) const
 {
+    MOZ_ASSERT(script_->zone() == zone.zone());
+
     // Make sure this is not called under CodeGenerator::link (before the
     // IonScript is created).
-    MOZ_ASSERT(!TlsContext.get()->runtime()->jitRuntime()->currentCompilationId().isSome());
+    MOZ_ASSERT_IF(zone.currentCompilationId(), zone.currentCompilationId().ref() != id_);
 
     if (!script_->hasIonScript() || script_->ionScript()->compilationId() != id_)
         return nullptr;
 
     return script_->ionScript();
 }
 
 inline bool
-RecompileInfo::shouldSweep()
+RecompileInfo::shouldSweep(const TypeZone& zone)
 {
     if (IsAboutToBeFinalizedUnbarriered(&script_))
         return true;
-    return maybeIonScriptToInvalidate() == nullptr;
+
+    MOZ_ASSERT(script_->zone() == zone.zone());
+
+    // Don't sweep if we're called under CodeGenerator::link, before the
+    // IonScript is created.
+    if (zone.currentCompilationId() && zone.currentCompilationId().ref() == id_)
+        return false;
+
+    return maybeIonScriptToInvalidate(zone) == nullptr;
 }
 
 /////////////////////////////////////////////////////////////////////
 // Types
 /////////////////////////////////////////////////////////////////////
 
 /* static */ inline TypeSet::ObjectKey*
 TypeSet::ObjectKey::get(JSObject* obj)
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -1208,17 +1208,17 @@ class TypeCompilerConstraint : public Ty
         // Note: Once the object has unknown properties, no more notifications
         // will be sent on changes to its state, so always invalidate any
         // associated compilations.
         if (group->unknownProperties() || data.invalidateOnNewObjectState(group))
             cx->zone()->types.addPendingRecompile(cx, compilation);
     }
 
     bool sweep(TypeZone& zone, TypeConstraint** res) override {
-        if (data.shouldSweep() || compilation.shouldSweep())
+        if (data.shouldSweep() || compilation.shouldSweep(zone))
             return false;
         *res = zone.typeLifoAlloc().new_<TypeCompilerConstraint<T> >(compilation, data);
         return true;
     }
 
     JSCompartment* maybeCompartment() override {
         return data.maybeCompartment();
     }
@@ -1417,17 +1417,17 @@ class TypeConstraintFreezeStack : public
 };
 
 } /* anonymous namespace */
 
 bool
 js::FinishCompilation(JSContext* cx, HandleScript script, CompilerConstraintList* constraints,
                       IonCompilationId compilationId, bool* isValidOut)
 {
-    MOZ_ASSERT(*cx->runtime()->jitRuntime()->currentCompilationId() == compilationId);
+    MOZ_ASSERT(*cx->zone()->types.currentCompilationId() == compilationId);
 
     if (constraints->failed())
         return false;
 
     RecompileInfo recompileInfo(script, compilationId);
 
     bool succeeded = true;
 
@@ -4410,17 +4410,17 @@ JSScript::maybeSweepTypes(AutoClearTypeI
 
     TypeZone& types = zone()->types;
 
     // Sweep the inlinedCompilations Vector.
     {
         RecompileInfoVector& inlinedCompilations = types_->inlinedCompilations();
         size_t dest = 0;
         for (size_t i = 0; i < inlinedCompilations.length(); i++) {
-            if (inlinedCompilations[i].shouldSweep())
+            if (inlinedCompilations[i].shouldSweep(types))
                 continue;
             inlinedCompilations[dest] = inlinedCompilations[i];
             dest++;
         }
         inlinedCompilations.shrinkTo(dest);
     }
 
     // Destroy all type information attached to the script if desired. We can
@@ -4480,16 +4480,17 @@ Zone::addSizeOfIncludingThis(mozilla::Ma
     *shapeTables += baseShapes().sizeOfExcludingThis(mallocSizeOf)
                   + initialShapes().sizeOfExcludingThis(mallocSizeOf);
     *atomsMarkBitmaps += markedAtoms().sizeOfExcludingThis(mallocSizeOf);
 }
 
 TypeZone::TypeZone(Zone* zone)
   : zone_(zone),
     typeLifoAlloc_(zone->group(), (size_t) TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
+    currentCompilationId_(zone->group()),
     generation(zone->group(), 0),
     sweepTypeLifoAlloc(zone->group(), (size_t) TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
     sweepReleaseTypes(zone->group(), false),
     sweepingTypes(zone->group(), false),
     keepTypeScripts(zone->group(), false),
     activeAnalysis(zone->group(), nullptr)
 {
 }
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -1101,27 +1101,31 @@ inline bool isInlinableCall(jsbytecode* 
 
 bool
 ClassCanHaveExtraProperties(const Class* clasp);
 
 // Each IonScript has a unique compilation id. This is used to sweep/ignore
 // constraints for IonScripts that have been invalidated/destroyed.
 class IonCompilationId
 {
-    uint64_t id_;
+    // Use two 32-bit integers instead of uint64_t to avoid 8-byte alignment on
+    // some 32-bit platforms.
+    uint32_t idLo_;
+    uint32_t idHi_;
 
   public:
     explicit IonCompilationId(uint64_t id)
-      : id_(id)
+      : idLo_(id & UINT32_MAX),
+        idHi_(id >> 32)
     {}
     bool operator==(const IonCompilationId& other) const {
-        return id_ == other.id_;
+        return idLo_ == other.idLo_ && idHi_ == other.idHi_;
     }
     bool operator!=(const IonCompilationId& other) const {
-        return id_ != other.id_;
+        return !operator==(other);
     }
 };
 
 class RecompileInfo
 {
     JSScript* script_;
     IonCompilationId id_;
 
@@ -1130,19 +1134,19 @@ class RecompileInfo
       : script_(script),
         id_(id)
     {}
 
     JSScript* script() const {
         return script_;
     }
 
-    inline jit::IonScript* maybeIonScriptToInvalidate() const;
+    inline jit::IonScript* maybeIonScriptToInvalidate(const TypeZone& zone) const;
 
-    inline bool shouldSweep();
+    inline bool shouldSweep(const TypeZone& zone);
 
     bool operator==(const RecompileInfo& other) const {
         return script_== other.script_ && id_ == other.id_;
     }
 };
 
 // The RecompileInfoVector has a MinInlineCapacity of one so that invalidating a
 // single IonScript doesn't require an allocation.
@@ -1331,16 +1335,19 @@ struct AutoEnterAnalysis;
 class TypeZone
 {
     JS::Zone* const zone_;
 
     /* Pool for type information in this zone. */
     static const size_t TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 8 * 1024;
     ZoneGroupData<LifoAlloc> typeLifoAlloc_;
 
+    // Under CodeGenerator::link, the id of the current compilation.
+    ZoneGroupData<mozilla::Maybe<IonCompilationId>> currentCompilationId_;
+
     TypeZone(const TypeZone&) = delete;
     void operator=(const TypeZone&) = delete;
 
   public:
     // Current generation for sweeping.
     ZoneGroupOrGCTaskOrIonCompileData<uint32_t> generation;
 
     // During incremental sweeping, allocator holding the old type information
@@ -1379,16 +1386,23 @@ class TypeZone
     void addPendingRecompile(JSContext* cx, JSScript* script);
 
     void processPendingRecompiles(FreeOp* fop, RecompileInfoVector& recompiles);
 
     void setSweepingTypes(bool sweeping) {
         MOZ_RELEASE_ASSERT(sweepingTypes != sweeping);
         sweepingTypes = sweeping;
     }
+
+    mozilla::Maybe<IonCompilationId> currentCompilationId() const {
+        return currentCompilationId_.ref();
+    }
+    mozilla::Maybe<IonCompilationId>& currentCompilationIdRef() {
+        return currentCompilationId_.ref();
+    }
 };
 
 enum SpewChannel {
     ISpewOps,      /* ops: New constraints and types. */
     ISpewResult,   /* result: Final type sets. */
     SPEW_COUNT
 };
 
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -35,16 +35,17 @@ import org.mozilla.gecko.util.EventCallb
 import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.PrefUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.ViewUtil;
 import org.mozilla.gecko.widget.ActionModePresenter;
 import org.mozilla.gecko.widget.AnchoredPopup;
+import org.mozilla.geckoview.GeckoRuntime;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.GeckoView;
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import android.annotation.TargetApi;
 import android.app.Activity;
@@ -1074,27 +1075,27 @@ public abstract class GeckoApp extends G
 
         // Set up Gecko layout.
         mRootLayout = (RelativeLayout) findViewById(R.id.root_layout);
         mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
         mMainLayout = (RelativeLayout) findViewById(R.id.main_layout);
         mLayerView = (GeckoView) findViewById(R.id.layer_view);
 
         final GeckoSession session = new GeckoSession();
+        session.getSettings().setString(GeckoSessionSettings.CHROME_URI,
+                                        "chrome://browser/content/browser.xul");
+        session.setContentDelegate(this);
+
         // If the view already has a session, we need to ensure it is closed.
         if (mLayerView.getSession() != null) {
             mLayerView.getSession().close();
         }
-        mLayerView.setSession(session);
+        mLayerView.setSession(session, GeckoRuntime.getDefault(this));
         mLayerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
 
-        session.getSettings().setString(GeckoSessionSettings.CHROME_URI,
-                                        "chrome://browser/content/browser.xul");
-        session.setContentDelegate(this);
-
         GeckoAccessibility.setDelegate(mLayerView);
 
         getAppEventDispatcher().registerGeckoThreadListener(this,
             "Locale:Set",
             "PrivateBrowsing:Data",
             null);
 
         getAppEventDispatcher().registerUiThreadListener(this,
--- a/mobile/android/base/java/org/mozilla/gecko/RemotePresentationService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/RemotePresentationService.java
@@ -2,16 +2,17 @@
  * vim: ts=4 sw=4 expandtab:
  * 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/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.ScreenManagerHelper;
+import org.mozilla.geckoview.GeckoRuntime;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.GeckoView;
 
 import com.google.android.gms.cast.CastMediaControlIntent;
 import com.google.android.gms.cast.CastPresentation;
 import com.google.android.gms.cast.CastRemoteDisplayLocalService;
 import com.google.android.gms.common.ConnectionResult;
@@ -119,25 +120,24 @@ class VirtualPresentation extends CastPr
         super.onCreate(savedInstanceState);
 
         /*
          * NOTICE: The context get from getContext() is different to the context
          * of the application. Presentaion has its own context to get correct
          * resources.
          */
 
-        // Create new GeckoView
-        view = new GeckoView(getContext());
-
         final GeckoSession session = new GeckoSession();
         session.getSettings().setString(GeckoSessionSettings.CHROME_URI,
                                         PRESENTATION_VIEW_URI + "#" + deviceId);
         session.getSettings().setInt(GeckoSessionSettings.SCREEN_ID, screenId);
 
-        view.setSession(session);
+        // Create new GeckoView
+        view = new GeckoView(getContext());
+        view.setSession(session, GeckoRuntime.getDefault(getContext()));
         view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
                                               LayoutParams.MATCH_PARENT));
 
         // Create new layout to put the GeckoView
         layout = new RelativeLayout(getContext());
         layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
                                                 LayoutParams.MATCH_PARENT));
         layout.addView(view);
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -53,16 +53,17 @@ import org.mozilla.gecko.text.TextSelect
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.ColorUtil;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.IntentUtils;
 import org.mozilla.gecko.util.PackageUtil;
 import org.mozilla.gecko.webapps.WebApps;
 import org.mozilla.gecko.widget.ActionModePresenter;
 import org.mozilla.gecko.widget.GeckoPopupMenu;
+import org.mozilla.geckoview.GeckoRuntime;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.GeckoView;
 
 import java.util.List;
 
 public class CustomTabsActivity extends AppCompatActivity
                                 implements ActionModePresenter,
@@ -120,39 +121,38 @@ public class CustomTabsActivity extends 
         actionBarPresenter.displayUrlOnly(intent.getDataString());
         actionBarPresenter.setBackgroundColor(IntentUtil.getToolbarColor(intent), getWindow());
         actionBarPresenter.setTextLongClickListener(new UrlCopyListener());
 
         mGeckoView = (GeckoView) findViewById(R.id.gecko_view);
 
         GeckoAccessibility.setDelegate(mGeckoView);
 
-        mGeckoSession = new GeckoSession();
-        mGeckoView.setSession(mGeckoSession);
-
+        final GeckoSessionSettings settings = new GeckoSessionSettings();
+        settings.setBoolean(GeckoSessionSettings.USE_MULTIPROCESS, false);
+        settings.setBoolean(
+            GeckoSessionSettings.USE_REMOTE_DEBUGGER,
+            GeckoSharedPrefs.forApp(this).getBoolean(
+                GeckoPreferences.PREFS_DEVTOOLS_REMOTE_USB_ENABLED, false));
+        mGeckoSession = new GeckoSession(settings);
         mGeckoSession.setNavigationDelegate(this);
         mGeckoSession.setProgressDelegate(this);
         mGeckoSession.setContentDelegate(this);
 
+        mGeckoView.setSession(mGeckoSession, GeckoRuntime.getDefault(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);
         mTextSelection.create();
 
-        final GeckoSessionSettings settings = mGeckoView.getSettings();
-        settings.setBoolean(GeckoSessionSettings.USE_MULTIPROCESS, false);
-        settings.setBoolean(
-            GeckoSessionSettings.USE_REMOTE_DEBUGGER,
-            GeckoSharedPrefs.forApp(this).getBoolean(
-                GeckoPreferences.PREFS_DEVTOOLS_REMOTE_USB_ENABLED, false));
-
         if (intent != null && !TextUtils.isEmpty(intent.getDataString())) {
             mGeckoSession.loadUri(intent.getDataString());
         } else {
             Log.w(LOGTAG, "No intend found for custom tab");
             finish();
         }
 
         sendTelemetry();
--- a/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
@@ -33,16 +33,17 @@ import org.mozilla.gecko.preferences.Gec
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.customtabs.CustomTabsActivity;
 import org.mozilla.gecko.permissions.Permissions;
 import org.mozilla.gecko.prompts.PromptService;
 import org.mozilla.gecko.text.TextSelection;
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.ColorUtil;
 import org.mozilla.gecko.widget.ActionModePresenter;
+import org.mozilla.geckoview.GeckoRuntime;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.GeckoView;
 
 public class WebAppActivity extends AppCompatActivity
                             implements ActionModePresenter,
                                        GeckoSession.ContentDelegate,
                                        GeckoSession.NavigationDelegate {
@@ -86,18 +87,24 @@ public class WebAppActivity extends AppC
             Intent lastLaunchIntent = savedInstanceState.getParcelable(SAVED_INTENT);
             setIntent(lastLaunchIntent);
         }
 
         super.onCreate(savedInstanceState);
         setContentView(R.layout.webapp_activity);
         mGeckoView = (GeckoView) findViewById(R.id.pwa_gecko_view);
 
-        mGeckoSession = new GeckoSession();
-        mGeckoView.setSession(mGeckoSession);
+        final GeckoSessionSettings settings = new GeckoSessionSettings();
+        settings.setBoolean(GeckoSessionSettings.USE_MULTIPROCESS, false);
+        settings.setBoolean(
+            GeckoSessionSettings.USE_REMOTE_DEBUGGER,
+            GeckoSharedPrefs.forApp(this).getBoolean(
+                GeckoPreferences.PREFS_DEVTOOLS_REMOTE_USB_ENABLED, false));
+        mGeckoSession = new GeckoSession(settings);
+        mGeckoView.setSession(mGeckoSession, GeckoRuntime.getDefault(this));
 
         mGeckoSession.setNavigationDelegate(this);
         mGeckoSession.setContentDelegate(this);
         mGeckoSession.setProgressDelegate(new GeckoSession.ProgressDelegate() {
             @Override
             public void onPageStart(GeckoSession session, String url) {
 
             }
@@ -142,23 +149,16 @@ public class WebAppActivity extends AppC
         mDoorHangerPopup = new DoorHangerPopup(this, mGeckoView.getEventDispatcher());
 
         mFormAssistPopup = (FormAssistPopup) findViewById(R.id.pwa_form_assist_popup);
         mFormAssistPopup.create(mGeckoView);
 
         mTextSelection = TextSelection.Factory.create(mGeckoView, this);
         mTextSelection.create();
 
-        final GeckoSessionSettings settings = mGeckoView.getSettings();
-        settings.setBoolean(GeckoSessionSettings.USE_MULTIPROCESS, false);
-        settings.setBoolean(
-            GeckoSessionSettings.USE_REMOTE_DEBUGGER,
-            GeckoSharedPrefs.forApp(this).getBoolean(
-                GeckoPreferences.PREFS_DEVTOOLS_REMOTE_USB_ENABLED, false));
-
         try {
             mManifest = WebAppManifest.fromFile(getIntent().getStringExtra(MANIFEST_URL),
                                                 getIntent().getStringExtra(MANIFEST_PATH));
         } catch (Exception e) {
             Log.w(LOGTAG, "Cannot retrieve manifest, launching in Firefox:" + e);
             fallbackToFennec(null);
             return;
         }
@@ -315,17 +315,17 @@ public class WebAppActivity extends AppC
                 mode = GeckoSessionSettings.DISPLAY_MODE_MINIMAL_UI;
                 break;
             case "browser":
             default:
                 mode = GeckoSessionSettings.DISPLAY_MODE_BROWSER;
                 break;
         }
 
-        mGeckoView.getSettings().setInt(GeckoSessionSettings.DISPLAY_MODE, mode);
+        mGeckoSession.getSettings().setInt(GeckoSessionSettings.DISPLAY_MODE, mode);
     }
 
     @Override // GeckoSession.NavigationDelegate
     public void onLocationChange(GeckoSession session, String url) {
     }
 
     @Override // GeckoSession.NavigationDelegate
     public void onCanGoBack(GeckoSession session, boolean canGoBack) {
--- 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
@@ -730,17 +730,18 @@ class GeckoSessionTestRuleTest : BaseSes
             }
         })
 
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
     }
 
     @Test fun wrapSession() {
-        val session = sessionRule.wrapSession(GeckoSession(sessionRule.session.settings))
+        val session = sessionRule.wrapSession(
+          GeckoSession(sessionRule.session.settings))
         sessionRule.openSession(session)
         session.reload()
         session.waitForPageStop()
     }
 
     @Test fun createOpenSession() {
         val newSession = sessionRule.createOpenSession()
         assertThat("Can create session", newSession, notNullValue())
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
@@ -351,17 +351,17 @@ class NavigationDelegateTest : BaseSessi
     @Test(expected = IllegalArgumentException::class)
     fun onNewSession_doesNotAllowOpened() {
         sessionRule.session.loadTestPath(NEW_SESSION_HTML_PATH)
         sessionRule.waitForPageStop()
 
         sessionRule.delegateDuringNextWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1)
             override fun onNewSession(session: GeckoSession, uri: String, response: GeckoSession.Response<GeckoSession>) {
-                var newSession = GeckoSession(session.settings)
+                val newSession = sessionRule.createClosedSession(session.settings)
                 newSession.open()
                 response.respond(newSession)
             }
         })
 
         sessionRule.session.synthesizeTap(5, 5)
         sessionRule.waitUntilCalled(GeckoSession.NavigationDelegate::class, "onNewSession")
     }
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ProgressDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ProgressDelegateTest.kt
@@ -55,17 +55,18 @@ class ProgressDelegateTest : BaseSession
 
     fun loadExpectNetError(testUri: String) {
         sessionRule.session.loadUri(testUri);
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate, Callbacks.NavigationDelegate {
             @AssertCalled(count = 2)
             override fun onLoadRequest(session: GeckoSession, uri: String,
-                                       where: Int, response: GeckoSession.Response<Boolean>) {
+                                       where: Int,
+                                       response: GeckoSession.Response<Boolean>) {
                 if (sessionRule.currentCall.counter == 1) {
                     assertThat("URI should be " + testUri, uri, equalTo(testUri));
                 } else {
                     assertThat("URI should be about:neterror", uri, startsWith("about:neterror"));
                 }
                 response.respond(false)
             }
 
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt
@@ -14,24 +14,16 @@ import android.support.test.runner.Andro
 import org.hamcrest.Matchers.*
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
 @MediumTest
 class SessionLifecycleTest : BaseSessionTest() {
 
-    @Test fun open_allowNullContext() {
-        sessionRule.session.close()
-
-        sessionRule.session.open(null)
-        sessionRule.session.reload()
-        sessionRule.session.waitForPageStop()
-    }
-
     @Test fun open_interleaved() {
         val session1 = sessionRule.createOpenSession()
         val session2 = sessionRule.createOpenSession()
         session1.close()
         val session3 = sessionRule.createOpenSession()
         session2.close()
         session3.close()
 
--- 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
@@ -3,25 +3,29 @@
  * 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/. */
 
 package org.mozilla.geckoview.test;
 
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.GeckoView;
+import org.mozilla.geckoview.GeckoRuntime;
+import org.mozilla.geckoview.GeckoRuntimeSettings;
 
 import android.app.Activity;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
 
 public class TestRunnerActivity extends Activity {
     private static final String LOGTAG = "TestRunnerActivity";
 
+    static GeckoRuntime sRuntime;
+
     GeckoSession mSession;
     GeckoView mView;
 
     private GeckoSession.NavigationDelegate mNavigationDelegate = new GeckoSession.NavigationDelegate() {
         @Override
         public void onLocationChange(GeckoSession session, String url) {
             getActionBar().setSubtitle(url);
         }
@@ -32,18 +36,18 @@ public class TestRunnerActivity extends 
         }
 
         @Override
         public void onCanGoForward(GeckoSession session, boolean canGoForward) {
 
         }
 
         @Override
-        public void onLoadRequest(GeckoSession session, String uri,
-                                  int target, GeckoSession.Response<Boolean> response) {
+        public void onLoadRequest(GeckoSession session, String uri, int target,
+                                  GeckoSession.Response<Boolean> response) {
             // Allow Gecko to load all URIs
             response.respond(false);
         }
 
         @Override
         public void onNewSession(GeckoSession session, String uri, GeckoSession.Response<GeckoSession> response) {
             response.respond(createSession(session.getSettings()));
         }
@@ -98,23 +102,26 @@ public class TestRunnerActivity extends 
         return session;
     }
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         final Intent intent = getIntent();
-        GeckoSession.preload(this, new String[] { "-purgecaches" },
-                             intent.getExtras(), false /* no multiprocess, see below */);
 
-        // We can't use e10s because we get deadlocked when quickly creating and
-        // destroying sessions. Bug 1348361.
+        if (sRuntime == null) {
+            final GeckoRuntimeSettings geckoSettings = new GeckoRuntimeSettings();
+            geckoSettings.setArguments(new String[] { "-purgecaches" });
+            geckoSettings.setExtras(intent.getExtras());
+            sRuntime = GeckoRuntime.create(this, geckoSettings);
+        }
+
         mSession = createSession();
-        mSession.open(this);
+        mSession.open(sRuntime);
 
         // If we were passed a URI in the Intent, open it
         final Uri uri = intent.getData();
         if (uri != null) {
             mSession.loadUri(uri);
         }
 
         mView = new GeckoView(this);
--- 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
@@ -1,14 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.geckoview.test.rule;
 
 import org.mozilla.gecko.gfx.GeckoDisplay;
+import org.mozilla.geckoview.GeckoRuntime;
+import org.mozilla.geckoview.GeckoRuntimeSettings;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.test.util.Callbacks;
 
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.fail;
 
 import org.hamcrest.Matcher;
@@ -501,16 +503,17 @@ public class GeckoSessionTestRule extend
             addCallbackClasses(list, ifce);
         }
 
         final HashSet<Class<?>> set = new HashSet<>(list);
         return set.toArray(new Class<?>[set.size()]);
     }
 
     private static final List<Class<?>> CALLBACK_CLASSES = Arrays.asList(getCallbackClasses());
+    private static GeckoRuntime sRuntime;
 
     public final Environment env = new Environment();
 
     protected final Instrumentation mInstrumentation =
             InstrumentationRegistry.getInstrumentation();
     protected final GeckoSessionSettings mDefaultSettings;
     protected final Set<GeckoSession> mSubSessions = new HashSet<>();
 
@@ -606,16 +609,25 @@ public class GeckoSessionTestRule extend
      * Get the session set up for the current test.
      *
      * @return GeckoSession object.
      */
     public @NonNull GeckoSession getSession() {
         return mMainSession;
     }
 
+    /**
+     * Get the runtime set up for the current test.
+     *
+     * @return GeckoRuntime object.
+     */
+    public @NonNull GeckoRuntime getRuntime() {
+        return sRuntime;
+    }
+
     protected static Method getCallbackSetter(final @NonNull Class<?> cls)
             throws NoSuchMethodException {
         return GeckoSession.class.getMethod("set" + cls.getSimpleName(), cls);
     }
 
     protected static Method getCallbackGetter(final @NonNull Class<?> cls)
             throws NoSuchMethodException {
         return GeckoSession.class.getMethod("get" + cls.getSimpleName());
@@ -709,17 +721,24 @@ public class GeckoSessionTestRule extend
                 }
             }
         };
 
         final Class<?>[] classes = CALLBACK_CLASSES.toArray(new Class<?>[CALLBACK_CLASSES.size()]);
         mCallbackProxy = Proxy.newProxyInstance(GeckoSession.class.getClassLoader(),
                                                 classes, recorder);
 
-        GeckoSession.preload(InstrumentationRegistry.getTargetContext(), new String[] { "-purgecaches" }, null, false);
+        if (sRuntime == null) {
+            final GeckoRuntimeSettings geckoSettings = new GeckoRuntimeSettings();
+            geckoSettings.setArguments(new String[] { "-purgecaches" });
+            sRuntime = GeckoRuntime.create(
+                InstrumentationRegistry.getTargetContext(),
+                geckoSettings);
+        }
+
         mMainSession = new GeckoSession(settings);
         prepareSession(mMainSession);
 
         if (mDisplaySize != null) {
             mDisplayTexture = new SurfaceTexture(0);
             mDisplaySurface = new Surface(mDisplayTexture);
             mDisplay = mMainSession.acquireDisplay();
             mDisplay.surfaceChanged(mDisplaySurface, mDisplaySize.x, mDisplaySize.y);
@@ -748,17 +767,17 @@ public class GeckoSessionTestRule extend
         final boolean e10s = session.getSettings().getBoolean(
                 GeckoSessionSettings.USE_MULTIPROCESS);
 
         if (e10s) {
             // Give any pending calls a chance to catch up.
             loopUntilIdle(/* timeout */ 0);
         }
 
-        session.open(mInstrumentation.getTargetContext());
+        session.open(sRuntime);
 
         if (!e10s) {
             return;
         }
 
         // Under e10s, we receive an initial about:blank load; don't expose that to the test.
         // The about:blank load is bounded by onLocationChange and onPageStop calls,
         // so find the first about:blank onLocationChange, then the next onPageStop,
--- 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
@@ -39,17 +39,18 @@ class Callbacks private constructor() {
         }
 
         override fun onCanGoBack(session: GeckoSession, canGoBack: Boolean) {
         }
 
         override fun onCanGoForward(session: GeckoSession, canGoForward: Boolean) {
         }
 
-        override fun onLoadRequest(session: GeckoSession, uri: String, where: Int, response: GeckoSession.Response<Boolean>) {
+        override fun onLoadRequest(session: GeckoSession, uri: String, where: Int,
+                                   response: GeckoSession.Response<Boolean>) {
             response.respond(false)
         }
 
         override fun onNewSession(session: GeckoSession, uri: String, response: GeckoSession.Response<GeckoSession>) {
             response.respond(null)
         }
     }
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
@@ -0,0 +1,178 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * vim: ts=4 sw=4 expandtab:
+ * 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/. */
+
+package org.mozilla.geckoview;
+
+import java.util.ArrayList;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.content.Context;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoThread;
+import org.mozilla.gecko.util.BundleEventListener;
+import org.mozilla.gecko.util.EventCallback;
+import org.mozilla.gecko.util.GeckoBundle;
+import org.mozilla.gecko.util.ThreadUtils;
+
+public final class GeckoRuntime implements Parcelable {
+    private static final String LOGTAG = "GeckoRuntime";
+    private static final boolean DEBUG = false;
+
+    private static GeckoRuntime sDefaultRuntime;
+
+    /**
+     * Get the default runtime for the given context.
+     * This will create and initialize the runtime with the default settings.
+     *
+     * Note: Only use this for session-less apps.
+     *       For regular apps, use create() and createSession() instead.
+     *
+     * @return The (static) default runtime for the context.
+     */
+    public static synchronized @NonNull GeckoRuntime getDefault(
+            final @NonNull Context context) {
+        Log.d(LOGTAG, "getDefault");
+        if (sDefaultRuntime == null) {
+            sDefaultRuntime = new GeckoRuntime();
+            sDefaultRuntime.attachTo(context);
+            sDefaultRuntime.init(new GeckoRuntimeSettings());
+        }
+
+        return sDefaultRuntime;
+    }
+
+    private GeckoRuntimeSettings mSettings;
+
+    /**
+     * Attach the runtime to the given context.
+     *
+     * @param context The new context to attach to.
+     */
+    public void attachTo(final @NonNull Context context) {
+        if (DEBUG) {
+            Log.d(LOGTAG, "attachTo " + context.getApplicationContext());
+        }
+        final Context appContext = context.getApplicationContext();
+        if (!appContext.equals(GeckoAppShell.getApplicationContext())) {
+            GeckoAppShell.setApplicationContext(appContext);
+        }
+    }
+
+    /* package */ boolean init(final @NonNull GeckoRuntimeSettings settings) {
+        if (DEBUG) {
+            Log.d(LOGTAG, "init");
+        }
+        final int flags = settings.getUseContentProcessHint()
+                          ? GeckoThread.FLAG_PRELOAD_CHILD
+                          : 0;
+        if (GeckoThread.initMainProcess(/* profile */ null,
+                                        settings.getArguments(),
+                                        settings.getExtras(),
+                                        flags)) {
+            if (!GeckoThread.launch()) {
+                Log.d(LOGTAG, "init failed (GeckoThread already launched)");
+                return false;
+            }
+            mSettings = settings;
+            return true;
+        }
+        Log.d(LOGTAG, "init failed (could not initiate GeckoThread)");
+        return false;
+    }
+
+    /**
+     * Create a new runtime with default settings and attach it to the given
+     * context.
+     *
+     * Create will throw if there is already an active Gecko instance running,
+     * to prevent that, bind the runtime to the process lifetime instead of the
+     * activity lifetime.
+     *
+     * @param context The context of the runtime.
+     * @return An initialized runtime.
+     */
+    public static @NonNull GeckoRuntime create(final @NonNull Context context) {
+        return create(context, new GeckoRuntimeSettings());
+    }
+
+    /**
+     * Create a new runtime with the given settings and attach it to the given
+     * context.
+     *
+     * Create will throw if there is already an active Gecko instance running,
+     * to prevent that, bind the runtime to the process lifetime instead of the
+     * activity lifetime.
+     *
+     * @param context The context of the runtime.
+     * @param settings The settings for the runtime.
+     * @return An initialized runtime.
+     */
+    public static @NonNull GeckoRuntime create(
+        final @NonNull Context context,
+        final @NonNull GeckoRuntimeSettings settings) {
+        if (DEBUG) {
+            Log.d(LOGTAG, "create " + context);
+        }
+
+        final GeckoRuntime runtime = new GeckoRuntime();
+        runtime.attachTo(context);
+
+        if (!runtime.init(settings)) {
+            throw new IllegalStateException("Failed to initialize GeckoRuntime");
+        }
+
+        return runtime;
+    }
+
+    /**
+     * Shutdown the runtime. This will invalidate all attached sessions.
+     */
+    public void shutdown() {
+        if (DEBUG) {
+            Log.d(LOGTAG, "shutdown");
+        }
+
+        GeckoThread.forceQuit();
+    }
+
+    @Override // Parcelable
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override // Parcelable
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeParcelable(mSettings, flags);
+    }
+
+    // AIDL code may call readFromParcel even though it's not part of Parcelable.
+    public void readFromParcel(final Parcel source) {
+        mSettings = source.readParcelable(getClass().getClassLoader());
+    }
+
+    public static final Parcelable.Creator<GeckoRuntime> CREATOR
+        = new Parcelable.Creator<GeckoRuntime>() {
+        @Override
+        public GeckoRuntime createFromParcel(final Parcel in) {
+            final GeckoRuntime runtime = new GeckoRuntime();
+            runtime.readFromParcel(in);
+            return runtime;
+        }
+
+        @Override
+        public GeckoRuntime[] newArray(final int size) {
+            return new GeckoRuntime[size];
+        }
+    };
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
@@ -0,0 +1,125 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * vim: ts=4 sw=4 expandtab:
+ * 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/. */
+
+package org.mozilla.geckoview;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+public final class GeckoRuntimeSettings implements Parcelable {
+    private boolean mUseContentProcess;
+    private String[] mArgs;
+    private Bundle mExtras;
+
+    /**
+     * Initialize default settings.
+     */
+    public  GeckoRuntimeSettings() {
+        this(null);
+    }
+
+    /* package */ GeckoRuntimeSettings(final @Nullable GeckoRuntimeSettings settings) {
+        if (settings != null) {
+            mUseContentProcess = settings.mUseContentProcess;
+            mArgs = settings.mArgs.clone();
+            mExtras = new Bundle(settings.mExtras);
+        } else {
+            mArgs = new String[0];
+            mExtras = new Bundle();
+        }
+    }
+
+    /**
+     * Get the content process hint flag.
+     *
+     * @return The content process hint flag.
+     */
+    public boolean getUseContentProcessHint() {
+        return mUseContentProcess;
+    }
+
+    /**
+     * Get the custom Gecko process arguments.
+     *
+     * @return The Gecko process arguments.
+     */
+    public String[] getArguments() {
+        return mArgs;
+    }
+
+    /**
+     * Get the custom Gecko intent extras.
+     *
+     * @return The Gecko intent extras.
+     */
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /**
+     * Set the content process hint flag.
+     *
+     * @param use If true, this will reload the content process for future use.
+     */
+    public void setUseContentProcessHint(boolean use) {
+        mUseContentProcess = use;
+    }
+
+    /**
+     * Set the custom Gecko process arguments.
+     *
+     * @param args The Gecko process arguments.
+     */
+    public void setArguments(final @NonNull String[] args) {
+        mArgs = args;
+    }
+
+    /**
+     * Set the custom Gecko intent extras.
+     *
+     * @param extras The Gecko intent extras.
+     */
+    public void setExtras(final @NonNull Bundle extras) {
+        mExtras = extras;
+    }
+
+    @Override // Parcelable
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override // Parcelable
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeByte((byte) (mUseContentProcess ? 1 : 0));
+        out.writeStringArray(mArgs);
+        mExtras.writeToParcel(out, flags);
+    }
+
+    // AIDL code may call readFromParcel even though it's not part of Parcelable.
+    public void readFromParcel(final Parcel source) {
+        mUseContentProcess = source.readByte() == 1;
+        mArgs = source.createStringArray();
+        mExtras.readFromParcel(source);
+    }
+
+    public static final Parcelable.Creator<GeckoRuntimeSettings> CREATOR
+        = new Parcelable.Creator<GeckoRuntimeSettings>() {
+        @Override
+        public GeckoRuntimeSettings createFromParcel(final Parcel in) {
+            final GeckoRuntimeSettings settings = new GeckoRuntimeSettings();
+            settings.readFromParcel(in);
+            return settings;
+        }
+
+        @Override
+        public GeckoRuntimeSettings[] newArray(final int size) {
+            return new GeckoRuntimeSettings[size];
+        }
+    };
+}
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -195,18 +195,22 @@ public class GeckoSession extends LayerS
                                     callback.sendSuccess(null);
                                     return;
                                 }
 
                                 if (session.isOpen()) {
                                     throw new IllegalArgumentException("Must use an unopened GeckoSession instance");
                                 }
 
-                                session.open(null);
-                                callback.sendSuccess(session.getId());
+                                if (GeckoSession.this.mWindow == null) {
+                                    callback.sendError("Session is not attached to a window");
+                                } else {
+                                    session.open(GeckoSession.this.mWindow.runtime);
+                                    callback.sendSuccess(session.getId());
+                                }
                             }
                         });
                 }
             }
         };
 
     private final GeckoSessionHandler<ProgressDelegate> mProgressHandler =
         new GeckoSessionHandler<ProgressDelegate>(
@@ -462,20 +466,23 @@ public class GeckoSession extends LayerS
         mPermissionHandler.setDelegate(delegate, this);
     }
 
     private PromptDelegate mPromptDelegate;
 
     private final Listener mListener = new Listener();
 
     /* package */ static final class Window extends JNIObject implements IInterface {
+        public final GeckoRuntime runtime;
         private NativeQueue mNativeQueue;
         private Binder mBinder;
 
-        public Window(final NativeQueue nativeQueue) {
+        public Window(final @NonNull GeckoRuntime runtime,
+                      final @NonNull NativeQueue nativeQueue) {
+            this.runtime = runtime;
             mNativeQueue = nativeQueue;
         }
 
         @Override // IInterface
         public Binder asBinder() {
             if (mBinder == null) {
                 mBinder = new Binder();
                 mBinder.attachInterface(this, Window.class.getName());
@@ -613,26 +620,34 @@ public class GeckoSession extends LayerS
 
     protected Window mWindow;
     private GeckoSessionSettings mSettings;
 
     public GeckoSession() {
         this(null);
     }
 
-    public GeckoSession(final GeckoSessionSettings settings) {
+    public GeckoSession(final @Nullable GeckoSessionSettings settings) {
         mSettings = new GeckoSessionSettings(settings, this);
         mListener.registerListeners();
 
         if (BuildConfig.DEBUG && handlersCount != mSessionHandlers.length) {
             throw new AssertionError("Add new handler to handlers list");
         }
     }
 
-    private void transferFrom(final Window window, final GeckoSessionSettings settings,
+    /* package */ @Nullable GeckoRuntime getRuntime() {
+        if (mWindow == null) {
+            return null;
+        }
+        return mWindow.runtime;
+    }
+
+    private void transferFrom(final Window window,
+                              final GeckoSessionSettings settings,
                               final String id) {
         if (isOpen()) {
             throw new IllegalStateException("Session is open");
         }
 
         if (window != null) {
             onWindowChanged(WINDOW_TRANSFER_IN, /* inProgress */ true);
         }
@@ -696,53 +711,16 @@ public class GeckoSession extends LayerS
         }
 
         @Override
         public GeckoSession[] newArray(final int size) {
             return new GeckoSession[size];
         }
     };
 
-    /**
-     * Preload GeckoSession by starting Gecko in the background, if Gecko is not already running.
-     *
-     * @param context Activity or Application Context for starting GeckoSession.
-     */
-    public static void preload(final @NonNull Context context) {
-        preload(context, /* geckoArgs */ null,
-                /* extras */ null, /* multiprocess */ false);
-    }
-
-    /**
-     * Preload GeckoSession by starting Gecko with the specified arguments in the background,
-     * if Gecko is not already running.
-     *
-     * @param context Activity or Application Context for starting GeckoSession.
-     * @param geckoArgs Arguments to be passed to Gecko, if Gecko is not already running.
-     * @param multiprocess True if child process in multiprocess mode should be preloaded.
-     */
-    public static void preload(final @NonNull Context context,
-                               final @Nullable String[] geckoArgs,
-                               final @Nullable Bundle extras,
-                               final boolean multiprocess) {
-        final Context appContext = context.getApplicationContext();
-        if (!appContext.equals(GeckoAppShell.getApplicationContext())) {
-            GeckoAppShell.setApplicationContext(appContext);
-        }
-
-        if (GeckoThread.isLaunched()) {
-            return;
-        }
-
-        final int flags = multiprocess ? GeckoThread.FLAG_PRELOAD_CHILD : 0;
-        if (GeckoThread.initMainProcess(/* profile */ null, geckoArgs, extras, flags)) {
-            GeckoThread.launch();
-        }
-    }
-
     public boolean isOpen() {
         return mWindow != null;
     }
 
     /* package */ boolean isReady() {
         return mNativeQueue.isReady();
     }
 
@@ -752,40 +730,34 @@ public class GeckoSession extends LayerS
      * The session is in a 'closed' state when first created. Opening it creates
      * the underlying Gecko objects necessary to load a page, etc. Most GeckoSession
      * methods only take affect on an open session, and are queued until the session
      * is opened here. Opening a session is an asynchronous operation. You can check
      * the current state via isOpen().
      *
      * Call this when you are ready to use a GeckoSession instance.
      *
-     * @param appContext An application context
+     * @param runtime The Gecko runtime to attach this session to.
      */
-    public void open(final @Nullable Context appContext) {
+    public void open(final @NonNull GeckoRuntime runtime) {
         ThreadUtils.assertOnUiThread();
 
         if (isOpen()) {
             throw new IllegalStateException("Session is open");
         }
 
-        if (appContext != null) {
-            final boolean multiprocess =
-                    mSettings.getBoolean(GeckoSessionSettings.USE_MULTIPROCESS);
-            preload(appContext, /* geckoArgs */ null, /* extras */ null, multiprocess);
-        }
-
-        openWindow();
+        openWindow(runtime);
     }
 
-    private void openWindow() {
+    private void openWindow(final @NonNull GeckoRuntime runtime) {
         final String chromeUri = mSettings.getString(GeckoSessionSettings.CHROME_URI);
         final int screenId = mSettings.getInt(GeckoSessionSettings.SCREEN_ID);
         final boolean isPrivate = mSettings.getBoolean(GeckoSessionSettings.USE_PRIVATE_MODE);
 
-        mWindow = new Window(mNativeQueue);
+        mWindow = new Window(runtime, mNativeQueue);
 
         onWindowChanged(WINDOW_OPEN, /* inProgress */ true);
 
         if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
             Window.open(mWindow, mNativeQueue, mCompositor, mEventDispatcher,
                         mSettings.asBundle(), mId, chromeUri, screenId, isPrivate);
         } else {
             GeckoThread.queueNativeCallUntil(
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
@@ -19,16 +19,18 @@ import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.annotation.Nullable;
+import android.support.annotation.NonNull;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
 import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
@@ -42,16 +44,17 @@ import android.widget.FrameLayout;
 public class GeckoView extends FrameLayout {
     private static final String LOGTAG = "GeckoView";
     private static final boolean DEBUG = false;
 
     private static AccessibilityManager sAccessibilityManager;
 
     protected final Display mDisplay = new Display();
     protected GeckoSession mSession;
+    protected GeckoRuntime mRuntime;
     private boolean mStateSaved;
 
     protected SurfaceView mSurfaceView;
 
     private boolean mIsResettingFocus;
 
     private GeckoSession.SelectionActionDelegate mSelectionActionDelegate;
 
@@ -212,25 +215,55 @@ public class GeckoView extends FrameLayo
         mSession.getCompositorController().setFirstPaintCallback(null);
         if (session.getSelectionActionDelegate() == mSelectionActionDelegate) {
             mSession.setSelectionActionDelegate(null);
         }
         mSession = null;
         return session;
     }
 
-    public void setSession(final GeckoSession session) {
+    /**
+     * Attach a session to this view. The session should be opened before
+     * attaching.
+     *
+     * @param session The session to be attached.
+     */
+    public void setSession(@NonNull final GeckoSession session) {
+        if (!session.isOpen()) {
+            throw new IllegalArgumentException("Session must be open before attaching");
+        }
+
+        setSession(session, session.getRuntime());
+    }
+
+    /**
+     * Attach a session to this view. The session should be opened before
+     * attaching or a runtime needs to be provided for automatic opening.
+     *
+     * @param session The session to be attached.
+     * @param runtime The runtime to be used for opening the session.
+     */
+    public void setSession(@NonNull final GeckoSession session,
+                           @Nullable final GeckoRuntime runtime) {
         if (mSession != null && mSession.isOpen()) {
             throw new IllegalStateException("Current session is open");
         }
 
         releaseSession();
+
         mSession = session;
-        if (mSession == null) {
-          return;
+        mRuntime = runtime;
+
+        if (session.isOpen()) {
+            if (runtime != null && runtime != session.getRuntime()) {
+                throw new IllegalArgumentException("Session was opened with non-matching runtime");
+            }
+            mRuntime = session.getRuntime();
+        } else if (runtime == null) {
+            throw new IllegalArgumentException("Session must be open before attaching");
         }
 
         mDisplay.acquire(session.acquireDisplay());
 
         final Context context = getContext();
         session.getOverscrollEdgeEffect().setTheme(context);
         session.getOverscrollEdgeEffect().setInvalidationCallback(new Runnable() {
             @Override
@@ -267,36 +300,32 @@ public class GeckoView extends FrameLayo
     public GeckoSession getSession() {
         return mSession;
     }
 
     public EventDispatcher getEventDispatcher() {
         return mSession.getEventDispatcher();
     }
 
-    public GeckoSessionSettings getSettings() {
-        return mSession.getSettings();
-    }
-
     public PanZoomController getPanZoomController() {
         return mSession.getPanZoomController();
     }
 
     public DynamicToolbarAnimator getDynamicToolbarAnimator() {
         return mSession.getDynamicToolbarAnimator();
     }
 
     @Override
     public void onAttachedToWindow() {
         if (mSession == null) {
-            setSession(new GeckoSession());
+            setSession(new GeckoSession(), GeckoRuntime.getDefault(getContext()));
         }
 
         if (!mSession.isOpen()) {
-            mSession.open(getContext().getApplicationContext());
+            mSession.open(mRuntime);
         }
 
         mSession.getTextInput().setView(this);
 
         super.onAttachedToWindow();
     }
 
     @Override
@@ -341,20 +370,21 @@ public class GeckoView extends FrameLayo
         if (!(state instanceof SavedState)) {
             super.onRestoreInstanceState(state);
             return;
         }
 
         final SavedState ss = (SavedState) state;
         super.onRestoreInstanceState(ss.getSuperState());
 
-        if (mSession == null) {
-            setSession(ss.session);
+        if (mSession == null && ss.session != null) {
+            setSession(ss.session, ss.session.getRuntime());
         } else if (ss.session != null) {
             mSession.transferFrom(ss.session);
+            mRuntime = ss.session.getRuntime();
         }
     }
 
     @Override
     public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
 
         if (!gainFocus || mIsResettingFocus) {
--- 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
@@ -12,80 +12,87 @@ import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.util.Log;
 import android.view.WindowManager;
 
 import java.util.Locale;
 
-import org.mozilla.gecko.GeckoThread;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.GeckoSession.Response;
 import org.mozilla.geckoview.GeckoSession.TrackingProtectionDelegate;
 import org.mozilla.geckoview.GeckoView;
+import org.mozilla.geckoview.GeckoRuntime;
+import org.mozilla.geckoview.GeckoRuntimeSettings;
 
 public class GeckoViewActivity extends Activity {
     private static final String LOGTAG = "GeckoViewActivity";
     private static final String DEFAULT_URL = "https://mozilla.org";
     private static final String USE_MULTIPROCESS_EXTRA = "use_multiprocess";
     private static final String USE_REMOTE_DEBUGGER_EXTRA = "use_remote_debugger";
     private static final String ACTION_SHUTDOWN =
         "org.mozilla.geckoview_example.SHUTDOWN";
     private boolean mKillProcessOnDestroy;
 
     /* package */ static final int REQUEST_FILE_PICKER = 1;
     private static final int REQUEST_PERMISSIONS = 2;
 
+    private static GeckoRuntime sGeckoRuntime;
     private GeckoSession mGeckoSession;
     private GeckoView mGeckoView;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         Log.i(LOGTAG, "zerdatime " + SystemClock.elapsedRealtime() +
               " - application start");
 
-        final String[] geckoArgs;
+        setContentView(R.layout.geckoview_activity);
+        mGeckoView = (GeckoView) findViewById(R.id.gecko_view);
+
+        final boolean useMultiprocess =
+            getIntent().getBooleanExtra(USE_MULTIPROCESS_EXTRA, true);
+
+        if (sGeckoRuntime == null) {
+            final GeckoRuntimeSettings geckoSettings = new GeckoRuntimeSettings();
 
-        if (BuildConfig.DEBUG) {
-            // In debug builds, we want to load JavaScript resources fresh with each build.
-            geckoArgs = new String[] { "-purgecaches" };
-        } else {
-            geckoArgs = null;
+            if (BuildConfig.DEBUG) {
+                // In debug builds, we want to load JavaScript resources fresh with
+                // each build.
+                geckoSettings.setArguments(new String[] { "-purgecaches" });
+            }
+
+            geckoSettings.setUseContentProcessHint(useMultiprocess);
+            geckoSettings.setExtras(getIntent().getExtras());
+            sGeckoRuntime = GeckoRuntime.create(this, geckoSettings);
         }
 
-        final boolean useMultiprocess = getIntent().getBooleanExtra(USE_MULTIPROCESS_EXTRA,
-                                                                    true);
-        GeckoSession.preload(this, geckoArgs, getIntent().getExtras(), useMultiprocess);
+        final GeckoSessionSettings sessionSettings = new GeckoSessionSettings();
+        sessionSettings.setBoolean(GeckoSessionSettings.USE_MULTIPROCESS,
+                                   useMultiprocess);
+        mGeckoSession = new GeckoSession(sessionSettings);
 
-        setContentView(R.layout.geckoview_activity);
-
-        mGeckoView = (GeckoView) findViewById(R.id.gecko_view);
-        mGeckoSession = new GeckoSession();
-        mGeckoView.setSession(mGeckoSession);
+        mGeckoView.setSession(mGeckoSession, sGeckoRuntime);
 
         mGeckoSession.setContentDelegate(new MyGeckoViewContent());
         final MyTrackingProtection tp = new MyTrackingProtection();
         mGeckoSession.setTrackingProtectionDelegate(tp);
         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);
 
-        mGeckoSession.getSettings().setBoolean(GeckoSessionSettings.USE_MULTIPROCESS,
-                                               useMultiprocess);
-
         mGeckoSession.enableTrackingProtection(
               TrackingProtectionDelegate.CATEGORY_AD |
               TrackingProtectionDelegate.CATEGORY_ANALYTIC |
               TrackingProtectionDelegate.CATEGORY_SOCIAL
         );
 
         loadSettings(getIntent());
         loadFromIntent(getIntent());
@@ -101,17 +108,19 @@ public class GeckoViewActivity extends A
     }
 
     @Override
     protected void onNewIntent(final Intent intent) {
         super.onNewIntent(intent);
 
         if (ACTION_SHUTDOWN.equals(intent.getAction())) {
             mKillProcessOnDestroy = true;
-            GeckoThread.forceQuit();
+            if (sGeckoRuntime != null) {
+                sGeckoRuntime.shutdown();
+            }
             finish();
             return;
         }
 
         setIntent(intent);
 
         loadSettings(intent);
         if (intent.getData() != null) {
@@ -120,17 +129,17 @@ public class GeckoViewActivity extends A
     }
 
     private void loadFromIntent(final Intent intent) {
         final Uri uri = intent.getData();
         mGeckoSession.loadUri(uri != null ? uri.toString() : DEFAULT_URL);
     }
 
     private void loadSettings(final Intent intent) {
-        final GeckoSessionSettings settings = mGeckoView.getSettings();
+        final GeckoSessionSettings settings = mGeckoSession.getSettings();
         settings.setBoolean(
             GeckoSessionSettings.USE_REMOTE_DEBUGGER,
             intent.getBooleanExtra(USE_REMOTE_DEBUGGER_EXTRA, false));
 
         Log.i(LOGTAG, "Load with settings " + settings);
     }
 
     @Override
--- a/mozglue/misc/TimeStamp.h
+++ b/mozglue/misc/TimeStamp.h
@@ -586,17 +586,16 @@ public:
   // two TimeStamps, or scaling TimeStamps, is nonsense and must never
   // be allowed.
 
   static MFBT_API void Startup();
   static MFBT_API void Shutdown();
 
 private:
   friend struct IPC::ParamTraits<mozilla::TimeStamp>;
-  friend void StartupTimelineRecordExternal(int, uint64_t);
 
   MOZ_IMPLICIT TimeStamp(TimeStampValue aValue) : mValue(aValue) {}
 
   static MFBT_API TimeStamp Now(bool aHighResolution);
 
   /**
    * Computes the uptime of the current process in microseconds. The result
    * is platform-dependent and needs to be checked against existing timestamps
--- a/mozglue/misc/TimeStamp_windows.h
+++ b/mozglue/misc/TimeStamp_windows.h
@@ -12,17 +12,16 @@
 namespace mozilla {
 
 class TimeStamp;
 
 class TimeStampValue
 {
   friend struct IPC::ParamTraits<mozilla::TimeStampValue>;
   friend class TimeStamp;
-  friend void StartupTimelineRecordExternal(int, uint64_t);
 
   // Both QPC and GTC are kept in [mt] units.
   uint64_t mGTC;
   uint64_t mQPC;
   bool mHasQPC;
   bool mIsNull;
 
   MFBT_API TimeStampValue(uint64_t aGTC, uint64_t aQPC, bool aHasQPC);
--- a/security/sandbox/win/SandboxInitialization.cpp
+++ b/security/sandbox/win/SandboxInitialization.cpp
@@ -2,35 +2,127 @@
 /* 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 "SandboxInitialization.h"
 
 #include "base/memory/ref_counted.h"
+#include "nsWindowsDllInterceptor.h"
 #include "sandbox/win/src/sandbox_factory.h"
 #include "mozilla/sandboxing/permissionsService.h"
 
 namespace mozilla {
 namespace sandboxing {
 
+typedef BOOL(WINAPI* CloseHandle_func) (HANDLE hObject);
+static CloseHandle_func stub_CloseHandle = nullptr;
+
+typedef BOOL(WINAPI* DuplicateHandle_func)(HANDLE hSourceProcessHandle,
+                                           HANDLE hSourceHandle,
+                                           HANDLE hTargetProcessHandle,
+                                           LPHANDLE lpTargetHandle,
+                                           DWORD dwDesiredAccess,
+                                           BOOL bInheritHandle,
+                                           DWORD dwOptions);
+static DuplicateHandle_func stub_DuplicateHandle = nullptr;
+
+static BOOL WINAPI
+patched_CloseHandle(HANDLE hObject)
+{
+  // Check all handles being closed against the sandbox's tracked handles.
+  base::win::OnHandleBeingClosed(hObject);
+  return stub_CloseHandle(hObject);
+}
+
+static BOOL WINAPI
+patched_DuplicateHandle(HANDLE hSourceProcessHandle,
+                        HANDLE hSourceHandle,
+                        HANDLE hTargetProcessHandle,
+                        LPHANDLE lpTargetHandle,
+                        DWORD dwDesiredAccess,
+                        BOOL bInheritHandle,
+                        DWORD dwOptions)
+{
+  // If closing a source handle from our process check it against the sandbox's
+  // tracked handles.
+  if ((dwOptions & DUPLICATE_CLOSE_SOURCE) &&
+    (GetProcessId(hSourceProcessHandle) == ::GetCurrentProcessId())) {
+    base::win::OnHandleBeingClosed(hSourceHandle);
+  }
+
+  return stub_DuplicateHandle(hSourceProcessHandle, hSourceHandle,
+                              hTargetProcessHandle, lpTargetHandle,
+                              dwDesiredAccess, bInheritHandle, dwOptions);
+}
+
+static WindowsDllInterceptor Kernel32Intercept;
+
+static bool
+EnableHandleCloseMonitoring()
+{
+  Kernel32Intercept.Init("kernel32.dll");
+  bool hooked =
+    Kernel32Intercept.AddHook("CloseHandle",
+                              reinterpret_cast<intptr_t>(patched_CloseHandle),
+                              (void**)&stub_CloseHandle);
+  if (!hooked) {
+    return false;
+  }
+
+  hooked =
+    Kernel32Intercept.AddHook("DuplicateHandle",
+                              reinterpret_cast<intptr_t>(patched_DuplicateHandle),
+                              (void**)&stub_DuplicateHandle);
+  if (!hooked) {
+    return false;
+  }
+
+  return true;
+}
+
+static bool
+ShouldDisableHandleVerifier()
+{
+#if defined(_X86_) && (defined(NIGHTLY_BUILD) || defined(DEBUG))
+  // Chromium only has the verifier enabled for 32-bit and our close monitoring
+  // hooks cause debug assertions for 64-bit anyway.
+  // For x86 keep the verifier enabled by default only for Nightly or debug.
+  return false;
+#else
+  return !getenv("MOZ_ENABLE_HANDLE_VERIFIER");
+#endif
+}
+
+static void
+InitializeHandleVerifier()
+{
+  // Disable the handle verifier if we don't want it or can't enable the close
+  // monitoring hooks.
+  if (ShouldDisableHandleVerifier() || !EnableHandleCloseMonitoring()) {
+    base::win::DisableHandleVerifier();
+  }
+}
+
 static sandbox::TargetServices*
 InitializeTargetServices()
 {
   sandbox::TargetServices* targetServices =
     sandbox::SandboxFactory::GetTargetServices();
   if (!targetServices) {
     return nullptr;
   }
 
   if (targetServices->Init() != sandbox::SBOX_ALL_OK) {
     return nullptr;
   }
 
+  InitializeHandleVerifier();
+
   return targetServices;
 }
 
 sandbox::TargetServices*
 GetInitializedTargetServices()
 {
   static sandbox::TargetServices* sInitializedTargetServices =
     InitializeTargetServices();
@@ -61,16 +153,18 @@ InitializeBrokerServices()
   // Precreate the desktop and window station used by the renderers.
   // IMPORTANT: This piece of code needs to run as early as possible in the
   // process because it will initialize the sandbox broker, which requires
   // the process to swap its window station. During this time all the UI
   // will be broken. This has to run before threads and windows are created.
   scoped_refptr<sandbox::TargetPolicy> policy = brokerServices->CreatePolicy();
   sandbox::ResultCode result = policy->CreateAlternateDesktop(true);
 
+  InitializeHandleVerifier();
+
   return brokerServices;
 }
 
 sandbox::BrokerServices*
 GetInitializedBrokerServices()
 {
   static sandbox::BrokerServices* sInitializedBrokerServices =
     InitializeBrokerServices();
--- a/toolkit/xre/test/win/TestDllInterceptor.cpp
+++ b/toolkit/xre/test/win/TestDllInterceptor.cpp
@@ -431,16 +431,30 @@ bool TestTlsAlloc(void* aFunc)
 
 bool TestTlsFree(void* aFunc)
 {
   auto patchedTlsFree =
     reinterpret_cast<decltype(&TlsFree)>(aFunc);
   return sTlsIndex != 0 && patchedTlsFree(sTlsIndex);
 }
 
+bool TestCloseHandle(void* aFunc)
+{
+  auto patchedCloseHandle =
+    reinterpret_cast<decltype(&CloseHandle)>(aFunc);
+  return patchedCloseHandle(0) == FALSE;
+}
+
+bool TestDuplicateHandle(void* aFunc)
+{
+  auto patchedDuplicateHandle =
+    reinterpret_cast<decltype(&DuplicateHandle)>(aFunc);
+  return patchedDuplicateHandle(0, 0, 0, 0, 0, 0, 0) == FALSE;
+}
+
 bool TestPrintDlgW(void* aFunc)
 {
   auto patchedPrintDlgW =
     reinterpret_cast<decltype(&PrintDlgW)>(aFunc);
   patchedPrintDlgW(0);
   return true;
 }
 
@@ -689,16 +703,18 @@ int main()
 #endif
       MaybeTestHook(ShouldTestTipTsf(), TestProcessCaretEvents, "tiptsf.dll", "ProcessCaretEvents") &&
 #ifdef _M_IX86
       TestHook(TestSendMessageTimeoutW, "user32.dll", "SendMessageTimeoutW") &&
 #endif
       TestHook(TestSetCursorPos, "user32.dll", "SetCursorPos") &&
       TestHook(TestTlsAlloc, "kernel32.dll", "TlsAlloc") &&
       TestHook(TestTlsFree, "kernel32.dll", "TlsFree") &&
+      TestHook(TestCloseHandle, "kernel32.dll", "CloseHandle") &&
+      TestHook(TestDuplicateHandle, "kernel32.dll", "DuplicateHandle") &&
 
       TestHook(TestInternetOpenA, "wininet.dll", "InternetOpenA") &&
       TestHook(TestInternetCloseHandle, "wininet.dll", "InternetCloseHandle") &&
       TestHook(TestInternetConnectA, "wininet.dll", "InternetConnectA") &&
       TestHook(TestInternetQueryDataAvailable, "wininet.dll", "InternetQueryDataAvailable") &&
       TestHook(TestInternetReadFile, "wininet.dll", "InternetReadFile") &&
       TestHook(TestInternetWriteFile, "wininet.dll", "InternetWriteFile") &&
       TestHook(TestInternetSetOptionA, "wininet.dll", "InternetSetOptionA") &&