merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 18 Nov 2015 14:50:18 +0100
changeset 307203 1d6155d7e6c91fa5ec1ef6927f3d3a044187896d
parent 307134 f8cc032951d3e6546c78816dc2bfae80461e1feb (current diff)
parent 307202 8c5d3e1ee51053e15337f5fa363098a3f606adb8 (diff)
child 307214 b03c8fd9d78bab1a5ebe283d5f60b86e93b3772c
child 307219 44f58c7fc121e68854da500798ef84c0f432a32a
child 307243 7b0d3fa99e655643a36575b8714e5c7420d29588
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone45.0a1
first release with
nightly linux32
1d6155d7e6c9 / 45.0a1 / 20151119030404 / files
nightly linux64
1d6155d7e6c9 / 45.0a1 / 20151119030404 / files
nightly mac
1d6155d7e6c9 / 45.0a1 / 20151119030404 / files
nightly win32
1d6155d7e6c9 / 45.0a1 / 20151119030404 / files
nightly win64
1d6155d7e6c9 / 45.0a1 / 20151119030404 / 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/push/test/xpcshell/test_webapps_cleardata.js
testing/web-platform/meta/html/webappapis/scripting/events/event-handler-attributes-body-window.html.ini
--- a/browser/components/newtab/RemoteAboutNewTab.jsm
+++ b/browser/components/newtab/RemoteAboutNewTab.jsm
@@ -38,16 +38,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 let RemoteAboutNewTab = {
 
   pageListener: null,
 
   /**
    * Initialize the RemotePageManager and add all message listeners for this page
    */
   init: function() {
+    RemoteNewTabLocation.init();
     this.pageListener = new RemotePages("about:remote-newtab");
     this.pageListener.addMessageListener("NewTab:InitializeGrid", this.initializeGrid.bind(this));
     this.pageListener.addMessageListener("NewTab:UpdateGrid", this.updateGrid.bind(this));
     this.pageListener.addMessageListener("NewTab:Customize", this.customize.bind(this));
     this.pageListener.addMessageListener("NewTab:CaptureBackgroundPageThumbs",
         this.captureBackgroundPageThumb.bind(this));
     this.pageListener.addMessageListener("NewTab:PageThumbs", this.createPageThumb.bind(this));
     this.pageListener.addMessageListener("NewTabFrame:GetInit", this.initContentFrame.bind(this));
@@ -287,13 +288,14 @@ let RemoteAboutNewTab = {
     NewTabPrefsProvider.prefs.off("browser.newtabpage.enhanced", this.setEnhanced.bind(this));
     NewTabPrefsProvider.prefs.off("browser.newtabpage.pinned", this.setPinned.bind(this));
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference]),
 
   uninit: function() {
+    RemoteNewTabLocation.uninit();
     this._removeObservers();
     this.pageListener.destroy();
     this.pageListener = null;
   },
 };
--- a/browser/components/newtab/RemoteNewTabLocation.jsm
+++ b/browser/components/newtab/RemoteNewTabLocation.jsm
@@ -1,42 +1,141 @@
-/* globals Services */
+/* globals Services, UpdateUtils, XPCOMUtils, URL, NewTabPrefsProvider, Locale */
+/* exported RemoteNewTabLocation */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["RemoteNewTabLocation"];
 
-Components.utils.import("resource://gre/modules/Services.jsm");
-Components.utils.importGlobalProperties(["URL"]);
+const {utils: Cu} = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.importGlobalProperties(["URL"]);
+
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+  "resource://gre/modules/UpdateUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
+  "resource:///modules/NewTabPrefsProvider.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Locale",
+  "resource://gre/modules/Locale.jsm");
+
+// The preference that tells whether to match the OS locale
+const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
+
+// The preference that tells what locale the user selected
+const PREF_SELECTED_LOCALE = "general.useragent.locale";
 
-// TODO: will get dynamically set in bug 1210478
-const DEFAULT_PAGE_LOCATION = "https://newtab.cdn.mozilla.net/v0/nightly/en-US/index.html";
+const DEFAULT_PAGE_LOCATION = "https://newtab.cdn.mozilla.net/" +
+                              "v%VERSION%/%CHANNEL%/%LOCALE%/index.html";
+
+const VALID_CHANNELS = new Set(["esr", "release", "beta", "aurora", "nightly"]);
+
+const NEWTAB_VERSION = "0";
 
-this.RemoteNewTabLocation = {
-  _url: new URL(DEFAULT_PAGE_LOCATION),
+let RemoteNewTabLocation = {
+  /*
+   * Generate a default url based on locale and update channel
+   */
+  _generateDefaultURL() {
+    let releaseName = this._releaseFromUpdateChannel(UpdateUtils.UpdateChannel);
+    let uri = DEFAULT_PAGE_LOCATION
+      .replace("%VERSION%", this.version)
+      .replace("%LOCALE%", Locale.getLocale())
+      .replace("%CHANNEL%", releaseName);
+    return new URL(uri);
+  },
+
+  _url: null,
   _overridden: false,
 
   get href() {
     return this._url.href;
   },
 
   get origin() {
     return this._url.origin;
   },
 
   get overridden() {
     return this._overridden;
   },
 
-  override: function(newURL) {
-    this._url = new URL(newURL);
-    this._overridden = true;
-    Services.obs.notifyObservers(null, "remote-new-tab-location-changed",
-      this._url.href);
+  get version() {
+    return NEWTAB_VERSION;
+  },
+
+  get channels() {
+    return VALID_CHANNELS;
+  },
+
+  /**
+   * Returns the release name from an Update Channel name
+   *
+   * @return {String} a release name based on the update channel. Defaults to nightly
+   */
+  _releaseFromUpdateChannel(channel) {
+    let result = "nightly";
+    if (VALID_CHANNELS.has(channel)) {
+      result = channel;
+    }
+    return result;
+  },
+
+  /*
+   * Updates the location when the page is not overriden.
+   * Useful when there is a pref change
+   */
+  _updateMaybe() {
+    if (!this.overridden) {
+      let url = this._generateDefaultURL();
+      if (url.href !== this._url.href) {
+        this._url = url;
+        Services.obs.notifyObservers(null, "remote-new-tab-location-changed",
+          this._url.href);
+      }
+    }
   },
 
-  reset: function() {
-    this._url = new URL(DEFAULT_PAGE_LOCATION);
+  /*
+   * Override the Remote newtab page location.
+   */
+  override(newURL) {
+    let url = new URL(newURL);
+    if (url.href !== this._url.href) {
+      this._overridden = true;
+      this._url = url;
+      Services.obs.notifyObservers(null, "remote-new-tab-location-changed",
+                                   this._url.href);
+    }
+  },
+
+  /*
+   * Reset the newtab page location to the default value
+   */
+  reset() {
+    let url = this._generateDefaultURL();
+    if (url.href !== this._url.href) {
+      this._url = url;
+      this._overridden = false;
+      Services.obs.notifyObservers(null, "remote-new-tab-location-changed",
+        this._url.href);
+    }
+  },
+
+  init() {
+    NewTabPrefsProvider.prefs.on(
+      PREF_SELECTED_LOCALE,
+      this._updateMaybe.bind(this));
+
+    NewTabPrefsProvider.prefs.on(
+      PREF_MATCH_OS_LOCALE,
+      this._updateMaybe.bind(this));
+
+    this._url = this._generateDefaultURL();
+  },
+
+  uninit() {
+    this._url = null;
     this._overridden = false;
-    Services.obs.notifyObservers(null, "remote-new-tab-location-changed",
-      this._url.href);
+    NewTabPrefsProvider.prefs.off(PREF_SELECTED_LOCALE, this._updateMaybe);
+    NewTabPrefsProvider.prefs.off(PREF_MATCH_OS_LOCALE, this._updateMaybe);
   }
 };
--- a/browser/components/newtab/tests/xpcshell/test_RemoteNewTabLocation.js
+++ b/browser/components/newtab/tests/xpcshell/test_RemoteNewTabLocation.js
@@ -1,40 +1,141 @@
-/* globals ok, equal, RemoteNewTabLocation, Services */
+/* globals ok, equal, RemoteNewTabLocation, NewTabPrefsProvider, Services, Preferences */
+/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
 "use strict";
 
 Components.utils.import("resource:///modules/RemoteNewTabLocation.jsm");
+Components.utils.import("resource:///modules/NewTabPrefsProvider.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/Preferences.jsm");
 Components.utils.importGlobalProperties(["URL"]);
 
-add_task(function* () {
-  var notificationPromise;
-  let defaultHref = RemoteNewTabLocation.href;
+RemoteNewTabLocation.init();
+const DEFAULT_HREF = RemoteNewTabLocation.href;
+RemoteNewTabLocation.uninit();
 
+add_task(function* test_defaults() {
+  RemoteNewTabLocation.init();
   ok(RemoteNewTabLocation.href, "Default location has an href");
   ok(RemoteNewTabLocation.origin, "Default location has an origin");
   ok(!RemoteNewTabLocation.overridden, "Default location is not overridden");
+  RemoteNewTabLocation.uninit();
+});
 
+/**
+ * Tests the overriding of the default URL
+ */
+add_task(function* test_overrides() {
+  RemoteNewTabLocation.init();
   let testURL = new URL("https://example.com/");
+  let notificationPromise;
 
-  notificationPromise = changeNotificationPromise(testURL.href);
+  notificationPromise = nextChangeNotificationPromise(
+    testURL.href, "Remote Location should change");
   RemoteNewTabLocation.override(testURL.href);
   yield notificationPromise;
   ok(RemoteNewTabLocation.overridden, "Remote location should be overridden");
-  equal(RemoteNewTabLocation.href, testURL.href, "Remote href should be the custom URL");
-  equal(RemoteNewTabLocation.origin, testURL.origin, "Remote origin should be the custom URL");
+  equal(RemoteNewTabLocation.href, testURL.href,
+        "Remote href should be the custom URL");
+  equal(RemoteNewTabLocation.origin, testURL.origin,
+        "Remote origin should be the custom URL");
 
-  notificationPromise = changeNotificationPromise(defaultHref);
+  notificationPromise = nextChangeNotificationPromise(
+    DEFAULT_HREF, "Remote href should be reset");
   RemoteNewTabLocation.reset();
   yield notificationPromise;
   ok(!RemoteNewTabLocation.overridden, "Newtab URL should not be overridden");
-  equal(RemoteNewTabLocation.href, defaultHref, "Remote href should be reset");
+  RemoteNewTabLocation.uninit();
 });
 
-function changeNotificationPromise(aNewURL) {
+/**
+ * Tests how RemoteNewTabLocation responds to updates to prefs
+ */
+add_task(function* test_updates() {
+  RemoteNewTabLocation.init();
+  let notificationPromise;
+  let expectedHref = "https://newtab.cdn.mozilla.net" +
+                     `/v${RemoteNewTabLocation.version}` +
+                     "/nightly" +
+                     "/en-GB" +
+                     "/index.html";
+  Preferences.set("intl.locale.matchOS", true);
+  Preferences.set("general.useragent.locale", "en-GB");
+  NewTabPrefsProvider.prefs.init();
+
+  // test update checks for prefs
+  notificationPromise = nextChangeNotificationPromise(
+    expectedHref, "Remote href should be updated");
+  Preferences.set("intl.locale.matchOS", false);
+  yield notificationPromise;
+
+  notificationPromise = nextChangeNotificationPromise(
+    DEFAULT_HREF, "Remote href changes back to default");
+  Preferences.set("general.useragent.locale", "en-US");
+
+  yield notificationPromise;
+
+  // test update fires on override and reset
+  let testURL = new URL("https://example.com/");
+  notificationPromise = nextChangeNotificationPromise(
+    testURL.href, "a notification occurs on override");
+  RemoteNewTabLocation.override(testURL.href);
+  yield notificationPromise;
+
+  // from overridden to default
+  notificationPromise = nextChangeNotificationPromise(
+    DEFAULT_HREF, "a notification occurs on reset");
+  RemoteNewTabLocation.reset();
+  yield notificationPromise;
+
+  // override to default URL from default URL
+  notificationPromise = nextChangeNotificationPromise(
+    testURL.href, "a notification only occurs for a change in overridden urls");
+  RemoteNewTabLocation.override(DEFAULT_HREF);
+  RemoteNewTabLocation.override(testURL.href);
+  yield notificationPromise;
+
+  // reset twice, only one notification for default URL
+  notificationPromise = nextChangeNotificationPromise(
+    DEFAULT_HREF, "reset occurs");
+  RemoteNewTabLocation.reset();
+  yield notificationPromise;
+
+  notificationPromise = nextChangeNotificationPromise(
+    testURL.href, "a notification only occurs for a change in reset urls");
+  RemoteNewTabLocation.reset();
+  RemoteNewTabLocation.override(testURL.href);
+  yield notificationPromise;
+
+  NewTabPrefsProvider.prefs.uninit();
+  RemoteNewTabLocation.uninit();
+});
+
+/**
+ * Verifies that RemoteNewTabLocation's _releaseFromUpdateChannel
+ * Returns the correct release names
+ */
+add_task(function* test_release_names() {
+  RemoteNewTabLocation.init();
+  let valid_channels = RemoteNewTabLocation.channels;
+  let invalid_channels = new Set(["default", "invalid"]);
+
+  for (let channel of valid_channels) {
+    equal(channel, RemoteNewTabLocation._releaseFromUpdateChannel(channel),
+          "release == channel name when valid");
+  }
+
+  for (let channel of invalid_channels) {
+    equal("nightly", RemoteNewTabLocation._releaseFromUpdateChannel(channel),
+          "release == nightly when invalid");
+  }
+  RemoteNewTabLocation.uninit();
+});
+
+function nextChangeNotificationPromise(aNewURL, testMessage) {
   return new Promise(resolve => {
     Services.obs.addObserver(function observer(aSubject, aTopic, aData) { // jshint ignore:line
       Services.obs.removeObserver(observer, aTopic);
-      equal(aData, aNewURL, "remote-new-tab-location-changed data should be new URL.");
+      equal(aData, aNewURL, testMessage);
       resolve();
     }, "remote-new-tab-location-changed", false);
   });
 }
--- a/browser/extensions/pdfjs/test/browser.ini
+++ b/browser/extensions/pdfjs/test/browser.ini
@@ -1,12 +1,10 @@
 [DEFAULT]
-support-files = file_pdfjs_test.pdf
+support-files =
+  file_pdfjs_test.pdf
+  head.js
 
 [browser_pdfjs_main.js]
-skip-if = e10s # Bug 1159385
 [browser_pdfjs_navigation.js]
-skip-if = e10s # Bug 1159385
 [browser_pdfjs_savedialog.js]
 [browser_pdfjs_views.js]
-skip-if = e10s # Bug 1159385
 [browser_pdfjs_zoom.js]
-skip-if = e10s # Bug 1159385
--- a/browser/extensions/pdfjs/test/browser_pdfjs_main.js
+++ b/browser/extensions/pdfjs/test/browser_pdfjs_main.js
@@ -1,98 +1,67 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const RELATIVE_DIR = "browser/extensions/pdfjs/test/";
 const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR;
 
-function test() {
-  var tab;
-
+add_task(function* test() {
   let handlerService = Cc["@mozilla.org/uriloader/handler-service;1"].getService(Ci.nsIHandlerService);
   let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
   let handlerInfo = mimeService.getFromTypeAndExtension('application/pdf', 'pdf');
 
   // Make sure pdf.js is the default handler.
   is(handlerInfo.alwaysAskBeforeHandling, false, 'pdf handler defaults to always-ask is false');
   is(handlerInfo.preferredAction, Ci.nsIHandlerInfo.handleInternally, 'pdf handler defaults to internal');
 
   info('Pref action: ' + handlerInfo.preferredAction);
 
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
-    gBrowser.removeTab(tab);
-  });
+  yield BrowserTestUtils.withNewTab({ gBrowser: gBrowser, url: "about:blank" },
+    function* (newTabBrowser) {
+      yield waitForPdfJS(newTabBrowser, TESTROOT + "file_pdfjs_test.pdf");
 
-  tab = gBrowser.addTab(TESTROOT + "file_pdfjs_test.pdf");
-  var newTabBrowser = gBrowser.getBrowserForTab(tab);
-  newTabBrowser.addEventListener("load", function eventHandler() {
-    newTabBrowser.removeEventListener("load", eventHandler, true);
-
-    var document = newTabBrowser.contentDocument,
-        window = newTabBrowser.contentWindow;
+      ok(gBrowser.isFindBarInitialized(), "Browser FindBar initialized!");
 
-    // Runs tests after all 'load' event handlers have fired off
-    window.addEventListener("documentload", function() {
-      runTests(document, window, tab, function () {
-        closePDFViewer(window, finish);
-      });
-    }, false, true);
-  }, true);
-}
-
-
-function runTests(document, window, tab, callback) {
+      yield ContentTask.spawn(newTabBrowser, null, function* () {
+        //
+        // Overall sanity tests
+        //
+        ok(content.document.querySelector('div#viewer'), "document content has viewer UI");
+        ok('PDFJS' in content.wrappedJSObject, "window content has PDFJS object");
 
-  //
-  // Overall sanity tests
-  //
-  ok(document.querySelector('div#viewer'), "document content has viewer UI");
-  ok('PDFJS' in window.wrappedJSObject, "window content has PDFJS object");
-  ok('PDFViewerApplication' in window.wrappedJSObject,
-     "window content has viewer object");
+        //
+        // Sidebar: open
+        //
+        var sidebar = content.document.querySelector('button#sidebarToggle'),
+            outerContainer = content.document.querySelector('div#outerContainer');
 
-  //
-  // Browser Find
-  //
-  ok(gBrowser.isFindBarInitialized(tab), "Browser FindBar initialized!");
+        sidebar.click();
+        ok(outerContainer.classList.contains('sidebarOpen'), "sidebar opens on click");
 
-  //
-  // Sidebar: open
-  //
-  var sidebar = document.querySelector('button#sidebarToggle'),
-      outerContainer = document.querySelector('div#outerContainer');
-
-  sidebar.click();
-  ok(outerContainer.classList.contains('sidebarOpen'), 'sidebar opens on click');
+        //
+        // Sidebar: close
+        //
+        sidebar.click();
+        ok(!outerContainer.classList.contains('sidebarOpen'), "sidebar closes on click");
 
-  //
-  // Sidebar: close
-  //
-  sidebar.click();
-  ok(!outerContainer.classList.contains('sidebarOpen'), 'sidebar closes on click');
-
-  //
-  // Page change from prev/next buttons
-  //
-  var prevPage = document.querySelector('button#previous'),
-      nextPage = document.querySelector('button#next');
+        //
+        // Page change from prev/next buttons
+        //
+        var prevPage = content.document.querySelector('button#previous'),
+            nextPage = content.document.querySelector('button#next');
 
-  var pageNumber = document.querySelector('input#pageNumber');
-  is(parseInt(pageNumber.value), 1, 'initial page is 1');
-
-  //
-  // Bookmark button
-  //
-  var viewBookmark = document.querySelector('a#viewBookmark');
-  viewBookmark.click();
-  ok(viewBookmark.href.length > 0, 'viewBookmark button has href');
+        var pgNumber = content.document.querySelector('input#pageNumber').value;
+        is(parseInt(pgNumber, 10), 1, 'initial page is 1');
 
-  callback();
-}
+        //
+        // Bookmark button
+        //
+        var viewBookmark = content.document.querySelector('a#viewBookmark');
+        viewBookmark.click();
 
-/**
- * Destroys PDF.js viewer opened document.
- */
-function closePDFViewer(window, callback) {
-  var viewer = window.wrappedJSObject.PDFViewerApplication;
-  viewer.close().then(callback);
-}
+        ok(viewBookmark.href.length > 0, "viewBookmark button has href");
+
+        var viewer = content.wrappedJSObject.PDFViewerApplication;
+        yield viewer.close();
+      });
+    });
+});
--- a/browser/extensions/pdfjs/test/browser_pdfjs_navigation.js
+++ b/browser/extensions/pdfjs/test/browser_pdfjs_navigation.js
@@ -134,172 +134,148 @@ const TESTS = [
       value: 5,
       event: "change"
     },
     expectedPage: 5,
     message: "navigated to 5th page using pagenumber"
   }
 ];
 
-function test() {
-  var tab;
-
+add_task(function* test() {
   let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
   let handlerInfo = mimeService.getFromTypeAndExtension('application/pdf', 'pdf');
 
   // Make sure pdf.js is the default handler.
   is(handlerInfo.alwaysAskBeforeHandling, false, 'pdf handler defaults to always-ask is false');
   is(handlerInfo.preferredAction, Ci.nsIHandlerInfo.handleInternally, 'pdf handler defaults to internal');
 
   info('Pref action: ' + handlerInfo.preferredAction);
 
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
-    gBrowser.removeTab(tab);
-  });
-
-  tab = gBrowser.addTab(TESTROOT + "file_pdfjs_test.pdf");
-  gBrowser.selectedTab = tab;
+  yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" },
+    function* (newTabBrowser) {
+      yield waitForPdfJS(newTabBrowser, TESTROOT + "file_pdfjs_test.pdf");
 
-  var newTabBrowser = gBrowser.getBrowserForTab(tab);
-  newTabBrowser.addEventListener("load", function eventHandler() {
-    newTabBrowser.removeEventListener("load", eventHandler, true);
-
-    var document = newTabBrowser.contentDocument,
-        window = newTabBrowser.contentWindow;
+      yield ContentTask.spawn(newTabBrowser, null, function* () {
+        // Check if PDF is opened with internal viewer
+        ok(content.document.querySelector('div#viewer'), "document content has viewer UI");
+        ok('PDFJS' in content.wrappedJSObject, "window content has PDFJS object");
+      });
 
-    // Runs tests after all 'load' event handlers have fired off
-    window.addEventListener("documentload", function() {
-      runTests(document, window, function () {
-        var pageNumber = document.querySelector('input#pageNumber');
+      yield ContentTask.spawn(newTabBrowser, null, contentSetUp);
+
+      yield Task.spawn(runTests(newTabBrowser));
+
+      yield ContentTask.spawn(newTabBrowser, null, function*() {
+        let pageNumber = content.document.querySelector('input#pageNumber');
         is(pageNumber.value, pageNumber.max, "Document is left on the last page");
-        finish();
       });
-    }, false, true);
-  }, true);
-}
+    });
+});
 
-function runTests(document, window, finish) {
-  // Check if PDF is opened with internal viewer
-  ok(document.querySelector('div#viewer'), "document content has viewer UI");
-  ok('PDFJS' in window.wrappedJSObject, "window content has PDFJS object");
+function* contentSetUp() {
+  /**
+   * Outline Items gets appended to the document later on we have to
+   * wait for them before we start to navigate though document
+   *
+   * @param document
+   * @returns {deferred.promise|*}
+   */
+  function waitForOutlineItems(document) {
+    return new Promise((resolve, reject) => {
+      document.addEventListener("outlineloaded", function outlineLoaded(evt) {
+        document.removeEventListener("outlineloaded", outlineLoaded);
+        var outlineCount = evt.detail.outlineCount;
 
-  // Wait for outline items, the start the navigation actions
-  waitForOutlineItems(document).then(function () {
-    // The key navigation has to happen in page-fit, otherwise it won't scroll
-    // trough a complete page
-    setZoomToPageFit(document).then(function () {
-      runNextTest(document, window, finish);
-    }, function () {
-      ok(false, "Current scale has been set to 'page-fit'");
-      finish();
+        if (document.querySelectorAll(".outlineItem").length === outlineCount) {
+          resolve();
+        } else {
+          reject();
+        }
+      });
     });
-  }, function () {
-    ok(false, "Outline items have been found");
-    finish();
-  });
+  }
+
+  /**
+   * The key navigation has to happen in page-fit, otherwise it won't scroll
+   * through a complete page
+   *
+   * @param document
+   * @returns {deferred.promise|*}
+   */
+  function setZoomToPageFit(document) {
+    return new Promise((resolve) => {
+      document.addEventListener("pagerendered", function onZoom(e) {
+        document.removeEventListener("pagerendered", onZoom);
+        document.querySelector("#viewer").click();
+        resolve();
+      });
+
+      var select = document.querySelector("select#scaleSelect");
+      select.selectedIndex = 2;
+      select.dispatchEvent(new Event("change"));
+    });
+  }
+
+  yield waitForOutlineItems(content.document);
+  yield setZoomToPageFit(content.document);
 }
 
 /**
  * As the page changes asynchronously, we have to wait for the event after
  * we trigger the action so we will be at the expected page number after each action
  *
  * @param document
  * @param window
  * @param test
  * @param callback
  */
-function runNextTest(document, window, endCallback) {
-  var test = TESTS.shift(),
-      deferred = Promise.defer(),
-      pageNumber = document.querySelector('input#pageNumber');
+function* runTests(browser) {
+  yield ContentTask.spawn(browser, TESTS, function* (TESTS) {
+    let window = content;
+    let document = window.document;
+
+    for (let test of TESTS) {
+      let deferred = {};
+      deferred.promise = new Promise((resolve, reject) => {
+        deferred.resolve = resolve;
+        deferred.reject = reject;
+      });
 
-  // Add an event-listener to wait for page to change, afterwards resolve the promise
-  var timeout = window.setTimeout(() => deferred.reject(), 5000);
-  window.addEventListener('pagechange', function pageChange() {
-    if (pageNumber.value == test.expectedPage) {
-      window.removeEventListener('pagechange', pageChange);
-      window.clearTimeout(timeout);
-      deferred.resolve(pageNumber.value);
-    }
-  });
+      let pageNumber = document.querySelector('input#pageNumber');
 
-  // Get the element and trigger the action for changing the page
-  var el = document.querySelector(test.action.selector);
-  ok(el, "Element '" + test.action.selector + "' has been found");
-
-  // The value option is for input case
-  if (test.action.value)
-    el.value = test.action.value;
+      // Add an event-listener to wait for page to change, afterwards resolve the promise
+      let timeout = window.setTimeout(() => deferred.reject(), 5000);
+      window.addEventListener('pagechange', function pageChange() {
+        if (pageNumber.value == test.expectedPage) {
+          window.removeEventListener('pagechange', pageChange);
+          window.clearTimeout(timeout);
+          deferred.resolve(+pageNumber.value);
+        }
+      });
 
-  // Dispatch the event for changing the page
-  if (test.action.event == "keydown") {
-    var ev = document.createEvent("KeyboardEvent");
-        ev.initKeyEvent("keydown", true, true, null, false, false, false, false,
-                        test.action.keyCode, 0);
-    el.dispatchEvent(ev);
-  }
-  else {
-    var ev = new Event(test.action.event);
-  }
-  el.dispatchEvent(ev);
+      // Get the element and trigger the action for changing the page
+      var el = document.querySelector(test.action.selector);
+      ok(el, "Element '" + test.action.selector + "' has been found");
 
+      // The value option is for input case
+      if (test.action.value)
+        el.value = test.action.value;
 
-  // When the promise gets resolved we call the next test if there are any left
-  // or else we call the final callback which will end the test
-  deferred.promise.then(function (pgNumber) {
-    is(pgNumber, test.expectedPage, test.message);
+      // Dispatch the event for changing the page
+      if (test.action.event == "keydown") {
+        var ev = document.createEvent("KeyboardEvent");
+            ev.initKeyEvent("keydown", true, true, null, false, false, false, false,
+                            test.action.keyCode, 0);
+        el.dispatchEvent(ev);
+      }
+      else {
+        var ev = new Event(test.action.event);
+      }
+      el.dispatchEvent(ev);
 
-    if (TESTS.length)
-      runNextTest(document, window, endCallback);
-    else
-      endCallback();
-  }, function () {
-    ok(false, "Test '" + test.message + "' failed with timeout.");
-    endCallback();
+      let pgNumber = yield deferred.promise;
+      is(pgNumber, test.expectedPage, test.message);
+    }
+
+    var viewer = content.wrappedJSObject.PDFViewerApplication;
+    yield viewer.close();
   });
 }
-
-/**
- * Outline Items gets appended to the document latter on we have to
- * wait for them before we start to navigate though document
- *
- * @param document
- * @returns {deferred.promise|*}
- */
-function waitForOutlineItems(document) {
-  var deferred = Promise.defer();
-  document.addEventListener("outlineloaded", function outlineLoaded(evt) {
-    document.removeEventListener("outlineloaded", outlineLoaded);
-    var outlineCount = evt.detail.outlineCount;
-
-    if (document.querySelectorAll(".outlineItem").length === outlineCount) {
-      deferred.resolve();
-    } else {
-      deferred.reject();
-    }
-  });
-
-  return deferred.promise;
-}
-
-/**
- * The key navigation has to happen in page-fit, otherwise it won't scroll
- * trough a complete page
- *
- * @param document
- * @returns {deferred.promise|*}
- */
-function setZoomToPageFit(document) {
-  var deferred = Promise.defer();
-  document.addEventListener("pagerendered", function onZoom(e) {
-    document.removeEventListener("pagerendered", onZoom);
-    document.querySelector("#viewer").click();
-    deferred.resolve();
-  });
-
-  var select = document.querySelector("select#scaleSelect");
-  select.selectedIndex = 2;
-  select.dispatchEvent(new Event("change"));
-
-  return deferred.promise;
-}
-
--- a/browser/extensions/pdfjs/test/browser_pdfjs_views.js
+++ b/browser/extensions/pdfjs/test/browser_pdfjs_views.js
@@ -1,86 +1,61 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const RELATIVE_DIR = "browser/extensions/pdfjs/test/";
 const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR;
 
-function test() {
-  var tab;
-
+add_task(function* test() {
   let handlerService = Cc["@mozilla.org/uriloader/handler-service;1"].getService(Ci.nsIHandlerService);
   let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
   let handlerInfo = mimeService.getFromTypeAndExtension('application/pdf', 'pdf');
 
   // Make sure pdf.js is the default handler.
   is(handlerInfo.alwaysAskBeforeHandling, false, 'pdf handler defaults to always-ask is false');
   is(handlerInfo.preferredAction, Ci.nsIHandlerInfo.handleInternally, 'pdf handler defaults to internal');
 
   info('Pref action: ' + handlerInfo.preferredAction);
 
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
-    gBrowser.removeTab(tab);
-  });
+  yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" },
+    function* (browser) {
+      // check that PDF is opened with internal viewer
+      yield waitForPdfJS(browser, TESTROOT + "file_pdfjs_test.pdf");
 
-  tab = gBrowser.addTab(TESTROOT + "file_pdfjs_test.pdf");
-  var newTabBrowser = gBrowser.getBrowserForTab(tab);
-  newTabBrowser.addEventListener("load", function eventHandler() {
-    newTabBrowser.removeEventListener("load", eventHandler, true);
-
-    var document = newTabBrowser.contentDocument,
-        window = newTabBrowser.contentWindow;
+      yield ContentTask.spawn(browser, null, function* () {
+        ok(content.document.querySelector('div#viewer'), "document content has viewer UI");
+        ok('PDFJS' in content.wrappedJSObject, "window content has PDFJS object");
 
-    // Runs tests after all 'load' event handlers have fired off
-    window.addEventListener("documentload", function() {
-      runTests(document, window, function () {
-        closePDFViewer(window, finish);
-      });
-    }, false, true);
-  }, true);
-}
+        //open sidebar
+        var sidebar = content.document.querySelector('button#sidebarToggle');
+        var outerContainer = content.document.querySelector('div#outerContainer');
 
-function runTests(document, window, callback) {
-  // check that PDF is opened with internal viewer
-  ok(document.querySelector('div#viewer'), "document content has viewer UI");
-  ok('PDFJS' in window.wrappedJSObject, "window content has PDFJS object");
+        sidebar.click();
+        ok(outerContainer.classList.contains('sidebarOpen'), 'sidebar opens on click');
 
-  //open sidebar
-  var sidebar = document.querySelector('button#sidebarToggle');
-  var outerContainer = document.querySelector('div#outerContainer');
-
-  sidebar.click();
-  ok(outerContainer.classList.contains('sidebarOpen'), 'sidebar opens on click');
+        // check that thumbnail view is open
+        var thumbnailView = content.document.querySelector('div#thumbnailView');
+        var outlineView = content.document.querySelector('div#outlineView');
 
-  // check that thumbnail view is open
-  var thumbnailView = document.querySelector('div#thumbnailView');
-  var outlineView = document.querySelector('div#outlineView');
-
-  is(thumbnailView.getAttribute('class'), null, 'Initial view is thumbnail view');
-  is(outlineView.getAttribute('class'), 'hidden', 'Outline view is hidden initially');
+        is(thumbnailView.getAttribute('class'), null, 'Initial view is thumbnail view');
+        is(outlineView.getAttribute('class'), 'hidden', 'Outline view is hidden initially');
 
-  //switch to outline view
-  var viewOutlineButton = document.querySelector('button#viewOutline');
-  viewOutlineButton.click();
+        //switch to outline view
+        var viewOutlineButton = content.document.querySelector('button#viewOutline');
+        viewOutlineButton.click();
 
-  is(outlineView.getAttribute('class'), '', 'Outline view is visible when selected');
-  is(thumbnailView.getAttribute('class'), 'hidden', 'Thumbnail view is hidden when outline is selected');
+        is(thumbnailView.getAttribute('class'), 'hidden', 'Thumbnail view is hidden when outline is selected');
+        is(outlineView.getAttribute('class'), '', 'Outline view is visible when selected');
 
-  //switch back to thumbnail view
-  var viewThumbnailButton = document.querySelector('button#viewThumbnail');
-  viewThumbnailButton.click();
+        //switch back to thumbnail view
+        var viewThumbnailButton = content.document.querySelector('button#viewThumbnail');
+        viewThumbnailButton.click();
 
-  is(thumbnailView.getAttribute('class'), '', 'Thumbnail view is visible when selected');
-  is(outlineView.getAttribute('class'), 'hidden', 'Outline view is hidden when thumbnail is selected');
-
-  sidebar.click();
+        is(thumbnailView.getAttribute('class'), '', 'Thumbnail view is visible when selected');
+        is(outlineView.getAttribute('class'), 'hidden', 'Outline view is hidden when thumbnail is selected');
 
-  callback();
-}
+        sidebar.click();
 
-/**
- * Destroys PDF.js viewer opened document.
- */
-function closePDFViewer(window, callback) {
-  var viewer = window.wrappedJSObject.PDFViewerApplication;
-  viewer.close().then(callback);
-}
+        var viewer = content.wrappedJSObject.PDFViewerApplication;
+        yield viewer.close();
+      });
+    });
+});
--- a/browser/extensions/pdfjs/test/browser_pdfjs_zoom.js
+++ b/browser/extensions/pdfjs/test/browser_pdfjs_zoom.js
@@ -23,25 +23,27 @@ const TESTS = [
     },
     expectedZoom: -1, // -1 - zoom out
     message: "Zoomed out using the '-' (zoom out) button"
   },
 
   {
     action: {
       keyboard: true,
+      keyCode: 61,
       event: "+"
     },
     expectedZoom: 1, // 1 - zoom in
     message: "Zoomed in using the CTRL++ keys"
   },
 
   {
     action: {
       keyboard: true,
+      keyCode: 109,
       event: "-"
     },
     expectedZoom: -1, // -1 - zoom out
     message: "Zoomed out using the CTRL+- keys"
   },
 
   {
     action: {
@@ -49,137 +51,102 @@ const TESTS = [
       index: 5,
       event: "change"
     },
     expectedZoom: -1, // -1 - zoom out
     message: "Zoomed using the zoom picker"
   }
 ];
 
-var initialWidth; // the initial width of the PDF document
-var previousWidth; // the width of the PDF document at previous step/test
-
-function test() {
-  var tab;
+add_task(function* test() {
   let handlerService = Cc["@mozilla.org/uriloader/handler-service;1"]
                        .getService(Ci.nsIHandlerService);
   let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
   let handlerInfo = mimeService.getFromTypeAndExtension('application/pdf', 'pdf');
 
   // Make sure pdf.js is the default handler.
   is(handlerInfo.alwaysAskBeforeHandling, false,
      'pdf handler defaults to always-ask is false');
   is(handlerInfo.preferredAction, Ci.nsIHandlerInfo.handleInternally,
     'pdf handler defaults to internal');
 
   info('Pref action: ' + handlerInfo.preferredAction);
 
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
-    gBrowser.removeTab(tab);
-  });
+  yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" },
+    function* (newTabBrowser) {
+      yield waitForPdfJS(newTabBrowser, TESTROOT + "file_pdfjs_test.pdf" + "#zoom=100");
 
-  tab = gBrowser.selectedTab = gBrowser.addTab(TESTROOT + "file_pdfjs_test.pdf");
-  var newTabBrowser = gBrowser.getBrowserForTab(tab);
-
-  newTabBrowser.addEventListener("load", function eventHandler() {
-    newTabBrowser.removeEventListener("load", eventHandler, true);
+      yield ContentTask.spawn(newTabBrowser, TESTS, function* (TESTS) {
+        let document = content.document;
 
-    var document = newTabBrowser.contentDocument,
-        window = newTabBrowser.contentWindow;
+        function waitForRender() {
+          return new Promise((resolve) => {
+            document.addEventListener("pagerendered", function onPageRendered(e) {
+              if(e.detail.pageNumber !== 1) {
+                return;
+              }
 
-    // Runs tests after all 'load' event handlers have fired off
-    window.addEventListener("documentload", function() {
-      initialWidth = parseInt(document.querySelector("div#pageContainer1").style.width);
-      previousWidth = initialWidth;
-      runTests(document, window, function () {
-        closePDFViewer(window, finish);
-      });
-    }, false, true);
-  }, true);
-}
+              document.removeEventListener("pagerendered", onPageRendered, true);
+              resolve();
+            }, true);
+          });
+        }
 
-function runTests(document, window, callback) {
-  // check that PDF is opened with internal viewer
-  ok(document.querySelector('div#viewer'), "document content has viewer UI");
-  ok('PDFJS' in window.wrappedJSObject, "window content has PDFJS object");
+        // check that PDF is opened with internal viewer
+        ok(content.document.querySelector('div#viewer'), "document content has viewer UI");
+        ok('PDFJS' in content.wrappedJSObject, "window content has PDFJS object");
 
-  // Start the zooming tests after the document is loaded
-  waitForDocumentLoad(document).then(function () {
-    zoomPDF(document, window, TESTS.shift(), callback);
-  });
-}
+        let initialWidth, previousWidth;
+        initialWidth = previousWidth =
+          parseInt(content.document.querySelector("div#pageContainer1").style.width);
 
-function waitForDocumentLoad(document) {
-  var deferred = Promise.defer();
-  var interval = setInterval(function () {
-    if (document.querySelector("div#pageContainer1") != null){
-      clearInterval(interval);
-      deferred.resolve();
-    }
-  }, 500);
+        for (let test of TESTS) {
+          // We zoom using an UI element
+          var ev;
+          if (test.action.selector) {
+            // Get the element and trigger the action for changing the zoom
+            var el = document.querySelector(test.action.selector);
+            ok(el, "Element '" + test.action.selector + "' has been found");
 
-  return deferred.promise;
-}
-
-function zoomPDF(document, window, test, endCallback) {
-  var renderedPage;
-
-  document.addEventListener("pagerendered", function onPageRendered(e) {
-    if(e.detail.pageNumber !== 1) {
-      return;
-    }
+            if (test.action.index){
+              el.selectedIndex = test.action.index;
+            }
 
-    document.removeEventListener("pagerendered", onPageRendered, true);
-
-    var pageZoomScale = document.querySelector('select#scaleSelect');
-
-    // The zoom value displayed in the zoom select
-    var zoomValue = pageZoomScale.options[pageZoomScale.selectedIndex].innerHTML;
-
-    let pageContainer = document.querySelector('div#pageContainer1');
-    let actualWidth  = parseInt(pageContainer.style.width);
-
-    // the actual zoom of the PDF document
-    let computedZoomValue = parseInt(((actualWidth/initialWidth).toFixed(2))*100) + "%";
-    is(computedZoomValue, zoomValue, "Content has correct zoom");
+            // Dispatch the event for changing the zoom
+            ev = new Event(test.action.event);
+          }
+          // We zoom using keyboard
+          else {
+            // Simulate key press
+            ev = new content.KeyboardEvent("keydown",
+                                           { key: test.action.event,
+                                             keyCode: test.action.keyCode,
+                                             ctrlKey: true });
+            el = content;
+          }
 
-    // Check that document zooms in the expected way (in/out)
-    let zoom = (actualWidth - previousWidth) * test.expectedZoom;
-    ok(zoom > 0, test.message);
+          el.dispatchEvent(ev);
+          yield waitForRender();
 
-    // Go to next test (if there is any) or finish
-    var nextTest = TESTS.shift();
-    if (nextTest) {
-      previousWidth = actualWidth;
-      zoomPDF(document, window, nextTest, endCallback);
-    }
-    else
-      endCallback();
-  }, true);
+          var pageZoomScale = content.document.querySelector('select#scaleSelect');
 
-  // We zoom using an UI element
-  if (test.action.selector) {
-    // Get the element and trigger the action for changing the zoom
-    var el = document.querySelector(test.action.selector);
-    ok(el, "Element '" + test.action.selector + "' has been found");
+          // The zoom value displayed in the zoom select
+          var zoomValue = pageZoomScale.options[pageZoomScale.selectedIndex].innerHTML;
 
-    if (test.action.index){
-      el.selectedIndex = test.action.index;
-    }
+          let pageContainer = content.document.querySelector('div#pageContainer1');
+          let actualWidth = parseInt(pageContainer.style.width);
+
+          // the actual zoom of the PDF document
+          let computedZoomValue = parseInt(((actualWidth/initialWidth).toFixed(2))*100) + "%";
+          is(computedZoomValue, zoomValue, "Content has correct zoom");
 
-    // Dispatch the event for changing the zoom
-    el.dispatchEvent(new Event(test.action.event));
-  }
-  // We zoom using keyboard
-  else {
-    // Simulate key press
-    EventUtils.synthesizeKey(test.action.event, { ctrlKey: true });
-  }
-}
+          // Check that document zooms in the expected way (in/out)
+          let zoom = (actualWidth - previousWidth) * test.expectedZoom;
+          ok(zoom > 0, test.message);
 
-/**
- * Destroys PDF.js viewer opened document.
- */
-function closePDFViewer(window, callback) {
-  var viewer = window.wrappedJSObject.PDFViewerApplication;
-  viewer.close().then(callback);
-}
+          previousWidth = actualWidth;
+        }
+
+        var viewer = content.wrappedJSObject.PDFViewerApplication;
+        yield viewer.close();
+      });
+    });
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pdfjs/test/head.js
@@ -0,0 +1,15 @@
+function waitForPdfJS(browser, url) {
+  // Runs tests after all 'load' event handlers have fired off
+  return ContentTask.spawn(browser, url, function* (url) {
+    yield new Promise((resolve) => {
+      // NB: Add the listener to the global object so that we receive the
+      // event fired from the new window.
+      addEventListener("documentload", function listener() {
+        removeEventListener("documentload", listener, false);
+        resolve();
+      }, false, true);
+
+      content.location = url;
+    });
+  });
+}
--- a/chrome/nsChromeRegistryChrome.cpp
+++ b/chrome/nsChromeRegistryChrome.cpp
@@ -391,55 +391,30 @@ SerializeURI(nsIURI* aURI,
 {
   if (!aURI)
     return;
 
   aURI->GetSpec(aSerializedURI.spec);
   aURI->GetOriginCharset(aSerializedURI.charset);
 }
 
-static PLDHashOperator
-EnumerateOverride(nsIURI* aURIKey,
-                  nsIURI* aURI,
-                  void* aArg)
-{
-  nsTArray<OverrideMapping>* overrides =
-      static_cast<nsTArray<OverrideMapping>*>(aArg);
-
-  SerializedURI chromeURI, overrideURI;
-
-  SerializeURI(aURIKey, chromeURI);
-  SerializeURI(aURI, overrideURI);
-
-  OverrideMapping override = {
-    chromeURI, overrideURI
-  };
-  overrides->AppendElement(override);
-  return (PLDHashOperator)PL_DHASH_NEXT;
-}
-
-struct EnumerationArgs
-{
-  InfallibleTArray<ChromePackage>& packages;
-  const nsCString& selectedLocale;
-  const nsCString& selectedSkin;
-};
-
 void
 nsChromeRegistryChrome::SendRegisteredChrome(
     mozilla::dom::PContentParent* aParent)
 {
   InfallibleTArray<ChromePackage> packages;
   InfallibleTArray<SubstitutionMapping> resources;
   InfallibleTArray<OverrideMapping> overrides;
 
-  EnumerationArgs args = {
-    packages, mSelectedLocale, mSelectedSkin
-  };
-  mPackagesHash.EnumerateRead(CollectPackages, &args);
+  for (auto iter = mPackagesHash.Iter(); !iter.Done(); iter.Next()) {
+    ChromePackage chromePackage;
+    ChromePackageFromPackageEntry(iter.Key(), iter.UserData(), &chromePackage,
+                                  mSelectedLocale, mSelectedSkin);
+    packages.AppendElement(chromePackage);
+  }
 
   // If we were passed a parent then a new child process has been created and
   // has requested all of the chrome so send it the resources too. Otherwise
   // resource mappings are sent by the resource protocol handler dynamically.
   if (aParent) {
     nsCOMPtr<nsIIOService> io (do_GetIOService());
     NS_ENSURE_TRUE_VOID(io);
 
@@ -447,17 +422,25 @@ nsChromeRegistryChrome::SendRegisteredCh
     nsresult rv = io->GetProtocolHandler("resource", getter_AddRefs(ph));
     NS_ENSURE_SUCCESS_VOID(rv);
 
     nsCOMPtr<nsIResProtocolHandler> irph (do_QueryInterface(ph));
     nsResProtocolHandler* rph = static_cast<nsResProtocolHandler*>(irph.get());
     rph->CollectSubstitutions(resources);
   }
 
-  mOverrideTable.EnumerateRead(&EnumerateOverride, &overrides);
+  for (auto iter = mOverrideTable.Iter(); !iter.Done(); iter.Next()) {
+    SerializedURI chromeURI, overrideURI;
+
+    SerializeURI(iter.Key(), chromeURI);
+    SerializeURI(iter.UserData(), overrideURI);
+
+    OverrideMapping override = { chromeURI, overrideURI };
+    overrides.AppendElement(override);
+  }
 
   if (aParent) {
     bool success = aParent->SendRegisterChrome(packages, resources, overrides,
                                                mSelectedLocale, false);
     NS_ENSURE_TRUE_VOID(success);
   } else {
     nsTArray<ContentParent*> parents;
     ContentParent::GetAll(parents);
@@ -485,30 +468,16 @@ nsChromeRegistryChrome::ChromePackageFro
                                          nsProviderArray::LOCALE),
                aChromePackage->localeBaseURI);
   SerializeURI(aPackage->skins.GetBase(aSelectedSkin, nsProviderArray::ANY),
                aChromePackage->skinBaseURI);
   aChromePackage->package = aPackageName;
   aChromePackage->flags = aPackage->flags;
 }
 
-PLDHashOperator
-nsChromeRegistryChrome::CollectPackages(const nsACString &aKey,
-                                        PackageEntry *package,
-                                        void *arg)
-{
-  EnumerationArgs* args = static_cast<EnumerationArgs*>(arg);
-
-  ChromePackage chromePackage;
-  ChromePackageFromPackageEntry(aKey, package, &chromePackage,
-                                args->selectedLocale, args->selectedSkin);
-  args->packages.AppendElement(chromePackage);
-  return PL_DHASH_NEXT;
-}
-
 static bool
 CanLoadResource(nsIURI* aResourceURI)
 {
   bool isLocalResource = false;
   (void)NS_URIChainHasFlags(aResourceURI,
                             nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
                             &isLocalResource);
   return isLocalResource;
--- a/chrome/nsChromeRegistryChrome.h
+++ b/chrome/nsChromeRegistryChrome.h
@@ -54,19 +54,16 @@ class nsChromeRegistryChrome : public ns
 
  private:
   struct PackageEntry;
   static void ChromePackageFromPackageEntry(const nsACString& aPackageName,
                                             PackageEntry* aPackage,
                                             ChromePackage* aChromePackage,
                                             const nsCString& aSelectedLocale,
                                             const nsCString& aSelectedSkin);
-  static PLDHashOperator CollectPackages(const nsACString &aKey,
-                                         PackageEntry *package,
-                                         void *arg);
 
   nsresult OverrideLocalePackage(const nsACString& aPackage,
                                  nsACString& aOverride);
   nsresult SelectLocaleFromPref(nsIPrefBranch* prefs);
   nsresult UpdateSelectedLocale() override;
   nsIURI* GetBaseURIFromPackage(const nsCString& aPackage,
                                  const nsCString& aProvider,
                                  const nsCString& aPath) override;
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -905,16 +905,17 @@ GK_ATOM(onsending, "onsending")
 GK_ATOM(onsent, "onsent")
 GK_ATOM(onset, "onset")
 GK_ATOM(onshow, "onshow")
 GK_ATOM(onshutter, "onshutter")
 GK_ATOM(onstatechange, "onstatechange")
 GK_ATOM(onstatuschanged, "onstatuschanged")
 GK_ATOM(onstkcommand, "onstkcommand")
 GK_ATOM(onstksessionend, "onstksessionend")
+GK_ATOM(onstorage, "onstorage")
 GK_ATOM(onstorageareachanged, "onstorageareachanged")
 GK_ATOM(onsubmit, "onsubmit")
 GK_ATOM(onsuccess, "onsuccess")
 GK_ATOM(ontypechange, "ontypechange")
 GK_ATOM(ontext, "ontext")
 GK_ATOM(ontouchstart, "ontouchstart")
 GK_ATOM(ontouchend, "ontouchend")
 GK_ATOM(ontouchmove, "ontouchmove")
--- a/dom/base/nsScriptNameSpaceManager.h
+++ b/dom/base/nsScriptNameSpaceManager.h
@@ -86,17 +86,17 @@ public:
   // Our hash table ops don't care about the order of these members.
   nsString mKey;
   nsGlobalNameStruct mGlobalName;
 
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
     // Measurement of the following members may be added later if DMD finds it
     // is worthwhile:
     // - mGlobalName
-    return mKey.SizeOfExcludingThisMustBeUnshared(aMallocSizeOf);
+    return mKey.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
   }
 };
 
 class nsICategoryManager;
 
 class nsScriptNameSpaceManager : public nsIObserver,
                                  public nsSupportsWeakReference,
                                  public nsIMemoryReporter
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -522,18 +522,20 @@ WINDOW_EVENT(pageshow,
              EventNameType_HTMLBodyOrFramesetOnly,
              eBasicEventClass)
 WINDOW_EVENT(popstate,
              ePopState,
              EventNameType_XUL | EventNameType_HTMLBodyOrFramesetOnly,
              eBasicEventClass)
 // Not supported yet
 // WINDOW_EVENT(redo)
-// Not supported yet
-// WINDOW_EVENT(storage)
+WINDOW_EVENT(storage,
+             eStorage,
+             EventNameType_HTMLBodyOrFramesetOnly,
+             eBasicEventClass)
 // Not supported yet
 // WINDOW_EVENT(undo)
 WINDOW_EVENT(unload,
              eUnload,
              (EventNameType_XUL | EventNameType_SVGSVG |
               EventNameType_HTMLBodyOrFramesetOnly),
              eBasicEventClass)
 
--- a/dom/imptests/failures/html/html/browsers/the-window-object/test_window-properties.html.json
+++ b/dom/imptests/failures/html/html/browsers/the-window-object/test_window-properties.html.json
@@ -20,13 +20,12 @@
   "Window readonly attribute: screenX": true,
   "Window readonly attribute: screenY": true,
   "Window readonly attribute: outerWidth": true,
   "Window readonly attribute: outerHeight": true,
   "Window attribute: oncancel": true,
   "Window attribute: onclose": true,
   "Window attribute: oncuechange": true,
   "Window attribute: onmousewheel": true,
-  "Window attribute: onstorage": true,
   "Window unforgeable attribute: window": true,
   "Window unforgeable attribute: document": true,
   "Window unforgeable attribute: top": true
 }
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -62,17 +62,17 @@ NodeValueWarning=Use of attributes' node
 TextContentWarning=Use of attributes' textContent attribute is deprecated. Use value instead.
 EnablePrivilegeWarning=Use of enablePrivilege is deprecated.  Please use code that runs with the system principal (e.g. an extension) instead.
 nsIJSONDecodeDeprecatedWarning=nsIJSON.decode is deprecated.  Please use JSON.parse instead.
 nsIJSONEncodeDeprecatedWarning=nsIJSON.encode is deprecated.  Please use JSON.stringify instead.
 nsIDOMWindowInternalWarning=Use of nsIDOMWindowInternal is deprecated. Use nsIDOMWindow instead.
 FullScreenDeniedDisabled=Request for full-screen was denied because full-screen API is disabled by user preference.
 FullScreenDeniedFocusedPlugin=Request for full-screen was denied because a windowed plugin is focused.
 FullScreenDeniedHidden=Request for full-screen was denied because the document is no longer visible.
-FullScreenDeniedContainerNotAllowed=Request for full-screen was denied because at least one of the document's containing element is not iframe or does not have an "allowfullscreen" attribute.
+FullScreenDeniedContainerNotAllowed=Request for full-screen was denied because at least one of the document's containing elements is not an iframe or does not have an "allowfullscreen" attribute.
 FullScreenDeniedNotInputDriven=Request for full-screen was denied because Element.mozRequestFullScreen() was not called from inside a short running user-generated event handler.
 FullScreenDeniedNotInDocument=Request for full-screen was denied because requesting element is no longer in its document.
 FullScreenDeniedMovedDocument=Request for full-screen was denied because requesting element has moved document.
 FullScreenDeniedLostWindow=Request for full-screen was denied because we no longer have a window.
 FullScreenDeniedSubDocFullScreen=Request for full-screen was denied because a subdocument of the document requesting full-screen is already full-screen.
 FullScreenDeniedNotDescendant=Request for full-screen was denied because requesting element is not a descendant of the current full-screen element.
 FullScreenDeniedNotFocusedTab=Request for full-screen was denied because requesting element is not in the currently focused tab.
 RemovedFullScreenElement=Exited full-screen because full-screen element was removed from document.
--- a/dom/media/AbstractMediaDecoder.h
+++ b/dom/media/AbstractMediaDecoder.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef AbstractMediaDecoder_h_
 #define AbstractMediaDecoder_h_
 
 #include "mozilla/Attributes.h"
 #include "mozilla/StateMirroring.h"
 
+#include "MediaEventSource.h"
 #include "MediaInfo.h"
 #include "nsISupports.h"
 #include "nsDataHashtable.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla
 {
 
@@ -63,16 +64,25 @@ public:
   // Increments the parsed, decoded and dropped frame counters by the passed in
   // counts.
   // Can be called on any thread.
   virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
                                    uint32_t aDropped) = 0;
 
   virtual AbstractCanonical<media::NullableTimeUnit>* CanonicalDurationOrNull() { return nullptr; };
 
+  // Return an event that will be notified when data arrives in MediaResource.
+  // MediaDecoderReader will register with this event to receive notifications
+  // in order to udpate buffer ranges.
+  // Return null if this decoder doesn't support the event.
+  virtual MediaEventSource<void>* DataArrivedEvent()
+  {
+    return nullptr;
+  }
+
 protected:
   virtual void UpdateEstimatedMediaDuration(int64_t aDuration) {};
 public:
   void DispatchUpdateEstimatedMediaDuration(int64_t aDuration)
   {
     nsCOMPtr<nsIRunnable> r =
       NS_NewRunnableMethodWithArg<int64_t>(this, &AbstractMediaDecoder::UpdateEstimatedMediaDuration,
                                            aDuration);
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -1442,20 +1442,23 @@ void MediaDecoder::AddSizeOfResources(Re
     aSizes->mByteSize += GetResource()->SizeOfIncludingThis(aSizes->mMallocSizeOf);
   }
 }
 
 void
 MediaDecoder::NotifyDataArrived() {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (mDecoderStateMachine) {
-    mDecoderStateMachine->DispatchNotifyDataArrived();
+  // Don't publish events since task queues might be shutting down.
+  if (mShuttingDown) {
+    return;
   }
 
+  mDataArrivedEvent.Notify();
+
   // ReadyState computation depends on MediaDecoder::CanPlayThrough, which
   // depends on the download rate.
   UpdateReadyState();
 }
 
 // Provide access to the state machine object
 MediaDecoderStateMachine*
 MediaDecoder::GetStateMachine() const {
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -815,16 +815,21 @@ protected:
   /******
    * The following member variables can be accessed from any thread.
    ******/
 
   // Media data resource.
   RefPtr<MediaResource> mResource;
 
 private:
+  MediaEventSource<void>*
+  DataArrivedEvent() override { return &mDataArrivedEvent; }
+
+  MediaEventProducer<void> mDataArrivedEvent;
+
   // The state machine object for handling the decoding. It is safe to
   // call methods of this object from other threads. Its internal data
   // is synchronised on a monitor. The lifetime of this object is
   // after mPlayState is LOADING and before mPlayState is SHUTDOWN. It
   // is safe to access it during this period.
   //
   // Explicitly prievate to force access via accessors.
   RefPtr<MediaDecoderStateMachine> mDecoderStateMachine;
--- a/dom/media/MediaDecoderReader.cpp
+++ b/dom/media/MediaDecoderReader.cpp
@@ -74,16 +74,21 @@ MediaDecoderReader::MediaDecoderReader(A
   , mHitAudioDecodeError(false)
   , mShutdown(false)
   , mAudioDiscontinuity(false)
   , mVideoDiscontinuity(false)
 {
   MOZ_COUNT_CTOR(MediaDecoderReader);
   MOZ_ASSERT(NS_IsMainThread());
 
+  if (mDecoder && mDecoder->DataArrivedEvent()) {
+    mDataArrivedListener = mDecoder->DataArrivedEvent()->Connect(
+      mTaskQueue, this, &MediaDecoderReader::NotifyDataArrived);
+  }
+
   // Dispatch initialization that needs to happen on that task queue.
   nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(this, &MediaDecoderReader::InitializationTask);
   mTaskQueue->Dispatch(r.forget());
 }
 
 void
 MediaDecoderReader::InitializationTask()
 {
@@ -355,16 +360,18 @@ RefPtr<ShutdownPromise>
 MediaDecoderReader::Shutdown()
 {
   MOZ_ASSERT(OnTaskQueue());
   mShutdown = true;
 
   mBaseAudioPromise.RejectIfExists(END_OF_STREAM, __func__);
   mBaseVideoPromise.RejectIfExists(END_OF_STREAM, __func__);
 
+  mDataArrivedListener.DisconnectIfExists();
+
   ReleaseMediaResources();
   mDuration.DisconnectIfConnected();
   mBuffered.DisconnectAll();
 
   // Shut down the watch manager before shutting down our task queue.
   mWatchManager.Shutdown();
 
   RefPtr<ShutdownPromise> p;
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -218,25 +218,16 @@ public:
 
   // Returns the number of bytes of memory allocated by structures/frames in
   // the audio queue.
   size_t SizeOfAudioQueueInBytes() const;
 
   virtual size_t SizeOfVideoQueueInFrames();
   virtual size_t SizeOfAudioQueueInFrames();
 
-  void DispatchNotifyDataArrived()
-  {
-    RefPtr<nsRunnable> r = NS_NewRunnableMethod(
-      this, &MediaDecoderReader::NotifyDataArrived);
-
-    OwnerThread()->Dispatch(
-      r.forget(), AbstractThread::DontAssertDispatchSuccess);
-  }
-
   void NotifyDataArrived()
   {
     MOZ_ASSERT(OnTaskQueue());
     NS_ENSURE_TRUE_VOID(!mShutdown);
     NotifyDataArrivedInternal();
     UpdateBuffered();
   }
 
@@ -423,13 +414,15 @@ private:
   // of Request{Audio,Video}Data.
   MozPromiseHolder<AudioDataPromise> mBaseAudioPromise;
   MozPromiseHolder<VideoDataPromise> mBaseVideoPromise;
 
   // Flags whether a the next audio/video sample comes after a "gap" or
   // "discontinuity" in the stream. For example after a seek.
   bool mAudioDiscontinuity;
   bool mVideoDiscontinuity;
+
+  MediaEventListener mDataArrivedListener;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -163,21 +163,16 @@ public:
 
   void DispatchStartBuffering()
   {
     nsCOMPtr<nsIRunnable> runnable =
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::StartBuffering);
     OwnerThread()->Dispatch(runnable.forget());
   }
 
-  void DispatchNotifyDataArrived()
-  {
-    mReader->DispatchNotifyDataArrived();
-  }
-
   // Notifies the state machine that should minimize the number of samples
   // decoded we preroll, until playback starts. The first time playback starts
   // the state machine is free to return to prerolling normally. Note
   // "prerolling" in this context refers to when we decode and buffer decoded
   // samples in advance of when they're needed for playback.
   void DispatchMinimizePrerollUntilPlaybackStarts()
   {
     RefPtr<MediaDecoderStateMachine> self = this;
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -1224,21 +1224,21 @@ MediaStreamGraphImpl::UpdateMainThreadSt
 
   SwapMessageQueues();
   return true;
 }
 
 bool
 MediaStreamGraphImpl::OneIteration(GraphTime aStateEnd)
 {
-  MaybeProduceMemoryReport();
-
   // Process graph message from the main thread for this iteration.
   RunMessagesInQueue();
 
+  MaybeProduceMemoryReport();
+
   GraphTime stateEnd = std::min(aStateEnd, mEndTime);
   UpdateGraph(stateEnd);
 
   mStateComputedTime = stateEnd;
 
   Process();
 
   GraphTime oldProcessedTime = mProcessedTime;
--- a/dom/media/gstreamer/GStreamerAllocator.cpp
+++ b/dom/media/gstreamer/GStreamerAllocator.cpp
@@ -55,17 +55,17 @@ G_DEFINE_TYPE(MozGfxBufferPool, moz_gfx_
 
 void
 moz_gfx_memory_reset(MozGfxMemory *mem)
 {
   if (mem->image)
     mem->image->Release();
 
   ImageContainer* container = ((MozGfxMemoryAllocator*) mem->memory.allocator)->reader->GetImageContainer();
-  mem->image = reinterpret_cast<PlanarYCbCrImage*>(container->CreateImage(ImageFormat::PLANAR_YCBCR).take());
+  mem->image = container->CreatePlanarYCbCrImage().forget().take();
   mem->data = mem->image->AllocateAndGetNewBuffer(mem->memory.size);
 }
 
 static GstMemory*
 moz_gfx_memory_allocator_alloc(GstAllocator* aAllocator, gsize aSize,
     GstAllocationParams* aParams)
 {
   MozGfxMemory* mem = g_slice_new (MozGfxMemory);
--- a/dom/media/platforms/PDMFactory.cpp
+++ b/dom/media/platforms/PDMFactory.cpp
@@ -222,17 +222,17 @@ PDMFactory::CreateDecoderWithPDM(Platfor
   if (callbackWrapper && m) {
     m = new DecoderFuzzingWrapper(m.forget(), callbackWrapper.forget());
   }
 
   return m.forget();
 }
 
 bool
-PDMFactory::SupportsMimeType(const nsACString& aMimeType)
+PDMFactory::SupportsMimeType(const nsACString& aMimeType) const
 {
   if (mEMEPDM) {
     return mEMEPDM->SupportsMimeType(aMimeType);
   }
   RefPtr<PlatformDecoderModule> current = GetDecoder(aMimeType);
   return !!current;
 }
 
@@ -300,17 +300,17 @@ PDMFactory::StartupPDM(PlatformDecoderMo
   if (aPDM && NS_SUCCEEDED(aPDM->Startup())) {
     mCurrentPDMs.AppendElement(aPDM);
     return true;
   }
   return false;
 }
 
 already_AddRefed<PlatformDecoderModule>
-PDMFactory::GetDecoder(const nsACString& aMimeType)
+PDMFactory::GetDecoder(const nsACString& aMimeType) const
 {
   RefPtr<PlatformDecoderModule> pdm;
   for (auto& current : mCurrentPDMs) {
     if (current->SupportsMimeType(aMimeType)) {
       pdm = current;
       break;
     }
   }
--- a/dom/media/platforms/PDMFactory.h
+++ b/dom/media/platforms/PDMFactory.h
@@ -30,34 +30,36 @@ public:
   // This is called on the decode task queue.
   already_AddRefed<MediaDataDecoder>
   CreateDecoder(const TrackInfo& aConfig,
                 FlushableTaskQueue* aTaskQueue,
                 MediaDataDecoderCallback* aCallback,
                 layers::LayersBackend aLayersBackend = layers::LayersBackend::LAYERS_NONE,
                 layers::ImageContainer* aImageContainer = nullptr);
 
-  bool SupportsMimeType(const nsACString& aMimeType);
+  bool SupportsMimeType(const nsACString& aMimeType) const;
 
 #ifdef MOZ_EME
   // Creates a PlatformDecoderModule that uses a CDMProxy to decrypt or
   // decrypt-and-decode EME encrypted content. If the CDM only decrypts and
   // does not decode, we create a PDM and use that to create MediaDataDecoders
   // that we use on on aTaskQueue to decode the decrypted stream.
   // This is called on the decode task queue.
   void SetCDMProxy(CDMProxy* aProxy);
 #endif
 
 private:
   virtual ~PDMFactory();
   void CreatePDMs();
   // Startup the provided PDM and add it to our list if successful.
   bool StartupPDM(PlatformDecoderModule* aPDM);
   // Returns the first PDM in our list supporting the mimetype.
-  already_AddRefed<PlatformDecoderModule> GetDecoder(const nsACString& aMimeType);
+  already_AddRefed<PlatformDecoderModule>
+  GetDecoder(const nsACString& aMimeType) const;
+
   already_AddRefed<MediaDataDecoder>
   CreateDecoderWithPDM(PlatformDecoderModule* aPDM,
                        const TrackInfo& aConfig,
                        FlushableTaskQueue* aTaskQueue,
                        MediaDataDecoderCallback* aCallback,
                        layers::LayersBackend aLayersBackend,
                        layers::ImageContainer* aImageContainer);
 
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -48,17 +48,17 @@ class PlatformDecoderModule {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PlatformDecoderModule)
 
   // Perform any per-instance initialization.
   // This is called on the decode task queue.
   virtual nsresult Startup() { return NS_OK; };
 
   // Indicates if the PlatformDecoderModule supports decoding of aMimeType.
-  virtual bool SupportsMimeType(const nsACString& aMimeType) = 0;
+  virtual bool SupportsMimeType(const nsACString& aMimeType) const = 0;
 
   enum ConversionRequired {
     kNeedNone,
     kNeedAVCC,
     kNeedAnnexB,
   };
 
   // Indicates that the decoder requires a specific format.
--- a/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
@@ -7,17 +7,17 @@
 #include "AgnosticDecoderModule.h"
 #include "OpusDecoder.h"
 #include "VorbisDecoder.h"
 #include "VPXDecoder.h"
 
 namespace mozilla {
 
 bool
-AgnosticDecoderModule::SupportsMimeType(const nsACString& aMimeType)
+AgnosticDecoderModule::SupportsMimeType(const nsACString& aMimeType) const
 {
   return VPXDecoder::IsVPX(aMimeType) ||
     OpusDataDecoder::IsOpus(aMimeType) ||
     VorbisDataDecoder::IsVorbis(aMimeType);
 }
 
 already_AddRefed<MediaDataDecoder>
 AgnosticDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
--- a/dom/media/platforms/agnostic/AgnosticDecoderModule.h
+++ b/dom/media/platforms/agnostic/AgnosticDecoderModule.h
@@ -5,17 +5,17 @@
 
 namespace mozilla {
 
 class AgnosticDecoderModule : public PlatformDecoderModule {
 public:
   AgnosticDecoderModule() = default;
   virtual ~AgnosticDecoderModule() = default;
 
-  bool SupportsMimeType(const nsACString& aMimeType) override;
+  bool SupportsMimeType(const nsACString& aMimeType) const override;
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override
   {
     return ConversionRequired::kNeedNone;
   }
 
 protected:
--- a/dom/media/platforms/agnostic/BlankDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/BlankDecoderModule.cpp
@@ -241,17 +241,17 @@ public:
       new BlankMediaDataDecoder<BlankAudioDataCreator>(creator,
                                                        aAudioTaskQueue,
                                                        aCallback,
                                                        TrackInfo::kAudioTrack);
     return decoder.forget();
   }
 
   bool
-  SupportsMimeType(const nsACString& aMimeType) override
+  SupportsMimeType(const nsACString& aMimeType) const override
   {
     return true;
   }
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override
   {
     return kNeedNone;
--- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
@@ -298,16 +298,16 @@ EMEDecoderModule::DecoderNeedsConversion
   if (aConfig.IsVideo()) {
     return kNeedAVCC;
   } else {
     return kNeedNone;
   }
 }
 
 bool
-EMEDecoderModule::SupportsMimeType(const nsACString& aMimeType)
+EMEDecoderModule::SupportsMimeType(const nsACString& aMimeType) const
 {
   Maybe<nsCString> gmp;
   gmp.emplace(NS_ConvertUTF16toUTF8(mProxy->KeySystem()));
   return GMPDecoderModule::SupportsMimeType(aMimeType, gmp);
 }
 
 } // namespace mozilla
--- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.h
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.h
@@ -40,17 +40,17 @@ protected:
   CreateAudioDecoder(const AudioInfo& aConfig,
                      FlushableTaskQueue* aAudioTaskQueue,
                      MediaDataDecoderCallback* aCallback) override;
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override;
 
   bool
-  SupportsMimeType(const nsACString& aMimeType) override;
+  SupportsMimeType(const nsACString& aMimeType) const override;
 
 private:
   RefPtr<CDMProxy> mProxy;
   // Will be null if CDM has decoding capability.
   RefPtr<PDMFactory> mPDM;
   // We run the PDM on its own task queue.
   RefPtr<TaskQueue> mTaskQueue;
   bool mCDMDecodesAudio;
--- a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
@@ -223,14 +223,14 @@ GMPDecoderModule::SupportsMimeType(const
       return true;
     }
   }
 
   return false;
 }
 
 bool
-GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType)
+GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType) const
 {
   return SupportsMimeType(aMimeType, PreferredGMP(aMimeType));
 }
 
 } // namespace mozilla
--- a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h
+++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h
@@ -31,17 +31,17 @@ public:
   CreateAudioDecoder(const AudioInfo& aConfig,
                      FlushableTaskQueue* aAudioTaskQueue,
                      MediaDataDecoderCallback* aCallback) override;
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override;
 
   bool
-  SupportsMimeType(const nsACString& aMimeType) override;
+  SupportsMimeType(const nsACString& aMimeType) const override;
 
   // Main thread only.
   static void Init();
 
   static const Maybe<nsCString> PreferredGMP(const nsACString& aMimeType);
 
   static bool SupportsMimeType(const nsACString& aMimeType,
                                const Maybe<nsCString>& aGMP);
--- a/dom/media/platforms/android/AndroidDecoderModule.cpp
+++ b/dom/media/platforms/android/AndroidDecoderModule.cpp
@@ -13,127 +13,156 @@
 
 #include "MediaData.h"
 #include "MediaInfo.h"
 
 #include "nsThreadUtils.h"
 #include "nsAutoPtr.h"
 #include "nsPromiseFlatString.h"
 
+#include "prlog.h"
+
 #include <jni.h>
 
+static PRLogModuleInfo* AndroidDecoderModuleLog()
+{
+  static PRLogModuleInfo* sLogModule = nullptr;
+  if (!sLogModule) {
+    sLogModule = PR_NewLogModule("AndroidDecoderModule");
+  }
+  return sLogModule;
+}
+
+#undef LOG
+#define LOG(arg, ...) MOZ_LOG(AndroidDecoderModuleLog(), \
+    mozilla::LogLevel::Debug, ("AndroidDecoderModule(%p)::%s: " arg, \
+      this, __func__, ##__VA_ARGS__))
+
 using namespace mozilla;
 using namespace mozilla::gl;
 using namespace mozilla::widget::sdk;
+using media::TimeUnit;
 
 namespace mozilla {
 
 #define INVOKE_CALLBACK(Func, ...) \
   if (mCallback) { \
     mCallback->Func(__VA_ARGS__); \
   } else { \
-    NS_WARNING("callback not set"); \
+    NS_WARNING("Callback not set"); \
   }
 
-static const char* TranslateMimeType(const nsACString& aMimeType)
+static const char*
+TranslateMimeType(const nsACString& aMimeType)
 {
   if (aMimeType.EqualsLiteral("video/webm; codecs=vp8")) {
     return "video/x-vnd.on2.vp8";
   } else if (aMimeType.EqualsLiteral("video/webm; codecs=vp9")) {
     return "video/x-vnd.on2.vp9";
   }
   return PromiseFlatCString(aMimeType).get();
 }
 
-static MediaCodec::LocalRef CreateDecoder(const nsACString& aMimeType)
+static MediaCodec::LocalRef
+CreateDecoder(const nsACString& aMimeType)
 {
   MediaCodec::LocalRef codec;
   NS_ENSURE_SUCCESS(MediaCodec::CreateDecoderByType(TranslateMimeType(aMimeType),
                     &codec), nullptr);
   return codec;
 }
 
-class VideoDataDecoder : public MediaCodecDataDecoder {
+class VideoDataDecoder : public MediaCodecDataDecoder
+{
 public:
   VideoDataDecoder(const VideoInfo& aConfig,
-                   MediaFormat::Param aFormat, MediaDataDecoderCallback* aCallback,
+                   MediaFormat::Param aFormat,
+                   MediaDataDecoderCallback* aCallback,
                    layers::ImageContainer* aImageContainer)
-    : MediaCodecDataDecoder(MediaData::Type::VIDEO_DATA, aConfig.mMimeType, aFormat, aCallback)
+    : MediaCodecDataDecoder(MediaData::Type::VIDEO_DATA, aConfig.mMimeType,
+                            aFormat, aCallback)
     , mImageContainer(aImageContainer)
     , mConfig(aConfig)
   {
 
   }
 
-  RefPtr<InitPromise> Init() override {
+  RefPtr<InitPromise> Init() override
+  {
     mSurfaceTexture = AndroidSurfaceTexture::Create();
     if (!mSurfaceTexture) {
       NS_WARNING("Failed to create SurfaceTexture for video decode\n");
       return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
     }
 
     if (NS_FAILED(InitDecoder(mSurfaceTexture->JavaSurface()))) {
       return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
     }
 
     return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
   }
 
-  void Cleanup() override {
+  void Cleanup() override
+  {
     mGLContext = nullptr;
   }
 
-  nsresult Input(MediaRawData* aSample) override {
+  nsresult Input(MediaRawData* aSample) override
+  {
     return MediaCodecDataDecoder::Input(aSample);
   }
 
-  bool WantCopy() {
+  bool WantCopy() const
+  {
     // Allocating a texture is incredibly slow on PowerVR and may fail on
     // emulators, see bug 1190379.
     return mGLContext->Vendor() != GLVendor::Imagination &&
            mGLContext->Renderer() != GLRenderer::AndroidEmulator;
   }
 
-  EGLImage CopySurface(layers::Image* img) {
+  EGLImage CopySurface(layers::Image* img)
+  {
     mGLContext->MakeCurrent();
 
     GLuint tex = CreateTextureForOffscreen(mGLContext, mGLContext->GetGLFormats(),
                                            img->GetSize());
 
     auto helper = mGLContext->BlitHelper();
     const gl::OriginPos destOrigin = gl::OriginPos::TopLeft;
-    if (!helper->BlitImageToTexture(img, img->GetSize(), tex, LOCAL_GL_TEXTURE_2D,
-                                    destOrigin))
+    if (!helper->BlitImageToTexture(img, img->GetSize(), tex,
+                                    LOCAL_GL_TEXTURE_2D, destOrigin))
     {
       mGLContext->fDeleteTextures(1, &tex);
       return nullptr;
     }
 
     EGLint attribs[] = {
       LOCAL_EGL_IMAGE_PRESERVED_KHR, LOCAL_EGL_TRUE,
       LOCAL_EGL_NONE, LOCAL_EGL_NONE
     };
 
     EGLContext eglContext = static_cast<GLContextEGL*>(mGLContext.get())->mContext;
-    EGLImage eglImage = sEGLLibrary.fCreateImage(EGL_DISPLAY(), eglContext,
-                                                 LOCAL_EGL_GL_TEXTURE_2D_KHR,
-                                                 (EGLClientBuffer)tex, attribs);
+    EGLImage eglImage = sEGLLibrary.fCreateImage(
+        EGL_DISPLAY(), eglContext, LOCAL_EGL_GL_TEXTURE_2D_KHR,
+        reinterpret_cast<EGLClientBuffer>(tex), attribs);
     mGLContext->fDeleteTextures(1, &tex);
 
     return eglImage;
   }
 
   nsresult PostOutput(BufferInfo::Param aInfo, MediaFormat::Param aFormat,
-                      const media::TimeUnit& aDuration) override {
+                      const TimeUnit& aDuration) override
+  {
     if (!EnsureGLContext()) {
       return NS_ERROR_FAILURE;
     }
 
     RefPtr<layers::Image> img =
-      new SurfaceTextureImage(mSurfaceTexture.get(), mConfig.mDisplay, gl::OriginPos::BottomLeft);
+      new SurfaceTextureImage(mSurfaceTexture.get(), mConfig.mDisplay,
+                              gl::OriginPos::BottomLeft);
 
     if (WantCopy()) {
       EGLImage eglImage = CopySurface(img);
       if (!eglImage) {
         return NS_ERROR_FAILURE;
       }
 
       EGLSync eglSync = nullptr;
@@ -145,20 +174,18 @@ public:
                                           LOCAL_EGL_SYNC_FENCE,
                                           nullptr);
         MOZ_ASSERT(eglSync);
         mGLContext->fFlush();
       } else {
         NS_WARNING("No EGL fence support detected, rendering artifacts may occur!");
       }
 
-      img = new layers::EGLImageImage(
-        eglImage, eglSync,
-        mConfig.mDisplay, gl::OriginPos::TopLeft,
-        true /* owns */);
+      img = new layers::EGLImageImage(eglImage, eglSync, mConfig.mDisplay,
+                                      gl::OriginPos::TopLeft, true /* owns */);
     }
 
     nsresult rv;
     int32_t flags;
     NS_ENSURE_SUCCESS(rv = aInfo->Flags(&flags), rv);
 
     bool isSync = !!(flags & MediaCodec::BUFFER_FLAG_SYNC_FRAME);
 
@@ -180,99 +207,106 @@ public:
                                  gfx::IntRect(0, 0,
                                               mConfig.mDisplay.width,
                                               mConfig.mDisplay.height));
     INVOKE_CALLBACK(Output, v);
     return NS_OK;
   }
 
 protected:
-  bool EnsureGLContext() {
+  bool EnsureGLContext()
+  {
     if (mGLContext) {
       return true;
     }
 
     mGLContext = GLContextProvider::CreateHeadless(CreateContextFlags::NONE);
     return mGLContext;
   }
 
   layers::ImageContainer* mImageContainer;
   const VideoInfo& mConfig;
   RefPtr<AndroidSurfaceTexture> mSurfaceTexture;
   RefPtr<GLContext> mGLContext;
 };
 
-class AudioDataDecoder : public MediaCodecDataDecoder {
-
+class AudioDataDecoder : public MediaCodecDataDecoder
+{
 public:
-  AudioDataDecoder(const AudioInfo& aConfig, MediaFormat::Param aFormat, MediaDataDecoderCallback* aCallback)
-    : MediaCodecDataDecoder(MediaData::Type::AUDIO_DATA, aConfig.mMimeType, aFormat, aCallback)
+  AudioDataDecoder(const AudioInfo& aConfig, MediaFormat::Param aFormat,
+                   MediaDataDecoderCallback* aCallback)
+    : MediaCodecDataDecoder(MediaData::Type::AUDIO_DATA, aConfig.mMimeType,
+                            aFormat, aCallback)
   {
     JNIEnv* const env = jni::GetEnvForThread();
 
     jni::Object::LocalRef buffer(env);
-    NS_ENSURE_SUCCESS_VOID(aFormat->GetByteBuffer(NS_LITERAL_STRING("csd-0"), &buffer));
+    NS_ENSURE_SUCCESS_VOID(aFormat->GetByteBuffer(NS_LITERAL_STRING("csd-0"),
+                                                  &buffer));
 
     if (!buffer && aConfig.mCodecSpecificConfig->Length() >= 2) {
-      buffer = jni::Object::LocalRef::Adopt(env, env->NewDirectByteBuffer(aConfig.mCodecSpecificConfig->Elements(),
-                                                                          aConfig.mCodecSpecificConfig->Length()));
-      NS_ENSURE_SUCCESS_VOID(aFormat->SetByteBuffer(NS_LITERAL_STRING("csd-0"), buffer));
+      buffer = jni::Object::LocalRef::Adopt(
+          env, env->NewDirectByteBuffer(aConfig.mCodecSpecificConfig->Elements(),
+          aConfig.mCodecSpecificConfig->Length()));
+      NS_ENSURE_SUCCESS_VOID(aFormat->SetByteBuffer(NS_LITERAL_STRING("csd-0"),
+                                                    buffer));
     }
   }
 
   nsresult Output(BufferInfo::Param aInfo, void* aBuffer,
-                  MediaFormat::Param aFormat,
-                  const media::TimeUnit& aDuration) {
+                  MediaFormat::Param aFormat, const TimeUnit& aDuration)
+  {
     // The output on Android is always 16-bit signed
-
     nsresult rv;
     int32_t numChannels;
     NS_ENSURE_SUCCESS(rv =
         aFormat->GetInteger(NS_LITERAL_STRING("channel-count"), &numChannels), rv);
 
     int32_t sampleRate;
     NS_ENSURE_SUCCESS(rv =
         aFormat->GetInteger(NS_LITERAL_STRING("sample-rate"), &sampleRate), rv);
 
     int32_t size;
     NS_ENSURE_SUCCESS(rv = aInfo->Size(&size), rv);
 
     int32_t offset;
     NS_ENSURE_SUCCESS(rv = aInfo->Offset(&offset), rv);
 
 #ifdef MOZ_SAMPLE_TYPE_S16
-    int32_t numSamples = size / 2;
+    const int32_t numSamples = size / 2;
 #else
 #error We only support 16-bit integer PCM
 #endif
 
     const int32_t numFrames = numSamples / numChannels;
     auto audio = MakeUnique<AudioDataValue[]>(numSamples);
 
-    uint8_t* bufferStart = static_cast<uint8_t*>(aBuffer) + offset;
-    PodCopy(audio.get(), reinterpret_cast<AudioDataValue*>(bufferStart), numSamples);
+    const uint8_t* bufferStart = static_cast<uint8_t*>(aBuffer) + offset;
+    PodCopy(audio.get(), reinterpret_cast<const AudioDataValue*>(bufferStart),
+            numSamples);
 
     int64_t presentationTimeUs;
     NS_ENSURE_SUCCESS(rv = aInfo->PresentationTimeUs(&presentationTimeUs), rv);
 
     RefPtr<AudioData> data = new AudioData(0, presentationTimeUs,
                                            aDuration.ToMicroseconds(),
                                            numFrames,
                                            Move(audio),
                                            numChannels,
                                            sampleRate);
     INVOKE_CALLBACK(Output, data);
     return NS_OK;
   }
 };
 
-
-bool AndroidDecoderModule::SupportsMimeType(const nsACString& aMimeType)
+bool
+AndroidDecoderModule::SupportsMimeType(const nsACString& aMimeType) const
 {
-  if (!AndroidBridge::Bridge() || (AndroidBridge::Bridge()->GetAPIVersion() < 16)) {
+  if (!AndroidBridge::Bridge() ||
+      (AndroidBridge::Bridge()->GetAPIVersion() < 16)) {
     return false;
   }
 
   if (aMimeType.EqualsLiteral("video/mp4") ||
       aMimeType.EqualsLiteral("video/avc")) {
     return true;
   }
 
@@ -281,105 +315,102 @@ bool AndroidDecoderModule::SupportsMimeT
     return false;
   }
   ref->Release();
   return true;
 }
 
 already_AddRefed<MediaDataDecoder>
 AndroidDecoderModule::CreateVideoDecoder(
-                                const VideoInfo& aConfig,
-                                layers::LayersBackend aLayersBackend,
-                                layers::ImageContainer* aImageContainer,
-                                FlushableTaskQueue* aVideoTaskQueue,
-                                MediaDataDecoderCallback* aCallback)
+    const VideoInfo& aConfig, layers::LayersBackend aLayersBackend,
+    layers::ImageContainer* aImageContainer, FlushableTaskQueue* aVideoTaskQueue,
+    MediaDataDecoderCallback* aCallback)
 {
   MediaFormat::LocalRef format;
 
   NS_ENSURE_SUCCESS(MediaFormat::CreateVideoFormat(
       TranslateMimeType(aConfig.mMimeType),
       aConfig.mDisplay.width,
       aConfig.mDisplay.height,
       &format), nullptr);
 
   RefPtr<MediaDataDecoder> decoder =
     new VideoDataDecoder(aConfig, format, aCallback, aImageContainer);
 
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
-AndroidDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig,
-                                         FlushableTaskQueue* aAudioTaskQueue,
-                                         MediaDataDecoderCallback* aCallback)
+AndroidDecoderModule::CreateAudioDecoder(
+    const AudioInfo& aConfig, FlushableTaskQueue* aAudioTaskQueue,
+    MediaDataDecoderCallback* aCallback)
 {
   MOZ_ASSERT(aConfig.mBitDepth == 16, "We only handle 16-bit audio!");
 
   MediaFormat::LocalRef format;
 
   NS_ENSURE_SUCCESS(MediaFormat::CreateAudioFormat(
       aConfig.mMimeType,
       aConfig.mBitDepth,
       aConfig.mChannels,
       &format), nullptr);
 
   RefPtr<MediaDataDecoder> decoder =
     new AudioDataDecoder(aConfig, format, aCallback);
 
   return decoder.forget();
-
 }
 
 PlatformDecoderModule::ConversionRequired
 AndroidDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
 {
   if (aConfig.IsVideo()) {
     return kNeedAnnexB;
-  } else {
-    return kNeedNone;
   }
+  return kNeedNone;
 }
 
 MediaCodecDataDecoder::MediaCodecDataDecoder(MediaData::Type aType,
                                              const nsACString& aMimeType,
                                              MediaFormat::Param aFormat,
                                              MediaDataDecoderCallback* aCallback)
   : mType(aType)
   , mMimeType(aMimeType)
   , mFormat(aFormat)
   , mCallback(aCallback)
   , mInputBuffers(nullptr)
   , mOutputBuffers(nullptr)
   , mMonitor("MediaCodecDataDecoder::mMonitor")
-  , mFlushing(false)
-  , mDraining(false)
-  , mStopping(false)
+  , mState(kDecoding)
 {
 
 }
 
 MediaCodecDataDecoder::~MediaCodecDataDecoder()
 {
   Shutdown();
 }
 
-RefPtr<MediaDataDecoder::InitPromise> MediaCodecDataDecoder::Init()
+RefPtr<MediaDataDecoder::InitPromise>
+MediaCodecDataDecoder::Init()
 {
   nsresult rv = InitDecoder(nullptr);
 
   TrackInfo::TrackType type =
     (mType == MediaData::AUDIO_DATA ? TrackInfo::TrackType::kAudioTrack
                                     : TrackInfo::TrackType::kVideoTrack);
 
   return NS_SUCCEEDED(rv) ?
            InitPromise::CreateAndResolve(type, __func__) :
-           InitPromise::CreateAndReject(MediaDataDecoder::DecoderFailureReason::INIT_ERROR, __func__);
+           InitPromise::CreateAndReject(
+               MediaDataDecoder::DecoderFailureReason::INIT_ERROR, __func__);
 }
 
-nsresult MediaCodecDataDecoder::InitDecoder(Surface::Param aSurface)
+nsresult
+MediaCodecDataDecoder::InitDecoder(Surface::Param aSurface)
 {
   mDecoder = CreateDecoder(mMimeType);
   if (!mDecoder) {
     INVOKE_CALLBACK(Error);
     return NS_ERROR_FAILURE;
   }
 
   nsresult rv;
@@ -390,297 +421,395 @@ nsresult MediaCodecDataDecoder::InitDeco
   NS_ENSURE_SUCCESS(rv = ResetOutputBuffers(), rv);
 
   NS_NewNamedThread("MC Decoder", getter_AddRefs(mThread),
                     NS_NewRunnableMethod(this, &MediaCodecDataDecoder::DecoderLoop));
 
   return NS_OK;
 }
 
-// This is in usec, so that's 10ms
-#define DECODER_TIMEOUT 10000
+// This is in usec, so that's 10ms.
+static const int64_t kDecoderTimeout = 10000;
 
-#define HANDLE_DECODER_ERROR() \
+#define BREAK_ON_DECODER_ERROR() \
   if (NS_FAILED(res)) { \
-    NS_WARNING("exiting decoder loop due to exception"); \
-    if (mDraining) { \
+    NS_WARNING("Exiting decoder loop due to exception"); \
+    if (State() == kDrainDecoder) { \
       INVOKE_CALLBACK(DrainComplete); \
-      mDraining = false; \
+      State(kDecoding); \
     } \
     INVOKE_CALLBACK(Error); \
     break; \
   }
 
-nsresult MediaCodecDataDecoder::GetInputBuffer(JNIEnv* env, int index, jni::Object::LocalRef* buffer)
+nsresult
+MediaCodecDataDecoder::GetInputBuffer(
+    JNIEnv* aEnv, int aIndex, jni::Object::LocalRef* aBuffer)
 {
-  bool retried = false;
-  while (!*buffer) {
-    *buffer = jni::Object::LocalRef::Adopt(env->GetObjectArrayElement(mInputBuffers.Get(), index));
-    if (!*buffer) {
-      if (!retried) {
-        // Reset the input buffers and then try again
-        nsresult res = ResetInputBuffers();
-        if (NS_FAILED(res)) {
-          return res;
-        }
-        retried = true;
-      } else {
-        // We already tried resetting the input buffers, return an error
-        return NS_ERROR_FAILURE;
-      }
+  MOZ_ASSERT(aEnv);
+  MOZ_ASSERT(!*aBuffer);
+
+  int numTries = 2;
+
+  while (numTries--) {
+    *aBuffer = jni::Object::LocalRef::Adopt(
+        aEnv->GetObjectArrayElement(mInputBuffers.Get(), aIndex));
+    if (*aBuffer) {
+      return NS_OK;
+    }
+    nsresult res = ResetInputBuffers();
+    if (NS_FAILED(res)) {
+      return res;
     }
   }
+  return NS_ERROR_FAILURE;
+}
+
+bool
+MediaCodecDataDecoder::WaitForInput()
+{
+  MonitorAutoLock lock(mMonitor);
+
+  while (State() == kDecoding && mQueue.empty()) {
+    // Signal that we require more input.
+    INVOKE_CALLBACK(InputExhausted);
+    lock.Wait();
+  }
+
+  return State() != kStopping;
+}
+
+
+MediaRawData*
+MediaCodecDataDecoder::PeekNextSample()
+{
+  MonitorAutoLock lock(mMonitor);
+
+  if (State() == kFlushing) {
+    mDecoder->Flush();
+    ClearQueue();
+    State(kDecoding);
+    lock.Notify();
+    return nullptr;
+  }
+
+  if (mQueue.empty()) {
+    if (State() == kDrainQueue) {
+      State(kDrainDecoder);
+    }
+    return nullptr;
+  }
+
+  // We're not stopping or flushing, so try to get a sample.
+  return mQueue.front();
+}
+
+nsresult
+MediaCodecDataDecoder::QueueSample(const MediaRawData* aSample)
+{
+  MOZ_ASSERT(aSample);
+  AutoLocalJNIFrame frame(jni::GetEnvForThread(), 1);
+
+  // We have a sample, try to feed it to the decoder.
+  int32_t inputIndex = -1;
+  nsresult res = mDecoder->DequeueInputBuffer(kDecoderTimeout, &inputIndex);
+  if (NS_FAILED(res)) {
+    return res;
+  }
+
+  if (inputIndex < 0) {
+    // There is no valid input buffer available.
+    return NS_ERROR_FAILURE;
+  }
+
+  jni::Object::LocalRef buffer(frame.GetEnv());
+  res = GetInputBuffer(frame.GetEnv(), inputIndex, &buffer);
+  if (NS_FAILED(res)) {
+    return res;
+  }
+
+  void* directBuffer = frame.GetEnv()->GetDirectBufferAddress(buffer.Get());
+
+  MOZ_ASSERT(frame.GetEnv()->GetDirectBufferCapacity(buffer.Get()) >=
+             aSample->Size(),
+             "Decoder buffer is not large enough for sample");
+
+  PodCopy(static_cast<uint8_t*>(directBuffer), aSample->Data(), aSample->Size());
+
+  res = mDecoder->QueueInputBuffer(inputIndex, 0, aSample->Size(),
+                                   aSample->mTime, 0);
+  if (NS_FAILED(res)) {
+    return res;
+  }
+
+  mDurations.push(TimeUnit::FromMicroseconds(aSample->mDuration));
+  return NS_OK;
+}
+
+nsresult
+MediaCodecDataDecoder::QueueEOS()
+{
+  mMonitor.AssertCurrentThreadOwns();
+
+  nsresult res = NS_OK;
+  int32_t inputIndex = -1;
+  res = mDecoder->DequeueInputBuffer(kDecoderTimeout, &inputIndex);
+  if (NS_FAILED(res) || inputIndex < 0) {
+    return res;
+  }
+
+  res = mDecoder->QueueInputBuffer(inputIndex, 0, 0, 0,
+                                   MediaCodec::BUFFER_FLAG_END_OF_STREAM);
+  if (NS_SUCCEEDED(res)) {
+    State(kDrainWaitEOS);
+    mMonitor.Notify();
+  }
+  return res;
+}
+
+void
+MediaCodecDataDecoder::HandleEOS(int32_t aOutputStatus)
+{
+  MonitorAutoLock lock(mMonitor);
+
+  if (State() == kDrainWaitEOS) {
+    State(kDecoding);
+    mMonitor.Notify();
+
+    INVOKE_CALLBACK(DrainComplete);
+  }
+
+  mDecoder->ReleaseOutputBuffer(aOutputStatus, false);
+}
+
+TimeUnit
+MediaCodecDataDecoder::GetOutputDuration()
+{
+  MOZ_ASSERT(!mDurations.empty(), "Should have had a duration queued");
+  const TimeUnit duration = mDurations.front();
+  mDurations.pop();
+  return duration;
+}
+
+nsresult
+MediaCodecDataDecoder::ProcessOutput(
+    BufferInfo::Param aInfo, MediaFormat::Param aFormat, int32_t aStatus)
+{
+  AutoLocalJNIFrame frame(jni::GetEnvForThread(), 1);
+
+  const TimeUnit duration = GetOutputDuration();
+  const auto buffer = jni::Object::LocalRef::Adopt(
+      frame.GetEnv()->GetObjectArrayElement(mOutputBuffers.Get(), aStatus));
+
+  if (buffer) {
+    // The buffer will be null on Android L if we are decoding to a Surface.
+    void* directBuffer = frame.GetEnv()->GetDirectBufferAddress(buffer.Get());
+    Output(aInfo, directBuffer, aFormat, duration);
+  }
+
+  // The Surface will be updated at this point (for video).
+  mDecoder->ReleaseOutputBuffer(aStatus, true);
+  PostOutput(aInfo, aFormat, duration);
 
   return NS_OK;
 }
 
-void MediaCodecDataDecoder::DecoderLoop()
+void
+MediaCodecDataDecoder::DecoderLoop()
 {
-  bool outputDone = false;
-
-  bool draining = false;
-  bool waitingEOF = false;
-
+  bool isOutputDone = false;
   AutoLocalJNIFrame frame(jni::GetEnvForThread(), 1);
   RefPtr<MediaRawData> sample;
+  MediaFormat::LocalRef outputFormat(frame.GetEnv());
+  nsresult res = NS_OK;
 
-  MediaFormat::LocalRef outputFormat(frame.GetEnv());
-  nsresult res;
+  while (WaitForInput()) {
+    sample = PeekNextSample();
 
-  for (;;) {
     {
       MonitorAutoLock lock(mMonitor);
-      while (!mStopping && !mDraining && !mFlushing && mQueue.empty()) {
-        if (mQueue.empty()) {
-          // We could be waiting here forever if we don't signal that we need more input
-          INVOKE_CALLBACK(InputExhausted);
-        }
-        lock.Wait();
-      }
-
-      if (mStopping) {
-        // Get out of the loop. This is the only exit point.
-        break;
-      }
-
-      if (mFlushing) {
-        mDecoder->Flush();
-        ClearQueue();
-        mFlushing =  false;
-        lock.Notify();
-        continue;
-      }
-
-      // We're not stopping or draining, so try to get a sample
-      if (!mQueue.empty()) {
-        sample = mQueue.front();
-      }
-
-      if (mDraining && !sample && !waitingEOF) {
-        draining = true;
-      }
-    }
-
-    if (draining && !waitingEOF) {
-      MOZ_ASSERT(!sample, "Shouldn't have a sample when pushing EOF frame");
-
-      int32_t inputIndex;
-      res = mDecoder->DequeueInputBuffer(DECODER_TIMEOUT, &inputIndex);
-      HANDLE_DECODER_ERROR();
-
-      if (inputIndex >= 0) {
-        res = mDecoder->QueueInputBuffer(inputIndex, 0, 0, 0, MediaCodec::BUFFER_FLAG_END_OF_STREAM);
-        HANDLE_DECODER_ERROR();
-
-        waitingEOF = true;
+      if (State() == kDrainDecoder) {
+        MOZ_ASSERT(!sample, "Shouldn't have a sample when pushing EOF frame");
+        res = QueueEOS();
+        BREAK_ON_DECODER_ERROR();
       }
     }
 
     if (sample) {
-      // We have a sample, try to feed it to the decoder
-      int inputIndex;
-      res = mDecoder->DequeueInputBuffer(DECODER_TIMEOUT, &inputIndex);
-      HANDLE_DECODER_ERROR();
-
-      if (inputIndex >= 0) {
-        jni::Object::LocalRef buffer(frame.GetEnv());
-        res = GetInputBuffer(frame.GetEnv(), inputIndex, &buffer);
-        HANDLE_DECODER_ERROR();
-
-        void* directBuffer = frame.GetEnv()->GetDirectBufferAddress(buffer.Get());
-
-        MOZ_ASSERT(frame.GetEnv()->GetDirectBufferCapacity(buffer.Get()) >= sample->Size(),
-          "Decoder buffer is not large enough for sample");
-
-        {
-          // We're feeding this to the decoder, so remove it from the queue
-          MonitorAutoLock lock(mMonitor);
-          mQueue.pop();
-        }
-
-        PodCopy((uint8_t*)directBuffer, sample->Data(), sample->Size());
-
-        res = mDecoder->QueueInputBuffer(inputIndex, 0, sample->Size(),
-                                         sample->mTime, 0);
-        HANDLE_DECODER_ERROR();
-
-        mDurations.push(media::TimeUnit::FromMicroseconds(sample->mDuration));
-        sample = nullptr;
-        outputDone = false;
+      res = QueueSample(sample);
+      if (NS_SUCCEEDED(res)) {
+        // We've fed this into the decoder, so remove it from the queue.
+        MonitorAutoLock lock(mMonitor);
+        mQueue.pop();
+        isOutputDone = false;
       }
     }
 
-    if (!outputDone) {
-      BufferInfo::LocalRef bufferInfo;
-      res = BufferInfo::New(&bufferInfo);
-      HANDLE_DECODER_ERROR();
-
-      int32_t outputStatus;
-      res = mDecoder->DequeueOutputBuffer(bufferInfo, DECODER_TIMEOUT, &outputStatus);
-      HANDLE_DECODER_ERROR();
+    if (isOutputDone) {
+      continue;
+    }
 
-      if (outputStatus == MediaCodec::INFO_TRY_AGAIN_LATER) {
-        // We might want to call mCallback->InputExhausted() here, but there seems to be
-        // some possible bad interactions here with the threading
-      } else if (outputStatus == MediaCodec::INFO_OUTPUT_BUFFERS_CHANGED) {
-        res = ResetOutputBuffers();
-        HANDLE_DECODER_ERROR();
-      } else if (outputStatus == MediaCodec::INFO_OUTPUT_FORMAT_CHANGED) {
-        res = mDecoder->GetOutputFormat(ReturnTo(&outputFormat));
-        HANDLE_DECODER_ERROR();
-      } else if (outputStatus < 0) {
-        NS_WARNING("unknown error from decoder!");
-        INVOKE_CALLBACK(Error);
+    BufferInfo::LocalRef bufferInfo;
+    nsresult res = BufferInfo::New(&bufferInfo);
+    BREAK_ON_DECODER_ERROR();
 
-        // Don't break here just in case it's recoverable. If it's not, others stuff will fail later and
-        // we'll bail out.
-      } else {
-        int32_t flags;
-        res = bufferInfo->Flags(&flags);
-        HANDLE_DECODER_ERROR();
-
-        // We have a valid buffer index >= 0 here
-        if (flags & MediaCodec::BUFFER_FLAG_END_OF_STREAM) {
-          if (draining) {
-            draining = false;
-            waitingEOF = false;
+    int32_t outputStatus = -1;
+    res = mDecoder->DequeueOutputBuffer(bufferInfo, kDecoderTimeout,
+                                        &outputStatus);
+    BREAK_ON_DECODER_ERROR();
 
-            mMonitor.Lock();
-            mDraining = false;
-            mMonitor.Notify();
-            mMonitor.Unlock();
-
-            INVOKE_CALLBACK(DrainComplete);
-          }
-
-          mDecoder->ReleaseOutputBuffer(outputStatus, false);
-          outputDone = true;
-
-          // We only queue empty EOF frames, so we're done for now
-          continue;
-        }
-
-        MOZ_ASSERT(!mDurations.empty(), "Should have had a duration queued");
+    if (outputStatus == MediaCodec::INFO_TRY_AGAIN_LATER) {
+      // We might want to call mCallback->InputExhausted() here, but there seems
+      // to be some possible bad interactions here with the threading.
+    } else if (outputStatus == MediaCodec::INFO_OUTPUT_BUFFERS_CHANGED) {
+      res = ResetOutputBuffers();
+      BREAK_ON_DECODER_ERROR();
+    } else if (outputStatus == MediaCodec::INFO_OUTPUT_FORMAT_CHANGED) {
+      res = mDecoder->GetOutputFormat(ReturnTo(&outputFormat));
+      BREAK_ON_DECODER_ERROR();
+    } else if (outputStatus < 0) {
+      NS_WARNING("Unknown error from decoder!");
+      INVOKE_CALLBACK(Error);
+      // Don't break here just in case it's recoverable. If it's not, other
+      // stuff will fail later and we'll bail out.
+    } else {
+      // We have a valid buffer index >= 0 here.
+      int32_t flags;
+      nsresult res = bufferInfo->Flags(&flags);
+      BREAK_ON_DECODER_ERROR();
 
-        media::TimeUnit duration;
-        if (!mDurations.empty()) {
-          duration = mDurations.front();
-          mDurations.pop();
-        }
+      if (flags & MediaCodec::BUFFER_FLAG_END_OF_STREAM) {
+        HandleEOS(outputStatus);
+        isOutputDone = true;
+        // We only queue empty EOF frames, so we're done for now.
+        continue;
+      }
 
-        auto buffer = jni::Object::LocalRef::Adopt(
-            frame.GetEnv()->GetObjectArrayElement(mOutputBuffers.Get(), outputStatus));
-        if (buffer) {
-          // The buffer will be null on Android L if we are decoding to a Surface
-          void* directBuffer = frame.GetEnv()->GetDirectBufferAddress(buffer.Get());
-          Output(bufferInfo, directBuffer, outputFormat, duration);
-        }
-
-        // The Surface will be updated at this point (for video)
-        mDecoder->ReleaseOutputBuffer(outputStatus, true);
-
-        PostOutput(bufferInfo, outputFormat, duration);
-      }
+      res = ProcessOutput(bufferInfo, outputFormat, outputStatus);
+      BREAK_ON_DECODER_ERROR();
     }
   }
 
   Cleanup();
 
-  // We're done
+  // We're done.
   MonitorAutoLock lock(mMonitor);
-  mStopping = false;
+  State(kShutdown);
   mMonitor.Notify();
 }
 
-void MediaCodecDataDecoder::ClearQueue()
+const char*
+MediaCodecDataDecoder::ModuleStateStr(ModuleState aState) {
+  static const char* kStr[] = {
+    "Decoding", "Flushing", "DrainQueue", "DrainDecoder", "DrainWaitEOS",
+    "Stopping", "Shutdown"
+  };
+
+  MOZ_ASSERT(aState < sizeof(kStr) / sizeof(kStr[0]));
+  return kStr[aState];
+}
+
+MediaCodecDataDecoder::ModuleState
+MediaCodecDataDecoder::State() const
+{
+  return mState;
+}
+
+void
+MediaCodecDataDecoder::State(ModuleState aState)
+{
+  LOG("%s -> %s", ModuleStateStr(mState), ModuleStateStr(aState));
+
+  if (aState == kDrainDecoder) {
+    MOZ_ASSERT(mState == kDrainQueue);
+  } else if (aState == kDrainWaitEOS) {
+    MOZ_ASSERT(mState == kDrainDecoder);
+  }
+
+  mState = aState;
+}
+
+void
+MediaCodecDataDecoder::ClearQueue()
 {
   mMonitor.AssertCurrentThreadOwns();
+
   while (!mQueue.empty()) {
     mQueue.pop();
   }
   while (!mDurations.empty()) {
     mDurations.pop();
   }
 }
 
-nsresult MediaCodecDataDecoder::Input(MediaRawData* aSample) {
+nsresult
+MediaCodecDataDecoder::Input(MediaRawData* aSample)
+{
   MonitorAutoLock lock(mMonitor);
   mQueue.push(aSample);
   lock.NotifyAll();
 
   return NS_OK;
 }
 
-nsresult MediaCodecDataDecoder::ResetInputBuffers()
+nsresult
+MediaCodecDataDecoder::ResetInputBuffers()
 {
   return mDecoder->GetInputBuffers(ReturnTo(&mInputBuffers));
 }
 
-nsresult MediaCodecDataDecoder::ResetOutputBuffers()
+nsresult
+MediaCodecDataDecoder::ResetOutputBuffers()
 {
   return mDecoder->GetOutputBuffers(ReturnTo(&mOutputBuffers));
 }
 
-nsresult MediaCodecDataDecoder::Flush() {
+nsresult
+MediaCodecDataDecoder::Flush()
+{
   MonitorAutoLock lock(mMonitor);
-  mFlushing = true;
+  State(kFlushing);
   lock.Notify();
 
-  while (mFlushing) {
+  while (State() == kFlushing) {
     lock.Wait();
   }
 
   return NS_OK;
 }
 
-nsresult MediaCodecDataDecoder::Drain() {
+nsresult
+MediaCodecDataDecoder::Drain()
+{
   MonitorAutoLock lock(mMonitor);
-  if (mDraining) {
+  if (State() == kDrainDecoder || State() == kDrainQueue) {
     return NS_OK;
   }
 
-  mDraining = true;
+  State(kDrainQueue);
   lock.Notify();
 
   return NS_OK;
 }
 
 
-nsresult MediaCodecDataDecoder::Shutdown() {
+nsresult
+MediaCodecDataDecoder::Shutdown()
+{
   MonitorAutoLock lock(mMonitor);
 
-  if (!mThread || mStopping) {
+  if (!mThread || State() == kStopping) {
     // Already shutdown or in the process of doing so
     return NS_OK;
   }
 
-  mStopping = true;
+  State(kStopping);
   lock.Notify();
 
-  while (mStopping) {
+  while (State() == kStopping) {
     lock.Wait();
   }
 
   mThread->Shutdown();
   mThread = nullptr;
 
   mDecoder->Stop();
   mDecoder->Release();
--- a/dom/media/platforms/android/AndroidDecoderModule.h
+++ b/dom/media/platforms/android/AndroidDecoderModule.h
@@ -31,17 +31,17 @@ public:
   CreateAudioDecoder(const AudioInfo& aConfig,
                      FlushableTaskQueue* aAudioTaskQueue,
                      MediaDataDecoderCallback* aCallback) override;
 
 
   AndroidDecoderModule() {}
   virtual ~AndroidDecoderModule() {}
 
-  bool SupportsMimeType(const nsACString& aMimeType) override;
+  bool SupportsMimeType(const nsACString& aMimeType) const override;
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override;
 };
 
 class MediaCodecDataDecoder : public MediaDataDecoder {
 public:
 
@@ -54,51 +54,84 @@ public:
 
   RefPtr<MediaDataDecoder::InitPromise> Init() override;
   nsresult Flush() override;
   nsresult Drain() override;
   nsresult Shutdown() override;
   nsresult Input(MediaRawData* aSample) override;
 
 protected:
+  enum ModuleState {
+    kDecoding = 0,
+    kFlushing,
+    kDrainQueue,
+    kDrainDecoder,
+    kDrainWaitEOS,
+    kStopping,
+    kShutdown
+  };
+
   friend class AndroidDecoderModule;
 
+  static const char* ModuleStateStr(ModuleState aState);
+
+  virtual nsresult InitDecoder(widget::sdk::Surface::Param aSurface);
+
+  virtual nsresult Output(widget::sdk::BufferInfo::Param aInfo, void* aBuffer,
+      widget::sdk::MediaFormat::Param aFormat, const media::TimeUnit& aDuration)
+  {
+    return NS_OK;
+  }
+
+  virtual nsresult PostOutput(widget::sdk::BufferInfo::Param aInfo,
+      widget::sdk::MediaFormat::Param aFormat, const media::TimeUnit& aDuration)
+  {
+    return NS_OK;
+  }
+
+  virtual void Cleanup() {};
+
+  nsresult ResetInputBuffers();
+  nsresult ResetOutputBuffers();
+
+  nsresult GetInputBuffer(JNIEnv* env, int index, jni::Object::LocalRef* buffer);
+  bool WaitForInput();
+  MediaRawData* PeekNextSample();
+  nsresult QueueSample(const MediaRawData* aSample);
+  nsresult QueueEOS();
+  void HandleEOS(int32_t aOutputStatus);
+  media::TimeUnit GetOutputDuration();
+  nsresult ProcessOutput(widget::sdk::BufferInfo::Param aInfo,
+                         widget::sdk::MediaFormat::Param aFormat,
+                         int32_t aStatus);
+  ModuleState State() const;
+  void State(ModuleState aState);
+  void DecoderLoop();
+
+  virtual void ClearQueue();
+
   MediaData::Type mType;
 
   nsAutoCString mMimeType;
   widget::sdk::MediaFormat::GlobalRef mFormat;
 
   MediaDataDecoderCallback* mCallback;
 
   widget::sdk::MediaCodec::GlobalRef mDecoder;
 
   jni::ObjectArray::GlobalRef mInputBuffers;
   jni::ObjectArray::GlobalRef mOutputBuffers;
 
   nsCOMPtr<nsIThread> mThread;
 
   // Only these members are protected by mMonitor.
   Monitor mMonitor;
-  bool mFlushing;
-  bool mDraining;
-  bool mStopping;
+
+  ModuleState mState;
 
   SampleQueue mQueue;
   // Durations are stored in microseconds.
   std::queue<media::TimeUnit> mDurations;
-
-  virtual nsresult InitDecoder(widget::sdk::Surface::Param aSurface);
-
-  virtual nsresult Output(widget::sdk::BufferInfo::Param aInfo, void* aBuffer, widget::sdk::MediaFormat::Param aFormat, const media::TimeUnit& aDuration) { return NS_OK; }
-  virtual nsresult PostOutput(widget::sdk::BufferInfo::Param aInfo, widget::sdk::MediaFormat::Param aFormat, const media::TimeUnit& aDuration) { return NS_OK; }
-  virtual void Cleanup() {};
-
-  nsresult ResetInputBuffers();
-  nsresult ResetOutputBuffers();
-
-  void DecoderLoop();
-  nsresult GetInputBuffer(JNIEnv* env, int index, jni::Object::LocalRef* buffer);
-  virtual void ClearQueue();
 };
 
-} // namwspace mozilla
+} // namespace mozilla
 
 #endif
--- a/dom/media/platforms/apple/AppleDecoderModule.cpp
+++ b/dom/media/platforms/apple/AppleDecoderModule.cpp
@@ -112,17 +112,17 @@ AppleDecoderModule::CreateAudioDecoder(c
                                        MediaDataDecoderCallback* aCallback)
 {
   RefPtr<MediaDataDecoder> decoder =
     new AppleATDecoder(aConfig, aAudioTaskQueue, aCallback);
   return decoder.forget();
 }
 
 bool
-AppleDecoderModule::SupportsMimeType(const nsACString& aMimeType)
+AppleDecoderModule::SupportsMimeType(const nsACString& aMimeType) const
 {
   return (sIsCoreMediaAvailable &&
           (aMimeType.EqualsLiteral("audio/mpeg") ||
            aMimeType.EqualsLiteral("audio/mp4a-latm"))) ||
     ((sIsVTAvailable || sIsVDAAvailable) &&
      (aMimeType.EqualsLiteral("video/mp4") ||
       aMimeType.EqualsLiteral("video/avc")));
 }
--- a/dom/media/platforms/apple/AppleDecoderModule.h
+++ b/dom/media/platforms/apple/AppleDecoderModule.h
@@ -27,17 +27,17 @@ public:
                      MediaDataDecoderCallback* aCallback) override;
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const AudioInfo& aConfig,
                      FlushableTaskQueue* aAudioTaskQueue,
                      MediaDataDecoderCallback* aCallback) override;
 
-  bool SupportsMimeType(const nsACString& aMimeType) override;
+  bool SupportsMimeType(const nsACString& aMimeType) const override;
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override;
 
   static void Init();
 
   static bool sCanUseHardwareVideoDecoder;
 
--- a/dom/media/platforms/ffmpeg/FFmpegDecoderModule.h
+++ b/dom/media/platforms/ffmpeg/FFmpegDecoderModule.h
@@ -56,17 +56,17 @@ public:
                      FlushableTaskQueue* aAudioTaskQueue,
                      MediaDataDecoderCallback* aCallback) override
   {
     RefPtr<MediaDataDecoder> decoder =
       new FFmpegAudioDecoder<V>(aAudioTaskQueue, aCallback, aConfig);
     return decoder.forget();
   }
 
-  bool SupportsMimeType(const nsACString& aMimeType) override
+  bool SupportsMimeType(const nsACString& aMimeType) const override
   {
     AVCodecID audioCodec = FFmpegAudioDecoder<V>::GetCodecId(aMimeType);
     AVCodecID videoCodec = FFmpegH264Decoder<V>::GetCodecId(aMimeType);
     if (audioCodec == AV_CODEC_ID_NONE && videoCodec == AV_CODEC_ID_NONE) {
       return false;
     }
     AVCodecID codec = audioCodec != AV_CODEC_ID_NONE ? audioCodec : videoCodec;
     return !!FFmpegDataDecoder<V>::FindAVCodec(codec);
--- a/dom/media/platforms/gonk/GonkDecoderModule.cpp
+++ b/dom/media/platforms/gonk/GonkDecoderModule.cpp
@@ -56,17 +56,17 @@ GonkDecoderModule::DecoderNeedsConversio
   if (aConfig.IsVideo()) {
     return kNeedAnnexB;
   } else {
     return kNeedNone;
   }
 }
 
 bool
-GonkDecoderModule::SupportsMimeType(const nsACString& aMimeType)
+GonkDecoderModule::SupportsMimeType(const nsACString& aMimeType) const
 {
   return aMimeType.EqualsLiteral("audio/mp4a-latm") ||
     aMimeType.EqualsLiteral("audio/3gpp") ||
     aMimeType.EqualsLiteral("audio/amr-wb") ||
     aMimeType.EqualsLiteral("audio/mpeg") ||
     aMimeType.EqualsLiteral("video/mp4") ||
     aMimeType.EqualsLiteral("video/mp4v-es") ||
     aMimeType.EqualsLiteral("video/avc") ||
--- a/dom/media/platforms/gonk/GonkDecoderModule.h
+++ b/dom/media/platforms/gonk/GonkDecoderModule.h
@@ -30,15 +30,15 @@ public:
                      FlushableTaskQueue* aAudioTaskQueue,
                      MediaDataDecoderCallback* aCallback) override;
 
   static void Init();
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override;
 
-  bool SupportsMimeType(const nsACString& aMimeType) override;
+  bool SupportsMimeType(const nsACString& aMimeType) const override;
 
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/platforms/wmf/WMFDecoderModule.cpp
+++ b/dom/media/platforms/wmf/WMFDecoderModule.cpp
@@ -159,17 +159,17 @@ CanCreateWMFDecoder()
   static Maybe<bool> result;
   if (result.isNothing()) {
     result.emplace(CanCreateMFTDecoder(aGuid));
   }
   return result.value();
 }
 
 bool
-WMFDecoderModule::SupportsMimeType(const nsACString& aMimeType)
+WMFDecoderModule::SupportsMimeType(const nsACString& aMimeType) const
 {
   if ((aMimeType.EqualsLiteral("audio/mp4a-latm") ||
        aMimeType.EqualsLiteral("audio/mp4")) &&
       CanCreateWMFDecoder<CLSID_CMSAACDecMFT>()) {
     return true;
   }
   if ((aMimeType.EqualsLiteral("video/avc") ||
        aMimeType.EqualsLiteral("video/mp4")) &&
--- a/dom/media/platforms/wmf/WMFDecoderModule.h
+++ b/dom/media/platforms/wmf/WMFDecoderModule.h
@@ -26,17 +26,17 @@ public:
                      FlushableTaskQueue* aVideoTaskQueue,
                      MediaDataDecoderCallback* aCallback) override;
 
   already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const AudioInfo& aConfig,
                      FlushableTaskQueue* aAudioTaskQueue,
                      MediaDataDecoderCallback* aCallback) override;
 
-  bool SupportsMimeType(const nsACString& aMimeType) override;
+  bool SupportsMimeType(const nsACString& aMimeType) const override;
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override;
 
   // Called on main thread.
   static void Init();
 
   // Called from any thread, must call init first
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -69,17 +69,19 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
     MOZ_ASSERT(tmp->mIsOffline,
                "Online AudioContexts should always be started");
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActiveNodes)
   }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_ADDREF_INHERITED(AudioContext, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(AudioContext, DOMEventTargetHelper)
+
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioContext)
+  NS_INTERFACE_MAP_ENTRY(nsIMemoryReporter)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 static float GetSampleRateForAudioContext(bool aIsOffline, float aSampleRate)
 {
   if (aIsOffline) {
     return aSampleRate;
   } else {
     CubebUtils::InitPreferredSampleRate();
--- a/dom/media/webaudio/AudioDestinationNode.cpp
+++ b/dom/media/webaudio/AudioDestinationNode.cpp
@@ -173,17 +173,19 @@ public:
     NS_DispatchToMainThread(onCompleteTask);
 
     context->OnStateChanged(nullptr, AudioContextState::Closed);
   }
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
   {
     size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
-    amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
+    if (mBuffer) {
+      amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
+    }
     return amount;
   }
 
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
   {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
--- a/dom/media/webaudio/MediaBufferDecoder.cpp
+++ b/dom/media/webaudio/MediaBufferDecoder.cpp
@@ -595,17 +595,19 @@ WebAudioDecodeJob::SizeOfExcludingThis(M
     amount += mSuccessCallback->SizeOfIncludingThis(aMallocSizeOf);
   }
   if (mFailureCallback) {
     amount += mFailureCallback->SizeOfIncludingThis(aMallocSizeOf);
   }
   if (mOutput) {
     amount += mOutput->SizeOfIncludingThis(aMallocSizeOf);
   }
-  amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
+  if (mBuffer) {
+    amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
+  }
   return amount;
 }
 
 size_t
 WebAudioDecodeJob::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
 {
   return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
 }
--- a/dom/media/webaudio/test/mochitest.ini
+++ b/dom/media/webaudio/test/mochitest.ini
@@ -178,9 +178,8 @@ skip-if = (toolkit == 'gonk' && !debug) 
 [test_stereoPanningWithGain.html]
 [test_waveDecoder.html]
 [test_waveShaper.html]
 [test_waveShaperGain.html]
 [test_waveShaperNoCurve.html]
 [test_waveShaperPassThrough.html]
 [test_waveShaperInvalidLengthCurve.html]
 [test_WebAudioMemoryReporting.html]
-skip-if = debug # assertion failures: bug 1222202
--- a/dom/media/webaudio/test/test_WebAudioMemoryReporting.html
+++ b/dom/media/webaudio/test/test_WebAudioMemoryReporting.html
@@ -8,20 +8,24 @@
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
 var ac = new AudioContext();
 var sp = ac.createScriptProcessor(4096, 1, 1);
 sp.connect(ac.destination);
 
+// Not started so as to test
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1225003#c2
+var oac = new OfflineAudioContext(1, 1, 48000);
+
 var nodeTypes = ["ScriptProcessorNode", "AudioDestinationNode"];
 var objectTypes = ["dom-nodes", "engine-objects", "stream-objects"];
 
-var usages = {};
+var usages = { "explicit/webaudio/audiocontext": 0 };
 
 for (var i = 0; i < nodeTypes.length; ++i) {
   for (var j = 0; j < objectTypes.length; ++j) {
     usages["explicit/webaudio/audio-node/" +
            nodeTypes[i] + "/" + objectTypes[j]] = 0;
   }
 }
 
@@ -38,10 +42,13 @@ var finished = function () {
   };
   SimpleTest.finish();
 }
 
 SpecialPowers.Cc["@mozilla.org/memory-reporter-manager;1"].
   getService(SpecialPowers.Ci.nsIMemoryReporterManager).
   getReports(handleReport, null, finished, null, /* anonymized = */ false);
 
+// To test bug 1225003, run a failing decodeAudioData() job over a time when
+// the tasks from getReports() are expected to run.
+ac.decodeAudioData(new ArrayBuffer(4), function(){}, function(){});
 </script>
 </html>
--- a/dom/messagechannel/MessagePort.cpp
+++ b/dom/messagechannel/MessagePort.cpp
@@ -144,16 +144,20 @@ public:
 
     RefPtr<MessagePortList> portList =
       new MessagePortList(static_cast<dom::Event*>(event.get()),
                           ports);
     event->SetPorts(portList);
 
     bool dummy;
     mPort->DispatchEvent(static_cast<dom::Event*>(event.get()), &dummy);
+
+    // We must check if we were waiting for this message in order to shutdown
+    // the port.
+    mPort->UpdateMustKeepAlive();
     return NS_OK;
   }
 
   NS_IMETHOD
   Cancel() override
   {
     mPort = nullptr;
     mData = nullptr;
@@ -211,18 +215,20 @@ public:
     : mPort(aPort)
   {
     MOZ_ASSERT(aPort);
     MOZ_COUNT_CTOR(MessagePortFeature);
   }
 
   virtual bool Notify(JSContext* aCx, workers::Status aStatus) override
   {
-    if (mPort && aStatus > Running) {
-      mPort->Close();
+    if (aStatus > Running) {
+      // We cannot process messages anymore because we cannot dispatch new
+      // runnables. Let's force a Close().
+      mPort->CloseForced();
     }
 
     return true;
   }
 
 private:
   ~MessagePortFeature()
   {
@@ -284,17 +290,17 @@ MessagePort::MessagePort(nsPIDOMWindow* 
 {
   mIdentifier = new MessagePortIdentifier();
   mIdentifier->neutered() = true;
   mIdentifier->sequenceId() = 0;
 }
 
 MessagePort::~MessagePort()
 {
-  Close();
+  CloseForced();
   MOZ_ASSERT(!mWorkerFeature);
 }
 
 /* static */ already_AddRefed<MessagePort>
 MessagePort::Create(nsPIDOMWindow* aWindow, const nsID& aUUID,
                     const nsID& aDestinationUUID, ErrorResult& aRv)
 {
   RefPtr<MessagePort> mp = new MessagePort(aWindow);
@@ -331,17 +337,16 @@ MessagePort::Initialize(const nsID& aUUI
                         State aState, ErrorResult& aRv)
 {
   MOZ_ASSERT(mIdentifier);
   mIdentifier->uuid() = aUUID;
   mIdentifier->destinationUuid() = aDestinationUUID;
   mIdentifier->sequenceId() = aSequenceID;
 
   mState = aState;
-  mNextStep = eNextStepNone;
 
   if (mNeutered) {
     // If this port is neutered we don't want to keep it alive artificially nor
     // we want to add listeners or workerFeatures.
     mState = eStateDisentangled;
     return;
   }
 
@@ -447,18 +452,19 @@ MessagePort::PostMessage(JSContext* aCx,
   // If we are unshipped we are connected to the other port on the same thread.
   if (mState == eStateUnshippedEntangled) {
     MOZ_ASSERT(mUnshippedEntangledPort);
     mUnshippedEntangledPort->mMessages.AppendElement(data);
     mUnshippedEntangledPort->Dispatch();
     return;
   }
 
-  // Not entangled yet, but already closed.
-  if (mNextStep != eNextStepNone) {
+  // Not entangled yet, but already closed/disentangled.
+  if (mState == eStateEntanglingForDisentangle ||
+      mState == eStateEntanglingForClose) {
     return;
   }
 
   RemoveDocFromBFCache();
 
   // Not entangled yet.
   if (mState == eStateEntangling) {
     mMessagesForTheOtherPort.AppendElement(data);
@@ -485,63 +491,138 @@ MessagePort::Start()
 
   mMessageQueueEnabled = true;
   Dispatch();
 }
 
 void
 MessagePort::Dispatch()
 {
-  if (!mMessageQueueEnabled || mMessages.IsEmpty() || mDispatchRunnable ||
-      mState > eStateEntangled || mNextStep != eNextStepNone) {
+  if (!mMessageQueueEnabled || mMessages.IsEmpty() || mDispatchRunnable) {
     return;
   }
 
+  switch (mState) {
+    case eStateUnshippedEntangled:
+      // Everything is fine here. We have messages because the other
+      // port populates our queue directly.
+      break;
+
+    case eStateEntangling:
+      // Everything is fine here as well. We have messages because the other
+      // port populated our queue directly when we were in the
+      // eStateUnshippedEntangled state.
+      break;
+
+    case eStateEntanglingForDisentangle:
+      // Here we don't want to ship messages because these messages must be
+      // delivered by the cloned version of this one. They will be sent in the
+      // SendDisentangle().
+      return;
+
+    case eStateEntanglingForClose:
+      // We still want to deliver messages if we are closing. These messages
+      // are here from the previous eStateUnshippedEntangled state.
+      break;
+
+    case eStateEntangled:
+      // This port is up and running.
+      break;
+
+    case eStateDisentangling:
+      // If we are in the process to disentangle the port, we cannot dispatch
+      // messages. They will be sent to the cloned version of this port via
+      // SendDisentangle();
+      return;
+
+    case eStateDisentangled:
+      MOZ_CRASH("This cannot happen.");
+      // It cannot happen because Disentangle should take off all the pending
+      // messages.
+      break;
+
+    case eStateDisentangledForClose:
+      // If we are here is because the port has been closed. We can still
+      // process the pending messages.
+      break;
+  }
+
   RefPtr<SharedMessagePortMessage> data = mMessages.ElementAt(0);
   mMessages.RemoveElementAt(0);
 
   RefPtr<PostMessageRunnable> runnable = new PostMessageRunnable(this, data);
 
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToCurrentThread(runnable)));
 
   mDispatchRunnable = new DispatchEventRunnable(this);
 
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToCurrentThread(mDispatchRunnable)));
 }
 
 void
 MessagePort::Close()
 {
+  CloseInternal(true /* aSoftly */);
+}
+
+void
+MessagePort::CloseForced()
+{
+  CloseInternal(false /* aSoftly */);
+}
+
+void
+MessagePort::CloseInternal(bool aSoftly)
+{
+  // If we have some messages to send but we don't want a 'soft' close, we have
+  // to flush them now.
+  if (!aSoftly) {
+    mMessages.Clear();
+  }
+
   if (mState == eStateUnshippedEntangled) {
     MOZ_ASSERT(mUnshippedEntangledPort);
 
     // This avoids loops.
     RefPtr<MessagePort> port = Move(mUnshippedEntangledPort);
     MOZ_ASSERT(mUnshippedEntangledPort == nullptr);
 
-    mState = eStateDisentangled;
-    port->Close();
+    mState = eStateDisentangledForClose;
+    port->CloseInternal(aSoftly);
 
     UpdateMustKeepAlive();
     return;
   }
 
   // Not entangled yet, we have to wait.
   if (mState == eStateEntangling) {
-    mNextStep = eNextStepClose;
+    mState = eStateEntanglingForClose;
+    return;
+  }
+
+  // Not entangled but already cloned or closed
+  if (mState == eStateEntanglingForDisentangle ||
+      mState == eStateEntanglingForClose) {
+    return;
+  }
+
+  // Maybe we were already closing the port but softly. In this case we call
+  // UpdateMustKeepAlive() to consider the empty pending message queue.
+  if (mState == eStateDisentangledForClose && !aSoftly) {
+    UpdateMustKeepAlive();
     return;
   }
 
   if (mState > eStateEntangled) {
     return;
   }
 
   // We don't care about stopping the sending of messages because from now all
   // the incoming messages will be ignored.
-  mState = eStateDisentangled;
+  mState = eStateDisentangledForClose;
 
   MOZ_ASSERT(mActor);
 
   mActor->SendClose();
   mActor->SetPort(nullptr);
   mActor = nullptr;
 
   UpdateMustKeepAlive();
@@ -571,18 +652,21 @@ MessagePort::SetOnmessage(EventHandlerNo
 
 // This method is called when the PMessagePortChild actor is entangled to
 // another actor. It receives a list of messages to be dispatch. It can be that
 // we were waiting for this entangling step in order to disentangle the port or
 // to close it.
 void
 MessagePort::Entangled(nsTArray<MessagePortMessage>& aMessages)
 {
-  MOZ_ASSERT(mState == eStateEntangling);
+  MOZ_ASSERT(mState == eStateEntangling ||
+             mState == eStateEntanglingForDisentangle ||
+             mState == eStateEntanglingForClose);
 
+  State oldState = mState;
   mState = eStateEntangled;
 
   // If we have pending messages, these have to be sent.
   if (!mMessagesForTheOtherPort.IsEmpty()) {
     nsTArray<MessagePortMessage> messages;
     SharedMessagePortMessage::FromSharedToMessagesChild(mActor,
                                                         mMessagesForTheOtherPort,
                                                         messages);
@@ -593,55 +677,59 @@ MessagePort::Entangled(nsTArray<MessageP
   // We must convert the messages into SharedMessagePortMessages to avoid leaks.
   FallibleTArray<RefPtr<SharedMessagePortMessage>> data;
   if (NS_WARN_IF(!SharedMessagePortMessage::FromMessagesToSharedChild(aMessages,
                                                                       data))) {
     // OOM, we cannot continue.
     return;
   }
 
-  if (mNextStep == eNextStepClose) {
-    Close();
+  // If the next step is to close the port, we do it ignoring the received
+  // messages.
+  if (oldState == eStateEntanglingForClose) {
+    CloseForced();
     return;
   }
 
   mMessages.AppendElements(data);
 
   // We were waiting for the entangling callback in order to disentangle this
   // port immediately after.
-  if (mNextStep == eNextStepDisentangle) {
+  if (oldState == eStateEntanglingForDisentangle) {
     StartDisentangling();
     return;
   }
 
-  MOZ_ASSERT(mNextStep == eNextStepNone);
   Dispatch();
 }
 
 void
 MessagePort::StartDisentangling()
 {
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(mState == eStateEntangled);
 
   mState = eStateDisentangling;
-  mNextStep = eNextStepNone;
 
   // Sending this message we communicate to the parent actor that we don't want
   // to receive any new messages. It is possible that a message has been
   // already sent but not received yet. So we have to collect all of them and
   // we send them in the SendDispatch() request.
   mActor->SendStopSendingData();
 }
 
 void
 MessagePort::MessagesReceived(nsTArray<MessagePortMessage>& aMessages)
 {
-  MOZ_ASSERT(mState == eStateEntangled || mState == eStateDisentangling);
-  MOZ_ASSERT(mNextStep == eNextStepNone);
+  MOZ_ASSERT(mState == eStateEntangled ||
+             mState == eStateDisentangling ||
+             // This last step can happen only if Close() has been called
+             // manually. At this point SendClose() is sent but we can still
+             // receive something until the Closing request is processed.
+             mState == eStateDisentangledForClose);
   MOZ_ASSERT(mMessagesForTheOtherPort.IsEmpty());
 
   RemoveDocFromBFCache();
 
   FallibleTArray<RefPtr<SharedMessagePortMessage>> data;
   if (NS_WARN_IF(!SharedMessagePortMessage::FromMessagesToSharedChild(aMessages,
                                                                       data))) {
     // OOM, We cannot continue.
@@ -695,17 +783,18 @@ MessagePort::CloneAndDisentangle(Message
   aIdentifier.neutered() = true;
 
   if (mState > eStateEntangled) {
     return;
   }
 
   // We already have a 'next step'. We have to consider this port as already
   // cloned/closed/disentangled.
-  if (mNextStep != eNextStepNone) {
+  if (mState == eStateEntanglingForDisentangle ||
+      mState == eStateEntanglingForClose) {
     return;
   }
 
   aIdentifier.uuid() = mIdentifier->uuid();
   aIdentifier.destinationUuid() = mIdentifier->destinationUuid();
   aIdentifier.sequenceId() = mIdentifier->sequenceId() + 1;
   aIdentifier.neutered() = false;
 
@@ -725,37 +814,38 @@ MessagePort::CloneAndDisentangle(Message
       mState = eStateDisentangled;
       UpdateMustKeepAlive();
       return;
     }
 
     // Register this component to PBackground.
     ConnectToPBackground();
 
-    mNextStep = eNextStepDisentangle;
+    mState = eStateEntanglingForDisentangle;
     return;
   }
 
   // Not entangled yet, we have to wait.
-  if (mState < eStateEntangled) {
-    mNextStep = eNextStepDisentangle;
+  if (mState == eStateEntangling) {
+    mState = eStateEntanglingForDisentangle;
     return;
   }
 
+  MOZ_ASSERT(mState == eStateEntangled);
   StartDisentangling();
 }
 
 void
 MessagePort::Closed()
 {
-  if (mState == eStateDisentangled) {
+  if (mState >= eStateDisentangled) {
     return;
   }
 
-  mState = eStateDisentangled;
+  mState = eStateDisentangledForClose;
 
   if (mActor) {
     mActor->SetPort(nullptr);
     mActor = nullptr;
   }
 
   UpdateMustKeepAlive();
 }
@@ -784,33 +874,37 @@ MessagePort::ActorFailed()
 }
 
 void
 MessagePort::ActorCreated(mozilla::ipc::PBackgroundChild* aActor)
 {
   MOZ_ASSERT(aActor);
   MOZ_ASSERT(!mActor);
   MOZ_ASSERT(mIdentifier);
-  MOZ_ASSERT(mState == eStateEntangling);
+  MOZ_ASSERT(mState == eStateEntangling ||
+             mState == eStateEntanglingForDisentangle ||
+             mState == eStateEntanglingForClose);
 
   PMessagePortChild* actor =
     aActor->SendPMessagePortConstructor(mIdentifier->uuid(),
                                         mIdentifier->destinationUuid(),
                                         mIdentifier->sequenceId());
 
   mActor = static_cast<MessagePortChild*>(actor);
   MOZ_ASSERT(mActor);
 
   mActor->SetPort(this);
 }
 
 void
 MessagePort::UpdateMustKeepAlive()
 {
-  if (mState == eStateDisentangled && mIsKeptAlive) {
+  if (mState >= eStateDisentangled &&
+      mMessages.IsEmpty() &&
+      mIsKeptAlive) {
     mIsKeptAlive = false;
 
     if (mWorkerFeature) {
       WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
       MOZ_ASSERT(workerPrivate);
 
       workerPrivate->RemoveFeature(workerPrivate->GetJSContext(),
                                    mWorkerFeature);
@@ -854,17 +948,17 @@ MessagePort::Observe(nsISupports* aSubje
   nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
   NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
 
   uint64_t innerID;
   nsresult rv = wrapper->GetData(&innerID);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (innerID == mInnerID) {
-    Close();
+    CloseForced();
   }
 
   return NS_OK;
 }
 
 void
 MessagePort::RemoveDocFromBFCache()
 {
--- a/dom/messagechannel/MessagePort.h
+++ b/dom/messagechannel/MessagePort.h
@@ -20,43 +20,46 @@ class nsPIDOMWindow;
 
 namespace mozilla {
 namespace dom {
 
 class DispatchEventRunnable;
 class MessagePortChild;
 class MessagePortIdentifier;
 class MessagePortMessage;
+class PostMessageRunnable;
 class SharedMessagePortMessage;
 
 namespace workers {
 class WorkerFeature;
 } // namespace workers
 
 class MessagePort final : public DOMEventTargetHelper
                         , public nsIIPCBackgroundChildCreateCallback
                         , public nsIObserver
 {
   friend class DispatchEventRunnable;
+  friend class PostMessageRunnable;
 
 public:
   NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
   NS_DECL_NSIOBSERVER
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MessagePort,
                                            DOMEventTargetHelper)
 
   static already_AddRefed<MessagePort>
   Create(nsPIDOMWindow* aWindow, const nsID& aUUID,
          const nsID& aDestinationUUID, ErrorResult& aRv);
 
   static already_AddRefed<MessagePort>
   Create(nsPIDOMWindow* aWindow, const MessagePortIdentifier& aIdentifier,
          ErrorResult& aRv);
 
+  // For IPC.
   static void
   ForceClose(const MessagePortIdentifier& aIdentifier);
 
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void
   PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
@@ -72,16 +75,18 @@ public:
   void SetOnmessage(EventHandlerNonNull* aCallback);
 
   // Non WebIDL methods
 
   void UnshippedEntangle(MessagePort* aEntangledPort);
 
   void CloneAndDisentangle(MessagePortIdentifier& aIdentifier);
 
+  void CloseForced();
+
   // These methods are useful for MessagePortChild
 
   void Entangled(nsTArray<MessagePortMessage>& aMessages);
   void MessagesReceived(nsTArray<MessagePortMessage>& aMessages);
   void StopSendingDataConfirmed();
   void Closed();
 
 private:
@@ -91,21 +96,26 @@ private:
   enum State {
     // When a port is created by a MessageChannel it is entangled with the
     // other. They both run on the same thread, same event loop and the
     // messages are added to the queues without using PBackground actors.
     // When one of the port is shipped, the state is changed to
     // StateEntangling.
     eStateUnshippedEntangled,
 
-    // If the port is closed or cloned when we are in this state, we set the
-    // mNextStep. This 'next' operation will be done when entangled() message
-    // is received.
+    // If the port is closed or cloned when we are in this state, we go in one
+    // of the following 2 steps. EntanglingForClose or ForDisentangle.
     eStateEntangling,
 
+    // We are not fully entangled yet but are already disentangled.
+    eStateEntanglingForDisentangle,
+
+    // We are not fully entangled yet but are already closed.
+    eStateEntanglingForClose,
+
     // When entangled() is received we send all the messages in the
     // mMessagesForTheOtherPort to the actor and we change the state to
     // StateEntangled. At this point the port is entangled with the other. We
     // send and receive messages.
     // If the port queue is not enabled, the received messages are stored in
     // the mMessages.
     eStateEntangled,
 
@@ -116,33 +126,39 @@ private:
     // dispatched.
     eStateDisentangling,
 
     // When 'StopSendingDataConfirmed' is received, we can disentangle the port
     // calling SendDisentangle in the actor because we are 100% sure that we
     // don't receive any other message, so nothing will be lost.
     // Disentangling the port we send all the messages from the mMessages
     // though the actor.
-    eStateDisentangled
+    eStateDisentangled,
+
+    // We are here if Close() has been called. We are disentangled but we can
+    // still send pending messages.
+    eStateDisentangledForClose
   };
 
   void Initialize(const nsID& aUUID, const nsID& aDestinationUUID,
                   uint32_t aSequenceID, bool mNeutered, State aState,
                   ErrorResult& aRv);
 
   void ConnectToPBackground();
 
   // Dispatch events from the Message Queue using a nsRunnable.
   void Dispatch();
 
   void StartDisentangling();
   void Disentangle();
 
   void RemoveDocFromBFCache();
 
+  void CloseInternal(bool aSoftly);
+
   // This method is meant to keep alive the MessagePort when this object is
   // creating the actor and until the actor is entangled.
   // We release the object when the port is closed or disentangled.
   void UpdateMustKeepAlive();
 
   bool IsCertainlyAliveForCC() const override
   {
     return mIsKeptAlive;
@@ -160,24 +176,16 @@ private:
   nsTArray<RefPtr<SharedMessagePortMessage>> mMessagesForTheOtherPort;
 
   nsAutoPtr<MessagePortIdentifier> mIdentifier;
 
   uint64_t mInnerID;
 
   State mState;
 
-  // This 'nextStep' is used when we are waiting to be entangled but the
-  // content has called Clone() or Close().
-  enum {
-    eNextStepNone,
-    eNextStepDisentangle,
-    eNextStepClose
-  } mNextStep;
-
   bool mMessageQueueEnabled;
 
   bool mIsKeptAlive;
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/messagechannel/MessagePortService.cpp
+++ b/dom/messagechannel/MessagePortService.cpp
@@ -31,16 +31,19 @@ AssertIsInMainProcess()
 
 class MessagePortService::MessagePortServiceData final
 {
 public:
   explicit MessagePortServiceData(const nsID& aDestinationUUID)
     : mDestinationUUID(aDestinationUUID)
     , mSequenceID(1)
     , mParent(nullptr)
+    // By default we don't know the next parent.
+    , mWaitingForNewParent(true)
+    , mNextStepCloseAll(false)
   {
     MOZ_COUNT_CTOR(MessagePortServiceData);
   }
 
   MessagePortServiceData(const MessagePortServiceData& aOther) = delete;
   MessagePortServiceData& operator=(const MessagePortServiceData&) = delete;
 
   ~MessagePortServiceData()
@@ -57,16 +60,19 @@ public:
   {
     uint32_t mSequenceID;
     // MessagePortParent keeps the service alive, and we don't want a cycle.
     MessagePortParent* mParent;
   };
 
   FallibleTArray<NextParent> mNextParents;
   FallibleTArray<RefPtr<SharedMessagePortMessage>> mMessages;
+
+  bool mWaitingForNewParent;
+  bool mNextStepCloseAll;
 };
 
 /* static */ MessagePortService*
 MessagePortService::Get()
 {
   AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
 
@@ -108,48 +114,67 @@ MessagePortService::RequestEntangling(Me
 
     data = new MessagePortServiceData(aDestinationUUID);
     mPorts.Put(aParent->ID(), data);
   }
 
   // This is a security check.
   if (!data->mDestinationUUID.Equals(aDestinationUUID)) {
     MOZ_ASSERT(false, "DestinationUUIDs do not match!");
+    CloseAll(aParent->ID());
     return false;
   }
 
   if (aSequenceID < data->mSequenceID) {
     MOZ_ASSERT(false, "Invalid sequence ID!");
+    CloseAll(aParent->ID());
     return false;
   }
 
   if (aSequenceID == data->mSequenceID) {
     if (data->mParent) {
       MOZ_ASSERT(false, "Two ports cannot have the same sequenceID.");
+      CloseAll(aParent->ID());
       return false;
     }
 
     // We activate this port, sending all the messages.
     data->mParent = aParent;
+    data->mWaitingForNewParent = false;
     FallibleTArray<MessagePortMessage> array;
     if (!SharedMessagePortMessage::FromSharedToMessagesParent(aParent,
                                                               data->mMessages,
                                                               array)) {
+      CloseAll(aParent->ID());
       return false;
     }
 
     data->mMessages.Clear();
-    return aParent->Entangled(array);
+
+    // We can entangle the port.
+    if (!aParent->Entangled(array)) {
+      CloseAll(aParent->ID());
+      return false;
+    }
+
+    // If we were waiting for this parent in order to close this channel, this
+    // is the time to do it.
+    if (data->mNextStepCloseAll) {
+      CloseAll(aParent->ID());
+    }
+
+    return true;
   }
 
   // This new parent will be the next one when a Disentangle request is
   // received from the current parent.
   MessagePortServiceData::NextParent* nextParent =
     data->mNextParents.AppendElement(mozilla::fallible);
   if (!nextParent) {
+    CloseAll(aParent->ID());
     return false;
   }
 
   nextParent->mSequenceID = aSequenceID;
   nextParent->mParent = aParent;
 
   return true;
 }
@@ -188,16 +213,17 @@ MessagePortService::DisentanglePort(
       nextParent = data->mNextParents[index].mParent;
       break;
     }
   }
 
   // We didn't find the parent.
   if (!nextParent) {
     data->mMessages.SwapElements(aMessages);
+    data->mWaitingForNewParent = true;
     data->mParent = nullptr;
     return true;
   }
 
   data->mParent = nextParent;
   data->mNextParents.RemoveElementAt(index);
 
   FallibleTArray<MessagePortMessage> array;
@@ -245,36 +271,51 @@ MessagePortService::CloseAllDebugCheck(c
 {
   nsID* id = static_cast<nsID*>(aPtr);
   MOZ_ASSERT(!id->Equals(aID));
   return PL_DHASH_NEXT;
 }
 #endif
 
 void
-MessagePortService::CloseAll(const nsID& aUUID)
+MessagePortService::CloseAll(const nsID& aUUID, bool aForced)
 {
   MessagePortServiceData* data;
   if (!mPorts.Get(aUUID, &data)) {
     MaybeShutdown();
     return;
   }
 
   if (data->mParent) {
     data->mParent->Close();
   }
 
   for (const MessagePortServiceData::NextParent& parent : data->mNextParents) {
     parent.mParent->CloseAndDelete();
   }
 
   nsID destinationUUID = data->mDestinationUUID;
+
+  // If we have informations about the other port and that port has some
+  // pending messages to deliver but the parent has not processed them yet,
+  // because its entangling request didn't arrive yet), we cannot close this
+  // channel.
+  MessagePortServiceData* destinationData;
+  if (!aForced &&
+      mPorts.Get(destinationUUID, &destinationData) &&
+      !destinationData->mMessages.IsEmpty() &&
+      destinationData->mWaitingForNewParent) {
+    MOZ_ASSERT(!destinationData->mNextStepCloseAll);
+    destinationData->mNextStepCloseAll = true;
+    return;
+  }
+
   mPorts.Remove(aUUID);
 
-  CloseAll(destinationUUID);
+  CloseAll(destinationUUID, aForced);
 
   // CloseAll calls itself recursively and it can happen that it deletes
   // itself. Before continuing we must check if we are still alive.
   if (!gInstance) {
     return;
   }
 
 #ifdef DEBUG
@@ -365,14 +406,14 @@ MessagePortService::ForceClose(const nsI
   }
 
   if (!data->mDestinationUUID.Equals(aDestinationUUID) ||
       data->mSequenceID != aSequenceID) {
     NS_WARNING("DestinationUUID and/or sequenceID do not match.");
     return false;
   }
 
-  CloseAll(aUUID);
+  CloseAll(aUUID, true);
   return true;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/messagechannel/MessagePortService.h
+++ b/dom/messagechannel/MessagePortService.h
@@ -41,17 +41,17 @@ public:
 
   bool ForceClose(const nsID& aUUID,
                   const nsID& aDestinationUUID,
                   const uint32_t& aSequenceID);
 
 private:
   ~MessagePortService() {}
 
-  void CloseAll(const nsID& aUUID);
+  void CloseAll(const nsID& aUUID, bool aForced = false);
   void MaybeShutdown();
 
   class MessagePortServiceData;
 
 #ifdef DEBUG
   static PLDHashOperator
   CloseAllDebugCheck(const nsID& aID, MessagePortServiceData* aData,
                      void* aPtr);
--- a/dom/messagechannel/tests/mochitest.ini
+++ b/dom/messagechannel/tests/mochitest.ini
@@ -19,8 +19,10 @@ support-files =
 [test_messageChannel_unshipped.html]
 [test_messageChannel_worker.html]
 [test_messageChannel_selfTransferring.html]
 [test_messageChannel_sharedWorker.html]
 [test_messageChannel_sharedWorker2.html]
 [test_messageChannel_any.html]
 [test_messageChannel_forceClose.html]
 [test_messageChannel_bug1178076.html]
+[test_messageChannel_bug1224825.html]
+[test_messageChannel_worker_forceClose.html]
new file mode 100644
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel_bug1224825.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1224825
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1224825</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1224825">Mozilla Bug 1224825</a>
+<div id="content"></div>
+<pre id="test">
+</pre>
+  <script type="application/javascript">
+
+var MAX = 100;
+
+function test_fullDeliveredMessages() {
+  var worker = new Worker('data:javascript,onmessage = function(e) { e.ports[0].onmessage = function(evt) { postMessage(evt.data);}}');
+
+  var count = 0;
+  worker.onmessage = function(e) {
+    is(e.data, count, "Correct value expected!");
+    ok(count < MAX,"No count > MAX messages!");
+    if (++count == MAX) {
+
+      SimpleTest.requestFlakyTimeout("Testing an event not happening");
+      setTimeout(function() {
+        runTests();
+      }, 200);
+
+      info("All the messages correctly received");
+    }
+  }
+
+  var mc = new MessageChannel();
+  worker.postMessage(42, [mc.port2]);
+
+  for (var i = 0; i < MAX; ++i) {
+    mc.port1.postMessage(i);
+  }
+
+  mc.port1.close();
+
+  for (var i = 0; i < MAX * 2; ++i) {
+    mc.port1.postMessage(i);
+  }
+}
+
+function test_closeInBetween() {
+  var mc = new MessageChannel();
+
+  for (var i = 0; i < MAX; ++i) {
+    mc.port1.postMessage(i);
+  }
+
+  mc.port1.onmessage = function(e) {
+    ok (e.data < MAX/2, "Correct message received from port1:" + e.data);
+  }
+
+  mc.port2.onmessage = function(e) {
+    ok (e.data < MAX, "Correct message received from port2:" + e.data);
+    if (e.data == MAX/2) {
+      mc.port2.close();
+    }
+
+    mc.port2.postMessage(e.data);
+
+    if (e.data == MAX - 1) {
+      runTests();
+    }
+  }
+}
+
+var tests = [ test_fullDeliveredMessages, test_closeInBetween ];
+
+function runTests() {
+  if (!tests.length) {
+    SimpleTest.finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTests();
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel_worker_forceClose.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test for forcing the closing of the port in workers</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="content"></div>
+<pre id="test">
+</pre>
+  <script type="application/javascript">
+
+  var worker = new Worker('data:javascript,onmessage = function(e) { "doing nothing with this port"; }');
+
+  var mc = new MessageChannel();
+  worker.postMessage(42, [mc.port2]);
+
+  for (var i = 0; i < 10; ++i) {
+    mc.port1.postMessage(i);
+  }
+
+  ok(true, "All the messages are sent! We should shutdown correctly.");
+  </script>
+</body>
+</html>
--- a/dom/push/PushRecord.jsm
+++ b/dom/push/PushRecord.jsm
@@ -204,16 +204,21 @@ PushRecord.prototype = {
   quotaApplies() {
     return Number.isFinite(this.quota);
   },
 
   isExpired() {
     return this.quota === 0;
   },
 
+  matchesOriginAttributes(pattern) {
+    return ChromeUtils.originAttributesMatchPattern(
+      this.principal.originAttributes, pattern);
+  },
+
   toSubscription() {
     return {
       pushEndpoint: this.pushEndpoint,
       lastPush: this.lastPush,
       pushCount: this.pushCount,
       p256dhKey: this.p256dhPublicKey,
     };
   },
--- a/dom/push/PushService.jsm
+++ b/dom/push/PushService.jsm
@@ -272,47 +272,41 @@ this.PushService = {
 
       case "perm-changed":
         this._onPermissionChange(aSubject, aData).catch(error => {
           console.error("onPermissionChange: Error updating registrations:",
             error);
         })
         break;
 
-      case "webapps-clear-data":
-        console.debug("webapps-clear-data");
-
-        let data = aSubject
-                     .QueryInterface(Ci.mozIApplicationClearPrivateDataParams);
-        if (!data) {
-          console.error("webapps-clear-data: Failed to get information " +
-            "about application");
-          return;
-        }
-
-        var originAttributes =
-          ChromeUtils.originAttributesToSuffix({ appId: data.appId,
-                                                 inBrowser: data.browserOnly });
-        this._db.getAllByOriginAttributes(originAttributes)
-          .then(records => Promise.all(records.map(record =>
-            this._db.delete(record.keyID)
-              .catch(err => {
-                console.error("webapps-clear-data: Error removing record",
-                  record, err);
-                // This is the record we were unable to delete.
-                return record;
-              })
-              .then(maybeDeleted => this._backgroundUnregister(maybeDeleted))
-            )
-          ));
-
+      case "clear-origin-data":
+        this._clearOriginData(data).catch(error => {
+          console.error("clearOriginData: Error clearing origin data:", error);
+        });
         break;
     }
   },
 
+  _clearOriginData: function(data) {
+    console.log("clearOriginData()");
+
+    if (!data) {
+      return Promise.resolve();
+    }
+
+    let pattern = JSON.parse(data);
+    return this._db.clearIf(record => {
+      if (!record.matchesOriginAttributes(pattern)) {
+        return false;
+      }
+      this._backgroundUnregister(record);
+      return true;
+    });
+  },
+
   /**
    * Sends an unregister request to the server in the background. If the
    * service is not connected, this function is a no-op.
    *
    * @param {PushRecord} record The record to unregister.
    */
   _backgroundUnregister: function(record) {
     console.debug("backgroundUnregister()");
@@ -482,17 +476,17 @@ this.PushService = {
 
   _startObservers: function() {
     console.debug("startObservers()");
 
     if (this._state != PUSH_SERVICE_ACTIVATING) {
       return;
     }
 
-    Services.obs.addObserver(this, "webapps-clear-data", false);
+    Services.obs.addObserver(this, "clear-origin-data", false);
 
     // On B2G the NetworkManager interface fires a network-active-changed
     // event.
     //
     // The "active network" is based on priority - i.e. Wi-Fi has higher
     // priority than data. The PushService should just use the preferred
     // network, and not care about all interface changes.
     // network-active-changed is not fired when the network goes offline, but
@@ -609,17 +603,17 @@ this.PushService = {
 
     if (this._state < PUSH_SERVICE_ACTIVATING) {
       return;
     }
 
     prefs.ignore("connection.enabled", this);
 
     Services.obs.removeObserver(this, this._networkStateChangeEventName);
-    Services.obs.removeObserver(this, "webapps-clear-data");
+    Services.obs.removeObserver(this, "clear-origin-data");
     Services.obs.removeObserver(this, "idle-daily");
     Services.obs.removeObserver(this, "perm-changed");
   },
 
   uninit: function() {
     console.debug("uninit()");
 
     this._childListeners = [];
--- a/dom/push/test/xpcshell/head.js
+++ b/dom/push/test/xpcshell/head.js
@@ -7,16 +7,17 @@ var {classes: Cc, interfaces: Ci, utils:
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import('resource://gre/modules/Task.jsm');
 Cu.import('resource://gre/modules/Timer.jsm');
 Cu.import('resource://gre/modules/Promise.jsm');
 Cu.import('resource://gre/modules/Preferences.jsm');
 Cu.import('resource://gre/modules/PlacesUtils.jsm');
+Cu.import('resource://gre/modules/ObjectUtils.jsm');
 
 const serviceExports = Cu.import('resource://gre/modules/PushService.jsm', {});
 const servicePrefs = new Preferences('dom.push.');
 
 XPCOMUtils.defineLazyServiceGetter(
   this,
   "PushNotificationService",
   "@mozilla.org/push/NotificationService;1",
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/test_clear_origin_data.js
@@ -0,0 +1,144 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
+
+const userAgentID = 'bd744428-f125-436a-b6d0-dd0c9845837f';
+
+let clearForPattern = Task.async(function* (testRecords, pattern) {
+  let patternString = JSON.stringify(pattern);
+  yield PushService._clearOriginData(patternString);
+
+  for (let length = testRecords.length; length--;) {
+    let test = testRecords[length];
+    let originSuffix = ChromeUtils.originAttributesToSuffix(
+      test.originAttributes);
+
+    let registration = yield PushNotificationService.registration(
+      test.scope,
+      originSuffix
+    );
+
+    let url = test.scope + originSuffix;
+
+    if (ObjectUtils.deepEqual(test.clearIf, pattern)) {
+      ok(!registration, 'Should clear registration ' + url +
+        ' for pattern ' + patternString);
+      testRecords.splice(length, 1);
+    } else {
+      ok(registration, 'Should not clear registration ' + url +
+        ' for pattern ' + patternString);
+    }
+  }
+});
+
+function run_test() {
+  do_get_profile();
+  setPrefs({
+    userAgentID,
+    requestTimeout: 1000,
+    retryBaseInterval: 150
+  });
+  disableServiceWorkerEvents(
+    'https://example.org/1'
+  );
+  run_next_test();
+}
+
+add_task(function* test_webapps_cleardata() {
+  let db = PushServiceWebSocket.newPushDB();
+  do_register_cleanup(() => {return db.drop().then(_ => db.close());});
+
+  let testRecords = [{
+    scope: 'https://example.org/1',
+    originAttributes: { appId: 1 },
+    clearIf: { appId: 1, inBrowser: false },
+  }, {
+    scope: 'https://example.org/1',
+    originAttributes: { appId: 1, inBrowser: true },
+    clearIf: { appId: 1 },
+  }, {
+    scope: 'https://example.org/1',
+    originAttributes: { appId: 2, inBrowser: true },
+    clearIf: { appId: 2, inBrowser: true },
+  }, {
+    scope: 'https://example.org/2',
+    originAttributes: { appId: 1 },
+    clearIf: { appId: 1, inBrowser: false },
+  }, {
+    scope: 'https://example.org/2',
+    originAttributes: { appId: 2, inBrowser: true },
+    clearIf: { appId: 2, inBrowser: true },
+  }, {
+    scope: 'https://example.org/3',
+    originAttributes: { appId: 3, inBrowser: true },
+    clearIf: { inBrowser: true },
+  }, {
+    scope: 'https://example.org/3',
+    originAttributes: { appId: 4, inBrowser: true },
+    clearIf: { inBrowser: true },
+  }];
+
+  let unregisterDone;
+  let unregisterPromise = new Promise(resolve =>
+    unregisterDone = after(testRecords.length, resolve));
+
+  PushService.init({
+    serverURI: "wss://push.example.org",
+    networkInfo: new MockDesktopNetworkInfo(),
+    db,
+    makeWebSocket(uri) {
+      return new MockWebSocket(uri, {
+        onHello(data) {
+          equal(data.messageType, 'hello', 'Handshake: wrong message type');
+          equal(data.uaid, userAgentID, 'Handshake: wrong device ID');
+          this.serverSendMsg(JSON.stringify({
+            messageType: 'hello',
+            status: 200,
+            uaid: userAgentID
+          }));
+        },
+        onRegister(data) {
+          equal(data.messageType, 'register', 'Register: wrong message type');
+          this.serverSendMsg(JSON.stringify({
+            messageType: 'register',
+            status: 200,
+            channelID: data.channelID,
+            uaid: userAgentID,
+            pushEndpoint: 'https://example.com/update/' + Math.random(),
+          }));
+        },
+        onUnregister(data) {
+          unregisterDone();
+        },
+      });
+    }
+  });
+
+  yield Promise.all(testRecords.map(test =>
+    PushNotificationService.register(
+      test.scope,
+      ChromeUtils.originAttributesToSuffix(test.originAttributes)
+    )
+  ));
+
+  // Removes records for all scopes with the same app ID. Excludes records
+  // where `inBrowser` is true.
+  yield clearForPattern(testRecords, { appId: 1, inBrowser: false });
+
+  // Removes the remaining record for app ID 1, where `inBrowser` is true.
+  yield clearForPattern(testRecords, { appId: 1 });
+
+  // Removes all records for all scopes with the same app ID, where
+  // `inBrowser` is true.
+  yield clearForPattern(testRecords, { appId: 2, inBrowser: true });
+
+  // Removes all records where `inBrowser` is true.
+  yield clearForPattern(testRecords, { inBrowser: true });
+
+  equal(testRecords.length, 0, 'Should remove all test records');
+  yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
+    'Timed out waiting for unregister');
+});
deleted file mode 100644
--- a/dom/push/test/xpcshell/test_webapps_cleardata.js
+++ /dev/null
@@ -1,95 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-'use strict';
-
-const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
-
-const userAgentID = 'bd744428-f125-436a-b6d0-dd0c9845837f';
-
-function run_test() {
-  do_get_profile();
-  setPrefs({
-    userAgentID,
-    requestTimeout: 1000,
-    retryBaseInterval: 150
-  });
-  disableServiceWorkerEvents(
-    'https://example.org/1'
-  );
-  run_next_test();
-}
-
-add_task(function* test_webapps_cleardata() {
-  let db = PushServiceWebSocket.newPushDB();
-  do_register_cleanup(() => {return db.drop().then(_ => db.close());});
-
-  let unregisterDone;
-  let unregisterPromise = new Promise(resolve => unregisterDone = resolve);
-
-  PushService.init({
-    serverURI: "wss://push.example.org",
-    networkInfo: new MockDesktopNetworkInfo(),
-    db,
-    makeWebSocket(uri) {
-      return new MockWebSocket(uri, {
-        onHello(data) {
-          equal(data.messageType, 'hello', 'Handshake: wrong message type');
-          equal(data.uaid, userAgentID, 'Handshake: wrong device ID');
-          this.serverSendMsg(JSON.stringify({
-            messageType: 'hello',
-            status: 200,
-            uaid: userAgentID
-          }));
-        },
-        onRegister(data) {
-          equal(data.messageType, 'register', 'Register: wrong message type');
-          this.serverSendMsg(JSON.stringify({
-            messageType: 'register',
-            status: 200,
-            channelID: data.channelID,
-            uaid: userAgentID,
-            pushEndpoint: 'https://example.com/update/' + Math.random(),
-          }));
-        },
-        onUnregister(data) {
-          unregisterDone();
-        },
-      });
-    }
-  });
-
-  let registers = yield Promise.all([
-    PushNotificationService.register(
-      'https://example.org/1',
-      ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: false })),
-    PushNotificationService.register(
-      'https://example.org/1',
-      ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: true })),
-  ]);
-
-  Services.obs.notifyObservers(
-      { appId: 1, browserOnly: false,
-        QueryInterface: XPCOMUtils.generateQI([Ci.mozIApplicationClearPrivateDataParams])},
-      "webapps-clear-data", "");
-
-  let waitAWhile = new Promise(function(res) {
-    setTimeout(res, 2000);
-  });
-  yield waitAWhile;
-
-  let registration;
-  registration = yield PushNotificationService.registration(
-    'https://example.org/1',
-    ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: false }));
-  ok(!registration, 'Registration for { 1, false } should not exist.');
-
-  registration = yield PushNotificationService.registration(
-    'https://example.org/1',
-    ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: true }));
-  ok(registration, 'Registration for { 1, true } should still exist.');
-
-  yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
-    'Timed out waiting for unregister');
-});
-
--- a/dom/push/test/xpcshell/xpcshell.ini
+++ b/dom/push/test/xpcshell/xpcshell.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 head = head.js head-http2.js
 tail =
 # Push notifications and alarms are currently disabled on Android.
 skip-if = toolkit == 'android'
 
+[test_clear_origin_data.js]
 [test_drop_expired.js]
 [test_notification_ack.js]
 [test_notification_data.js]
 [test_notification_duplicate.js]
 [test_notification_error.js]
 [test_notification_incomplete.js]
 [test_notification_version_string.js]
 
@@ -34,17 +35,16 @@ run-sequentially = This will delete all 
 [test_registration_missing_scope.js]
 [test_registration_none.js]
 [test_registration_success.js]
 [test_unregister_empty_scope.js]
 [test_unregister_error.js]
 [test_unregister_invalid_json.js]
 [test_unregister_not_found.js]
 [test_unregister_success.js]
-[test_webapps_cleardata.js]
 [test_updateRecordNoEncryptionKeys_ws.js]
 [test_reconnect_retry.js]
 [test_retry_ws.js]
 #http2 test
 [test_resubscribe_4xxCode_http2.js]
 [test_resubscribe_5xxCode_http2.js]
 [test_resubscribe_listening_for_msg_error_http2.js]
 [test_register_5xxCode_http2.js]
--- a/dom/webidl/EventHandler.webidl
+++ b/dom/webidl/EventHandler.webidl
@@ -129,17 +129,17 @@ interface WindowEventHandlers {
            attribute EventHandler onhashchange;
            attribute EventHandler onlanguagechange;
            attribute EventHandler onmessage;
            attribute EventHandler onoffline;
            attribute EventHandler ononline;
            attribute EventHandler onpagehide;
            attribute EventHandler onpageshow;
            attribute EventHandler onpopstate;
-           //(Not implemented)attribute EventHandler onstorage;
+           attribute EventHandler onstorage;
            attribute EventHandler onunload;
 };
 
 // The spec has |attribute OnErrorEventHandler onerror;| on
 // GlobalEventHandlers, and calls the handler differently depending on
 // whether an ErrorEvent was fired. We don't do that, and until we do we'll
 // need to distinguish between onerror on Window or on nodes.
 
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -49,18 +49,20 @@ interface NavigatorID {
   // Everyone but WebKit/Blink supports this.  See bug 679971.
   [Exposed=Window]
   boolean taintEnabled(); // constant false
 };
 
 [NoInterfaceObject, Exposed=(Window,Worker)]
 interface NavigatorLanguage {
 
-  // These 2 values are cached. They are updated when pref
-  // intl.accept_languages is changed.
+  // These two attributes are cached because this interface is also implemented
+  // by Workernavigator and this way we don't have to go back to the
+  // main-thread from the worker thread anytime we need to retrieve them. They
+  // are updated when pref intl.accept_languages is changed.
 
   [Pure, Cached]
   readonly attribute DOMString? language;
   [Pure, Cached, Frozen]
   readonly attribute sequence<DOMString> languages;
 };
 
 [NoInterfaceObject, Exposed=(Window,Worker)]
--- a/dom/workers/DataStore.cpp
+++ b/dom/workers/DataStore.cpp
@@ -74,84 +74,88 @@ public:
 // on the main thread.
 class DataStoreGetStringRunnable final : public DataStoreRunnable
 {
   typedef void
   (DataStore::*FuncType)(nsAString&, ErrorResult&);
 
   FuncType mFunc;
   nsAString& mString;
-  ErrorResult& mRv;
 
 public:
   DataStoreGetStringRunnable(WorkerPrivate* aWorkerPrivate,
                              const nsMainThreadPtrHandle<DataStore>& aBackingStore,
                              FuncType aFunc,
-                             nsAString& aString,
-                             ErrorResult& aRv)
+                             nsAString& aString)
     : DataStoreRunnable(aWorkerPrivate, aBackingStore)
     , mFunc(aFunc)
     , mString(aString)
-    , mRv(aRv)
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
   }
 
 protected:
   virtual bool
   MainThreadRun() override
   {
     AssertIsOnMainThread();
 
+    ErrorResult rv;
     nsString string;
-    (mBackingStore.get()->*mFunc)(string, mRv);
+    (mBackingStore.get()->*mFunc)(string, rv);
     mString.Assign(string);
+
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+    }
+
     return true;
   }
 };
 
 // A DataStoreRunnable to run DataStore::GetReadOnly(...) on the main
 // thread.
 class DataStoreGetReadOnlyRunnable final : public DataStoreRunnable
 {
-  ErrorResult& mRv;
-
 public:
   bool mReadOnly;
 
-public:
   DataStoreGetReadOnlyRunnable(WorkerPrivate* aWorkerPrivate,
-                               const nsMainThreadPtrHandle<DataStore>& aBackingStore,
-                               ErrorResult& aRv)
+                               const nsMainThreadPtrHandle<DataStore>& aBackingStore)
     : DataStoreRunnable(aWorkerPrivate, aBackingStore)
-    , mRv(aRv)
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
   }
 
 protected:
   virtual bool
   MainThreadRun() override
   {
     AssertIsOnMainThread();
 
-    mReadOnly = mBackingStore->GetReadOnly(mRv);
+    ErrorResult rv;
+    mReadOnly = mBackingStore->GetReadOnly(rv);
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+    }
+
     return true;
   }
 };
 
 class DataStoreProxyRunnable : public DataStoreRunnable
 {
 public:
   DataStoreProxyRunnable(WorkerPrivate* aWorkerPrivate,
                          const nsMainThreadPtrHandle<DataStore>& aBackingStore,
                          Promise* aWorkerPromise)
     : DataStoreRunnable(aWorkerPrivate, aBackingStore)
+    , mFailed(false)
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
 
     mPromiseWorkerProxy =
       PromiseWorkerProxy::Create(aWorkerPrivate, aWorkerPromise);
   }
 
@@ -162,277 +166,313 @@ public:
     }
 
     // If the creation of mProxyWorkerProxy failed, the worker is terminating.
     // In this case we don't want to dispatch the runnable and we should stop
     // the promise chain here.
     return true;
   }
 
+  bool Failed() const
+  {
+    return mFailed;
+  }
+
 protected:
   RefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
+  bool mFailed;
 };
 
 // A DataStoreRunnable to run DataStore::Get(...) on the main thread.
 class DataStoreGetRunnable final : public DataStoreProxyRunnable
 {
   Sequence<OwningStringOrUnsignedLong> mId;
-  ErrorResult& mRv;
 
 public:
   DataStoreGetRunnable(WorkerPrivate* aWorkerPrivate,
                        const nsMainThreadPtrHandle<DataStore>& aBackingStore,
-                       Promise* aWorkerPromise,
-                       const Sequence<OwningStringOrUnsignedLong>& aId,
-                       ErrorResult& aRv)
+                       Promise* aWorkerPromise)
     : DataStoreProxyRunnable(aWorkerPrivate, aBackingStore, aWorkerPromise)
-    , mRv(aRv)
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
+  }
 
-    if (!mId.AppendElements(aId, fallible)) {
-      mRv.Throw(NS_ERROR_OUT_OF_MEMORY);
-    }
+  Sequence<OwningStringOrUnsignedLong>& Id()
+  {
+    return mId;
   }
 
 protected:
   virtual bool
   MainThreadRun() override
   {
     AssertIsOnMainThread();
 
-    RefPtr<Promise> promise = mBackingStore->Get(mId, mRv);
+    ErrorResult rv;
+    RefPtr<Promise> promise = mBackingStore->Get(mId, rv);
     promise->AppendNativeHandler(mPromiseWorkerProxy);
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+      mFailed = true;
+    }
+
     return true;
   }
 };
 
 // A DataStoreRunnable to run DataStore::Put(...) on the main thread.
 class DataStorePutRunnable final : public DataStoreProxyRunnable
                                  , public StructuredCloneHolder
 {
   const StringOrUnsignedLong& mId;
   const nsString mRevisionId;
-  ErrorResult& mRv;
+  nsresult mError;
 
 public:
   DataStorePutRunnable(WorkerPrivate* aWorkerPrivate,
                        const nsMainThreadPtrHandle<DataStore>& aBackingStore,
                        Promise* aWorkerPromise,
-                       JSContext* aCx,
-                       JS::Handle<JS::Value> aObj,
                        const StringOrUnsignedLong& aId,
-                       const nsAString& aRevisionId,
-                       ErrorResult& aRv)
+                       const nsAString& aRevisionId)
     : DataStoreProxyRunnable(aWorkerPrivate, aBackingStore, aWorkerPromise)
     , StructuredCloneHolder(CloningNotSupported, TransferringNotSupported,
                             SameProcessDifferentThread)
     , mId(aId)
     , mRevisionId(aRevisionId)
-    , mRv(aRv)
+    , mError(NS_OK)
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
+  }
 
-    // This needs to be structured cloned while it's still on the worker thread.
-    Write(aCx, aObj, mRv);
-    NS_WARN_IF(mRv.Failed());
+  nsresult ErrorCode() const
+  {
+    return mError;
   }
 
 protected:
   virtual bool
   MainThreadRun() override
   {
     AssertIsOnMainThread();
 
     // Initialise an AutoJSAPI with the target window.
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(mBackingStore->GetParentObject()))) {
-      mRv.Throw(NS_ERROR_UNEXPECTED);
+      mError = NS_ERROR_UNEXPECTED;
       return true;
     }
     JSContext* cx = jsapi.cx();
 
+    ErrorResult rv;
     JS::Rooted<JS::Value> value(cx);
-    Read(mBackingStore->GetParentObject(), cx, &value, mRv);
-    if (NS_WARN_IF(mRv.Failed())) {
+    Read(mBackingStore->GetParentObject(), cx, &value, rv);
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+      mError = NS_ERROR_DOM_DATA_CLONE_ERR;
       return true;
     }
 
-    RefPtr<Promise> promise = mBackingStore->Put(cx,
-                                                   value,
-                                                   mId,
-                                                   mRevisionId,
-                                                   mRv);
+    RefPtr<Promise> promise = mBackingStore->Put(cx, value, mId,
+                                                 mRevisionId, rv);
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+      mError = NS_ERROR_FAILURE;
+      return true;
+    }
+
     promise->AppendNativeHandler(mPromiseWorkerProxy);
     return true;
   }
 };
 
 // A DataStoreRunnable to run DataStore::Add(...) on the main thread.
 class DataStoreAddRunnable final : public DataStoreProxyRunnable
                                  , public StructuredCloneHolder
 {
   const Optional<StringOrUnsignedLong>& mId;
   const nsString mRevisionId;
-  ErrorResult& mRv;
+  nsresult mResult;
 
 public:
   DataStoreAddRunnable(WorkerPrivate* aWorkerPrivate,
                        const nsMainThreadPtrHandle<DataStore>& aBackingStore,
                        Promise* aWorkerPromise,
-                       JSContext* aCx,
-                       JS::Handle<JS::Value> aObj,
                        const Optional<StringOrUnsignedLong>& aId,
-                       const nsAString& aRevisionId,
-                       ErrorResult& aRv)
+                       const nsAString& aRevisionId)
     : DataStoreProxyRunnable(aWorkerPrivate, aBackingStore, aWorkerPromise)
     , StructuredCloneHolder(CloningNotSupported, TransferringNotSupported,
                             SameProcessDifferentThread)
     , mId(aId)
     , mRevisionId(aRevisionId)
-    , mRv(aRv)
+    , mResult(NS_OK)
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
+  }
 
-    // This needs to be structured cloned while it's still on the worker thread.
-    Write(aCx, aObj, mRv);
-    NS_WARN_IF(mRv.Failed());
+  nsresult ErrorCode() const
+  {
+    return mResult;
   }
 
 protected:
   virtual bool
   MainThreadRun() override
   {
     AssertIsOnMainThread();
 
     // Initialise an AutoJSAPI with the target window.
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(mBackingStore->GetParentObject()))) {
-      mRv.Throw(NS_ERROR_UNEXPECTED);
+      mResult = NS_ERROR_UNEXPECTED;
       return true;
     }
     JSContext* cx = jsapi.cx();
 
+    ErrorResult rv;
     JS::Rooted<JS::Value> value(cx);
-    Read(mBackingStore->GetParentObject(), cx, &value, mRv);
-    if (NS_WARN_IF(mRv.Failed())) {
+    Read(mBackingStore->GetParentObject(), cx, &value, rv);
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+      mResult = NS_ERROR_DOM_DATA_CLONE_ERR;
       return true;
     }
 
-    RefPtr<Promise> promise = mBackingStore->Add(cx,
-                                                   value,
-                                                   mId,
-                                                   mRevisionId,
-                                                   mRv);
+    RefPtr<Promise> promise = mBackingStore->Add(cx, value, mId,
+                                                 mRevisionId, rv);
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+      mResult = NS_ERROR_FAILURE;
+      return true;
+    }
+
     promise->AppendNativeHandler(mPromiseWorkerProxy);
     return true;
   }
 };
 
 // A DataStoreRunnable to run DataStore::Remove(...) on the main
 // thread.
 class DataStoreRemoveRunnable final : public DataStoreProxyRunnable
 {
   const StringOrUnsignedLong& mId;
   const nsString mRevisionId;
-  ErrorResult& mRv;
 
 public:
   DataStoreRemoveRunnable(WorkerPrivate* aWorkerPrivate,
                           const nsMainThreadPtrHandle<DataStore>& aBackingStore,
                           Promise* aWorkerPromise,
                           const StringOrUnsignedLong& aId,
-                          const nsAString& aRevisionId,
-                          ErrorResult& aRv)
+                          const nsAString& aRevisionId)
     : DataStoreProxyRunnable(aWorkerPrivate, aBackingStore, aWorkerPromise)
     , mId(aId)
     , mRevisionId(aRevisionId)
-    , mRv(aRv)
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
   }
 
 protected:
   virtual bool
   MainThreadRun() override
   {
     AssertIsOnMainThread();
 
-    RefPtr<Promise> promise = mBackingStore->Remove(mId, mRevisionId, mRv);
+    ErrorResult rv;
+    RefPtr<Promise> promise = mBackingStore->Remove(mId, mRevisionId, rv);
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+      mFailed = true;
+      return true;
+    }
+
     promise->AppendNativeHandler(mPromiseWorkerProxy);
     return true;
   }
 };
 
 // A DataStoreRunnable to run DataStore::Clear(...) on the main thread.
 class DataStoreClearRunnable final : public DataStoreProxyRunnable
 {
   const nsString mRevisionId;
-  ErrorResult& mRv;
 
 public:
   DataStoreClearRunnable(WorkerPrivate* aWorkerPrivate,
                          const nsMainThreadPtrHandle<DataStore>& aBackingStore,
                          Promise* aWorkerPromise,
-                         const nsAString& aRevisionId,
-                         ErrorResult& aRv)
+                         const nsAString& aRevisionId)
     : DataStoreProxyRunnable(aWorkerPrivate, aBackingStore, aWorkerPromise)
     , mRevisionId(aRevisionId)
-    , mRv(aRv)
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
   }
 
 protected:
   virtual bool
   MainThreadRun() override
   {
     AssertIsOnMainThread();
 
-    RefPtr<Promise> promise = mBackingStore->Clear(mRevisionId, mRv);
+    ErrorResult rv;
+    RefPtr<Promise> promise = mBackingStore->Clear(mRevisionId, rv);
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+      mFailed = true;
+      return true;
+    }
+
     promise->AppendNativeHandler(mPromiseWorkerProxy);
     return true;
   }
 };
 
 // A DataStoreRunnable to run DataStore::Sync(...) on the main thread.
 class DataStoreSyncStoreRunnable final : public DataStoreRunnable
 {
   WorkerDataStoreCursor* mWorkerCursor;
   const nsString mRevisionId;
-  ErrorResult& mRv;
+  bool mFailed;
 
 public:
   DataStoreSyncStoreRunnable(WorkerPrivate* aWorkerPrivate,
                              const nsMainThreadPtrHandle<DataStore>& aBackingStore,
                              WorkerDataStoreCursor* aWorkerCursor,
-                             const nsAString& aRevisionId,
-                             ErrorResult& aRv)
+                             const nsAString& aRevisionId)
     : DataStoreRunnable(aWorkerPrivate, aBackingStore)
     , mWorkerCursor(aWorkerCursor)
     , mRevisionId(aRevisionId)
-    , mRv(aRv)
+    , mFailed(false)
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
   }
 
+  bool Failed() const
+  {
+    return mFailed;
+  }
+
 protected:
   virtual bool
   MainThreadRun() override
   {
     AssertIsOnMainThread();
 
     // Point WorkerDataStoreCursor to DataStoreCursor.
-    RefPtr<DataStoreCursor> cursor = mBackingStore->Sync(mRevisionId, mRv);
+    ErrorResult rv;
+    RefPtr<DataStoreCursor> cursor = mBackingStore->Sync(mRevisionId, rv);
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+      mFailed = true;
+      return true;
+    }
+
     nsMainThreadPtrHandle<DataStoreCursor> backingCursor(
       new nsMainThreadPtrHolder<DataStoreCursor>(cursor));
     mWorkerCursor->SetBackingDataStoreCursor(backingCursor);
 
     return true;
   }
 };
 
@@ -442,46 +482,44 @@ WorkerDataStore::GetName(JSContext* aCx,
   WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
   MOZ_ASSERT(workerPrivate);
   workerPrivate->AssertIsOnWorkerThread();
 
   RefPtr<DataStoreGetStringRunnable> runnable =
     new DataStoreGetStringRunnable(workerPrivate,
                                    mBackingStore,
                                    &DataStore::GetName,
-                                   aName,
-                                   aRv);
+                                   aName);
   runnable->Dispatch(aCx);
 }
 
 void
 WorkerDataStore::GetOwner(JSContext* aCx, nsAString& aOwner, ErrorResult& aRv)
 {
   WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
   MOZ_ASSERT(workerPrivate);
   workerPrivate->AssertIsOnWorkerThread();
 
   RefPtr<DataStoreGetStringRunnable> runnable =
     new DataStoreGetStringRunnable(workerPrivate,
                                    mBackingStore,
                                    &DataStore::GetOwner,
-                                   aOwner,
-                                   aRv);
+                                   aOwner);
   runnable->Dispatch(aCx);
 }
 
 bool
 WorkerDataStore::GetReadOnly(JSContext* aCx, ErrorResult& aRv)
 {
   WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
   MOZ_ASSERT(workerPrivate);
   workerPrivate->AssertIsOnWorkerThread();
 
   RefPtr<DataStoreGetReadOnlyRunnable> runnable =
-    new DataStoreGetReadOnlyRunnable(workerPrivate, mBackingStore, aRv);
+    new DataStoreGetReadOnlyRunnable(workerPrivate, mBackingStore);
   runnable->Dispatch(aCx);
 
   return runnable->mReadOnly;
 }
 
 already_AddRefed<Promise>
 WorkerDataStore::Get(JSContext* aCx,
                      const Sequence<OwningStringOrUnsignedLong>& aId,
@@ -494,21 +532,30 @@ WorkerDataStore::Get(JSContext* aCx,
   RefPtr<Promise> promise = Promise::Create(workerPrivate->GlobalScope(), aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   RefPtr<DataStoreGetRunnable> runnable =
     new DataStoreGetRunnable(workerPrivate,
                              mBackingStore,
-                             promise,
-                             aId,
-                             aRv);
+                             promise);
+
+  if (!runnable->Id().AppendElements(aId, fallible)) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return nullptr;
+  }
+
   runnable->Dispatch(aCx);
 
+  if (runnable->Failed()) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 WorkerDataStore::Put(JSContext* aCx,
                      JS::Handle<JS::Value> aObj,
                      const StringOrUnsignedLong& aId,
                      const nsAString& aRevisionId,
@@ -522,23 +569,30 @@ WorkerDataStore::Put(JSContext* aCx,
   if (aRv.Failed()) {
     return nullptr;
   }
 
   RefPtr<DataStorePutRunnable> runnable =
     new DataStorePutRunnable(workerPrivate,
                              mBackingStore,
                              promise,
-                             aCx,
-                             aObj,
                              aId,
-                             aRevisionId,
-                             aRv);
+                             aRevisionId);
+  runnable->Write(aCx, aObj, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
   runnable->Dispatch(aCx);
 
+  if (NS_FAILED(runnable->ErrorCode())) {
+    aRv.Throw(runnable->ErrorCode());
+    return nullptr;
+  }
+
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 WorkerDataStore::Add(JSContext* aCx,
                      JS::Handle<JS::Value> aObj,
                      const Optional<StringOrUnsignedLong>& aId,
                      const nsAString& aRevisionId,
@@ -552,23 +606,30 @@ WorkerDataStore::Add(JSContext* aCx,
   if (aRv.Failed()) {
     return nullptr;
   }
 
   RefPtr<DataStoreAddRunnable> runnable =
     new DataStoreAddRunnable(workerPrivate,
                              mBackingStore,
                              promise,
-                             aCx,
-                             aObj,
                              aId,
-                             aRevisionId,
-                             aRv);
+                             aRevisionId);
+  runnable->Write(aCx, aObj, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
   runnable->Dispatch(aCx);
 
+  if (NS_FAILED(runnable->ErrorCode())) {
+    aRv.Throw(runnable->ErrorCode());
+    return nullptr;
+  }
+
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 WorkerDataStore::Remove(JSContext* aCx,
                         const StringOrUnsignedLong& aId,
                         const nsAString& aRevisionId,
                         ErrorResult& aRv)
@@ -582,20 +643,24 @@ WorkerDataStore::Remove(JSContext* aCx,
     return nullptr;
   }
 
   RefPtr<DataStoreRemoveRunnable> runnable =
     new DataStoreRemoveRunnable(workerPrivate,
                                 mBackingStore,
                                 promise,
                                 aId,
-                                aRevisionId,
-                                aRv);
+                                aRevisionId);
   runnable->Dispatch(aCx);
 
+  if (runnable->Failed()) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 WorkerDataStore::Clear(JSContext* aCx,
                        const nsAString& aRevisionId,
                        ErrorResult& aRv)
 {
@@ -607,66 +672,71 @@ WorkerDataStore::Clear(JSContext* aCx,
   if (aRv.Failed()) {
     return nullptr;
   }
 
   RefPtr<DataStoreClearRunnable> runnable =
     new DataStoreClearRunnable(workerPrivate,
                                mBackingStore,
                                promise,
-                               aRevisionId,
-                               aRv);
+                               aRevisionId);
   runnable->Dispatch(aCx);
 
+  if (runnable->Failed()) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
   return promise.forget();
 }
 
 void
 WorkerDataStore::GetRevisionId(JSContext* aCx,
                                nsAString& aRevisionId,
                                ErrorResult& aRv)
 {
   WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
   MOZ_ASSERT(workerPrivate);
   workerPrivate->AssertIsOnWorkerThread();
 
   RefPtr<DataStoreGetStringRunnable> runnable =
     new DataStoreGetStringRunnable(workerPrivate,
                                    mBackingStore,
                                    &DataStore::GetRevisionId,
-                                   aRevisionId,
-                                   aRv);
+                                   aRevisionId);
   runnable->Dispatch(aCx);
 }
 
 // A DataStoreRunnable to run DataStore::GetLength(...) on the main thread.
 class DataStoreGetLengthRunnable final : public DataStoreProxyRunnable
 {
-  ErrorResult& mRv;
-
 public:
   DataStoreGetLengthRunnable(WorkerPrivate* aWorkerPrivate,
                              const nsMainThreadPtrHandle<DataStore>& aBackingStore,
-                             Promise* aWorkerPromise,
-                             ErrorResult& aRv)
+                             Promise* aWorkerPromise)
     : DataStoreProxyRunnable(aWorkerPrivate, aBackingStore, aWorkerPromise)
-    , mRv(aRv)
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
   }
 
 protected:
   virtual bool
   MainThreadRun() override
   {
     AssertIsOnMainThread();
 
-    RefPtr<Promise> promise = mBackingStore->GetLength(mRv);
+    ErrorResult rv;
+    RefPtr<Promise> promise = mBackingStore->GetLength(rv);
     promise->AppendNativeHandler(mPromiseWorkerProxy);
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+      mFailed = true;
+    }
+
     return true;
   }
 };
 
 already_AddRefed<Promise>
 WorkerDataStore::GetLength(JSContext* aCx, ErrorResult& aRv)
 {
   WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
@@ -676,20 +746,24 @@ WorkerDataStore::GetLength(JSContext* aC
   RefPtr<Promise> promise = Promise::Create(workerPrivate->GlobalScope(), aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   RefPtr<DataStoreGetLengthRunnable> runnable =
     new DataStoreGetLengthRunnable(workerPrivate,
                                    mBackingStore,
-                                   promise,
-                                   aRv);
+                                   promise);
   runnable->Dispatch(aCx);
 
+  if (runnable->Failed()) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
   return promise.forget();
 }
 
 already_AddRefed<WorkerDataStoreCursor>
 WorkerDataStore::Sync(JSContext* aCx,
                       const nsAString& aRevisionId,
                       ErrorResult& aRv)
 {
@@ -704,20 +778,24 @@ WorkerDataStore::Sync(JSContext* aCx,
     new WorkerDataStoreCursor(this);
 
   // DataStoreSyncStoreRunnable will point the WorkerDataStoreCursor to the
   // DataStoreCursor created on the main thread.
   RefPtr<DataStoreSyncStoreRunnable> runnable =
     new DataStoreSyncStoreRunnable(workerPrivate,
                                    mBackingStore,
                                    workerCursor,
-                                   aRevisionId,
-                                   aRv);
+                                   aRevisionId);
   runnable->Dispatch(aCx);
 
+  if (runnable->Failed()) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
   return workerCursor.forget();
 }
 
 void
 WorkerDataStore::SetBackingDataStore(
   const nsMainThreadPtrHandle<DataStore>& aBackingStore)
 {
   mBackingStore = aBackingStore;
--- a/dom/workers/SharedWorker.cpp
+++ b/dom/workers/SharedWorker.cpp
@@ -131,17 +131,16 @@ SharedWorker::QueueEvent(nsIDOMEvent* aE
 
 void
 SharedWorker::Close()
 {
   AssertIsOnMainThread();
 
   if (mMessagePort) {
     mMessagePort->Close();
-    mMessagePort = nullptr;
   }
 }
 
 void
 SharedWorker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
                           const Optional<Sequence<JS::Value>>& aTransferable,
                           ErrorResult& aRv)
 {
--- a/dom/workers/test/sharedWorker_ports.js
+++ b/dom/workers/test/sharedWorker_ports.js
@@ -12,12 +12,13 @@ onconnect = function(evt) {
                         test: (evtFromPort.ports.length == 1),
                         msg: "1 port transferred"});
 
       evtFromPort.ports[0].onmessage = function(evtFromPort2) {
         port.postMessage({type: "status",
                           test: (evtFromPort2.data.type == "connected"),
                           msg: "The original message received" });
         port.postMessage({type: "finish"});
+        close();
       }
     }
   }
 }
--- a/dom/workers/test/test_sharedWorker_ports.html
+++ b/dom/workers/test/test_sharedWorker_ports.html
@@ -23,16 +23,19 @@ sw1.port.onmessage = function(event) {
   }
 
   if (event.data.type == "status") {
     ok(event.data.test, event.data.msg);
     return;
   }
 
   if (event.data.type == "finish") {
+    info("Finished!");
+    ok(sw1.port, "The port still exists");
+    sw1.port.foo = sw1; // Just a test to see if we leak.
     SimpleTest.finish();
   }
 }
 
 SimpleTest.waitForExplicitFinish();
     </script>
   </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/gfx/tests/crashtests/1225125-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<body>
+
+<div style="mix-blend-mode: saturation; margin-right: 1187px; transform: translateX(2px);">
+    <div style="border-style: outset; mix-blend-mode: color-dodge; display: inherit; padding: 1760px; float: right;"></div>
+    <div style="height: 2000px; overflow: auto;"></div>
+</div>
+
+</body>
+</html>
--- a/gfx/tests/crashtests/crashtests.list
+++ b/gfx/tests/crashtests/crashtests.list
@@ -121,8 +121,9 @@ load 893572-2.html
 load 893572-3.html
 load 893572-4.html
 pref(layers.force-active,true) load 914457-1.html
 load 944579.svg
 load 944579.html
 pref(security.fileuri.strict_origin_policy,false) load 950000.html
 load 1034403-1.html
 load balinese-letter-spacing.html
+load 1225125-1.html
--- a/js/public/TrackedOptimizationInfo.h
+++ b/js/public/TrackedOptimizationInfo.h
@@ -20,16 +20,17 @@ namespace JS {
     _(GetProp_SimdGetter)                               \
     _(GetProp_TypedObject)                              \
     _(GetProp_DefiniteSlot)                             \
     _(GetProp_Unboxed)                                  \
     _(GetProp_CommonGetter)                             \
     _(GetProp_InlineAccess)                             \
     _(GetProp_Innerize)                                 \
     _(GetProp_InlineCache)                              \
+    _(GetProp_SharedCache)                              \
                                                         \
     _(SetProp_CommonSetter)                             \
     _(SetProp_TypedObject)                              \
     _(SetProp_DefiniteSlot)                             \
     _(SetProp_Unboxed)                                  \
     _(SetProp_InlineAccess)                             \
     _(SetProp_InlineCache)                              \
                                                         \
new file mode 100644
--- /dev/null
+++ b/js/public/UbiNodeDominatorTree.h
@@ -0,0 +1,354 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef js_UbiNodeDominatorTree_h
+#define js_UbiNodeDominatorTree_h
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Move.h"
+#include "mozilla/UniquePtr.h"
+
+#include "jsalloc.h"
+
+#include "js/UbiNode.h"
+#include "js/UbiNodePostOrder.h"
+#include "js/Utility.h"
+#include "js/Vector.h"
+
+namespace JS {
+namespace ubi {
+
+/**
+ * In a directed graph with a root node `R`, a node `A` is said to "dominate" a
+ * node `B` iff every path from `R` to `B` contains `A`. A node `A` is said to
+ * be the "immediate dominator" of a node `B` iff it dominates `B`, is not `B`
+ * itself, and does not dominate any other nodes which also dominate `B` in
+ * turn.
+ *
+ * If we take every node from a graph `G` and create a new graph `T` with edges
+ * to each node from its immediate dominator, then `T` is a tree (each node has
+ * only one immediate dominator, or none if it is the root). This tree is called
+ * a "dominator tree".
+ *
+ * This class represents a dominator tree constructed from a `JS::ubi::Node`
+ * heap graph. The domination relationship and dominator trees are useful tools
+ * for analyzing heap graphs because they tell you:
+ *
+ *   - Exactly what could be reclaimed by the GC if some node `A` became
+ *     unreachable: those nodes which are dominated by `A`,
+ *
+ *   - The "retained size" of a node in the heap graph, in contrast to its
+ *     "shallow size". The "shallow size" is the space taken by a node itself,
+ *     not counting anything it references. The "retained size" of a node is its
+ *     shallow size plus the size of all the things that would be collected if
+ *     the original node wasn't (directly or indirectly) referencing them. In
+ *     other words, the retained size is the shallow size of a node plus the
+ *     shallow sizes of every other node it dominates. For example, the root
+ *     node in a binary tree might have a small shallow size that does not take
+ *     up much space itself, but it dominates the rest of the binary tree and
+ *     its retained size is therefore significant (assuming no external
+ *     references into the tree).
+ *
+ * The simple, engineered algorithm presented in "A Simple, Fast Dominance
+ * Algorithm" by Cooper el al[0] is used to find dominators and construct the
+ * dominator tree. This algorithm runs in O(n^2) time, but is faster in practice
+ * than alternative algorithms with better theoretical running times, such as
+ * Lengauer-Tarjan which runs in O(e * log(n)). The big caveat to that statement
+ * is that Cooper et al found it is faster in practice *on control flow graphs*
+ * and I'm not convinced that this property also holds on *heap* graphs. That
+ * said, the implementation of this algorithm is *much* simpler than
+ * Lengauer-Tarjan and has been found to be fast enough at least for the time
+ * being.
+ *
+ * [0]: http://www.cs.rice.edu/~keith/EMBED/dom.pdf
+ */
+class JS_PUBLIC_API(DominatorTree)
+{
+  private:
+    // Type aliases.
+    using NodeSet = js::HashSet<Node, js::DefaultHasher<Node>, js::SystemAllocPolicy>;
+    using NodeSetPtr = mozilla::UniquePtr<NodeSet, JS::DeletePolicy<NodeSet>>;
+    using PredecessorSets = js::HashMap<Node, NodeSetPtr, js::DefaultHasher<Node>,
+                                        js::SystemAllocPolicy>;
+    using NodeToIndexMap = js::HashMap<Node, uint32_t, js::DefaultHasher<Node>,
+                                       js::SystemAllocPolicy>;
+
+  private:
+    // Data members.
+    mozilla::Vector<Node> postOrder;
+    NodeToIndexMap nodeToPostOrderIndex;
+    mozilla::Vector<uint32_t> doms;
+
+  private:
+    // We use `UNDEFINED` as a sentinel value in the `doms` vector to signal
+    // that we haven't found any dominators for the node at the corresponding
+    // index in `postOrder` yet.
+    static const uint32_t UNDEFINED = UINT32_MAX;
+
+    DominatorTree(mozilla::Vector<Node>&& postOrder, NodeToIndexMap&& nodeToPostOrderIndex,
+                  mozilla::Vector<uint32_t>&& doms)
+        : postOrder(mozilla::Move(postOrder))
+        , nodeToPostOrderIndex(mozilla::Move(nodeToPostOrderIndex))
+        , doms(mozilla::Move(doms))
+    { }
+
+    static uint32_t intersect(mozilla::Vector<uint32_t>& doms, uint32_t finger1, uint32_t finger2) {
+        while (finger1 != finger2) {
+            if (finger1 < finger2)
+                finger1 = doms[finger1];
+            else if (finger2 < finger1)
+                finger2 = doms[finger2];
+        }
+        return finger1;
+    }
+
+    // Do the post order traversal of the heap graph and populate our
+    // predecessor sets.
+    static bool doTraversal(JSRuntime* rt, AutoCheckCannotGC& noGC, const Node& root,
+                            mozilla::Vector<Node>& postOrder, PredecessorSets& predecessorSets) {
+        uint32_t nodeCount = 0;
+        auto onNode = [&](const Node& node) {
+            nodeCount++;
+            if (MOZ_UNLIKELY(nodeCount == UINT32_MAX))
+                return false;
+            return postOrder.append(node);
+        };
+
+        auto onEdge = [&](const Node& origin, const Edge& edge) {
+            auto p = predecessorSets.lookupForAdd(edge.referent);
+            if (!p) {
+                auto set = rt->make_unique<NodeSet>();
+                if (!set ||
+                    !set->init() ||
+                    !predecessorSets.add(p, edge.referent, mozilla::Move(set)))
+                {
+                    return false;
+                }
+            }
+            MOZ_ASSERT(p && p->value());
+            return p->value()->put(origin);
+        };
+
+        PostOrder traversal(rt, noGC);
+        return traversal.init() &&
+               traversal.addStart(root) &&
+               traversal.traverse(onNode, onEdge);
+    }
+
+    // Populates the given `map` with an entry for each node to its index in
+    // `postOrder`.
+    static bool mapNodesToTheirIndices(mozilla::Vector<Node>& postOrder, NodeToIndexMap& map) {
+        MOZ_ASSERT(!map.initialized());
+        MOZ_ASSERT(postOrder.length() < UINT32_MAX);
+        uint32_t length = postOrder.length();
+        if (!map.init(length))
+            return false;
+        for (uint32_t i = 0; i < length; i++)
+            map.putNewInfallible(postOrder[i], i);
+        return true;
+    }
+
+    // Convert the Node -> NodeSet predecessorSets to a index -> Vector<index>
+    // form.
+    static bool convertPredecessorSetsToVectors(
+        const Node& root,
+        mozilla::Vector<Node>& postOrder,
+        PredecessorSets& predecessorSets,
+        NodeToIndexMap& nodeToPostOrderIndex,
+        mozilla::Vector<mozilla::Vector<uint32_t>>& predecessorVectors)
+    {
+        MOZ_ASSERT(postOrder.length() < UINT32_MAX);
+        uint32_t length = postOrder.length();
+
+        MOZ_ASSERT(predecessorVectors.length() == 0);
+        if (!predecessorVectors.growBy(length))
+            return false;
+
+        for (uint32_t i = 0; i < length - 1; i++) {
+            auto& node = postOrder[i];
+            MOZ_ASSERT(node != root,
+                       "Only the last node should be root, since this was a post order traversal.");
+
+            auto ptr = predecessorSets.lookup(node);
+            MOZ_ASSERT(ptr,
+                       "Because this isn't the root, it had better have predecessors, or else how "
+                       "did we even find it.");
+
+            auto& predecessors = ptr->value();
+            if (!predecessorVectors[i].reserve(predecessors->count()))
+                return false;
+            for (auto range = predecessors->all(); !range.empty(); range.popFront()) {
+                auto ptr = nodeToPostOrderIndex.lookup(range.front());
+                MOZ_ASSERT(ptr);
+                predecessorVectors[i].infallibleAppend(ptr->value());
+            }
+        }
+        predecessorSets.finish();
+        return true;
+    }
+
+    // Initialize `doms` such that the immediate dominator of the `root` is the
+    // `root` itself and all others are `UNDEFINED`.
+    static bool initializeDominators(mozilla::Vector<uint32_t>& doms, uint32_t length) {
+        MOZ_ASSERT(doms.length() == 0);
+        if (!doms.growByUninitialized(length))
+            return false;
+        doms[length - 1] = length - 1;
+        for (uint32_t i = 0; i < length - 1; i++)
+            doms[i] = UNDEFINED;
+        return true;
+    }
+
+    void assertSanity() const {
+        MOZ_ASSERT(postOrder.length() == doms.length());
+        MOZ_ASSERT(postOrder.length() == nodeToPostOrderIndex.count());
+    }
+
+  public:
+    // DominatorTree is not copy-able.
+    DominatorTree(const DominatorTree&) = delete;
+    DominatorTree& operator=(const DominatorTree&) = delete;
+
+    // DominatorTree is move-able.
+    DominatorTree(DominatorTree&& rhs)
+      : postOrder(mozilla::Move(rhs.postOrder))
+      , nodeToPostOrderIndex(mozilla::Move(rhs.nodeToPostOrderIndex))
+      , doms(mozilla::Move(rhs.doms))
+    {
+        MOZ_ASSERT(this != &rhs, "self-move is not allowed");
+    }
+    DominatorTree& operator=(DominatorTree&& rhs) {
+        this->~DominatorTree();
+        new (this) DominatorTree(mozilla::Move(rhs));
+        return *this;
+    }
+
+    /**
+     * Construct a `DominatorTree` of the heap graph visible from `root`. The
+     * `root` is also used as the root of the resulting dominator tree.
+     *
+     * The resulting `DominatorTree` instance must not outlive the
+     * `JS::ubi::Node` graph it was constructed from.
+     *
+     *   - For `JS::ubi::Node` graphs backed by the live heap graph, this means
+     *     that the `DominatorTree`'s lifetime _must_ be contained within the
+     *     scope of the provided `AutoCheckCannotGC` reference because a GC will
+     *     invalidate the nodes.
+     *
+     *   - For `JS::ubi::Node` graphs backed by some other offline structure
+     *     provided by the embedder, the resulting `DominatorTree`'s lifetime is
+     *     bounded by that offline structure's lifetime.
+     *
+     * In practice, this means that within SpiderMonkey we must treat
+     * `DominatorTree` as if it were backed by the live heap graph and trust
+     * that embedders with knowledge of the graph's implementation will do the
+     * Right Thing.
+     *
+     * Returns `mozilla::Nothing()` on OOM failure. It is the caller's
+     * responsibility to handle and report the OOM.
+     */
+    static mozilla::Maybe<DominatorTree>
+    Create(JSRuntime* rt, AutoCheckCannotGC& noGC, const Node& root) {
+        mozilla::Vector<Node> postOrder;
+        PredecessorSets predecessorSets;
+        if (!predecessorSets.init() || !doTraversal(rt, noGC, root, postOrder, predecessorSets))
+            return mozilla::Nothing();
+
+        MOZ_ASSERT(postOrder.length() < UINT32_MAX);
+        uint32_t length = postOrder.length();
+        MOZ_ASSERT(postOrder[length - 1] == root);
+
+        // From here on out we wish to avoid hash table lookups, and we use
+        // indices into `postOrder` instead of actual nodes wherever
+        // possible. This greatly improves the performance of this
+        // implementation, but we have to pay a little bit of upfront cost to
+        // convert our data structures to play along first.
+
+        NodeToIndexMap nodeToPostOrderIndex;
+        if (!mapNodesToTheirIndices(postOrder, nodeToPostOrderIndex))
+            return mozilla::Nothing();
+
+        mozilla::Vector<mozilla::Vector<uint32_t>> predecessorVectors;
+        if (!convertPredecessorSetsToVectors(root, postOrder, predecessorSets, nodeToPostOrderIndex,
+                                             predecessorVectors))
+            return mozilla::Nothing();
+
+        mozilla::Vector<uint32_t> doms;
+        if (!initializeDominators(doms, length))
+            return mozilla::Nothing();
+
+        bool changed = true;
+        while (changed) {
+            changed = false;
+
+            // Iterate over the non-root nodes in reverse post order.
+            for (uint32_t indexPlusOne = length - 1; indexPlusOne > 0; indexPlusOne--) {
+                MOZ_ASSERT(postOrder[indexPlusOne - 1] != root);
+
+                // Take the intersection of every predecessor's dominator set;
+                // that is the current best guess at the immediate dominator for
+                // this node.
+
+                uint32_t newIDomIdx = UNDEFINED;
+
+                auto& predecessors = predecessorVectors[indexPlusOne - 1];
+                auto range = predecessors.all();
+                for ( ; !range.empty(); range.popFront()) {
+                    auto idx = range.front();
+                    if (doms[idx] != UNDEFINED) {
+                        newIDomIdx = idx;
+                        break;
+                    }
+                }
+
+                MOZ_ASSERT(newIDomIdx != UNDEFINED,
+                           "Because the root is initialized to dominate itself and is the first "
+                           "node in every path, there must exist a predecessor to this node that "
+                           "also has a dominator.");
+
+                for ( ; !range.empty(); range.popFront()) {
+                    auto idx = range.front();
+                    if (doms[idx] != UNDEFINED)
+                        newIDomIdx = intersect(doms, newIDomIdx, idx);
+                }
+
+                // If the immediate dominator changed, we will have to do
+                // another pass of the outer while loop to continue the forward
+                // dataflow.
+                if (newIDomIdx != doms[indexPlusOne - 1]) {
+                    doms[indexPlusOne - 1] = newIDomIdx;
+                    changed = true;
+                }
+            }
+        }
+
+        return mozilla::Some(DominatorTree(mozilla::Move(postOrder),
+                                           mozilla::Move(nodeToPostOrderIndex),
+                                           mozilla::Move(doms)));
+    }
+
+    /**
+     * Return the immediate dominator of the given `node`. If `node` was not
+     * reachable from the `root` that this dominator tree was constructed from,
+     * then return the null `JS::ubi::Node`.
+     */
+    Node getImmediateDominator(const Node& node) const {
+        assertSanity();
+        auto ptr = nodeToPostOrderIndex.lookup(node);
+        if (!ptr)
+            return Node();
+
+        auto idx = ptr->value();
+        MOZ_ASSERT(idx < postOrder.length());
+        return postOrder[doms[idx]];
+    }
+};
+
+} // namespace ubi
+} // namespace JS
+
+#endif // js_UbiNodeDominatorTree_h
--- a/js/public/UbiNodePostOrder.h
+++ b/js/public/UbiNodePostOrder.h
@@ -3,53 +3,65 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef js_UbiNodePostOrder_h
 #define js_UbiNodePostOrder_h
 
 #include "mozilla/DebugOnly.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/Move.h"
 
 #include "jsalloc.h"
 
 #include "js/UbiNode.h"
 #include "js/Utility.h"
 #include "js/Vector.h"
 
 namespace JS {
 namespace ubi {
 
-// A post-order depth-first traversal of `ubi::Node` graphs.
-//
-// NB: This traversal visits each node reachable from the start set exactly
-// once, and does not visit edges at all. Therefore, this traversal would be a
-// very poor choice for recording multiple paths to the same node, for example.
-// If your analysis needs to consider edges, use `JS::ubi::BreadthFirst`
-// instead.
-//
-// No GC may occur while an instance of `PostOrder` is live.
-//
-// The `Visitor` type provided to `PostOrder::traverse` must have the following
-// member:
-//
-//   bool operator()(Node& node)
-//
-//     The visitor method. This method is called once for each `node` reachable
-//     from the start set in post-order.
-//
-//     The visitor function should return true on success, or false if an error
-//     occurs. A false return value terminates the traversal immediately, and
-//     causes `PostOrder::traverse` to return false.
+/**
+ * A post-order depth-first traversal of `ubi::Node` graphs.
+ *
+ * No GC may occur while an instance of `PostOrder` is live.
+ *
+ * The `NodeVisitor` type provided to `PostOrder::traverse` must have the
+ * following member:
+ *
+ *   bool operator()(Node& node)
+ *
+ *     The node visitor method. This method is called once for each `node`
+ *     reachable from the start set in post-order.
+ *
+ *     This visitor function should return true on success, or false if an error
+ *     occurs. A false return value terminates the traversal immediately, and
+ *     causes `PostOrder::traverse` to return false.
+ *
+ * The `EdgeVisitor` type provided to `PostOrder::traverse` must have the
+ * following member:
+ *
+ *   bool operator()(Node& origin, Edge& edge)
+ *
+ *     The edge visitor method. This method is called once for each outgoing
+ *     `edge` from `origin` that is reachable from the start set.
+ *
+ *     NB: UNLIKE NODES, THERE IS NO GUARANTEED ORDER IN WHICH EDGES AND THEIR
+ *     ORIGINS ARE VISITED!
+ *
+ *     This visitor function should return true on success, or false if an error
+ *     occurs. A false return value terminates the traversal immediately, and
+ *     causes `PostOrder::traverse` to return false.
+ */
 struct PostOrder {
   private:
     struct OriginAndEdges {
-        Node       origin;
-        EdgeVector edges;
+        Node                 origin;
+        EdgeVector           edges;
 
         OriginAndEdges(const Node& node, EdgeVector&& edges)
           : origin(node)
           , edges(mozilla::Move(edges))
         { }
 
         OriginAndEdges(const OriginAndEdges& rhs) = delete;
         OriginAndEdges& operator=(const OriginAndEdges& rhs) = delete;
@@ -115,49 +127,57 @@ struct PostOrder {
     // as many starting points as you like. Returns false on OOM.
     bool addStart(const Node& node) {
         if (!seen.put(node))
             return false;
         return pushForTraversing(node);
     }
 
     // Traverse the graph in post-order, starting with the set of nodes passed
-    // to `addStart` and applying `visitor::operator()` for each node in
-    // the graph, as described above.
+    // to `addStart` and applying `onNode::operator()` for each node in the
+    // graph and `onEdge::operator()` for each edge in the graph, as described
+    // above.
     //
     // This should be called only once per instance of this class.
     //
-    // Return false on OOM or error return from `visitor::operator()`.
-    template<typename Visitor>
-    bool traverse(Visitor visitor) {
+    // Return false on OOM or error return from `onNode::operator()` or
+    // `onEdge::operator()`.
+    template<typename NodeVisitor, typename EdgeVisitor>
+    bool traverse(NodeVisitor onNode, EdgeVisitor onEdge) {
         MOZ_ASSERT(!traversed, "Can only traverse() once!");
         traversed = true;
 
         while (!stack.empty()) {
             auto& origin = stack.back().origin;
             auto& edges = stack.back().edges;
 
             if (edges.empty()) {
-                if (!visitor(origin))
+                if (!onNode(origin))
                     return false;
                 stack.popBack();
                 continue;
             }
 
             Edge edge = mozilla::Move(edges.back());
             edges.popBack();
 
+            if (!onEdge(origin, edge))
+                return false;
+
             auto ptr = seen.lookupForAdd(edge.referent);
             // We've already seen this node, don't follow its edges.
             if (ptr)
                 continue;
 
             // Mark the referent as seen and follow its edges.
-            if (!seen.add(ptr, edge.referent) || !pushForTraversing(edge.referent))
+            if (!seen.add(ptr, edge.referent) ||
+                !pushForTraversing(edge.referent))
+            {
                 return false;
+            }
         }
 
         return true;
     }
 };
 
 } // namespace ubi
 } // namespace JS
--- a/js/public/WeakMapPtr.h
+++ b/js/public/WeakMapPtr.h
@@ -28,18 +28,16 @@ class JS_PUBLIC_API(WeakMapPtr)
     bool initialized() { return ptr != nullptr; }
     void destroy();
     virtual ~WeakMapPtr() { MOZ_ASSERT(!initialized()); }
     void trace(JSTracer* tracer);
 
     V lookup(const K& key);
     bool put(JSContext* cx, const K& key, const V& value);
 
-    static void keyMarkCallback(JSTracer* trc, K key, void* data);
-
   private:
     void* ptr;
 
     // WeakMapPtr is neither copyable nor assignable.
     WeakMapPtr(const WeakMapPtr& wmp) = delete;
     WeakMapPtr& operator=(const WeakMapPtr& wmp) = delete;
 };
 
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -403,19 +403,26 @@ struct Zone : public JS::shadow::Zone,
     }
 
     // Remove any unique id associated with this Cell.
     void removeUniqueId(js::gc::Cell* cell) {
         MOZ_ASSERT(js::CurrentThreadCanAccessZone(this));
         uniqueIds_.remove(cell);
     }
 
-    // Off-thread parsing should not result in any UIDs being created.
-    void assertNoUniqueIdsInZone() const {
-        MOZ_ASSERT(uniqueIds_.count() == 0);
+    // When finished parsing off-thread, transfer any UIDs we created in the
+    // off-thread zone into the target zone.
+    void adoptUniqueIds(JS::Zone* source) {
+        js::AutoEnterOOMUnsafeRegion oomUnsafe;
+        for (js::gc::UniqueIdMap::Enum e(source->uniqueIds_); !e.empty(); e.popFront()) {
+            MOZ_ASSERT(!uniqueIds_.has(e.front().key()));
+            if (!uniqueIds_.put(e.front().key(), e.front().value()))
+                oomUnsafe.crash("failed to transfer unique ids from off-main-thread");
+        }
+        source->uniqueIds_.clear();
     }
 
 #ifdef JSGC_HASH_TABLE_CHECKS
     // Assert that the UniqueId table has been redirected successfully.
     void checkUniqueIdTableAfterMovingGC();
 #endif
 
   private:
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -761,25 +761,27 @@ bool
 BaselineCompiler::emitArgumentTypeChecks()
 {
     if (!function())
         return true;
 
     frame.pushThis();
     frame.popRegsAndSync(1);
 
-    ICTypeMonitor_Fallback::Compiler compiler(cx, (uint32_t) 0);
+    ICTypeMonitor_Fallback::Compiler compiler(cx, ICStubCompiler::Engine::Baseline,
+                                              (uint32_t) 0);
     if (!emitNonOpIC(compiler.getStub(&stubSpace_)))
         return false;
 
     for (size_t i = 0; i < function()->nargs(); i++) {
         frame.pushArg(i);
         frame.popRegsAndSync(1);
 
-        ICTypeMonitor_Fallback::Compiler compiler(cx, i + 1);
+        ICTypeMonitor_Fallback::Compiler compiler(cx, ICStubCompiler::Engine::Baseline,
+                                                  i + 1);
         if (!emitNonOpIC(compiler.getStub(&stubSpace_)))
             return false;
     }
 
     return true;
 }
 
 bool
@@ -2253,17 +2255,17 @@ BaselineCompiler::emit_JSOP_STRICTSETGNA
 
 bool
 BaselineCompiler::emit_JSOP_GETPROP()
 {
     // Keep object in R0.
     frame.popRegsAndSync(1);
 
     // Call IC.
-    ICGetProp_Fallback::Compiler compiler(cx);
+    ICGetProp_Fallback::Compiler compiler(cx, ICStubCompiler::Engine::Baseline);
     if (!emitOpIC(compiler.getStub(&stubSpace_)))
         return false;
 
     // Mark R0 as pushed stack value.
     frame.push(R0);
     return true;
 }
 
@@ -2356,17 +2358,18 @@ BaselineCompiler::emit_JSOP_GETALIASEDVA
 {
     frame.syncStack(0);
 
     Address address = getScopeCoordinateAddress(R0.scratchReg());
     masm.loadValue(address, R0);
 
     if (ionCompileable_) {
         // No need to monitor types if we know Ion can't compile this script.
-        ICTypeMonitor_Fallback::Compiler compiler(cx, (ICMonitoredFallbackStub*) nullptr);
+        ICTypeMonitor_Fallback::Compiler compiler(cx, ICStubCompiler::Engine::Baseline,
+                                                  (ICMonitoredFallbackStub*) nullptr);
         if (!emitOpIC(compiler.getStub(&stubSpace_)))
             return false;
     }
 
     frame.push(R0);
     return true;
 }
 
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -41,54 +41,16 @@
 #include "vm/StringObject-inl.h"
 #include "vm/UnboxedObject-inl.h"
 
 using mozilla::DebugOnly;
 
 namespace js {
 namespace jit {
 
-static void
-GuardReceiverObject(MacroAssembler& masm, ReceiverGuard guard,
-                    Register object, Register scratch,
-                    size_t receiverGuardOffset, Label* failure)
-{
-    Address groupAddress(ICStubReg, receiverGuardOffset + HeapReceiverGuard::offsetOfGroup());
-    Address shapeAddress(ICStubReg, receiverGuardOffset + HeapReceiverGuard::offsetOfShape());
-    Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando());
-
-    if (guard.group) {
-        masm.loadPtr(groupAddress, scratch);
-        masm.branchTestObjGroup(Assembler::NotEqual, object, scratch, failure);
-
-        if (guard.group->clasp() == &UnboxedPlainObject::class_ && !guard.shape) {
-            // Guard the unboxed object has no expando object.
-            masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), failure);
-        }
-    }
-
-    if (guard.shape) {
-        masm.loadPtr(shapeAddress, scratch);
-        if (guard.group && guard.group->clasp() == &UnboxedPlainObject::class_) {
-            // Guard the unboxed object has a matching expando object.
-            masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failure);
-            Label done;
-            masm.push(object);
-            masm.loadPtr(expandoAddress, object);
-            masm.branchTestObjShape(Assembler::Equal, object, scratch, &done);
-            masm.pop(object);
-            masm.jump(failure);
-            masm.bind(&done);
-            masm.pop(object);
-        } else {
-            masm.branchTestObjShape(Assembler::NotEqual, object, scratch, failure);
-        }
-    }
-}
-
 //
 // WarmUpCounter_Fallback
 //
 
 static bool
 EnsureCanEnterIon(JSContext* cx, ICWarmUpCounter_Fallback* stub, BaselineFrame* frame,
                   HandleScript script, jsbytecode* pc, void** jitcodePtr)
 {
@@ -372,379 +334,16 @@ ICWarmUpCounter_Fallback::Compiler::gene
     masm.jump(scratchReg);
 
     // No jitcode available, do nothing.
     masm.bind(&noCompiledCode);
     EmitReturnFromIC(masm);
     return true;
 }
 
-
-//
-// TypeMonitor_Fallback
-//
-
-bool
-ICTypeMonitor_Fallback::addMonitorStubForValue(JSContext* cx, JSScript* script, HandleValue val)
-{
-    bool wasDetachedMonitorChain = lastMonitorStubPtrAddr_ == nullptr;
-    MOZ_ASSERT_IF(wasDetachedMonitorChain, numOptimizedMonitorStubs_ == 0);
-
-    if (numOptimizedMonitorStubs_ >= MAX_OPTIMIZED_STUBS) {
-        // TODO: if the TypeSet becomes unknown or has the AnyObject type,
-        // replace stubs with a single stub to handle these.
-        return true;
-    }
-
-    if (val.isPrimitive()) {
-        if (val.isMagic(JS_UNINITIALIZED_LEXICAL))
-            return true;
-        MOZ_ASSERT(!val.isMagic());
-        JSValueType type = val.isDouble() ? JSVAL_TYPE_DOUBLE : val.extractNonDoubleType();
-
-        // Check for existing TypeMonitor stub.
-        ICTypeMonitor_PrimitiveSet* existingStub = nullptr;
-        for (ICStubConstIterator iter(firstMonitorStub()); !iter.atEnd(); iter++) {
-            if (iter->isTypeMonitor_PrimitiveSet()) {
-                existingStub = iter->toTypeMonitor_PrimitiveSet();
-                if (existingStub->containsType(type))
-                    return true;
-            }
-        }
-
-        ICTypeMonitor_PrimitiveSet::Compiler compiler(cx, existingStub, type);
-        ICStub* stub = existingStub ? compiler.updateStub()
-                                    : compiler.getStub(compiler.getStubSpace(script));
-        if (!stub) {
-            ReportOutOfMemory(cx);
-            return false;
-        }
-
-        JitSpew(JitSpew_BaselineIC, "  %s TypeMonitor stub %p for primitive type %d",
-                existingStub ? "Modified existing" : "Created new", stub, type);
-
-        if (!existingStub) {
-            MOZ_ASSERT(!hasStub(TypeMonitor_PrimitiveSet));
-            addOptimizedMonitorStub(stub);
-        }
-
-    } else if (val.toObject().isSingleton()) {
-        RootedObject obj(cx, &val.toObject());
-
-        // Check for existing TypeMonitor stub.
-        for (ICStubConstIterator iter(firstMonitorStub()); !iter.atEnd(); iter++) {
-            if (iter->isTypeMonitor_SingleObject() &&
-                iter->toTypeMonitor_SingleObject()->object() == obj)
-            {
-                return true;
-            }
-        }
-
-        ICTypeMonitor_SingleObject::Compiler compiler(cx, obj);
-        ICStub* stub = compiler.getStub(compiler.getStubSpace(script));
-        if (!stub) {
-            ReportOutOfMemory(cx);
-            return false;
-        }
-
-        JitSpew(JitSpew_BaselineIC, "  Added TypeMonitor stub %p for singleton %p",
-                stub, obj.get());
-
-        addOptimizedMonitorStub(stub);
-
-    } else {
-        RootedObjectGroup group(cx, val.toObject().group());
-
-        // Check for existing TypeMonitor stub.
-        for (ICStubConstIterator iter(firstMonitorStub()); !iter.atEnd(); iter++) {
-            if (iter->isTypeMonitor_ObjectGroup() &&
-                iter->toTypeMonitor_ObjectGroup()->group() == group)
-            {
-                return true;
-            }
-        }
-
-        ICTypeMonitor_ObjectGroup::Compiler compiler(cx, group);
-        ICStub* stub = compiler.getStub(compiler.getStubSpace(script));
-        if (!stub) {
-            ReportOutOfMemory(cx);
-            return false;
-        }
-
-        JitSpew(JitSpew_BaselineIC, "  Added TypeMonitor stub %p for ObjectGroup %p",
-                stub, group.get());
-
-        addOptimizedMonitorStub(stub);
-    }
-
-    bool firstMonitorStubAdded = wasDetachedMonitorChain && (numOptimizedMonitorStubs_ > 0);
-
-    if (firstMonitorStubAdded) {
-        // Was an empty monitor chain before, but a new stub was added.  This is the
-        // only time that any main stubs' firstMonitorStub fields need to be updated to
-        // refer to the newly added monitor stub.
-        ICStub* firstStub = mainFallbackStub_->icEntry()->firstStub();
-        for (ICStubConstIterator iter(firstStub); !iter.atEnd(); iter++) {
-            // Non-monitored stubs are used if the result has always the same type,
-            // e.g. a StringLength stub will always return int32.
-            if (!iter->isMonitored())
-                continue;
-
-            // Since we just added the first optimized monitoring stub, any
-            // existing main stub's |firstMonitorStub| MUST be pointing to the fallback
-            // monitor stub (i.e. this stub).
-            MOZ_ASSERT(iter->toMonitoredStub()->firstMonitorStub() == this);
-            iter->toMonitoredStub()->updateFirstMonitorStub(firstMonitorStub_);
-        }
-    }
-
-    return true;
-}
-
-static bool
-DoTypeMonitorFallback(JSContext* cx, BaselineFrame* frame, ICTypeMonitor_Fallback* stub,
-                      HandleValue value, MutableHandleValue res)
-{
-    // It's possible that we arrived here from bailing out of Ion, and that
-    // Ion proved that the value is dead and optimized out. In such cases, do
-    // nothing. However, it's also possible that we have an uninitialized this,
-    // in which case we should not look for other magic values.
-    if (stub->monitorsThis()) {
-        MOZ_ASSERT_IF(value.isMagic(), value.isMagic(JS_UNINITIALIZED_LEXICAL));
-    } else {
-        if (value.isMagic(JS_OPTIMIZED_OUT)) {
-            res.set(value);
-            return true;
-        }
-    }
-
-    RootedScript script(cx, frame->script());
-    jsbytecode* pc = stub->icEntry()->pc(script);
-    TypeFallbackICSpew(cx, stub, "TypeMonitor");
-
-    uint32_t argument;
-    if (stub->monitorsThis()) {
-        MOZ_ASSERT(pc == script->code());
-        if (value.isMagic(JS_UNINITIALIZED_LEXICAL))
-            TypeScript::SetThis(cx, script, TypeSet::UnknownType());
-        else
-            TypeScript::SetThis(cx, script, value);
-    } else if (stub->monitorsArgument(&argument)) {
-        MOZ_ASSERT(pc == script->code());
-        TypeScript::SetArgument(cx, script, argument, value);
-    } else {
-        TypeScript::Monitor(cx, script, pc, value);
-    }
-
-    if (!stub->addMonitorStubForValue(cx, script, value))
-        return false;
-
-    // Copy input value to res.
-    res.set(value);
-    return true;
-}
-
-typedef bool (*DoTypeMonitorFallbackFn)(JSContext*, BaselineFrame*, ICTypeMonitor_Fallback*,
-                                        HandleValue, MutableHandleValue);
-static const VMFunction DoTypeMonitorFallbackInfo =
-    FunctionInfo<DoTypeMonitorFallbackFn>(DoTypeMonitorFallback, TailCall);
-
-bool
-ICTypeMonitor_Fallback::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-    MOZ_ASSERT(R0 == JSReturnOperand);
-
-    // Restore the tail call register.
-    EmitRestoreTailCallReg(masm);
-
-    masm.pushValue(R0);
-    masm.push(ICStubReg);
-    pushFramePtr(masm, R0.scratchReg());
-
-    return tailCallVM(DoTypeMonitorFallbackInfo, masm);
-}
-
-bool
-ICTypeMonitor_PrimitiveSet::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    Label success;
-    if ((flags_ & TypeToFlag(JSVAL_TYPE_INT32)) && !(flags_ & TypeToFlag(JSVAL_TYPE_DOUBLE)))
-        masm.branchTestInt32(Assembler::Equal, R0, &success);
-
-    if (flags_ & TypeToFlag(JSVAL_TYPE_DOUBLE))
-        masm.branchTestNumber(Assembler::Equal, R0, &success);
-
-    if (flags_ & TypeToFlag(JSVAL_TYPE_UNDEFINED))
-        masm.branchTestUndefined(Assembler::Equal, R0, &success);
-
-    if (flags_ & TypeToFlag(JSVAL_TYPE_BOOLEAN))
-        masm.branchTestBoolean(Assembler::Equal, R0, &success);
-
-    if (flags_ & TypeToFlag(JSVAL_TYPE_STRING))
-        masm.branchTestString(Assembler::Equal, R0, &success);
-
-    if (flags_ & TypeToFlag(JSVAL_TYPE_SYMBOL))
-        masm.branchTestSymbol(Assembler::Equal, R0, &success);
-
-    // Currently, we will never generate primitive stub checks for object.  However,
-    // when we do get to the point where we want to collapse our monitor chains of
-    // objects and singletons down (when they get too long) to a generic "any object"
-    // in coordination with the typeset doing the same thing, this will need to
-    // be re-enabled.
-    /*
-    if (flags_ & TypeToFlag(JSVAL_TYPE_OBJECT))
-        masm.branchTestObject(Assembler::Equal, R0, &success);
-    */
-    MOZ_ASSERT(!(flags_ & TypeToFlag(JSVAL_TYPE_OBJECT)));
-
-    if (flags_ & TypeToFlag(JSVAL_TYPE_NULL))
-        masm.branchTestNull(Assembler::Equal, R0, &success);
-
-    EmitStubGuardFailure(masm);
-
-    masm.bind(&success);
-    EmitReturnFromIC(masm);
-    return true;
-}
-
-bool
-ICTypeMonitor_SingleObject::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    Label failure;
-    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
-
-    // Guard on the object's identity.
-    Register obj = masm.extractObject(R0, ExtractTemp0);
-    Address expectedObject(ICStubReg, ICTypeMonitor_SingleObject::offsetOfObject());
-    masm.branchPtr(Assembler::NotEqual, expectedObject, obj, &failure);
-
-    EmitReturnFromIC(masm);
-
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-    return true;
-}
-
-bool
-ICTypeMonitor_ObjectGroup::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    Label failure;
-    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
-
-    // Guard on the object's ObjectGroup.
-    Register obj = masm.extractObject(R0, ExtractTemp0);
-    masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), R1.scratchReg());
-
-    Address expectedGroup(ICStubReg, ICTypeMonitor_ObjectGroup::offsetOfGroup());
-    masm.branchPtr(Assembler::NotEqual, expectedGroup, R1.scratchReg(), &failure);
-
-    EmitReturnFromIC(masm);
-
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-    return true;
-}
-
-bool
-ICUpdatedStub::addUpdateStubForValue(JSContext* cx, HandleScript script, HandleObject obj,
-                                     HandleId id, HandleValue val)
-{
-    if (numOptimizedStubs_ >= MAX_OPTIMIZED_STUBS) {
-        // TODO: if the TypeSet becomes unknown or has the AnyObject type,
-        // replace stubs with a single stub to handle these.
-        return true;
-    }
-
-    EnsureTrackPropertyTypes(cx, obj, id);
-
-    // Make sure that undefined values are explicitly included in the property
-    // types for an object if generating a stub to write an undefined value.
-    if (val.isUndefined() && CanHaveEmptyPropertyTypesForOwnProperty(obj))
-        AddTypePropertyId(cx, obj, id, val);
-
-    if (val.isPrimitive()) {
-        JSValueType type = val.isDouble() ? JSVAL_TYPE_DOUBLE : val.extractNonDoubleType();
-
-        // Check for existing TypeUpdate stub.
-        ICTypeUpdate_PrimitiveSet* existingStub = nullptr;
-        for (ICStubConstIterator iter(firstUpdateStub_); !iter.atEnd(); iter++) {
-            if (iter->isTypeUpdate_PrimitiveSet()) {
-                existingStub = iter->toTypeUpdate_PrimitiveSet();
-                if (existingStub->containsType(type))
-                    return true;
-            }
-        }
-
-        ICTypeUpdate_PrimitiveSet::Compiler compiler(cx, existingStub, type);
-        ICStub* stub = existingStub ? compiler.updateStub()
-                                    : compiler.getStub(compiler.getStubSpace(script));
-        if (!stub)
-            return false;
-        if (!existingStub) {
-            MOZ_ASSERT(!hasTypeUpdateStub(TypeUpdate_PrimitiveSet));
-            addOptimizedUpdateStub(stub);
-        }
-
-        JitSpew(JitSpew_BaselineIC, "  %s TypeUpdate stub %p for primitive type %d",
-                existingStub ? "Modified existing" : "Created new", stub, type);
-
-    } else if (val.toObject().isSingleton()) {
-        RootedObject obj(cx, &val.toObject());
-
-        // Check for existing TypeUpdate stub.
-        for (ICStubConstIterator iter(firstUpdateStub_); !iter.atEnd(); iter++) {
-            if (iter->isTypeUpdate_SingleObject() &&
-                iter->toTypeUpdate_SingleObject()->object() == obj)
-            {
-                return true;
-            }
-        }
-
-        ICTypeUpdate_SingleObject::Compiler compiler(cx, obj);
-        ICStub* stub = compiler.getStub(compiler.getStubSpace(script));
-        if (!stub)
-            return false;
-
-        JitSpew(JitSpew_BaselineIC, "  Added TypeUpdate stub %p for singleton %p", stub, obj.get());
-
-        addOptimizedUpdateStub(stub);
-
-    } else {
-        RootedObjectGroup group(cx, val.toObject().group());
-
-        // Check for existing TypeUpdate stub.
-        for (ICStubConstIterator iter(firstUpdateStub_); !iter.atEnd(); iter++) {
-            if (iter->isTypeUpdate_ObjectGroup() &&
-                iter->toTypeUpdate_ObjectGroup()->group() == group)
-            {
-                return true;
-            }
-        }
-
-        ICTypeUpdate_ObjectGroup::Compiler compiler(cx, group);
-        ICStub* stub = compiler.getStub(compiler.getStubSpace(script));
-        if (!stub)
-            return false;
-
-        JitSpew(JitSpew_BaselineIC, "  Added TypeUpdate stub %p for ObjectGroup %p",
-                stub, group.get());
-
-        addOptimizedUpdateStub(stub);
-    }
-
-    return true;
-}
-
 //
 // TypeUpdate_Fallback
 //
 static bool
 DoTypeUpdateFallback(JSContext* cx, BaselineFrame* frame, ICUpdatedStub* stub, HandleValue objval,
                      HandleValue value)
 {
     FallbackICSpew(cx, stub->getChainFallback(), "TypeUpdate(%s)",
@@ -904,38 +503,16 @@ ICTypeUpdate_ObjectGroup::Compiler::gene
     masm.mov(ImmWord(1), R1.scratchReg());
     EmitReturnFromIC(masm);
 
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
 }
 
-//
-// VM function to help call native getters.
-//
-
-static bool
-DoCallNativeGetter(JSContext* cx, HandleFunction callee, HandleObject obj,
-                   MutableHandleValue result)
-{
-    MOZ_ASSERT(callee->isNative());
-    JSNative natfun = callee->native();
-
-    JS::AutoValueArray<2> vp(cx);
-    vp[0].setObject(*callee.get());
-    vp[1].setObject(*obj.get());
-
-    if (!natfun(cx, 0, vp.begin()))
-        return false;
-
-    result.set(vp[0]);
-    return true;
-}
-
 typedef bool (*DoCallNativeGetterFn)(JSContext*, HandleFunction, HandleObject, MutableHandleValue);
 static const VMFunction DoCallNativeGetterInfo =
     FunctionInfo<DoCallNativeGetterFn>(DoCallNativeGetter);
 
 //
 // This_Fallback
 //
 
@@ -1398,284 +975,16 @@ ICToNumber_Fallback::Compiler::generateS
 
     return tailCallVM(DoToNumberFallbackInfo, masm);
 }
 
 //
 // GetElem_Fallback
 //
 
-static void GetFixedOrDynamicSlotOffset(Shape* shape, bool* isFixed, uint32_t* offset)
-{
-    MOZ_ASSERT(isFixed);
-    MOZ_ASSERT(offset);
-    *isFixed = shape->slot() < shape->numFixedSlots();
-    *offset = *isFixed ? NativeObject::getFixedSlotOffset(shape->slot())
-                       : (shape->slot() - shape->numFixedSlots()) * sizeof(Value);
-}
-
-static JSObject*
-GetDOMProxyProto(JSObject* obj)
-{
-    MOZ_ASSERT(IsCacheableDOMProxy(obj));
-    return obj->getTaggedProto().toObjectOrNull();
-}
-
-// Callers are expected to have already guarded on the shape of the
-// object, which guarantees the object is a DOM proxy.
-static void
-CheckDOMProxyExpandoDoesNotShadow(JSContext* cx, MacroAssembler& masm, Register object,
-                                  const Address& checkExpandoShapeAddr,
-                                  Address* expandoAndGenerationAddr,
-                                  Address* generationAddr,
-                                  Register scratch,
-                                  AllocatableGeneralRegisterSet& domProxyRegSet,
-                                  Label* checkFailed)
-{
-    // Guard that the object does not have expando properties, or has an expando
-    // which is known to not have the desired property.
-
-    // For the remaining code, we need to reserve some registers to load a value.
-    // This is ugly, but unavoidable.
-    ValueOperand tempVal = domProxyRegSet.takeAnyValue();
-    masm.pushValue(tempVal);
-
-    Label failDOMProxyCheck;
-    Label domProxyOk;
-
-    masm.loadPtr(Address(object, ProxyObject::offsetOfValues()), scratch);
-    Address expandoAddr(scratch, ProxyObject::offsetOfExtraSlotInValues(GetDOMProxyExpandoSlot()));
-
-    if (expandoAndGenerationAddr) {
-        MOZ_ASSERT(generationAddr);
-
-        masm.loadPtr(*expandoAndGenerationAddr, tempVal.scratchReg());
-        masm.branchPrivatePtr(Assembler::NotEqual, expandoAddr, tempVal.scratchReg(),
-                              &failDOMProxyCheck);
-
-        masm.load32(*generationAddr, scratch);
-        masm.branch32(Assembler::NotEqual,
-                      Address(tempVal.scratchReg(), offsetof(ExpandoAndGeneration, generation)),
-                      scratch, &failDOMProxyCheck);
-
-        masm.loadValue(Address(tempVal.scratchReg(), 0), tempVal);
-    } else {
-        masm.loadValue(expandoAddr, tempVal);
-    }
-
-    // If the incoming object does not have an expando object then we're sure we're not
-    // shadowing.
-    masm.branchTestUndefined(Assembler::Equal, tempVal, &domProxyOk);
-
-    // The reference object used to generate this check may not have had an
-    // expando object at all, in which case the presence of a non-undefined
-    // expando value in the incoming object is automatically a failure.
-    masm.loadPtr(checkExpandoShapeAddr, scratch);
-    masm.branchPtr(Assembler::Equal, scratch, ImmPtr(nullptr), &failDOMProxyCheck);
-
-    // Otherwise, ensure that the incoming object has an object for its expando value and that
-    // the shape matches.
-    masm.branchTestObject(Assembler::NotEqual, tempVal, &failDOMProxyCheck);
-    Register objReg = masm.extractObject(tempVal, tempVal.scratchReg());
-    masm.branchTestObjShape(Assembler::Equal, objReg, scratch, &domProxyOk);
-
-    // Failure case: restore the tempVal registers and jump to failures.
-    masm.bind(&failDOMProxyCheck);
-    masm.popValue(tempVal);
-    masm.jump(checkFailed);
-
-    // Success case: restore the tempval and proceed.
-    masm.bind(&domProxyOk);
-    masm.popValue(tempVal);
-}
-
-// Look up a property's shape on an object, being careful never to do any effectful
-// operations.  This procedure not yielding a shape should not be taken as a lack of
-// existence of the property on the object.
-static bool
-EffectlesslyLookupProperty(JSContext* cx, HandleObject obj, HandleId id,
-                           MutableHandleObject holder, MutableHandleShape shape,
-                           bool* checkDOMProxy=nullptr,
-                           DOMProxyShadowsResult* shadowsResult=nullptr,
-                           bool* domProxyHasGeneration=nullptr)
-{
-    shape.set(nullptr);
-    holder.set(nullptr);
-
-    if (checkDOMProxy) {
-        *checkDOMProxy = false;
-        *shadowsResult = ShadowCheckFailed;
-    }
-
-    // Check for list base if asked to.
-    RootedObject checkObj(cx, obj);
-    if (checkDOMProxy && IsCacheableDOMProxy(obj)) {
-        MOZ_ASSERT(domProxyHasGeneration);
-        MOZ_ASSERT(shadowsResult);
-
-        *checkDOMProxy = true;
-        if (obj->hasUncacheableProto())
-            return true;
-
-        *shadowsResult = GetDOMProxyShadowsCheck()(cx, obj, id);
-        if (*shadowsResult == ShadowCheckFailed)
-            return false;
-
-        if (DOMProxyIsShadowing(*shadowsResult)) {
-            holder.set(obj);
-            return true;
-        }
-
-        *domProxyHasGeneration = (*shadowsResult == DoesntShadowUnique);
-
-        checkObj = GetDOMProxyProto(obj);
-        if (!checkObj)
-            return true;
-    }
-
-    if (LookupPropertyPure(cx, checkObj, id, holder.address(), shape.address()))
-        return true;
-
-    holder.set(nullptr);
-    shape.set(nullptr);
-    return true;
-}
-
-static bool
-CheckHasNoSuchProperty(JSContext* cx, HandleObject obj, HandlePropertyName name,
-                       MutableHandleObject lastProto, size_t* protoChainDepthOut)
-{
-    MOZ_ASSERT(protoChainDepthOut != nullptr);
-
-    size_t depth = 0;
-    RootedObject curObj(cx, obj);
-    while (curObj) {
-        if (curObj->isNative()) {
-            // Don't handle proto chains with resolve hooks.
-            if (ClassMayResolveId(cx->names(), curObj->getClass(), NameToId(name), curObj))
-                return false;
-            if (curObj->as<NativeObject>().contains(cx, NameToId(name)))
-                return false;
-        } else if (curObj != obj) {
-            // Non-native objects are only handled as the original receiver.
-            return false;
-        } else if (curObj->is<UnboxedPlainObject>()) {
-            if (curObj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, NameToId(name)))
-                return false;
-        } else if (curObj->is<UnboxedArrayObject>()) {
-            if (name == cx->names().length)
-                return false;
-        } else if (curObj->is<TypedObject>()) {
-            if (curObj->as<TypedObject>().typeDescr().hasProperty(cx->names(), NameToId(name)))
-                return false;
-        } else {
-            return false;
-        }
-
-        JSObject* proto = curObj->getTaggedProto().toObjectOrNull();
-        if (!proto)
-            break;
-
-        curObj = proto;
-        depth++;
-    }
-
-    lastProto.set(curObj);
-    *protoChainDepthOut = depth;
-    return true;
-}
-
-static bool
-IsCacheableProtoChain(JSObject* obj, JSObject* holder, bool isDOMProxy=false)
-{
-    MOZ_ASSERT_IF(isDOMProxy, IsCacheableDOMProxy(obj));
-
-    if (!isDOMProxy && !obj->isNative()) {
-        if (obj == holder)
-            return false;
-        if (!obj->is<UnboxedPlainObject>() &&
-            !obj->is<UnboxedArrayObject>() &&
-            !obj->is<TypedObject>())
-        {
-            return false;
-        }
-    }
-
-    // Don't handle objects which require a prototype guard. This should
-    // be uncommon so handling it is likely not worth the complexity.
-    if (obj->hasUncacheableProto())
-        return false;
-
-    JSObject* cur = obj;
-    while (cur != holder) {
-        // We cannot assume that we find the holder object on the prototype
-        // chain and must check for null proto. The prototype chain can be
-        // altered during the lookupProperty call.
-        JSObject* proto;
-        if (isDOMProxy && cur == obj)
-            proto = cur->getTaggedProto().toObjectOrNull();
-        else
-            proto = cur->getProto();
-
-        if (!proto || !proto->isNative())
-            return false;
-
-        if (proto->hasUncacheableProto())
-            return false;
-
-        cur = proto;
-    }
-    return true;
-}
-
-static bool
-IsCacheableGetPropReadSlot(JSObject* obj, JSObject* holder, Shape* shape, bool isDOMProxy=false)
-{
-    if (!shape || !IsCacheableProtoChain(obj, holder, isDOMProxy))
-        return false;
-
-    if (!shape->hasSlot() || !shape->hasDefaultGetter())
-        return false;
-
-    return true;
-}
-
-static bool
-IsCacheableGetPropCall(JSContext* cx, JSObject* obj, JSObject* holder, Shape* shape,
-                       bool* isScripted, bool* isTemporarilyUnoptimizable, bool isDOMProxy=false)
-{
-    MOZ_ASSERT(isScripted);
-
-    if (!shape || !IsCacheableProtoChain(obj, holder, isDOMProxy))
-        return false;
-
-    if (shape->hasSlot() || shape->hasDefaultGetter())
-        return false;
-
-    if (!shape->hasGetterValue())
-        return false;
-
-    if (!shape->getterValue().isObject() || !shape->getterObject()->is<JSFunction>())
-        return false;
-
-    JSFunction* func = &shape->getterObject()->as<JSFunction>();
-    if (func->isNative()) {
-        *isScripted = false;
-        return true;
-    }
-
-    if (!func->hasJITCode()) {
-        *isTemporarilyUnoptimizable = true;
-        return false;
-    }
-
-    *isScripted = true;
-    return true;
-}
-
 static Shape*
 LastPropertyForSetProp(JSObject* obj)
 {
     if (obj->isNative())
         return obj->as<NativeObject>().lastProperty();
 
     if (obj->is<UnboxedPlainObject>()) {
         UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando();
@@ -2525,18 +1834,21 @@ DoGetElemFallback(JSContext* cx, Baselin
         TypeScript::Monitor(cx, frame->script(), pc, res);
     }
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid())
         return true;
 
     // Add a type monitor stub for the resulting value.
-    if (!stub->addMonitorStubForValue(cx, frame->script(), res))
+    if (!stub->addMonitorStubForValue(cx, frame->script(), res,
+        ICStubCompiler::Engine::Baseline))
+    {
         return false;
+    }
 
     if (attached)
         return true;
 
     // Try to attach an optimized stub.
     if (!TryAttachGetElemStub(cx, frame->script(), pc, stub, lhs, rhs, res, &attached))
         return false;
 
@@ -3080,44 +2392,16 @@ LoadTypedThingLength(MacroAssembler& mas
         masm.loadPtr(Address(result, ObjectGroup::offsetOfAddendum()), result);
         masm.unboxInt32(Address(result, ArrayTypeDescr::offsetOfLength()), result);
         break;
       default:
         MOZ_CRASH();
     }
 }
 
-static void
-LoadTypedThingData(MacroAssembler& masm, TypedThingLayout layout, Register obj, Register result)
-{
-    switch (layout) {
-      case Layout_TypedArray:
-        masm.loadPtr(Address(obj, TypedArrayLayout::dataOffset()), result);
-        break;
-      case Layout_OutlineTypedObject:
-        masm.loadPtr(Address(obj, OutlineTypedObject::offsetOfData()), result);
-        break;
-      case Layout_InlineTypedObject:
-        masm.computeEffectiveAddress(Address(obj, InlineTypedObject::offsetOfDataStart()), result);
-        break;
-      default:
-        MOZ_CRASH();
-    }
-}
-
-static void
-CheckForNeuteredTypedObject(JSContext* cx, MacroAssembler& masm, Label* failure)
-{
-    // All stubs which manipulate typed objects need to check the compartment
-    // wide flag indicating whether the objects are neutered, and bail out in
-    // this case.
-    int32_t* address = &cx->compartment()->neuteredTypedObjects;
-    masm.branch32(Assembler::NotEqual, AbsoluteAddress(address), Imm32(0), failure);
-}
-
 bool
 ICGetElem_TypedArray::Compiler::generateStubCode(MacroAssembler& masm)
 {
     MOZ_ASSERT(engine_ == Engine::Baseline);
 
     Label failure;
 
     if (layout_ != Layout_TypedArray)
@@ -3853,29 +3137,16 @@ ICSetElem_DenseOrUnboxedArray::Compiler:
     }
 
     // Failure case - jump to next stub
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
 }
 
-static bool
-GetProtoShapes(JSObject* obj, size_t protoChainDepth, MutableHandle<ShapeVector> shapes)
-{
-    JSObject* curProto = obj->getProto();
-    for (size_t i = 0; i < protoChainDepth; i++) {
-        if (!shapes.append(curProto->as<NativeObject>().lastProperty()))
-            return false;
-        curProto = curProto->getProto();
-    }
-    MOZ_ASSERT(!curProto);
-    return true;
-}
-
 //
 // SetElem_DenseOrUnboxedArrayAdd
 //
 
 ICUpdatedStub*
 ICSetElemDenseOrUnboxedArrayAddCompiler::getStub(ICStubSpace* space)
 {
     Rooted<ShapeVector> shapes(cx, ShapeVector(cx));
@@ -4595,80 +3866,16 @@ ICIn_Dense::Compiler::generateStubCode(M
     EmitReturnFromIC(masm);
 
     // Failure case - jump to next stub
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
 }
 
-// Try to update all existing GetProp/GetName getter call stubs that match the
-// given holder in place with a new shape and getter.  fallbackStub can be
-// either an ICGetProp_Fallback or an ICGetName_Fallback.
-//
-// If 'getter' is an own property, holder == receiver must be true.
-static bool
-UpdateExistingGetPropCallStubs(ICFallbackStub* fallbackStub,
-                               ICStub::Kind kind,
-                               HandleNativeObject holder,
-                               HandleObject receiver,
-                               HandleFunction getter)
-{
-    MOZ_ASSERT(kind == ICStub::GetProp_CallScripted ||
-               kind == ICStub::GetProp_CallNative ||
-               kind == ICStub::GetProp_CallNativeGlobal);
-    MOZ_ASSERT(fallbackStub->isGetName_Fallback() ||
-               fallbackStub->isGetProp_Fallback());
-    MOZ_ASSERT(holder);
-    MOZ_ASSERT(receiver);
-
-    bool isOwnGetter = (holder == receiver);
-    bool foundMatchingStub = false;
-    ReceiverGuard receiverGuard(receiver);
-    for (ICStubConstIterator iter = fallbackStub->beginChainConst(); !iter.atEnd(); iter++) {
-        if (iter->kind() == kind) {
-            ICGetPropCallGetter* getPropStub = static_cast<ICGetPropCallGetter*>(*iter);
-            if (getPropStub->holder() == holder && getPropStub->isOwnGetter() == isOwnGetter) {
-                // If this is an own getter, update the receiver guard as well,
-                // since that's the shape we'll be guarding on. Furthermore,
-                // isOwnGetter() relies on holderShape_ and receiverGuard_ being
-                // the same shape.
-                if (isOwnGetter)
-                    getPropStub->receiverGuard().update(receiverGuard);
-
-                MOZ_ASSERT(getPropStub->holderShape() != holder->lastProperty() ||
-                           !getPropStub->receiverGuard().matches(receiverGuard) ||
-                           getPropStub->toGetProp_CallNativeGlobal()->globalShape() !=
-                           receiver->as<ClonedBlockObject>().global().lastProperty(),
-                           "Why didn't we end up using this stub?");
-
-                // We want to update the holder shape to match the new one no
-                // matter what, even if the receiver shape is different.
-                getPropStub->holderShape() = holder->lastProperty();
-
-                // Make sure to update the getter, since a shape change might
-                // have changed which getter we want to use.
-                getPropStub->getter() = getter;
-
-                if (getPropStub->isGetProp_CallNativeGlobal()) {
-                    ICGetProp_CallNativeGlobal* globalStub =
-                        getPropStub->toGetProp_CallNativeGlobal();
-                    globalStub->globalShape() =
-                        receiver->as<ClonedBlockObject>().global().lastProperty();
-                }
-
-                if (getPropStub->receiverGuard().matches(receiverGuard))
-                    foundMatchingStub = true;
-            }
-        }
-    }
-
-    return foundMatchingStub;
-}
-
 // Try to update existing SetProp setter call stubs for the given holder in
 // place with a new shape and setter.
 static bool
 UpdateExistingSetPropCallStubs(ICSetProp_Fallback* fallbackStub,
                                ICStub::Kind kind,
                                NativeObject* holder,
                                JSObject* receiver,
                                JSFunction* setter)
@@ -4765,17 +3972,18 @@ TryAttachGlobalNameValueStub(JSContext* 
             // Check the prototype chain from the global to the current
             // prototype. Ignore the global lexical scope as it doesn' figure
             // into the prototype chain. We guard on the global lexical
             // scope's shape independently.
             if (!IsCacheableGetPropReadSlot(&globalLexical->global(), current, shape))
                 return true;
 
             JitSpew(JitSpew_BaselineIC, "  Generating GetName(GlobalName non-lexical) stub");
-            ICGetPropNativeCompiler compiler(cx, ICStub::GetName_Global, monitorStub,
+            ICGetPropNativeCompiler compiler(cx, ICStub::GetName_Global,
+                                             ICStubCompiler::Engine::Baseline, monitorStub,
                                              globalLexical, current, name, isFixedSlot, offset,
                                              /* inputDefinitelyObject = */ true);
             newStub = compiler.getStub(compiler.getStubSpace(script));
         }
         if (!newStub)
             return false;
 
         stub->addNewStub(newStub);
@@ -4842,16 +4050,17 @@ TryAttachGlobalNameAccessorStub(JSContex
         JitSpew(JitSpew_BaselineIC, "  Generating GetName(GlobalName/NativeGetter) stub");
         if (UpdateExistingGetPropCallStubs(stub, ICStub::GetProp_CallNativeGlobal, current,
                                            globalLexical, getter))
         {
             *attached = true;
             return true;
         }
         ICGetPropCallNativeCompiler compiler(cx, ICStub::GetProp_CallNativeGlobal,
+                                             ICStubCompiler::Engine::Baseline,
                                              monitorStub, globalLexical, current,
                                              getter, script->pcToOffset(pc),
                                              /* outerClass = */ nullptr,
                                              /* inputDefinitelyObject = */ true);
 
         ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
         if (!newStub)
             return false;
@@ -5011,17 +4220,17 @@ DoGetNameFallback(JSContext* cx, Baselin
 
     TypeScript::Monitor(cx, script, pc, res);
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid())
         return true;
 
     // Add a type monitor stub for the resulting value.
-    if (!stub->addMonitorStubForValue(cx, script, res))
+    if (!stub->addMonitorStubForValue(cx, script, res, ICStubCompiler::Engine::Baseline))
         return false;
     if (attached)
         return true;
 
     if (IsGlobalOp(JSOp(*pc)) && !script->hasNonSyntacticScope()) {
         Handle<ClonedBlockObject*> globalLexical = scopeChain.as<ClonedBlockObject>();
         if (!TryAttachGlobalNameValueStub(cx, script, pc, stub, globalLexical, name, &attached))
             return false;
@@ -5235,1805 +4444,16 @@ ICGetIntrinsic_Constant::Compiler::gener
 
     masm.loadValue(Address(ICStubReg, ICGetIntrinsic_Constant::offsetOfValue()), R0);
 
     EmitReturnFromIC(masm);
     return true;
 }
 
 //
-// GetProp_Fallback
-//
-
-static bool
-TryAttachMagicArgumentsGetPropStub(JSContext* cx, JSScript* script, ICGetProp_Fallback* stub,
-                                   HandlePropertyName name, HandleValue val, HandleValue res,
-                                   bool* attached)
-{
-    MOZ_ASSERT(!*attached);
-
-    if (!val.isMagic(JS_OPTIMIZED_ARGUMENTS))
-        return true;
-
-    // Try handling arguments.callee on optimized arguments.
-    if (name == cx->names().callee) {
-        MOZ_ASSERT(script->hasMappedArgsObj());
-
-        JitSpew(JitSpew_BaselineIC, "  Generating GetProp(MagicArgs.callee) stub");
-
-        // Unlike ICGetProp_ArgumentsLength, only magic argument stubs are
-        // supported at the moment.
-        ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub();
-        ICGetProp_ArgumentsCallee::Compiler compiler(cx, monitorStub);
-        ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
-        if (!newStub)
-            return false;
-        stub->addNewStub(newStub);
-
-        *attached = true;
-        return true;
-    }
-
-    return true;
-}
-
-static bool
-TryAttachLengthStub(JSContext* cx, JSScript* script, ICGetProp_Fallback* stub, HandleValue val,
-                    HandleValue res, bool* attached)
-{
-    MOZ_ASSERT(!*attached);
-
-    if (val.isString()) {
-        MOZ_ASSERT(res.isInt32());
-        JitSpew(JitSpew_BaselineIC, "  Generating GetProp(String.length) stub");
-        ICGetProp_StringLength::Compiler compiler(cx);
-        ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
-        if (!newStub)
-            return false;
-
-        *attached = true;
-        stub->addNewStub(newStub);
-        return true;
-    }
-
-    if (val.isMagic(JS_OPTIMIZED_ARGUMENTS) && res.isInt32()) {
-        JitSpew(JitSpew_BaselineIC, "  Generating GetProp(MagicArgs.length) stub");
-        ICGetProp_ArgumentsLength::Compiler compiler(cx, ICGetProp_ArgumentsLength::Magic);
-        ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
-        if (!newStub)
-            return false;
-
-        *attached = true;
-        stub->addNewStub(newStub);
-        return true;
-    }
-
-    if (!val.isObject())
-        return true;
-
-    RootedObject obj(cx, &val.toObject());
-
-    if (obj->is<ArrayObject>() && res.isInt32()) {
-        JitSpew(JitSpew_BaselineIC, "  Generating GetProp(Array.length) stub");
-        ICGetProp_ArrayLength::Compiler compiler(cx);
-        ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
-        if (!newStub)
-            return false;
-
-        *attached = true;
-        stub->addNewStub(newStub);
-        return true;
-    }
-
-    if (obj->is<UnboxedArrayObject>() && res.isInt32()) {
-        JitSpew(JitSpew_BaselineIC, "  Generating GetProp(UnboxedArray.length) stub");
-        ICGetProp_UnboxedArrayLength::Compiler compiler(cx);
-        ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
-        if (!newStub)
-            return false;
-
-        *attached = true;
-        stub->addNewStub(newStub);
-        return true;
-    }
-
-    if (obj->is<ArgumentsObject>() && res.isInt32()) {
-        JitSpew(JitSpew_BaselineIC, "  Generating GetProp(ArgsObj.length %s) stub",
-                obj->is<MappedArgumentsObject>() ? "Mapped" : "Unmapped");
-        ICGetProp_ArgumentsLength::Which which = ICGetProp_ArgumentsLength::Mapped;
-        if (obj->is<UnmappedArgumentsObject>())
-            which = ICGetProp_ArgumentsLength::Unmapped;
-        ICGetProp_ArgumentsLength::Compiler compiler(cx, which);
-        ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
-        if (!newStub)
-            return false;
-
-        *attached = true;
-        stub->addNewStub(newStub);
-        return true;
-    }
-
-    return true;
-}
-
-static bool
-UpdateExistingGenerationalDOMProxyStub(ICGetProp_Fallback* stub,
-                                       HandleObject obj)
-{
-    Value expandoSlot = GetProxyExtra(obj, GetDOMProxyExpandoSlot());
-    MOZ_ASSERT(!expandoSlot.isObject() && !expandoSlot.isUndefined());
-    ExpandoAndGeneration* expandoAndGeneration = (ExpandoAndGeneration*)expandoSlot.toPrivate();
-    for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
-        if (iter->isGetProp_CallDOMProxyWithGenerationNative()) {
-            ICGetProp_CallDOMProxyWithGenerationNative* updateStub =
-                iter->toGetProp_CallDOMProxyWithGenerationNative();
-            if (updateStub->expandoAndGeneration() == expandoAndGeneration) {
-                // Update generation
-                uint32_t generation = expandoAndGeneration->generation;
-                JitSpew(JitSpew_BaselineIC,
-                        "  Updating existing stub with generation, old value: %i, "
-                        "new value: %i", updateStub->generation(),
-                        generation);
-                updateStub->setGeneration(generation);
-                return true;
-            }
-        }
-    }
-    return false;
-}
-
-// Return whether obj is in some PreliminaryObjectArray and has a structure
-// that might change in the future.
-static bool
-IsPreliminaryObject(JSObject* obj)
-{
-    if (obj->isSingleton())
-        return false;
-
-    TypeNewScript* newScript = obj->group()->newScript();
-    if (newScript && !newScript->analyzed())
-        return true;
-
-    if (obj->group()->maybePreliminaryObjects())
-        return true;
-
-    return false;
-}
-
-static void
-StripPreliminaryObjectStubs(JSContext* cx, ICFallbackStub* stub)
-{
-    // Before the new script properties analysis has been performed on a type,
-    // all instances of that type have the maximum number of fixed slots.
-    // Afterwards, the objects (even the preliminary ones) might be changed
-    // to reduce the number of fixed slots they have. If we generate stubs for
-    // both the old and new number of fixed slots, the stub will look
-    // polymorphic to IonBuilder when it is actually monomorphic. To avoid
-    // this, strip out any stubs for preliminary objects before attaching a new
-    // stub which isn't on a preliminary object.
-
-    for (ICStubIterator iter = stub->beginChain(); !iter.atEnd(); iter++) {
-        if (iter->isGetProp_Native() && iter->toGetProp_Native()->hasPreliminaryObject())
-            iter.unlink(cx);
-        else if (iter->isSetProp_Native() && iter->toSetProp_Native()->hasPreliminaryObject())
-            iter.unlink(cx);
-    }
-}
-
-static bool
-TryAttachNativeGetValuePropStub(JSContext* cx, HandleScript script, jsbytecode* pc,
-                                ICGetProp_Fallback* stub, HandlePropertyName name,
-                                HandleValue val, HandleShape oldShape,
-                                HandleValue res, bool* attached)
-{
-    MOZ_ASSERT(!*attached);
-
-    if (!val.isObject())
-        return true;
-
-    RootedObject obj(cx, &val.toObject());
-
-    if (obj->isNative() && oldShape != obj->as<NativeObject>().lastProperty()) {
-        // No point attaching anything, since we know the shape guard will fail
-        return true;
-    }
-
-    RootedShape shape(cx);
-    RootedObject holder(cx);
-    RootedId id(cx, NameToId(name));
-    if (!EffectlesslyLookupProperty(cx, obj, id, &holder, &shape))
-        return false;
-
-    ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub();
-    if (IsCacheableGetPropReadSlot(obj, holder, shape)) {
-        bool isFixedSlot;
-        uint32_t offset;
-        GetFixedOrDynamicSlotOffset(shape, &isFixedSlot, &offset);
-
-        // Instantiate this property for singleton holders, for use during Ion compilation.
-        if (IsIonEnabled(cx))
-            EnsureTrackPropertyTypes(cx, holder, NameToId(name));
-
-        ICStub::Kind kind =
-            (obj == holder) ? ICStub::GetProp_Native : ICStub::GetProp_NativePrototype;
-
-        JitSpew(JitSpew_BaselineIC, "  Generating GetProp(Native %s) stub",
-                    (obj == holder) ? "direct" : "prototype");
-        ICGetPropNativeCompiler compiler(cx, kind, monitorStub, obj, holder,
-                                         name, isFixedSlot, offset);
-        ICGetPropNativeStub* newStub = compiler.getStub(compiler.getStubSpace(script));
-        if (!newStub)
-            return false;
-
-        if (IsPreliminaryObject(obj))
-            newStub->notePreliminaryObject();
-        else
-            StripPreliminaryObjectStubs(cx, stub);
-
-        stub->addNewStub(newStub);
-        *attached = true;
-        return true;
-    }
-    return true;
-}
-
-
-static bool
-TryAttachNativeGetAccessorPropStub(JSContext* cx, HandleScript script, jsbytecode* pc,
-                                   ICGetProp_Fallback* stub, HandlePropertyName name,
-                                   HandleValue val, HandleValue res, bool* attached,
-                                   bool* isTemporarilyUnoptimizable)
-{
-    MOZ_ASSERT(!*attached);
-    MOZ_ASSERT(!*isTemporarilyUnoptimizable);
-
-    if (!val.isObject())
-        return true;
-
-    RootedObject obj(cx, &val.toObject());
-
-    bool isDOMProxy;
-    bool domProxyHasGeneration;
-    DOMProxyShadowsResult domProxyShadowsResult;
-    RootedShape shape(cx);
-    RootedObject holder(cx);
-    RootedId id(cx, NameToId(name));
-    if (!EffectlesslyLookupProperty(cx, obj, id, &holder, &shape, &isDOMProxy,
-                                    &domProxyShadowsResult, &domProxyHasGeneration))
-    {
-        return false;
-    }
-
-    ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub();
-
-    bool isScripted = false;
-    bool cacheableCall = IsCacheableGetPropCall(cx, obj, holder, shape, &isScripted,
-                                                isTemporarilyUnoptimizable);
-
-    // Try handling scripted getters.
-    if (cacheableCall && isScripted && !isDOMProxy) {
-        RootedFunction callee(cx, &shape->getterObject()->as<JSFunction>());
-        MOZ_ASSERT(callee->hasScript());
-
-        if (UpdateExistingGetPropCallStubs(stub, ICStub::GetProp_CallScripted,
-                                           holder.as<NativeObject>(), obj, callee)) {
-            *attached = true;
-            return true;
-        }
-
-        JitSpew(JitSpew_BaselineIC, "  Generating GetProp(NativeObj/ScriptedGetter %s:%" PRIuSIZE ") stub",
-                callee->nonLazyScript()->filename(), callee->nonLazyScript()->lineno());
-
-        ICGetProp_CallScripted::Compiler compiler(cx, monitorStub, obj, holder, callee,
-                                                  script->pcToOffset(pc));
-        ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
-        if (!newStub)
-            return false;
-
-        stub->addNewStub(newStub);
-        *attached = true;
-        return true;
-    }
-
-    // If it's a shadowed listbase proxy property, attach stub to call Proxy::get instead.
-    if (isDOMProxy && DOMProxyIsShadowing(domProxyShadowsResult)) {
-        MOZ_ASSERT(obj == holder);
-
-        JitSpew(JitSpew_BaselineIC, "  Generating GetProp(DOMProxyProxy) stub");
-        Rooted<ProxyObject*> proxy(cx, &obj->as<ProxyObject>());
-        ICGetProp_DOMProxyShadowed::Compiler compiler(cx, monitorStub, proxy, name,
-                                                      script->pcToOffset(pc));
-        ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
-        if (!newStub)
-            return false;
-        stub->addNewStub(newStub);
-        *attached = true;
-        return true;
-    }
-
-    const Class* outerClass = nullptr;
-    if (!isDOMProxy && !obj->isNative()) {
-        outerClass = obj->getClass();
-        if (!IsWindowProxy(obj))
-            return true;
-
-        // This must be a WindowProxy for the current Window/global. Else it'd
-        // be a cross-compartment wrapper and IsWindowProxy returns false for
-        // those.
-        MOZ_ASSERT(ToWindowIfWindowProxy(obj) == cx->global());
-        obj = cx->global();
-
-        if (!EffectlesslyLookupProperty(cx, obj, id, &holder, &shape, &isDOMProxy,
-                                        &domProxyShadowsResult, &domProxyHasGeneration))
-        {
-            return false;
-        }
-        cacheableCall = IsCacheableGetPropCall(cx, obj, holder, shape, &isScripted,
-                                               isTemporarilyUnoptimizable, isDOMProxy);
-    }
-
-    // Try handling JSNative getters.
-    if (!cacheableCall || isScripted)
-        return true;
-
-    if (!shape || !shape->hasGetterValue() || !shape->getterValue().isObject() ||
-        !shape->getterObject()->is<JSFunction>())
-    {
-        return true;
-    }
-
-    RootedFunction callee(cx, &shape->getterObject()->as<JSFunction>());
-    MOZ_ASSERT(callee->isNative());
-
-    if (outerClass && (!callee->jitInfo() || callee->jitInfo()->needsOuterizedThisObject()))
-        return true;
-
-    JitSpew(JitSpew_BaselineIC, "  Generating GetProp(%s%s/NativeGetter %p) stub",
-            isDOMProxy ? "DOMProxyObj" : "NativeObj",
-            isDOMProxy && domProxyHasGeneration ? "WithGeneration" : "",
-            callee->native());
-
-    ICStub* newStub = nullptr;
-    if (isDOMProxy) {
-        MOZ_ASSERT(obj != holder);
-        ICStub::Kind kind;
-        if (domProxyHasGeneration) {
-            if (UpdateExistingGenerationalDOMProxyStub(stub, obj)) {
-                *attached = true;
-                return true;
-            }
-            kind = ICStub::GetProp_CallDOMProxyWithGenerationNative;
-        } else {
-            kind = ICStub::GetProp_CallDOMProxyNative;
-        }
-        Rooted<ProxyObject*> proxy(cx, &obj->as<ProxyObject>());
-        ICGetPropCallDOMProxyNativeCompiler compiler(cx, kind, monitorStub, proxy, holder, callee,
-                                                     script->pcToOffset(pc));
-        newStub = compiler.getStub(compiler.getStubSpace(script));
-    } else {
-        if (UpdateExistingGetPropCallStubs(stub, ICStub::GetProp_CallNative,
-                                           holder.as<NativeObject>(), obj, callee))
-        {
-            *attached = true;
-            return true;
-        }
-
-        ICGetPropCallNativeCompiler compiler(cx, ICStub::GetProp_CallNative,
-                                             monitorStub, obj, holder, callee,
-                                             script->pcToOffset(pc), outerClass);
-        newStub = compiler.getStub(compiler.getStubSpace(script));
-    }
-    if (!newStub)
-        return false;
-    stub->addNewStub(newStub);
-    *attached = true;
-    return true;
-}
-
-static bool
-TryAttachUnboxedGetPropStub(JSContext* cx, HandleScript script,
-                            ICGetProp_Fallback* stub, HandlePropertyName name, HandleValue val,
-                            bool* attached)
-{
-    MOZ_ASSERT(!*attached);
-
-    if (!cx->runtime()->jitSupportsFloatingPoint)
-        return true;
-
-    if (!val.isObject() || !val.toObject().is<UnboxedPlainObject>())
-        return true;
-    Rooted<UnboxedPlainObject*> obj(cx, &val.toObject().as<UnboxedPlainObject>());
-
-    const UnboxedLayout::Property* property = obj->layout().lookup(name);
-    if (!property)
-        return true;
-
-    ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub();
-
-    ICGetProp_Unboxed::Compiler compiler(cx, monitorStub, obj->group(),
-                                         property->offset + UnboxedPlainObject::offsetOfData(),
-                                         property->type);
-    ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
-    if (!newStub)
-        return false;
-    stub->addNewStub(newStub);
-
-    StripPreliminaryObjectStubs(cx, stub);
-
-    *attached = true;
-    return true;
-}
-
-static bool
-TryAttachUnboxedExpandoGetPropStub(JSContext* cx, HandleScript script, jsbytecode* pc,
-                                   ICGetProp_Fallback* stub, HandlePropertyName name, HandleValue val,
-                                   bool* attached)
-{
-    MOZ_ASSERT(!*attached);
-
-    if (!val.isObject() || !val.toObject().is<UnboxedPlainObject>())
-        return true;
-    Rooted<UnboxedPlainObject*> obj(cx, &val.toObject().as<UnboxedPlainObject>());
-
-    Rooted<UnboxedExpandoObject*> expando(cx, obj->maybeExpando());
-    if (!expando)
-        return true;
-
-    Shape* shape = expando->lookup(cx, name);
-    if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot())
-        return true;
-
-    bool isFixedSlot;
-    uint32_t offset;
-    GetFixedOrDynamicSlotOffset(shape, &isFixedSlot, &offset);
-
-    ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub();
-
-    ICGetPropNativeCompiler compiler(cx, ICStub::GetProp_Native, monitorStub, obj, obj,
-                                     name, isFixedSlot, offset);
-    ICGetPropNativeStub* newStub = compiler.getStub(compiler.getStubSpace(script));
-    if (!newStub)
-        return false;
-
-    StripPreliminaryObjectStubs(cx, stub);
-
-    stub->addNewStub(newStub);
-    *attached = true;
-    return true;
-}
-
-static bool
-TryAttachTypedObjectGetPropStub(JSContext* cx, HandleScript script,
-                                ICGetProp_Fallback* stub, HandlePropertyName name, HandleValue val,
-                                bool* attached)
-{
-    MOZ_ASSERT(!*attached);
-
-    if (!cx->runtime()->jitSupportsFloatingPoint)
-        return true;
-
-    if (!val.isObject() || !val.toObject().is<TypedObject>())
-        return true;
-    Rooted<TypedObject*> obj(cx, &val.toObject().as<TypedObject>());
-
-    if (!obj->typeDescr().is<StructTypeDescr>())
-        return true;
-    Rooted<StructTypeDescr*> structDescr(cx, &obj->typeDescr().as<StructTypeDescr>());
-
-    size_t fieldIndex;
-    if (!structDescr->fieldIndex(NameToId(name), &fieldIndex))
-        return true;
-
-    Rooted<TypeDescr*> fieldDescr(cx, &structDescr->fieldDescr(fieldIndex));
-    if (!fieldDescr->is<SimpleTypeDescr>())
-        return true;
-
-    uint32_t fieldOffset = structDescr->fieldOffset(fieldIndex);
-    ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub();
-
-    ICGetProp_TypedObject::Compiler compiler(cx, monitorStub, obj->maybeShape(),
-                                             fieldOffset, &fieldDescr->as<SimpleTypeDescr>());
-    ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
-    if (!newStub)
-        return false;
-    stub->addNewStub(newStub);
-
-    *attached = true;
-    return true;
-}
-
-static bool
-TryAttachPrimitiveGetPropStub(JSContext* cx, HandleScript script, jsbytecode* pc,
-                              ICGetProp_Fallback* stub, HandlePropertyName name, HandleValue val,
-                              HandleValue res, bool* attached)
-{
-    MOZ_ASSERT(!*attached);
-
-    JSValueType primitiveType;
-    RootedNativeObject proto(cx);
-    Rooted<GlobalObject*> global(cx, &script->global());
-    if (val.isString()) {
-        primitiveType = JSVAL_TYPE_STRING;
-        proto = GlobalObject::getOrCreateStringPrototype(cx, global);
-    } else if (val.isSymbol()) {
-        primitiveType = JSVAL_TYPE_SYMBOL;
-        proto = GlobalObject::getOrCreateSymbolPrototype(cx, global);
-    } else if (val.isNumber()) {
-        primitiveType = JSVAL_TYPE_DOUBLE;
-        proto = GlobalObject::getOrCreateNumberPrototype(cx, global);
-    } else {
-        MOZ_ASSERT(val.isBoolean());
-        primitiveType = JSVAL_TYPE_BOOLEAN;
-        proto = GlobalObject::getOrCreateBooleanPrototype(cx, global);
-    }
-    if (!proto)
-        return false;
-
-    // Instantiate this property, for use during Ion compilation.
-    RootedId id(cx, NameToId(name));
-    if (IsIonEnabled(cx))
-        EnsureTrackPropertyTypes(cx, proto, id);
-
-    // For now, only look for properties directly set on the prototype.
-    RootedShape shape(cx, proto->lookup(cx, id));
-    if (!shape || !shape->hasSlot() || !shape->hasDefaultGetter())
-        return true;
-
-    bool isFixedSlot;
-    uint32_t offset;
-    GetFixedOrDynamicSlotOffset(shape, &isFixedSlot, &offset);
-
-    ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub();
-
-    JitSpew(JitSpew_BaselineIC, "  Generating GetProp_Primitive stub");
-    ICGetProp_Primitive::Compiler compiler(cx, monitorStub, primitiveType, proto,
-                                           isFixedSlot, offset);
-    ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
-    if (!newStub)
-        return false;
-
-    stub->addNewStub(newStub);
-    *attached = true;
-    return true;
-}
-
-static bool
-TryAttachNativeGetPropDoesNotExistStub(JSContext* cx, HandleScript script,
-                                       jsbytecode* pc, ICGetProp_Fallback* stub,
-                                       HandlePropertyName name, HandleValue val,
-                                       bool* attached)
-{
-    MOZ_ASSERT(!*attached);
-
-    if (!val.isObject())
-        return true;
-
-    RootedObject obj(cx, &val.toObject());
-
-    // Don't attach stubs for CALLPROP since those need NoSuchMethod handling.
-    if (JSOp(*pc) == JSOP_CALLPROP)
-        return true;
-
-    // Check if does-not-exist can be confirmed on property.
-    RootedObject lastProto(cx);
-    size_t protoChainDepth = SIZE_MAX;
-    if (!CheckHasNoSuchProperty(cx, obj, name, &lastProto, &protoChainDepth))
-        return true;
-    MOZ_ASSERT(protoChainDepth < SIZE_MAX);
-
-    if (protoChainDepth > ICGetProp_NativeDoesNotExist::MAX_PROTO_CHAIN_DEPTH)
-        return true;
-
-    ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub();
-
-    // Confirmed no-such-property.  Add stub.
-    JitSpew(JitSpew_BaselineIC, "  Generating GetProp_NativeDoesNotExist stub");
-    ICGetPropNativeDoesNotExistCompiler compiler(cx, monitorStub, obj, protoChainDepth);
-    ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
-    if (!newStub)
-        return false;
-
-    stub->addNewStub(newStub);
-    *attached = true;
-    return true;
-}
-
-static bool
-ComputeGetPropResult(JSContext* cx, BaselineFrame* frame, JSOp op, HandlePropertyName name,
-                     MutableHandleValue val, MutableHandleValue res)
-{
-    // Handle arguments.length and arguments.callee on optimized arguments, as
-    // it is not an object.
-    if (val.isMagic(JS_OPTIMIZED_ARGUMENTS) && IsOptimizedArguments(frame, val)) {
-        if (op == JSOP_LENGTH) {
-            res.setInt32(frame->numActualArgs());
-        } else {
-            MOZ_ASSERT(name == cx->names().callee);
-            MOZ_ASSERT(frame->script()->hasMappedArgsObj());
-            res.setObject(*frame->callee());
-        }
-    } else {
-        if (op == JSOP_GETXPROP) {
-            RootedObject obj(cx, &val.toObject());
-            RootedId id(cx, NameToId(name));
-            if (!GetPropertyForNameLookup(cx, obj, id, res))
-                return false;
-        } else {
-            MOZ_ASSERT(op == JSOP_GETPROP || op == JSOP_CALLPROP || op == JSOP_LENGTH);
-            if (!GetProperty(cx, val, name, res))
-                return false;
-        }
-    }
-
-    return true;
-}
-
-static bool
-DoGetPropFallback(JSContext* cx, BaselineFrame* frame, ICGetProp_Fallback* stub_,
-                  MutableHandleValue val, MutableHandleValue res)
-{
-    // This fallback stub may trigger debug mode toggling.
-    DebugModeOSRVolatileStub<ICGetProp_Fallback*> stub(frame, stub_);
-
-    jsbytecode* pc = stub->icEntry()->pc(frame->script());
-    JSOp op = JSOp(*pc);
-    FallbackICSpew(cx, stub, "GetProp(%s)", js_CodeName[op]);
-
-    MOZ_ASSERT(op == JSOP_GETPROP || op == JSOP_CALLPROP || op == JSOP_LENGTH || op == JSOP_GETXPROP);
-
-    // Grab our old shape before it goes away.
-    RootedShape oldShape(cx);
-    if (val.isObject())
-        oldShape = val.toObject().maybeShape();
-
-    bool attached = false;
-    // There are some reasons we can fail to attach a stub that are temporary.
-    // We want to avoid calling noteUnoptimizableAccess() if the reason we
-    // failed to attach a stub is one of those temporary reasons, since we might
-    // end up attaching a stub for the exact same access later.
-    bool isTemporarilyUnoptimizable = false;
-
-    RootedScript script(cx, frame->script());
-    RootedPropertyName name(cx, frame->script()->getName(pc));
-
-    // After the  Genericstub was added, we should never reach the Fallbackstub again.
-    MOZ_ASSERT(!stub->hasStub(ICStub::GetProp_Generic));
-
-    if (stub->numOptimizedStubs() >= ICGetProp_Fallback::MAX_OPTIMIZED_STUBS) {
-        // Discard all stubs in this IC and replace with generic getprop stub.
-        for(ICStubIterator iter = stub->beginChain(); !iter.atEnd(); iter++)
-            iter.unlink(cx);
-        ICGetProp_Generic::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub());
-        ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
-        if (!newStub)
-            return false;
-        stub->addNewStub(newStub);
-        attached = true;
-    }
-
-    if (!attached && !TryAttachNativeGetAccessorPropStub(cx, script, pc, stub, name, val, res,
-                                                         &attached, &isTemporarilyUnoptimizable))
-    {
-        return false;
-    }
-
-    if (!ComputeGetPropResult(cx, frame, op, name, val, res))
-        return false;
-
-    TypeScript::Monitor(cx, script, pc, res);
-
-    // Check if debug mode toggling made the stub invalid.
-    if (stub.invalid())
-        return true;
-
-    // Add a type monitor stub for the resulting value.
-    if (!stub->addMonitorStubForValue(cx, script, res))
-        return false;
-
-    if (attached)
-        return true;
-
-    if (op == JSOP_LENGTH) {
-        if (!TryAttachLengthStub(cx, script, stub, val, res, &attached))
-            return false;
-        if (attached)
-            return true;
-    }
-
-    if (!TryAttachMagicArgumentsGetPropStub(cx, script, stub, name, val, res, &attached))
-        return false;
-    if (attached)
-        return true;
-
-    if (!TryAttachNativeGetValuePropStub(cx, script, pc, stub, name, val, oldShape,
-                                         res, &attached))
-        return false;
-    if (attached)
-        return true;
-
-    if (!TryAttachUnboxedGetPropStub(cx, script, stub, name, val, &attached))
-        return false;
-    if (attached)
-        return true;
-
-    if (!TryAttachUnboxedExpandoGetPropStub(cx, script, pc, stub, name, val, &attached))
-        return false;
-    if (attached)
-        return true;
-
-    if (!TryAttachTypedObjectGetPropStub(cx, script, stub, name, val, &attached))
-        return false;
-    if (attached)
-        return true;
-
-    if (val.isString() || val.isNumber() || val.isBoolean()) {
-        if (!TryAttachPrimitiveGetPropStub(cx, script, pc, stub, name, val, res, &attached))
-            return false;
-        if (attached)
-            return true;
-    }
-
-    if (res.isUndefined()) {
-        // Try attaching property-not-found optimized stub for undefined results.
-        if (!TryAttachNativeGetPropDoesNotExistStub(cx, script, pc, stub, name, val, &attached))
-            return false;
-        if (attached)
-            return true;
-    }
-
-    MOZ_ASSERT(!attached);
-    if (!isTemporarilyUnoptimizable)
-        stub->noteUnoptimizableAccess();
-
-    return true;
-}
-
-typedef bool (*DoGetPropFallbackFn)(JSContext*, BaselineFrame*, ICGetProp_Fallback*,
-                                    MutableHandleValue, MutableHandleValue);
-static const VMFunction DoGetPropFallbackInfo =
-    FunctionInfo<DoGetPropFallbackFn>(DoGetPropFallback, TailCall, PopValues(1));
-
-bool
-ICGetProp_Fallback::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-    MOZ_ASSERT(R0 == JSReturnOperand);
-
-    EmitRestoreTailCallReg(masm);
-
-    // Ensure stack is fully synced for the expression decompiler.
-    masm.pushValue(R0);
-
-    // Push arguments.
-    masm.pushValue(R0);
-    masm.push(ICStubReg);
-    pushFramePtr(masm, R0.scratchReg());
-
-    if (!tailCallVM(DoGetPropFallbackInfo, masm))
-        return false;
-
-    // What follows is bailout for inlined scripted getters.
-    // The return address pointed to by the baseline stack points here.
-    returnOffset_ = masm.currentOffset();
-
-    // Even though the fallback frame doesn't enter a stub frame, the CallScripted
-    // frame that we are emulating does. Again, we lie.
-    inStubFrame_ = true;
-#ifdef DEBUG
-    entersStubFrame_ = true;
-#endif
-
-    leaveStubFrame(masm, true);
-
-    // When we get here, ICStubReg contains the ICGetProp_Fallback stub,
-    // which we can't use to enter the TypeMonitor IC, because it's a MonitoredFallbackStub
-    // instead of a MonitoredStub. So, we cheat.
-    masm.loadPtr(Address(ICStubReg, ICMonitoredFallbackStub::offsetOfFallbackMonitorStub()),
-                 ICStubReg);
-    EmitEnterTypeMonitorIC(masm, ICTypeMonitor_Fallback::offsetOfFirstMonitorStub());
-
-    return true;
-}
-
-void
-ICGetProp_Fallback::Compiler::postGenerateStubCode(MacroAssembler& masm, Handle<JitCode*> code)
-{
-    cx->compartment()->jitCompartment()->initBaselineGetPropReturnAddr(code->raw() + returnOffset_);
-}
-
-bool
-ICGetProp_ArrayLength::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    Label failure;
-    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
-
-    Register scratch = R1.scratchReg();
-
-    // Unbox R0 and guard it's an array.
-    Register obj = masm.extractObject(R0, ExtractTemp0);
-    masm.branchTestObjClass(Assembler::NotEqual, obj, scratch, &ArrayObject::class_, &failure);
-
-    // Load obj->elements->length.
-    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
-    masm.load32(Address(scratch, ObjectElements::offsetOfLength()), scratch);
-
-    // Guard length fits in an int32.
-    masm.branchTest32(Assembler::Signed, scratch, scratch, &failure);
-
-    masm.tagValue(JSVAL_TYPE_INT32, scratch, R0);
-    EmitReturnFromIC(masm);
-
-    // Failure case - jump to next stub
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-    return true;
-}
-
-bool
-ICGetProp_UnboxedArrayLength::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    Label failure;
-    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
-
-    Register scratch = R1.scratchReg();
-
-    // Unbox R0 and guard it's an unboxed array.
-    Register obj = masm.extractObject(R0, ExtractTemp0);
-    masm.branchTestObjClass(Assembler::NotEqual, obj, scratch, &UnboxedArrayObject::class_, &failure);
-
-    // Load obj->length.
-    masm.load32(Address(obj, UnboxedArrayObject::offsetOfLength()), scratch);
-
-    masm.tagValue(JSVAL_TYPE_INT32, scratch, R0);
-    EmitReturnFromIC(masm);
-
-    // Failure case - jump to next stub
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-    return true;
-}
-
-bool
-ICGetProp_StringLength::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    Label failure;
-    masm.branchTestString(Assembler::NotEqual, R0, &failure);
-
-    // Unbox string and load its length.
-    Register string = masm.extractString(R0, ExtractTemp0);
-    masm.loadStringLength(string, string);
-
-    masm.tagValue(JSVAL_TYPE_INT32, string, R0);
-    EmitReturnFromIC(masm);
-
-    // Failure case - jump to next stub
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-    return true;
-}
-
-bool
-ICGetProp_Primitive::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    Label failure;
-    switch (primitiveType_) {
-      case JSVAL_TYPE_STRING:
-        masm.branchTestString(Assembler::NotEqual, R0, &failure);
-        break;
-      case JSVAL_TYPE_SYMBOL:
-        masm.branchTestSymbol(Assembler::NotEqual, R0, &failure);
-        break;
-      case JSVAL_TYPE_DOUBLE: // Also used for int32.
-        masm.branchTestNumber(Assembler::NotEqual, R0, &failure);
-        break;
-      case JSVAL_TYPE_BOOLEAN:
-        masm.branchTestBoolean(Assembler::NotEqual, R0, &failure);
-        break;
-      default:
-        MOZ_CRASH("unexpected type");
-    }
-
-    AllocatableGeneralRegisterSet regs(availableGeneralRegs(1));
-    Register holderReg = regs.takeAny();
-    Register scratchReg = regs.takeAny();
-
-    // Verify the shape of the prototype.
-    masm.movePtr(ImmGCPtr(prototype_.get()), holderReg);
-
-    Address shapeAddr(ICStubReg, ICGetProp_Primitive::offsetOfProtoShape());
-    masm.loadPtr(Address(holderReg, JSObject::offsetOfShape()), scratchReg);
-    masm.branchPtr(Assembler::NotEqual, shapeAddr, scratchReg, &failure);
-
-    if (!isFixedSlot_)
-        masm.loadPtr(Address(holderReg, NativeObject::offsetOfSlots()), holderReg);
-
-    masm.load32(Address(ICStubReg, ICGetProp_Primitive::offsetOfOffset()), scratchReg);
-    masm.loadValue(BaseIndex(holderReg, scratchReg, TimesOne), R0);
-
-    // Enter type monitor IC to type-check result.
-    EmitEnterTypeMonitorIC(masm);
-
-    // Failure case - jump to next stub
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-    return true;
-}
-
-ICGetPropNativeStub*
-ICGetPropNativeCompiler::getStub(ICStubSpace* space)
-{
-    ReceiverGuard guard(obj_);
-
-    switch (kind) {
-      case ICStub::GetProp_Native: {
-        MOZ_ASSERT(obj_ == holder_);
-        return newStub<ICGetProp_Native>(space, getStubCode(), firstMonitorStub_, guard, offset_);
-      }
-
-      case ICStub::GetProp_NativePrototype: {
-        MOZ_ASSERT(obj_ != holder_);
-        Shape* holderShape = holder_->as<NativeObject>().lastProperty();
-        return newStub<ICGetProp_NativePrototype>(space, getStubCode(), firstMonitorStub_, guard,
-                                                  offset_, holder_, holderShape);
-      }
-
-      case ICStub::GetName_Global: {
-        MOZ_ASSERT(obj_ != holder_);
-        Shape* holderShape = holder_->as<NativeObject>().lastProperty();
-        Shape* globalShape = obj_->as<ClonedBlockObject>().global().lastProperty();
-        return newStub<ICGetName_Global>(space, getStubCode(), firstMonitorStub_, guard,
-                                         offset_, holder_, holderShape, globalShape);
-      }
-
-      default:
-        MOZ_CRASH("Bad stub kind");
-    }
-}
-
-static void
-GuardGlobalObject(MacroAssembler& masm, HandleObject holder, Register globalLexicalReg,
-                  Register holderReg, Register scratch, size_t globalShapeOffset, Label* failure)
-{
-    if (holder->is<GlobalObject>())
-        return;
-    masm.extractObject(Address(globalLexicalReg, ScopeObject::offsetOfEnclosingScope()),
-                       holderReg);
-    masm.loadPtr(Address(ICStubReg, globalShapeOffset), scratch);
-    masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, failure);
-}
-
-bool
-ICGetPropNativeCompiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    Label failure;
-    AllocatableGeneralRegisterSet regs(availableGeneralRegs(0));
-    Register objReg = InvalidReg;
-
-    if (inputDefinitelyObject_) {
-        objReg = R0.scratchReg();
-    } else {
-        regs.take(R0);
-        // Guard input is an object and unbox.
-        masm.branchTestObject(Assembler::NotEqual, R0, &failure);
-        objReg = masm.extractObject(R0, ExtractTemp0);
-    }
-    regs.takeUnchecked(objReg);
-
-    Register scratch = regs.takeAnyExcluding(ICTailCallReg);
-
-    // Shape/group guard.
-    GuardReceiverObject(masm, ReceiverGuard(obj_), objReg, scratch,
-                        ICGetPropNativeStub::offsetOfReceiverGuard(), &failure);
-
-    Register holderReg;
-    if (obj_ == holder_) {
-        MOZ_ASSERT(kind != ICStub::GetName_Global);
-        if (obj_->is<UnboxedPlainObject>()) {
-            // We are loading off the expando object, so use that for the holder.
-            holderReg = regs.takeAny();
-            masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg);
-        } else {
-            holderReg = objReg;
-        }
-    } else {
-        holderReg = regs.takeAny();
-
-        // If we are generating a non-lexical GETGNAME stub, we must also
-        // guard on the shape of the GlobalObject.
-        if (kind == ICStub::GetName_Global) {
-            MOZ_ASSERT(obj_->is<ClonedBlockObject>() && obj_->as<ClonedBlockObject>().isGlobal());
-            GuardGlobalObject(masm, holder_, objReg, holderReg, scratch,
-                              ICGetName_Global::offsetOfGlobalShape(), &failure);
-        }
-
-        // Shape guard holder.
-        masm.loadPtr(Address(ICStubReg, ICGetProp_NativePrototype::offsetOfHolder()),
-                     holderReg);
-        masm.loadPtr(Address(ICStubReg, ICGetProp_NativePrototype::offsetOfHolderShape()),
-                     scratch);
-        masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failure);
-    }
-
-    if (!isFixedSlot_) {
-        // Don't overwrite actual holderReg if we need to load a dynamic slots object.
-        // May need to preserve object for noSuchMethod check later.
-        Register nextHolder = regs.takeAny();
-        masm.loadPtr(Address(holderReg, NativeObject::offsetOfSlots()), nextHolder);
-        holderReg = nextHolder;
-    }
-
-    masm.load32(Address(ICStubReg, ICGetPropNativeStub::offsetOfOffset()), scratch);
-    BaseIndex result(holderReg, scratch, TimesOne);
-
-    masm.loadValue(result, R0);
-
-    // Enter type monitor IC to type-check result.
-    EmitEnterTypeMonitorIC(masm);
-
-    // Failure case - jump to next stub
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-    return true;
-}
-
-ICStub*
-ICGetPropNativeDoesNotExistCompiler::getStub(ICStubSpace* space)
-{
-    Rooted<ShapeVector> shapes(cx, ShapeVector(cx));
-
-    if (!GetProtoShapes(obj_, protoChainDepth_, &shapes))
-        return nullptr;
-
-    JS_STATIC_ASSERT(ICGetProp_NativeDoesNotExist::MAX_PROTO_CHAIN_DEPTH == 8);
-
-    ICStub* stub = nullptr;
-    switch(protoChainDepth_) {
-      case 0: stub = getStubSpecific<0>(space, shapes); break;
-      case 1: stub = getStubSpecific<1>(space, shapes); break;
-      case 2: stub = getStubSpecific<2>(space, shapes); break;
-      case 3: stub = getStubSpecific<3>(space, shapes); break;
-      case 4: stub = getStubSpecific<4>(space, shapes); break;
-      case 5: stub = getStubSpecific<5>(space, shapes); break;
-      case 6: stub = getStubSpecific<6>(space, shapes); break;
-      case 7: stub = getStubSpecific<7>(space, shapes); break;
-      case 8: stub = getStubSpecific<8>(space, shapes); break;
-      default: MOZ_CRASH("ProtoChainDepth too high.");
-    }
-    if (!stub)
-        return nullptr;
-    return stub;
-}
-
-bool
-ICGetPropNativeDoesNotExistCompiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    Label failure;
-
-    AllocatableGeneralRegisterSet regs(availableGeneralRegs(1));
-    Register scratch = regs.takeAny();
-
-#ifdef DEBUG
-    // Ensure that protoChainDepth_ matches the protoChainDepth stored on the stub.
-    {
-        Label ok;
-        masm.load16ZeroExtend(Address(ICStubReg, ICStub::offsetOfExtra()), scratch);
-        masm.branch32(Assembler::Equal, scratch, Imm32(protoChainDepth_), &ok);
-        masm.assumeUnreachable("Non-matching proto chain depth on stub.");
-        masm.bind(&ok);
-    }
-#endif // DEBUG
-
-    // Guard input is an object.
-    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
-
-    // Unbox and guard against old shape/group.
-    Register objReg = masm.extractObject(R0, ExtractTemp0);
-    GuardReceiverObject(masm, ReceiverGuard(obj_), objReg, scratch,
-                        ICGetProp_NativeDoesNotExist::offsetOfGuard(), &failure);
-
-    Register protoReg = regs.takeAny();
-    // Check the proto chain.
-    for (size_t i = 0; i < protoChainDepth_; i++) {
-        masm.loadObjProto(i == 0 ? objReg : protoReg, protoReg);
-        masm.branchTestPtr(Assembler::Zero, protoReg, protoReg, &failure);
-        size_t shapeOffset = ICGetProp_NativeDoesNotExistImpl<0>::offsetOfShape(i);
-        masm.loadPtr(Address(ICStubReg, shapeOffset), scratch);
-        masm.branchTestObjShape(Assembler::NotEqual, protoReg, scratch, &failure);
-    }
-
-    // Shape and type checks succeeded, ok to proceed.
-    masm.moveValue(UndefinedValue(), R0);
-
-    // Normally for this op, the result would have to be monitored by TI.
-    // However, since this stub ALWAYS returns UndefinedValue(), and we can be sure
-    // that undefined is already registered with the type-set, this can be avoided.
-    EmitReturnFromIC(masm);
-
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-    return true;
-}
-
-bool
-ICGetProp_CallScripted::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    Label failure;
-    Label failureLeaveStubFrame;
-    AllocatableGeneralRegisterSet regs(availableGeneralRegs(1));
-    Register scratch = regs.takeAnyExcluding(ICTailCallReg);
-
-    // Guard input is an object.
-    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
-
-    // Unbox and shape guard.
-    Register objReg = masm.extractObject(R0, ExtractTemp0);
-    GuardReceiverObject(masm, ReceiverGuard(receiver_), objReg, scratch,
-                        ICGetProp_CallScripted::offsetOfReceiverGuard(), &failure);
-
-    if (receiver_ != holder_) {
-        Register holderReg = regs.takeAny();
-        masm.loadPtr(Address(ICStubReg, ICGetProp_CallScripted::offsetOfHolder()), holderReg);
-        masm.loadPtr(Address(ICStubReg, ICGetProp_CallScripted::offsetOfHolderShape()), scratch);
-        masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failure);
-        regs.add(holderReg);
-    }
-
-    // Push a stub frame so that we can perform a non-tail call.
-    enterStubFrame(masm, scratch);
-
-    // Load callee function and code.  To ensure that |code| doesn't end up being
-    // ArgumentsRectifierReg, if it's available we assign it to |callee| instead.
-    Register callee;
-    if (regs.has(ArgumentsRectifierReg)) {
-        callee = ArgumentsRectifierReg;
-        regs.take(callee);
-    } else {
-        callee = regs.takeAny();
-    }
-    Register code = regs.takeAny();
-    masm.loadPtr(Address(ICStubReg, ICGetProp_CallScripted::offsetOfGetter()), callee);
-    masm.branchIfFunctionHasNoScript(callee, &failureLeaveStubFrame);
-    masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), code);
-    masm.loadBaselineOrIonRaw(code, code, &failureLeaveStubFrame);
-
-    // Align the stack such that the JitFrameLayout is aligned on
-    // JitStackAlignment.
-    masm.alignJitStackBasedOnNArgs(0);
-
-    // Getter is called with 0 arguments, just |obj| as thisv.
-    // Note that we use Push, not push, so that callJit will align the stack
-    // properly on ARM.
-    masm.Push(R0);
-    EmitBaselineCreateStubFrameDescriptor(masm, scratch);
-    masm.Push(Imm32(0));  // ActualArgc is 0
-    masm.Push(callee);
-    masm.Push(scratch);
-
-    // Handle arguments underflow.
-    Label noUnderflow;
-    masm.load16ZeroExtend(Address(callee, JSFunction::offsetOfNargs()), scratch);
-    masm.branch32(Assembler::Equal, scratch, Imm32(0), &noUnderflow);
-    {
-        // Call the arguments rectifier.
-        MOZ_ASSERT(ArgumentsRectifierReg != code);
-
-        JitCode* argumentsRectifier =
-            cx->runtime()->jitRuntime()->getArgumentsRectifier();
-
-        masm.movePtr(ImmGCPtr(argumentsRectifier), code);
-        masm.loadPtr(Address(code, JitCode::offsetOfCode()), code);
-        masm.movePtr(ImmWord(0), ArgumentsRectifierReg);
-    }
-
-    masm.bind(&noUnderflow);
-    masm.callJit(code);
-
-    leaveStubFrame(masm, true);
-
-    // Enter type monitor IC to type-check result.
-    EmitEnterTypeMonitorIC(masm);
-
-    // Leave stub frame and go to next stub.
-    masm.bind(&failureLeaveStubFrame);
-    inStubFrame_ = true;
-    leaveStubFrame(masm, false);
-
-    // Failure case - jump to next stub
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-    return true;
-}
-
-bool
-ICGetPropCallNativeCompiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    Label failure;
-
-    AllocatableGeneralRegisterSet regs(availableGeneralRegs(1));
-    Register objReg = InvalidReg;
-
-    MOZ_ASSERT(!(inputDefinitelyObject_ && outerClass_));
-    if (inputDefinitelyObject_) {
-        objReg = R0.scratchReg();
-    } else {
-        // Guard input is an object and unbox.
-        masm.branchTestObject(Assembler::NotEqual, R0, &failure);
-        objReg = masm.extractObject(R0, ExtractTemp0);
-        if (outerClass_) {
-            Register tmp = regs.takeAny();
-            masm.branchTestObjClass(Assembler::NotEqual, objReg, tmp, outerClass_, &failure);
-            masm.movePtr(ImmGCPtr(cx->global()), objReg);
-            regs.add(tmp);
-        }
-    }
-
-    Register scratch = regs.takeAnyExcluding(ICTailCallReg);
-
-    // Shape guard.
-    GuardReceiverObject(masm, ReceiverGuard(receiver_), objReg, scratch,
-                        ICGetPropCallGetter::offsetOfReceiverGuard(), &failure);
-
-    if (receiver_ != holder_) {
-        Register holderReg = regs.takeAny();
-
-        // If we are generating a non-lexical GETGNAME stub, we must also
-        // guard on the shape of the GlobalObject.
-        if (kind == ICStub::GetProp_CallNativeGlobal) {
-            MOZ_ASSERT(receiver_->is<ClonedBlockObject>() &&
-                       receiver_->as<ClonedBlockObject>().isGlobal());
-            GuardGlobalObject(masm, holder_, objReg, holderReg, scratch,
-                              ICGetProp_CallNativeGlobal::offsetOfGlobalShape(), &failure);
-        }
-
-        masm.loadPtr(Address(ICStubReg, ICGetPropCallGetter::offsetOfHolder()), holderReg);
-        masm.loadPtr(Address(ICStubReg, ICGetPropCallGetter::offsetOfHolderShape()), scratch);
-        masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failure);
-        regs.add(holderReg);
-    }
-
-    // Box and push obj onto baseline frame stack for decompiler
-    if (inputDefinitelyObject_)
-        masm.tagValue(JSVAL_TYPE_OBJECT, objReg, R0);
-    EmitStowICValues(masm, 1);
-    if (inputDefinitelyObject_)
-        objReg = masm.extractObject(R0, ExtractTemp0);
-
-    // Push a stub frame so that we can perform a non-tail call.
-    enterStubFrame(masm, scratch);
-
-    // Load callee function.
-    Register callee = regs.takeAny();
-    masm.loadPtr(Address(ICStubReg, ICGetPropCallGetter::offsetOfGetter()), callee);
-
-    // If we're calling a getter on the global, inline the logic for the
-    // 'this' hook on the global lexical scope and manually push the global.
-    if (kind == ICStub::GetProp_CallNativeGlobal)
-        masm.extractObject(Address(objReg, ScopeObject::offsetOfEnclosingScope()), objReg);
-
-    // Push args for vm call.
-    masm.push(objReg);
-    masm.push(callee);
-
-    regs.add(R0);
-
-    if (!callVM(DoCallNativeGetterInfo, masm))
-        return false;
-    leaveStubFrame(masm);
-
-    EmitUnstowICValues(masm, 1, /* discard = */true);
-
-    // Enter type monitor IC to type-check result.
-    EmitEnterTypeMonitorIC(masm);
-
-    // Failure case - jump to next stub
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-    return true;
-}
-
-ICStub*
-ICGetPropCallNativeCompiler::getStub(ICStubSpace* space)
-{
-    ReceiverGuard guard(receiver_);
-    Shape* holderShape = holder_->as<NativeObject>().lastProperty();
-
-    switch (kind) {
-      case ICStub::GetProp_CallNative:
-        return newStub<ICGetProp_CallNative>(space, getStubCode(), firstMonitorStub_,
-                                             guard, holder_, holderShape,
-                                             getter_, pcOffset_);
-
-      case ICStub::GetProp_CallNativeGlobal: {
-        Shape* globalShape = receiver_->as<ClonedBlockObject>().global().lastProperty();
-        return newStub<ICGetProp_CallNativeGlobal>(space, getStubCode(), firstMonitorStub_,
-                                                   guard, holder_, holderShape, globalShape,
-                                                   getter_, pcOffset_);
-      }
-
-      default:
-        MOZ_CRASH("Bad stub kind");
-    }
-}
-
-bool
-ICGetPropCallDOMProxyNativeCompiler::generateStubCode(MacroAssembler& masm,
-                                                      Address* expandoAndGenerationAddr,
-                                                      Address* generationAddr)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    Label failure;
-    AllocatableGeneralRegisterSet regs(availableGeneralRegs(1));
-    Register scratch = regs.takeAnyExcluding(ICTailCallReg);
-
-    // Guard input is an object.
-    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
-
-    // Unbox.
-    Register objReg = masm.extractObject(R0, ExtractTemp0);
-
-    // Shape guard.
-    static const size_t receiverShapeOffset =
-        ICGetProp_CallDOMProxyNative::offsetOfReceiverGuard() +
-        HeapReceiverGuard::offsetOfShape();
-    masm.loadPtr(Address(ICStubReg, receiverShapeOffset), scratch);
-    masm.branchTestObjShape(Assembler::NotEqual, objReg, scratch, &failure);
-
-    // Guard that our expando object hasn't started shadowing this property.
-    {
-        AllocatableGeneralRegisterSet domProxyRegSet(GeneralRegisterSet::All());
-        domProxyRegSet.take(ICStubReg);
-        domProxyRegSet.take(objReg);
-        domProxyRegSet.take(scratch);
-        Address expandoShapeAddr(ICStubReg, ICGetProp_CallDOMProxyNative::offsetOfExpandoShape());
-        CheckDOMProxyExpandoDoesNotShadow(
-                cx, masm, objReg,
-                expandoShapeAddr, expandoAndGenerationAddr, generationAddr,
-                scratch,
-                domProxyRegSet,
-                &failure);
-    }
-
-    Register holderReg = regs.takeAny();
-    masm.loadPtr(Address(ICStubReg, ICGetProp_CallDOMProxyNative::offsetOfHolder()),
-                 holderReg);
-    masm.loadPtr(Address(ICStubReg, ICGetProp_CallDOMProxyNative::offsetOfHolderShape()),
-                 scratch);
-    masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failure);
-    regs.add(holderReg);
-
-    // Push a stub frame so that we can perform a non-tail call.
-    enterStubFrame(masm, scratch);
-
-    // Load callee function.
-    Register callee = regs.takeAny();
-    masm.loadPtr(Address(ICStubReg, ICGetProp_CallDOMProxyNative::offsetOfGetter()), callee);
-
-    // Push args for vm call.
-    masm.push(objReg);
-    masm.push(callee);
-
-    // Don't have to preserve R0 anymore.
-    regs.add(R0);
-
-    if (!callVM(DoCallNativeGetterInfo, masm))
-        return false;
-    leaveStubFrame(masm);
-
-    // Enter type monitor IC to type-check result.
-    EmitEnterTypeMonitorIC(masm);
-
-    // Failure case - jump to next stub
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-    return true;
-}
-
-bool
-ICGetPropCallDOMProxyNativeCompiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    if (kind == ICStub::GetProp_CallDOMProxyNative)
-        return generateStubCode(masm, nullptr, nullptr);
-
-    Address internalStructAddress(ICStubReg,
-        ICGetProp_CallDOMProxyWithGenerationNative::offsetOfInternalStruct());
-    Address generationAddress(ICStubReg,
-        ICGetProp_CallDOMProxyWithGenerationNative::offsetOfGeneration());
-    return generateStubCode(masm, &internalStructAddress, &generationAddress);
-}
-
-ICStub*
-ICGetPropCallDOMProxyNativeCompiler::getStub(ICStubSpace* space)
-{
-    RootedShape shape(cx, proxy_->maybeShape());
-    RootedShape holderShape(cx, holder_->as<NativeObject>().lastProperty());
-
-    Value expandoSlot = GetProxyExtra(proxy_, GetDOMProxyExpandoSlot());
-    RootedShape expandoShape(cx, nullptr);
-    ExpandoAndGeneration* expandoAndGeneration;
-    int32_t generation;
-    Value expandoVal;
-    if (kind == ICStub::GetProp_CallDOMProxyNative) {
-        expandoVal = expandoSlot;
-        expandoAndGeneration = nullptr;  // initialize to silence GCC warning
-        generation = 0;  // initialize to silence GCC warning
-    } else {
-        MOZ_ASSERT(kind == ICStub::GetProp_CallDOMProxyWithGenerationNative);
-        MOZ_ASSERT(!expandoSlot.isObject() && !expandoSlot.isUndefined());
-        expandoAndGeneration = (ExpandoAndGeneration*)expandoSlot.toPrivate();
-        expandoVal = expandoAndGeneration->expando;
-        generation = expandoAndGeneration->generation;
-    }
-
-    if (expandoVal.isObject())
-        expandoShape = expandoVal.toObject().as<NativeObject>().lastProperty();
-
-    if (kind == ICStub::GetProp_CallDOMProxyNative) {
-        return newStub<ICGetProp_CallDOMProxyNative>(
-            space, getStubCode(), firstMonitorStub_, shape,
-            expandoShape, holder_, holderShape, getter_, pcOffset_);
-    }
-
-    return newStub<ICGetProp_CallDOMProxyWithGenerationNative>(
-        space, getStubCode(), firstMonitorStub_, shape,
-        expandoAndGeneration, generation, expandoShape, holder_, holderShape, getter_,
-        pcOffset_);
-}
-
-ICStub*
-ICGetProp_DOMProxyShadowed::Compiler::getStub(ICStubSpace* space)
-{
-    RootedShape shape(cx, proxy_->maybeShape());
-    return New<ICGetProp_DOMProxyShadowed>(cx, space, getStubCode(), firstMonitorStub_, shape,
-                                           proxy_->handler(), name_, pcOffset_);
-}
-
-static bool
-ProxyGet(JSContext* cx, HandleObject proxy, HandlePropertyName name, MutableHandleValue vp)
-{
-    RootedValue receiver(cx, ObjectValue(*proxy));
-    RootedId id(cx, NameToId(name));
-    return Proxy::get(cx, proxy, receiver, id, vp);
-}
-
-typedef bool (*ProxyGetFn)(JSContext* cx, HandleObject proxy, HandlePropertyName name,
-                           MutableHandleValue vp);
-static const VMFunction ProxyGetInfo = FunctionInfo<ProxyGetFn>(ProxyGet);
-
-bool
-ICGetProp_DOMProxyShadowed::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    Label failure;
-
-    AllocatableGeneralRegisterSet regs(availableGeneralRegs(1));
-    // Need to reserve a scratch register, but the scratch register should not be
-    // ICTailCallReg, because it's used for |enterStubFrame| which needs a
-    // non-ICTailCallReg scratch reg.
-    Register scratch = regs.takeAnyExcluding(ICTailCallReg);
-
-    // Guard input is an object.
-    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
-
-    // Unbox.
-    Register objReg = masm.extractObject(R0, ExtractTemp0);
-
-    // Shape guard.
-    masm.loadPtr(Address(ICStubReg, ICGetProp_DOMProxyShadowed::offsetOfShape()), scratch);
-    masm.branchTestObjShape(Assembler::NotEqual, objReg, scratch, &failure);
-
-    // No need to do any more guards; it's safe to call ProxyGet even
-    // if we've since stopped shadowing.
-
-    // Call ProxyGet(JSContext* cx, HandleObject proxy, HandlePropertyName name, MutableHandleValue vp);
-
-    // Push a stub frame so that we can perform a non-tail call.
-    enterStubFrame(masm, scratch);
-
-    // Push property name and proxy object.
-    masm.loadPtr(Address(ICStubReg, ICGetProp_DOMProxyShadowed::offsetOfName()), scratch);
-    masm.push(scratch);
-    masm.push(objReg);
-
-    // Don't have to preserve R0 anymore.
-    regs.add(R0);
-
-    if (!callVM(ProxyGetInfo, masm))
-        return false;
-    leaveStubFrame(masm);
-
-    // Enter type monitor IC to type-check result.
-    EmitEnterTypeMonitorIC(masm);
-
-    // Failure case - jump to next stub
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-    return true;
-}
-
-bool
-ICGetProp_ArgumentsLength::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    Label failure;
-    if (which_ == ICGetProp_ArgumentsLength::Magic) {
-        // Ensure that this is lazy arguments.
-        masm.branchTestMagicValue(Assembler::NotEqual, R0, JS_OPTIMIZED_ARGUMENTS, &failure);
-
-        // Ensure that frame has not loaded different arguments object since.
-        masm.branchTest32(Assembler::NonZero,
-                          Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFlags()),
-                          Imm32(BaselineFrame::HAS_ARGS_OBJ),
-                          &failure);
-
-        Address actualArgs(BaselineFrameReg, BaselineFrame::offsetOfNumActualArgs());
-        masm.loadPtr(actualArgs, R0.scratchReg());
-        masm.tagValue(JSVAL_TYPE_INT32, R0.scratchReg(), R0);
-        EmitReturnFromIC(masm);
-
-        masm.bind(&failure);
-        EmitStubGuardFailure(masm);
-        return true;
-    }
-    MOZ_ASSERT(which_ == ICGetProp_ArgumentsLength::Mapped ||
-               which_ == ICGetProp_ArgumentsLength::Unmapped);
-
-    const Class* clasp = (which_ == ICGetProp_ArgumentsLength::Mapped)
-                         ? &MappedArgumentsObject::class_
-                         : &UnmappedArgumentsObject::class_;
-
-    Register scratchReg = R1.scratchReg();
-
-    // Guard on input being an arguments object.
-    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
-    Register objReg = masm.extractObject(R0, ExtractTemp0);
-    masm.branchTestObjClass(Assembler::NotEqual, objReg, scratchReg, clasp, &failure);
-
-    // Get initial length value.
-    masm.unboxInt32(Address(objReg, ArgumentsObject::getInitialLengthSlotOffset()), scratchReg);
-
-    // Test if length has been overridden.
-    masm.branchTest32(Assembler::NonZero,
-                      scratchReg,
-                      Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT),
-                      &failure);
-
-    // Nope, shift out arguments length and return it.
-    // No need to type monitor because this stub always returns Int32.
-    masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), scratchReg);
-    masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R0);
-    EmitReturnFromIC(masm);
-
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-    return true;
-}
-
-ICGetProp_ArgumentsCallee::ICGetProp_ArgumentsCallee(JitCode* stubCode, ICStub* firstMonitorStub)
-  : ICMonitoredStub(GetProp_ArgumentsCallee, stubCode, firstMonitorStub)
-{ }
-
-bool
-ICGetProp_ArgumentsCallee::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    Label failure;
-
-    // Ensure that this is lazy arguments.
-    masm.branchTestMagicValue(Assembler::NotEqual, R0, JS_OPTIMIZED_ARGUMENTS, &failure);
-
-    // Ensure that frame has not loaded different arguments object since.
-    masm.branchTest32(Assembler::NonZero,
-                      Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFlags()),
-                      Imm32(BaselineFrame::HAS_ARGS_OBJ),
-                      &failure);
-
-    Address callee(BaselineFrameReg, BaselineFrame::offsetOfCalleeToken());
-    masm.loadFunctionFromCalleeToken(callee, R0.scratchReg());
-    masm.tagValue(JSVAL_TYPE_OBJECT, R0.scratchReg(), R0);
-
-    EmitEnterTypeMonitorIC(masm);
-
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-    return true;
-}
-
-/* static */ ICGetProp_Generic*
-ICGetProp_Generic::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                         ICGetProp_Generic& other)
-{
-    return New<ICGetProp_Generic>(cx, space, other.jitCode(), firstMonitorStub);
-}
-
-static bool
-DoGetPropGeneric(JSContext* cx, BaselineFrame* frame, ICGetProp_Generic* stub, MutableHandleValue val, MutableHandleValue res)
-{
-    jsbytecode* pc = stub->getChainFallback()->icEntry()->pc(frame->script());
-    JSOp op = JSOp(*pc);
-    RootedPropertyName name(cx, frame->script()->getName(pc));
-    return ComputeGetPropResult(cx, frame, op, name, val, res);
-}
-
-typedef bool (*DoGetPropGenericFn)(JSContext*, BaselineFrame*, ICGetProp_Generic*, MutableHandleValue, MutableHandleValue);
-static const VMFunction DoGetPropGenericInfo = FunctionInfo<DoGetPropGenericFn>(DoGetPropGeneric);
-
-bool
-ICGetProp_Generic::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    AllocatableGeneralRegisterSet regs(availableGeneralRegs(1));
-
-    Register scratch = regs.takeAnyExcluding(ICTailCallReg);
-
-    // Sync for the decompiler.
-    EmitStowICValues(masm, 1);
-
-    enterStubFrame(masm, scratch);
-
-    // Push arguments.
-    masm.pushValue(R0);
-    masm.push(ICStubReg);
-    pushFramePtr(masm, R0.scratchReg());
-
-    if(!callVM(DoGetPropGenericInfo, masm))
-        return false;
-
-    leaveStubFrame(masm);
-    EmitUnstowICValues(masm, 1, /* discard = */ true);
-    EmitEnterTypeMonitorIC(masm);
-    return true;
-}
-
-bool
-ICGetProp_Unboxed::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    Label failure;
-
-    AllocatableGeneralRegisterSet regs(availableGeneralRegs(1));
-
-    Register scratch = regs.takeAnyExcluding(ICTailCallReg);
-
-    // Object and group guard.
-    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
-    Register object = masm.extractObject(R0, ExtractTemp0);
-    masm.loadPtr(Address(ICStubReg, ICGetProp_Unboxed::offsetOfGroup()), scratch);
-    masm.branchPtr(Assembler::NotEqual, Address(object, JSObject::offsetOfGroup()), scratch,
-                   &failure);
-
-    // Get the address being read from.
-    masm.load32(Address(ICStubReg, ICGetProp_Unboxed::offsetOfFieldOffset()), scratch);
-
-    masm.loadUnboxedProperty(BaseIndex(object, scratch, TimesOne), fieldType_, TypedOrValueRegister(R0));
-
-    // Only monitor the result if its type might change.
-    if (fieldType_ == JSVAL_TYPE_OBJECT)
-        EmitEnterTypeMonitorIC(masm);
-    else
-        EmitReturnFromIC(masm);
-
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-
-    return true;
-}
-
-bool
-ICGetProp_TypedObject::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    Label failure;
-
-    CheckForNeuteredTypedObject(cx, masm, &failure);
-
-    AllocatableGeneralRegisterSet regs(availableGeneralRegs(1));
-
-    Register scratch1 = regs.takeAnyExcluding(ICTailCallReg);
-    Register scratch2 = regs.takeAnyExcluding(ICTailCallReg);
-
-    // Object and shape guard.
-    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
-    Register object = masm.extractObject(R0, ExtractTemp0);
-    masm.loadPtr(Address(ICStubReg, ICGetProp_TypedObject::offsetOfShape()), scratch1);
-    masm.branchTestObjShape(Assembler::NotEqual, object, scratch1, &failure);
-
-    // Get the object's data pointer.
-    LoadTypedThingData(masm, layout_, object, scratch1);
-
-    // Get the address being written to.
-    masm.load32(Address(ICStubReg, ICGetProp_TypedObject::offsetOfFieldOffset()), scratch2);
-    masm.addPtr(scratch2, scratch1);
-
-    // Only monitor the result if the type produced by this stub might vary.
-    bool monitorLoad;
-
-    if (fieldDescr_->is<ScalarTypeDescr>()) {
-        Scalar::Type type = fieldDescr_->as<ScalarTypeDescr>().type();
-        monitorLoad = type == Scalar::Uint32;
-
-        masm.loadFromTypedArray(type, Address(scratch1, 0), R0, /* allowDouble = */ true,
-                                scratch2, nullptr);
-    } else {
-        ReferenceTypeDescr::Type type = fieldDescr_->as<ReferenceTypeDescr>().type();
-        monitorLoad = type != ReferenceTypeDescr::TYPE_STRING;
-
-        switch (type) {
-          case ReferenceTypeDescr::TYPE_ANY:
-            masm.loadValue(Address(scratch1, 0), R0);
-            break;
-
-          case ReferenceTypeDescr::TYPE_OBJECT: {
-            Label notNull, done;
-            masm.loadPtr(Address(scratch1, 0), scratch1);
-            masm.branchTestPtr(Assembler::NonZero, scratch1, scratch1, &notNull);
-            masm.moveValue(NullValue(), R0);
-            masm.jump(&done);
-            masm.bind(&notNull);
-            masm.tagValue(JSVAL_TYPE_OBJECT, scratch1, R0);
-            masm.bind(&done);
-            break;
-          }
-
-          case ReferenceTypeDescr::TYPE_STRING:
-            masm.loadPtr(Address(scratch1, 0), scratch1);
-            masm.tagValue(JSVAL_TYPE_STRING, scratch1, R0);
-            break;
-
-          default:
-            MOZ_CRASH();
-        }
-    }
-
-    if (monitorLoad)
-        EmitEnterTypeMonitorIC(masm);
-    else
-        EmitReturnFromIC(masm);
-
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-
-    return true;
-}
-
-void
-BaselineScript::noteAccessedGetter(uint32_t pcOffset)
-{
-    ICEntry& entry = icEntryFromPCOffset(pcOffset);
-    ICFallbackStub* stub = entry.fallbackStub();
-
-    if (stub->isGetProp_Fallback())
-        stub->toGetProp_Fallback()->noteAccessedGetter();
-}
-
-//
 // SetProp_Fallback
 //
 
 // Attach an optimized property set stub for a SETPROP/SETGNAME/SETNAME op on a
 // value property.
 static bool
 TryAttachSetValuePropStub(JSContext* cx, HandleScript script, jsbytecode* pc, ICSetProp_Fallback* stub,
                           HandleObject obj, HandleShape oldShape, HandleObjectGroup oldGroup,
@@ -7449,27 +4869,30 @@ ICSetProp_Fallback::Compiler::generateSt
     masm.pushValue(R1);
     masm.pushValue(R0);
     masm.push(ICStubReg);
     pushFramePtr(masm, R0.scratchReg());
 
     if (!tailCallVM(DoSetPropFallbackInfo, masm))
         return false;
 
+    // Even though the fallback frame doesn't enter a stub frame, the CallScripted
+    // frame that we are emulating does. Again, we lie.
+#ifdef DEBUG
+    EmitRepushTailCallReg(masm);
+    EmitStowICValues(masm, 1);
+    enterStubFrame(masm, R1.scratchReg());
+#else
+    inStubFrame_ = true;
+#endif
+
     // What follows is bailout-only code for inlined script getters.
     // The return address pointed to by the baseline stack points here.
     returnOffset_ = masm.currentOffset();
 
-    // Even though the fallback frame doesn't enter a stub frame, the CallScripted
-    // frame that we are emulating does. Again, we lie.
-    inStubFrame_ = true;
-#ifdef DEBUG
-    entersStubFrame_ = true;
-#endif
-
     leaveStubFrame(masm, true);
 
     // Retrieve the stashed initial argument from the caller's frame before returning
     EmitUnstowICValues(masm, 1);
     EmitReturnFromIC(masm);
 
     return true;
 }
@@ -7945,16 +5368,17 @@ ICSetProp_CallScripted::Compiler::genera
 
     // Stow R0 and R1 to free up registers.
     EmitStowICValues(masm, 2);
 
     AllocatableGeneralRegisterSet regs(availableGeneralRegs(1));
     Register scratch = regs.takeAnyExcluding(ICTailCallReg);
 
     // Unbox and shape guard.
+    uint32_t framePushed = masm.framePushed();
     Register objReg = masm.extractObject(R0, ExtractTemp0);
     GuardReceiverObject(masm, ReceiverGuard(receiver_), objReg, scratch,
                         ICSetProp_CallScripted::offsetOfReceiverGuard(), &failureUnstow);
 
     if (receiver_ != holder_) {
         Register holderReg = regs.takeAny();
         masm.loadPtr(Address(ICStubReg, ICSetProp_CallScripted::offsetOfHolder()), holderReg);
         masm.loadPtr(Address(ICStubReg, ICSetProp_CallScripted::offsetOfHolderShape()), scratch);
@@ -8011,30 +5435,34 @@ ICSetProp_CallScripted::Compiler::genera
         masm.movePtr(ImmGCPtr(argumentsRectifier), code);
         masm.loadPtr(Address(code, JitCode::offsetOfCode()), code);
         masm.movePtr(ImmWord(1), ArgumentsRectifierReg);
     }
 
     masm.bind(&noUnderflow);
     masm.callJit(code);
 
+    uint32_t framePushedAfterCall = masm.framePushed();
+
     leaveStubFrame(masm, true);
     // Do not care about return value from function. The original RHS should be returned
     // as the result of this operation.
     EmitUnstowICValues(masm, 2);
     masm.moveValue(R1, R0);
     EmitReturnFromIC(masm);
 
     // Leave stub frame and go to next stub.
     masm.bind(&failureLeaveStubFrame);
+    masm.setFramePushed(framePushedAfterCall);
     inStubFrame_ = true;
     leaveStubFrame(masm, false);
 
     // Unstow R0 and R1
     masm.bind(&failureUnstow);
+    masm.setFramePushed(framePushed);
     EmitUnstowICValues(masm, 2);
 
     // Failure case - jump to next stub
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
 }
 
@@ -8069,16 +5497,17 @@ ICSetProp_CallNative::Compiler::generate
 
     // Stow R0 and R1 to free up registers.
     EmitStowICValues(masm, 2);
 
     AllocatableGeneralRegisterSet regs(availableGeneralRegs(1));
     Register scratch = regs.takeAnyExcluding(ICTailCallReg);
 
     // Unbox and shape guard.
+    uint32_t framePushed = masm.framePushed();
     Register objReg = masm.extractObject(R0, ExtractTemp0);
     GuardReceiverObject(masm, ReceiverGuard(receiver_), objReg, scratch,
                         ICSetProp_CallNative::offsetOfReceiverGuard(), &failureUnstow);
 
     if (receiver_ != holder_) {
         Register holderReg = regs.takeAny();
         masm.loadPtr(Address(ICStubReg, ICSetProp_CallNative::offsetOfHolder()), holderReg);
         masm.loadPtr(Address(ICStubReg, ICSetProp_CallNative::offsetOfHolderShape()), scratch);
@@ -8111,16 +5540,17 @@ ICSetProp_CallNative::Compiler::generate
     // Do not care about return value from function. The original RHS should be returned
     // as the result of this operation.
     EmitUnstowICValues(masm, 2);
     masm.moveValue(R1, R0);
     EmitReturnFromIC(masm);
 
     // Unstow R0 and R1
     masm.bind(&failureUnstow);
+    masm.setFramePushed(framePushed);
     EmitUnstowICValues(masm, 2);
 
     // Failure case - jump to next stub
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
 }
 
@@ -8753,20 +6183,24 @@ DoCallFallback(JSContext* cx, BaselineFr
     TypeScript::Monitor(cx, script, pc, res);
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid())
         return true;
 
     // Attach a new TypeMonitor stub for this value.
     ICTypeMonitor_Fallback* typeMonFbStub = stub->fallbackMonitorStub();
-    if (!typeMonFbStub->addMonitorStubForValue(cx, script, res))
+    if (!typeMonFbStub->addMonitorStubForValue(cx, script, res,
+        ICStubCompiler::Engine::Baseline))
+    {
         return false;
+    }
+
     // Add a type monitor stub for the resulting value.
-    if (!stub->addMonitorStubForValue(cx, script, res))
+    if (!stub->addMonitorStubForValue(cx, script, res, ICStubCompiler::Engine::Baseline))
         return false;
 
     // If 'callee' is a potential Call_StringSplit, try to attach an
     // optimized StringSplit stub.
     if (!TryAttachStringSplit(cx, stub, script, argc, vp, pc, res, &handled))
         return false;
 
     if (!handled)
@@ -8808,20 +6242,23 @@ DoSpreadCallFallback(JSContext* cx, Base
         return false;
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid())
         return true;
 
     // Attach a new TypeMonitor stub for this value.
     ICTypeMonitor_Fallback* typeMonFbStub = stub->fallbackMonitorStub();
-    if (!typeMonFbStub->addMonitorStubForValue(cx, script, res))
+    if (!typeMonFbStub->addMonitorStubForValue(cx, script, res,
+        ICStubCompiler::Engine::Baseline))
+    {
         return false;
+    }
     // Add a type monitor stub for the resulting value.
-    if (!stub->addMonitorStubForValue(cx, script, res))
+    if (!stub->addMonitorStubForValue(cx, script, res, ICStubCompiler::Engine::Baseline))
         return false;
 
     if (!handled)
         stub->noteUnoptimizableCall();
     return true;
 }
 
 void
@@ -9169,17 +6606,17 @@ ICCall_Fallback::Compiler::generateStubC
         masm.pushValue(Address(BaselineFrameReg, valueOffset++ * sizeof(Value) + STUB_FRAME_SIZE));
 
         // callee
         masm.pushValue(Address(BaselineFrameReg, valueOffset++ * sizeof(Value) + STUB_FRAME_SIZE));
 
         masm.push(masm.getStackPointer());
         masm.push(ICStubReg);
 
-        pushFramePtr(masm, R0.scratchReg());
+        PushFramePtr(masm, R0.scratchReg());
 
         if (!callVM(DoSpreadCallFallbackInfo, masm))
             return false;
 
         leaveStubFrame(masm);
         EmitReturnFromIC(masm);
 
         // SPREADCALL is not yet supported in Ion, so do not generate asmcode for
@@ -9193,31 +6630,33 @@ ICCall_Fallback::Compiler::generateStubC
     regs.take(R0.scratchReg()); // argc.
 
     pushCallArguments(masm, regs, R0.scratchReg(), /* isJitCall = */ false, isConstructing_);
 
     masm.push(masm.getStackPointer());
     masm.push(R0.scratchReg());
     masm.push(ICStubReg);
 
-    pushFramePtr(masm, R0.scratchReg());
+    PushFramePtr(masm, R0.scratchReg());
 
     if (!callVM(DoCallFallbackInfo, masm))
         return false;
 
+    uint32_t framePushed = masm.framePushed();
     leaveStubFrame(masm);
     EmitReturnFromIC(masm);
 
     // The following asmcode is only used when an Ion inlined frame bails out
     // into into baseline jitcode. The return address pushed onto the
     // reconstructed baseline stack points here.
     returnOffset_ = masm.currentOffset();
 
     // Here we are again in a stub frame. Marking as so.
     inStubFrame_ = true;
+    masm.setFramePushed(framePushed);
 
     // Load passed-in ThisV into R1 just in case it's needed.  Need to do this before
     // we leave the stub frame since that info will be lost.
     // Current stack:  [...., ThisV, ActualArgc, CalleeToken, Descriptor ]
     masm.loadValue(Address(masm.getStackPointer(), 3 * sizeof(size_t)), R1);
 
     leaveStubFrame(masm, true);
 
@@ -11142,61 +8581,16 @@ ICGetName_Scope<NumHops>::ICGetName_Scop
 ICGetIntrinsic_Constant::ICGetIntrinsic_Constant(JitCode* stubCode, const Value& value)
   : ICStub(GetIntrinsic_Constant, stubCode),
     value_(value)
 { }
 
 ICGetIntrinsic_Constant::~ICGetIntrinsic_Constant()
 { }
 
-ICGetProp_Primitive::ICGetProp_Primitive(JitCode* stubCode, ICStub* firstMonitorStub,
-                                         JSValueType primitiveType, Shape* protoShape,
-                                         uint32_t offset)
-  : ICMonitoredStub(GetProp_Primitive, stubCode, firstMonitorStub),
-    protoShape_(protoShape),
-    offset_(offset)
-{
-    extra_ = uint16_t(primitiveType);
-    MOZ_ASSERT(JSValueType(extra_) == primitiveType);
-}
-
-ICGetPropNativeStub::ICGetPropNativeStub(ICStub::Kind kind, JitCode* stubCode,
-                                         ICStub* firstMonitorStub,
-                                         ReceiverGuard guard, uint32_t offset)
-  : ICMonitoredStub(kind, stubCode, firstMonitorStub),
-    receiverGuard_(guard),
-    offset_(offset)
-{ }
-
-/* static */ ICGetProp_Native*
-ICGetProp_Native::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                        ICGetProp_Native& other)
-{
-    return New<ICGetProp_Native>(cx, space, other.jitCode(), firstMonitorStub,
-                                 other.receiverGuard(), other.offset());
-}
-
-ICGetPropNativePrototypeStub::ICGetPropNativePrototypeStub(ICStub::Kind kind, JitCode* stubCode,
-                                                           ICStub* firstMonitorStub,
-                                                           ReceiverGuard guard, uint32_t offset,
-                                                           JSObject* holder, Shape* holderShape)
-  : ICGetPropNativeStub(kind, stubCode, firstMonitorStub, guard, offset),
-    holder_(holder),
-    holderShape_(holderShape)
-{ }
-
-/* static */ ICGetProp_NativePrototype*
-ICGetProp_NativePrototype::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                                 ICGetProp_NativePrototype& other)
-{
-    return New<ICGetProp_NativePrototype>(cx, space, other.jitCode(), firstMonitorStub,
-                                          other.receiverGuard(), other.offset(),
-                                          other.holder(), other.holderShape());
-}
-
 ICGetName_Global::ICGetName_Global(JitCode* stubCode, ICStub* firstMonitorStub,
                                    ReceiverGuard guard, uint32_t offset,
                                    JSObject* holder, Shape* holderShape, Shape* globalShape)
   : ICGetPropNativePrototypeStub(GetName_Global, stubCode, firstMonitorStub, guard, offset,
                                  holder, holderShape),
     globalShape_(globalShape)
 { }
 
@@ -11204,113 +8598,24 @@ ICGetName_Global::ICGetName_Global(JitCo
 ICGetName_Global::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
                         ICGetName_Global& other)
 {
     return New<ICGetName_Global>(cx, space, other.jitCode(), firstMonitorStub,
                                  other.receiverGuard(), other.offset(),
                                  other.holder(), other.holderShape(), other.globalShape());
 }
 
-ICGetProp_NativeDoesNotExist::ICGetProp_NativeDoesNotExist(
-    JitCode* stubCode, ICStub* firstMonitorStub, ReceiverGuard guard,
-    size_t protoChainDepth)
-  : ICMonitoredStub(GetProp_NativeDoesNotExist, stubCode, firstMonitorStub),
-    guard_(guard)
-{
-    MOZ_ASSERT(protoChainDepth <= MAX_PROTO_CHAIN_DEPTH);
-    extra_ = protoChainDepth;
-}
-
-/* static */ size_t
-ICGetProp_NativeDoesNotExist::offsetOfShape(size_t idx)
-{
-    MOZ_ASSERT(ICGetProp_NativeDoesNotExistImpl<0>::offsetOfShape(idx) ==
-               ICGetProp_NativeDoesNotExistImpl<
-                    ICGetProp_NativeDoesNotExist::MAX_PROTO_CHAIN_DEPTH>::offsetOfShape(idx));
-    return ICGetProp_NativeDoesNotExistImpl<0>::offsetOfShape(idx);
-}
-
-template <size_t ProtoChainDepth>
-ICGetProp_NativeDoesNotExistImpl<ProtoChainDepth>::ICGetProp_NativeDoesNotExistImpl(
-        JitCode* stubCode, ICStub* firstMonitorStub, ReceiverGuard guard,
-        Handle<ShapeVector> shapes)
-  : ICGetProp_NativeDoesNotExist(stubCode, firstMonitorStub, guard, ProtoChainDepth)
-{
-    MOZ_ASSERT(shapes.length() == NumShapes);
-
-    // Note: using int32_t here to avoid gcc warning.
-    for (int32_t i = 0; i < int32_t(NumShapes); i++)
-        shapes_[i].init(shapes[i]);
-}
-
-ICGetPropNativeDoesNotExistCompiler::ICGetPropNativeDoesNotExistCompiler(
-        JSContext* cx, ICStub* firstMonitorStub, HandleObject obj, size_t protoChainDepth)
-  : ICStubCompiler(cx, ICStub::GetProp_NativeDoesNotExist, Engine::Baseline),
-    firstMonitorStub_(firstMonitorStub),
-    obj_(cx, obj),
-    protoChainDepth_(protoChainDepth)
-{
-    MOZ_ASSERT(protoChainDepth_ <= ICGetProp_NativeDoesNotExist::MAX_PROTO_CHAIN_DEPTH);
-}
-
-ICGetPropCallGetter::ICGetPropCallGetter(Kind kind, JitCode* stubCode, ICStub* firstMonitorStub,
-                                         ReceiverGuard receiverGuard, JSObject* holder,
-                                         Shape* holderShape, JSFunction* getter,
-                                         uint32_t pcOffset)
-  : ICMonitoredStub(kind, stubCode, firstMonitorStub),
-    receiverGuard_(receiverGuard),
-    holder_(holder),
-    holderShape_(holderShape),
-    getter_(getter),
-    pcOffset_(pcOffset)
-{
-    MOZ_ASSERT(kind == ICStub::GetProp_CallScripted  ||
-               kind == ICStub::GetProp_CallNative    ||
-               kind == ICStub::GetProp_CallNativeGlobal ||
-               kind == ICStub::GetProp_CallDOMProxyNative ||
-               kind == ICStub::GetProp_CallDOMProxyWithGenerationNative);
-}
-
 ICInstanceOf_Function::ICInstanceOf_Function(JitCode* stubCode, Shape* shape,
                                              JSObject* prototypeObj, uint32_t slot)
   : ICStub(InstanceOf_Function, stubCode),
     shape_(shape),
     prototypeObj_(prototypeObj),
     slot_(slot)
 { }
 
-/* static */ ICGetProp_CallScripted*
-ICGetProp_CallScripted::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                              ICGetProp_CallScripted& other)
-{
-    return New<ICGetProp_CallScripted>(cx, space, other.jitCode(), firstMonitorStub,
-                                       other.receiverGuard(),
-                                       other.holder_, other.holderShape_,
-                                       other.getter_, other.pcOffset_);
-}
-
-/* static */ ICGetProp_CallNative*
-ICGetProp_CallNative::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                            ICGetProp_CallNative& other)
-{
-    return New<ICGetProp_CallNative>(cx, space, other.jitCode(), firstMonitorStub,
-                                     other.receiverGuard(), other.holder_,
-                                     other.holderShape_, other.getter_, other.pcOffset_);
-}
-
-/* static */ ICGetProp_CallNativeGlobal*
-ICGetProp_CallNativeGlobal::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                            ICGetProp_CallNativeGlobal& other)
-{
-    return New<ICGetProp_CallNativeGlobal>(cx, space, other.jitCode(), firstMonitorStub,
-                                           other.receiverGuard(), other.holder_,
-                                           other.holderShape_, other.globalShape_,
-                                           other.getter_, other.pcOffset_);
-}
-
 ICSetProp_Native::ICSetProp_Native(JitCode* stubCode, ObjectGroup* group, Shape* shape,
                                    uint32_t offset)
   : ICUpdatedStub(SetProp_Native, stubCode),
     group_(group),
     shape_(shape),
     offset_(offset)
 { }
 
@@ -11503,96 +8808,16 @@ ICCall_ScriptedApplyArguments::Clone(JSC
 /* static */ ICCall_ScriptedFunCall*
 ICCall_ScriptedFunCall::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
                               ICCall_ScriptedFunCall& other)
 {
     return New<ICCall_ScriptedFunCall>(cx, space, other.jitCode(), firstMonitorStub,
                                        other.pcOffset_);
 }
 
-ICGetPropCallDOMProxyNativeStub::ICGetPropCallDOMProxyNativeStub(Kind kind, JitCode* stubCode,
-                                                                 ICStub* firstMonitorStub,
-                                                                 Shape* shape,
-                                                                 Shape* expandoShape,
-                                                                 JSObject* holder,
-                                                                 Shape* holderShape,
-                                                                 JSFunction* getter,
-                                                                 uint32_t pcOffset)
-  : ICGetPropCallGetter(kind, stubCode, firstMonitorStub, ReceiverGuard(nullptr, shape),
-                        holder, holderShape, getter, pcOffset),
-    expandoShape_(expandoShape)
-{ }
-
-ICGetPropCallDOMProxyNativeCompiler::ICGetPropCallDOMProxyNativeCompiler(JSContext* cx,
-                                                                         ICStub::Kind kind,
-                                                                         ICStub* firstMonitorStub,
-                                                                         Handle<ProxyObject*> proxy,
-                                                                         HandleObject holder,
-                                                                         HandleFunction getter,
-                                                                         uint32_t pcOffset)
-  : ICStubCompiler(cx, kind, Engine::Baseline),
-    firstMonitorStub_(firstMonitorStub),
-    proxy_(cx, proxy),
-    holder_(cx, holder),
-    getter_(cx, getter),
-    pcOffset_(pcOffset)
-{
-    MOZ_ASSERT(kind == ICStub::GetProp_CallDOMProxyNative ||
-               kind == ICStub::GetProp_CallDOMProxyWithGenerationNative);
-    MOZ_ASSERT(proxy_->handler()->family() == GetDOMProxyHandlerFamily());
-}
-
-/* static */ ICGetProp_CallDOMProxyNative*
-ICGetProp_CallDOMProxyNative::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                                    ICGetProp_CallDOMProxyNative& other)
-{
-    return New<ICGetProp_CallDOMProxyNative>(cx, space, other.jitCode(), firstMonitorStub,
-                                             other.receiverGuard_.shape(), other.expandoShape_,
-                                             other.holder_, other.holderShape_, other.getter_,
-                                             other.pcOffset_);
-}
-
-/* static */ ICGetProp_CallDOMProxyWithGenerationNative*
-ICGetProp_CallDOMProxyWithGenerationNative::Clone(JSContext* cx,
-                                                  ICStubSpace* space,
-                                                  ICStub* firstMonitorStub,
-                                                  ICGetProp_CallDOMProxyWithGenerationNative& other)
-{
-    return New<ICGetProp_CallDOMProxyWithGenerationNative>(cx, space, other.jitCode(),
-                                                           firstMonitorStub,
-                                                           other.receiverGuard_.shape(),
-                                                           other.expandoAndGeneration_,
-                                                           other.generation_,
-                                                           other.expandoShape_, other.holder_,
-                                                           other.holderShape_, other.getter_,
-                                                           other.pcOffset_);
-}
-
-ICGetProp_DOMProxyShadowed::ICGetProp_DOMProxyShadowed(JitCode* stubCode,
-                                                       ICStub* firstMonitorStub,
-                                                       Shape* shape,
-                                                       const BaseProxyHandler* proxyHandler,
-                                                       PropertyName* name,
-                                                       uint32_t pcOffset)
-  : ICMonitoredStub(ICStub::GetProp_DOMProxyShadowed, stubCode, firstMonitorStub),
-    shape_(shape),
-    proxyHandler_(proxyHandler),
-    name_(name),
-    pcOffset_(pcOffset)
-{ }
-
-/* static */ ICGetProp_DOMProxyShadowed*
-ICGetProp_DOMProxyShadowed::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                                  ICGetProp_DOMProxyShadowed& other)
-{
-    return New<ICGetProp_DOMProxyShadowed>(cx, space, other.jitCode(), firstMonitorStub,
-                                           other.shape_, other.proxyHandler_, other.name_,
-                                           other.pcOffset_);
-}
-
 //
 // Rest_Fallback
 //
 
 static bool DoRestFallback(JSContext* cx, BaselineFrame* frame, ICRest_Fallback* stub,
                            MutableHandleValue res)
 {
     unsigned numFormals = frame->numFormalArgs() - 1;
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -16,18 +16,16 @@
 
 #include "builtin/TypedObject.h"
 #include "jit/BaselineICList.h"
 #include "jit/BaselineJIT.h"
 #include "jit/SharedIC.h"
 #include "jit/SharedICRegisters.h"
 #include "js/TraceableVector.h"
 #include "vm/ArrayObject.h"
-#include "vm/ReceiverGuard.h"
-#include "vm/TypedArrayCommon.h"
 #include "vm/UnboxedObject.h"
 
 namespace js {
 namespace jit {
 
 // WarmUpCounter_Fallback
 
 // A WarmUpCounter IC chain has only the fallback stub.
@@ -52,366 +50,16 @@ class ICWarmUpCounter_Fallback : public 
 
         ICWarmUpCounter_Fallback* getStub(ICStubSpace* space) {
             return newStub<ICWarmUpCounter_Fallback>(space, getStubCode());
         }
     };
 };
 
 
-// TypeCheckPrimitiveSetStub
-//   Base class for IC stubs (TypeUpdate or TypeMonitor) that check that a given
-//   value's type falls within a set of primitive types.
-
-class TypeCheckPrimitiveSetStub : public ICStub
-{
-    friend class ICStubSpace;
-  protected:
-    inline static uint16_t TypeToFlag(JSValueType type) {
-        return 1u << static_cast<unsigned>(type);
-    }
-
-    inline static uint16_t ValidFlags() {
-        return ((TypeToFlag(JSVAL_TYPE_OBJECT) << 1) - 1) & ~TypeToFlag(JSVAL_TYPE_MAGIC);
-    }
-
-    TypeCheckPrimitiveSetStub(Kind kind, JitCode* stubCode, uint16_t flags)
-        : ICStub(kind, stubCode)
-    {
-        MOZ_ASSERT(kind == TypeMonitor_PrimitiveSet || kind == TypeUpdate_PrimitiveSet);
-        MOZ_ASSERT(flags && !(flags & ~ValidFlags()));
-        extra_ = flags;
-    }
-
-    TypeCheckPrimitiveSetStub* updateTypesAndCode(uint16_t flags, JitCode* code) {
-        MOZ_ASSERT(flags && !(flags & ~ValidFlags()));
-        if (!code)
-            return nullptr;
-        extra_ = flags;
-        updateCode(code);
-        return this;
-    }
-
-  public:
-    uint16_t typeFlags() const {
-        return extra_;
-    }
-
-    bool containsType(JSValueType type) const {
-        MOZ_ASSERT(type <= JSVAL_TYPE_OBJECT);
-        MOZ_ASSERT(type != JSVAL_TYPE_MAGIC);
-        return extra_ & TypeToFlag(type);
-    }
-
-    ICTypeMonitor_PrimitiveSet* toMonitorStub() {
-        return toTypeMonitor_PrimitiveSet();
-    }
-
-    ICTypeUpdate_PrimitiveSet* toUpdateStub() {
-        return toTypeUpdate_PrimitiveSet();
-    }
-
-    class Compiler : public ICStubCompiler {
-      protected:
-        TypeCheckPrimitiveSetStub* existingStub_;
-        uint16_t flags_;
-
-        virtual int32_t getKey() const {
-            return static_cast<int32_t>(engine_) |
-                  (static_cast<int32_t>(kind) << 1) |
-                  (static_cast<int32_t>(flags_) << 17);
-        }
-
-      public:
-        Compiler(JSContext* cx, Kind kind, TypeCheckPrimitiveSetStub* existingStub,
-                 JSValueType type)
-          : ICStubCompiler(cx, kind, Engine::Baseline),
-            existingStub_(existingStub),
-            flags_((existingStub ? existingStub->typeFlags() : 0) | TypeToFlag(type))
-        {
-            MOZ_ASSERT_IF(existingStub_, flags_ != existingStub_->typeFlags());
-        }
-
-        TypeCheckPrimitiveSetStub* updateStub() {
-            MOZ_ASSERT(existingStub_);
-            return existingStub_->updateTypesAndCode(flags_, getStubCode());
-        }
-    };
-};
-
-// TypeMonitor
-
-// The TypeMonitor fallback stub is not always a regular fallback stub. When
-// used for monitoring the values pushed by a bytecode it doesn't hold a
-// pointer to the IC entry, but rather back to the main fallback stub for the
-// IC (from which a pointer to the IC entry can be retrieved). When monitoring
-// the types of 'this', arguments or other values with no associated IC, there
-// is no main fallback stub, and the IC entry is referenced directly.
-class ICTypeMonitor_Fallback : public ICStub
-{
-    friend class ICStubSpace;
-
-    static const uint32_t MAX_OPTIMIZED_STUBS = 8;
-
-    // Pointer to the main fallback stub for the IC or to the main IC entry,
-    // depending on hasFallbackStub.
-    union {
-        ICMonitoredFallbackStub* mainFallbackStub_;
-        ICEntry* icEntry_;
-    };
-
-    // Pointer to the first monitor stub.
-    ICStub* firstMonitorStub_;
-
-    // Address of the last monitor stub's field pointing to this
-    // fallback monitor stub.  This will get updated when new
-    // monitor stubs are created and added.
-    ICStub** lastMonitorStubPtrAddr_;
-
-    // Count of optimized type monitor stubs in this chain.
-    uint32_t numOptimizedMonitorStubs_ : 8;
-
-    // Whether this has a fallback stub referring to the IC entry.
-    bool hasFallbackStub_ : 1;
-
-    // Index of 'this' or argument which is being monitored, or BYTECODE_INDEX
-    // if this is monitoring the types of values pushed at some bytecode.
-    uint32_t argumentIndex_ : 23;
-
-    static const uint32_t BYTECODE_INDEX = (1 << 23) - 1;
-
-    ICTypeMonitor_Fallback(JitCode* stubCode, ICMonitoredFallbackStub* mainFallbackStub,
-                           uint32_t argumentIndex)
-      : ICStub(ICStub::TypeMonitor_Fallback, stubCode),
-        mainFallbackStub_(mainFallbackStub),
-        firstMonitorStub_(thisFromCtor()),
-        lastMonitorStubPtrAddr_(nullptr),
-        numOptimizedMonitorStubs_(0),
-        hasFallbackStub_(mainFallbackStub != nullptr),
-        argumentIndex_(argumentIndex)
-    { }
-
-    ICTypeMonitor_Fallback* thisFromCtor() {
-        return this;
-    }
-
-    void addOptimizedMonitorStub(ICStub* stub) {
-        stub->setNext(this);
-
-        MOZ_ASSERT((lastMonitorStubPtrAddr_ != nullptr) ==
-                   (numOptimizedMonitorStubs_ || !hasFallbackStub_));
-
-        if (lastMonitorStubPtrAddr_)
-            *lastMonitorStubPtrAddr_ = stub;
-
-        if (numOptimizedMonitorStubs_ == 0) {
-            MOZ_ASSERT(firstMonitorStub_ == this);
-            firstMonitorStub_ = stub;
-        } else {
-            MOZ_ASSERT(firstMonitorStub_ != nullptr);
-        }
-
-        lastMonitorStubPtrAddr_ = stub->addressOfNext();
-        numOptimizedMonitorStubs_++;
-    }
-
-  public:
-    bool hasStub(ICStub::Kind kind) {
-        ICStub* stub = firstMonitorStub_;
-        do {
-            if (stub->kind() == kind)
-                return true;
-
-            stub = stub->next();
-        } while (stub);
-
-        return false;
-    }
-
-    inline ICFallbackStub* mainFallbackStub() const {
-        MOZ_ASSERT(hasFallbackStub_);
-        return mainFallbackStub_;
-    }
-
-    inline ICEntry* icEntry() const {
-        return hasFallbackStub_ ? mainFallbackStub()->icEntry() : icEntry_;
-    }
-
-    inline ICStub* firstMonitorStub() const {
-        return firstMonitorStub_;
-    }
-
-    static inline size_t offsetOfFirstMonitorStub() {
-        return offsetof(ICTypeMonitor_Fallback, firstMonitorStub_);
-    }
-
-    inline uint32_t numOptimizedMonitorStubs() const {
-        return numOptimizedMonitorStubs_;
-    }
-
-    inline bool monitorsThis() const {
-        return argumentIndex_ == 0;
-    }
-
-    inline bool monitorsArgument(uint32_t* pargument) const {
-        if (argumentIndex_ > 0 && argumentIndex_ < BYTECODE_INDEX) {
-            *pargument = argumentIndex_ - 1;
-            return true;
-        }
-        return false;
-    }
-
-    inline bool monitorsBytecode() const {
-        return argumentIndex_ == BYTECODE_INDEX;
-    }
-
-    // Fixup the IC entry as for a normal fallback stub, for this/arguments.
-    void fixupICEntry(ICEntry* icEntry) {
-        MOZ_ASSERT(!hasFallbackStub_);
-        MOZ_ASSERT(icEntry_ == nullptr);
-        MOZ_ASSERT(lastMonitorStubPtrAddr_ == nullptr);
-        icEntry_ = icEntry;
-        lastMonitorStubPtrAddr_ = icEntry_->addressOfFirstStub();
-    }
-
-    // Create a new monitor stub for the type of the given value, and
-    // add it to this chain.
-    bool addMonitorStubForValue(JSContext* cx, JSScript* script, HandleValue val);
-
-    void resetMonitorStubChain(Zone* zone);
-
-    // Compiler for this stub kind.
-    class Compiler : public ICStubCompiler {
-        ICMonitoredFallbackStub* mainFallbackStub_;
-        uint32_t argumentIndex_;
-
-      protected:
-        bool generateStubCode(MacroAssembler& masm);
-
-      public:
-        Compiler(JSContext* cx, ICMonitoredFallbackStub* mainFallbackStub)
-            : ICStubCompiler(cx, ICStub::TypeMonitor_Fallback, Engine::Baseline),
-            mainFallbackStub_(mainFallbackStub),
-            argumentIndex_(BYTECODE_INDEX)
-        { }
-
-        Compiler(JSContext* cx, uint32_t argumentIndex)
-          : ICStubCompiler(cx, ICStub::TypeMonitor_Fallback, Engine::Baseline),
-            mainFallbackStub_(nullptr),
-            argumentIndex_(argumentIndex)
-        { }
-
-        ICTypeMonitor_Fallback* getStub(ICStubSpace* space) {
-            return newStub<ICTypeMonitor_Fallback>(space, getStubCode(), mainFallbackStub_,
-                                                       argumentIndex_);
-        }
-    };
-};
-
-class ICTypeMonitor_PrimitiveSet : public TypeCheckPrimitiveSetStub
-{
-    friend class ICStubSpace;
-
-    ICTypeMonitor_PrimitiveSet(JitCode* stubCode, uint16_t flags)
-        : TypeCheckPrimitiveSetStub(TypeMonitor_PrimitiveSet, stubCode, flags)
-    {}
-
-  public:
-    class Compiler : public TypeCheckPrimitiveSetStub::Compiler {
-      protected:
-        bool generateStubCode(MacroAssembler& masm);
-
-      public:
-        Compiler(JSContext* cx, ICTypeMonitor_PrimitiveSet* existingStub, JSValueType type)
-          : TypeCheckPrimitiveSetStub::Compiler(cx, TypeMonitor_PrimitiveSet, existingStub, type)
-        {}
-
-        ICTypeMonitor_PrimitiveSet* updateStub() {
-            TypeCheckPrimitiveSetStub* stub =
-                this->TypeCheckPrimitiveSetStub::Compiler::updateStub();
-            if (!stub)
-                return nullptr;
-            return stub->toMonitorStub();
-        }
-
-        ICTypeMonitor_PrimitiveSet* getStub(ICStubSpace* space) {
-            MOZ_ASSERT(!existingStub_);
-            return newStub<ICTypeMonitor_PrimitiveSet>(space, getStubCode(), flags_);
-        }
-    };
-};
-
-class ICTypeMonitor_SingleObject : public ICStub
-{
-    friend class ICStubSpace;
-
-    HeapPtrObject obj_;
-
-    ICTypeMonitor_SingleObject(JitCode* stubCode, JSObject* obj);
-
-  public:
-    HeapPtrObject& object() {
-        return obj_;
-    }
-
-    static size_t offsetOfObject() {
-        return offsetof(ICTypeMonitor_SingleObject, obj_);
-    }
-
-    class Compiler : public ICStubCompiler {
-      protected:
-        HandleObject obj_;
-        bool generateStubCode(MacroAssembler& masm);
-
-      public:
-        Compiler(JSContext* cx, HandleObject obj)
-          : ICStubCompiler(cx, TypeMonitor_SingleObject, Engine::Baseline),
-            obj_(obj)
-        { }
-
-        ICTypeMonitor_SingleObject* getStub(ICStubSpace* space) {
-            return newStub<ICTypeMonitor_SingleObject>(space, getStubCode(), obj_);
-        }
-    };
-};
-
-class ICTypeMonitor_ObjectGroup : public ICStub
-{
-    friend class ICStubSpace;
-
-    HeapPtrObjectGroup group_;
-
-    ICTypeMonitor_ObjectGroup(JitCode* stubCode, ObjectGroup* group);
-
-  public:
-    HeapPtrObjectGroup& group() {
-        return group_;
-    }
-
-    static size_t offsetOfGroup() {
-        return offsetof(ICTypeMonitor_ObjectGroup, group_);
-    }
-
-    class Compiler : public ICStubCompiler {
-      protected:
-        HandleObjectGroup group_;
-        bool generateStubCode(MacroAssembler& masm);
-
-      public:
-        Compiler(JSContext* cx, HandleObjectGroup group)
-          : ICStubCompiler(cx, TypeMonitor_ObjectGroup, Engine::Baseline),
-            group_(group)
-        { }
-
-        ICTypeMonitor_ObjectGroup* getStub(ICStubSpace* space) {
-            return newStub<ICTypeMonitor_ObjectGroup>(space, getStubCode(), group_);
-        }
-    };
-};
-
 // TypeUpdate
 
 extern const VMFunction DoTypeUpdateFallbackInfo;
 
 // The TypeUpdate fallback is not a regular fallback, since it just
 // forwards to a different entry point in the main fallback stub.
 class ICTypeUpdate_Fallback : public ICStub
 {
@@ -448,17 +96,18 @@ class ICTypeUpdate_PrimitiveSet : public
 
   public:
     class Compiler : public TypeCheckPrimitiveSetStub::Compiler {
       protected:
         bool generateStubCode(MacroAssembler& masm);
 
       public:
         Compiler(JSContext* cx, ICTypeUpdate_PrimitiveSet* existingStub, JSValueType type)
-          : TypeCheckPrimitiveSetStub::Compiler(cx, TypeUpdate_PrimitiveSet, existingStub, type)
+          : TypeCheckPrimitiveSetStub::Compiler(cx, TypeUpdate_PrimitiveSet,
+                                                Engine::Baseline, existingStub, type)
         {}
 
         ICTypeUpdate_PrimitiveSet* updateStub() {
             TypeCheckPrimitiveSetStub* stub =
                 this->TypeCheckPrimitiveSetStub::Compiler::updateStub();
             if (!stub)
                 return nullptr;
             return stub->toUpdateStub();
@@ -875,17 +524,17 @@ class ICGetElem_Fallback : public ICMoni
         explicit Compiler(JSContext* cx)
           : ICStubCompiler(cx, ICStub::GetElem_Fallback, Engine::Baseline)
         { }
 
         ICStub* getStub(ICStubSpace* space) {
             ICGetElem_Fallback* stub = newStub<ICGetElem_Fallback>(space, getStubCode());
             if (!stub)
                 return nullptr;
-            if (!stub->initMonitoringChain(cx, space))
+            if (!stub->initMonitoringChain(cx, space, engine_))
                 return nullptr;
             return stub;
         }
     };
 };
 
 class ICGetElemNativeStub : public ICMonitoredStub
 {
@@ -1401,35 +1050,16 @@ class ICGetElem_UnboxedArray : public IC
         {}
 
         ICStub* getStub(ICStubSpace* space) {
             return newStub<ICGetElem_UnboxedArray>(space, getStubCode(), firstMonitorStub_, group_);
         }
     };
 };
 
-// Enum for stubs handling a combination of typed arrays and typed objects.
-enum TypedThingLayout {
-    Layout_TypedArray,
-    Layout_OutlineTypedObject,
-    Layout_InlineTypedObject
-};
-
-static inline TypedThingLayout
-GetTypedThingLayout(const Class* clasp)
-{
-    if (IsAnyTypedArrayClass(clasp))
-        return Layout_TypedArray;
-    if (IsOutlineTypedObjectClass(clasp))
-        return Layout_OutlineTypedObject;
-    if (IsInlineTypedObjectClass(clasp))
-        return Layout_InlineTypedObject;
-    MOZ_CRASH("Bad object class");
-}
-
 // Accesses scalar elements of a typed array or typed object.
 class ICGetElem_TypedArray : public ICStub
 {
     friend class ICStubSpace;
 
   protected: // Protected to silence Clang warning.
     HeapPtrShape shape_;
 
@@ -2064,17 +1694,17 @@ class ICGetName_Fallback : public ICMoni
 
       public:
         explicit Compiler(JSContext* cx)
           : ICStubCompiler(cx, ICStub::GetName_Fallback, Engine::Baseline)
         { }
 
         ICStub* getStub(ICStubSpace* space) {
             ICGetName_Fallback* stub = newStub<ICGetName_Fallback>(space, getStubCode());
-            if (!stub || !stub->initMonitoringChain(cx, space))
+            if (!stub || !stub->initMonitoringChain(cx, space, engine_))
                 return nullptr;
             return stub;
         }
     };
 };
 
 // Optimized lexical GETGNAME stub.
 class ICGetName_GlobalLexical : public ICMonitoredStub
@@ -2224,17 +1854,17 @@ class ICGetIntrinsic_Fallback : public I
       public:
         explicit Compiler(JSContext* cx)
           : ICStubCompiler(cx, ICStub::GetIntrinsic_Fallback, Engine::Baseline)
         { }
 
         ICStub* getStub(ICStubSpace* space) {
             ICGetIntrinsic_Fallback* stub =
                 newStub<ICGetIntrinsic_Fallback>(space, getStubCode());
-            if (!stub || !stub->initMonitoringChain(cx, space))
+            if (!stub || !stub->initMonitoringChain(cx, space, engine_))
                 return nullptr;
             return stub;
         }
     };
 };
 
 // Stub that loads the constant result of a GETINTRINSIC operation.
 class ICGetIntrinsic_Constant : public ICStub
@@ -2266,1072 +1896,16 @@ class ICGetIntrinsic_Constant : public I
         {}
 
         ICStub* getStub(ICStubSpace* space) {
             return newStub<ICGetIntrinsic_Constant>(space, getStubCode(), value_);
         }
     };
 };
 
-class ICGetProp_Fallback : public ICMonitoredFallbackStub
-{
-    friend class ICStubSpace;
-
-    explicit ICGetProp_Fallback(JitCode* stubCode)
-      : ICMonitoredFallbackStub(ICStub::GetProp_Fallback, stubCode)
-    { }
-
-  public:
-    static const uint32_t MAX_OPTIMIZED_STUBS = 16;
-    static const size_t UNOPTIMIZABLE_ACCESS_BIT = 0;
-    static const size_t ACCESSED_GETTER_BIT = 1;
-
-    void noteUnoptimizableAccess() {
-        extra_ |= (1u << UNOPTIMIZABLE_ACCESS_BIT);
-    }
-    bool hadUnoptimizableAccess() const {
-        return extra_ & (1u << UNOPTIMIZABLE_ACCESS_BIT);
-    }
-
-    void noteAccessedGetter() {
-        extra_ |= (1u << ACCESSED_GETTER_BIT);
-    }
-    bool hasAccessedGetter() const {
-        return extra_ & (1u << ACCESSED_GETTER_BIT);
-    }
-
-    class Compiler : public ICStubCompiler {
-      public:
-        static const int32_t BASELINE_KEY =
-            (static_cast<int32_t>(Engine::Baseline)) |
-            (static_cast<int32_t>(ICStub::GetProp_Fallback) << 1);
-
-      protected:
-        uint32_t returnOffset_;
-        bool generateStubCode(MacroAssembler& masm);
-        void postGenerateStubCode(MacroAssembler& masm, Handle<JitCode*> code);
-
-      public:
-        explicit Compiler(JSContext* cx)
-          : ICStubCompiler(cx, ICStub::GetProp_Fallback, Engine::Baseline)
-        { }
-
-        ICStub* getStub(ICStubSpace* space) {
-            ICGetProp_Fallback* stub = newStub<ICGetProp_Fallback>(space, getStubCode());
-            if (!stub || !stub->initMonitoringChain(cx, space))
-                return nullptr;
-            return stub;
-        }
-    };
-};
-
-// Stub for sites, which are too polymorphic (i.e. MAX_OPTIMIZED_STUBS was reached)
-class ICGetProp_Generic : public ICMonitoredStub
-{
-    friend class ICStubSpace;
-
-  protected:
-    explicit ICGetProp_Generic(JitCode* stubCode, ICStub* firstMonitorStub)
-      : ICMonitoredStub(ICStub::GetProp_Generic, stubCode, firstMonitorStub) {}
-
-  public:
-    static ICGetProp_Generic* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                                    ICGetProp_Generic& other);
-
-    class Compiler : public ICStubCompiler {
-      protected:
-        bool generateStubCode(MacroAssembler& masm);
-        ICStub* firstMonitorStub_;
-      public:
-        explicit Compiler(JSContext* cx, ICStub* firstMonitorStub)
-          : ICStubCompiler(cx, ICStub::GetProp_Generic, Engine::Baseline),
-            firstMonitorStub_(firstMonitorStub)
-        {}
-
-        ICStub* getStub(ICStubSpace* space) {
-            return newStub<ICGetProp_Generic>(space, getStubCode(), firstMonitorStub_);
-        }
-    };
-};
-
-// Stub for accessing a dense array's length.
-class ICGetProp_ArrayLength : public ICStub
-{
-    friend class ICStubSpace;
-
-    explicit ICGetProp_ArrayLength(JitCode* stubCode)
-      : ICStub(GetProp_ArrayLength, stubCode)
-    {}
-
-  public:
-    class Compiler : public ICStubCompiler {
-        bool generateStubCode(MacroAssembler& masm);
-
-      public:
-        explicit Compiler(JSContext* cx)
-          : ICStubCompiler(cx, ICStub::GetProp_ArrayLength, Engine::Baseline)
-        {}
-
-        ICStub* getStub(ICStubSpace* space) {
-            return newStub<ICGetProp_ArrayLength>(space, getStubCode());
-        }
-    };
-};
-
-// Stub for accessing an unboxed array's length.
-class ICGetProp_UnboxedArrayLength : public ICStub
-{
-    friend class ICStubSpace;
-
-    explicit ICGetProp_UnboxedArrayLength(JitCode* stubCode)
-      : ICStub(GetProp_UnboxedArrayLength, stubCode)
-    {}
-
-  public:
-    class Compiler : public ICStubCompiler {
-        bool generateStubCode(MacroAssembler& masm);
-
-      public:
-        explicit Compiler(JSContext* cx)
-          : ICStubCompiler(cx, ICStub::GetProp_UnboxedArrayLength, Engine::Baseline)
-        {}
-
-        ICStub* getStub(ICStubSpace* space) {
-            return newStub<ICGetProp_UnboxedArrayLength>(space, getStubCode());
-        }
-    };
-};
-
-// Stub for accessing a property on a primitive's prototype.
-class ICGetProp_Primitive : public ICMonitoredStub
-{
-    friend class ICStubSpace;
-
-  protected: // Protected to silence Clang warning.
-    // Shape of String.prototype/Number.prototype to check for.
-    HeapPtrShape protoShape_;
-
-    // Fixed or dynamic slot offset.
-    uint32_t offset_;
-
-    ICGetProp_Primitive(JitCode* stubCode, ICStub* firstMonitorStub, JSValueType primitiveType,
-                        Shape* protoShape, uint32_t offset);
-
-  public:
-    HeapPtrShape& protoShape() {
-        return protoShape_;
-    }
-    JSValueType primitiveType() const {
-        return JSValueType(extra_);
-    }
-
-    static size_t offsetOfProtoShape() {
-        return offsetof(ICGetProp_Primitive, protoShape_);
-    }
-
-    static size_t offsetOfOffset() {
-        return offsetof(ICGetProp_Primitive, offset_);
-    }
-
-    class Compiler : public ICStubCompiler {
-        ICStub* firstMonitorStub_;
-        JSValueType primitiveType_;
-        RootedObject prototype_;
-        bool isFixedSlot_;
-        uint32_t offset_;
-
-        bool generateStubCode(MacroAssembler& masm);
-
-      protected:
-        virtual int32_t getKey() const {
-            static_assert(sizeof(JSValueType) == 1, "JSValueType should fit in one byte");
-            return static_cast<int32_t>(engine_) |
-                  (static_cast<int32_t>(kind) << 1) |
-                  (static_cast<int32_t>(isFixedSlot_) << 17) |
-                  (static_cast<int32_t>(primitiveType_) << 25);
-        }
-
-      public:
-        Compiler(JSContext* cx, ICStub* firstMonitorStub, JSValueType primitiveType,
-                 HandleObject prototype, bool isFixedSlot, uint32_t offset)
-          : ICStubCompiler(cx, ICStub::GetProp_Primitive, Engine::Baseline),
-            firstMonitorStub_(firstMonitorStub),
-            primitiveType_(primitiveType),
-            prototype_(cx, prototype),
-            isFixedSlot_(isFixedSlot),
-            offset_(offset)
-        {}
-
-        ICStub* getStub(ICStubSpace* space) {
-            RootedShape protoShape(cx, prototype_->as<NativeObject>().lastProperty());
-            return newStub<ICGetProp_Primitive>(space, getStubCode(), firstMonitorStub_,
-                                                primitiveType_, protoShape, offset_);
-        }
-    };
-};
-
-// Stub for accessing a string's length.
-class ICGetProp_StringLength : public ICStub
-{
-    friend class ICStubSpace;
-
-    explicit ICGetProp_StringLength(JitCode* stubCode)
-      : ICStub(GetProp_StringLength, stubCode)
-    {}
-
-  public:
-    class Compiler : public ICStubCompiler {
-        bool generateStubCode(MacroAssembler& masm);
-
-      public:
-        explicit Compiler(JSContext* cx)
-          : ICStubCompiler(cx, ICStub::GetProp_StringLength, Engine::Baseline)
-        {}
-
-        ICStub* getStub(ICStubSpace* space) {
-            return newStub<ICGetProp_StringLength>(space, getStubCode());
-        }
-    };
-};
-
-// Base class for native GetProp stubs.
-class ICGetPropNativeStub : public ICMonitoredStub
-{
-    // Object shape/group.
-    HeapReceiverGuard receiverGuard_;
-
-    // Fixed or dynamic slot offset.
-    uint32_t offset_;
-
-  protected:
-    ICGetPropNativeStub(ICStub::Kind kind, JitCode* stubCode, ICStub* firstMonitorStub,
-                        ReceiverGuard guard, uint32_t offset);
-
-  public:
-    HeapReceiverGuard& receiverGuard() {
-        return receiverGuard_;
-    }
-    uint32_t offset() const {
-        return offset_;
-    }
-
-    void notePreliminaryObject() {
-        extra_ = 1;
-    }
-    bool hasPreliminaryObject() const {
-        return extra_;
-    }
-
-    static size_t offsetOfReceiverGuard() {
-        return offsetof(ICGetPropNativeStub, receiverGuard_);
-    }
-    static size_t offsetOfOffset() {
-        return offsetof(ICGetPropNativeStub, offset_);
-    }
-};
-
-// Stub for accessing an own property on a native object.
-class ICGetProp_Native : public ICGetPropNativeStub
-{
-    friend class ICStubSpace;
-
-    ICGetProp_Native(JitCode* stubCode, ICStub* firstMonitorStub, ReceiverGuard guard,
-                     uint32_t offset)
-      : ICGetPropNativeStub(GetProp_Native, stubCode, firstMonitorStub, guard, offset)
-    {}
-
-  public:
-    static ICGetProp_Native* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                                   ICGetProp_Native& other);
-};
-
-class ICGetPropNativePrototypeStub : public ICGetPropNativeStub
-{
-    // Holder and its shape.
-    HeapPtrObject holder_;
-    HeapPtrShape holderShape_;