Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Mon, 20 Mar 2017 18:19:52 -0700
changeset 348535 5fe5dcf1c10a4523ba3f0a20295551462c2dae11
parent 348457 f897fc6fb17f13251963aa4236e541064e9881d3 (current diff)
parent 348534 c9812b88b9ed45fa80e0c45a8e80cf89c66d4c8f (diff)
child 348536 ca4ae502156eaea6fffb296bb9c3b3930af8ab58
child 348553 667bc38a3624e8f11193a5cf382d0bb57d834e96
child 348573 942dd08a9878bf57a32b63c33ba57284211c8c37
push id31526
push userkwierso@gmail.com
push dateTue, 21 Mar 2017 01:20:02 +0000
treeherdermozilla-central@5fe5dcf1c10a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.0a1
first release with
nightly mac
5fe5dcf1c10a / 55.0a1 / 20170321030211 / files
nightly win32
5fe5dcf1c10a / 55.0a1 / 20170321030211 / files
nightly win64
5fe5dcf1c10a / 55.0a1 / 20170321030211 / files
nightly linux32
nightly linux64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly mac
nightly win32
nightly win64
Merge inbound to central, a=merge MozReview-Commit-ID: 2omKclRg40c
browser/base/content/browser.js
dom/base/nsDocument.cpp
--- a/accessible/base/nsAccCache.h
+++ b/accessible/base/nsAccCache.h
@@ -20,26 +20,9 @@ UnbindCacheEntriesFromDocument(
   for (auto iter = aCache.Iter(); !iter.Done(); iter.Next()) {
     T* accessible = iter.Data();
     MOZ_ASSERT(accessible && !accessible->IsDefunct());
     accessible->Document()->UnbindFromDocument(accessible);
     iter.Remove();
   }
 }
 
-/**
- * Clear the cache and shutdown the accessibles.
- */
-template <class T>
-static void
-ClearCache(nsRefPtrHashtable<nsPtrHashKey<const void>, T>& aCache)
-{
-  for (auto iter = aCache.Iter(); !iter.Done(); iter.Next()) {
-    T* accessible = iter.Data();
-    MOZ_ASSERT(accessible);
-    if (accessible && !accessible->IsDefunct()) {
-      accessible->Shutdown();
-    }
-    iter.Remove();
-  }
-}
-
 #endif
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -478,17 +478,27 @@ DocAccessible::Shutdown()
     mVirtualCursor = nullptr;
   }
 
   mPresShell->SetDocAccessible(nullptr);
   mPresShell = nullptr;  // Avoid reentrancy
 
   mDependentIDsHash.Clear();
   mNodeToAccessibleMap.Clear();
-  ClearCache(mAccessibleCache);
+
+  for (auto iter = mAccessibleCache.Iter(); !iter.Done(); iter.Next()) {
+    Accessible* accessible = iter.Data();
+    MOZ_ASSERT(accessible);
+    if (accessible && !accessible->IsDefunct()) {
+      // Unlink parent to avoid its cleaning overhead in shutdown.
+      accessible->mParent = nullptr;
+      accessible->Shutdown();
+    }
+    iter.Remove();
+  }
 
   HyperTextAccessibleWrap::Shutdown();
 
   GetAccService()->NotifyOfDocumentShutdown(this, kungFuDeathGripDoc);
 }
 
 nsIFrame*
 DocAccessible::GetFrame() const
--- a/browser/base/content/browser-feeds.js
+++ b/browser/base/content/browser-feeds.js
@@ -1,18 +1,143 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
+                                  "resource://gre/modules/DeferredTask.jsm");
+
+const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
+const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
+const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
+
+const PREF_SHOW_FIRST_RUN_UI = "browser.feeds.showFirstRunUI";
+
+const PREF_SELECTED_APP = "browser.feeds.handlers.application";
+const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
+const PREF_SELECTED_ACTION = "browser.feeds.handler";
+const PREF_SELECTED_READER = "browser.feeds.handler.default";
+
+const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application";
+const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
+const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler";
+const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default";
+
+const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application";
+const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
+const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler";
+const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default";
+
+const PREF_UPDATE_DELAY = 2000;
+
+const SETTABLE_PREFS = new Set([
+  PREF_VIDEO_SELECTED_ACTION,
+  PREF_AUDIO_SELECTED_ACTION,
+  PREF_SELECTED_ACTION,
+  PREF_VIDEO_SELECTED_READER,
+  PREF_AUDIO_SELECTED_READER,
+  PREF_SELECTED_READER,
+  PREF_VIDEO_SELECTED_WEB,
+  PREF_AUDIO_SELECTED_WEB,
+  PREF_SELECTED_WEB
+]);
+
+const EXECUTABLE_PREFS = new Set([
+  PREF_SELECTED_APP,
+  PREF_VIDEO_SELECTED_APP,
+  PREF_AUDIO_SELECTED_APP
+]);
+
+const VALID_ACTIONS = new Set(["ask", "reader", "bookmarks"]);
+const VALID_READERS = new Set(["web", "client", "default", "bookmarks"]);
+
+XPCOMUtils.defineLazyPreferenceGetter(this, "SHOULD_LOG",
+                                      "feeds.log", false);
+
+function LOG(str) {
+  if (SHOULD_LOG)
+    dump("*** Feeds: " + str + "\n");
+}
+
+function getPrefActionForType(t) {
+  switch (t) {
+    case Ci.nsIFeed.TYPE_VIDEO:
+      return PREF_VIDEO_SELECTED_ACTION;
+
+    case Ci.nsIFeed.TYPE_AUDIO:
+      return PREF_AUDIO_SELECTED_ACTION;
+
+    default:
+      return PREF_SELECTED_ACTION;
+  }
+}
+
+function getPrefReaderForType(t) {
+  switch (t) {
+    case Ci.nsIFeed.TYPE_VIDEO:
+      return PREF_VIDEO_SELECTED_READER;
+
+    case Ci.nsIFeed.TYPE_AUDIO:
+      return PREF_AUDIO_SELECTED_READER;
+
+    default:
+      return PREF_SELECTED_READER;
+  }
+}
+
+function getPrefWebForType(t) {
+  switch (t) {
+    case Ci.nsIFeed.TYPE_VIDEO:
+      return PREF_VIDEO_SELECTED_WEB;
+
+    case Ci.nsIFeed.TYPE_AUDIO:
+      return PREF_AUDIO_SELECTED_WEB;
+
+    default:
+      return PREF_SELECTED_WEB;
+  }
+}
+
+function getPrefAppForType(t) {
+  switch (t) {
+    case Ci.nsIFeed.TYPE_VIDEO:
+      return PREF_VIDEO_SELECTED_APP;
+
+    case Ci.nsIFeed.TYPE_AUDIO:
+      return PREF_AUDIO_SELECTED_APP;
+
+    default:
+      return PREF_SELECTED_APP;
+  }
+}
+
+/**
+ * Maps a feed type to a maybe-feed mimetype.
+ */
+function getMimeTypeForFeedType(aFeedType) {
+  switch (aFeedType) {
+    case Ci.nsIFeed.TYPE_VIDEO:
+      return TYPE_MAYBE_VIDEO_FEED;
+
+    case Ci.nsIFeed.TYPE_AUDIO:
+      return TYPE_MAYBE_AUDIO_FEED;
+
+    default:
+      return TYPE_MAYBE_FEED;
+  }
+}
+
 /**
  * The Feed Handler object manages discovery of RSS/ATOM feeds in web pages
  * and shows UI when they are discovered.
  */
 var FeedHandler = {
+  _prefChangeCallback: null,
+
   /** Called when the user clicks on the Subscribe to This Page... menu item,
    * or when the user clicks the feed button when the page contains multiple
    * feeds.
    * Builds a menu of unique feeds associated with the page, and if there
    * is only one, shows the feed inline in the browser window.
    * @param   container
    *          The feed list container (menupopup or subview) to be populated.
    * @param   isSubview
@@ -190,17 +315,18 @@ var FeedHandler = {
           } catch (e) {}
         }
         break;
     }
 
     return file.leafName;
   },
 
-  chooseClientApp(aTitle, aPrefName, aBrowser) {
+  _chooseClientApp(aTitle, aTypeName, aBrowser) {
+    const prefName = getPrefAppForType(aTypeName);
     let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
 
     fp.init(window, aTitle, Ci.nsIFilePicker.modeOpen);
     fp.appendFilters(Ci.nsIFilePicker.filterApps);
 
     fp.open((aResult) => {
       if (aResult == Ci.nsIFilePicker.returnOK) {
         let selectedApp = fp.file;
@@ -217,17 +343,17 @@ var FeedHandler = {
               appName = AppConstants.MOZ_MACBUNDLE_NAME;
               break;
             default:
               appName = AppConstants.MOZ_APP_NAME + "-bin";
               break;
           }
 
           if (fp.file.leafName != appName) {
-            Services.prefs.setComplexValue(aPrefName, Ci.nsILocalFile, selectedApp);
+            Services.prefs.setComplexValue(prefName, Ci.nsILocalFile, selectedApp);
             aBrowser.messageManager.sendAsyncMessage("FeedWriter:SetApplicationLauncherMenuItem",
                                                     { name: this._getFileDisplayName(selectedApp),
                                                       type: "SelectedAppMenuItem" });
           }
         }
       }
     });
 
@@ -272,76 +398,246 @@ var FeedHandler = {
       // nsIProcess instance
       let p = Cc["@mozilla.org/process/util;1"]
                 .createInstance(Ci.nsIProcess);
       p.init(clientApp);
       p.run(false, [aSpec], 1);
     }
   },
 
+  // nsISupports
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+                                         Ci.nsISupportsWeakReference]),
+
+
   init() {
     window.messageManager.addMessageListener("FeedWriter:ChooseClientApp", this);
-    window.messageManager.addMessageListener("FeedWriter:RequestClientAppName", this);
-    window.messageManager.addMessageListener("FeedWriter:SetFeedCharPref", this);
-    window.messageManager.addMessageListener("FeedWriter:SetFeedComplexString", this);
+    window.messageManager.addMessageListener("FeedWriter:GetSubscriptionUI", this);
+    window.messageManager.addMessageListener("FeedWriter:SetFeedPrefsAndSubscribe", this);
     window.messageManager.addMessageListener("FeedWriter:ShownFirstRun", this);
 
     Services.ppmm.addMessageListener("FeedConverter:ExecuteClientApp", this);
+
+    const prefs = Services.prefs;
+    prefs.addObserver(PREF_SELECTED_ACTION, this, true);
+    prefs.addObserver(PREF_SELECTED_READER, this, true);
+    prefs.addObserver(PREF_SELECTED_WEB, this, true);
+    prefs.addObserver(PREF_VIDEO_SELECTED_ACTION, this, true);
+    prefs.addObserver(PREF_VIDEO_SELECTED_READER, this, true);
+    prefs.addObserver(PREF_VIDEO_SELECTED_WEB, this, true);
+    prefs.addObserver(PREF_AUDIO_SELECTED_ACTION, this, true);
+    prefs.addObserver(PREF_AUDIO_SELECTED_READER, this, true);
+    prefs.addObserver(PREF_AUDIO_SELECTED_WEB, this, true);
   },
 
   uninit() {
     Services.ppmm.removeMessageListener("FeedConverter:ExecuteClientApp", this);
+
+    this._prefChangeCallback = null;
+  },
+
+  // nsIObserver
+  observe(subject, topic, data) {
+    if (topic == "nsPref:changed") {
+      LOG(`Pref changed ${data}`)
+      if (this._prefChangeCallback) {
+        this._prefChangeCallback.disarm();
+      }
+      // Multiple prefs are set at the same time, debounce to reduce noise
+      // This can happen in one feed and we want to message all feed pages
+      this._prefChangeCallback = new DeferredTask(() => {
+        this._prefChanged(data);
+      }, PREF_UPDATE_DELAY);
+      this._prefChangeCallback.arm();
+    }
+  },
+
+  _prefChanged(prefName) {
+    // Don't observe for PREF_*SELECTED_APP as user likely just picked one
+    // That is also handled by SetApplicationLauncherMenuItem call
+    // Rather than the others which happen on subscription
+    switch (prefName) {
+      case PREF_SELECTED_READER:
+      case PREF_SELECTED_WEB:
+      case PREF_VIDEO_SELECTED_READER:
+      case PREF_VIDEO_SELECTED_WEB:
+      case PREF_AUDIO_SELECTED_READER:
+      case PREF_AUDIO_SELECTED_WEB:
+      case PREF_SELECTED_ACTION:
+      case PREF_VIDEO_SELECTED_ACTION:
+      case PREF_AUDIO_SELECTED_ACTION:
+        const response = {
+         default: this._getReaderForType(Ci.nsIFeed.TYPE_FEED),
+         [Ci.nsIFeed.TYPE_AUDIO]: this._getReaderForType(Ci.nsIFeed.TYPE_AUDIO),
+         [Ci.nsIFeed.TYPE_VIDEO]: this._getReaderForType(Ci.nsIFeed.TYPE_VIDEO)
+        };
+        Services.mm.broadcastAsyncMessage("FeedWriter:PreferenceUpdated",
+                                          response);
+        break;
+    }
+  },
+
+  _initSubscriptionUIResponse(feedType) {
+    const wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
+               getService(Ci.nsIWebContentConverterService);
+    const handlersRaw = wccr.getContentHandlers(getMimeTypeForFeedType(feedType));
+    const handlers = [];
+    for (let handler of handlersRaw) {
+      LOG(`Handler found: ${handler}`);
+      handlers.push({
+        name: handler.name,
+        uri: handler.uri
+      });
+    }
+    let showFirstRunUI = true;
+    // eslint-disable-next-line mozilla/use-default-preference-values
+    try {
+      showFirstRunUI = Services.prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI);
+    } catch (ex) { }
+    const response = { handlers, showFirstRunUI };
+    let selectedClientApp;
+    const feedTypePref = getPrefAppForType(feedType);
+    try {
+      selectedClientApp = Services.prefs.getComplexValue(feedTypePref, Ci.nsILocalFile);
+    } catch (ex) {
+      // Just do nothing, then we won't bother populating
+    }
+
+    let defaultClientApp = null;
+    try {
+      // This can sometimes not exist
+      defaultClientApp = Cc["@mozilla.org/browser/shell-service;1"]
+                           .getService(Ci.nsIShellService)
+                           .defaultFeedReader;
+    } catch (ex) {
+      // Just do nothing, then we don't bother populating
+    }
+
+    if (selectedClientApp && selectedClientApp.exists()) {
+      if (defaultClientApp && selectedClientApp.path != defaultClientApp.path) {
+        // Only set the default menu item if it differs from the selected one
+        response.defaultMenuItem = this._getFileDisplayName(defaultClientApp);
+      }
+      response.selectedMenuItem = this._getFileDisplayName(selectedClientApp);
+    }
+    response.reader = this._getReaderForType(feedType);
+    return response;
+  },
+
+  _setPref(aPrefName, aPrefValue, aIsComplex = false) {
+    LOG(`FeedWriter._setPref ${aPrefName}`);
+    // Ensure we have a pref that is settable
+    if (aPrefName && SETTABLE_PREFS.has(aPrefName)) {
+      if (aIsComplex) {
+        Services.prefs.setStringPref(aPrefName, aPrefValue);
+      } else {
+        Services.prefs.setCharPref(aPrefName, aPrefValue);
+      }
+    } else {
+      LOG(`FeedWriter._setPref ${aPrefName} not allowed`);
+    }
+  },
+
+  _getReaderForType(feedType) {
+    let prefs = Services.prefs;
+    let handler = "bookmarks";
+    let url;
+    // eslint-disable-next-line mozilla/use-default-preference-values
+    try {
+      handler = prefs.getCharPref(getPrefReaderForType(feedType));
+    } catch (ex) { }
+
+    if (handler === "web") {
+      try {
+        url = prefs.getStringPref(getPrefWebForType(feedType));
+      } catch (ex) {
+        LOG("FeedWriter._setSelectedHandler: invalid or no handler in prefs");
+        url = null;
+      }
+    }
+    const alwaysUse = this._getAlwaysUseState(feedType);
+    const action = prefs.getCharPref(getPrefActionForType(feedType));
+    return { handler, url, alwaysUse, action };
+  },
+
+  _getAlwaysUseState(feedType) {
+    try {
+      return Services.prefs.getCharPref(getPrefActionForType(feedType)) != "ask";
+    } catch (ex) { }
+    return false;
   },
 
   receiveMessage(msg) {
+    let handler;
     switch (msg.name) {
-      case "FeedWriter:ChooseClientApp":
-        this.chooseClientApp(msg.data.title, msg.data.prefName, msg.target);
+      case "FeedWriter:GetSubscriptionUI":
+        const response = this._initSubscriptionUIResponse(msg.data.feedType);
+        msg.target.messageManager
+           .sendAsyncMessage("FeedWriter:GetSubscriptionUIResponse",
+                            response);
         break;
-      case "FeedWriter:RequestClientAppName":
-        let selectedClientApp;
-        try {
-          selectedClientApp = Services.prefs.getComplexValue(msg.data.feedTypePref, Ci.nsILocalFile);
-        } catch (ex) {
-          // Just do nothing, then we won't bother populating
-        }
-
-        let defaultClientApp = null;
-        try {
-          // This can sometimes not exist
-          defaultClientApp = Cc["@mozilla.org/browser/shell-service;1"]
-                               .getService(Ci.nsIShellService)
-                               .defaultFeedReader;
-        } catch (ex) {
-          // Just do nothing, then we don't bother populating
-        }
-
-        if (selectedClientApp && selectedClientApp.exists()) {
-          if (defaultClientApp && selectedClientApp.path != defaultClientApp.path) {
-            // Only set the default menu item if it differs from the selected one
-            msg.target.messageManager
-               .sendAsyncMessage("FeedWriter:SetApplicationLauncherMenuItem",
-                                { name: this._getFileDisplayName(defaultClientApp),
-                                  type: "DefaultAppMenuItem" });
-          }
-          msg.target.messageManager
-             .sendAsyncMessage("FeedWriter:SetApplicationLauncherMenuItem",
-                              { name: this._getFileDisplayName(selectedClientApp),
-                                type: "SelectedAppMenuItem" });
-        }
+      case "FeedWriter:ChooseClientApp":
+        this._chooseClientApp(msg.data.title, msg.data.feedType, msg.target);
         break;
       case "FeedWriter:ShownFirstRun":
-        Services.prefs.setBoolPref("browser.feeds.showFirstRunUI", false);
-        break;
-      case "FeedWriter:SetFeedCharPref":
-        Services.prefs.setCharPref(msg.data.pref, msg.data.value);
+        Services.prefs.setBoolPref(PREF_SHOW_FIRST_RUN_UI, false);
         break;
-      case "FeedWriter:SetFeedComplexString": {
-        Services.prefs.setStringPref(msg.data.pref, msg.data.value);
-        break;
-      }
+      case "FeedWriter:SetFeedPrefsAndSubscribe":
+        const settings = msg.data;
+        if (!settings.action || !VALID_ACTIONS.has(settings.action)) {
+          LOG(`Invalid action ${settings.action}`);
+          return;
+        }
+        if (!settings.reader || !VALID_READERS.has(settings.reader)) {
+          LOG(`Invalid reader ${settings.reader}`);
+          return;
+        }
+        const actionPref = getPrefActionForType(settings.feedType);
+        this._setPref(actionPref, settings.action);
+        const readerPref = getPrefReaderForType(settings.feedType);
+        this._setPref(readerPref, settings.reader);
+        handler = null;
+
+        switch (settings.reader) {
+          case "web":
+            // This is a web set URI by content using window.registerContentHandler()
+            // Lets make sure we know about it before setting it
+            const webPref = getPrefWebForType(settings.feedType);
+            let wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
+                       getService(Ci.nsIWebContentConverterService);
+            // If the user provided an invalid web URL this function won't give us a reference
+            handler = wccr.getWebContentHandlerByURI(getMimeTypeForFeedType(settings.feedType), settings.uri);
+            if (handler) {
+              this._setPref(webPref, settings.uri, true);
+              if (settings.useAsDefault) {
+                wccr.setAutoHandler(getMimeTypeForFeedType(settings.feedType), handler);
+              }
+              msg.target.messageManager
+                 .sendAsyncMessage("FeedWriter:SetFeedPrefsAndSubscribeResponse",
+                                  { redirect: handler.getHandlerURI(settings.feedLocation) });
+            } else {
+              LOG(`No handler found for web ${settings.feedType} ${settings.uri}`);
+            }
+            break;
+          default:
+            const feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
+                                getService(Ci.nsIFeedResultService);
+
+            feedService.addToClientReader(settings.feedLocation,
+                                          settings.feedTitle,
+                                          settings.feedSubtitle,
+                                          settings.feedType,
+                                          settings.reader);
+         }
+         break;
       case "FeedConverter:ExecuteClientApp":
-        this.executeClientApp(msg.data.spec, msg.data.title,
-                              msg.data.subtitle, msg.data.feedHandler);
+        // Always check feedHandler is from a set array of executable prefs
+        if (EXECUTABLE_PREFS.has(msg.data.feedHandler)) {
+          this.executeClientApp(msg.data.spec, msg.data.title,
+                                msg.data.subtitle, msg.data.feedHandler);
+        } else {
+          LOG(`FeedConverter:ExecuteClientApp - Will not exec ${msg.data.feedHandler}`);
+        }
         break;
     }
   },
 };
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2566,18 +2566,23 @@ function URLBarSetURI(aURI) {
       } catch (ex) {
         value = "about:blank";
       }
     }
 
     valid = !isBlankPageURL(uri.spec);
   }
 
+  let isDifferentValidValue = valid && value != gURLBar.value;
   gURLBar.value = value;
   gURLBar.valueIsTyped = !valid;
+  if (isDifferentValidValue) {
+    gURLBar.selectionStart = gURLBar.selectionEnd = 0;
+  }
+
   SetPageProxyState(valid ? "valid" : "invalid");
 }
 
 function losslessDecodeURI(aURI) {
   let scheme = aURI.scheme;
   if (scheme == "moz-action")
     throw new Error("losslessDecodeURI should never get a moz-action URI");
 
--- a/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js
+++ b/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js
@@ -1,10 +1,14 @@
 "use strict";
 
+// Bug 1318389 - This test does a lot of window and tab manipulation,
+//               causing it to take a long time on debug.
+requestLongerTimeout(2);
+
 add_task(setupPrefsAndRecentWindowBehavior);
 
 // Each of the test cases below is run twice: once for login-success and once
 // for login-abort (aSuccess set to true and false respectively).
 let testCasesForBothSuccessAndAbort = [
   /**
    * A portal is detected when there's no browser window, then a browser
    * window is opened, then the portal is freed.
--- a/browser/base/content/test/popupNotifications/browser.ini
+++ b/browser/base/content/test/popupNotifications/browser.ini
@@ -8,17 +8,17 @@ skip-if = (os == "linux" && (debug || as
 skip-if = (os == "linux" && (debug || asan))
 [browser_popupNotification_2.js]
 skip-if = (os == "linux" && (debug || asan))
 [browser_popupNotification_3.js]
 skip-if = (os == "linux" && (debug || asan))
 [browser_popupNotification_4.js]
 skip-if = (os == "linux" && (debug || asan))
 [browser_popupNotification_5.js]
-skip-if = (os == "linux" && (debug || asan))
+skip-if = true # bug 1332646
 [browser_popupNotification_checkbox.js]
 skip-if = (os == "linux" && (debug || asan))
 [browser_popupNotification_keyboard.js]
 skip-if = (os == "linux" && (debug || asan))
 [browser_popupNotification_no_anchors.js]
 skip-if = (os == "linux" && (debug || asan))
 [browser_reshow_in_background.js]
 skip-if = (os == "linux" && (debug || asan))
--- a/browser/base/content/test/urlbar/browser_bug304198.js
+++ b/browser/base/content/test/urlbar/browser_bug304198.js
@@ -43,16 +43,19 @@ add_task(function* () {
 
   function urlbarBackspace() {
     return new Promise((resolve, reject) => {
       gBrowser.selectedBrowser.focus();
       gURLBar.addEventListener("input", function() {
         resolve();
       }, {once: true});
       gURLBar.focus();
+      if (gURLBar.selectionStart == gURLBar.selectionEnd) {
+        gURLBar.selectionStart = gURLBar.selectionEnd = gURLBar.textValue.length;
+      }
       EventUtils.synthesizeKey("VK_BACK_SPACE", {});
     });
   }
 
   function* prepareDeletedURLTab() {
     yield BrowserTestUtils.switchTab(gBrowser, deletedURLTab);
     is(gURLBar.textValue, testURL, "gURLBar.textValue should be testURL after initial switch to deletedURLTab");
 
--- a/browser/base/content/test/urlbar/browser_canonizeURL.js
+++ b/browser/base/content/test/urlbar/browser_canonizeURL.js
@@ -24,16 +24,18 @@ add_task(function*() {
   registerCleanupFunction(() => {
     Preferences.set("browser.urlbar.autoFill", autoFill);
   });
 
   for (let [inputValue, expectedURL, options] of testcases) {
     let promiseLoad = waitForDocLoadAndStopIt(expectedURL);
     gURLBar.focus();
     if (Object.keys(options).length > 0) {
+      gURLBar.selectionStart = gURLBar.selectionEnd =
+        gURLBar.inputField.value.length;
       gURLBar.inputField.value = inputValue.slice(0, -1);
       EventUtils.synthesizeKey(inputValue.slice(-1), {});
     } else {
       gURLBar.textValue = inputValue;
     }
     EventUtils.synthesizeKey("VK_RETURN", options);
     yield promiseLoad;
   }
--- a/browser/components/feeds/FeedConverter.js
+++ b/browser/components/feeds/FeedConverter.js
@@ -218,18 +218,17 @@ FeedConverter.prototype = {
               LOG("unexpected handler: " + handler);
               // fall through -- let feed service handle error
             case "bookmarks":
             case "client":
             case "default":
               try {
                 let title = feed.title ? feed.title.plainText() : "";
                 let desc = feed.subtitle ? feed.subtitle.plainText() : "";
-                let feedReader = safeGetCharPref(getPrefActionForType(feed.type), "bookmarks");
-                feedService.addToClientReader(result.uri.spec, title, desc, feed.type, feedReader);
+                feedService.addToClientReader(result.uri.spec, title, desc, feed.type, handler);
                 return;
               } catch (ex) { /* fallback to preview mode */ }
           }
         }
       }
 
       let ios =
           Cc["@mozilla.org/network/io-service;1"].
--- a/browser/components/feeds/FeedWriter.js
+++ b/browser/components/feeds/FeedWriter.js
@@ -39,93 +39,21 @@ function makeURI(aURLSpec, aCharset) {
   } catch (ex) { }
 
   return null;
 }
 
 const XML_NS = "http://www.w3.org/XML/1998/namespace";
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
-const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
-const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
 const URI_BUNDLE = "chrome://browser/locale/feeds/subscribe.properties";
 
-const PREF_SELECTED_APP = "browser.feeds.handlers.application";
-const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
-const PREF_SELECTED_ACTION = "browser.feeds.handler";
-const PREF_SELECTED_READER = "browser.feeds.handler.default";
-
-const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application";
-const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
-const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler";
-const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default";
-
-const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application";
-const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
-const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler";
-const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default";
-
-const PREF_SHOW_FIRST_RUN_UI = "browser.feeds.showFirstRunUI";
-
 const TITLE_ID = "feedTitleText";
 const SUBTITLE_ID = "feedSubtitleText";
 
-function getPrefAppForType(t) {
-  switch (t) {
-    case Ci.nsIFeed.TYPE_VIDEO:
-      return PREF_VIDEO_SELECTED_APP;
-
-    case Ci.nsIFeed.TYPE_AUDIO:
-      return PREF_AUDIO_SELECTED_APP;
-
-    default:
-      return PREF_SELECTED_APP;
-  }
-}
-
-function getPrefWebForType(t) {
-  switch (t) {
-    case Ci.nsIFeed.TYPE_VIDEO:
-      return PREF_VIDEO_SELECTED_WEB;
-
-    case Ci.nsIFeed.TYPE_AUDIO:
-      return PREF_AUDIO_SELECTED_WEB;
-
-    default:
-      return PREF_SELECTED_WEB;
-  }
-}
-
-function getPrefActionForType(t) {
-  switch (t) {
-    case Ci.nsIFeed.TYPE_VIDEO:
-      return PREF_VIDEO_SELECTED_ACTION;
-
-    case Ci.nsIFeed.TYPE_AUDIO:
-      return PREF_AUDIO_SELECTED_ACTION;
-
-    default:
-      return PREF_SELECTED_ACTION;
-  }
-}
-
-function getPrefReaderForType(t) {
-  switch (t) {
-    case Ci.nsIFeed.TYPE_VIDEO:
-      return PREF_VIDEO_SELECTED_READER;
-
-    case Ci.nsIFeed.TYPE_AUDIO:
-      return PREF_AUDIO_SELECTED_READER;
-
-    default:
-      return PREF_SELECTED_READER;
-  }
-}
-
 /**
  * Converts a number of bytes to the appropriate unit that results in a
  * number that needs fewer than 4 digits
  *
  * @return a pair: [new value with 3 sig. figs., its unit]
   */
 function convertByteUnits(aBytes) {
   let units = ["bytes", "kilobyte", "megabyte", "gigabyte"];
@@ -148,19 +76,16 @@ function convertByteUnits(aBytes) {
 function FeedWriter() {
   this._selectedApp = undefined;
   this._selectedAppMenuItem = null;
   this._subscribeCallback = null;
   this._defaultHandlerMenuItem = null;
 }
 
 FeedWriter.prototype = {
-  _mimeSvc      : Cc["@mozilla.org/mime;1"].
-                  getService(Ci.nsIMIMEService),
-
   _getPropertyAsBag(container, property) {
     return container.fields.getProperty(property).
                      QueryInterface(Ci.nsIPropertyBag2);
   },
 
   _getPropertyAsString(container, property) {
     try {
       return container.fields.getPropertyAsAString(property);
@@ -221,28 +146,31 @@ FeedWriter.prototype = {
   _getFormattedString(key, params) {
     return this._bundle.formatStringFromName(key, params, params.length);
   },
 
   _getString(key) {
     return this._bundle.GetStringFromName(key);
   },
 
-  _setCheckboxCheckedState(aCheckbox, aValue) {
-    // see checkbox.xml, xbl bindings are not applied within the sandbox! TODO
-    let change = (aValue != (aCheckbox.getAttribute("checked") == "true"));
-    if (aValue)
-      aCheckbox.setAttribute("checked", "true");
-    else
-      aCheckbox.removeAttribute("checked");
+  _setCheckboxCheckedState(aValue) {
+    let checkbox = this._document.getElementById("alwaysUse");
+    if (checkbox) {
+      // see checkbox.xml, xbl bindings are not applied within the sandbox! TODO
+      let change = (aValue != (checkbox.getAttribute("checked") == "true"));
+      if (aValue)
+        checkbox.setAttribute("checked", "true");
+      else
+        checkbox.removeAttribute("checked");
 
-    if (change) {
-      let event = this._document.createEvent("Events");
-      event.initEvent("CheckboxStateChange", true, true);
-      aCheckbox.dispatchEvent(event);
+      if (change) {
+        let event = this._document.createEvent("Events");
+        event.initEvent("CheckboxStateChange", true, true);
+        checkbox.dispatchEvent(event);
+      }
     }
   },
 
    /**
    * Returns a date suitable for displaying in the feed preview.
    * If the date cannot be parsed, the return value is "false".
    * @param   dateString
    *          A date as extracted from a feed entry. (entry.updated)
@@ -286,32 +214,16 @@ FeedWriter.prototype = {
       this.__feedType = feed.type;
       return feed.type;
     } catch (ex) { }
 
     return Ci.nsIFeed.TYPE_FEED;
   },
 
   /**
-   * Maps a feed type to a maybe-feed mimetype.
-   */
-  _getMimeTypeForFeedType() {
-    switch (this._getFeedType()) {
-      case Ci.nsIFeed.TYPE_VIDEO:
-        return TYPE_MAYBE_VIDEO_FEED;
-
-      case Ci.nsIFeed.TYPE_AUDIO:
-        return TYPE_MAYBE_AUDIO_FEED;
-
-      default:
-        return TYPE_MAYBE_FEED;
-    }
-  },
-
-  /**
    * Writes the feed title into the preview document.
    * @param   container
    *          The feed container
    */
   _setTitleText(container) {
     if (container.title) {
       let title = container.title.plainText();
       this._setContentText(TITLE_ID, container.title);
@@ -486,27 +398,21 @@ FeedWriter.prototype = {
       enclosureDiv.setAttribute("class", "enclosure");
 
       let mozicon = "moz-icon://.txt?size=16";
       let type_text = null;
       let size_text = null;
 
       if (enc.hasKey("type")) {
         type_text = enc.get("type");
-        try {
-          let handlerInfoWrapper = this._mimeSvc.getFromTypeAndExtension(enc.get("type"), null);
-
-          if (handlerInfoWrapper)
-            type_text = handlerInfoWrapper.description;
+        if (enc.hasKey("typeDesc"))
+          type_text = enc.get("typeDesc");
 
-          if (type_text && type_text.length > 0)
-            mozicon = "moz-icon://goat?size=16&contentType=" + enc.get("type");
-
-        } catch (ex) { }
-
+        if (type_text && type_text.length > 0)
+          mozicon = "moz-icon://goat?size=16&contentType=" + enc.get("type");
       }
 
       if (enc.hasKey("length") && /^[0-9]+$/.test(enc.get("length"))) {
         let enc_size = convertByteUnits(parseInt(enc.get("length")));
 
         size_text = this._getFormattedString("enclosureSizeText",
                                              [enc_size[0],
                                              this._getString(enc_size[1])]);
@@ -591,29 +497,17 @@ FeedWriter.prototype = {
    * Displays a prompt from which the user may choose a (client) feed reader.
    * @param aCallback the callback method, passes in true if a feed reader was
    *        selected, false otherwise.
    */
   _chooseClientApp(aCallback) {
     this._subscribeCallback = aCallback;
     this._mm.sendAsyncMessage("FeedWriter:ChooseClientApp",
                               { title: this._getString("chooseApplicationDialogTitle"),
-                                prefName: getPrefAppForType(this._getFeedType()) });
-  },
-
-  _setAlwaysUseCheckedState(feedType) {
-    let checkbox = this._document.getElementById("alwaysUse");
-    if (checkbox) {
-      let alwaysUse = false;
-      try {
-        if (Services.prefs.getCharPref(getPrefActionForType(feedType)) != "ask")
-          alwaysUse = true;
-      } catch (ex) { }
-      this._setCheckboxCheckedState(checkbox, alwaysUse);
-    }
+                                feedType: this._getFeedType() });
   },
 
   _setSubscribeUsingLabel() {
     let stringLabel = "subscribeFeedUsing";
     switch (this._getFeedType()) {
       case Ci.nsIFeed.TYPE_VIDEO:
         stringLabel = "subscribeVideoPodcastUsing";
         break;
@@ -666,54 +560,44 @@ FeedWriter.prototype = {
 
     switch (event.type) {
       case "click":
         if (event.target.id == "subscribeButton") {
           this.subscribe();
         }
       break;
       case "change":
+        LOG("Change fired");
         if (event.target.selectedOptions[0].id == "chooseApplicationMenuItem") {
-          this._chooseClientApp((aResult) => {
-            if (!aResult) {
-              // Select the (per-prefs) selected handler if no application
-              // was selected
-              this._setSelectedHandler(this._getFeedType());
-            }
+          this._chooseClientApp(() => {
+            // Select the (per-prefs) selected handler if no application
+            // was selected
+            LOG("Selected handler after callback");
+            this._setAlwaysUseLabel();
           });
         } else {
           this._setAlwaysUseLabel();
         }
         break;
     }
   },
 
   _getWebHandlerElementsForURL(aURL) {
-    let menu = this._document.getElementById("handlersMenuList");
-    return menu.querySelectorAll('[webhandlerurl="' + aURL + '"]');
+    return this._handlersList.querySelectorAll('[webhandlerurl="' + aURL + '"]');
   },
 
-  _setSelectedHandler(feedType) {
-    let prefs = Services.prefs;
-    let handler = prefs.getCharPref(getPrefReaderForType(feedType), "bookmarks");
-
+  _setSelectedHandlerResponse(handler, url) {
+    LOG(`Selecting handler response ${handler} ${url}`);
     switch (handler) {
       case "web": {
         if (this._handlersList) {
-          let url;
-          try {
-            url = prefs.getStringPref(getPrefWebForType(feedType));
-          } catch (ex) {
-            LOG("FeedWriter._setSelectedHandler: invalid or no handler in prefs");
-            return;
-          }
           let handlers =
             this._getWebHandlerElementsForURL(url);
           if (handlers.length == 0) {
-            LOG("FeedWriter._setSelectedHandler: selected web handler isn't in the menulist")
+            LOG(`Selected web handler isn't in the menulist ${url}`);
             return;
           }
 
           handlers[0].selected = true;
         }
         break;
       }
       case "client":
@@ -724,20 +608,20 @@ FeedWriter.prototype = {
       default: {
         let liveBookmarksMenuItem = this._document.getElementById("liveBookmarksMenuItem");
         if (liveBookmarksMenuItem)
           liveBookmarksMenuItem.selected = true;
       }
     }
   },
 
-  _initSubscriptionUI() {
-    let handlersList = this._document.getElementById("handlersMenuList");
-    if (!handlersList)
+  _initSubscriptionUI(setupMessage) {
+    if (!this._handlersList)
       return;
+    LOG("UI init");
 
     let feedType = this._getFeedType();
 
     // change the background
     let header = this._document.getElementById("feedHeader");
     switch (feedType) {
       case Ci.nsIFeed.TYPE_VIDEO:
         header.className = "videoPodcastBackground";
@@ -758,83 +642,84 @@ FeedWriter.prototype = {
     menuItem.removeAttribute("selected");
     menuItem.setAttribute("id", "selectedAppMenuItem");
     menuItem.setAttribute("handlerType", "client");
 
     // Hide the menuitem until we select an app
     menuItem.style.display = "none";
     this._selectedAppMenuItem = menuItem;
 
-    handlersList.appendChild(this._selectedAppMenuItem);
+    this._handlersList.appendChild(this._selectedAppMenuItem);
 
     // Create the menuitem for the default reader, but don't show/populate it until
     // we get confirmation of what it is from the parent
     menuItem = liveBookmarksMenuItem.cloneNode(false);
     menuItem.removeAttribute("selected");
     menuItem.setAttribute("id", "defaultHandlerMenuItem");
     menuItem.setAttribute("handlerType", "client");
     menuItem.style.display = "none";
 
     this._defaultHandlerMenuItem = menuItem;
-    handlersList.appendChild(this._defaultHandlerMenuItem);
-
-    this._mm.sendAsyncMessage("FeedWriter:RequestClientAppName",
-                              { feedTypePref: getPrefAppForType(feedType) });
+    this._handlersList.appendChild(this._defaultHandlerMenuItem);
 
     // "Choose Application..." menuitem
     menuItem = liveBookmarksMenuItem.cloneNode(false);
     menuItem.removeAttribute("selected");
     menuItem.setAttribute("id", "chooseApplicationMenuItem");
     menuItem.textContent = this._getString("chooseApplicationMenuItem");
 
-    handlersList.appendChild(menuItem);
+    this._handlersList.appendChild(menuItem);
 
     // separator
     let chooseAppSep = liveBookmarksMenuItem.nextElementSibling.cloneNode(false);
     chooseAppSep.textContent = liveBookmarksMenuItem.nextElementSibling.textContent;
-    handlersList.appendChild(chooseAppSep);
+    this._handlersList.appendChild(chooseAppSep);
 
-    // List of web handlers
-    let wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
-               getService(Ci.nsIWebContentConverterService);
-    let handlers = wccr.getContentHandlers(this._getMimeTypeForFeedType(feedType));
-    for (let handler of handlers) {
+    for (let handler of setupMessage.handlers) {
       if (!handler.uri) {
         LOG("Handler with name " + handler.name + " has no URI!? Skipping...");
         continue;
       }
       menuItem = liveBookmarksMenuItem.cloneNode(false);
       menuItem.removeAttribute("selected");
       menuItem.className = "menuitem-iconic";
       menuItem.textContent = handler.name;
       menuItem.setAttribute("handlerType", "web");
       menuItem.setAttribute("webhandlerurl", handler.uri);
-      handlersList.appendChild(menuItem);
+      this._handlersList.appendChild(menuItem);
     }
 
-    this._setSelectedHandler(feedType);
+    this._setSelectedHandlerResponse(setupMessage.reader.handler, setupMessage.reader.url);
+
+    if (setupMessage.defaultMenuItem) {
+      LOG(`Setting default menu item ${setupMessage.defaultMenuItem}`);
+      this._setApplicationLauncherMenuItem(this._defaultHandlerMenuItem, setupMessage.defaultMenuItem);
+    }
+    if (setupMessage.selectedMenuItem) {
+      LOG(`Setting selected menu item ${setupMessage.selectedMenuItem}`);
+      this._setApplicationLauncherMenuItem(this._selectedAppMenuItem, setupMessage.selectedMenuItem);
+    }
 
     // "Subscribe using..."
     this._setSubscribeUsingLabel();
 
     // "Always use..." checkbox initial state
-    this._setAlwaysUseCheckedState(feedType);
+    this._setCheckboxCheckedState(setupMessage.reader.alwaysUse);
     this._setAlwaysUseLabel();
 
     // We update the "Always use.." checkbox label whenever the selected item
     // in the list is changed
-    handlersList.addEventListener("change", this);
+    this._handlersList.addEventListener("change", this);
 
     // Set up the "Subscribe Now" button
     this._document.getElementById("subscribeButton")
         .addEventListener("click", this);
 
     // first-run ui
-    let showFirstRunUI = Services.prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI, true);
-    if (showFirstRunUI) {
+    if (setupMessage.showFirstRunUI) {
       let textfeedinfo1, textfeedinfo2;
       switch (feedType) {
         case Ci.nsIFeed.TYPE_VIDEO:
           textfeedinfo1 = "feedSubscriptionVideoPodcast1";
           textfeedinfo2 = "feedSubscriptionVideoPodcast2";
           break;
         case Ci.nsIFeed.TYPE_AUDIO:
           textfeedinfo1 = "feedSubscriptionAudioPodcast1";
@@ -910,63 +795,84 @@ FeedWriter.prototype = {
     this._handlersList = this._document.getElementById("handlersMenuList");
 
     let secman = Cc["@mozilla.org/scriptsecuritymanager;1"].
                  getService(Ci.nsIScriptSecurityManager);
     this._feedPrincipal = secman.createCodebasePrincipal(this._feedURI, {});
 
     LOG("Subscribe Preview: feed uri = " + this._window.location.href);
 
-    // Set up the subscription UI
-    this._initSubscriptionUI();
-    let prefs = Services.prefs;
-    prefs.addObserver(PREF_SELECTED_ACTION, this, false);
-    prefs.addObserver(PREF_SELECTED_READER, this, false);
-    prefs.addObserver(PREF_SELECTED_WEB, this, false);
-    prefs.addObserver(PREF_SELECTED_APP, this, false);
-    prefs.addObserver(PREF_VIDEO_SELECTED_ACTION, this, false);
-    prefs.addObserver(PREF_VIDEO_SELECTED_READER, this, false);
-    prefs.addObserver(PREF_VIDEO_SELECTED_WEB, this, false);
-    prefs.addObserver(PREF_VIDEO_SELECTED_APP, this, false);
 
-    prefs.addObserver(PREF_AUDIO_SELECTED_ACTION, this, false);
-    prefs.addObserver(PREF_AUDIO_SELECTED_READER, this, false);
-    prefs.addObserver(PREF_AUDIO_SELECTED_WEB, this, false);
-    prefs.addObserver(PREF_AUDIO_SELECTED_APP, this, false);
+    this._mm.addMessageListener("FeedWriter:PreferenceUpdated", this);
+    this._mm.addMessageListener("FeedWriter:SetApplicationLauncherMenuItem", this);
+    this._mm.addMessageListener("FeedWriter:GetSubscriptionUIResponse", this);
+    this._mm.addMessageListener("FeedWriter:SetFeedPrefsAndSubscribeResponse", this);
 
-    this._mm.addMessageListener("FeedWriter:SetApplicationLauncherMenuItem", this);
+    const feedType = this._getFeedType();
+    this._mm.sendAsyncMessage("FeedWriter:GetSubscriptionUI",
+                              { feedType });
   },
 
   receiveMessage(msg) {
+    if (!this._window) {
+      // this._window is null unless this.init was called with a trusted
+      // window object.
+      return;
+    }
+    LOG(`received message from parent ${msg.name}`);
     switch (msg.name) {
+      case "FeedWriter:PreferenceUpdated":
+        // This is called when browser-feeds.js spots a pref change
+        // This will happen when
+        // - about:preferences#applications changes
+        // - another feed reader page changes the preference
+        // - when this page itself changes the select and there isn't a redirect
+        //   bookmarks and launching an external app means the page stays open after subscribe
+        const feedType = this._getFeedType();
+        LOG(`Got prefChange! ${JSON.stringify(msg.data)} current type: ${feedType}`);
+        let feedTypePref = msg.data.default;
+        if (feedType in msg.data) {
+          feedTypePref = msg.data[feedType];
+        }
+        LOG(`Got pref ${JSON.stringify(feedTypePref)}`);
+        this._setCheckboxCheckedState(feedTypePref.alwaysUse);
+        this._setSelectedHandlerResponse(feedTypePref.handler, feedTypePref.url);
+        this._setAlwaysUseLabel();
+        break;
+      case "FeedWriter:SetFeedPrefsAndSubscribeResponse":
+        LOG(`FeedWriter:SetFeedPrefsAndSubscribeResponse - Redirecting ${msg.data.redirect}`);
+        this._window.location.href = msg.data.redirect;
+        break;
+      case "FeedWriter:GetSubscriptionUIResponse":
+        // Set up the subscription UI
+        this._initSubscriptionUI(msg.data);
+        break;
       case "FeedWriter:SetApplicationLauncherMenuItem":
-        let menuItem = null;
-
-        if (msg.data.type == "DefaultAppMenuItem") {
-          menuItem = this._defaultHandlerMenuItem;
-        } else {
-          // Most likely SelectedAppMenuItem
-          menuItem = this._selectedAppMenuItem;
-        }
-
-        menuItem.textContent = msg.data.name;
-        menuItem.style.display = "";
-        menuItem.selected = true;
-
+        LOG(`FeedWriter:SetApplicationLauncherMenuItem - picked ${msg.data.name}`);
+        this._setApplicationLauncherMenuItem(this._selectedAppMenuItem, msg.data.name);
         // Potentially a bit racy, but I don't think we can get into a state where this callback is set and
         // we're not coming back from ChooseClientApp in browser-feeds.js
         if (this._subscribeCallback) {
           this._subscribeCallback();
           this._subscribeCallback = null;
         }
-
         break;
     }
   },
 
+  _setApplicationLauncherMenuItem(menuItem, aName) {
+    /* unselect all handlers */
+    [...this._handlersList.children].forEach((option) => {
+      option.removeAttribute("selected");
+    });
+    menuItem.textContent = aName;
+    menuItem.style.display = "";
+    menuItem.selected = true;
+  },
+
   writeContent() {
     if (!this._window)
       return;
 
     try {
       // Set up the feed content
       let container = this._getContainer();
       if (!container)
@@ -978,34 +884,21 @@ FeedWriter.prototype = {
     } finally {
       this._removeFeedFromCache();
     }
   },
 
   close() {
     this._document.getElementById("subscribeButton")
         .removeEventListener("click", this);
-    this._document.getElementById("handlersMenuList")
+    this._handlersList
         .removeEventListener("change", this);
     this._document = null;
     this._window = null;
-    let prefs = Services.prefs;
-    prefs.removeObserver(PREF_SELECTED_ACTION, this);
-    prefs.removeObserver(PREF_SELECTED_READER, this);
-    prefs.removeObserver(PREF_SELECTED_WEB, this);
-    prefs.removeObserver(PREF_SELECTED_APP, this);
-    prefs.removeObserver(PREF_VIDEO_SELECTED_ACTION, this);
-    prefs.removeObserver(PREF_VIDEO_SELECTED_READER, this);
-    prefs.removeObserver(PREF_VIDEO_SELECTED_WEB, this);
-    prefs.removeObserver(PREF_VIDEO_SELECTED_APP, this);
-
-    prefs.removeObserver(PREF_AUDIO_SELECTED_ACTION, this);
-    prefs.removeObserver(PREF_AUDIO_SELECTED_READER, this);
-    prefs.removeObserver(PREF_AUDIO_SELECTED_WEB, this);
-    prefs.removeObserver(PREF_AUDIO_SELECTED_APP, this);
+    this._handlersList = null;
 
     this._removeFeedFromCache();
     this.__bundle = null;
     this._feedURI = null;
 
     this._selectedApp = undefined;
     this._selectedAppMenuItem = null;
     this._defaultHandlerMenuItem = null;
@@ -1015,90 +908,64 @@ FeedWriter.prototype = {
     if (this._feedURI) {
       let feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
                         getService(Ci.nsIFeedResultService);
       feedService.removeFeedResult(this._feedURI);
       this._feedURI = null;
     }
   },
 
-  setFeedCharPref(aPrefName, aPrefValue) {
-      this._mm.sendAsyncMessage("FeedWriter:SetFeedCharPref",
-                                { pref: aPrefName,
-                                  value: aPrefValue });
-  },
-
-  setFeedComplexString(aPrefName, aPrefValue) {
-      // This sends the string data across to the parent, which will use it in an nsISupportsString
-      // for a complex value pref.
-      this._mm.sendAsyncMessage("FeedWriter:SetFeedComplexString",
-                                { pref: aPrefName,
-                                  value: aPrefValue });
-  },
-
   subscribe() {
     let feedType = this._getFeedType();
 
     // Subscribe to the feed using the selected handler and save prefs
     let defaultHandler = "reader";
     let useAsDefault = this._document.getElementById("alwaysUse").getAttribute("checked");
 
-    let menuList = this._document.getElementById("handlersMenuList");
-    let selectedItem = menuList.selectedOptions[0];
+    let selectedItem = this._handlersList.selectedOptions[0];
     let subscribeCallback = () => {
+      let feedReader = null;
+      let settings = {
+        feedType,
+        useAsDefault,
+        // Pull the title and subtitle out of the document
+        feedTitle: this._document.getElementById(TITLE_ID).textContent,
+        feedSubtitle: this._document.getElementById(SUBTITLE_ID).textContent,
+        feedLocation: this._window.location.href
+      };
       if (selectedItem.hasAttribute("webhandlerurl")) {
-        let webURI = selectedItem.getAttribute("webhandlerurl");
-        this.setFeedCharPref(getPrefReaderForType(feedType), "web");
-        this.setFeedComplexString(getPrefWebForType(feedType), webURI);
-
-        let wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
-                   getService(Ci.nsIWebContentConverterService);
-        let handler = wccr.getWebContentHandlerByURI(this._getMimeTypeForFeedType(feedType), webURI);
-        if (handler) {
-          if (useAsDefault) {
-            wccr.setAutoHandler(this._getMimeTypeForFeedType(feedType), handler);
-          }
-
-          this._window.location.href = handler.getHandlerURI(this._window.location.href);
-        }
+        feedReader = "web";
+        settings.uri = selectedItem.getAttribute("webhandlerurl");
       } else {
-        let feedReader = null;
         switch (selectedItem.id) {
           case "selectedAppMenuItem":
             feedReader = "client";
             break;
           case "defaultHandlerMenuItem":
             feedReader = "default";
             break;
           case "liveBookmarksMenuItem":
             defaultHandler = "bookmarks";
             feedReader = "bookmarks";
             break;
         }
-
-        this.setFeedCharPref(getPrefReaderForType(feedType), feedReader);
-
-        let feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
-                          getService(Ci.nsIFeedResultService);
-
-        // Pull the title and subtitle out of the document
-        let feedTitle = this._document.getElementById(TITLE_ID).textContent;
-        let feedSubtitle = this._document.getElementById(SUBTITLE_ID).textContent;
-        feedService.addToClientReader(this._window.location.href, feedTitle, feedSubtitle, feedType, feedReader);
       }
+      settings.reader = feedReader;
 
       // If "Always use..." is checked, we should set PREF_*SELECTED_ACTION
       // to either "reader" (If a web reader or if an application is selected),
       // or to "bookmarks" (if the live bookmarks option is selected).
       // Otherwise, we should set it to "ask"
-      if (useAsDefault) {
-        this.setFeedCharPref(getPrefActionForType(feedType), defaultHandler);
-      } else {
-        this.setFeedCharPref(getPrefActionForType(feedType), "ask");
+      if (!useAsDefault) {
+        defaultHandler = "ask";
       }
+      settings.action = defaultHandler;
+      LOG(`FeedWriter:SetFeedPrefsAndSubscribe - ${JSON.stringify(settings)}`);
+      this._mm.sendAsyncMessage("FeedWriter:SetFeedPrefsAndSubscribe",
+                                settings);
     }
 
     // Show the file picker before subscribing if the
     // choose application menuitem was chosen using the keyboard
     if (selectedItem.id == "chooseApplicationMenuItem") {
       this._chooseClientApp(function(aResult) {
         if (aResult) {
           selectedItem =
@@ -1106,47 +973,16 @@ FeedWriter.prototype = {
           subscribeCallback();
         }
       }.bind(this));
     } else {
       subscribeCallback();
     }
   },
 
-  // nsIObserver
-  observe(subject, topic, data) {
-    if (!this._window) {
-      // this._window is null unless this.init was called with a trusted
-      // window object.
-      return;
-    }
-
-    let feedType = this._getFeedType();
-
-    if (topic == "nsPref:changed") {
-      switch (data) {
-        case PREF_SELECTED_READER:
-        case PREF_SELECTED_WEB:
-        case PREF_SELECTED_APP:
-        case PREF_VIDEO_SELECTED_READER:
-        case PREF_VIDEO_SELECTED_WEB:
-        case PREF_VIDEO_SELECTED_APP:
-        case PREF_AUDIO_SELECTED_READER:
-        case PREF_AUDIO_SELECTED_WEB:
-        case PREF_AUDIO_SELECTED_APP:
-          this._setSelectedHandler(feedType);
-          break;
-        case PREF_SELECTED_ACTION:
-        case PREF_VIDEO_SELECTED_ACTION:
-        case PREF_AUDIO_SELECTED_ACTION:
-          this._setAlwaysUseCheckedState(feedType);
-      }
-    }
-  },
-
   get _mm() {
     let mm = this._window.QueryInterface(Ci.nsIInterfaceRequestor).
                           getInterface(Ci.nsIDocShell).
                           QueryInterface(Ci.nsIInterfaceRequestor).
                           getInterface(Ci.nsIContentFrameMessageManager);
     delete this._mm;
     return this._mm = mm;
   },
--- a/browser/extensions/e10srollout/bootstrap.js
+++ b/browser/extensions/e10srollout/bootstrap.js
@@ -14,17 +14,17 @@ Cu.import("resource://gre/modules/Update
 const TEST_THRESHOLD = {
   "beta"    : 0.5,  // 50%
   "release" : 1.0,  // 100%
   "esr"     : 1.0,  // 100%
 };
 
 const ADDON_ROLLOUT_POLICY = {
   "beta"    : "51alladdons", // Any WebExtension or addon except with mpc = false
-  "release" : "51set1",
+  "release" : "50allmpc",
   "esr"     : "esrA", // WebExtensions and Addons with mpc=true
 };
 
 const PREF_COHORT_SAMPLE       = "e10s.rollout.cohortSample";
 const PREF_COHORT_NAME         = "e10s.rollout.cohort";
 const PREF_E10S_OPTED_IN       = "browser.tabs.remote.autostart";
 const PREF_E10S_FORCE_ENABLED  = "browser.tabs.remote.force-enable";
 const PREF_E10S_FORCE_DISABLED = "browser.tabs.remote.force-disable";
@@ -68,17 +68,20 @@ function defineCohort() {
   if (updateChannel in ADDON_ROLLOUT_POLICY) {
     addonPolicy = ADDON_ROLLOUT_POLICY[updateChannel];
     Preferences.set(PREF_E10S_ADDON_POLICY, addonPolicy);
     // This is also the proper place to set the blocklist pref
     // in case it is necessary.
 
     Preferences.set(PREF_E10S_ADDON_BLOCKLIST,
                     // bug 1185672 - Tab Mix Plus
-                    "{dc572301-7619-498c-a57d-39143191b318};");
+                    "{dc572301-7619-498c-a57d-39143191b318};"
+                    // bug 1344345 - Mega
+                    + "firefox@mega.co.nz"
+                    );
   } else {
     Preferences.reset(PREF_E10S_ADDON_POLICY);
   }
 
   let userOptedOut = optedOut();
   let userOptedIn = optedIn();
   let disqualified = (Services.appinfo.multiprocessBlockPolicy != 0);
   let testGroup = (getUserSample() < TEST_THRESHOLD[updateChannel]);
--- a/browser/extensions/e10srollout/install.rdf.in
+++ b/browser/extensions/e10srollout/install.rdf.in
@@ -5,17 +5,17 @@
 
 #filter substitution
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
      xmlns:em="http://www.mozilla.org/2004/em-rdf#">
 
   <Description about="urn:mozilla:install-manifest">
     <em:id>e10srollout@mozilla.org</em:id>
-    <em:version>1.11</em:version>
+    <em:version>1.12</em:version>
     <em:type>2</em:type>
     <em:bootstrap>true</em:bootstrap>
     <em:multiprocessCompatible>true</em:multiprocessCompatible>
 
     <!-- Target Application this theme can install into,
         with minimum and maximum supported versions. -->
     <em:targetApplication>
       <Description>
--- a/caps/nsJSPrincipals.cpp
+++ b/caps/nsJSPrincipals.cpp
@@ -130,26 +130,32 @@ ReadSuffixAndSpec(JSStructuredCloneReade
                   nsACString& aSpec)
 {
     uint32_t suffixLength, specLength;
     if (!JS_ReadUint32Pair(aReader, &suffixLength, &specLength)) {
         return false;
     }
 
     nsAutoCString suffix;
-    suffix.SetLength(suffixLength);
+    if (!suffix.SetLength(suffixLength, fallible)) {
+        return false;
+    }
+
     if (!JS_ReadBytes(aReader, suffix.BeginWriting(), suffixLength)) {
         return false;
     }
 
     if (!aAttrs.PopulateFromSuffix(suffix)) {
         return false;
     }
 
-    aSpec.SetLength(specLength);
+    if (!aSpec.SetLength(specLength, fallible)) {
+        return false;
+    }
+
     if (!JS_ReadBytes(aReader, aSpec.BeginWriting(), specLength)) {
         return false;
     }
 
     return true;
 }
 
 static bool
@@ -190,17 +196,17 @@ ReadPrincipalInfo(JSStructuredCloneReade
         aInfo = expanded;
     } else if (aTag == SCTAG_DOM_CONTENT_PRINCIPAL) {
         OriginAttributes attrs;
         nsAutoCString spec;
         if (!ReadSuffixAndSpec(aReader, attrs, spec)) {
             return false;
         }
 
-        aInfo = ContentPrincipalInfo(attrs, spec);
+        aInfo = ContentPrincipalInfo(attrs, void_t(), spec);
     } else {
         MOZ_CRASH("unexpected principal structured clone tag");
     }
 
     return true;
 }
 
 /* static */ bool
--- a/devtools/client/webconsole/test/browser.ini
+++ b/devtools/client/webconsole/test/browser.ini
@@ -176,17 +176,16 @@ skip-if = (os == 'linux' && bits == 32 &
 [browser_console_error_source_click.js]
 [browser_console_filters.js]
 [browser_console_iframe_messages.js]
 [browser_console_keyboard_accessibility.js]
 [browser_console_log_inspectable_object.js]
 [browser_console_native_getters.js]
 [browser_console_navigation_marker.js]
 [browser_console_netlogging.js]
-skip-if = true # Bug 1298364
 [browser_console_nsiconsolemessage.js]
 [browser_console_optimized_out_vars.js]
 [browser_console_private_browsing.js]
 skip-if = e10s # Bug 1042253 - webconsole e10s tests
 [browser_console_server_logging.js]
 [browser_console_variables_view.js]
 [browser_console_variables_view_filter.js]
 [browser_console_variables_view_dom_nodes.js]
@@ -333,16 +332,17 @@ skip-if = e10s # Bug 1042253 - webconsol
 [browser_webconsole_js_input_expansion.js]
 [browser_webconsole_jsterm.js]
 skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout)
 [browser_webconsole_live_filtering_of_message_types.js]
 [browser_webconsole_live_filtering_on_search_strings.js]
 [browser_webconsole_message_node_id.js]
 [browser_webconsole_multiline_input.js]
 [browser_webconsole_netlogging.js]
+skip-if = true # Bug 1298364
 [browser_webconsole_netlogging_basic.js]
 [browser_webconsole_netlogging_panel.js]
 [browser_webconsole_netlogging_reset_filter.js]
 [browser_webconsole_notifications.js]
 [browser_webconsole_open-links-without-callback.js]
 [browser_webconsole_promise.js]
 [browser_webconsole_output_copy_newlines.js]
 subsuite = clipboard
--- a/devtools/server/actors/layout.js
+++ b/devtools/server/actors/layout.js
@@ -120,21 +120,26 @@ var LayoutActor = ActorClassWithSpec(lay
    * iterating below the given rootNode and optionally including nested frames.
    *
    * @param  {NodeActor} rootNode
    * @param  {Boolean} traverseFrames
    *         Whether or not we should iterate through nested frames.
    * @return {Array} An array of GridActor objects.
    */
   getAllGrids: function (rootNode, traverseFrames) {
+    let grids = [];
+
+    if (!rootNode) {
+      return grids;
+    }
+
     if (!traverseFrames) {
       return this.getGrids(rootNode.rawNode);
     }
 
-    let grids = [];
     for (let {document} of this.tabActor.windows) {
       grids = [...grids, ...this.getGrids(document.documentElement)];
     }
 
     return grids;
   },
 
   onNavigate: function () {
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -10560,24 +10560,22 @@ nsIDocument::ObsoleteSheet(const nsAStri
 
 class UnblockParsingPromiseHandler final : public PromiseNativeHandler
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(UnblockParsingPromiseHandler)
 
   explicit UnblockParsingPromiseHandler(nsIDocument* aDocument, Promise* aPromise)
-    : mDocument(aDocument)
-    , mPromise(aPromise)
+    : mPromise(aPromise)
   {
-    nsCOMPtr<nsIParser> parser = mDocument->CreatorParserOrNull();
+    nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull();
     if (parser) {
       parser->BlockParser();
-    } else {
-      mDocument = nullptr;
+      mParser = do_GetWeakReference(parser);
     }
   }
 
   void
   ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
   {
     MaybeUnblockParser();
 
@@ -10595,31 +10593,29 @@ public:
 protected:
   virtual ~UnblockParsingPromiseHandler()
   {
     MaybeUnblockParser();
   }
 
 private:
   void MaybeUnblockParser() {
-    if (mDocument) {
-      nsCOMPtr<nsIParser> parser = mDocument->CreatorParserOrNull();
-      if (parser) {
-        parser->UnblockParser();
-        parser->ContinueInterruptedParsingAsync();
-      }
-      mDocument = nullptr;
-    }
-  }
-
-  RefPtr<nsIDocument> mDocument;
+    nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser);
+    if (parser) {
+      parser->UnblockParser();
+      parser->ContinueInterruptedParsingAsync();
+      mParser = nullptr;
+    }
+  }
+
+  nsWeakPtr mParser;
   RefPtr<Promise> mPromise;
 };
 
-NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mDocument, mPromise)
+NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mPromise)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnblockParsingPromiseHandler)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(UnblockParsingPromiseHandler)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(UnblockParsingPromiseHandler)
 
--- a/dom/cache/DBSchema.cpp
+++ b/dom/cache/DBSchema.cpp
@@ -1970,25 +1970,25 @@ ReadResponse(mozIStorageConnection* aCon
   }
 
   nsAutoCString serializedInfo;
   rv = state->GetUTF8String(5, serializedInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   aSavedResponseOut->mValue.principalInfo() = void_t();
   if (!serializedInfo.IsEmpty()) {
-    nsAutoCString originNoSuffix;
+    nsAutoCString specNoSuffix;
     OriginAttributes attrs;
-    if (!attrs.PopulateFromOrigin(serializedInfo, originNoSuffix)) {
+    if (!attrs.PopulateFromOrigin(serializedInfo, specNoSuffix)) {
       NS_WARNING("Something went wrong parsing a serialized principal!");
       return NS_ERROR_FAILURE;
     }
 
     aSavedResponseOut->mValue.principalInfo() =
-      mozilla::ipc::ContentPrincipalInfo(attrs, originNoSuffix);
+      mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), specNoSuffix);
   }
 
   rv = state->GetBlobAsUTF8String(6, aSavedResponseOut->mValue.channelInfo().securityInfo());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT "
       "name, "
--- a/dom/events/test/pointerevents/mochitest.ini
+++ b/dom/events/test/pointerevents/mochitest.ini
@@ -1,16 +1,26 @@
 [DEFAULT]
 skip-if = os == 'android' # Bug 1312791
 support-files =
   mochitest_support_external.js
   mochitest_support_internal.js
   pointerevent_styles.css
   pointerevent_support.js
 
+[test_bug1285128.html]
+[test_bug1293174_implicit_pointer_capture_for_touch_1.html]
+  support-files = bug1293174_implicit_pointer_capture_for_touch_1.html
+[test_bug1293174_implicit_pointer_capture_for_touch_2.html]
+  support-files = bug1293174_implicit_pointer_capture_for_touch_2.html
+[test_bug1303704.html]
+[test_bug1315862.html]
+[test_bug1323158.html]
+[test_empty_file.html]
+  disabled = disabled # Bug 1150091 - Issue with support-files
 [test_pointerevent_attributes_hoverable_pointers-manual.html]
   support-files =
     pointerevent_attributes_hoverable_pointers-manual.html
     ./resources/pointerevent_attributes_hoverable_pointers-iframe.html
 [test_pointerevent_attributes_nohover_pointers-manual.html]
   support-files =
     pointerevent_attributes_nohover_pointers-manual.html
     ./resources/pointerevent_attributes_hoverable_pointers-iframe.html
@@ -113,19 +123,10 @@ support-files =
     pointerevent_touch-action-pan-y-css_touch-manual.html
     pointerevent_touch-action-span-test_touch-manual.html
     pointerevent_touch-action-svg-test_touch-manual.html
     pointerevent_touch-action-table-test_touch-manual.html
     pointerevent_touch-action-pan-down-css_touch-manual.html
     pointerevent_touch-action-pan-left-css_touch-manual.html
     pointerevent_touch-action-pan-right-css_touch-manual.html
     pointerevent_touch-action-pan-up-css_touch-manual.html
-[test_bug1285128.html]
-[test_bug1293174_implicit_pointer_capture_for_touch_1.html]
-  support-files = bug1293174_implicit_pointer_capture_for_touch_1.html
-[test_bug1293174_implicit_pointer_capture_for_touch_2.html]
-  support-files = bug1293174_implicit_pointer_capture_for_touch_2.html
-[test_bug1303704.html]
-[test_bug1323158.html]
+[test_trigger_fullscreen_by_pointer_events.html]
 [test_trigger_popup_by_pointer_events.html]
-[test_empty_file.html]
-  disabled = disabled # Bug 1150091 - Issue with support-files
-[test_bug1315862.html]
new file mode 100644
--- /dev/null
+++ b/dom/events/test/pointerevents/test_trigger_fullscreen_by_pointer_events.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test for triggering Fullscreen by pointer events</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="target" style="width: 50px; height: 50px; background: green"></div>
+<script>
+
+SimpleTest.waitForExplicitFinish();
+
+var target = document.getElementById("target");
+target.addEventListener("pointerdown", () => {
+  target.requestFullscreen();
+  target.addEventListener("pointerdown", () => {
+    document.exitFullscreen();
+  }, {once: true});
+}, {once: true});
+
+document.addEventListener("fullscreenchange", () => {
+  if (document.fullscreenElement) {
+    ok(document.fullscreenElement, target, "fullscreenElement should be the div element");
+    // synthesize mouse events to generate pointer events and leave full screen.
+    synthesizeMouseAtCenter(target, { type: "mousedown" });
+    synthesizeMouseAtCenter(target, { type: "mouseup" });
+  } else {
+    SimpleTest.finish();
+  }
+});
+
+function startTest() {
+  // synthesize mouse events to generate pointer events and enter full screen.
+  synthesizeMouseAtCenter(target, { type: "mousedown" });
+  synthesizeMouseAtCenter(target, { type: "mouseup" });
+}
+
+SimpleTest.waitForFocus(() => {
+  SpecialPowers.pushPrefEnv({
+    "set": [
+      ["full-screen-api.unprefix.enabled", true],
+      ["full-screen-api.allow-trusted-requests-only", false],
+      ["dom.w3c_pointer_events.enabled", true]
+    ]
+  }, startTest);
+});
+
+</script>
+</body>
+</html>
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -18791,20 +18791,22 @@ Maintenance::DirectoryWork()
         }
 
         // Found a database.
         if (databasePaths.IsEmpty()) {
           MOZ_ASSERT(group.IsEmpty());
           MOZ_ASSERT(origin.IsEmpty());
 
           int64_t dummyTimeStamp;
+          bool dummyPersisted;
           nsCString dummySuffix;
           if (NS_WARN_IF(NS_FAILED(
                 quotaManager->GetDirectoryMetadata2(originDir,
                                                     &dummyTimeStamp,
+                                                    &dummyPersisted,
                                                     dummySuffix,
                                                     group,
                                                     origin)))) {
             // Not much we can do here...
             continue;
           }
         }
 
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -4004,17 +4004,18 @@ MediaStreamGraph::ApplyAudioContextOpera
     }
     void Run() override
     {
       mStream->GraphImpl()->ApplyAudioContextOperationImpl(mStream,
         mStreams, mAudioContextOperation, mPromise);
     }
     void RunDuringShutdown() override
     {
-      MOZ_ASSERT(false, "We should be reviving the graph?");
+      MOZ_ASSERT(mAudioContextOperation == AudioContextOperation::Close,
+                 "We should be reviving the graph?");
     }
 
   private:
     // We don't need strong references here for the same reason ControlMessage
     // doesn't.
     nsTArray<MediaStream*> mStreams;
     AudioContextOperation mAudioContextOperation;
     void* mPromise;
--- a/dom/media/systemservices/MediaParent.cpp
+++ b/dom/media/systemservices/MediaParent.cpp
@@ -102,38 +102,38 @@ class OriginKeyStore : public nsISupport
         }
       }
       mPersistCount = 0;
     }
 
   private:
     void
     PrincipalInfoToString(const ipc::PrincipalInfo& aPrincipalInfo,
-                          nsAutoCString aString)
+                          nsACString& aString)
     {
       switch (aPrincipalInfo.type()) {
         case ipc::PrincipalInfo::TSystemPrincipalInfo:
           aString.Assign("[System Principal]");
           return;
 
         case ipc::PrincipalInfo::TNullPrincipalInfo: {
           const ipc::NullPrincipalInfo& info =
             aPrincipalInfo.get_NullPrincipalInfo();
           aString.Assign(info.spec());
           return;
         }
 
         case ipc::PrincipalInfo::TContentPrincipalInfo: {
           const ipc::ContentPrincipalInfo& info =
             aPrincipalInfo.get_ContentPrincipalInfo();
-          aString.Assign(info.spec());
+          aString.Assign(info.originNoSuffix());
 
           nsAutoCString suffix;
           info.attrs().CreateSuffix(suffix);
-          suffix.Append(suffix);
+          aString.Append(suffix);
           return;
         }
 
         case ipc::PrincipalInfo::TExpandedPrincipalInfo: {
           const ipc::ExpandedPrincipalInfo& info =
             aPrincipalInfo.get_ExpandedPrincipalInfo();
 
           aString.Assign("[Expanded Principal [");
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -1091,17 +1091,17 @@ tags = webvtt
 skip-if = toolkit == 'android' # android(bug 1232305)
 tags = webvtt
 [test_trackevent.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 tags = webvtt
 [test_unseekable.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 [test_video_to_canvas.html]
-skip-if = android_version == '15' || android_version == '17' || (android_version == '19' && debug) || android_version == '22' # bug 1320418, android(bug 1232305)
+skip-if = toolkit == 'android' # android(bug 1232305), bugs 1320418,1347953,1347954,1348140,1348386
 [test_video_in_audio_element.html]
 skip-if = android_version == '15' || android_version == '17' # bug 1320417, 1326326, android(bug 1232323, bug 1232305)
 [test_videoDocumentTitle.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 [test_VideoPlaybackQuality.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 [test_VideoPlaybackQuality_disabled.html]
 skip-if = android_version == '15' || android_version == '17' # android(bug 1232305)
--- a/dom/media/tests/mochitest/test_enumerateDevices.html
+++ b/dom/media/tests/mochitest/test_enumerateDevices.html
@@ -112,18 +112,17 @@ runTest(async () => {
   is(differentOriginDevices.length, devices.length);
   [...sameOriginDevices, ...differentOriginDevices].forEach(d => validateDevice(d));
 
   for (let device of sameOriginDevices) {
     ok(devices.find(d => d.deviceId == device.deviceId),
        "Same origin deviceId for " + device.label + " must match");
   }
   for (let device of differentOriginDevices) {
-    // TODO: s/todo/ok/ once bug 1340163 is fixed.
-    todo(!devices.find(d => d.deviceId == device.deviceId),
+    ok(!devices.find(d => d.deviceId == device.deviceId),
          "Different origin deviceId for " + device.label + " must be different");
   }
 
   // Check the special case of no devices found.
   await pushPrefs(["media.navigator.streams.fake", false],
                   ["media.audio_loopback_dev", "none"],
                   ["media.video_loopback_dev", "none"]);
   devices = await navigator.mediaDevices.enumerateDevices();
--- a/dom/quota/ActorsChild.cpp
+++ b/dom/quota/ActorsChild.cpp
@@ -267,23 +267,28 @@ QuotaRequestChild::Recv__delete__(const 
       HandleResponse(aResponse.get_nsresult());
       break;
 
     case RequestResponse::TInitResponse:
     case RequestResponse::TClearOriginResponse:
     case RequestResponse::TClearDataResponse:
     case RequestResponse::TClearAllResponse:
     case RequestResponse::TResetAllResponse:
+    case RequestResponse::TPersistResponse:
       HandleResponse();
       break;
 
     case RequestResponse::TInitOriginResponse:
       HandleResponse(aResponse.get_InitOriginResponse().created());
       break;
 
+    case RequestResponse::TPersistedResponse:
+      HandleResponse(aResponse.get_PersistedResponse().persisted());
+      break;
+
     default:
       MOZ_CRASH("Unknown response type!");
   }
 
   return IPC_OK();
 }
 
 } // namespace quota
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -1250,16 +1250,73 @@ private:
 
   nsresult
   DoInitOnMainThread() override;
 
   void
   GetResponse(RequestResponse& aResponse) override;
 };
 
+class PersistRequestBase
+  : public QuotaRequestBase
+{
+  const PrincipalInfo mPrincipalInfo;
+
+protected:
+  nsCString mSuffix;
+  nsCString mGroup;
+
+public:
+  bool
+  Init(Quota* aQuota) override;
+
+protected:
+  explicit PersistRequestBase(const PrincipalInfo& aPrincipalInfo);
+
+private:
+  nsresult
+  DoInitOnMainThread() override;
+};
+
+class PersistedOp final
+  : public PersistRequestBase
+{
+  bool mPersisted;
+
+public:
+  explicit PersistedOp(const RequestParams& aParams);
+
+private:
+  ~PersistedOp()
+  { }
+
+  nsresult
+  DoDirectoryWork(QuotaManager* aQuotaManager) override;
+
+  void
+  GetResponse(RequestResponse& aResponse) override;
+};
+
+class PersistOp final
+  : public PersistRequestBase
+{
+public:
+  explicit PersistOp(const RequestParams& aParams);
+
+private:
+  ~PersistOp()
+  { }
+
+  nsresult
+  DoDirectoryWork(QuotaManager* aQuotaManager) override;
+
+  void
+  GetResponse(RequestResponse& aResponse) override;
+};
+
 /*******************************************************************************
  * Helper Functions
  ******************************************************************************/
 
 template <typename T, bool = mozilla::IsUnsigned<T>::value>
 struct IntChecker
 {
   static void
@@ -1847,16 +1904,62 @@ EnsureDirectory(nsIFile* aDirectory, boo
     NS_ENSURE_SUCCESS(rv, rv);
 
     *aCreated = true;
   }
 
   return NS_OK;
 }
 
+nsresult
+EnsureOriginDirectory(nsIFile* aDirectory, bool* aCreated)
+{
+  AssertIsOnIOThread();
+
+  nsresult rv;
+
+#ifndef RELEASE_OR_BETA
+  bool exists;
+  rv = aDirectory->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!exists) {
+    nsString leafName;
+    nsresult rv = aDirectory->GetLeafName(leafName);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (!leafName.EqualsLiteral(kChromeOrigin)) {
+      nsCString spec;
+      OriginAttributes attrs;
+      OriginParser::ResultType result =
+        OriginParser::ParseOrigin(NS_ConvertUTF16toUTF8(leafName),
+                                  spec,
+                                  &attrs);
+      if (NS_WARN_IF(result != OriginParser::ValidOrigin)) {
+        QM_WARNING("Preventing creation of a new origin directory which is not "
+                   "supported by our origin parser or is obsolete!");
+
+        return NS_ERROR_FAILURE;
+      }
+    }
+  }
+#endif
+
+  rv = EnsureDirectory(aDirectory, aCreated);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
 enum FileFlag {
   kTruncateFileFlag,
   kUpdateFileFlag,
   kAppendFileFlag
 };
 
 nsresult
 GetOutputStream(nsIFile* aFile,
@@ -2072,16 +2175,17 @@ CreateDirectoryMetadata(nsIFile* aDirect
   }
 
   return NS_OK;
 }
 
 nsresult
 CreateDirectoryMetadata2(nsIFile* aDirectory,
                          int64_t aTimestamp,
+                         bool aPersisted,
                          const nsACString& aSuffix,
                          const nsACString& aGroup,
                          const nsACString& aOrigin)
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(aDirectory);
 
   nsCOMPtr<nsIFile> file;
@@ -2103,18 +2207,17 @@ CreateDirectoryMetadata2(nsIFile* aDirec
 
   MOZ_ASSERT(stream);
 
   rv = stream->Write64(aTimestamp);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  // Reserved for navigator.persist()
-  rv = stream->WriteBoolean(false);
+  rv = stream->WriteBoolean(aPersisted);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Reserved data 1
   rv = stream->Write32(0);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -2164,16 +2267,53 @@ CreateDirectoryMetadata2(nsIFile* aDirec
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 nsresult
+CreateDirectoryMetadataFiles(nsIFile* aDirectory,
+                             bool aPersisted,
+                             const nsACString& aSuffix,
+                             const nsACString& aGroup,
+                             const nsACString& aOrigin,
+                             int64_t* aTimestamp)
+{
+  AssertIsOnIOThread();
+
+  int64_t timestamp = PR_Now();
+
+  nsresult rv = CreateDirectoryMetadata(aDirectory,
+                                        timestamp,
+                                        aSuffix,
+                                        aGroup,
+                                        aOrigin);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = CreateDirectoryMetadata2(aDirectory,
+                                timestamp,
+                                aPersisted,
+                                aSuffix,
+                                aGroup,
+                                aOrigin);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (aTimestamp) {
+    *aTimestamp = timestamp;
+  }
+  return NS_OK;
+}
+
+nsresult
 GetBinaryInputStream(nsIFile* aDirectory,
                      const nsAString& aFilename,
                      nsIBinaryInputStream** aStream)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aDirectory);
   MOZ_ASSERT(aStream);
 
@@ -3507,16 +3647,18 @@ QuotaManager::UpdateOriginAccessTime(Per
 
     op->RunImmediately();
   }
 }
 
 void
 QuotaManager::RemoveQuota()
 {
+  AssertIsOnIOThread();
+
   MutexAutoLock lock(mQuotaMutex);
 
   for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
     nsAutoPtr<GroupInfoPair>& pair = iter.Data();
 
     MOZ_ASSERT(!iter.Key().IsEmpty(), "Empty key!");
     MOZ_ASSERT(pair, "Null pointer!");
 
@@ -3632,16 +3774,50 @@ QuotaManager::GetQuotaObject(Persistence
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   rv = file->InitWithPath(aPath);
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   return GetQuotaObject(aPersistenceType, aGroup, aOrigin, file);
 }
 
+Nullable<bool>
+QuotaManager::OriginPersisted(const nsACString& aGroup,
+                              const nsACString& aOrigin)
+{
+  AssertIsOnIOThread();
+
+  MutexAutoLock lock(mQuotaMutex);
+
+  RefPtr<OriginInfo> originInfo = LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT,
+                                                      aGroup,
+                                                      aOrigin);
+  if (originInfo) {
+    return Nullable<bool>(originInfo->LockedPersisted());
+  }
+
+  return Nullable<bool>();
+}
+
+void
+QuotaManager::PersistOrigin(const nsACString& aGroup,
+                            const nsACString& aOrigin)
+{
+  AssertIsOnIOThread();
+
+  MutexAutoLock lock(mQuotaMutex);
+
+  RefPtr<OriginInfo> originInfo = LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT,
+                                                      aGroup,
+                                                      aOrigin);
+  if (originInfo && !originInfo->LockedPersisted()) {
+    originInfo->LockedPersist();
+  }
+}
+
 void
 QuotaManager::AbortOperationsForProcess(ContentParentId aContentParentId)
 {
   AssertIsOnOwningThread();
 
   for (RefPtr<Client>& client : mClients) {
     client->AbortOperationsForProcess(aContentParentId);
   }
@@ -3686,23 +3862,25 @@ QuotaManager::RestoreDirectoryMetadata2(
   }
 
   return NS_OK;
 }
 
 nsresult
 QuotaManager::GetDirectoryMetadata2(nsIFile* aDirectory,
                                     int64_t* aTimestamp,
+                                    bool* aPersisted,
                                     nsACString& aSuffix,
                                     nsACString& aGroup,
                                     nsACString& aOrigin)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aDirectory);
   MOZ_ASSERT(aTimestamp);
+  MOZ_ASSERT(aPersisted);
   MOZ_ASSERT(mStorageInitialized);
 
   nsCOMPtr<nsIBinaryInputStream> binaryStream;
   nsresult rv = GetBinaryInputStream(aDirectory,
                                      NS_LITERAL_STRING(METADATA_V2_FILE_NAME),
                                      getter_AddRefs(binaryStream));
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -3745,93 +3923,113 @@ QuotaManager::GetDirectoryMetadata2(nsIF
   // Currently unused (used to be isApp).
   bool dummy;
   rv = binaryStream->ReadBoolean(&dummy);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   *aTimestamp = timestamp;
+  *aPersisted = persisted;
   aSuffix = suffix;
   aGroup = group;
   aOrigin = origin;
   return NS_OK;
 }
 
 nsresult
 QuotaManager::GetDirectoryMetadata2WithRestore(nsIFile* aDirectory,
                                                bool aPersistent,
                                                int64_t* aTimestamp,
+                                               bool* aPersisted,
                                                nsACString& aSuffix,
                                                nsACString& aGroup,
                                                nsACString& aOrigin)
 {
   nsresult rv = GetDirectoryMetadata2(aDirectory,
                                       aTimestamp,
+                                      aPersisted,
                                       aSuffix,
                                       aGroup,
                                       aOrigin);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     rv = RestoreDirectoryMetadata2(aDirectory, aPersistent);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     rv = GetDirectoryMetadata2(aDirectory,
                                aTimestamp,
+                               aPersisted,
                                aSuffix,
                                aGroup,
                                aOrigin);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   return NS_OK;
 }
 
 nsresult
-QuotaManager::GetDirectoryMetadata2(nsIFile* aDirectory, int64_t* aTimestamp)
+QuotaManager::GetDirectoryMetadata2(nsIFile* aDirectory,
+                                    int64_t* aTimestamp,
+                                    bool* aPersisted)
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(aDirectory);
-  MOZ_ASSERT(aTimestamp);
+  MOZ_ASSERT(aTimestamp || aPersisted);
   MOZ_ASSERT(mStorageInitialized);
 
   nsCOMPtr<nsIBinaryInputStream> binaryStream;
   nsresult rv = GetBinaryInputStream(aDirectory,
                                      NS_LITERAL_STRING(METADATA_V2_FILE_NAME),
                                      getter_AddRefs(binaryStream));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   uint64_t timestamp;
   rv = binaryStream->Read64(&timestamp);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  *aTimestamp = timestamp;
+  bool persisted;
+  if (aPersisted) {
+    rv = binaryStream->ReadBoolean(&persisted);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  if (aTimestamp) {
+    *aTimestamp = timestamp;
+  }
+  if (aPersisted) {
+    *aPersisted = persisted;
+  }
   return NS_OK;
 }
 
 nsresult
 QuotaManager::GetDirectoryMetadata2WithRestore(nsIFile* aDirectory,
                                                bool aPersistent,
-                                               int64_t* aTimestamp)
-{
-  nsresult rv = GetDirectoryMetadata2(aDirectory, aTimestamp);
+                                               int64_t* aTimestamp,
+                                               bool* aPersisted)
+{
+  nsresult rv = GetDirectoryMetadata2(aDirectory, aTimestamp, aPersisted);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     rv = RestoreDirectoryMetadata2(aDirectory, aPersistent);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    rv = GetDirectoryMetadata2(aDirectory, aTimestamp);
+    rv = GetDirectoryMetadata2(aDirectory, aTimestamp, aPersisted);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   return NS_OK;
 }
 
@@ -3894,31 +4092,33 @@ QuotaManager::InitializeRepository(Persi
         continue;
       }
 
       UNKNOWN_FILE_WARNING(leafName);
       return NS_ERROR_UNEXPECTED;
     }
 
     int64_t timestamp;
+    bool persisted;
     nsCString suffix;
     nsCString group;
     nsCString origin;
     rv = GetDirectoryMetadata2WithRestore(childDirectory,
                                           /* aPersistent */ false,
                                           &timestamp,
+                                          &persisted,
                                           suffix,
                                           group,
                                           origin);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    rv = InitializeOrigin(aPersistenceType, group, origin, timestamp,
-                          /* aPersisted */ false, childDirectory);
+    rv = InitializeOrigin(aPersistenceType, group, origin, timestamp, persisted,
+                          childDirectory);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
@@ -4815,107 +5015,62 @@ QuotaManager::EnsureOriginIsInitializedI
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     mTemporaryStorageInitialized = true;
 
     CheckTemporaryStorageLimits();
   }
 
+  bool created;
+  rv = EnsureOriginDirectory(directory, &created);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   int64_t timestamp;
-
-#ifndef RELEASE_OR_BETA
-  bool exists;
-  rv = directory->Exists(&exists);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  if (!exists) {
-    nsString leafName;
-    nsresult rv = directory->GetLeafName(leafName);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    if (!leafName.EqualsLiteral(kChromeOrigin)) {
-      nsCString spec;
-      OriginAttributes attrs;
-      OriginParser::ResultType result =
-        OriginParser::ParseOrigin(NS_ConvertUTF16toUTF8(leafName),
-                                  spec,
-                                  &attrs);
-      if (NS_WARN_IF(result != OriginParser::ValidOrigin)) {
-        QM_WARNING("Preventing creation of a new origin directory which is not "
-                   "supported by our origin parser or is obsolete!");
-
-        return NS_ERROR_FAILURE;
-      }
-    }
-  }
-#endif
-
-  bool created;
-  rv = EnsureDirectory(directory, &created);
-  NS_ENSURE_SUCCESS(rv, rv);
-
   if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
     if (created) {
-      timestamp = PR_Now();
-
-      rv = CreateDirectoryMetadata(directory,
-                                   timestamp,
-                                   aSuffix,
-                                   aGroup,
-                                   aOrigin);
+      rv = CreateDirectoryMetadataFiles(directory,
+                                        /* aPersisted */ true,
+                                        aSuffix,
+                                        aGroup,
+                                        aOrigin,
+                                        &timestamp);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
-
-      rv = CreateDirectoryMetadata2(directory,
-                                    timestamp,
-                                    aSuffix,
-                                    aGroup,
-                                    aOrigin);
-      NS_ENSURE_SUCCESS(rv, rv);
     } else {
       rv = GetDirectoryMetadata2WithRestore(directory,
                                             /* aPersistent */ true,
-                                            &timestamp);
+                                            &timestamp,
+                                            /* aPersisted */ nullptr);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       MOZ_ASSERT(timestamp <= PR_Now());
     }
 
     rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, timestamp,
                           /* aPersisted */ true, directory);
     NS_ENSURE_SUCCESS(rv, rv);
 
     mInitializedOrigins.AppendElement(aOrigin);
   } else if (created) {
-    timestamp = PR_Now();
-
-    rv = CreateDirectoryMetadata(directory,
-                                 timestamp,
-                                 aSuffix,
-                                 aGroup,
-                                 aOrigin);
+    rv = CreateDirectoryMetadataFiles(directory,
+                                      /* aPersisted */ false,
+                                      aSuffix,
+                                      aGroup,
+                                      aOrigin,
+                                      &timestamp);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    rv = CreateDirectoryMetadata2(directory,
-                                  timestamp,
-                                  aSuffix,
-                                  aGroup,
-                                  aOrigin);
-    NS_ENSURE_SUCCESS(rv, rv);
-
     rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, timestamp,
                           /* aPersisted */ false, directory);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   directory.forget(aDirectory);
   *aCreated = created;
   return NS_OK;
@@ -5223,16 +5378,35 @@ QuotaManager::LockedRemoveQuotaForOrigin
 
       if (!pair->LockedHasGroupInfos()) {
         mGroupInfoPairs.Remove(aGroup);
       }
     }
   }
 }
 
+already_AddRefed<OriginInfo>
+QuotaManager::LockedGetOriginInfo(PersistenceType aPersistenceType,
+                                  const nsACString& aGroup,
+                                  const nsACString& aOrigin)
+{
+  mQuotaMutex.AssertCurrentThreadOwns();
+  MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+
+  GroupInfoPair* pair;
+  if (mGroupInfoPairs.Get(aGroup, &pair)) {
+    RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
+    if (groupInfo) {
+      return groupInfo->LockedGetOriginInfo(aOrigin);
+    }
+  }
+
+  return nullptr;
+}
+
 void
 QuotaManager::CheckTemporaryStorageLimits()
 {
   AssertIsOnIOThread();
 
   nsTArray<OriginInfo*> doomedOriginInfos;
   {
     MutexAutoLock lock(mQuotaMutex);
@@ -6130,16 +6304,24 @@ Quota::AllocPQuotaRequestParent(const Re
     case RequestParams::TClearAllParams:
       actor = new ResetOrClearOp(/* aClear */ true);
       break;
 
     case RequestParams::TResetAllParams:
       actor = new ResetOrClearOp(/* aClear */ false);
       break;
 
+    case RequestParams::TPersistedParams:
+      actor = new PersistedOp(aParams);
+      break;
+
+    case RequestParams::TPersistParams:
+      actor = new PersistOp(aParams);
+      break;
+
     default:
       MOZ_CRASH("Should never get here!");
   }
 
   MOZ_ASSERT(actor);
 
   // Transfer ownership to IPDL.
   return actor.forget().take();
@@ -6812,19 +6994,21 @@ ClearRequestBase::DeleteFiles(QuotaManag
     }
 
     bool persistent = aPersistenceType == PERSISTENCE_TYPE_PERSISTENT;
 
     int64_t timestamp;
     nsCString suffix;
     nsCString group;
     nsCString origin;
+    bool persisted;
     rv = aQuotaManager->GetDirectoryMetadata2WithRestore(file,
                                                          persistent,
                                                          &timestamp,
+                                                         &persisted,
                                                          suffix,
                                                          group,
                                                          origin);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return;
     }
 
     for (uint32_t index = 0; index < 10; index++) {
@@ -6976,16 +7160,245 @@ ClearDataOp::DoInitOnMainThread()
 void
 ClearDataOp::GetResponse(RequestResponse& aResponse)
 {
   AssertIsOnOwningThread();
 
   aResponse = ClearDataResponse();
 }
 
+PersistRequestBase::PersistRequestBase(const PrincipalInfo& aPrincipalInfo)
+  : QuotaRequestBase(/* aExclusive */ false)
+  , mPrincipalInfo(aPrincipalInfo)
+{
+  AssertIsOnOwningThread();
+}
+
+bool
+PersistRequestBase::Init(Quota* aQuota)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aQuota);
+
+  if (NS_WARN_IF(!QuotaRequestBase::Init(aQuota))) {
+    return false;
+  }
+
+  mPersistenceType.SetValue(PERSISTENCE_TYPE_DEFAULT);
+
+  mNeedsMainThreadInit = true;
+
+  return true;
+}
+
+nsresult
+PersistRequestBase::DoInitOnMainThread()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(GetState() == State_Initializing);
+  MOZ_ASSERT(mNeedsMainThreadInit);
+
+  nsresult rv;
+  nsCOMPtr<nsIPrincipal> principal =
+    PrincipalInfoToPrincipal(mPrincipalInfo, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Figure out which origin we're dealing with.
+  nsCString origin;
+  rv = QuotaManager::GetInfoFromPrincipal(principal, &mSuffix, &mGroup,
+                                          &origin);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mOriginScope.SetFromOrigin(origin);
+
+  return NS_OK;
+}
+
+PersistedOp::PersistedOp(const RequestParams& aParams)
+  : PersistRequestBase(aParams.get_PersistedParams().principalInfo())
+  , mPersisted(false)
+{
+  MOZ_ASSERT(aParams.type() == RequestParams::TPersistedParams);
+}
+
+nsresult
+PersistedOp::DoDirectoryWork(QuotaManager* aQuotaManager)
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(!mPersistenceType.IsNull());
+  MOZ_ASSERT(mPersistenceType.Value() == PERSISTENCE_TYPE_DEFAULT);
+  MOZ_ASSERT(mOriginScope.IsOrigin());
+
+  PROFILER_LABEL("Quota", "PersistedOp::DoDirectoryWork",
+                 js::ProfileEntry::Category::OTHER);
+
+  Nullable<bool> persisted =
+    aQuotaManager->OriginPersisted(mGroup, mOriginScope.GetOrigin());
+
+  if (!persisted.IsNull()) {
+    mPersisted = persisted.Value();
+    return NS_OK;
+  }
+
+  // If we get here, it means the origin hasn't been initialized yet.
+  // Try to get the persisted flag from directory metadata on disk.
+
+  nsCOMPtr<nsIFile> directory;
+  nsresult rv = aQuotaManager->GetDirectoryForOrigin(mPersistenceType.Value(),
+                                                     mOriginScope.GetOrigin(),
+                                                     getter_AddRefs(directory));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  bool exists;
+  rv = directory->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (exists) {
+    // Get the persisted flag.
+    bool persisted;
+    rv =
+      aQuotaManager->GetDirectoryMetadata2WithRestore(directory,
+                                                      /* aPersistent */ false,
+                                                      /* aTimestamp */ nullptr,
+                                                      &persisted);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    mPersisted = persisted;
+  } else {
+    // The directory has not been created yet.
+    mPersisted = false;
+  }
+
+  return NS_OK;
+}
+
+void
+PersistedOp::GetResponse(RequestResponse& aResponse)
+{
+  AssertIsOnOwningThread();
+
+  PersistedResponse persistedResponse;
+  persistedResponse.persisted() = mPersisted;
+
+  aResponse = persistedResponse;
+}
+
+PersistOp::PersistOp(const RequestParams& aParams)
+  : PersistRequestBase(aParams.get_PersistParams().principalInfo())
+{
+  MOZ_ASSERT(aParams.type() == RequestParams::TPersistParams);
+}
+
+nsresult
+PersistOp::DoDirectoryWork(QuotaManager* aQuotaManager)
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(!mPersistenceType.IsNull());
+  MOZ_ASSERT(mPersistenceType.Value() == PERSISTENCE_TYPE_DEFAULT);
+  MOZ_ASSERT(mOriginScope.IsOrigin());
+
+  PROFILER_LABEL("Quota", "PersistOp::DoDirectoryWork",
+                 js::ProfileEntry::Category::OTHER);
+
+  // Update directory metadata on disk first.
+  nsCOMPtr<nsIFile> directory;
+  nsresult rv = aQuotaManager->GetDirectoryForOrigin(mPersistenceType.Value(),
+                                                     mOriginScope.GetOrigin(),
+                                                     getter_AddRefs(directory));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  bool created;
+  rv = EnsureOriginDirectory(directory, &created);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (created) {
+    rv = CreateDirectoryMetadataFiles(directory,
+                                      /* aPersisted */ true,
+                                      mSuffix,
+                                      mGroup,
+                                      mOriginScope.GetOrigin(),
+                                      /* aTimestamp */ nullptr);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  } else {
+    // Get the persisted flag (restore the metadata file if necessary).
+    bool persisted;
+    rv =
+      aQuotaManager->GetDirectoryMetadata2WithRestore(directory,
+                                                      /* aPersistent */ false,
+                                                      /* aTimestamp */ nullptr,
+                                                      &persisted);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (!persisted) {
+      nsCOMPtr<nsIFile> file;
+      nsresult rv = directory->Clone(getter_AddRefs(file));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      rv = file->Append(NS_LITERAL_STRING(METADATA_V2_FILE_NAME));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      nsCOMPtr<nsIBinaryOutputStream> stream;
+      rv = GetBinaryOutputStream(file, kUpdateFileFlag, getter_AddRefs(stream));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      MOZ_ASSERT(stream);
+
+      // Update origin access time while we are here.
+      rv = stream->Write64(PR_Now());
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      // Set the persisted flag to true.
+      rv = stream->WriteBoolean(true);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    }
+  }
+
+  // Directory metadata has been successfully created/updated, try to update
+  // OriginInfo too (it's ok if OriginInfo doesn't exist yet).
+  aQuotaManager->PersistOrigin(mGroup, mOriginScope.GetOrigin());
+
+  return NS_OK;
+}
+
+void
+PersistOp::GetResponse(RequestResponse& aResponse)
+{
+  AssertIsOnOwningThread();
+
+  aResponse = PersistResponse();
+}
+
 nsresult
 StorageDirectoryHelper::GetDirectoryMetadata(nsIFile* aDirectory,
                                              int64_t& aTimestamp,
                                              nsACString& aGroup,
                                              nsACString& aOrigin,
                                              Nullable<bool>& aIsApp)
 {
   AssertIsOnIOThread();
@@ -8120,16 +8533,17 @@ UpgradeStorageFrom0_0To1_0Helper::Proces
                                  aOriginProps.mOrigin);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   rv = CreateDirectoryMetadata2(aOriginProps.mDirectory,
                                 aOriginProps.mTimestamp,
+                                /* aPersisted */ false,
                                 aOriginProps.mSuffix,
                                 aOriginProps.mGroup,
                                 aOriginProps.mOrigin);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsString oldName;
@@ -8402,16 +8816,17 @@ UpgradeStorageFrom1_0To2_0Helper::MaybeS
                                         aOriginProps.mGroup,
                                         aOriginProps.mOrigin);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = CreateDirectoryMetadata2(aOriginProps.mDirectory,
                                 aOriginProps.mTimestamp,
+                                /* aPersisted */ false,
                                 aOriginProps.mSuffix,
                                 aOriginProps.mGroup,
                                 aOriginProps.mOrigin);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsCOMPtr<nsIFile> newFile;
@@ -8473,16 +8888,17 @@ UpgradeStorageFrom1_0To2_0Helper::Proces
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   if (aOriginProps.mNeedsRestore2) {
     rv = CreateDirectoryMetadata2(aOriginProps.mDirectory,
                                   aOriginProps.mTimestamp,
+                                  /* aPersisted */ false,
                                   aOriginProps.mSuffix,
                                   aOriginProps.mGroup,
                                   aOriginProps.mOrigin);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
@@ -8515,18 +8931,20 @@ RestoreDirectoryMetadata2Helper::Restore
 }
 
 nsresult
 RestoreDirectoryMetadata2Helper::ProcessOriginDirectory(
                                                 const OriginProps& aOriginProps)
 {
   AssertIsOnIOThread();
 
+  // We don't have any approach to restore aPersisted, so reset it to false.
   nsresult rv = CreateDirectoryMetadata2(aOriginProps.mDirectory,
                                          aOriginProps.mTimestamp,
+                                         /* aPersisted */ false,
                                          aOriginProps.mSuffix,
                                          aOriginProps.mGroup,
                                          aOriginProps.mOrigin);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
--- a/dom/quota/PQuota.ipdl
+++ b/dom/quota/PQuota.ipdl
@@ -54,24 +54,36 @@ struct ClearDataParams
 struct ClearAllParams
 {
 };
 
 struct ResetAllParams
 {
 };
 
+struct PersistedParams
+{
+  PrincipalInfo principalInfo;
+};
+
+struct PersistParams
+{
+  PrincipalInfo principalInfo;
+};
+
 union RequestParams
 {
   InitParams;
   InitOriginParams;
   ClearOriginParams;
   ClearDataParams;
   ClearAllParams;
   ResetAllParams;
+  PersistedParams;
+  PersistParams;
 };
 
 protocol PQuota
 {
   manager PBackground;
 
   manages PQuotaRequest;
   manages PQuotaUsageRequest;
--- a/dom/quota/PQuotaRequest.ipdl
+++ b/dom/quota/PQuotaRequest.ipdl
@@ -28,25 +28,36 @@ struct ClearDataResponse
 struct ClearAllResponse
 {
 };
 
 struct ResetAllResponse
 {
 };
 
+struct PersistedResponse
+{
+  bool persisted;
+};
+
+struct PersistResponse
+{
+};
+
 union RequestResponse
 {
   nsresult;
   InitResponse;
   InitOriginResponse;
   ClearOriginResponse;
   ClearDataResponse;
   ClearAllResponse;
   ResetAllResponse;
+  PersistedResponse;
+  PersistResponse;
 };
 
 protocol PQuotaRequest
 {
   manager PQuota;
 
 child:
   async __delete__(RequestResponse response);
--- a/dom/quota/QuotaManager.h
+++ b/dom/quota/QuotaManager.h
@@ -180,51 +180,64 @@ public:
                  nsIFile* aFile);
 
   already_AddRefed<QuotaObject>
   GetQuotaObject(PersistenceType aPersistenceType,
                  const nsACString& aGroup,
                  const nsACString& aOrigin,
                  const nsAString& aPath);
 
+  Nullable<bool>
+  OriginPersisted(const nsACString& aGroup,
+                  const nsACString& aOrigin);
+
+  void
+  PersistOrigin(const nsACString& aGroup,
+                const nsACString& aOrigin);
+
   // Called when a process is being shot down. Aborts any running operations
   // for the given process.
   void
   AbortOperationsForProcess(ContentParentId aContentParentId);
 
   nsresult
   GetDirectoryForOrigin(PersistenceType aPersistenceType,
                         const nsACString& aASCIIOrigin,
                         nsIFile** aDirectory) const;
 
   nsresult
   RestoreDirectoryMetadata2(nsIFile* aDirectory, bool aPersistent);
 
   nsresult
   GetDirectoryMetadata2(nsIFile* aDirectory,
                         int64_t* aTimestamp,
+                        bool* aPersisted,
                         nsACString& aSuffix,
                         nsACString& aGroup,
                         nsACString& aOrigin);
 
   nsresult
   GetDirectoryMetadata2WithRestore(nsIFile* aDirectory,
                                    bool aPersistent,
                                    int64_t* aTimestamp,
+                                   bool* aPersisted,
                                    nsACString& aSuffix,
                                    nsACString& aGroup,
                                    nsACString& aOrigin);
 
   nsresult
-  GetDirectoryMetadata2(nsIFile* aDirectory, int64_t* aTimestamp);
+  GetDirectoryMetadata2(nsIFile* aDirectory,
+                        int64_t* aTimestamp,
+                        bool* aPersisted);
 
   nsresult
   GetDirectoryMetadata2WithRestore(nsIFile* aDirectory,
                                    bool aPersistent,
-                                   int64_t* aTimestamp);
+                                   int64_t* aTimestamp,
+                                   bool* aPersisted);
 
   // This is the main entry point into the QuotaManager API.
   // Any storage API implementation (quota client) that participates in
   // centralized quota and storage handling should call this method to get
   // a directory lock which will protect client's files from being deleted
   // while they are still in use.
   // After a lock is acquired, client is notified via the open listener's
   // method DirectoryLockAcquired. If the lock couldn't be acquired, client
@@ -433,16 +446,21 @@ private:
                                  uint64_t aMinSizeToBeFreed,
                                  nsTArray<RefPtr<DirectoryLockImpl>>& aLocks);
 
   void
   LockedRemoveQuotaForOrigin(PersistenceType aPersistenceType,
                              const nsACString& aGroup,
                              const nsACString& aOrigin);
 
+  already_AddRefed<OriginInfo>
+  LockedGetOriginInfo(PersistenceType aPersistenceType,
+                      const nsACString& aGroup,
+                      const nsACString& aOrigin);
+
   nsresult
   MaybeUpgradeIndexedDBDirectory();
 
   nsresult
   MaybeUpgradePersistentStorageDirectory();
 
   nsresult
   MaybeRemoveOldDirectories();
--- a/dom/quota/QuotaManagerService.cpp
+++ b/dom/quota/QuotaManagerService.cpp
@@ -55,16 +55,35 @@ TestingPrefChangedCallback(const char* a
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!strcmp(aPrefName, kTestingPref));
   MOZ_ASSERT(!aClosure);
 
   gTestingMode = Preferences::GetBool(aPrefName);
 }
 
+nsresult
+CheckedPrincipalToPrincipalInfo(nsIPrincipal* aPrincipal,
+                                PrincipalInfo& aPrincipalInfo)
+{
+  MOZ_ASSERT(aPrincipal);
+
+  nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &aPrincipalInfo);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (aPrincipalInfo.type() != PrincipalInfo::TContentPrincipalInfo &&
+      aPrincipalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  return NS_OK;
+}
+
 class AbortOperationsRunnable final
   : public Runnable
 {
   ContentParentId mContentParentId;
 
 public:
   explicit AbortOperationsRunnable(ContentParentId aContentParentId)
     : mContentParentId(aContentParentId)
@@ -535,28 +554,22 @@ QuotaManagerService::InitStoragesForPrin
   if (NS_WARN_IF(!gTestingMode)) {
     return NS_ERROR_UNEXPECTED;
   }
 
   RefPtr<Request> request = new Request();
 
   InitOriginParams params;
 
-  PrincipalInfo& principalInfo = params.principalInfo();
-
-  nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+  nsresult rv = CheckedPrincipalToPrincipalInfo(aPrincipal,
+                                                params.principalInfo());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  if (principalInfo.type() != PrincipalInfo::TContentPrincipalInfo &&
-      principalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) {
-    return NS_ERROR_UNEXPECTED;
-  }
-
   Nullable<PersistenceType> persistenceType;
   rv = NullablePersistenceTypeFromText(aPersistenceType, &persistenceType);
   if (NS_WARN_IF(NS_FAILED(rv)) || persistenceType.IsNull()) {
     return NS_ERROR_INVALID_ARG;
   }
 
   params.persistenceType() = persistenceType.Value();
 
@@ -580,27 +593,22 @@ QuotaManagerService::GetUsageForPrincipa
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aPrincipal);
   MOZ_ASSERT(aCallback);
 
   RefPtr<UsageRequest> request = new UsageRequest(aPrincipal, aCallback);
 
   UsageParams params;
 
-  PrincipalInfo& principalInfo = params.principalInfo();
-  nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+  nsresult rv = CheckedPrincipalToPrincipalInfo(aPrincipal,
+                                                params.principalInfo());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  if (principalInfo.type() != PrincipalInfo::TContentPrincipalInfo &&
-      principalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) {
-    return NS_ERROR_UNEXPECTED;
-  }
-
   params.getGroupUsage() = aGetGroupUsage;
 
   nsAutoPtr<PendingRequestInfo> info(new UsageRequestInfo(request, params));
 
   rv = InitiateRequest(info);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -650,28 +658,22 @@ QuotaManagerService::ClearStoragesForPri
     // aClearAll flag is set.
     return NS_ERROR_INVALID_ARG;
   }
 
   RefPtr<Request> request = new Request(aPrincipal);
 
   ClearOriginParams params;
 
-  PrincipalInfo& principalInfo = params.principalInfo();
-
-  nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+  nsresult rv = CheckedPrincipalToPrincipalInfo(aPrincipal,
+                                                params.principalInfo());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  if (principalInfo.type() != PrincipalInfo::TContentPrincipalInfo &&
-      principalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) {
-    return NS_ERROR_UNEXPECTED;
-  }
-
   Nullable<PersistenceType> persistenceType;
   rv = NullablePersistenceTypeFromText(aPersistenceType, &persistenceType);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return NS_ERROR_INVALID_ARG;
   }
 
   if (persistenceType.IsNull()) {
     params.persistenceTypeIsExplicit() = false;
@@ -713,16 +715,74 @@ QuotaManagerService::Reset(nsIQuotaReque
     return rv;
   }
 
   request.forget(_retval);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+QuotaManagerService::Persisted(nsIPrincipal* aPrincipal,
+                               nsIQuotaRequest** _retval)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aPrincipal);
+  MOZ_ASSERT(_retval);
+
+  RefPtr<Request> request = new Request(aPrincipal);
+
+  PersistedParams params;
+
+  nsresult rv = CheckedPrincipalToPrincipalInfo(aPrincipal,
+                                                params.principalInfo());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params));
+
+  rv = InitiateRequest(info);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  request.forget(_retval);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+QuotaManagerService::Persist(nsIPrincipal* aPrincipal,
+                             nsIQuotaRequest** _retval)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aPrincipal);
+  MOZ_ASSERT(_retval);
+
+  RefPtr<Request> request = new Request(aPrincipal);
+
+  PersistParams params;
+
+  nsresult rv = CheckedPrincipalToPrincipalInfo(aPrincipal,
+                                                params.principalInfo());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params));
+
+  rv = InitiateRequest(info);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  request.forget(_retval);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 QuotaManagerService::Observe(nsISupports* aSubject,
                              const char* aTopic,
                              const char16_t* aData)
 {
   MOZ_ASSERT(XRE_IsParentProcess());
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)) {
--- a/dom/quota/nsIQuotaManagerService.idl
+++ b/dom/quota/nsIQuotaManagerService.idl
@@ -95,9 +95,27 @@ interface nsIQuotaManagerService : nsISu
    * overriding the temp storage limit has changed.
    * Be carefull, this invalidates all live storages!
    *
    * If the dom.quotaManager.testing preference is not true the call will be
    * a no-op.
    */
   [must_use] nsIQuotaRequest
   reset();
+
+  /**
+   * Check if given origin is persisted.
+   *
+   * @param aPrincipal
+   *        A principal for the origin which we want to check.
+   */
+  [must_use] nsIQuotaRequest
+  persisted(in nsIPrincipal aPrincipal);
+
+  /**
+   * Persist given origin.
+   *
+   * @param aPrincipal
+   *        A principal for the origin which we want to persist.
+   */
+  [must_use] nsIQuotaRequest
+  persist(in nsIPrincipal aPrincipal);
 };
--- a/dom/quota/test/unit/head.js
+++ b/dom/quota/test/unit/head.js
@@ -1,16 +1,17 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 const NS_OK = Cr.NS_OK;
+const NS_ERROR_FAILURE = Cr.NS_ERROR_FAILURE;
 const NS_ERROR_UNEXPECTED = Cr.NS_ERROR_UNEXPECTED;
 
 function is(a, b, msg)
 {
   do_check_eq(a, b, Components.stack.caller);
 }
 
 function ok(cond, msg)
@@ -141,16 +142,30 @@ function clearChromeOrigin(callback)
 function reset(callback)
 {
   let request = SpecialPowers._getQuotaManager().reset();
   request.callback = callback;
 
   return request;
 }
 
+function persist(principal, callback) {
+  let request = SpecialPowers._getQuotaManager().persist(principal);
+  request.callback = callback;
+
+  return request;
+}
+
+function persisted(principal, callback) {
+  let request = SpecialPowers._getQuotaManager().persisted(principal);
+  request.callback = callback;
+
+  return request;
+}
+
 function installPackage(packageName)
 {
   let directoryService = Cc["@mozilla.org/file/directory_service;1"]
                          .getService(Ci.nsIProperties);
 
   let currentDir = directoryService.get("CurWorkD", Ci.nsIFile);
 
   let packageFile = currentDir.clone();
@@ -232,16 +247,25 @@ function compareBuffers(buffer1, buffer2
   for (let i = 0; i < buffer1.byteLength; i++) {
     if (view1[i] != view2[i]) {
       return false;
     }
   }
   return true;
 }
 
+function getPersistedFromMetadata(readBuffer)
+{
+  const persistedPosition = 8; // Persisted state is stored in the 9th byte
+  let view =
+    readBuffer instanceof Uint8Array ? readBuffer : new Uint8Array(readBuffer);
+
+  return !!view[persistedPosition];
+}
+
 function grabUsageAndContinueHandler(request)
 {
   testGenerator.next(request.usage);
 }
 
 function getUsage(usageHandler)
 {
   let principal = Cc["@mozilla.org/systemprincipal;1"]
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/unit/test_persist.js
@@ -0,0 +1,150 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var testGenerator = testSteps();
+
+function* testSteps()
+{
+  const origins = [
+    {
+      url: "http://default.test.persist",
+      path: "storage/default/http+++default.test.persist",
+      persistence: "default"
+    },
+
+    {
+      url: "ftp://ftp.invalid.origin",
+      path: "storage/default/ftp+++ftp.invalid.origin",
+      persistence: "default"
+    },
+  ];
+
+  const metadataFileName = ".metadata-v2";
+
+  let principal = getPrincipal(origins[0].url);
+
+  info("Persisting an uninitialized origin");
+
+  // Origin directory doesn't exist yet, so only check the result for
+  // persisted().
+  let request = persisted(principal, continueToNextStepSync);
+  yield undefined;
+
+  ok(request.resultCode === NS_OK, "Persisted() succeeded");
+  ok(!request.result, "The origin is not persisted");
+
+  info("Verifying persist() does update the metadata");
+
+  request = persist(principal, continueToNextStepSync);
+  yield undefined;
+
+  ok(request.resultCode === NS_OK, "Persist() succeeded");
+
+  let originDir = getRelativeFile(origins[0].path);
+  let exists = originDir.exists();
+  ok(exists, "Origin directory does exist");
+
+  info("Reading out contents of metadata file");
+
+  let metadataFile = originDir.clone();
+  metadataFile.append(metadataFileName);
+
+  File.createFromNsIFile(metadataFile).then(grabArgAndContinueHandler);
+  let file = yield undefined;
+
+  let fileReader = new FileReader();
+  fileReader.onload = continueToNextStepSync;
+  fileReader.readAsArrayBuffer(file);
+  yield undefined;
+
+  let originPersisted = getPersistedFromMetadata(fileReader.result);
+  ok(originPersisted, "The origin is persisted");
+
+  info("Verifying persisted()");
+
+  request = persisted(principal, continueToNextStepSync);
+  yield undefined;
+
+  ok(request.resultCode === NS_OK, "Persisted() succeeded");
+  ok(request.result === originPersisted, "Persisted() concurs with metadata");
+
+  info("Clearing the origin");
+
+  // Clear the origin since we'll test the same directory again under different
+  // circumstances.
+  clearOrigin(principal, origins[0].persistence, continueToNextStepSync);
+  yield undefined;
+
+  info("Persisting an already initialized origin");
+
+  initOrigin(principal, origins[0].persistence, continueToNextStepSync);
+  yield undefined;
+
+  info("Reading out contents of metadata file");
+
+  fileReader = new FileReader();
+  fileReader.onload = continueToNextStepSync;
+  fileReader.readAsArrayBuffer(file);
+  yield undefined;
+
+  originPersisted = getPersistedFromMetadata(fileReader.result);
+  ok(!originPersisted, "The origin isn't persisted after clearing");
+
+  info("Verifying persisted()");
+
+  request = persisted(principal, continueToNextStepSync);
+  yield undefined;
+
+  ok(request.resultCode === NS_OK, "Persisted() succeeded");
+  ok(request.result === originPersisted, "Persisted() concurs with metadata");
+
+  info("Verifying persist() does update the metadata");
+
+  request = persist(principal, continueToNextStepSync);
+  yield undefined;
+
+  ok(request.resultCode === NS_OK, "Persist() succeeded");
+
+  info("Reading out contents of metadata file");
+
+  fileReader = new FileReader();
+  fileReader.onload = continueToNextStepSync;
+  fileReader.readAsArrayBuffer(file);
+  yield undefined;
+
+  originPersisted = getPersistedFromMetadata(fileReader.result);
+  ok(originPersisted, "The origin is persisted");
+
+  info("Verifying persisted()");
+
+  request = persisted(principal, continueToNextStepSync);
+  yield undefined;
+
+  ok(request.resultCode === NS_OK, "Persisted() succeeded");
+  ok(request.result === originPersisted, "Persisted() concurs with metadata");
+
+  info("Persisting an invalid origin");
+
+  let invalidPrincipal = getPrincipal(origins[1].url);
+
+  request = persist(invalidPrincipal, continueToNextStepSync);
+  yield undefined;
+
+  ok(request.resultCode === NS_ERROR_FAILURE,
+     "Persist() failed because of the invalid origin");
+
+  originDir = getRelativeFile(origins[1].path);
+  exists = originDir.exists();
+  ok(!exists, "Directory for invalid origin doesn't exist");
+
+  request = persisted(invalidPrincipal, continueToNextStepSync);
+  yield undefined;
+
+  ok(request.resultCode === NS_OK, "Persisted() succeeded");
+  ok(!request.result,
+     "The origin isn't persisted since the operation failed");
+
+  finishTest();
+}
--- a/dom/quota/test/unit/xpcshell.ini
+++ b/dom/quota/test/unit/xpcshell.ini
@@ -17,12 +17,13 @@ support-files =
   tempMetadataCleanup_profile.zip
 
 [test_basics.js]
 [test_defaultStorageUpgrade.js]
 [test_idbSubdirUpgrade.js]
 [test_morgueCleanup.js]
 [test_obsoleteOriginAttributesUpgrade.js]
 [test_originAttributesUpgrade.js]
+[test_persist.js]
 [test_removeAppsUpgrade.js]
 [test_storagePersistentUpgrade.js]
 [test_tempMetadataCleanup.js]
 [test_unknownFiles.js]
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -653,17 +653,17 @@ RespondWithHandler::ResolvedCallback(JSC
   nsCOMPtr<nsIInputStream> body;
   ir->GetUnfilteredBody(getter_AddRefs(body));
   // Errors and redirects may not have a body.
   if (body) {
     response->SetBodyUsed();
 
     nsCOMPtr<nsIOutputStream> responseBody;
     rv = mInterceptedChannel->GetResponseBody(getter_AddRefs(responseBody));
-    if (NS_WARN_IF(NS_FAILED(rv))) {
+    if (NS_WARN_IF(NS_FAILED(rv)) || !responseBody) {
       return;
     }
 
     const uint32_t kCopySegmentSize = 4096;
 
     // Depending on how the Response passed to .respondWith() was created, we may
     // get a non-buffered input stream.  In addition, in some configurations the
     // destination channel's output stream can be unbuffered.  We wrap the output
--- a/dom/workers/ServiceWorkerPrivate.cpp
+++ b/dom/workers/ServiceWorkerPrivate.cpp
@@ -1575,17 +1575,18 @@ private:
     }
 
     event->PostInit(mInterceptedChannel, mRegistration, mScriptSpec);
     event->SetTrusted(true);
 
     nsresult rv2 =
       DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(),
                                            event, nullptr);
-    if (NS_WARN_IF(NS_FAILED(rv2)) || !event->WaitToRespond()) {
+    if ((NS_WARN_IF(NS_FAILED(rv2)) && rv2 != NS_ERROR_XPC_JS_THREW_EXCEPTION) ||
+        !event->WaitToRespond()) {
       nsCOMPtr<nsIRunnable> runnable;
       MOZ_ASSERT(!aWorkerPrivate->UsesSystemPrincipal(),
                  "We don't support system-principal serviceworkers");
       if (event->DefaultPrevented(CallerType::NonSystem)) {
         runnable = new CancelChannelRunnable(mInterceptedChannel,
                                              mRegistration,
                                              NS_ERROR_INTERCEPTION_FAILED);
       } else {
--- a/dom/workers/ServiceWorkerRegistrar.cpp
+++ b/dom/workers/ServiceWorkerRegistrar.cpp
@@ -349,17 +349,17 @@ ServiceWorkerRegistrar::ReadData()
       OriginAttributes attrs;
       if (!attrs.PopulateFromSuffix(suffix)) {
         return NS_ERROR_INVALID_ARG;
       }
 
       GET_LINE(entry->scope());
 
       entry->principal() =
-        mozilla::ipc::ContentPrincipalInfo(attrs, entry->scope());
+        mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), entry->scope());
 
       GET_LINE(entry->currentWorkerURL());
 
       nsAutoCString fetchFlag;
       GET_LINE(fetchFlag);
       if (!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) &&
           !fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) {
         return NS_ERROR_INVALID_ARG;
@@ -390,17 +390,17 @@ ServiceWorkerRegistrar::ReadData()
       OriginAttributes attrs;
       if (!attrs.PopulateFromSuffix(suffix)) {
         return NS_ERROR_INVALID_ARG;
       }
 
       GET_LINE(entry->scope());
 
       entry->principal() =
-        mozilla::ipc::ContentPrincipalInfo(attrs, entry->scope());
+        mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), entry->scope());
 
       GET_LINE(entry->currentWorkerURL());
 
       nsAutoCString fetchFlag;
       GET_LINE(fetchFlag);
       if (!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) &&
           !fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) {
         return NS_ERROR_INVALID_ARG;
@@ -423,17 +423,17 @@ ServiceWorkerRegistrar::ReadData()
       OriginAttributes attrs;
       if (!attrs.PopulateFromSuffix(suffix)) {
         return NS_ERROR_INVALID_ARG;
       }
 
       GET_LINE(entry->scope());
 
       entry->principal() =
-        mozilla::ipc::ContentPrincipalInfo(attrs, entry->scope());
+        mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), entry->scope());
 
       GET_LINE(entry->currentWorkerURL());
 
       // default handlesFetch flag to Enabled
       entry->currentWorkerHandlesFetch() = true;
 
       nsAutoCString cacheName;
       GET_LINE(cacheName);
@@ -453,17 +453,17 @@ ServiceWorkerRegistrar::ReadData()
       }
 
       // principal spec is no longer used; we use scope directly instead
       GET_LINE(unused);
 
       GET_LINE(entry->scope());
 
       entry->principal() =
-        mozilla::ipc::ContentPrincipalInfo(attrs, entry->scope());
+        mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), entry->scope());
 
       GET_LINE(entry->currentWorkerURL());
 
       // default handlesFetch flag to Enabled
       entry->currentWorkerHandlesFetch() = true;
 
       nsAutoCString cacheName;
       GET_LINE(cacheName);
@@ -483,17 +483,17 @@ ServiceWorkerRegistrar::ReadData()
       }
 
       // principal spec is no longer used; we use scope directly instead
       GET_LINE(unused);
 
       GET_LINE(entry->scope());
 
       entry->principal() =
-        mozilla::ipc::ContentPrincipalInfo(attrs, entry->scope());
+        mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), entry->scope());
 
       // scriptSpec is no more used in latest version.
       GET_LINE(unused);
 
       GET_LINE(entry->currentWorkerURL());
 
       // default handlesFetch flag to Enabled
       entry->currentWorkerHandlesFetch() = true;
--- a/dom/workers/test/gtest/TestReadWrite.cpp
+++ b/dom/workers/test/gtest/TestReadWrite.cpp
@@ -238,17 +238,18 @@ TEST(ServiceWorkerRegistrar, TestWriteDa
       reg.currentWorkerHandlesFetch() = true;
       reg.cacheName() =
         NS_ConvertUTF8toUTF16(nsPrintfCString("cacheName write %d", i));
       reg.loadFlags() = nsIRequest::VALIDATE_ALWAYS;
 
       nsAutoCString spec;
       spec.AppendPrintf("spec write %d", i);
       reg.principal() =
-        mozilla::ipc::ContentPrincipalInfo(mozilla::OriginAttributes(i, i % 2), spec);
+        mozilla::ipc::ContentPrincipalInfo(mozilla::OriginAttributes(i, i % 2),
+                                           mozilla::void_t(), spec);
 
       swr->TestRegisterServiceWorker(reg);
     }
 
     nsresult rv = swr->TestWriteData();
     ASSERT_EQ(NS_OK, rv) << "WriteData() should not fail";
   }
 
@@ -592,17 +593,18 @@ TEST(ServiceWorkerRegistrar, TestDedupeW
       reg.currentWorkerHandlesFetch() = true;
       reg.cacheName() =
         NS_ConvertUTF8toUTF16(nsPrintfCString("cacheName write %d", i));
       reg.loadFlags() = nsIRequest::VALIDATE_ALWAYS;
 
       nsAutoCString spec;
       spec.AppendPrintf("spec write dedupe/%d", i);
       reg.principal() =
-        mozilla::ipc::ContentPrincipalInfo(mozilla::OriginAttributes(0, false), spec);
+        mozilla::ipc::ContentPrincipalInfo(mozilla::OriginAttributes(0, false),
+                                           mozilla::void_t(), spec);
 
       swr->TestRegisterServiceWorker(reg);
     }
 
     nsresult rv = swr->TestWriteData();
     ASSERT_EQ(NS_OK, rv) << "WriteData() should not fail";
   }
 
--- a/gfx/angle/src/compiler/translator/glslang_lex.cpp
+++ b/gfx/angle/src/compiler/translator/glslang_lex.cpp
@@ -2333,17 +2333,17 @@ static int yy_get_next_buffer (yyscan_t 
 	if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING )
 		/* don't do the read, it's not guaranteed to return an EOF,
 		 * just force an EOF
 		 */
 		YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0;
 
 	else
 		{
-			yy_size_t num_to_read =
+			int num_to_read =
 			YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1;
 
 		while ( num_to_read <= 0 )
 			{ /* Not enough room in the buffer - grow it. */
 
 			/* just a shorter name for the current buffer */
 			YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE;
 
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -1268,16 +1268,19 @@ nsEventStatus AsyncPanZoomController::On
     // what our final state is to avoid notification churn.
     StateChangeNotificationBlocker blocker(this);
     SetState(NOTHING);
 
     APZC_LOG("%p starting a fling animation if %f >= %f\n", this,
         flingVelocity.Length().value, gfxPrefs::APZFlingMinVelocityThreshold());
 
     if (flingVelocity.Length() < gfxPrefs::APZFlingMinVelocityThreshold()) {
+      // Relieve overscroll now if needed, since we will not transition to a fling
+      // animation and then an overscroll animation, and relieve it then.
+      GetCurrentTouchBlock()->GetOverscrollHandoffChain()->SnapBackOverscrolledApzc(this);
       return nsEventStatus_eConsumeNoDefault;
     }
 
     // Make a local copy of the tree manager pointer and check that it's not
     // null before calling DispatchFling(). This is necessary because Destroy(),
     // which nulls out mTreeManager, could be called concurrently.
     if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
       FlingHandoffState handoffState{flingVelocity,
--- a/gfx/layers/apz/test/gtest/TestBasic.cpp
+++ b/gfx/layers/apz/test/gtest/TestBasic.cpp
@@ -309,16 +309,33 @@ TEST_F(APZCBasicTester, OverScroll_Bug11
 
   // Sample the second overscroll animation to its end.
   // If the ending of the first overscroll animation fails to clear state
   // properly, this will assert.
   ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost());
   SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
 }
 
+// Tests that the page doesn't get stuck in an
+// overscroll animation after a low-velocity pan.
+TEST_F(APZCBasicTester, OverScrollAfterLowVelocityPan_Bug1343775) {
+  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
+
+  // Pan into overscroll with a velocity less than the
+  // apz.fling_min_velocity_threshold preference.
+  Pan(apzc, 10, 30);
+
+  EXPECT_TRUE(apzc->IsOverscrolled());
+
+  apzc->AdvanceAnimationsUntilEnd();
+
+  // Check that we recovered from overscroll.
+  EXPECT_FALSE(apzc->IsOverscrolled());
+}
+
 TEST_F(APZCBasicTester, OverScrollAbort) {
   SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
 
   // Pan sufficiently to hit overscroll behavior
   int touchStart = 500;
   int touchEnd = 10;
   Pan(apzc, touchStart, touchEnd);
   EXPECT_TRUE(apzc->IsOverscrolled());
--- a/image/imgFrame.h
+++ b/image/imgFrame.h
@@ -473,16 +473,19 @@ class DrawableFrameRef final
   typedef gfx::DataSourceSurface DataSourceSurface;
 
 public:
   DrawableFrameRef() { }
 
   explicit DrawableFrameRef(imgFrame* aFrame)
     : mFrame(aFrame)
   {
+    MOZ_ASSERT(aFrame);
+    MonitorAutoLock lock(aFrame->mMonitor);
+
     // Paletted images won't have a surface so there is no strong reference
     // to hold on to. Since Draw() and GetSourceSurface() calls will not work
     // in that case, we should be using RawAccessFrameRef exclusively instead.
     // See FrameAnimator::GetRawFrame for an example of this behaviour.
     if (aFrame->mRawSurface) {
       mRef = new DataSourceSurface::ScopedMap(aFrame->mRawSurface,
                                               DataSourceSurface::READ_WRITE);
       if (!mRef->IsMapped()) {
--- a/intl/locale/LocaleService.cpp
+++ b/intl/locale/LocaleService.cpp
@@ -458,17 +458,17 @@ LocaleService::NegotiateLanguages(const 
 {
   if (aStrategy < 0 || aStrategy > 2) {
     return NS_ERROR_INVALID_ARG;
   }
 
   // Check that the given string contains only ASCII characters valid in tags
   // (i.e. alphanumerics, plus '-' and '_'), and is non-empty.
   auto validTagChars = [](const char* s) {
-    if (!*s) {
+    if (!s || !*s) {
       return false;
     }
     while (*s) {
       if (isalnum((unsigned char)*s) || *s == '-' || *s == '_' || *s == '*') {
         s++;
       } else {
         return false;
       }
--- a/intl/locale/tests/unit/test_localeService_negotiateLanguages.js
+++ b/intl/locale/tests/unit/test_localeService_negotiateLanguages.js
@@ -81,16 +81,21 @@ const data = {
       [["fr_Fr"], ["fr-fR"], ["fr-fR"]],
       [["fr_lAtN_fr"], ["fr-Latn-FR"], ["fr-Latn-FR"]],
       [["fr_FR"], ["fr_FR"], ["fr_FR"]],
       [["fr-FR"], ["fr_FR"], ["fr_FR"]],
       [["fr_Cyrl_FR_mac"], ["fr_Cyrl_fr-mac"], ["fr_Cyrl_fr-mac"]],
     ],
     "should not crash on invalid input": [
       [null, ["fr-FR"], []],
+      [[null], [], []],
+      [[undefined], [], []],
+      [[undefined], [null], []],
+      [[undefined], [undefined], []],
+      [[null], [null], null, null, []],
       [undefined, ["fr-FR"], []],
       [2, ["fr-FR"], []],
       ["fr-FR", ["fr-FR"], []],
       [["fr-FR"], null, []],
       [["fr-FR"], undefined, []],
       [["fr-FR"], 2, []],
       [["fr-FR"], "fr-FR", []],
       [["2"], ["ąóżł"], []],
--- a/ipc/glue/BackgroundUtils.cpp
+++ b/ipc/glue/BackgroundUtils.cpp
@@ -89,16 +89,28 @@ PrincipalInfoToPrincipal(const Principal
         attrs = info.attrs();
       }
       principal = BasePrincipal::CreateCodebasePrincipal(uri, attrs);
       rv = principal ? NS_OK : NS_ERROR_FAILURE;
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return nullptr;
       }
 
+      // When the principal is serialized, the origin is extract from it. This
+      // can fail, and in case, here we will havea Tvoid_t. If we have a string,
+      // it must match with what the_new_principal.getOrigin returns.
+      if (info.originNoSuffix().type() == ContentPrincipalInfoOriginNoSuffix::TnsCString) {
+        nsAutoCString originNoSuffix;
+        rv = principal->GetOriginNoSuffix(originNoSuffix);
+        if (NS_WARN_IF(NS_FAILED(rv)) ||
+            !info.originNoSuffix().get_nsCString().Equals(originNoSuffix)) {
+          MOZ_CRASH("If the origin was in the contentPrincipalInfo, it must be available when deserialized");
+        }
+      }
+
       return principal.forget();
     }
 
     case PrincipalInfo::TExpandedPrincipalInfo: {
       const ExpandedPrincipalInfo& info = aPrincipalInfo.get_ExpandedPrincipalInfo();
 
       nsTArray<nsCOMPtr<nsIPrincipal>> whitelist;
       nsCOMPtr<nsIPrincipal> wlPrincipal;
@@ -217,18 +229,28 @@ PrincipalToPrincipalInfo(nsIPrincipal* a
   }
 
   nsAutoCString spec;
   rv = uri->GetSpec(spec);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  ContentPrincipalInfoOriginNoSuffix infoOriginNoSuffix;
+
+  nsCString originNoSuffix;
+  rv = aPrincipal->GetOriginNoSuffix(originNoSuffix);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    infoOriginNoSuffix = void_t();
+  } else {
+    infoOriginNoSuffix = originNoSuffix;
+  }
+
   *aPrincipalInfo = ContentPrincipalInfo(aPrincipal->OriginAttributesRef(),
-                                         spec);
+                                         infoOriginNoSuffix, spec);
   return NS_OK;
 }
 
 bool
 IsPincipalInfoPrivate(const PrincipalInfo& aPrincipalInfo)
 {
   if (aPrincipalInfo.type() != ipc::PrincipalInfo::TContentPrincipalInfo) {
     return false;
--- a/ipc/glue/PBackgroundSharedTypes.ipdlh
+++ b/ipc/glue/PBackgroundSharedTypes.ipdlh
@@ -3,19 +3,31 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 
 namespace mozilla {
 namespace ipc {
 
+union ContentPrincipalInfoOriginNoSuffix
+{
+  nsCString;
+  void_t;
+};
+
 struct ContentPrincipalInfo
 {
   OriginAttributes attrs;
+
+  // nsIPrincipal.originNoSuffix can fail. In case this happens, this value
+  // will be set to void_t. So far, this is used only for dom/media.
+  // It will be removed in bug 1347817.
+  ContentPrincipalInfoOriginNoSuffix originNoSuffix;
+
   nsCString spec;
 };
 
 struct SystemPrincipalInfo
 { };
 
 struct NullPrincipalInfo
 {
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -5,20 +5,16 @@
 // RAII types within which we should assume GC is suppressed, eg
 // AutoSuppressGC.
 var GCSuppressionTypes = [];
 
 // Ignore calls made through these function pointers
 var ignoreIndirectCalls = {
     "mallocSizeOf" : true,
     "aMallocSizeOf" : true,
-    "_malloc_message" : true,
-    "je_malloc_message" : true,
-    "chunk_dalloc" : true,
-    "chunk_alloc" : true,
     "__conv" : true,
     "__convf" : true,
     "prerrortable.c:callback_newtable" : true,
     "mozalloc_oom.cpp:void (* gAbortHandler)(size_t)" : true,
 };
 
 function indirectCallCannotGC(fullCaller, fullVariable)
 {
@@ -170,16 +166,22 @@ var ignoreFunctions = {
 
     // Has an indirect call under it by the name "__f", which seemed too
     // generic to ignore by itself.
     "void* std::_Locale_impl::~_Locale_impl(int32)" : true,
 
     // Bug 1056410 - devirtualization prevents the standard nsISupports::Release heuristic from working
     "uint32 nsXPConnect::Release()" : true,
 
+    // Allocation API
+    "malloc": true,
+    "calloc": true,
+    "realloc": true,
+    "free": true,
+
     // FIXME!
     "NS_LogInit": true,
     "NS_LogTerm": true,
     "NS_LogAddRef": true,
     "NS_LogRelease": true,
     "NS_LogCtor": true,
     "NS_LogDtor": true,
     "NS_LogCOMPtrAddRef": true,
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -1005,18 +1005,20 @@ Parser<ParseHandler>::hasValidSimpleStri
 }
 
 template <typename ParseHandler>
 void
 Parser<ParseHandler>::reportMissingClosing(unsigned errorNumber, unsigned noteNumber,
                                            uint32_t openedPos)
 {
     auto notes = MakeUnique<JSErrorNotes>();
-    if (!notes)
+    if (!notes) {
+        ReportOutOfMemory(pc->sc()->context);
         return;
+    }
 
     uint32_t line, column;
     tokenStream.srcCoords.lineNumAndColumnIndex(openedPos, &line, &column);
 
     const size_t MaxWidth = sizeof("4294967295");
     char columnNumber[MaxWidth];
     SprintfLiteral(columnNumber, "%" PRIu32, column);
     char lineNumber[MaxWidth];
@@ -1043,18 +1045,20 @@ Parser<ParseHandler>::reportRedeclaratio
         return;
 
     if (prevPos == DeclaredNameInfo::npos) {
         errorAt(pos.begin, JSMSG_REDECLARED_VAR, DeclarationKindString(prevKind), bytes.ptr());
         return;
     }
 
     auto notes = MakeUnique<JSErrorNotes>();
-    if (!notes)
+    if (!notes) {
+        ReportOutOfMemory(pc->sc()->context);
         return;
+    }
 
     uint32_t line, column;
     tokenStream.srcCoords.lineNumAndColumnIndex(prevPos, &line, &column);
 
     const size_t MaxWidth = sizeof("4294967295");
     char columnNumber[MaxWidth];
     SprintfLiteral(columnNumber, "%" PRIu32, column);
     char lineNumber[MaxWidth];
--- a/js/src/jit-test/tests/asm.js/testZOOB.js
+++ b/js/src/jit-test/tests/asm.js/testZOOB.js
@@ -1,15 +1,20 @@
 // |jit-test|
 load(libdir + "asm.js");
 load(libdir + "asserts.js");
 
 if (!isAsmJSCompilationAvailable())
     quit();
 
+// This test runs a lot of code and is very slow with --ion-eager. Use a minimum
+// Ion warmup trigger of 2 to avoid timeouts.
+if (getJitCompilerOptions()["ion.warmup.trigger"] < 5)
+    setJitCompilerOption("ion.warmup.trigger", 5);
+
 var ab = new ArrayBuffer(BUF_MIN);
 
 // Compute a set of interesting indices.
 indices = [0]
 for (var i of [4,1024,BUF_MIN,Math.pow(2,30),Math.pow(2,31),Math.pow(2,32),Math.pow(2,33)]) {
     for (var j of [-2,-1,0,1,2]) {
         for (var k of [1,-1])
             indices.push((i+j)*k);
--- a/js/src/jit/BaselineCacheIRCompiler.cpp
+++ b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -377,16 +377,136 @@ BaselineCacheIRCompiler::emitLoadDynamic
 
     masm.load32(stubAddress(reader.stubOffset()), scratch);
     masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), scratch2);
     masm.loadValue(BaseIndex(scratch2, scratch, TimesOne), output.valueReg());
     return true;
 }
 
 bool
+BaselineCacheIRCompiler::emitMegamorphicLoadSlotResult()
+{
+    AutoOutputRegister output(*this);
+
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    Address nameAddr = stubAddress(reader.stubOffset());
+    bool handleMissing = reader.readBool();
+
+    AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
+    AutoScratchRegister scratch2(allocator, masm);
+    AutoScratchRegister scratch3(allocator, masm);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure))
+        return false;
+
+    masm.Push(UndefinedValue());
+    masm.moveStackPtrTo(scratch3.get());
+
+    LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs());
+    volatileRegs.takeUnchecked(scratch1);
+    volatileRegs.takeUnchecked(scratch2);
+    volatileRegs.takeUnchecked(scratch3);
+    masm.PushRegsInMask(volatileRegs);
+
+    masm.setupUnalignedABICall(scratch1);
+    masm.loadJSContext(scratch1);
+    masm.passABIArg(scratch1);
+    masm.passABIArg(obj);
+    masm.loadPtr(nameAddr, scratch2);
+    masm.passABIArg(scratch2);
+    masm.passABIArg(scratch3);
+    if (handleMissing)
+        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, (GetNativeDataProperty<true>)));
+    else
+        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, (GetNativeDataProperty<false>)));
+    masm.mov(ReturnReg, scratch2);
+    masm.PopRegsInMask(volatileRegs);
+
+    masm.loadTypedOrValue(Address(masm.getStackPointer(), 0), output);
+    masm.adjustStack(sizeof(Value));
+
+    masm.branchIfFalseBool(scratch2, failure->label());
+    return true;
+}
+
+bool
+BaselineCacheIRCompiler::emitMegamorphicStoreSlot()
+{
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    Address nameAddr = stubAddress(reader.stubOffset());
+    ValueOperand val = allocator.useValueRegister(masm, reader.valOperandId());
+
+    AutoScratchRegister scratch1(allocator, masm);
+    AutoScratchRegister scratch2(allocator, masm);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure))
+        return false;
+
+    masm.Push(val);
+    masm.moveStackPtrTo(val.scratchReg());
+
+    LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs());
+    volatileRegs.takeUnchecked(scratch1);
+    volatileRegs.takeUnchecked(scratch2);
+    volatileRegs.takeUnchecked(val);
+    masm.PushRegsInMask(volatileRegs);
+
+    masm.setupUnalignedABICall(scratch1);
+    masm.loadJSContext(scratch1);
+    masm.passABIArg(scratch1);
+    masm.passABIArg(obj);
+    masm.loadPtr(nameAddr, scratch2);
+    masm.passABIArg(scratch2);
+    masm.passABIArg(val.scratchReg());
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, SetNativeDataProperty));
+    masm.mov(ReturnReg, scratch1);
+    masm.PopRegsInMask(volatileRegs);
+
+    masm.loadValue(Address(masm.getStackPointer(), 0), val);
+    masm.adjustStack(sizeof(Value));
+
+    masm.branchIfFalseBool(scratch1, failure->label());
+    return true;
+}
+
+bool
+BaselineCacheIRCompiler::emitGuardHasGetterSetter()
+{
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    Address shapeAddr = stubAddress(reader.stubOffset());
+
+    AutoScratchRegister scratch1(allocator, masm);
+    AutoScratchRegister scratch2(allocator, masm);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure))
+        return false;
+
+    LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs());
+    volatileRegs.takeUnchecked(scratch1);
+    volatileRegs.takeUnchecked(scratch2);
+    masm.PushRegsInMask(volatileRegs);
+
+    masm.setupUnalignedABICall(scratch1);
+    masm.loadJSContext(scratch1);
+    masm.passABIArg(scratch1);
+    masm.passABIArg(obj);
+    masm.loadPtr(shapeAddr, scratch2);
+    masm.passABIArg(scratch2);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ObjectHasGetterSetter));
+    masm.mov(ReturnReg, scratch1);
+    masm.PopRegsInMask(volatileRegs);
+
+    masm.branchIfFalseBool(scratch1, failure->label());
+    return true;
+}
+
+bool
 BaselineCacheIRCompiler::emitCallScriptedGetterResult()
 {
     MOZ_ASSERT(engine_ == ICStubEngine::Baseline);
 
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     Address getterAddr(stubAddress(reader.stubOffset()));
 
     AutoScratchRegisterExcluding code(allocator, masm, ArgumentsRectifierReg);
@@ -1732,22 +1852,24 @@ BaselineCacheIRCompiler::init(CacheKind 
     return true;
 }
 
 static const size_t MaxOptimizedCacheIRStubs = 16;
 
 ICStub*
 jit::AttachBaselineCacheIRStub(JSContext* cx, const CacheIRWriter& writer,
                                CacheKind kind, ICStubEngine engine, JSScript* outerScript,
-                               ICFallbackStub* stub)
+                               ICFallbackStub* stub, bool* attached)
 {
     // We shouldn't GC or report OOM (or any other exception) here.
     AutoAssertNoPendingException aanpe(cx);
     JS::AutoCheckCannotGC nogc;
 
+    MOZ_ASSERT(!*attached);
+
     if (writer.failed())
         return nullptr;
 
     // Just a sanity check: the caller should ensure we don't attach an
     // unlimited number of stubs.
     MOZ_ASSERT(stub->numOptimizedStubs() < MaxOptimizedCacheIRStubs);
 
     enum class CacheIRStubKind { Regular, Monitored, Updated };
@@ -1805,52 +1927,55 @@ jit::AttachBaselineCacheIRStub(JSContext
     MOZ_ASSERT(code);
     MOZ_ASSERT(stubInfo);
     MOZ_ASSERT(stubInfo->stubDataSize() == writer.stubDataSize());
 
     // Ensure we don't attach duplicate stubs. This can happen if a stub failed
     // for some reason and the IR generator doesn't check for exactly the same
     // conditions.
     for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
+        bool updated = false;
         switch (stubKind) {
           case CacheIRStubKind::Regular: {
             if (!iter->isCacheIR_Regular())
                 continue;
             auto otherStub = iter->toCacheIR_Regular();
             if (otherStub->stubInfo() != stubInfo)
                 continue;
-            if (!writer.stubDataEqualsMaybeUpdate(otherStub->stubDataStart()))
+            if (!writer.stubDataEqualsMaybeUpdate(otherStub->stubDataStart(), &updated))
                 continue;
             break;
           }
           case CacheIRStubKind::Monitored: {
             if (!iter->isCacheIR_Monitored())
                 continue;
             auto otherStub = iter->toCacheIR_Monitored();
             if (otherStub->stubInfo() != stubInfo)
                 continue;
-            if (!writer.stubDataEqualsMaybeUpdate(otherStub->stubDataStart()))
+            if (!writer.stubDataEqualsMaybeUpdate(otherStub->stubDataStart(), &updated))
                 continue;
             break;
           }
           case CacheIRStubKind::Updated: {
             if (!iter->isCacheIR_Updated())
                 continue;
             auto otherStub = iter->toCacheIR_Updated();
             if (otherStub->stubInfo() != stubInfo)
                 continue;
-            if (!writer.stubDataEqualsMaybeUpdate(otherStub->stubDataStart()))
+            if (!writer.stubDataEqualsMaybeUpdate(otherStub->stubDataStart(), &updated))
                 continue;
             break;
           }
         }
 
         // We found a stub that's exactly the same as the stub we're about to
         // attach. Just return nullptr, the caller should do nothing in this
         // case.
+        if (updated)
+            *attached = true;
         return nullptr;
     }
 
     // Time to allocate and attach a new stub.
 
     size_t bytesNeeded = stubInfo->stubDataOffset() + stubInfo->stubDataSize();
 
     ICStubSpace* stubSpace = ICStubCompiler::StubSpaceForStub(stubInfo->makesGCCalls(),
@@ -1859,34 +1984,37 @@ jit::AttachBaselineCacheIRStub(JSContext
     if (!newStubMem)
         return nullptr;
 
     switch (stubKind) {
       case CacheIRStubKind::Regular: {
         auto newStub = new(newStubMem) ICCacheIR_Regular(code, stubInfo);
         writer.copyStubData(newStub->stubDataStart());
         stub->addNewStub(newStub);
+        *attached = true;
         return newStub;
       }
       case CacheIRStubKind::Monitored: {
         ICStub* monitorStub =
             stub->toMonitoredFallbackStub()->fallbackMonitorStub()->firstMonitorStub();
         auto newStub = new(newStubMem) ICCacheIR_Monitored(code, monitorStub, stubInfo);
         writer.copyStubData(newStub->stubDataStart());
         stub->addNewStub(newStub);
+        *attached = true;
         return newStub;
       }
       case CacheIRStubKind::Updated: {
         auto newStub = new(newStubMem) ICCacheIR_Updated(code, stubInfo);
         if (!newStub->initUpdatingChain(cx, stubSpace)) {
             cx->recoverFromOutOfMemory();
             return nullptr;
         }
         writer.copyStubData(newStub->stubDataStart());
         stub->addNewStub(newStub);
+        *attached = true;
         return newStub;
       }
     }
 
     MOZ_CRASH("Invalid kind");
 }
 
 uint8_t*
--- a/js/src/jit/BaselineCacheIRCompiler.h
+++ b/js/src/jit/BaselineCacheIRCompiler.h
@@ -14,14 +14,14 @@
 namespace js {
 namespace jit {
 
 class ICFallbackStub;
 class ICStub;
 
 ICStub* AttachBaselineCacheIRStub(JSContext* cx, const CacheIRWriter& writer,
                                   CacheKind kind, ICStubEngine engine, JSScript* outerScript,
-                                  ICFallbackStub* stub);
+                                  ICFallbackStub* stub, bool* attached);
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_BaselineCacheIRCompiler_h */
--- a/js/src/jit/BaselineDebugModeOSR.cpp
+++ b/js/src/jit/BaselineDebugModeOSR.cpp
@@ -694,18 +694,17 @@ RecompileBaselineScriptForDebugMode(JSCo
     _(CacheIR_Monitored)                        \
     _(CacheIR_Updated)                          \
     _(Call_Scripted)                            \
     _(Call_AnyScripted)                         \
     _(Call_Native)                              \
     _(Call_ClassHook)                           \
     _(Call_ScriptedApplyArray)                  \
     _(Call_ScriptedApplyArguments)              \
-    _(Call_ScriptedFunCall)                     \
-    _(GetProp_Generic)
+    _(Call_ScriptedFunCall)
 
 static bool
 CloneOldBaselineStub(JSContext* cx, DebugModeOSREntryVector& entries, size_t entryIndex)
 {
     DebugModeOSREntry& entry = entries[entryIndex];
     if (!entry.oldStub)
         return true;
 
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -798,40 +798,39 @@ DoGetElemFallback(JSContext* cx, Baselin
         // Handle optimized arguments[i] access.
         if (!GetElemOptimizedArguments(cx, frame, &lhsCopy, rhs, res, &isOptimizedArgs))
             return false;
         if (isOptimizedArgs)
             TypeScript::Monitor(cx, frame->script(), pc, res);
     }
 
     bool attached = false;
-    if (stub->numOptimizedStubs() >= ICGetElem_Fallback::MAX_OPTIMIZED_STUBS) {
-        // TODO: Discard all stubs in this IC and replace with inert megamorphic stub.
-        // But for now we just bail.
-        stub->noteUnoptimizableAccess();
-        attached = true;
-    }
-
     bool isTemporarilyUnoptimizable = false;
-    if (!attached && !JitOptions.disableCacheIR) {
+
+    if (stub->state().maybeTransition())
+        stub->discardStubs(cx);
+
+    if (stub->state().canAttachStub()) {
         ICStubEngine engine = ICStubEngine::Baseline;
-        GetPropIRGenerator gen(cx, script, pc, CacheKind::GetElem,
+        GetPropIRGenerator gen(cx, script, pc, CacheKind::GetElem, stub->state().mode(),
                                &isTemporarilyUnoptimizable, lhs, rhs, CanAttachGetter::Yes);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        engine, info.outerScript(cx), stub);
+                                                        engine, info.outerScript(cx), stub,
+                                                        &attached);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
-                attached = true;
                 if (gen.shouldNotePreliminaryObjectStub())
                     newStub->toCacheIR_Monitored()->notePreliminaryObject();
                 else if (gen.shouldUnlinkPreliminaryObjectStubs())
                     StripPreliminaryObjectStubs(cx, stub);
             }
         }
+        if (!attached && !isTemporarilyUnoptimizable)
+            stub->state().trackNotAttached();
     }
 
     if (!isOptimizedArgs) {
         if (!GetElementOperation(cx, op, lhsCopy, rhs, res))
             return false;
         TypeScript::Monitor(cx, frame->script(), pc, res);
     }
 
@@ -952,29 +951,30 @@ DoSetElemFallback(JSContext* cx, Baselin
 
     if (obj->is<UnboxedPlainObject>()) {
         MOZ_ASSERT(!oldShape);
         if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
             oldShape = expando->lastProperty();
     }
 
     bool isTemporarilyUnoptimizable = false;
-
     bool attached = false;
-    if (stub->numOptimizedStubs() < ICSetElem_Fallback::MAX_OPTIMIZED_STUBS &&
-        !JitOptions.disableCacheIR)
-    {
-        SetPropIRGenerator gen(cx, script, pc, CacheKind::SetElem, &isTemporarilyUnoptimizable,
-                               objv, index, rhs);
+
+    if (stub->state().maybeTransition())
+        stub->discardStubs(cx);
+
+    if (stub->state().canAttachStub()) {
+        SetPropIRGenerator gen(cx, script, pc, CacheKind::SetElem, stub->state().mode(),
+                               &isTemporarilyUnoptimizable, objv, index, rhs);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        ICStubEngine::Baseline, frame->script(), stub);
+                                                        ICStubEngine::Baseline, frame->script(),
+                                                        stub, &attached);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
-                attached = true;
 
                 SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
 
                 if (gen.shouldNotePreliminaryObjectStub())
                     newStub->toCacheIR_Updated()->notePreliminaryObject();
                 else if (gen.shouldUnlinkPreliminaryObjectStubs())
                     StripPreliminaryObjectStubs(cx, stub);
 
@@ -1014,37 +1014,33 @@ DoSetElemFallback(JSContext* cx, Baselin
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid())
         return true;
 
     if (attached)
         return true;
 
-    if (stub->numOptimizedStubs() >= ICSetElem_Fallback::MAX_OPTIMIZED_STUBS) {
-        // TODO: Discard all stubs in this IC and replace with inert megamorphic stub.
-        // But for now we just bail.
-        return true;
-    }
-
-    if (!JitOptions.disableCacheIR) {
-        SetPropIRGenerator gen(cx, script, pc, CacheKind::SetElem, &isTemporarilyUnoptimizable,
-                               objv, index, rhs);
+    if (stub->state().canAttachStub()) {
+        SetPropIRGenerator gen(cx, script, pc, CacheKind::SetElem, stub->state().mode(),
+                               &isTemporarilyUnoptimizable, objv, index, rhs);
         if (gen.tryAttachAddSlotStub(oldGroup, oldShape)) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        ICStubEngine::Baseline, frame->script(), stub);
+                                                        ICStubEngine::Baseline, frame->script(),
+                                                        stub, &attached);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
-                attached = true;
                 SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
                 return true;
             }
         } else {
             gen.trackNotAttached();
         }
+        if (!attached && !isTemporarilyUnoptimizable)
+            stub->state().trackNotAttached();
     }
 
     return true;
 }
 
 typedef bool (*DoSetElemFallbackFn)(JSContext*, BaselineFrame*, ICSetElem_Fallback*, Value*,
                                     HandleValue, HandleValue, HandleValue);
 static const VMFunction DoSetElemFallbackInfo =
@@ -1203,36 +1199,36 @@ DoInFallback(JSContext* cx, BaselineFram
 
     FallbackICSpew(cx, stub, "In");
 
     if (!objValue.isObject()) {
         ReportValueError(cx, JSMSG_IN_NOT_OBJECT, -1, objValue, nullptr);
         return false;
     }
 
-    bool attached = false;
-
-    if (stub->numOptimizedStubs() >= ICIn_Fallback::MAX_OPTIMIZED_STUBS)
-        attached = true;
-
-    RootedScript script(cx, frame->script());
     RootedObject obj(cx, &objValue.toObject());
-    jsbytecode* pc = stub->icEntry()->pc(script);
-
-    if (!attached && !JitOptions.disableCacheIR) {
+
+    if (stub->state().maybeTransition())
+        stub->discardStubs(cx);
+
+    if (stub->state().canAttachStub()) {
+        RootedScript script(cx, frame->script());
+        jsbytecode* pc = stub->icEntry()->pc(script);
+
         ICStubEngine engine = ICStubEngine::Baseline;
-        InIRGenerator gen(cx, script, pc, key, obj);
+        InIRGenerator gen(cx, script, pc, stub->state().mode(), key, obj);
+        bool attached = false;
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        engine, script, stub);
-            if (newStub) {
+                                                        engine, script, stub, &attached);
+            if (newStub)
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
-                attached = true;
-            }
         }
+        if (!attached)
+            stub->state().trackNotAttached();
     }
 
     bool cond = false;
     if (!OperatorIn(cx, key, obj, &cond))
         return false;
     res.setBoolean(cond);
 
     return true;
@@ -1277,33 +1273,31 @@ DoGetNameFallback(JSContext* cx, Baselin
     mozilla::DebugOnly<JSOp> op = JSOp(*pc);
     FallbackICSpew(cx, stub, "GetName(%s)", CodeName[JSOp(*pc)]);
 
     MOZ_ASSERT(op == JSOP_GETNAME || op == JSOP_GETGNAME);
 
     RootedPropertyName name(cx, script->getName(pc));
     bool attached = false;
 
-    // Attach new stub.
-    if (stub->numOptimizedStubs() >= ICGetName_Fallback::MAX_OPTIMIZED_STUBS) {
-        // TODO: Discard all stubs in this IC and replace with generic stub.
-        attached = true;
-    }
-
-    if (!attached && !JitOptions.disableCacheIR) {
+    if (stub->state().maybeTransition())
+        stub->discardStubs(cx);
+
+    if (stub->state().canAttachStub()) {
         ICStubEngine engine = ICStubEngine::Baseline;
-        GetNameIRGenerator gen(cx, script, pc, envChain, name);
+        GetNameIRGenerator gen(cx, script, pc, stub->state().mode(), envChain, name);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        engine, info.outerScript(cx), stub);
-            if (newStub) {
+                                                        engine, info.outerScript(cx), stub,
+                                                        &attached);
+            if (newStub)
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
-                attached = true;
-            }
         }
+        if (!attached)
+            stub->state().trackNotAttached();
     }
 
     static_assert(JSOP_GETGNAME_LENGTH == JSOP_GETNAME_LENGTH,
                   "Otherwise our check for JSOP_TYPEOF isn't ok");
     if (JSOp(pc[JSOP_GETGNAME_LENGTH]) == JSOP_TYPEOF) {
         if (!GetEnvironmentName<GetNameMode::TypeOf>(cx, envChain, name, res))
             return false;
     } else {
@@ -1513,28 +1507,29 @@ DoSetPropFallback(JSContext* cx, Baselin
 
     // 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;
 
     bool attached = false;
-    if (stub->numOptimizedStubs() < ICSetProp_Fallback::MAX_OPTIMIZED_STUBS &&
-        !JitOptions.disableCacheIR)
-    {
+    if (stub->state().maybeTransition())
+        stub->discardStubs(cx);
+
+    if (stub->state().canAttachStub()) {
         RootedValue idVal(cx, StringValue(name));
-        SetPropIRGenerator gen(cx, script, pc, CacheKind::SetProp, &isTemporarilyUnoptimizable,
-                               lhs, idVal, rhs);
+        SetPropIRGenerator gen(cx, script, pc, CacheKind::SetProp, stub->state().mode(),
+                               &isTemporarilyUnoptimizable, lhs, idVal, rhs);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        ICStubEngine::Baseline, frame->script(), stub);
+                                                        ICStubEngine::Baseline, frame->script(),
+                                                        stub, &attached);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
-                attached = true;
 
                 SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
 
                 if (gen.shouldNotePreliminaryObjectStub())
                     newStub->toCacheIR_Updated()->notePreliminaryObject();
                 else if (gen.shouldUnlinkPreliminaryObjectStubs())
                     StripPreliminaryObjectStubs(cx, stub);
             }
@@ -1578,34 +1573,33 @@ DoSetPropFallback(JSContext* cx, Baselin
     // Overwrite the LHS on the stack (pushed for the decompiler) with the RHS.
     MOZ_ASSERT(stack[1] == lhs);
     stack[1] = rhs;
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid())
         return true;
 
-    if (!attached &&
-        stub->numOptimizedStubs() < ICSetProp_Fallback::MAX_OPTIMIZED_STUBS &&
-        !JitOptions.disableCacheIR)
-    {
+    if (!attached && stub->state().canAttachStub()) {
         RootedValue idVal(cx, StringValue(name));
-        SetPropIRGenerator gen(cx, script, pc, CacheKind::SetProp, &isTemporarilyUnoptimizable,
-                               lhs, idVal, rhs);
+        SetPropIRGenerator gen(cx, script, pc, CacheKind::SetProp, stub->state().mode(),
+                               &isTemporarilyUnoptimizable, lhs, idVal, rhs);
         if (gen.tryAttachAddSlotStub(oldGroup, oldShape)) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        ICStubEngine::Baseline, frame->script(), stub);
+                                                        ICStubEngine::Baseline, frame->script(),
+                                                        stub, &attached);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
-                attached = true;
                 SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
             }
         } else {
             gen.trackNotAttached();
         }
+        if (!attached && !isTemporarilyUnoptimizable)
+            stub->state().trackNotAttached();
     }
 
     if (!attached && !isTemporarilyUnoptimizable)
         stub->noteUnoptimizableAccess();
 
     return true;
 }
 
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -371,18 +371,16 @@ class ICGetElem_Fallback : public ICMoni
       : ICMonitoredFallbackStub(ICStub::GetElem_Fallback, stubCode)
     { }
 
     static const uint16_t EXTRA_NON_NATIVE = 0x1;
     static const uint16_t EXTRA_NEGATIVE_INDEX = 0x2;
     static const uint16_t EXTRA_UNOPTIMIZABLE_ACCESS = 0x4;
 
   public:
-    static const uint32_t MAX_OPTIMIZED_STUBS = 16;
-
     void noteNonNativeAccess() {
         extra_ |= EXTRA_NON_NATIVE;
     }
     bool hasNonNativeAccess() const {
         return extra_ & EXTRA_NON_NATIVE;
     }
 
     void noteNegativeIndex() {
@@ -430,18 +428,16 @@ class ICSetElem_Fallback : public ICFall
     explicit ICSetElem_Fallback(JitCode* stubCode)
       : ICFallbackStub(ICStub::SetElem_Fallback, stubCode)
     { }
 
     static const size_t HasDenseAddFlag = 0x1;
     static const size_t HasTypedArrayOOBFlag = 0x2;
 
   public:
-    static const uint32_t MAX_OPTIMIZED_STUBS = 8;
-
     void noteHasDenseAdd() { extra_ |= HasDenseAddFlag; }
     bool hasDenseAdd() const { return extra_ & HasDenseAddFlag; }
 
     void noteHasTypedArrayOOB() { extra_ |= HasTypedArrayOOBFlag; }
     bool hasTypedArrayOOB() const { return extra_ & HasTypedArrayOOBFlag; }
 
     // Compiler for this stub kind.
     class Compiler : public ICStubCompiler {
@@ -465,18 +461,16 @@ class ICIn_Fallback : public ICFallbackS
 {
     friend class ICStubSpace;
 
     explicit ICIn_Fallback(JitCode* stubCode)
       : ICFallbackStub(ICStub::In_Fallback, stubCode)
     { }
 
   public:
-    static const uint32_t MAX_OPTIMIZED_STUBS = 8;
-
     class Compiler : public ICStubCompiler {
       protected:
         MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm);
 
       public:
         explicit Compiler(JSContext* cx)
           : ICStubCompiler(cx, ICStub::In_Fallback, Engine::Baseline)
         { }
@@ -495,17 +489,16 @@ class ICGetName_Fallback : public ICMoni
 {
     friend class ICStubSpace;
 
     explicit ICGetName_Fallback(JitCode* stubCode)
       : ICMonitoredFallbackStub(ICStub::GetName_Fallback, stubCode)
     { }
 
   public:
-    static const uint32_t MAX_OPTIMIZED_STUBS = 8;
     static const size_t UNOPTIMIZABLE_ACCESS_BIT = 0;
 
     void noteUnoptimizableAccess() {
         extra_ |= (1u << UNOPTIMIZABLE_ACCESS_BIT);
     }
     bool hadUnoptimizableAccess() const {
         return extra_ & (1u << UNOPTIMIZABLE_ACCESS_BIT);
     }
@@ -629,18 +622,16 @@ class ICSetProp_Fallback : public ICFall
 {
     friend class ICStubSpace;
 
     explicit ICSetProp_Fallback(JitCode* stubCode)
       : ICFallbackStub(ICStub::SetProp_Fallback, stubCode)
     { }
 
   public:
-    static const uint32_t MAX_OPTIMIZED_STUBS = 8;
-
     static const size_t UNOPTIMIZABLE_ACCESS_BIT = 0;
     void noteUnoptimizableAccess() {
         extra_ |= (1u << UNOPTIMIZABLE_ACCESS_BIT);
     }
     bool hadUnoptimizableAccess() const {
         return extra_ & (1u << UNOPTIMIZABLE_ACCESS_BIT);
     }
 
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -1013,16 +1013,97 @@ BaselineInspector::commonGetPropFunction
     if (!*commonGetter)
         return false;
 
     MOZ_ASSERT(*isOwnProperty == !*holder);
     MOZ_ASSERT(*isOwnProperty == (receivers.empty() && convertUnboxedGroups.empty()));
     return true;
 }
 
+static JSFunction*
+GetMegamorphicGetterSetterFunction(ICStub* stub, const CacheIRStubInfo* stubInfo, bool isGetter)
+{
+    // We match:
+    //
+    //   GuardIsObject objId
+    //   GuardHasGetterSetter objId propShape
+    //
+    // propShape has the getter/setter we're interested in.
+
+    CacheIRReader reader(stubInfo);
+
+    ObjOperandId objId = ObjOperandId(0);
+    if (!reader.matchOp(CacheOp::GuardIsObject, objId))
+        return nullptr;
+
+    if (!reader.matchOp(CacheOp::GuardHasGetterSetter, objId))
+        return nullptr;
+    Shape* propShape = stubInfo->getStubField<Shape*>(stub, reader.stubOffset());
+
+    JSObject* obj = isGetter ? propShape->getterObject() : propShape->setterObject();
+    return &obj->as<JSFunction>();
+}
+
+bool
+BaselineInspector::megamorphicGetterSetterFunction(jsbytecode* pc, bool isGetter,
+                                                   JSFunction** getterOrSetter)
+{
+    if (!hasBaselineScript())
+        return false;
+
+    *getterOrSetter = nullptr;
+    const ICEntry& entry = icEntryFromPC(pc);
+
+    for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
+        if (stub->isCacheIR_Monitored()) {
+            MOZ_ASSERT(isGetter);
+            JSFunction* getter =
+                GetMegamorphicGetterSetterFunction(stub,
+                                                   stub->toCacheIR_Monitored()->stubInfo(),
+                                                   isGetter);
+            if (!getter || (*getterOrSetter && *getterOrSetter != getter))
+                return false;
+            *getterOrSetter = getter;
+            continue;
+        }
+        if (stub->isCacheIR_Updated()) {
+            MOZ_ASSERT(!isGetter);
+            JSFunction* setter =
+                GetMegamorphicGetterSetterFunction(stub,
+                                                   stub->toCacheIR_Updated()->stubInfo(),
+                                                   isGetter);
+            if (!setter || (*getterOrSetter && *getterOrSetter != setter))
+                return false;
+            *getterOrSetter = setter;
+            continue;
+        }
+        if (stub->isGetProp_Fallback()) {
+            if (stub->toGetProp_Fallback()->hadUnoptimizableAccess())
+                return false;
+            if (stub->toGetProp_Fallback()->state().mode() != ICState::Mode::Megamorphic)
+                return false;
+            continue;
+        }
+        if (stub->isSetProp_Fallback()) {
+            if (stub->toSetProp_Fallback()->hadUnoptimizableAccess())
+                return false;
+            if (stub->toSetProp_Fallback()->state().mode() != ICState::Mode::Megamorphic)
+                return false;
+            continue;
+        }
+
+        return false;
+    }
+
+    if (!*getterOrSetter)
+        return false;
+
+    return true;
+}
+
 static bool
 AddCacheIRSetPropFunction(ICCacheIR_Updated* stub, JSObject** holder, Shape** holderShape,
                           JSFunction** commonSetter, bool* isOwnProperty,
                           BaselineInspector::ReceiverVector& receivers,
                           BaselineInspector::ObjectGroupVector& convertUnboxedGroups)
 {
     // We match either an own setter:
     //
@@ -1182,19 +1263,16 @@ BaselineInspector::expectedPropertyAcces
                 return MIRType::Value;
             continue;
 
           case ICStub::GetElem_Fallback:
             if (stub->toGetElem_Fallback()->hadUnoptimizableAccess())
                 return MIRType::Value;
             continue;
 
-          case ICStub::GetProp_Generic:
-            return MIRType::Value;
-
           case ICStub::CacheIR_Monitored:
             stubType = GetCacheIRExpectedInputType(stub->toCacheIR_Monitored());
             if (stubType == MIRType::Value)
                 return MIRType::Value;
             break;
 
           default:
             MOZ_CRASH("Unexpected stub");
--- a/js/src/jit/BaselineInspector.h
+++ b/js/src/jit/BaselineInspector.h
@@ -132,16 +132,19 @@ class BaselineInspector
     // global object) instead. In this case we should only look for Baseline
     // stubs that performed the same optimization.
     MOZ_MUST_USE bool commonGetPropFunction(jsbytecode* pc, bool innerized,
                                             JSObject** holder, Shape** holderShape,
                                             JSFunction** commonGetter, Shape** globalShape,
                                             bool* isOwnProperty, ReceiverVector& receivers,
                                             ObjectGroupVector& convertUnboxedGroups);
 
+    MOZ_MUST_USE bool megamorphicGetterSetterFunction(jsbytecode* pc, bool isGetter,
+                                                      JSFunction** getterOrSetter);
+
     MOZ_MUST_USE bool commonSetPropFunction(jsbytecode* pc, JSObject** holder, Shape** holderShape,
                                             JSFunction** commonSetter, bool* isOwnProperty,
                                             ReceiverVector& receivers,
                                             ObjectGroupVector& convertUnboxedGroups);
 
     MOZ_MUST_USE bool instanceOfData(jsbytecode* pc, Shape** shape, uint32_t* slot,
                                      JSObject** prototypeObject);
 };
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -26,29 +26,31 @@ using mozilla::Maybe;
 
 const char* js::jit::CacheKindNames[] = {
 #define DEFINE_KIND(kind) #kind,
     CACHE_IR_KINDS(DEFINE_KIND)
 #undef DEFINE_KIND
 };
 
 
-IRGenerator::IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind)
+IRGenerator::IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind,
+                         ICState::Mode mode)
   : writer(cx),
     cx_(cx),
     script_(script),
     pc_(pc),
-    cacheKind_(cacheKind)
+    cacheKind_(cacheKind),
+    mode_(mode)
 {}
 
 GetPropIRGenerator::GetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
-                                       CacheKind cacheKind, bool* isTemporarilyUnoptimizable,
-                                       HandleValue val, HandleValue idVal,
-                                       CanAttachGetter canAttachGetter)
-  : IRGenerator(cx, script, pc, cacheKind),
+                                       CacheKind cacheKind, ICState::Mode mode,
+                                       bool* isTemporarilyUnoptimizable, HandleValue val,
+                                       HandleValue idVal, CanAttachGetter canAttachGetter)
+  : IRGenerator(cx, script, pc, cacheKind, mode),
     val_(val),
     idVal_(idVal),
     isTemporarilyUnoptimizable_(isTemporarilyUnoptimizable),
     canAttachGetter_(canAttachGetter),
     preliminaryObjectAction_(PreliminaryObjectAction::None)
 {}
 
 static void
@@ -500,47 +502,79 @@ EmitCallGetterResultNoGuards(CacheIRWrit
     JSFunction* target = &shape->getterValue().toObject().as<JSFunction>();
     MOZ_ASSERT(target->hasJITCode());
     writer.callScriptedGetterResult(objId, target);
     writer.typeMonitorResult();
 }
 
 static void
 EmitCallGetterResult(CacheIRWriter& writer, JSObject* obj, JSObject* holder,
-                     Shape* shape, ObjOperandId objId)
+                     Shape* shape, ObjOperandId objId, ICState::Mode mode)
 {
-    Maybe<ObjOperandId> expandoId;
-    TestMatchingReceiver(writer, obj, shape, objId, &expandoId);
-
-    if (obj != holder) {
-        GeneratePrototypeGuards(writer, obj, holder, objId);
-
-        // Guard on the holder's shape.
-        ObjOperandId holderId = writer.loadObject(holder);
-        writer.guardShape(holderId, holder->as<NativeObject>().lastProperty());
+    // Use the megamorphic guard if we're in megamorphic mode, except if |obj|
+    // is a Window as GuardHasGetterSetter doesn't support this yet (Window may
+    // require outerizing).
+    if (mode == ICState::Mode::Specialized || IsWindow(obj)) {
+        Maybe<ObjOperandId> expandoId;
+        TestMatchingReceiver(writer, obj, shape, objId, &expandoId);
+
+        if (obj != holder) {
+            GeneratePrototypeGuards(writer, obj, holder, objId);
+
+            // Guard on the holder's shape.
+            ObjOperandId holderId = writer.loadObject(holder);
+            writer.guardShape(holderId, holder->as<NativeObject>().lastProperty());
+        }
+    } else {
+        writer.guardHasGetterSetter(objId, shape);
     }
 
     EmitCallGetterResultNoGuards(writer, obj, holder, shape, objId);
 }
 
+void
+GetPropIRGenerator::attachMegamorphicNativeSlot(ObjOperandId objId, jsid id, bool handleMissing)
+{
+    MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
+
+    // The stub handles the missing-properties case only if we're seeing one
+    // now, to make sure Ion ICs correctly monitor the undefined type.
+
+    if (cacheKind_ == CacheKind::GetProp) {
+        writer.megamorphicLoadSlotResult(objId, JSID_TO_ATOM(id)->asPropertyName(),
+                                         handleMissing);
+    } else {
+        MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
+        writer.megamorphicLoadSlotByValueResult(objId, getElemKeyValueId(), handleMissing);
+    }
+    writer.typeMonitorResult();
+
+    trackAttached("MegamorphicNativeSlot");
+}
+
 bool
 GetPropIRGenerator::tryAttachNative(HandleObject obj, ObjOperandId objId, HandleId id)
 {
     RootedShape shape(cx_);
     RootedNativeObject holder(cx_);
 
     NativeGetPropCacheability type = CanAttachNativeGetProp(cx_, obj, id, &holder, &shape, pc_,
                                                             canAttachGetter_,
                                                             isTemporarilyUnoptimizable_);
     MOZ_ASSERT_IF(idempotent(),
                   type == CanAttachNone || (type == CanAttachReadSlot && holder));
     switch (type) {
       case CanAttachNone:
         return false;
       case CanAttachReadSlot:
+        if (mode_ == ICState::Mode::Megamorphic) {
+            attachMegamorphicNativeSlot(objId, id, holder == nullptr);
+            return true;
+        }
+
         maybeEmitIdGuard(id);
         if (holder) {
             EnsureTrackPropertyTypes(cx_, holder, id);
             if (obj == holder) {
                 // See the comment in StripPreliminaryObjectStubs.
                 if (IsPreliminaryObject(obj))
                     preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary;
                 else
@@ -549,17 +583,17 @@ GetPropIRGenerator::tryAttachNative(Hand
         }
         EmitReadSlotResult(writer, obj, holder, shape, objId);
         EmitReadSlotReturn(writer, obj, holder, shape);
 
         trackAttached("NativeSlot");
         return true;
       case CanAttachCallGetter:
         maybeEmitIdGuard(id);
-        EmitCallGetterResult(writer, obj, holder, shape, objId);
+        EmitCallGetterResult(writer, obj, holder, shape, objId, mode_);
 
         trackAttached("NativeGetter");
         return true;
     }
 
     MOZ_CRASH("Bad NativeGetPropCacheability");
 }
 
@@ -567,16 +601,21 @@ bool
 GetPropIRGenerator::tryAttachWindowProxy(HandleObject obj, ObjOperandId objId, HandleId id)
 {
     // Attach a stub when the receiver is a WindowProxy and we can do the lookup
     // on the Window (the global object).
 
     if (!IsWindowProxy(obj))
         return false;
 
+    // If we're megamorphic prefer a generic proxy stub that handles a lot more
+    // cases.
+    if (mode_ == ICState::Mode::Megamorphic)
+        return false;
+
     // This must be a WindowProxy for the current Window/global. Else it would
     // be a cross-compartment wrapper and IsWindowProxy returns false for
     // those.
     MOZ_ASSERT(obj->getClass() == cx_->runtime()->maybeWindowProxyClass());
     MOZ_ASSERT(ToWindowIfWindowProxy(obj) == cx_->global());
 
     // Now try to do the lookup on the Window (the current global).
     HandleObject windowObj = cx_->global();
@@ -612,17 +651,17 @@ GetPropIRGenerator::tryAttachWindowProxy
         if (!callee->jitInfo() || callee->jitInfo()->needsOuterizedThisObject())
             return false;
 
         // Guard the incoming object is a WindowProxy and inline a getter call based
         // on the Window object.
         maybeEmitIdGuard(id);
         writer.guardClass(objId, GuardClassKind::WindowProxy);
         ObjOperandId windowObjId = writer.loadObject(windowObj);
-        EmitCallGetterResult(writer, windowObj, holder, shape, windowObjId);
+        EmitCallGetterResult(writer, windowObj, holder, shape, windowObjId, mode_);
 
         trackAttached("WindowProxyGetter");
         return true;
       }
     }
 
     MOZ_CRASH("Unreachable");
 }
@@ -631,16 +670,21 @@ bool
 GetPropIRGenerator::tryAttachCrossCompartmentWrapper(HandleObject obj, ObjOperandId objId,
                                                      HandleId id)
 {
     // We can only optimize this very wrapper-handler, because others might
     // have a security policy.
     if (!IsWrapper(obj) || Wrapper::wrapperHandler(obj) != &CrossCompartmentWrapper::singleton)
         return false;
 
+    // If we're megamorphic prefer a generic proxy stub that handles a lot more
+    // cases.
+    if (mode_ == ICState::Mode::Megamorphic)
+        return false;
+
     RootedObject unwrapped(cx_, Wrapper::wrappedObject(obj));
     MOZ_ASSERT(unwrapped == UnwrapOneChecked(obj));
 
     // If we allowed different zones we would have to wrap strings.
     if (unwrapped->compartment()->zone() != cx_->compartment()->zone())
         return false;
 
     AutoCompartment ac(cx_, unwrapped);
@@ -695,33 +739,36 @@ GetPropIRGenerator::tryAttachCrossCompar
     EmitReadSlotResult(writer, unwrapped, holder, shape, unwrappedId);
     EmitReadSlotReturn(writer, unwrapped, holder, shape, /* wrapResult = */ true);
 
     trackAttached("CCWSlot");
     return true;
 }
 
 bool
-GetPropIRGenerator::tryAttachGenericProxy(HandleObject obj, ObjOperandId objId, HandleId id)
+GetPropIRGenerator::tryAttachGenericProxy(HandleObject obj, ObjOperandId objId, HandleId id,
+                                          bool handleDOMProxies)
 {
     MOZ_ASSERT(obj->is<ProxyObject>());
 
     writer.guardIsProxy(objId);
 
-    // Ensure that the incoming object is not a DOM proxy, so that we can get to
-    // the specialized stubs
-    writer.guardNotDOMProxy(objId);
-
-    if (cacheKind_ == CacheKind::GetProp) {
+    if (!handleDOMProxies) {
+        // Ensure that the incoming object is not a DOM proxy, so that we can get to
+        // the specialized stubs
+        writer.guardNotDOMProxy(objId);
+    }
+
+    if (cacheKind_ == CacheKind::GetProp || mode_ == ICState::Mode::Specialized) {
+        maybeEmitIdGuard(id);
         writer.callProxyGetResult(objId, id);
     } else {
-        // We could call maybeEmitIdGuard here and then emit CallProxyGetResult,
-        // but for GetElem we prefer to attach a stub that can handle any Value
-        // so we don't attach a new stub for every id.
+        // Attach a stub that handles every id.
         MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
+        MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
         writer.callProxyGetByValueResult(objId, getElemKeyValueId());
     }
 
     writer.typeMonitorResult();
 
     trackAttached("GenericProxy");
     return true;
 }
@@ -893,33 +940,46 @@ GetPropIRGenerator::tryAttachDOMProxyUns
 
     trackAttached("DOMProxyUnshadowed");
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachProxy(HandleObject obj, ObjOperandId objId, HandleId id)
 {
-    switch (GetProxyStubType(cx_, obj, id)) {
+    ProxyStubType type = GetProxyStubType(cx_, obj, id);
+    if (type == ProxyStubType::None)
+        return false;
+
+    if (mode_ == ICState::Mode::Megamorphic)
+        return tryAttachGenericProxy(obj, objId, id, /* handleDOMProxies = */ true);
+
+    switch (type) {
       case ProxyStubType::None:
-        return false;
+        break;
       case ProxyStubType::DOMExpando:
         if (tryAttachDOMProxyExpando(obj, objId, id))
             return true;
         if (*isTemporarilyUnoptimizable_) {
             // Scripted getter without JIT code. Just wait.
             return false;
         }
         MOZ_FALLTHROUGH; // Fall through to the generic shadowed case.
       case ProxyStubType::DOMShadowed:
         return tryAttachDOMProxyShadowed(obj, objId, id);
       case ProxyStubType::DOMUnshadowed:
-        return tryAttachDOMProxyUnshadowed(obj, objId, id);
+        if (tryAttachDOMProxyUnshadowed(obj, objId, id))
+            return true;
+        if (*isTemporarilyUnoptimizable_) {
+            // Scripted getter without JIT code. Just wait.
+            return false;
+        }
+        return tryAttachGenericProxy(obj, objId, id, /* handleDOMProxies = */ true);
       case ProxyStubType::Generic:
-        return tryAttachGenericProxy(obj, objId, id);
+        return tryAttachGenericProxy(obj, objId, id, /* handleDOMProxies = */ false);
     }
 
     MOZ_CRASH("Unexpected ProxyStubType");
 }
 
 bool
 GetPropIRGenerator::tryAttachUnboxed(HandleObject obj, ObjOperandId objId, HandleId id)
 {
@@ -1525,18 +1585,19 @@ SetPropIRGenerator::maybeEmitIdGuard(jsi
         return;
     }
 
     MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
     emitIdGuard(setElemKeyValueId(), id);
 }
 
 GetNameIRGenerator::GetNameIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
-                                       HandleObject env, HandlePropertyName name)
-  : IRGenerator(cx, script, pc, CacheKind::GetName),
+                                       ICState::Mode mode, HandleObject env,
+                                       HandlePropertyName name)
+  : IRGenerator(cx, script, pc, CacheKind::GetName, mode),
     env_(env),
     name_(name)
 {}
 
 bool
 GetNameIRGenerator::tryAttachStub()
 {
     MOZ_ASSERT(cacheKind_ == CacheKind::GetName);
@@ -1746,18 +1807,18 @@ GetNameIRGenerator::tryAttachEnvironment
         writer.loadEnvironmentDynamicSlotResult(lastObjId, dynamicSlotOffset);
     }
 
     writer.typeMonitorResult();
     return true;
 }
 
 InIRGenerator::InIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
-                             HandleValue key, HandleObject obj)
-  : IRGenerator(cx, script, pc, CacheKind::In),
+                             ICState::Mode mode, HandleValue key, HandleObject obj)
+  : IRGenerator(cx, script, pc, CacheKind::In, mode),
     key_(key), obj_(obj)
 { }
 
 bool
 InIRGenerator::tryAttachDenseIn(uint32_t index, Int32OperandId indexId,
                                 HandleObject obj, ObjOperandId objId)
 {
     if (!obj->isNative())
@@ -1950,20 +2011,21 @@ IRGenerator::maybeGuardInt32Index(const 
         *int32IndexId = writer.guardAndGetIndexFromString(strId);
         return true;
     }
 
     return false;
 }
 
 SetPropIRGenerator::SetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
-                                       CacheKind cacheKind, bool* isTemporarilyUnoptimizable,
+                                       CacheKind cacheKind, ICState::Mode mode,
+                                       bool* isTemporarilyUnoptimizable,
                                        HandleValue lhsVal, HandleValue idVal, HandleValue rhsVal,
                                        bool needsTypeBarrier, bool maybeHasExtraIndexedProps)
-  : IRGenerator(cx, script, pc, cacheKind),
+  : IRGenerator(cx, script, pc, cacheKind, mode),
     lhsVal_(lhsVal),
     idVal_(idVal),
     rhsVal_(rhsVal),
     isTemporarilyUnoptimizable_(isTemporarilyUnoptimizable),
     typeCheckInfo_(cx, needsTypeBarrier),
     preliminaryObjectAction_(PreliminaryObjectAction::None),
     attachedTypedArrayOOBStub_(false),
     maybeHasExtraIndexedProps_(maybeHasExtraIndexedProps)
@@ -2088,16 +2150,26 @@ SetPropIRGenerator::tryAttachNativeSetSl
     // overwritten. Don't attach a stub in this case, so that we don't
     // execute another write to the property without TI seeing that write.
     EnsureTrackPropertyTypes(cx_, obj, id);
     if (!PropertyHasBeenMarkedNonConstant(obj, id)) {
         *isTemporarilyUnoptimizable_ = true;
         return false;
     }
 
+    if (mode_ == ICState::Mode::Megamorphic &&
+        cacheKind_ == CacheKind::SetProp &&
+        !IsPreliminaryObject(obj))
+    {
+        writer.megamorphicStoreSlot(objId, JSID_TO_ATOM(id)->asPropertyName(), rhsId);
+        writer.returnFromIC();
+        trackAttached("MegamorphicNativeSlot");
+        return true;
+    }
+
     maybeEmitIdGuard(id);
 
     // If we need a property type barrier (always in Baseline, sometimes in
     // Ion), guard on both the shape and the group. If Ion knows the property
     // types match, we don't need the group guard.
     NativeObject* nobj = &obj->as<NativeObject>();
     if (typeCheckInfo_.needsTypeBarrier())
         writer.guardGroup(objId, nobj->group());
@@ -2328,25 +2400,32 @@ SetPropIRGenerator::tryAttachSetter(Hand
 {
     RootedObject holder(cx_);
     RootedShape propShape(cx_);
     if (!CanAttachSetter(cx_, pc_, obj, id, &holder, &propShape, isTemporarilyUnoptimizable_))
         return false;
 
     maybeEmitIdGuard(id);
 
-    Maybe<ObjOperandId> expandoId;
-    TestMatchingReceiver(writer, obj, propShape, objId, &expandoId);
-
-    if (obj != holder) {
-        GeneratePrototypeGuards(writer, obj, holder, objId);
-
-        // Guard on the holder's shape.
-        ObjOperandId holderId = writer.loadObject(holder);
-        writer.guardShape(holderId, holder->as<NativeObject>().lastProperty());
+    // Use the megamorphic guard if we're in megamorphic mode, except if |obj|
+    // is a Window as GuardHasGetterSetter doesn't support this yet (Window may
+    // require outerizing).
+    if (mode_ == ICState::Mode::Specialized || IsWindow(obj)) {
+        Maybe<ObjOperandId> expandoId;
+        TestMatchingReceiver(writer, obj, propShape, objId, &expandoId);
+
+        if (obj != holder) {
+            GeneratePrototypeGuards(writer, obj, holder, objId);
+
+            // Guard on the holder's shape.
+            ObjOperandId holderId = writer.loadObject(holder);
+            writer.guardShape(holderId, holder->as<NativeObject>().lastProperty());
+        }
+    } else {
+        writer.guardHasGetterSetter(objId, propShape);
     }
 
     EmitCallSetterNoGuards(writer, obj, holder, propShape, objId, rhsId);
 
     trackAttached("Setter");
     return true;
 }
 
@@ -2649,23 +2728,23 @@ SetPropIRGenerator::tryAttachGenericProx
     if (!handleDOMProxies) {
         // Ensure that the incoming object is not a DOM proxy, so that we can
         // get to the specialized stubs. If handleDOMProxies is true, we were
         // unable to attach a specialized DOM stub, so we just handle all
         // proxies here.
         writer.guardNotDOMProxy(objId);
     }
 
-    if (cacheKind_ == CacheKind::SetProp) {
+    if (cacheKind_ == CacheKind::SetProp || mode_ == ICState::Mode::Specialized) {
+        maybeEmitIdGuard(id);
         writer.callProxySet(objId, id, rhsId, IsStrictSetPC(pc_));
     } else {
-        // We could call maybeEmitIdGuard here and then emit CallProxySet, but
-        // for SetElem we prefer to attach a stub that can handle any Value
-        // so we don't attach a new stub for every id.
+        // Attach a stub that handles every id.
         MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
+        MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
         writer.callProxySetByValue(objId, setElemKeyValueId(), rhsId, IsStrictSetPC(pc_));
     }
 
     writer.returnFromIC();
 
     trackAttached("GenericProxy");
     return true;
 }
@@ -2727,25 +2806,36 @@ SetPropIRGenerator::tryAttachDOMProxyUns
 
 bool
 SetPropIRGenerator::tryAttachProxy(HandleObject obj, ObjOperandId objId, HandleId id,
                                    ValOperandId rhsId)
 {
     // Don't attach a proxy stub for ops like JSOP_INITELEM.
     MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
 
-    switch (GetProxyStubType(cx_, obj, id)) {
+    ProxyStubType type = GetProxyStubType(cx_, obj, id);
+    if (type == ProxyStubType::None)
+        return false;
+
+    if (mode_ == ICState::Mode::Megamorphic)
+        return tryAttachGenericProxy(obj, objId, id, rhsId, /* handleDOMProxies = */ true);
+
+    switch (type) {
       case ProxyStubType::None:
-        return false;
+        break;
       case ProxyStubType::DOMExpando:
       case ProxyStubType::DOMShadowed:
         return tryAttachDOMProxyShadowed(obj, objId, id, rhsId);
       case ProxyStubType::DOMUnshadowed:
         if (tryAttachDOMProxyUnshadowed(obj, objId, id, rhsId))
             return true;
+        if (*isTemporarilyUnoptimizable_) {
+            // Scripted setter without JIT code. Just wait.
+            return false;
+        }
         return tryAttachGenericProxy(obj, objId, id, rhsId, /* handleDOMProxies = */ true);
       case ProxyStubType::Generic:
         return tryAttachGenericProxy(obj, objId, id, rhsId, /* handleDOMProxies = */ false);
     }
 
     MOZ_CRASH("Unexpected ProxyStubType");
 }
 
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -8,16 +8,17 @@
 #define jit_CacheIR_h
 
 #include "mozilla/Maybe.h"
 
 #include "NamespaceImports.h"
 
 #include "gc/Rooting.h"
 #include "jit/CompactBuffer.h"
+#include "jit/ICState.h"
 #include "jit/SharedIC.h"
 
 namespace js {
 namespace jit {
 
 // CacheIR is an (extremely simple) linear IR language for inline caches.
 // From this IR, we can generate machine code for Baseline or Ion IC stubs.
 //
@@ -169,21 +170,26 @@ extern const char* CacheKindNames[];
     _(GuardSpecificSymbol)                \
     _(GuardNoDetachedTypedObjects)        \
     _(GuardMagicValue)                    \
     _(GuardFrameHasNoArgumentsObject)     \
     _(GuardNoDenseElements)               \
     _(GuardNoUnboxedExpando)              \
     _(GuardAndLoadUnboxedExpando)         \
     _(GuardAndGetIndexFromString)         \
+    _(GuardHasGetterSetter)               \
     _(LoadObject)                         \
     _(LoadProto)                          \
     _(LoadEnclosingEnvironment)           \
     _(LoadWrapperTarget)                  \
                                           \
+    _(MegamorphicLoadSlotResult)          \
+    _(MegamorphicLoadSlotByValueResult)   \
+    _(MegamorphicStoreSlot)               \
+                                          \
     /* See CacheIR.cpp 'DOM proxies' comment. */ \
     _(LoadDOMExpandoValue)                \
     _(LoadDOMExpandoValueGuardGeneration) \
     _(LoadDOMExpandoValueIgnoreGeneration)\
     _(GuardDOMExpandoMissingOrGuardShape) \
                                           \
     _(StoreFixedSlot)                     \
     _(StoreDynamicSlot)                   \
@@ -412,17 +418,17 @@ class MOZ_RAII CacheIRWriter : public JS
         // For now, assert we only GC before we append stub fields.
         MOZ_RELEASE_ASSERT(stubFields_.empty());
     }
 
     size_t stubDataSize() const {
         return stubDataSize_;
     }
     void copyStubData(uint8_t* dest) const;
-    bool stubDataEqualsMaybeUpdate(uint8_t* stubData) const;
+    bool stubDataEqualsMaybeUpdate(uint8_t* stubData, bool* updated) const;
 
     bool operandIsDead(uint32_t operandId, uint32_t currentInstruction) const {
         if (operandId >= operandLastUsed_.length())
             return false;
         return currentInstruction > operandLastUsed_[operandId];
     }
     const uint8_t* codeStart() const {
         MOZ_ASSERT(!failed());
@@ -530,16 +536,21 @@ class MOZ_RAII CacheIRWriter : public JS
 
     Int32OperandId guardAndGetIndexFromString(StringOperandId str) {
         Int32OperandId res(nextOperandId_++);
         writeOpWithOperandId(CacheOp::GuardAndGetIndexFromString, str);
         writeOperandId(res);
         return res;
     }
 
+    void guardHasGetterSetter(ObjOperandId obj, Shape* shape) {
+        writeOpWithOperandId(CacheOp::GuardHasGetterSetter, obj);
+        addStubField(uintptr_t(shape), StubField::Type::Shape);
+    }
+
     void loadFrameCalleeResult() {
         writeOp(CacheOp::LoadFrameCalleeResult);
     }
     void loadFrameNumActualArgsResult() {
         writeOp(CacheOp::LoadFrameNumActualArgsResult);
     }
     void loadFrameArgumentResult(Int32OperandId index) {
         writeOpWithOperandId(CacheOp::LoadFrameArgumentResult, index);
@@ -743,16 +754,32 @@ class MOZ_RAII CacheIRWriter : public JS
     }
     void callProxySetByValue(ObjOperandId obj, ValOperandId id, ValOperandId rhs, bool strict) {
         writeOpWithOperandId(CacheOp::CallProxySetByValue, obj);
         writeOperandId(id);
         writeOperandId(rhs);
         buffer_.writeByte(uint32_t(strict));
     }
 
+    void megamorphicLoadSlotResult(ObjOperandId obj, PropertyName* name, bool handleMissing) {
+        writeOpWithOperandId(CacheOp::MegamorphicLoadSlotResult, obj);
+        addStubField(uintptr_t(name), StubField::Type::String);
+        buffer_.writeByte(uint32_t(handleMissing));
+    }
+    void megamorphicLoadSlotByValueResult(ObjOperandId obj, ValOperandId id, bool handleMissing) {
+        writeOpWithOperandId(CacheOp::MegamorphicLoadSlotByValueResult, obj);
+        writeOperandId(id);
+        buffer_.writeByte(uint32_t(handleMissing));
+    }
+    void megamorphicStoreSlot(ObjOperandId obj, PropertyName* name, ValOperandId rhs) {
+        writeOpWithOperandId(CacheOp::MegamorphicStoreSlot, obj);
+        addStubField(uintptr_t(name), StubField::Type::String);
+        writeOperandId(rhs);
+    }
+
     void loadBooleanResult(bool val) {
         writeOp(CacheOp::LoadBooleanResult);
         buffer_.writeByte(uint32_t(val));
     }
     void loadUndefinedResult() {
         writeOp(CacheOp::LoadUndefinedResult);
     }
     void loadFixedSlotResult(ObjOperandId obj, size_t offset) {
@@ -940,29 +967,31 @@ class MOZ_RAII CacheIRReader
 class MOZ_RAII IRGenerator
 {
   protected:
     CacheIRWriter writer;
     JSContext* cx_;
     HandleScript script_;
     jsbytecode* pc_;
     CacheKind cacheKind_;
+    ICState::Mode mode_;
 
     IRGenerator(const IRGenerator&) = delete;
     IRGenerator& operator=(const IRGenerator&) = delete;
 
     bool maybeGuardInt32Index(const Value& index, ValOperandId indexId,
                               uint32_t* int32Index, Int32OperandId* int32IndexId);
 
     void emitIdGuard(ValOperandId valId, jsid id);
 
     friend class CacheIRSpewer;
 
   public:
-    explicit IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind);
+    explicit IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind,
+                         ICState::Mode mode);
 
     const CacheIRWriter& writerRef() const { return writer; }
     CacheKind cacheKind() const { return cacheKind_; }
 };
 
 enum class CanAttachGetter { Yes, No };
 
 // GetPropIRGenerator generates CacheIR for a GetProp IC.
@@ -981,17 +1010,18 @@ class MOZ_RAII GetPropIRGenerator : publ
     bool tryAttachUnboxedExpando(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachTypedObject(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachObjectLength(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachModuleNamespace(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachWindowProxy(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachCrossCompartmentWrapper(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachFunction(HandleObject obj, ObjOperandId objId, HandleId id);
 
-    bool tryAttachGenericProxy(HandleObject obj, ObjOperandId objId, HandleId id);
+    bool tryAttachGenericProxy(HandleObject obj, ObjOperandId objId, HandleId id,
+                               bool handleDOMProxies);
     bool tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachDOMProxyShadowed(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachDOMProxyUnshadowed(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachProxy(HandleObject obj, ObjOperandId objId, HandleId id);
 
     bool tryAttachPrimitive(ValOperandId valId, HandleId id);
     bool tryAttachStringChar(ValOperandId valId, ValOperandId indexId);
     bool tryAttachStringLength(ValOperandId valId, HandleId id);
@@ -1007,16 +1037,18 @@ class MOZ_RAII GetPropIRGenerator : publ
                                    uint32_t index, Int32OperandId indexId);
     bool tryAttachUnboxedArrayElement(HandleObject obj, ObjOperandId objId,
                                       uint32_t index, Int32OperandId indexId);
     bool tryAttachTypedElement(HandleObject obj, ObjOperandId objId,
                                uint32_t index, Int32OperandId indexId);
 
     bool tryAttachProxyElement(HandleObject obj, ObjOperandId objId);
 
+    void attachMegamorphicNativeSlot(ObjOperandId objId, jsid id, bool handleMissing);
+
     ValOperandId getElemKeyValueId() const {
         MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
         return ValOperandId(1);
     }
 
     // No pc if idempotent, as there can be multiple bytecode locations
     // due to GVN.
     bool idempotent() const { return pc_ == nullptr; }
@@ -1025,18 +1057,18 @@ class MOZ_RAII GetPropIRGenerator : publ
     // matches |id|.
     void maybeEmitIdGuard(jsid id);
 
     void trackAttached(const char* name);
     void trackNotAttached();
 
   public:
     GetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind,
-                       bool* isTemporarilyUnoptimizable, HandleValue val,HandleValue idVal,
-                       CanAttachGetter canAttachGetter);
+                       ICState::Mode mode, bool* isTemporarilyUnoptimizable, HandleValue val,
+                       HandleValue idVal, CanAttachGetter canAttachGetter);
 
     bool tryAttachStub();
     bool tryAttachIdempotentStub();
 
     bool shouldUnlinkPreliminaryObjectStubs() const {
         return preliminaryObjectAction_ == PreliminaryObjectAction::Unlink;
     }
     bool shouldNotePreliminaryObjectStub() const {
@@ -1050,17 +1082,17 @@ class MOZ_RAII GetNameIRGenerator : publ
     HandleObject env_;
     HandlePropertyName name_;
 
     bool tryAttachGlobalNameValue(ObjOperandId objId, HandleId id);
     bool tryAttachGlobalNameGetter(ObjOperandId objId, HandleId id);
     bool tryAttachEnvironmentName(ObjOperandId objId, HandleId id);
 
   public:
-    GetNameIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
+    GetNameIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, ICState::Mode mode,
                        HandleObject env, HandlePropertyName name);
 
     bool tryAttachStub();
 };
 
 // Information used by SetProp/SetElem stubs to check/update property types.
 class MOZ_RAII PropertyTypeCheckInfo
 {
@@ -1154,18 +1186,18 @@ class MOZ_RAII SetPropIRGenerator : publ
                                      ValOperandId rhsId);
     bool tryAttachProxy(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId);
     bool tryAttachProxyElement(HandleObject obj, ObjOperandId objId, ValOperandId rhsId);
 
     void trackAttached(const char* name);
 
   public:
     SetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind,
-                       bool* isTemporarilyUnoptimizable, HandleValue lhsVal, HandleValue idVal,
-                       HandleValue rhsVal, bool needsTypeBarrier = true,
+                       ICState::Mode mode, bool* isTemporarilyUnoptimizable, HandleValue lhsVal,
+                       HandleValue idVal, HandleValue rhsVal, bool needsTypeBarrier = true,
                        bool maybeHasExtraIndexedProps = true);
 
     bool tryAttachStub();
     bool tryAttachAddSlotStub(HandleObjectGroup oldGroup, HandleShape oldShape);
     void trackNotAttached();
 
     bool shouldUnlinkPreliminaryObjectStubs() const {
         return preliminaryObjectAction_ == PreliminaryObjectAction::Unlink;
@@ -1197,17 +1229,18 @@ class MOZ_RAII InIRGenerator : public IR
                            HandleObject obj, ObjOperandId objId);
     bool tryAttachNativeInDoesNotExist(HandleId key, ValOperandId keyId,
                                        HandleObject obj, ObjOperandId objId);
 
     void trackAttached(const char* name);
     void trackNotAttached();
 
   public:
-    InIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc, HandleValue key, HandleObject obj);
+    InIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc, ICState::Mode mode, HandleValue key,
+                  HandleObject obj);
 
     bool tryAttachStub();
 };
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_CacheIR_h */
--- a/js/src/jit/CacheIRCompiler.cpp
+++ b/js/src/jit/CacheIRCompiler.cpp
@@ -956,20 +956,21 @@ jit::TraceCacheIRStub(JSTracer* trc, T* 
 
 template
 void jit::TraceCacheIRStub(JSTracer* trc, ICStub* stub, const CacheIRStubInfo* stubInfo);
 
 template
 void jit::TraceCacheIRStub(JSTracer* trc, IonICStub* stub, const CacheIRStubInfo* stubInfo);
 
 bool
-CacheIRWriter::stubDataEqualsMaybeUpdate(uint8_t* stubData) const
+CacheIRWriter::stubDataEqualsMaybeUpdate(uint8_t* stubData, bool* updated) const
 {
     MOZ_ASSERT(!failed());
 
+    *updated = false;
     const uintptr_t* stubDataWords = reinterpret_cast<const uintptr_t*>(stubData);
 
     // If DOMExpandoGeneration fields are different but all other stub fields
     // are exactly the same, we overwrite the old stub data instead of attaching
     // a new stub, as the old stub is never going to succeed. This works because
     // even Ion stubs read the DOMExpandoGeneration field from the stub instead
     // of baking it in.
     bool expandoGenerationIsDifferent = false;
@@ -985,18 +986,20 @@ CacheIRWriter::stubDataEqualsMaybeUpdate
         if (field.asInt64() != *reinterpret_cast<const uint64_t*>(stubDataWords)) {
             if (field.type() != StubField::Type::DOMExpandoGeneration)
                 return false;
             expandoGenerationIsDifferent = true;
         }
         stubDataWords += sizeof(uint64_t) / sizeof(uintptr_t);
     }
 
-    if (expandoGenerationIsDifferent)
+    if (expandoGenerationIsDifferent) {
         copyStubData(stubData);
+        *updated = true;
+    }
 
     return true;
 }
 
 HashNumber
 CacheIRStubKey::hash(const CacheIRStubKey::Lookup& l)
 {
     HashNumber hash = mozilla::HashBytes(l.code, l.length);
@@ -2197,8 +2200,60 @@ CacheIRCompiler::emitWrapResult()
     masm.branchTestPtr(Assembler::Zero, obj, obj, failure->label());
 
     // We clobbered the output register, so we have to retag.
     masm.tagValue(JSVAL_TYPE_OBJECT, obj, output.valueReg());
 
     masm.bind(&done);
     return true;
 }
+
+bool
+CacheIRCompiler::emitMegamorphicLoadSlotByValueResult()
+{
+    AutoOutputRegister output(*this);
+
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    ValueOperand idVal = allocator.useValueRegister(masm, reader.valOperandId());
+    bool handleMissing = reader.readBool();
+
+    AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure))
+        return false;
+
+    // idVal will be in vp[0], result will be stored in vp[1].
+    masm.reserveStack(sizeof(Value));
+    masm.Push(idVal);
+    masm.moveStackPtrTo(idVal.scratchReg());
+
+    LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs());
+    volatileRegs.takeUnchecked(scratch);
+    volatileRegs.takeUnchecked(idVal);
+    masm.PushRegsInMask(volatileRegs);
+
+    masm.setupUnalignedABICall(scratch);
+    masm.loadJSContext(scratch);
+    masm.passABIArg(scratch);
+    masm.passABIArg(obj);
+    masm.passABIArg(idVal.scratchReg());
+    if (handleMissing)
+        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, (GetNativeDataPropertyByValue<true>)));
+    else
+        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, (GetNativeDataPropertyByValue<false>)));
+    masm.mov(ReturnReg, scratch);
+    masm.PopRegsInMask(volatileRegs);
+
+    masm.Pop(idVal);
+
+    Label ok;
+    uint32_t framePushed = masm.framePushed();
+    masm.branchIfTrueBool(scratch, &ok);
+    masm.adjustStack(sizeof(Value));
+    masm.jump(failure->label());
+
+    masm.bind(&ok);
+    masm.setFramePushed(framePushed);
+    masm.loadTypedOrValue(Address(masm.getStackPointer(), 0), output);
+    masm.adjustStack(sizeof(Value));
+    return true;
+}
--- a/js/src/jit/CacheIRCompiler.h
+++ b/js/src/jit/CacheIRCompiler.h
@@ -46,16 +46,17 @@ namespace jit {
     _(LoadStringCharResult)               \
     _(LoadArgumentsObjectArgResult)       \
     _(LoadDenseElementResult)             \
     _(LoadDenseElementHoleResult)         \
     _(LoadDenseElementExistsResult)       \
     _(LoadDenseElementHoleExistsResult)   \
     _(LoadUnboxedArrayElementResult)      \
     _(LoadTypedElementResult)             \
+    _(MegamorphicLoadSlotByValueResult)   \
     _(WrapResult)
 
 // Represents a Value on the Baseline frame's expression stack. Slot 0 is the
 // value on top of the stack (the most recently pushed value), slot 1 is the
 // value pushed before that, etc.
 class BaselineFrameSlot
 {
     uint32_t slot_;
new file mode 100644
--- /dev/null
+++ b/js/src/jit/ICState.h
@@ -0,0 +1,127 @@
+/* -*- 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 jit_ICState_h
+#define jit_ICState_h
+
+#include "jit/JitOptions.h"
+
+namespace js {
+namespace jit {
+
+// ICState stores information about a Baseline or Ion IC.
+class ICState
+{
+  public:
+    // When we attach the maximum number of stubs, we discard all stubs and
+    // transition the IC to Megamorphic to attach stubs that are more generic
+    // (handle more cases). If we again attach the maximum number of stubs, we
+    // transition to Generic and (depending on the IC) will either attach a
+    // single stub that handles everything or stop attaching new stubs.
+    //
+    // We also transition to Generic when we repeatedly fail to attach a stub,
+    // to avoid wasting time trying.
+    enum class Mode : uint8_t { Specialized = 0, Megamorphic, Generic };
+
+  private:
+    Mode mode_;
+
+    // Number of optimized stubs currently attached to this IC.
+    uint8_t numOptimizedStubs_;
+
+    // Number of times we failed to attach a stub.
+    uint8_t numFailures_;
+
+    // This is only used for shared Baseline ICs and stored here to save space.
+    bool invalid_ : 1;
+
+    static const size_t MaxOptimizedStubs = 6;
+
+    void transition(Mode mode) {
+        MOZ_ASSERT(mode > mode_);
+        mode_ = mode;
+        numFailures_ = 0;
+    }
+
+    MOZ_ALWAYS_INLINE size_t maxFailures() const {
+        // Allow more failures if we attached stubs.
+        static_assert(MaxOptimizedStubs == 6,
+                      "numFailures_/maxFailures should fit in uint8_t");
+        size_t res = 5 + size_t(40) * numOptimizedStubs_;
+        MOZ_ASSERT(res <= UINT8_MAX, "numFailures_ should not overflow");
+        return res;
+    }
+
+  public:
+    ICState()
+      : invalid_(false)
+    {
+        reset();
+    }
+
+    Mode mode() const { return mode_; }
+    size_t numOptimizedStubs() const { return numOptimizedStubs_; }
+
+    MOZ_ALWAYS_INLINE bool canAttachStub() const {
+        MOZ_ASSERT(numOptimizedStubs_ <= MaxOptimizedStubs);
+        if (mode_ == Mode::Generic || JitOptions.disableCacheIR)
+            return false;
+        return true;
+    }
+
+    bool invalid() const { return invalid_; }
+    void setInvalid() { invalid_ = true; }
+
+    // If this returns true, we transitioned to a new mode and the caller
+    // should discard all stubs.
+    MOZ_MUST_USE MOZ_ALWAYS_INLINE bool maybeTransition() {
+        MOZ_ASSERT(numOptimizedStubs_ <= MaxOptimizedStubs);
+        if (mode_ == Mode::Generic)
+            return false;
+        if (numOptimizedStubs_ < MaxOptimizedStubs && numFailures_ < maxFailures())
+            return false;
+        if (numFailures_ == maxFailures() || mode_ == Mode::Megamorphic) {
+            transition(Mode::Generic);
+            return true;
+        }
+        MOZ_ASSERT(mode_ == Mode::Specialized);
+        transition(Mode::Megamorphic);
+        return true;
+    }
+    void reset() {
+        mode_ = Mode::Specialized;
+        numOptimizedStubs_ = 0;
+        numFailures_ = 0;
+    }
+    void trackAttached() {
+        // We'd like to assert numOptimizedStubs_ < MaxOptimizedStubs, but
+        // since this code is also used for non-CacheIR Baseline stubs, assert
+        // < 16 for now. Note that we do have the stronger assert in other
+        // methods, because they are only used by CacheIR ICs.
+        MOZ_ASSERT(numOptimizedStubs_ < 16);
+        numOptimizedStubs_++;
+        numFailures_ = 0;
+    }
+    void trackNotAttached() {
+        // Note: we can't assert numFailures_ < maxFailures() because
+        // maxFailures() depends on numOptimizedStubs_ and it's possible a
+        // GC discarded stubs before we got here.
+        numFailures_++;
+        MOZ_ASSERT(numFailures_ > 0, "numFailures_ should not overflow");
+    }
+    void trackUnlinkedStub() {
+        MOZ_ASSERT(numOptimizedStubs_ > 0);
+        numOptimizedStubs_--;
+    }
+    void trackUnlinkedAllStubs() {
+        numOptimizedStubs_ = 0;
+    }
+};
+
+} // namespace jit
+} // namespace js
+
+#endif /* jit_ICState_h */
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -10792,16 +10792,25 @@ IonBuilder::getPropTryCommonGetter(bool*
                 // If it's an own property or type information is bad, we can still
                 // optimize the getter if we shape guard.
                 obj = addShapeGuardsForGetterSetter(obj, foundProto, lastProperty,
                                                     receivers, convertUnboxedGroups,
                                                     isOwnProperty);
                 if (!obj)
                     return abort(AbortReason::Alloc);
             }
+        } else if (inspector->megamorphicGetterSetterFunction(pc, /* isGetter = */ true,
+                                                              &commonGetter))
+        {
+            // Try to use TI to guard on this getter.
+            if (!testCommonGetterSetter(objTypes, name, /* isGetter = */ true,
+                                        commonGetter, &guard))
+            {
+                return Ok();
+            }
         } else {
             // The Baseline IC didn't have any information we can use.
             return Ok();
         }
     }
 
     bool isDOM = objTypes && objTypes->isDOMClass(constraints());
     if (isDOM)
@@ -11344,16 +11353,25 @@ IonBuilder::setPropTryCommonSetter(bool*
                 // If it's an own property or type information is bad, we can still
                 // optimize the setter if we shape guard.
                 obj = addShapeGuardsForGetterSetter(obj, foundProto, lastProperty,
                                                     receivers, convertUnboxedGroups,
                                                     isOwnProperty);
                 if (!obj)
                     return abort(AbortReason::Alloc);
             }
+        } else if (inspector->megamorphicGetterSetterFunction(pc, /* isGetter = */ false,
+                                                              &commonSetter))
+        {
+            // Try to use TI to guard on this setter.
+            if (!testCommonGetterSetter(objTypes, name, /* isGetter = */ false,
+                                        commonSetter, &guard))
+            {
+                return Ok();
+            }
         } else {
             // The Baseline IC didn't have any information we can use.
             return Ok();
         }
     }
 
     // Emit common setter.
 
--- a/js/src/jit/IonCacheIRCompiler.cpp
+++ b/js/src/jit/IonCacheIRCompiler.cpp
@@ -650,16 +650,136 @@ IonCacheIRCompiler::emitLoadDynamicSlotR
 
     AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
     masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), scratch);
     masm.loadTypedOrValue(Address(scratch, offset), output);
     return true;
 }
 
 bool
+IonCacheIRCompiler::emitMegamorphicLoadSlotResult()
+{
+    AutoOutputRegister output(*this);
+
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    PropertyName* name = stringStubField(reader.stubOffset())->asAtom().asPropertyName();
+    bool handleMissing = reader.readBool();
+
+    AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
+    AutoScratchRegister scratch2(allocator, masm);
+    AutoScratchRegister scratch3(allocator, masm);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure))
+        return false;
+
+    masm.Push(UndefinedValue());
+    masm.moveStackPtrTo(scratch3.get());
+
+    LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs());
+    volatileRegs.takeUnchecked(scratch1);
+    volatileRegs.takeUnchecked(scratch2);
+    volatileRegs.takeUnchecked(scratch3);
+    masm.PushRegsInMask(volatileRegs);
+
+    masm.setupUnalignedABICall(scratch1);
+    masm.loadJSContext(scratch1);
+    masm.passABIArg(scratch1);
+    masm.passABIArg(obj);
+    masm.movePtr(ImmGCPtr(name), scratch2);
+    masm.passABIArg(scratch2);
+    masm.passABIArg(scratch3);
+    if (handleMissing)
+        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, (GetNativeDataProperty<true>)));
+    else
+        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, (GetNativeDataProperty<false>)));
+    masm.mov(ReturnReg, scratch2);
+    masm.PopRegsInMask(volatileRegs);
+
+    masm.loadTypedOrValue(Address(masm.getStackPointer(), 0), output);
+    masm.adjustStack(sizeof(Value));
+
+    masm.branchIfFalseBool(scratch2, failure->label());
+    return true;
+}
+
+bool
+IonCacheIRCompiler::emitMegamorphicStoreSlot()
+{
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    PropertyName* name = stringStubField(reader.stubOffset())->asAtom().asPropertyName();
+    ValueOperand val = allocator.useValueRegister(masm, reader.valOperandId());
+
+    AutoScratchRegister scratch1(allocator, masm);
+    AutoScratchRegister scratch2(allocator, masm);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure))
+        return false;
+
+    masm.Push(val);
+    masm.moveStackPtrTo(val.scratchReg());
+
+    LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs());
+    volatileRegs.takeUnchecked(scratch1);
+    volatileRegs.takeUnchecked(scratch2);
+    volatileRegs.takeUnchecked(val);
+    masm.PushRegsInMask(volatileRegs);
+
+    masm.setupUnalignedABICall(scratch1);
+    masm.loadJSContext(scratch1);
+    masm.passABIArg(scratch1);
+    masm.passABIArg(obj);
+    masm.movePtr(ImmGCPtr(name), scratch2);
+    masm.passABIArg(scratch2);
+    masm.passABIArg(val.scratchReg());
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, SetNativeDataProperty));
+    masm.mov(ReturnReg, scratch1);
+    masm.PopRegsInMask(volatileRegs);
+
+    masm.loadValue(Address(masm.getStackPointer(), 0), val);
+    masm.adjustStack(sizeof(Value));
+
+    masm.branchIfFalseBool(scratch1, failure->label());
+    return true;
+}
+
+bool
+IonCacheIRCompiler::emitGuardHasGetterSetter()
+{
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    Shape* shape = shapeStubField(reader.stubOffset());
+
+    AutoScratchRegister scratch1(allocator, masm);
+    AutoScratchRegister scratch2(allocator, masm);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure))
+        return false;
+
+    LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs());
+    volatileRegs.takeUnchecked(scratch1);
+    volatileRegs.takeUnchecked(scratch2);
+    masm.PushRegsInMask(volatileRegs);
+
+    masm.setupUnalignedABICall(scratch1);
+    masm.loadJSContext(scratch1);
+    masm.passABIArg(scratch1);
+    masm.passABIArg(obj);
+    masm.movePtr(ImmGCPtr(shape), scratch2);
+    masm.passABIArg(scratch2);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ObjectHasGetterSetter));
+    masm.mov(ReturnReg, scratch1);
+    masm.PopRegsInMask(volatileRegs);
+
+    masm.branchIfFalseBool(scratch1, failure->label());
+    return true;
+}
+
+bool
 IonCacheIRCompiler::emitCallScriptedGetterResult()
 {
     AutoSaveLiveRegisters save(*this);
     AutoOutputRegister output(*this);
 
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     JSFunction* target = &objectStubField(reader.stubOffset())->as<JSFunction>();
     AutoScratchRegister scratch(allocator, masm);
@@ -1761,31 +1881,34 @@ IonCacheIRCompiler::emitLoadDOMExpandoVa
                   scratch2,
                   failure->label());
 
     // Load expandoAndGeneration->expando into the output Value register.
     masm.loadValue(Address(output.scratchReg(), ExpandoAndGeneration::offsetOfExpando()), output);
     return true;
 }
 
-bool
+void
 IonIC::attachCacheIRStub(JSContext* cx, const CacheIRWriter& writer, CacheKind kind,
-                         IonScript* ionScript, const PropertyTypeCheckInfo* typeCheckInfo)
+                         IonScript* ionScript, bool* attached,
+                         const PropertyTypeCheckInfo* typeCheckInfo)
 {
     // We shouldn't GC or report OOM (or any other exception) here.
     AutoAssertNoPendingException aanpe(cx);
     JS::AutoCheckCannotGC nogc;
 
+    MOZ_ASSERT(!*attached);
+
     // SetProp/SetElem stubs must have non-null typeCheckInfo.
     MOZ_ASSERT(!!typeCheckInfo == (kind == CacheKind::SetProp || kind == CacheKind::SetElem));
 
     // Do nothing if the IR generator failed or triggered a GC that invalidated
     // the script.
     if (writer.failed() || ionScript->invalidated())
-        return false;
+        return;
 
     JitZone* jitZone = cx->zone()->jitZone();
     uint32_t stubDataOffset = sizeof(IonICStub);
 
     // Try to reuse a previously-allocated CacheIRStubInfo.
     CacheIRStubKey::Lookup lookup(kind, ICStubEngine::IonIC,
                                   writer.codeStart(), writer.codeLength());
     CacheIRStubInfo* stubInfo = jitZone->getIonCacheIRStubInfo(lookup);
@@ -1795,55 +1918,62 @@ IonIC::attachCacheIRStub(JSContext* cx, 
         // the stub info HashSet, so we don't have to worry about freeing
         // it below.
 
         // For Ion ICs, we don't track/use the makesGCCalls flag, so just pass true.
         bool makesGCCalls = true;
         stubInfo = CacheIRStubInfo::New(kind, ICStubEngine::IonIC, makesGCCalls,
                                         stubDataOffset, writer);
         if (!stubInfo)
-            return false;
+            return;
 
         CacheIRStubKey key(stubInfo);
         if (!jitZone->putIonCacheIRStubInfo(lookup, key))
-            return false;
+            return;
     }
 
     MOZ_ASSERT(stubInfo);
 
     // Ensure we don't attach duplicate stubs. This can happen if a stub failed
     // for some reason and the IR generator doesn't check for exactly the same
     // conditions.
     for (IonICStub* stub = firstStub_; stub; stub = stub->next()) {
         if (stub->stubInfo() != stubInfo)
             continue;
-        if (!writer.stubDataEqualsMaybeUpdate(stub->stubDataStart()))
+        bool updated = false;
+        if (!writer.stubDataEqualsMaybeUpdate(stub->stubDataStart(), &updated))
             continue;
-        return true;
+        if (updated || (typeCheckInfo && typeCheckInfo->needsTypeBarrier())) {
+            // We updated a stub or have a stub that requires property type
+            // checks. In this case the stub will likely handle more cases in
+            // the future and we shouldn't deoptimize.
+            *attached = true;
+        }
+        return;
     }
 
     size_t bytesNeeded = stubInfo->stubDataOffset() + stubInfo->stubDataSize();
 
     // Allocate the IonICStub in the optimized stub space. Ion stubs and
     // CacheIRStubInfo instances for Ion stubs can be purged on GC. That's okay
     // because the stub code is rooted separately when we make a VM call, and
     // stub code should never access the IonICStub after making a VM call. The
     // IonICStub::poison method poisons the stub to catch bugs in this area.
     ICStubSpace* stubSpace = cx->zone()->jitZone()->optimizedStubSpace();
     void* newStubMem = stubSpace->alloc(bytesNeeded);
     if (!newStubMem)
-        return false;
+        return;
 
     IonICStub* newStub = new(newStubMem) IonICStub(fallbackLabel_.raw(), stubInfo);
     writer.copyStubData(newStub->stubDataStart());
 
     JitContext jctx(cx, nullptr);
     IonCacheIRCompiler compiler(cx, writer, this, ionScript, newStub, typeCheckInfo);
     if (!compiler.init())
-        return false;
+        return;
 
     JitCode* code = compiler.compile();
     if (!code)
-        return false;
+        return;
 
     attachStub(newStub, code);
-    return true;
+    *attached = true;
 }
--- a/js/src/jit/IonIC.cpp
+++ b/js/src/jit/IonIC.cpp
@@ -48,17 +48,17 @@ IonIC::scratchRegisterForEntryJump()
       case CacheKind::In:
         MOZ_CRASH("Baseline-specific for now");
     }
 
     MOZ_CRASH("Invalid kind");
 }
 
 void
-IonIC::reset(Zone* zone)
+IonIC::discardStubs(Zone* zone)
 {
     if (firstStub_ && zone->needsIncrementalBarrier()) {
         // We are removing edges from IonIC to gcthings. Perform one final trace
         // of the stub for incremental GC, as it must know about those edges.
         trace(zone->barrierTracer());
     }
 
 #ifdef JS_CRASH_DIAGNOSTICS
@@ -67,17 +67,24 @@ IonIC::reset(Zone* zone)
         IonICStub* next = stub->next();
         stub->poison();
         stub = next;
     }
 #endif
 
     firstStub_ = nullptr;
     codeRaw_ = fallbackLabel_.raw();
-    numStubs_ = 0;
+    state_.trackUnlinkedAllStubs();
+}
+
+void
+IonIC::reset(Zone* zone)
+{
+    discardStubs(zone);
+    state_.reset();
 }
 
 void
 IonIC::trace(JSTracer* trc)
 {
     if (script_)
         TraceManuallyBarrieredEdge(trc, &script_, "IonIC::script_");
 
@@ -102,69 +109,48 @@ IonIC::togglePreBarriers(bool enabled, R
         JitCode* code = JitCode::FromExecutable(nextCodeRaw);
         code->togglePreBarriers(enabled, reprotect);
         nextCodeRaw = stub->nextCodeRaw();
     }
 
     MOZ_ASSERT(nextCodeRaw == fallbackLabel_.raw());
 }
 
-void
-IonGetPropertyIC::maybeDisable(Zone* zone, bool attached)
-{
-    if (attached) {
-        failedUpdates_ = 0;
-        return;
-    }
-
-    if (!canAttachStub() && kind() == CacheKind::GetProp) {
-        // Don't disable the cache (and discard stubs) if we have a GETPROP and
-        // attached the maximum number of stubs. This can happen when JS code
-        // uses an AST-like data structure and accesses a field of a "base
-        // class", like node.nodeType. This should be temporary until we handle
-        // this case better, see bug 1107515.
-        return;
-    }
-
-    if (++failedUpdates_ > MAX_FAILED_UPDATES) {
-        JitSpew(JitSpew_IonIC, "Disable inline cache");
-        disable(zone);
-    }
-}
-
 /* static */ bool
 IonGetPropertyIC::update(JSContext* cx, HandleScript outerScript, IonGetPropertyIC* ic,
 			 HandleValue val, HandleValue idVal, MutableHandleValue res)
 {
     // Override the return value if we are invalidated (bug 728188).
     IonScript* ionScript = outerScript->ionScript();
     AutoDetectInvalidation adi(cx, res, ionScript);
 
     // If the IC is idempotent, we will redo the op in the interpreter.
     if (ic->idempotent())
         adi.disable();
 
+    if (ic->state().maybeTransition())
+        ic->discardStubs(cx->zone());
+
     bool attached = false;
-    if (!JitOptions.disableCacheIR && !ic->disabled()) {
-        if (ic->canAttachStub()) {
-            // IonBuilder calls PropertyReadNeedsTypeBarrier to determine if it
-            // needs a type barrier. Unfortunately, PropertyReadNeedsTypeBarrier
-            // does not account for getters, so we should only attach a getter
-            // stub if we inserted a type barrier.
-            CanAttachGetter canAttachGetter =
-                ic->monitoredResult() ? CanAttachGetter::Yes : CanAttachGetter::No;
-            jsbytecode* pc = ic->idempotent() ? nullptr : ic->pc();
-            bool isTemporarilyUnoptimizable;
-            GetPropIRGenerator gen(cx, outerScript, pc, ic->kind(), &isTemporarilyUnoptimizable,
-                                   val, idVal, canAttachGetter);
-            if (ic->idempotent() ? gen.tryAttachIdempotentStub() : gen.tryAttachStub()) {
-                attached = ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript);
-            }
-        }
-        ic->maybeDisable(cx->zone(), attached);
+    if (ic->state().canAttachStub()) {
+        // IonBuilder calls PropertyReadNeedsTypeBarrier to determine if it
+        // needs a type barrier. Unfortunately, PropertyReadNeedsTypeBarrier
+        // does not account for getters, so we should only attach a getter
+        // stub if we inserted a type barrier.
+        CanAttachGetter canAttachGetter =
+            ic->monitoredResult() ? CanAttachGetter::Yes : CanAttachGetter::No;
+        jsbytecode* pc = ic->idempotent() ? nullptr : ic->pc();
+        bool isTemporarilyUnoptimizable = false;
+        GetPropIRGenerator gen(cx, outerScript, pc, ic->kind(), ic->state().mode(),
+                               &isTemporarilyUnoptimizable, val, idVal, canAttachGetter);
+        if (ic->idempotent() ? gen.tryAttachIdempotentStub() : gen.tryAttachStub())
+            ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript, &attached);
+
+        if (!attached && !isTemporarilyUnoptimizable)
+            ic->state().trackNotAttached();
     }
 
     if (!attached && ic->idempotent()) {
         // Invalidate the cache if the property was not found, or was found on
         // a non-native object. This ensures:
         // 1) The property read has no observable side-effects.
         // 2) There's no need to dynamically monitor the return type. This would
         //    be complicated since (due to GVN) there can be multiple pc's
@@ -205,36 +191,42 @@ IonGetPropertyIC::update(JSContext* cx, 
 IonSetPropertyIC::update(JSContext* cx, HandleScript outerScript, IonSetPropertyIC* ic,
 			 HandleObject obj, HandleValue idVal, HandleValue rhs)
 {
     RootedShape oldShape(cx);
     RootedObjectGroup oldGroup(cx);
     IonScript* ionScript = outerScript->ionScript();
 
     bool attached = false;
-    if (!JitOptions.disableCacheIR && ic->canAttachStub()) {
+    bool isTemporarilyUnoptimizable = false;
+
+    if (ic->state().maybeTransition())
+        ic->discardStubs(cx->zone());
+
+    if (ic->state().canAttachStub()) {
         oldShape = obj->maybeShape();
         oldGroup = JSObject::getGroup(cx, obj);
         if (!oldGroup)
             return false;
         if (obj->is<UnboxedPlainObject>()) {
             MOZ_ASSERT(!oldShape);
             if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
                 oldShape = expando->lastProperty();
         }
 
         RootedValue objv(cx, ObjectValue(*obj));
         RootedScript script(cx, ic->script());
         jsbytecode* pc = ic->pc();
         bool isTemporarilyUnoptimizable;
-        SetPropIRGenerator gen(cx, script, pc, ic->kind(), &isTemporarilyUnoptimizable,
+        SetPropIRGenerator gen(cx, script, pc, ic->kind(), ic->state().mode(),
+                               &isTemporarilyUnoptimizable,
                                objv, idVal, rhs, ic->needsTypeBarrier(), ic->guardHoles());
         if (gen.tryAttachStub()) {
-            attached = ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                             ionScript, gen.typeCheckInfo());
+            ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript, &attached,
+                                  gen.typeCheckInfo());
         }
     }
 
     jsbytecode* pc = ic->pc();
     if (ic->kind() == CacheKind::SetElem) {
         if (IsPropertyInitOp(JSOp(*pc))) {
             if (!InitElemOperation(cx, pc, obj, idVal, rhs))
                 return false;
@@ -252,51 +244,53 @@ IonSetPropertyIC::update(JSContext* cx, 
             InitGlobalLexicalOperation(cx, &cx->global()->lexicalEnvironment(), script, pc, rhs);
         } else {
             RootedPropertyName name(cx, idVal.toString()->asAtom().asPropertyName());
             if (!SetProperty(cx, obj, name, rhs, ic->strict(), pc))
                 return false;
         }
     }
 
-    if (!attached && !JitOptions.disableCacheIR && ic->canAttachStub()) {
+    if (!attached && ic->state().canAttachStub()) {
         RootedValue objv(cx, ObjectValue(*obj));
         RootedScript script(cx, ic->script());
         jsbytecode* pc = ic->pc();
-        bool isTemporarilyUnoptimizable;
-        SetPropIRGenerator gen(cx, script, pc, ic->kind(), &isTemporarilyUnoptimizable,
+        SetPropIRGenerator gen(cx, script, pc, ic->kind(), ic->state().mode(),
+                               &isTemporarilyUnoptimizable,
                                objv, idVal, rhs, ic->needsTypeBarrier(), ic->guardHoles());
         if (gen.tryAttachAddSlotStub(oldGroup, oldShape)) {
-            attached = ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                             ionScript, gen.typeCheckInfo());
+            ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript, &attached,
+                                  gen.typeCheckInfo());
         } else {
             gen.trackNotAttached();
         }
+
+        if (!attached && !isTemporarilyUnoptimizable)
+            ic->state().trackNotAttached();
     }
 
     return true;
 }
 
 uint8_t*
 IonICStub::stubDataStart()
 {
     return reinterpret_cast<uint8_t*>(this) + stubInfo_->stubDataOffset();
 }
 
 void
 IonIC::attachStub(IonICStub* newStub, JitCode* code)
 {
-    MOZ_ASSERT(canAttachStub());
     MOZ_ASSERT(newStub);
     MOZ_ASSERT(code);
 
     if (firstStub_) {
         IonICStub* last = firstStub_;
         while (IonICStub* next = last->next())
             last = next;
         last->setNext(newStub, code);
     } else {
         firstStub_ = newStub;
         codeRaw_ = code->raw();
     }
 
-    numStubs_++;
+    state_.trackAttached();
 }
--- a/js/src/jit/IonIC.h
+++ b/js/src/jit/IonIC.h
@@ -74,32 +74,30 @@ class IonIC
     // The OOL path that calls the IC's update function.
     CodeLocationLabel fallbackLabel_;
 
     // Location of this IC, nullptr for idempotent caches.
     JSScript* script_;
     jsbytecode* pc_;
 
     CacheKind kind_;
-    uint8_t numStubs_;
     bool idempotent_ : 1;
-    bool disabled_ : 1;
+    ICState state_;
 
   protected:
     explicit IonIC(CacheKind kind)
       : codeRaw_(nullptr),
         firstStub_(nullptr),
         rejoinLabel_(),
         fallbackLabel_(),
         script_(nullptr),
         pc_(nullptr),
         kind_(kind),
-        numStubs_(0),
         idempotent_(false),
-        disabled_(false)
+        state_()
     {}
 
     void attachStub(IonICStub* newStub, JitCode* code);
 
   public:
     void setScriptedLocation(JSScript* script, jsbytecode* pc) {
         MOZ_ASSERT(!script_ && !pc_);
         MOZ_ASSERT(script && pc);
@@ -107,29 +105,25 @@ class IonIC
         pc_ = pc;
     }
 
     JSScript* script() const { MOZ_ASSERT(script_); return script_; }
     jsbytecode* pc() const { MOZ_ASSERT(pc_); return pc_; }
 
     CodeLocationLabel rejoinLabel() const { return rejoinLabel_; }
 
-    static const size_t MAX_STUBS = 16;
-
-    bool canAttachStub() const { return numStubs_ < MAX_STUBS; }
+    // Discard all stubs.
+    void discardStubs(Zone* zone);
 
-    void disable(Zone* zone) {
-        reset(zone);
-        disabled_ = true;
-    }
+    // Discard all stubs and reset the ICState.
+    void reset(Zone* zone);
 
-    bool disabled() const { return disabled_; }
-
-    // Discard all stubs.
-    void reset(Zone* zone);
+    ICState& state() {
+        return state_;
+    }
 
     void togglePreBarriers(bool enabled, ReprotectCode reprotect);
 
     CacheKind kind() const { return kind_; }
     uint8_t** codeRawPtr() { return &codeRaw_; }
 
     bool idempotent() const { return idempotent_; }
     void setIdempotent() { idempotent_ = true; }
@@ -149,61 +143,55 @@ class IonIC
     void updateBaseAddress(JitCode* code, MacroAssembler& masm);
 
     // Returns the Register to use as scratch when entering IC stubs. This
     // should either be an output register or a temp.
     Register scratchRegisterForEntryJump();
 
     void trace(JSTracer* trc);
 
-    bool attachCacheIRStub(JSContext* cx, const CacheIRWriter& writer, CacheKind kind,
-                           IonScript* ionScript,
+    void attachCacheIRStub(JSContext* cx, const CacheIRWriter& writer, CacheKind kind,
+                           IonScript* ionScript, bool* attached,
                            const PropertyTypeCheckInfo* typeCheckInfo = nullptr);
 };
 
 class IonGetPropertyIC : public IonIC
 {
     LiveRegisterSet liveRegs_;
 
     TypedOrValueRegister value_;
     ConstantOrRegister id_;
     TypedOrValueRegister output_;
     Register maybeTemp_; // Might be InvalidReg.
 
-    static const size_t MAX_FAILED_UPDATES = 16;
-    uint16_t failedUpdates_;
-
     bool monitoredResult_ : 1;
     bool allowDoubleResult_ : 1;
 
   public:
     IonGetPropertyIC(CacheKind kind, LiveRegisterSet liveRegs, TypedOrValueRegister value,
                      const ConstantOrRegister& id, TypedOrValueRegister output, Register maybeTemp,
                      bool monitoredResult, bool allowDoubleResult)
       : IonIC(kind),
         liveRegs_(liveRegs),
         value_(value),
         id_(id),
         output_(output),
         maybeTemp_(maybeTemp),
-        failedUpdates_(0),
         monitoredResult_(monitoredResult),
         allowDoubleResult_(allowDoubleResult)
     { }
 
     bool monitoredResult() const { return monitoredResult_; }
     TypedOrValueRegister value() const { return value_; }
     ConstantOrRegister id() const { return id_; }
     TypedOrValueRegister output() const { return output_; }
     Register maybeTemp() const { return maybeTemp_; }
     LiveRegisterSet liveRegs() const { return liveRegs_; }
     bool allowDoubleResult() const { return allowDoubleResult_; }
 
-    void maybeDisable(Zone* zone, bool attached);
-
     static MOZ_MUST_USE bool update(JSContext* cx, HandleScript outerScript, IonGetPropertyIC* ic,
                                     HandleValue val, HandleValue idVal, MutableHandleValue res);
 };
 
 class IonSetPropertyIC : public IonIC
 {
     LiveRegisterSet liveRegs_;
 
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -170,17 +170,16 @@ ICStub::NonCacheIRStubMakesGCCalls(Kind 
       case Call_AnyScripted:
       case Call_Native:
       case Call_ClassHook:
       case Call_ScriptedApplyArray:
       case Call_ScriptedApplyArguments:
       case Call_ScriptedFunCall:
       case Call_StringSplit:
       case WarmUpCounter_Fallback:
-      case GetProp_Generic:
       case RetSub_Fallback:
       // These two fallback stubs don't actually make non-tail calls,
       // but the fallback code for the bailout path needs to pop the stub frame
       // pushed during the bailout.
       case GetProp_Fallback:
       case SetProp_Fallback:
         return true;
       default:
@@ -350,18 +349,17 @@ ICFallbackStub::unlinkStub(Zone* zone, I
             MOZ_ASSERT(prev->next() == stub);
             prev->setNext(stub->next());
         } else {
             MOZ_ASSERT(icEntry()->firstStub() == stub);
             icEntry()->setFirstStub(stub->next());
         }
     }
 
-    MOZ_ASSERT(numOptimizedStubs_ > 0);
-    numOptimizedStubs_--;
+    state_.trackUnlinkedStub();
 
     if (zone->needsIncrementalBarrier()) {
         // We are removing edges from ICStub to gcthings. Perform one final trace
         // of the stub for incremental GC, as it must know about those edges.
         stub->trace(zone->barrierTracer());
     }
 
     if (stub->makesGCCalls() && stub->isMonitored()) {
@@ -388,16 +386,23 @@ ICFallbackStub::unlinkStubsWithKind(JSCo
 {
     for (ICStubIterator iter = beginChain(); !iter.atEnd(); iter++) {
         if (iter->kind() == kind)
             iter.unlink(cx);
     }
 }
 
 void
+ICFallbackStub::discardStubs(JSContext* cx)
+{
+    for (ICStubIterator iter = beginChain(); !iter.atEnd(); iter++)
+        iter.unlink(cx);
+}
+
+void
 ICTypeMonitor_Fallback::resetMonitorStubChain(Zone* zone)
 {
     if (zone->needsIncrementalBarrier()) {
         // We are removing edges from monitored stubs to gcthings (JitCode).
         // Perform one final trace of all monitor stubs for incremental GC,
         // as it must know about those edges.
         for (ICStub* s = firstMonitorStub_; !s->isTypeMonitor_Fallback(); s = s->next())
             s->trace(zone->barrierTracer());
@@ -1988,62 +1993,46 @@ DoGetPropFallback(JSContext* cx, Baselin
     JSOp op = JSOp(*pc);
     FallbackICSpew(cx, stub, "GetProp(%s)", CodeName[op]);
 
     MOZ_ASSERT(op == JSOP_GETPROP ||
                op == JSOP_CALLPROP ||
                op == JSOP_LENGTH ||
                op == JSOP_GETBOUNDNAME);
 
-    // Grab our old shape before it goes away.
-    RootedShape oldShape(cx);
-    if (val.isObject())
-        oldShape = val.toObject().maybeShape();
-
-    bool attached = false;
+    RootedPropertyName name(cx, script->getName(pc));
+
     // 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;
 
-    RootedPropertyName name(cx, 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 && !stub.invalid()) {
-        // 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 && !JitOptions.disableCacheIR) {
+    if (stub->state().maybeTransition())
+        stub->discardStubs(cx);
+
+    bool attached = false;
+    if (stub->state().canAttachStub()) {
         RootedValue idVal(cx, StringValue(name));
-        GetPropIRGenerator gen(cx, script, pc, CacheKind::GetProp, &isTemporarilyUnoptimizable,
-                               val, idVal, CanAttachGetter::Yes);
+        GetPropIRGenerator gen(cx, script, pc, CacheKind::GetProp, stub->state().mode(),
+                               &isTemporarilyUnoptimizable, val, idVal, CanAttachGetter::Yes);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         ICStubEngine::Baseline, script,
-                                                        stub);
+                                                        stub, &attached);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
-                attached = true;
                 if (gen.shouldNotePreliminaryObjectStub())
                     newStub->toCacheIR_Monitored()->notePreliminaryObject();
                 else if (gen.shouldUnlinkPreliminaryObjectStubs())
                     StripPreliminaryObjectStubs(cx, stub);
             }
         }
+        if (!attached && !isTemporarilyUnoptimizable)
+            stub->state().trackNotAttached();
     }
 
     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.
@@ -2117,70 +2106,16 @@ void
 ICGetProp_Fallback::Compiler::postGenerateStubCode(MacroAssembler& masm, Handle<JitCode*> code)
 {
     if (engine_ == Engine::Baseline) {
         void* address = code->raw() + returnOffset_;
         cx->compartment()->jitCompartment()->initBaselineGetPropReturnAddr(address);
     }
 }
 
-/* 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)
-{
-    ICFallbackStub* fallback = stub->getChainFallback();
-    JSScript* script = frame->script();
-    jsbytecode* pc = fallback->icEntry()->pc(script);
-    JSOp op = JSOp(*pc);
-    RootedPropertyName name(cx, 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, "DoGetPropGeneric");
-
-bool
-ICGetProp_Generic::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    AllocatableGeneralRegisterSet regs(availableGeneralRegs(1));
-
-    Register scratch = regs.takeAnyExcluding(ICTailCallReg);
-
-    // Sync for the decompiler.
-    if (engine_ == Engine::Baseline)
-        EmitStowICValues(masm, 1);
-
-    enterStubFrame(masm, scratch);
-
-    // Push arguments.
-    masm.Push(R0);
-    masm.Push(ICStubReg);
-    PushStubPayload(masm, R0.scratchReg());
-
-    if (!callVM(DoGetPropGenericInfo, masm))
-        return false;
-
-    leaveStubFrame(masm);
-
-    if (engine_ == Engine::Baseline)
-        EmitUnstowICValues(masm, 1, /* discard = */ true);
-
-    EmitEnterTypeMonitorIC(masm);
-    return true;
-}
-
 void
 CheckForTypedObjectWithDetachedStorage(JSContext* cx, MacroAssembler& masm, Label* failure)
 {
     // All stubs manipulating typed objects must check the compartment-wide
     // flag indicating whether their underlying storage might be detached, to
     // bail out if needed.
     int32_t* address = &cx->compartment()->detachedTypedObjects;
     masm.branch32(Assembler::NotEqual, AbsoluteAddress(address), Imm32(0), failure);
--- a/js/src/jit/SharedIC.h
+++ b/js/src/jit/SharedIC.h
@@ -8,16 +8,17 @@
 #define jit_SharedIC_h
 
 #include "jscntxt.h"
 #include "jscompartment.h"
 #include "jsgc.h"
 
 #include "jit/BaselineICList.h"
 #include "jit/BaselineJIT.h"
+#include "jit/ICState.h"
 #include "jit/MacroAssembler.h"
 #include "jit/SharedICList.h"
 #include "jit/SharedICRegisters.h"
 #include "vm/ReceiverGuard.h"
 #include "vm/TypedArrayObject.h"
 
 namespace js {
 namespace jit {
@@ -730,59 +731,60 @@ class ICFallbackStub : public ICStub
   protected:
     // Fallback stubs need these fields to easily add new stubs to
     // the linked list of stubs for an IC.
 
     // The IC entry for this linked list of stubs.
     ICEntry* icEntry_;
 
     // The number of stubs kept in the IC entry.
-    uint32_t numOptimizedStubs_ : 31;
-    uint32_t invalid_ : 1;
+    ICState state_;
 
     // A pointer to the location stub pointer that needs to be
     // changed to add a new "last" stub immediately before the fallback
     // stub.  This'll start out pointing to the icEntry's "firstStub_"
     // field, and as new stubs are added, it'll point to the current
     // last stub's "next_" field.
     ICStub** lastStubPtrAddr_;
 
     ICFallbackStub(Kind kind, JitCode* stubCode)
       : ICStub(kind, ICStub::Fallback, stubCode),
         icEntry_(nullptr),
-        numOptimizedStubs_(0),
-        invalid_(false),
+        state_(),
         lastStubPtrAddr_(nullptr) {}
 
     ICFallbackStub(Kind kind, Trait trait, JitCode* stubCode)
       : ICStub(kind, trait, stubCode),
         icEntry_(nullptr),
-        numOptimizedStubs_(0),
-        invalid_(false),
+        state_(),
         lastStubPtrAddr_(nullptr)
     {
         MOZ_ASSERT(trait == ICStub::Fallback ||
                    trait == ICStub::MonitoredFallback);
     }
 
   public:
     inline ICEntry* icEntry() const {
         return icEntry_;
     }
 
     inline size_t numOptimizedStubs() const {
-        return (size_t) numOptimizedStubs_;
+        return state_.numOptimizedStubs();
     }
 
     void setInvalid() {
-        invalid_ = 1;
+        state_.setInvalid();
     }
 
     bool invalid() const {
-        return invalid_;
+        return state_.invalid();
+    }
+
+    ICState& state() {
+        return state_;
     }
 
     // The icEntry and lastStubPtrAddr_ fields can't be initialized when the stub is
     // created since the stub is created at compile time, and we won't know the IC entry
     // address until after compile when the JitScript is created.  This method
     // allows these fields to be fixed up at that point.
     void fixupICEntry(ICEntry* icEntry) {
         MOZ_ASSERT(icEntry_ == nullptr);
@@ -794,17 +796,17 @@ class ICFallbackStub : public ICStub
     // Add a new stub to the IC chain terminated by this fallback stub.
     void addNewStub(ICStub* stub) {
         MOZ_ASSERT(!invalid());
         MOZ_ASSERT(*lastStubPtrAddr_ == this);
         MOZ_ASSERT(stub->next() == nullptr);
         stub->setNext(this);
         *lastStubPtrAddr_ = stub;
         lastStubPtrAddr_ = stub->addressOfNext();
-        numOptimizedStubs_++;
+        state_.trackAttached();
     }
 
     ICStubConstIterator beginChainConst() const {
         return ICStubConstIterator(icEntry_->firstStub());
     }
 
     ICStubIterator beginChain() {
         return ICStubIterator(this);
@@ -822,16 +824,18 @@ class ICFallbackStub : public ICStub
         unsigned count = 0;
         for (ICStubConstIterator iter = beginChainConst(); !iter.atEnd(); iter++) {
             if (iter->kind() == kind)
                 count++;
         }
         return count;
     }
 
+    void discardStubs(JSContext* cx);
+
     void unlinkStub(Zone* zone, ICStub* prev, ICStub* stub);
     void unlinkStubsWithKind(JSContext* cx, ICStub::Kind kind);
 };
 
 // Base class for Trait::Regular CacheIR stubs
 class ICCacheIR_Regular : public ICStub
 {
     const CacheIRStubInfo* stubInfo_;
@@ -2319,17 +2323,16 @@ class ICGetProp_Fallback : public ICMoni
 {
     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);
@@ -2362,45 +2365,16 @@ class ICGetProp_Fallback : public ICMoni
             ICGetProp_Fallback* stub = newStub<ICGetProp_Fallback>(space, getStubCode());
             if (!stub || !stub->initMonitoringChain(cx, space, engine_))
                 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:
-        MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm);
-        ICStub* firstMonitorStub_;
-      public:
-        explicit Compiler(JSContext* cx, ICStub* firstMonitorStub)
-          : ICStubCompiler(cx, ICStub::GetProp_Generic, ICStubEngine::Baseline),
-            firstMonitorStub_(firstMonitorStub)
-        {}
-
-        ICStub* getStub(ICStubSpace* space) {
-            return newStub<ICGetProp_Generic>(space, getStubCode(), firstMonitorStub_);
-        }
-    };
-};
-
 static inline uint32_t
 SimpleTypeDescrKey(SimpleTypeDescr* descr)
 {
     if (descr->is<ScalarTypeDescr>())
         return uint32_t(descr->as<ScalarTypeDescr>().type()) << 1;
     return (uint32_t(descr->as<ReferenceTypeDescr>().type()) << 1) | 1;
 }
 
--- a/js/src/jit/SharedICList.h
+++ b/js/src/jit/SharedICList.h
@@ -31,17 +31,16 @@ namespace jit {
     _(Compare_String)                            \
     _(Compare_Symbol)                            \
     _(Compare_Boolean)                           \
     _(Compare_Object)                            \
     _(Compare_ObjectWithUndefined)               \
     _(Compare_Int32WithBoolean)                  \
                                                  \
     _(GetProp_Fallback)                          \
-    _(GetProp_Generic)                           \
                                                  \
     _(CacheIR_Regular)                           \
     _(CacheIR_Monitored)                         \
     _(CacheIR_Updated)                           \
                                                  \
 
 } // namespace jit
 } // namespace js
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -1522,10 +1522,189 @@ bool
 CheckIsCallable(JSContext* cx, HandleValue v, CheckIsCallableKind kind)
 {
     if (!IsCallable(v))
         return ThrowCheckIsCallable(cx, kind);
 
     return true;
 }
 
+template <bool HandleMissing>
+static MOZ_ALWAYS_INLINE bool
+GetNativeDataProperty(JSContext* cx, NativeObject* obj, jsid id, Value* vp)
+{
+    // Fast path used by megamorphic IC stubs. Unlike our other property
+    // lookup paths, this is optimized to be as fast as possible for simple
+    // data property lookups.
+
+    JS::AutoCheckCannotGC nogc;
+
+    MOZ_ASSERT(JSID_IS_ATOM(id) || JSID_IS_SYMBOL(id));
+
+    while (true) {
+        if (Shape* shape = obj->lastProperty()->search(cx, id)) {
+            if (!shape->hasSlot() || !shape->hasDefaultGetter())
+                return false;
+
+            *vp = obj->getSlot(shape->slot());
+            return true;
+        }
+
+        // Property not found. Watch out for Class hooks.
+        if (MOZ_UNLIKELY(!obj->is<PlainObject>())) {
+            if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj) ||
+                obj->getClass()->getGetProperty())
+            {
+                return false;
+            }
+        }
+
+        JSObject* proto = obj->staticPrototype();
+        if (!proto) {
+            if (HandleMissing) {
+                vp->setUndefined();
+                return true;
+            }
+            return false;
+        }
+
+        if (!proto->isNative())
+            return false;
+        obj = &proto->as<NativeObject>();
+    }
+}
+
+template <bool HandleMissing>
+bool
+GetNativeDataProperty(JSContext* cx, JSObject* obj, PropertyName* name, Value* vp)
+{
+    if (MOZ_UNLIKELY(!obj->isNative()))
+        return false;
+    return GetNativeDataProperty<HandleMissing>(cx, &obj->as<NativeObject>(), NameToId(name), vp);
+}
+
+template bool
+GetNativeDataProperty<true>(JSContext* cx, JSObject* obj, PropertyName* name, Value* vp);
+
+template bool
+GetNativeDataProperty<false>(JSContext* cx, JSObject* obj, PropertyName* name, Value* vp);
+
+template <bool HandleMissing>
+bool
+GetNativeDataPropertyByValue(JSContext* cx, JSObject* obj, Value* vp)
+{
+    JS::AutoCheckCannotGC nogc;
+
+    if (MOZ_UNLIKELY(!obj->isNative()))
+        return false;
+
+    // vp[0] contains the id, result will be stored in vp[1].
+    Value idVal = vp[0];
+
+    jsid id;
+    if (MOZ_LIKELY(idVal.isString())) {
+        JSString* s = idVal.toString();
+        JSAtom* atom;
+        if (s->isAtom()) {
+            atom = &s->asAtom();
+        } else {
+            atom = AtomizeString(cx, s);
+            if (!atom)
+                return false;
+        }
+        id = AtomToId(atom);
+    } else if (idVal.isSymbol()) {
+        id = SYMBOL_TO_JSID(idVal.toSymbol());
+    } else {
+        if (!ValueToIdPure(idVal, &id))
+            return false;
+    }
+
+    // Watch out for ids that may be stored in dense elements.
+    static_assert(NativeObject::MAX_DENSE_ELEMENTS_COUNT < JSID_INT_MAX,
+                  "All dense elements must have integer jsids");
+    if (MOZ_UNLIKELY(JSID_IS_INT(id)))
+        return false;
+
+    Value* res = vp + 1;
+    return GetNativeDataProperty<HandleMissing>(cx, &obj->as<NativeObject>(), id, res);
+}
+
+template bool
+GetNativeDataPropertyByValue<true>(JSContext* cx, JSObject* obj, Value* vp);
+
+template bool
+GetNativeDataPropertyByValue<false>(JSContext* cx, JSObject* obj, Value* vp);
+
+bool
+SetNativeDataProperty(JSContext* cx, JSObject* obj, PropertyName* name, Value* val)
+{
+    JS::AutoCheckCannotGC nogc;
+
+    if (MOZ_UNLIKELY(!obj->isNative()))
+        return false;
+
+    NativeObject* nobj = &obj->as<NativeObject>();
+    Shape* shape = nobj->lastProperty()->search(cx, NameToId(name));
+    if (!shape ||
+        !shape->hasSlot() ||
+        !shape->hasDefaultSetter() ||
+        !shape->writable() ||
+        nobj->watched())
+    {
+        return false;
+    }
+
+    if (!HasTypePropertyId(nobj, NameToId(name), *val))
+        return false;
+
+    nobj->setSlot(shape->slot(), *val);
+    return true;
+}
+
+bool
+ObjectHasGetterSetter(JSContext* cx, JSObject* objArg, Shape* propShape)
+{
+    JS::AutoCheckCannotGC nogc;
+
+    MOZ_ASSERT(propShape->hasGetterObject() || propShape->hasSetterObject());
+
+    // Window objects may require outerizing (passing the WindowProxy to the
+    // getter/setter), so we don't support them here.
+    if (MOZ_UNLIKELY(!objArg->isNative() || IsWindow(objArg)))
+        return false;
+
+    NativeObject* nobj = &objArg->as<NativeObject>();
+    jsid id = propShape->propid();
+
+    while (true) {
+        if (Shape* shape = nobj->lastProperty()->search(cx, id)) {
+            if (shape == propShape)
+                return true;
+            if (shape->getterOrUndefined() == propShape->getterOrUndefined() &&
+                shape->setterOrUndefined() == propShape->setterOrUndefined())
+            {
+                return true;
+            }
+            return false;
+        }
+
+        // Property not found. Watch out for Class hooks.
+        if (!nobj->is<PlainObject>()) {
+            if (ClassMayResolveId(cx->names(), nobj->getClass(), id, nobj) ||
+                nobj->getClass()->getGetProperty())
+            {
+                return false;
+            }
+        }
+
+        JSObject* proto = nobj->staticPrototype();
+        if (!proto)
+            return false;
+
+        if (!proto->isNative())
+            return false;
+        nobj = &proto->as<NativeObject>();
+    }
+}
+
 } // namespace jit
 } // namespace js
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -838,12 +838,26 @@ CallNativeSetter(JSContext* cx, HandleFu
                  HandleValue rhs);
 
 MOZ_MUST_USE bool
 EqualStringsHelper(JSString* str1, JSString* str2);
 
 MOZ_MUST_USE bool
 CheckIsCallable(JSContext* cx, HandleValue v, CheckIsCallableKind kind);
 
+template <bool HandleMissing>
+bool
+GetNativeDataProperty(JSContext* cx, JSObject* obj, PropertyName* name, Value* vp);
+
+template <bool HandleMissing>
+bool
+GetNativeDataPropertyByValue(JSContext* cx, JSObject* obj, Value* vp);
+
+bool
+SetNativeDataProperty(JSContext* cx, JSObject* obj, PropertyName* name, Value* val);
+
+bool
+ObjectHasGetterSetter(JSContext* cx, JSObject* obj, Shape* propShape);
+
 } // namespace jit
 } // namespace js
 
 #endif /* jit_VMFunctions_h */
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6336,18 +6336,20 @@ JSErrorNotes::~JSErrorNotes()
 static UniquePtr<JSErrorNotes::Note>
 CreateErrorNoteVA(JSContext* cx,
                   const char* filename, unsigned lineno, unsigned column,
                   JSErrorCallback errorCallback, void* userRef,
                   const unsigned errorNumber,
                   ErrorArgumentsType argumentsType, va_list ap)
 {
     auto note = MakeUnique<JSErrorNotes::Note>();
-    if (!note)
+    if (!note) {
+        ReportOutOfMemory(cx);
         return nullptr;
+    }
 
     note->errorNumber = errorNumber;
     note->filename = filename;
     note->lineno = lineno;
     note->column = column;
 
     if (!ExpandErrorArgumentsVA(cx, errorCallback, userRef, errorNumber,
                                 nullptr, argumentsType, note.get(), ap)) {
@@ -6419,18 +6421,20 @@ JSErrorNotes::length()
 {
     return notes_.length();
 }
 
 UniquePtr<JSErrorNotes>
 JSErrorNotes::copy(JSContext* cx)
 {
     auto copiedNotes = MakeUnique<JSErrorNotes>();
-    if (!copiedNotes)
+    if (!copiedNotes) {
+        ReportOutOfMemory(cx);
         return nullptr;
+    }
 
     for (auto&& note : *this) {
         js::UniquePtr<JSErrorNotes::Note> copied(CopyErrorNote(cx, note.get()));
         if (!copied)
             return nullptr;
 
         if (!copiedNotes->notes_.append(Move(copied)))
             return nullptr;
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3874,22 +3874,23 @@ JS::ubi::Concrete<JSObject>::size(mozill
 
 const char16_t JS::ubi::Concrete<JSObject>::concreteTypeName[] = u"JSObject";
 
 void
 JSObject::traceChildren(JSTracer* trc)
 {
     TraceEdge(trc, &group_, "group");
 
+    if (is<ShapedObject>())
+        as<ShapedObject>().traceShape(trc);
+
     const Class* clasp = group_->clasp();
     if (clasp->isNative()) {
         NativeObject* nobj = &as<NativeObject>();
 
-        TraceEdge(trc, &nobj->shape_, "shape");
-
         {
             GetObjectSlotNameFunctor func(nobj);
             JS::AutoTracingDetails ctx(trc, func);
             JS::AutoTracingIndex index(trc);
             // Tracing can mutate the target but cannot change the slot count,
             // but the compiler has no way of knowing this.
             const uint32_t nslots = nobj->slotSpan();
             for (uint32_t i = 0; i < nslots; ++i) {
--- a/js/src/vm/Scope.cpp
+++ b/js/src/vm/Scope.cpp
@@ -625,16 +625,17 @@ FunctionScope::create(JSContext* cx, Han
         // The data that's passed in may be from the frontend and LifoAlloc'd.
         // Copy it now that we're creating a permanent VM scope.
         RootedShape envShape(cx);
         Rooted<UniquePtr<Data>> copy(cx, copyData(cx, data, hasParameterExprs, &envShape));
         if (!copy)
             return nullptr;
 
         copy->hasParameterExprs = hasParameterExprs;
+        copy->canonicalFunction.init(fun);
 
         // An environment may be needed regardless of existence of any closed over
         // bindings:
         //   - Extensible scopes (i.e., due to direct eval)
         //   - Needing a home object
         //   - Being a derived class constructor
         //   - Being a generator
         if (!envShape && needsEnvironment) {
@@ -642,18 +643,16 @@ FunctionScope::create(JSContext* cx, Han
             if (!envShape)
                 return nullptr;
         }
 
         Scope* scope = Scope::create(cx, ScopeKind::Function, enclosing, envShape);
         if (!scope)
             return nullptr;
 
-        copy->canonicalFunction.init(fun);
-
         funScope = &scope->as<FunctionScope>();
         funScope->initData(Move(copy.get()));
     }
 
     return funScope;
 }
 
 JSScript*
@@ -696,22 +695,22 @@ FunctionScope::clone(JSContext* cx, Hand
                 return nullptr;
         }
 
         Rooted<Data*> dataOriginal(cx, &scope->as<FunctionScope>().data());
         Rooted<UniquePtr<Data>> dataClone(cx, CopyScopeData<FunctionScope>(cx, dataOriginal));
         if (!dataClone)
             return nullptr;
 
-        Scope* scopeClone= Scope::create(cx, scope->kind(), enclosing, envShape);
+        dataClone->canonicalFunction.init(fun);
+
+        Scope* scopeClone = Scope::create(cx, scope->kind(), enclosing, envShape);
         if (!scopeClone)
             return nullptr;
 
-        dataClone->canonicalFunction.init(fun);
-
         funScopeClone = &scopeClone->as<FunctionScope>();
         funScopeClone->initData(Move(dataClone.get()));
     }
 
     return funScopeClone;
 }
 
 template <XDRMode mode>
--- a/js/src/vm/ShapedObject.h
+++ b/js/src/vm/ShapedObject.h
@@ -34,16 +34,20 @@ class ShapedObject : public JSObject
     }
 
     void setShape(Shape* shape) {
         this->shape_ = shape;
     }
 
     Shape* shape() const { return this->shape_; }
 
+    void traceShape(JSTracer* trc) {
+        TraceEdge(trc, &shape_, "shape");
+    }
+
     static size_t offsetOfShape() { return offsetof(ShapedObject, shape_); }
 
   private:
     static void staticAsserts() {
         static_assert(offsetof(ShapedObject, shape_) == offsetof(shadow::Object, shape),
                       "shadow shape must match actual shape");
     }
 };
--- a/js/src/vm/TypeInference-inl.h
+++ b/js/src/vm/TypeInference-inl.h
@@ -409,17 +409,17 @@ HasTypePropertyId(JSObject* obj, jsid id
 {
     if (obj->hasLazyGroup())
         return true;
 
     if (obj->group()->unknownProperties())
         return true;
 
     if (HeapTypeSet* types = obj->group()->maybeGetProperty(IdToTypeId(id)))
-        return types->hasType(type);
+        return types->hasType(type) && !types->nonConstantProperty();
 
     return false;
 }
 
 inline bool
 HasTypePropertyId(JSObject* obj, jsid id, const Value& value)
 {
     return HasTypePropertyId(obj, id, TypeSet::GetValueType(value));
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -763,76 +763,86 @@ class FunctionCompiler
             MOZ_ASSERT(access->offset() == 0);
             MWasmLoadTls* boundsCheckLimit = maybeLoadBoundsCheckLimit();
             load = MAsmJSLoadHeap::New(alloc(), memoryBase, base, boundsCheckLimit, access->type());
         } else {
             checkOffsetAndBounds(access, &base);
             load = MWasmLoad::New(alloc(), memoryBase, base, *access, ToMIRType(result));
         }
 
-        curBlock_->add(load);
+        if (load)
+            curBlock_->add(load);
+
         return load;
     }
 
-    void store(MDefinition* base, MemoryAccessDesc* access, MDefinition* v)
+    MOZ_MUST_USE bool store(MDefinition* base, MemoryAccessDesc* access, MDefinition* v)
     {
         if (inDeadCode())
-            return;
+            return true;
 
         MWasmLoadTls* memoryBase = maybeLoadMemoryBase();
         MInstruction* store = nullptr;
         if (access->isPlainAsmJS()) {
             MOZ_ASSERT(access->offset() == 0);
             MWasmLoadTls* boundsCheckLimit = maybeLoadBoundsCheckLimit();
-            store = MAsmJSStoreHeap::New(alloc(), memoryBase, base, boundsCheckLimit, access->type(), v);
+            store = MAsmJSStoreHeap::New(alloc(), memoryBase, base, boundsCheckLimit,
+                                         access->type(), v);
         } else {
             checkOffsetAndBounds(access, &base);
             store = MWasmStore::New(alloc(), memoryBase, base, *access, v);
         }
 
-        curBlock_->add(store);
+        if (store)
+            curBlock_->add(store);
+
+        return !!store;
     }
 
     MDefinition* atomicCompareExchangeHeap(MDefinition* base, MemoryAccessDesc* access,
                                            MDefinition* oldv, MDefinition* newv)
     {
         if (inDeadCode())
             return nullptr;
 
         checkOffsetAndBounds(access, &base);
         MWasmLoadTls* memoryBase = maybeLoadMemoryBase();
-        auto* cas = MAsmJSCompareExchangeHeap::New(alloc(), memoryBase, base, *access, oldv, newv, tlsPointer_);
-        curBlock_->add(cas);
+        auto* cas = MAsmJSCompareExchangeHeap::New(alloc(), memoryBase, base, *access, oldv, newv,
+                                                   tlsPointer_);
+        if (cas)
+            curBlock_->add(cas);
         return cas;
     }
 
     MDefinition* atomicExchangeHeap(MDefinition* base, MemoryAccessDesc* access,
                                     MDefinition* value)
     {
         if (inDeadCode())
             return nullptr;
 
         checkOffsetAndBounds(access, &base);
         MWasmLoadTls* memoryBase = maybeLoadMemoryBase();
         auto* cas = MAsmJSAtomicExchangeHeap::New(alloc(), memoryBase, base, *access, value, tlsPointer_);
-        curBlock_->add(cas);
+        if (cas)
+            curBlock_->add(cas);
         return cas;
     }
 
     MDefinition* atomicBinopHeap(js::jit::AtomicOp op,
-                                 MDefinition* base, MemoryAccessDesc* access,
-                                 MDefinition* v)
+                                 MDefinition* base, MemoryAccessDesc* access, MDefinition* v)
     {
         if (inDeadCode())
             return nullptr;
 
         checkOffsetAndBounds(access, &base);
         MWasmLoadTls* memoryBase = maybeLoadMemoryBase();
-        auto* binop = MAsmJSAtomicBinopHeap::New(alloc(), op, memoryBase, base, *access, v, tlsPointer_);
-        curBlock_->add(binop);
+        auto* binop = MAsmJSAtomicBinopHeap::New(alloc(), op, memoryBase, base, *access, v,
+                                                 tlsPointer_);
+        if (binop)
+            curBlock_->add(binop);
         return binop;
     }
 
     MDefinition* loadGlobalVar(unsigned globalDataOffset, bool isConst, MIRType type)
     {
         if (inDeadCode())
             return nullptr;
 
@@ -2450,46 +2460,48 @@ EmitSelect(FunctionCompiler& f)
 static bool
 EmitLoad(FunctionCompiler& f, ValType type, Scalar::Type viewType)
 {
     LinearMemoryAddress<MDefinition*> addr;
     if (!f.iter().readLoad(type, Scalar::byteSize(viewType), &addr))
         return false;
 
     MemoryAccessDesc access(viewType, addr.align, addr.offset, f.trapIfNotAsmJS());
-    f.iter().setResult(f.load(addr.base, &access, type));
+    auto* ins = f.load(addr.base, &access, type);
+    if (!f.inDeadCode() && !ins)
+        return false;
+
+    f.iter().setResult(ins);
     return true;
 }
 
 static bool
 EmitStore(FunctionCompiler& f, ValType resultType, Scalar::Type viewType)
 {
     LinearMemoryAddress<MDefinition*> addr;
     MDefinition* value;
     if (!f.iter().readStore(resultType, Scalar::byteSize(viewType), &addr, &value))
         return false;
 
     MemoryAccessDesc access(viewType, addr.align, addr.offset, f.trapIfNotAsmJS());
 
-    f.store(addr.base, &access, value);
-    return true;
+    return f.store(addr.base, &access, value);
 }
 
 static bool
 EmitTeeStore(FunctionCompiler& f, ValType resultType, Scalar::Type viewType)
 {
     LinearMemoryAddress<MDefinition*> addr;
     MDefinition* value;
     if (!f.iter().readTeeStore(resultType, Scalar::byteSize(viewType), &addr, &value))
         return false;
 
     MemoryAccessDesc access(viewType, addr.align, addr.offset, f.trapIfNotAsmJS());
 
-    f.store(addr.base, &access, value);
-    return true;
+    return f.store(addr.base, &access, value);
 }
 
 static bool
 EmitTeeStoreWithCoercion(FunctionCompiler& f, ValType resultType, Scalar::Type viewType)
 {
     LinearMemoryAddress<MDefinition*> addr;
     MDefinition* value;
     if (!f.iter().readTeeStore(resultType, Scalar::byteSize(viewType), &addr, &value))
@@ -2499,18 +2511,17 @@ EmitTeeStoreWithCoercion(FunctionCompile
         value = f.unary<MToDouble>(value);
     else if (resultType == ValType::F64 && viewType == Scalar::Float32)
         value = f.unary<MToFloat32>(value);
     else
         MOZ_CRASH("unexpected coerced store");
 
     MemoryAccessDesc access(viewType, addr.align, addr.offset, f.trapIfNotAsmJS());
 
-    f.store(addr.base, &access, value);
-    return true;
+    return f.store(addr.base, &access, value);
 }
 
 static bool
 TryInlineUnaryBuiltin(FunctionCompiler& f, SymbolicAddress callee, MDefinition* input)
 {
     if (!input)
         return false;
 
@@ -2594,81 +2605,99 @@ EmitAtomicsLoad(FunctionCompiler& f)
     LinearMemoryAddress<MDefinition*> addr;
     Scalar::Type viewType;
     if (!f.iter().readAtomicLoad(&addr, &viewType))
         return false;
 
     MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset()), 0,
                             MembarBeforeLoad, MembarAfterLoad);
 
-    f.iter().setResult(f.load(addr.base, &access, ValType::I32));
+    auto* ins = f.load(addr.base, &access, ValType::I32);
+    if (!f.inDeadCode() && !ins)
+        return false;
+
+    f.iter().setResult(ins);
     return true;
 }
 
 static bool
 EmitAtomicsStore(FunctionCompiler& f)
 {
     LinearMemoryAddress<MDefinition*> addr;
     Scalar::Type viewType;
     MDefinition* value;
     if (!f.iter().readAtomicStore(&addr, &viewType, &value))
         return false;
 
     MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset()), 0,
                             MembarBeforeStore, MembarAfterStore);
 
-    f.store(addr.base, &access, value);
+    if (!f.store(addr.base, &access, value))
+        return false;
+
     f.iter().setResult(value);
     return true;
 }
 
 static bool
 EmitAtomicsBinOp(FunctionCompiler& f)
 {
     LinearMemoryAddress<MDefinition*> addr;
     Scalar::Type viewType;
     jit::AtomicOp op;
     MDefinition* value;
     if (!f.iter().readAtomicBinOp(&addr, &viewType, &op, &value))
         return false;
 
     MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset()));
 
-    f.iter().setResult(f.atomicBinopHeap(op, addr.base, &access, value));
+    auto* ins = f.atomicBinopHeap(op, addr.base, &access, value);
+    if (!f.inDeadCode() && !ins)
+        return false;
+
+    f.iter().setResult(ins);
     return true;
 }
 
 static bool
 EmitAtomicsCompareExchange(FunctionCompiler& f)
 {
     LinearMemoryAddress<MDefinition*> addr;
     Scalar::Type viewType;
     MDefinition* oldValue;
     MDefinition* newValue;
     if (!f.iter().readAtomicCompareExchange(&addr, &viewType, &oldValue, &newValue))
         return false;
 
     MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset()));
 
-    f.iter().setResult(f.atomicCompareExchangeHeap(addr.base, &access, oldValue, newValue));
+    auto* ins = f.atomicCompareExchangeHeap(addr.base, &access, oldValue, newValue);
+    if (!f.inDeadCode() && !ins)
+        return false;
+
+    f.iter().setResult(ins);
     return true;
 }
 
 static bool
 EmitAtomicsExchange(FunctionCompiler& f)
 {
     LinearMemoryAddress<MDefinition*> addr;
     Scalar::Type viewType;
     MDefinition* value;
     if (!f.iter().readAtomicExchange(&addr, &viewType, &value))
         return false;
 
     MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset()));
 
-    f.iter().setResult(f.atomicExchangeHeap(addr.base, &access, value));
+    auto* ins = f.atomicExchangeHeap(addr.base, &access, value);
+    if (!f.inDeadCode() && !ins)
+        return false;
+
+    f.iter().setResult(ins);
     return true;
 }
 
 static bool
 EmitSimdUnary(FunctionCompiler& f, ValType type, SimdOperation simdOp)
 {
     MSimdUnaryArith::Operation op;
     switch (simdOp) {
@@ -2881,17 +2910,21 @@ EmitSimdLoad(FunctionCompiler& f, ValTyp
         numElems = defaultNumElems;
 
     LinearMemoryAddress<MDefinition*> addr;
     if (!f.iter().readLoad(resultType, Scalar::byteSize(viewType), &addr))
         return false;
 
     MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset()), numElems);
 
-    f.iter().setResult(f.load(addr.base, &access, resultType));
+    auto* ins = f.load(addr.base, &access, resultType);
+    if (!f.inDeadCode() && !ins)
+        return false;
+
+    f.iter().setResult(ins);
     return true;
 }
 
 static bool
 EmitSimdStore(FunctionCompiler& f, ValType resultType, unsigned numElems)
 {
     unsigned defaultNumElems;
     Scalar::Type viewType = SimdExprTypeToViewType(resultType, &defaultNumElems);
@@ -2901,18 +2934,17 @@ EmitSimdStore(FunctionCompiler& f, ValTy
 
     LinearMemoryAddress<MDefinition*> addr;
     MDefinition* value;
     if (!f.iter().readTeeStore(resultType, Scalar::byteSize(viewType), &addr, &value))
         return false;
 
     MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset()), numElems);
 
-    f.store(addr.base, &access, value);
-    return true;
+    return f.store(addr.base, &access, value);
 }
 
 static bool
 EmitSimdSelect(FunctionCompiler& f, ValType simdType)
 {
     MDefinition* trueValue;
     MDefinition* falseValue;
     MDefinition* condition;
--- a/layout/base/GeckoRestyleManager.cpp
+++ b/layout/base/GeckoRestyleManager.cpp
@@ -195,34 +195,33 @@ GeckoRestyleManager::ReframingStyleConte
 
 static inline dom::Element*
 ElementForStyleContext(nsIContent* aParentContent,
                        nsIFrame* aFrame,
                        CSSPseudoElementType aPseudoType);
 
 // Forwarded nsIDocumentObserver method, to handle restyling (and
 // passing the notification to the frame).
-nsresult
+void
 GeckoRestyleManager::ContentStateChanged(nsIContent* aContent,
                                          EventStates aStateMask)
 {
   // XXXbz it would be good if this function only took Elements, but
   // we'd have to make ESM guarantee that usefully.
   if (!aContent->IsElement()) {
-    return NS_OK;
+    return;
   }
 
   Element* aElement = aContent->AsElement();
 
   nsChangeHint changeHint;
   nsRestyleHint restyleHint;
   ContentStateChangedInternal(aElement, aStateMask, &changeHint, &restyleHint);
 
   PostRestyleEvent(aElement, restyleHint, changeHint);
-  return NS_OK;
 }
 
 // Forwarded nsIMutationObserver method, to handle restyling.
 void
 GeckoRestyleManager::AttributeWillChange(Element* aElement,
                                          int32_t aNameSpaceID,
                                          nsIAtom* aAttribute,
                                          int32_t aModType,
@@ -1510,17 +1509,17 @@ ElementRestyler::ConditionallyRestyleUnd
   if (aUndisplayedParent &&
       aUndisplayedParent->IsElement() &&
       aUndisplayedParent->HasFlag(mRestyleTracker.RootBit())) {
     MOZ_ASSERT(!aUndisplayedParent->IsStyledByServo());
     aRestyleRoot = aUndisplayedParent->AsElement();
   }
 
   for (UndisplayedNode* undisplayed = aUndisplayed; undisplayed;
-       undisplayed = undisplayed->mNext) {
+       undisplayed = undisplayed->getNext()) {
 
     if (!undisplayed->mContent->IsElement()) {
       continue;
     }
 
     Element* element = undisplayed->mContent->AsElement();
 
     if (!ConditionallyRestyle(element, aRestyleRoot)) {
@@ -3169,17 +3168,17 @@ ElementRestyler::RestyleUndisplayedNodes
                                          const StyleDisplay aDisplay)
 {
   nsIContent* undisplayedParent = aUndisplayedParent;
   UndisplayedNode* undisplayed = aUndisplayed;
   TreeMatchContext::AutoAncestorPusher pusher(mTreeMatchContext);
   if (undisplayed) {
     pusher.PushAncestorAndStyleScope(undisplayedParent);
   }
-  for (; undisplayed; undisplayed = undisplayed->mNext) {
+  for (; undisplayed; undisplayed = undisplayed->getNext()) {
     NS_ASSERTION(undisplayedParent ||
                  undisplayed->mContent ==
                    mPresContext->Document()->GetRootElement(),
                  "undisplayed node child of null must be root");
     NS_ASSERTION(!undisplayed->mStyle->GetPseudo(),
                  "Shouldn't have random pseudo style contexts in the "
                  "undisplayed map");
 
--- a/layout/base/GeckoRestyleManager.h
+++ b/layout/base/GeckoRestyleManager.h
@@ -49,18 +49,18 @@ protected:
   {
     MOZ_ASSERT(!mReframingStyleContexts,
                "temporary member should be nulled out before destruction");
   }
 
 public:
   // Forwarded nsIDocumentObserver method, to handle restyling (and
   // passing the notification to the frame).
-  nsresult ContentStateChanged(nsIContent*   aContent,
-                               EventStates aStateMask);
+  void ContentStateChanged(nsIContent* aContent,
+                           EventStates aStateMask);
 
   // Forwarded nsIMutationObserver method, to handle restyling.
   void AttributeWillChange(Element* aElement,
                            int32_t  aNameSpaceID,
                            nsIAtom* aAttribute,
                            int32_t  aModType,
                            const nsAttrValue* aNewValue);
   // Forwarded nsIMutationObserver method, to handle restyling (and
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -4521,43 +4521,41 @@ void
 PresShell::NotifyCounterStylesAreDirty()
 {
   nsAutoCauseReflowNotifier reflowNotifier(this);
   mFrameConstructor->BeginUpdate();
   mFrameConstructor->NotifyCounterStylesAreDirty();
   mFrameConstructor->EndUpdate();
 }
 
-nsresult
-PresShell::ReconstructFrames(void)
+void
+PresShell::ReconstructFrames()
 {
   NS_PRECONDITION(!mFrameConstructor->GetRootFrame() || mDidInitialize,
                   "Must not have root frame before initial reflow");
   if (!mDidInitialize || mIsDestroying) {
     // Nothing to do here
-    return NS_OK;
+    return;
   }
 
   nsCOMPtr<nsIPresShell> kungFuDeathGrip(this);
 
   // Have to make sure that the content notifications are flushed before we
   // start messing with the frame model; otherwise we can get content doubling.
   mDocument->FlushPendingNotifications(FlushType::ContentAndNotify);
 
   if (mIsDestroying) {
-    return NS_OK;
+    return;
   }
 
   nsAutoCauseReflowNotifier crNotifier(this);
   mFrameConstructor->BeginUpdate();
-  nsresult rv = mFrameConstructor->ReconstructDocElementHierarchy();
+  mFrameConstructor->ReconstructDocElementHierarchy();
   VERIFY_STYLE_TREE;
   mFrameConstructor->EndUpdate();
-
-  return rv;
 }
 
 void
 nsIPresShell::RestyleForCSSRuleChanges()
 {
   AutoTArray<RefPtr<mozilla::dom::Element>,1> scopeRoots;
   mChangedScopeStyleRoots.SwapElements(scopeRoots);
 
--- a/layout/base/PresShell.h
+++ b/layout/base/PresShell.h
@@ -183,17 +183,17 @@ public:
                                  nsIContent* aContent,
                                  nsEventStatus* aStatus) override;
   virtual nsIFrame* GetEventTargetFrame() override;
   virtual already_AddRefed<nsIContent> GetEventTargetContent(
                                                      mozilla::WidgetEvent* aEvent) override;
 
   virtual void NotifyCounterStylesAreDirty() override;
 
-  virtual nsresult ReconstructFrames(void) override;
+  virtual void ReconstructFrames(void) override;
   virtual void Freeze() override;
   virtual void Thaw() override;
   virtual void FireOrClearDelayedEvents(bool aFireEvents) override;
 
   virtual nsresult RenderDocument(const nsRect& aRect, uint32_t aFlags,
                                               nscolor aBackgroundColor,
                                               gfxContext* aThebesContext) override;
 
--- a/layout/base/RestyleManager.h
+++ b/layout/base/RestyleManager.h
@@ -169,18 +169,18 @@ public:
   inline void PostRestyleEvent(dom::Element* aElement,
                                nsRestyleHint aRestyleHint,
                                nsChangeHint aMinChangeHint);
   inline void RebuildAllStyleData(nsChangeHint aExtraHint,
                                   nsRestyleHint aRestyleHint);
   inline void PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
                                            nsRestyleHint aRestyleHint);
   inline void ProcessPendingRestyles();
-  inline nsresult ContentStateChanged(nsIContent* aContent,
-                                      EventStates aStateMask);
+  inline void ContentStateChanged(nsIContent* aContent,
+                                  EventStates aStateMask);
   inline void AttributeWillChange(dom::Element* aElement,
                                   int32_t aNameSpaceID,
                                   nsIAtom* aAttribute,
                                   int32_t aModType,
                                   const nsAttrValue* aNewValue);
   inline void AttributeChanged(dom::Element* aElement,
                                int32_t aNameSpaceID,
                                nsIAtom* aAttribute,
--- a/layout/base/RestyleManagerInlines.h
+++ b/layout/base/RestyleManagerInlines.h
@@ -39,17 +39,17 @@ RestyleManager::PostRebuildAllStyleDataE
 }
 
 void
 RestyleManager::ProcessPendingRestyles()
 {
   MOZ_STYLO_FORWARD(ProcessPendingRestyles, ());
 }
 
-nsresult
+void
 RestyleManager::ContentStateChanged(nsIContent* aContent,
                                     EventStates aStateMask)
 {
   MOZ_STYLO_FORWARD(ContentStateChanged, (aContent, aStateMask));
 }
 
 void
 RestyleManager::AttributeWillChange(dom::Element* aElement,
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -457,22 +457,22 @@ ServoRestyleManager::RestyleForAppend(ns
 void
 ServoRestyleManager::ContentRemoved(nsINode* aContainer,
                                     nsIContent* aOldChild,
                                     nsIContent* aFollowingSibling)
 {
   NS_WARNING("stylo: ServoRestyleManager::ContentRemoved not implemented");
 }
 
-nsresult
+void
 ServoRestyleManager::ContentStateChanged(nsIContent* aContent,
                                          EventStates aChangedBits)
 {
   if (!aContent->IsElement()) {
-    return NS_OK;
+    return;
   }
 
   Element* aElement = aContent->AsElement();
   nsChangeHint changeHint;
   nsRestyleHint restyleHint;
 
   // NOTE: restyleHint here is effectively always 0, since that's what
   // ServoStyleSet::HasStateDependentStyle returns. Servo computes on
@@ -497,18 +497,16 @@ ServoRestyleManager::ContentStateChanged
                               &restyleHint);
 
   EventStates previousState = aElement->StyleState() ^ aChangedBits;
   ServoElementSnapshot* snapshot = Servo_Element_GetSnapshot(aElement);
   if (snapshot) {
     snapshot->AddState(previousState);
     PostRestyleEvent(aElement, restyleHint, changeHint);
   }
-
-  return NS_OK;
 }
 
 void
 ServoRestyleManager::AttributeWillChange(Element* aElement,
                                          int32_t aNameSpaceID,
                                          nsIAtom* aAttribute, int32_t aModType,
                                          const nsAttrValue* aNewValue)
 {
--- a/layout/base/ServoRestyleManager.h
+++ b/layout/base/ServoRestyleManager.h
@@ -58,18 +58,17 @@ public:
   void ContentRemoved(nsINode* aContainer,
                       nsIContent* aOldChild,
                       nsIContent* aFollowingSibling);
 
   void RestyleForInsertOrChange(nsINode* aContainer,
                                 nsIContent* aChild);
   void RestyleForAppend(nsIContent* aContainer,
                         nsIContent* aFirstNewContent);
-  nsresult ContentStateChanged(nsIContent* aContent,
-                               EventStates aStateMask);
+  void ContentStateChanged(nsIContent* aContent, EventStates aStateMask);
   void AttributeWillChange(dom::Element* aElement,
                            int32_t aNameSpaceID,
                            nsIAtom* aAttribute,
                            int32_t aModType,
                            const nsAttrValue* aNewValue);
 
   void AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID,
                         nsIAtom* aAttribute, int32_t aModType,
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -3210,17 +3210,17 @@ nsCSSFrameConstructor::ConstructSelectFr
   return listFrame;
 }
 
 /**
  * Used to be InitializeScrollFrame but now it's only used for the select tag
  * But the select tag should really be fixed to use GFX scrollbars that can
  * be create with BuildScrollFrame.
  */
-nsresult
+void
 nsCSSFrameConstructor::InitializeSelectFrame(nsFrameConstructorState& aState,
                                              nsContainerFrame*        scrollFrame,
                                              nsContainerFrame*        scrolledFrame,
                                              nsIContent*              aContent,
                                              nsContainerFrame*        aParentFrame,
                                              nsStyleContext*          aStyleContext,
                                              bool                     aBuildCombobox,
                                              PendingBinding*          aPendingBinding,
@@ -3256,17 +3256,16 @@ nsCSSFrameConstructor::InitializeSelectF
   // Process children
   nsFrameItems                childItems;
 
   ProcessChildren(aState, aContent, aStyleContext, scrolledFrame, false,
                   childItems, false, aPendingBinding);
 
   // Set the scrolled frame's initial child lists
   scrolledFrame->SetInitialChildList(kPrincipalList, childItems);
-  return NS_OK;
 }
 
 nsIFrame*
 nsCSSFrameConstructor::ConstructFieldSetFrame(nsFrameConstructorState& aState,
                                               FrameConstructionItem&   aItem,
                                               nsContainerFrame*        aParentFrame,
                                               const nsStyleDisplay*    aStyleDisplay,
                                               nsFrameItems&            aFrameItems)
@@ -6259,26 +6258,26 @@ nsCSSFrameConstructor::ConstructFramesFr
 
 
 inline bool
 IsRootBoxFrame(nsIFrame *aFrame)
 {
   return (aFrame->GetType() == nsGkAtoms::rootFrame);
 }
 
-nsresult
+void
 nsCSSFrameConstructor::ReconstructDocElementHierarchy()
 {
   Element* rootElement = mDocument->GetRootElement();
   if (!rootElement) {
     /* nothing to do */
-    return NS_OK;
-  }
-  return RecreateFramesForContent(rootElement, false, REMOVE_FOR_RECONSTRUCTION,
-                                  nullptr);
+    return;
+  }
+  RecreateFramesForContent(rootElement, false, REMOVE_FOR_RECONSTRUCTION,
+                           nullptr);
 }
 
 nsContainerFrame*
 nsCSSFrameConstructor::GetAbsoluteContainingBlock(nsIFrame* aFrame,
                                                   ContainingBlockType aType)
 {
   // Starting with aFrame, look for a frame that is absolutely positioned or
   // relatively positioned (and transformed, if aType is FIXED)
@@ -6518,17 +6517,17 @@ GetInsertNextSibling(nsIFrame* aParentFr
   return aParentFrame->PrincipalChildList().FirstChild();
 }
 
 /**
  * This function is called by ContentAppended() and ContentInserted() when
  * appending flowed frames to a parent's principal child list. It handles the
  * case where the parent is the trailing inline of an {ib} split.
  */
-nsresult
+void
 nsCSSFrameConstructor::AppendFramesToParent(nsFrameConstructorState&       aState,
                                             nsContainerFrame*              aParentFrame,
                                             nsFrameItems&                  aFrameList,
                                             nsIFrame*                      aPrevSibling,
                                             bool                           aIsRecursiveCall)
 {
   NS_PRECONDITION(!IsFramePartOfIBSplit(aParentFrame) ||
                   !GetIBSplitSibling(aParentFrame) ||
@@ -6601,25 +6600,23 @@ nsCSSFrameConstructor::AppendFramesToPar
       // last one and now isn't anymore, since its GetSkipSides() has
       // changed.
       mPresShell->FrameNeedsReflow(aParentFrame,
                                    nsIPresShell::eTreeChange,
                                    NS_FRAME_HAS_DIRTY_CHILDREN);
 
       // Recurse so we create new ib siblings as needed for aParentFrame's parent
       return AppendFramesToParent(aState, aParentFrame->GetParent(), ibSiblings,
-                                  aParentFrame, true);
-    }
-
-    return NS_OK;
+                                   aParentFrame, true);
+    }
+    return;
   }
 
   // Insert the frames after our aPrevSibling
   InsertFrames(aParentFrame, kPrincipalList, aPrevSibling, aFrameList);
-  return NS_OK;
 }
 
 #define UNSET_DISPLAY static_cast<StyleDisplay>(255)
 
 // This gets called to see if the frames corresponding to aSibling and aContent
 // should be siblings in the frame tree. Although (1) rows and cols, (2) row
 // groups and col groups, (3) row groups and captions, (4) legends and content
 // inside fieldsets, (5) popups and other kids of the menu are siblings from a
@@ -7378,17 +7375,17 @@ nsCSSFrameConstructor::MaybeRecreateForF
                                  REMOVE_FOR_RECONSTRUCTION, nullptr);
         return true;
       }
     }
   }
   return false;
 }
 
-nsresult
+void
 nsCSSFrameConstructor::ContentAppended(nsIContent* aContainer,
                                        nsIContent* aFirstNewContent,
                                        bool aAllowLazyConstruction,
                                        TreeMatchContext* aProvidedTreeMatchContext)
 {
   MOZ_ASSERT_IF(aProvidedTreeMatchContext, !aAllowLazyConstruction);
 
   AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
@@ -7424,34 +7421,33 @@ nsCSSFrameConstructor::ContentAppended(n
     int32_t namespaceID;
     nsIAtom* tag =
       mDocument->BindingManager()->ResolveTag(aContainer, &namespaceID);
 
     // Just ignore tree tags, anyway we don't create any frames for them.
     if (tag == nsGkAtoms::treechildren ||
         tag == nsGkAtoms::treeitem ||
         tag == nsGkAtoms::treerow)
-      return NS_OK;
-
+      return;
   }
 #endif // MOZ_XUL
 
   if (aContainer && aContainer->HasFlag(NODE_IS_IN_SHADOW_TREE) &&
       !aContainer->IsInNativeAnonymousSubtree() &&
       !aFirstNewContent->IsInNativeAnonymousSubtree()) {
     // Recreate frames if content is appended into a ShadowRoot
     // because children of ShadowRoot are rendered in place of children
     // of the host.
     //XXXsmaug This is super unefficient!
     nsIContent* bindingParent = aContainer->GetBindingParent();
     LAYOUT_PHASE_TEMP_EXIT();
-    nsresult rv = RecreateFramesForContent(bindingParent, false,
-                                           REMOVE_FOR_RECONSTRUCTION, nullptr);
+    RecreateFramesForContent(bindingParent, false,
+                             REMOVE_FOR_RECONSTRUCTION, nullptr);
     LAYOUT_PHASE_TEMP_REENTER();
-    return rv;
+    return;
   }
 
   // The frame constructor uses this codepath both for bonafide newly-added
   // content and for RestyleManager-driven frame construction (RECONSTRUCT_FRAME
   // and lazy frame construction). If we're using the Servo style system, we
   // want to ensure that styles get resolved in the first case, whereas for the
   // second case they should have already been resolved if needed.
   bool isNewlyAddedContentForServo = aContainer->IsStyledByServo() &&
@@ -7461,62 +7457,62 @@ nsCSSFrameConstructor::ContentAppended(n
   if (!GetContentInsertionFrameFor(aContainer) &&
       !aContainer->IsActiveChildrenElement()) {
     // We're punting on frame construction because there's no container frame.
     // The Servo-backed style system handles this case like the lazy frame
     // construction case.
     if (isNewlyAddedContentForServo) {
       aContainer->AsElement()->NoteDirtyDescendantsForServo();
     }
-    return NS_OK;
+    return;
   }
 
   if (aAllowLazyConstruction &&
       MaybeConstructLazily(CONTENTAPPEND, aContainer, aFirstNewContent)) {
     if (isNewlyAddedContentForServo) {
       aContainer->AsElement()->NoteDirtyDescendantsForServo();
     }
-    return NS_OK;
+    return;
   }
 
   // We couldn't construct lazily. Make Servo eagerly traverse the subtree.
   if (isNewlyAddedContentForServo) {
     mPresShell->StyleSet()->AsServo()->StyleNewChildren(aContainer->AsElement());
   }
 
   LAYOUT_PHASE_TEMP_EXIT();
   InsertionPoint insertion =
     GetRangeInsertionPoint(aContainer, aFirstNewContent, nullptr,
                            aAllowLazyConstruction);
   nsContainerFrame*& parentFrame = insertion.mParentFrame;
   LAYOUT_PHASE_TEMP_REENTER();
   if (!parentFrame) {
-    return NS_OK;
+    return;
   }
 
   LAYOUT_PHASE_TEMP_EXIT();
   if (MaybeRecreateForFrameset(parentFrame, aFirstNewContent, nullptr)) {
     LAYOUT_PHASE_TEMP_REENTER();
-    return NS_OK;
+    return;
   }
   LAYOUT_PHASE_TEMP_REENTER();
 
   if (parentFrame->IsLeaf()) {
     // Nothing to do here; we shouldn't be constructing kids of leaves
     // Clear lazy bits so we don't try to construct again.
     ClearLazyBits(aFirstNewContent, nullptr);
-    return NS_OK;
+    return;
   }
 
   if (parentFrame->IsFrameOfType(nsIFrame::eMathML)) {
     LAYOUT_PHASE_TEMP_EXIT();
-    nsresult rv = RecreateFramesForContent(parentFrame->GetContent(), false,
-                                           REMOVE_FOR_RECONSTRUCTION, nullptr);
+    RecreateFramesForContent(parentFrame->GetContent(), false,
+                             REMOVE_FOR_RECONSTRUCTION, nullptr);
     LAYOUT_PHASE_TEMP_REENTER();
-    return rv;
+    return;
   }
 
   // If the frame we are manipulating is a ib-split frame (that is, one
   // that's been created as a result of a block-in-inline situation) then we
   // need to append to the last ib-split sibling, not to the frame itself.
   bool parentIBSplit = IsFramePartOfIBSplit(parentFrame);
   if (parentIBSplit) {
 #ifdef DEBUG
@@ -7613,17 +7609,17 @@ nsCSSFrameConstructor::ContentAppended(n
   // Perform special check for diddling around with the frames in
   // a ib-split inline frame.
   // If we're appending before :after content, then we're not really
   // appending, so let WipeContainingBlock know that.
   LAYOUT_PHASE_TEMP_EXIT();
   if (WipeContainingBlock(state, containingBlock, parentFrame, items,
                           true, prevSibling)) {
     LAYOUT_PHASE_TEMP_REENTER();
-    return NS_OK;
+    return;
   }
   LAYOUT_PHASE_TEMP_REENTER();
 
   // If the parent is a block frame, and we're not in a special case
   // where frames can be moved around, determine if the list is for the
   // start or end of the block.
   if (nsLayoutUtils::GetAsBlock(parentFrame) && !haveFirstLetterStyle &&
       !haveFirstLineStyle && !parentIBSplit) {
@@ -7699,18 +7695,16 @@ nsCSSFrameConstructor::ContentAppended(n
 
 #ifdef ACCESSIBILITY
   nsAccessibilityService* accService = nsIPresShell::AccService();
   if (accService) {
     accService->ContentRangeInserted(mPresShell, aContainer,
                                      aFirstNewContent, nullptr);
   }
 #endif
-
-  return NS_OK;
 }
 
 #ifdef MOZ_XUL
 
 enum content_operation
 {
     CONTENT_INSERTED,
     CONTENT_REMOVED
@@ -7743,27 +7737,27 @@ bool NotifyListBoxBody(nsPresContext*   
       return true;
     }
   }
 
   return false;
 }
 #endif // MOZ_XUL
 
-nsresult
+void
 nsCSSFrameConstructor::ContentInserted(nsIContent*            aContainer,
                                        nsIContent*            aChild,
                                        nsILayoutHistoryState* aFrameState,
                                        bool                   aAllowLazyConstruction)
 {
-  return ContentRangeInserted(aContainer,
-                              aChild,
-                              aChild->GetNextSibling(),
-                              aFrameState,
-                              aAllowLazyConstruction);
+  ContentRangeInserted(aContainer,
+                       aChild,
+                       aChild->GetNextSibling(),
+                       aFrameState,
+                       aAllowLazyConstruction);
 }
 
 // ContentRangeInserted handles creating frames for a range of nodes that
 // aren't at the end of their childlist. ContentRangeInserted isn't a real
 // content notification, but rather it handles regular ContentInserted calls
 // for a single node as well as the lazy construction of frames for a range of
 // nodes when called from CreateNeededFrames. For a range of nodes to be
 // suitable to have its frames constructed all at once they must meet the same
@@ -7774,17 +7768,17 @@ nsCSSFrameConstructor::ContentInserted(n
 // pass the first node in the range to GetInsertionPrevSibling, and if
 // IsValidSibling (the only place GetInsertionPrevSibling might look at the
 // passed in node itself) needs to resolve style on the node we record this and
 // return that this range needs to be split up and inserted separately. Table
 // captions need extra attention as we need to determine where to insert them
 // in the caption list, while skipping any nodes in the range being inserted
 // (because when we treat the caption frames the other nodes have had their
 // frames constructed but not yet inserted into the frame tree).
-nsresult
+void
 nsCSSFrameConstructor::ContentRangeInserted(nsIContent* aContainer,
                                             nsIContent* aStartChild,
                                             nsIContent* aEndChild,
                                             nsILayoutHistoryState* aFrameState,
                                             bool aAllowLazyConstruction,
                                             TreeMatchContext* aProvidedTreeMatchContext)
 {
   AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
@@ -7832,41 +7826,41 @@ nsCSSFrameConstructor::ContentRangeInser
 
 #ifdef MOZ_XUL
   if (aContainer && IsXULListBox(aContainer)) {
     if (isSingleInsert) {
       if (NotifyListBoxBody(mPresShell->GetPresContext(), aContainer,
                             // The insert case in NotifyListBoxBody
                             // doesn't use "old next sibling".
                             aStartChild, nullptr, nullptr, CONTENT_INSERTED)) {
-        return NS_OK;
+        return;
       }
     } else {
       // We don't handle a range insert to a listbox parent, issue single
       // ContertInserted calls for each node inserted.
       LAYOUT_PHASE_TEMP_EXIT();
       IssueSingleInsertNofications(aContainer, aStartChild, aEndChild,
                                    aAllowLazyConstruction);
       LAYOUT_PHASE_TEMP_REENTER();
-      return NS_OK;
+      return;
     }
   }
 #endif // MOZ_XUL
 
   // If we have a null parent, then this must be the document element being
   // inserted, or some other child of the document in the DOM (might be a PI,
   // say).
   if (! aContainer) {
     NS_ASSERTION(isSingleInsert,
                  "root node insertion should be a single insertion");
     Element *docElement = mDocument->GetRootElement();
 
     if (aStartChild != docElement) {
       // Not the root element; just bail out
-      return NS_OK;
+      return;
     }
 
     NS_PRECONDITION(nullptr == mRootElementFrame,
                     "root element frame already created");
 
     // Create frames for the document element and its child elements
     nsIFrame* docElementFrame =
       ConstructDocElementFrame(docElement, aFrameState);
@@ -7893,33 +7887,33 @@ nsCSSFrameConstructor::ContentRangeInser
 #ifdef ACCESSIBILITY
     nsAccessibilityService* accService = nsIPresShell::AccService();
     if (accService) {
       accService->ContentRangeInserted(mPresShell, aContainer,
                                        aStartChild, aEndChild);
     }
 #endif
 
-    return NS_OK;
+    return;
   }
 
   if (aContainer->HasFlag(NODE_IS_IN_SHADOW_TREE) &&
       !aContainer->IsInNativeAnonymousSubtree() &&
       (!aStartChild || !aStartChild->IsInNativeAnonymousSubtree()) &&
       (!aEndChild || !aEndChild->IsInNativeAnonymousSubtree())) {
     // Recreate frames if content is inserted into a ShadowRoot
     // because children of ShadowRoot are rendered in place of
     // the children of the host.
     //XXXsmaug This is super unefficient!
     nsIContent* bindingParent = aContainer->GetBindingParent();
     LAYOUT_PHASE_TEMP_EXIT();
-    nsresult rv = RecreateFramesForContent(bindingParent, false,
-                                           REMOVE_FOR_RECONSTRUCTION, nullptr);
+    RecreateFramesForContent(bindingParent, false,
+                             REMOVE_FOR_RECONSTRUCTION, nullptr);
     LAYOUT_PHASE_TEMP_REENTER();
-    return rv;
+    return;
   }
 
   // The frame constructor uses this codepath both for bonafide newly-added
   // content and for RestyleManager-driven frame construction (RECONSTRUCT_FRAME
   // and lazy frame construction). If we're using the Servo style system, we
   // want to ensure that styles get resolved in the first case, whereas for the
   // second case they should have already been resolved if needed.
   bool isNewlyAddedContentForServo = aContainer->IsStyledByServo() &&
@@ -7935,29 +7929,29 @@ nsCSSFrameConstructor::ContentRangeInser
     // for example, can be reframed by having its value attribute set or removed.
     if (!parentFrame && !aContainer->IsActiveChildrenElement()) {
       // We're punting on frame construction because there's no container frame.
       // The Servo-backed style system handles this case like the lazy frame
       // construction case.
       if (isNewlyAddedContentForServo) {
         aContainer->AsElement()->NoteDirtyDescendantsForServo();
       }
-      return NS_OK;
+      return;
     }
 
     // Otherwise, we've got parent content. Find its frame.
     NS_ASSERTION(!parentFrame || parentFrame->GetContent() == aContainer ||
                  GetDisplayContentsStyleFor(aContainer), "New XBL code is possibly wrong!");
 
     if (aAllowLazyConstruction &&
         MaybeConstructLazily(CONTENTINSERT, aContainer, aStartChild)) {
       if (isNewlyAddedContentForServo) {
         aContainer->AsElement()->NoteDirtyDescendantsForServo();
       }
-      return NS_OK;
+      return;
     }
   }
 
   // We couldn't construct lazily. Make Servo eagerly traverse the subtree.
   if (isNewlyAddedContentForServo) {
     mPresShell->StyleSet()->AsServo()->StyleNewChildren(aContainer->AsElement());
   }
 
@@ -7972,91 +7966,90 @@ nsCSSFrameConstructor::ContentRangeInser
     // GetRangeInsertionPoint will take care of that for us.
     LAYOUT_PHASE_TEMP_EXIT();
     insertion = GetRangeInsertionPoint(aContainer, aStartChild, aEndChild,
                                        aAllowLazyConstruction);
     LAYOUT_PHASE_TEMP_REENTER();
   }
 
   if (!insertion.mParentFrame) {
-    return NS_OK;
+    return;
   }
 
   bool isAppend, isRangeInsertSafe;
   nsIFrame* prevSibling = GetInsertionPrevSibling(&insertion, aStartChild,
                                                   &isAppend, &isRangeInsertSafe);
 
   // check if range insert is safe
   if (!isSingleInsert && !isRangeInsertSafe) {
     // must fall back to a single ContertInserted for each child in the range
     LAYOUT_PHASE_TEMP_EXIT();
     IssueSingleInsertNofications(aContainer, aStartChild, aEndChild,
                                  aAllowLazyConstruction);
     LAYOUT_PHASE_TEMP_REENTER();
-    return NS_OK;
+    return;
   }
 
   nsIContent* container = insertion.mParentFrame->GetContent();
 
   nsIAtom* frameType = insertion.mParentFrame->GetType();
   LAYOUT_PHASE_TEMP_EXIT();
   if (MaybeRecreateForFrameset(insertion.mParentFrame, aStartChild, aEndChild)) {
     LAYOUT_PHASE_TEMP_REENTER();
-    return NS_OK;
+    return;
   }
   LAYOUT_PHASE_TEMP_REENTER();
 
   // We should only get here with fieldsets when doing a single insert, because
   // fieldsets have multiple insertion points.
   NS_ASSERTION(isSingleInsert || frameType != nsGkAtoms::fieldSetFrame,
                "Unexpected parent");
   if (IsFrameForFieldSet(insertion.mParentFrame, frameType) &&
       aStartChild->NodeInfo()->NameAtom() == nsGkAtoms::legend) {
     // Just reframe the parent, since figuring out whether this
     // should be the new legend and then handling it is too complex.
     // We could do a little better here --- check if the fieldset already
     // has a legend which occurs earlier in its child list than this node,
     // and if so, proceed. But we'd have to extend nsFieldSetFrame
     // to locate this legend in the inserted frames and extract it.
     LAYOUT_PHASE_TEMP_EXIT();
-    nsresult rv = RecreateFramesForContent(insertion.mParentFrame->GetContent(), false,
-                                           REMOVE_FOR_RECONSTRUCTION, nullptr);
+    RecreateFramesForContent(insertion.mParentFrame->GetContent(), false,
+                             REMOVE_FOR_RECONSTRUCTION, nullptr);
     LAYOUT_PHASE_TEMP_REENTER();
-    return rv;
+    return;
   }
 
   // We should only get here with details when doing a single insertion because
   // we treat details frame as if it has multiple insertion points.
   MOZ_ASSERT(isSingleInsert || frameType != nsGkAtoms::detailsFrame);
   if (frameType == nsGkAtoms::detailsFrame) {
     // When inserting an element into <details>, just reframe the details frame
     // and let it figure out where the element should be laid out. It might seem
     // expensive to recreate the entire details frame, but it's the simplest way
     // to handle the insertion.
     LAYOUT_PHASE_TEMP_EXIT();
-    nsresult rv =
-      RecreateFramesForContent(insertion.mParentFrame->GetContent(), false,
-                               REMOVE_FOR_RECONSTRUCTION, nullptr);
+    RecreateFramesForContent(insertion.mParentFrame->GetContent(), false,
+                             REMOVE_FOR_RECONSTRUCTION, nullptr);
     LAYOUT_PHASE_TEMP_REENTER();
-    return rv;
+    return;
   }
 
   // Don't construct kids of leaves
   if (insertion.mParentFrame->IsLeaf()) {
     // Clear lazy bits so we don't try to construct again.
     ClearLazyBits(aStartChild, aEndChild);
-    return NS_OK;
+    return;
   }
 
   if (insertion.mParentFrame->IsFrameOfType(nsIFrame::eMathML)) {
     LAYOUT_PHASE_TEMP_EXIT();
-    nsresult rv = RecreateFramesForContent(insertion.mParentFrame->GetContent(), false,
-                                           REMOVE_FOR_RECONSTRUCTION, nullptr);
+    RecreateFramesForContent(insertion.mParentFrame->GetContent(), false,
+                             REMOVE_FOR_RECONSTRUCTION, nullptr);
     LAYOUT_PHASE_TEMP_REENTER();
-    return rv;
+    return;
   }
 
   Maybe<TreeMatchContext> matchContext;
   if (!aProvidedTreeMatchContext) {
     matchContext.emplace(mDocument, TreeMatchContext::ForFrameConstruction);
     matchContext->InitAncestors(aContainer ? aContainer->AsElement() : nullptr);
   }
   nsFrameConstructorState state(mPresShell,
@@ -8127,17 +8120,17 @@ nsCSSFrameConstructor::ContentRangeInser
         // Need to recover the letter frames first.
         RecoverLetterFrames(state.mFloatedItems.containingBlock);
 
         // must fall back to a single ContertInserted for each child in the range
         LAYOUT_PHASE_TEMP_EXIT();
         IssueSingleInsertNofications(aContainer, aStartChild, aEndChild,
                                      aAllowLazyConstruction);
         LAYOUT_PHASE_TEMP_REENTER();
-        return NS_OK;
+        return;
       }
 
       container = insertion.mParentFrame->GetContent();
       frameType = insertion.mParentFrame->GetType();
     }
   }
 
   if (!prevSibling) {
@@ -8195,17 +8188,17 @@ nsCSSFrameConstructor::ContentRangeInser
   // Perform special check for diddling around with the frames in
   // a special inline frame.
   // If we're appending before :after content, then we're not really
   // appending, so let WipeContainingBlock know that.
   LAYOUT_PHASE_TEMP_EXIT();
   if (WipeContainingBlock(state, containingBlock, insertion.mParentFrame, items,
                           isAppend, prevSibling)) {
     LAYOUT_PHASE_TEMP_REENTER();
-    return NS_OK;
+    return;
   }
   LAYOUT_PHASE_TEMP_REENTER();
 
   // If the container is a table and a caption will be appended, it needs to be
   // put in the table wrapper frame's additional child list.
   // We make no attempt here to set flags to indicate whether the list
   // will be at the start or end of a block. It doesn't seem worthwhile.
   nsFrameItems frameItems, captionItems;
@@ -8366,21 +8359,19 @@ nsCSSFrameConstructor::ContentRangeInser
 
 #ifdef ACCESSIBILITY
   nsAccessibilityService* accService = nsIPresShell::AccService();
   if (accService) {
     accService->ContentRangeInserted(mPresShell, aContainer,
                                      aStartChild, aEndChild);
   }
 #endif
-
-  return NS_OK;
-}
-
-nsresult
+}
+
+void
 nsCSSFrameConstructor::ContentRemoved(nsIContent*  aContainer,
                                       nsIContent*  aChild,
                                       nsIContent*  aOldNextSibling,
                                       RemoveFlags  aFlags,
                                       bool*        aDidReconstruct,
                                       nsIContent** aDestroyedFramesFor)
 {
   AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
@@ -8410,17 +8401,16 @@ nsCSSFrameConstructor::ContentRemoved(ns
            static_cast<void*>(aChild),
            static_cast<void*>(aOldNextSibling));
     if (gReallyNoisyContentUpdates) {
       aContainer->List(stdout, 0);
     }
   }
 #endif
 
-  nsresult rv = NS_OK;
   nsIFrame* childFrame = aChild->GetPrimaryFrame();
   if (!childFrame || childFrame->GetContent() != aChild) {
     // XXXbz the GetContent() != aChild check is needed due to bug 135040.
     // Remove it once that's fixed.
     ClearUndisplayedContentIn(aChild, aContainer);
   }
   MOZ_ASSERT(!childFrame || !GetDisplayContentsStyleFor(aChild),
              "display:contents nodes shouldn't have a frame");
@@ -8437,43 +8427,42 @@ nsCSSFrameConstructor::ContentRemoved(ns
       nsTArray<nsIContent*>* generated = ancestorFrame->GetGenConPseudos();
       if (generated) {
         *aDidReconstruct = true;
         LAYOUT_PHASE_TEMP_EXIT();
         // XXXmats Can we recreate frames only for the ::after/::before content?
         // XXX Perhaps even only those that belong to the aChild sub-tree?
         RecreateFramesForContent(ancestor, false, aFlags, aDestroyedFramesFor);
         LAYOUT_PHASE_TEMP_REENTER();
-        return NS_OK;
+        return;
       }
     }
 
     FlattenedChildIterator iter(aChild);
     for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) {
       if (c->GetPrimaryFrame() || GetDisplayContentsStyleFor(c)) {
         LAYOUT_PHASE_TEMP_EXIT();
-        rv = ContentRemoved(aChild, c, nullptr, aFlags, aDidReconstruct, aDestroyedFramesFor);
+        ContentRemoved(aChild, c, nullptr, aFlags, aDidReconstruct, aDestroyedFramesFor);
         LAYOUT_PHASE_TEMP_REENTER();
-        NS_ENSURE_SUCCESS(rv, rv);
         if (aFlags != REMOVE_DESTROY_FRAMES && *aDidReconstruct) {
-          return rv;
+          return;
         }
       }
     }
     ClearDisplayContentsIn(aChild, aContainer);
   }
 
   nsPresContext* presContext = mPresShell->GetPresContext();
 #ifdef MOZ_XUL
   if (NotifyListBoxBody(presContext, aContainer, aChild, aOldNextSibling,
                         childFrame, CONTENT_REMOVED)) {
     if (aFlags == REMOVE_DESTROY_FRAMES) {
       CaptureStateForFramesOf(aChild, mTempFrameTreeState);
     }
-    return NS_OK;
+    return;
   }
 
 #endif // MOZ_XUL
 
   // If we're removing the root, then make sure to remove things starting at
   // the viewport's child instead of the primary frame (which might even be
   // null if the root had an XBL binding or display:none, even though the
   // frames above it got created).  We do the adjustment after the childFrame
@@ -8501,87 +8490,86 @@ nsCSSFrameConstructor::ContentRemoved(ns
       !aChild->IsInNativeAnonymousSubtree()) {
     // Recreate frames if content is removed from a ShadowRoot
     // because it may contain an insertion point which can change
     // how the host is rendered.
     //XXXsmaug This is super unefficient!
     nsIContent* bindingParent = aContainer->GetBindingParent();
     *aDidReconstruct = true;
     LAYOUT_PHASE_TEMP_EXIT();
-    nsresult rv = RecreateFramesForContent(bindingParent, false,
-                                           aFlags, aDestroyedFramesFor);
+    RecreateFramesForContent(bindingParent, false, aFlags, aDestroyedFramesFor);
     LAYOUT_PHASE_TEMP_REENTER();
-    return rv;
+    return;
   }
 
   if (aFlags == REMOVE_DESTROY_FRAMES) {
     CaptureStateForFramesOf(aChild, mTempFrameTreeState);
   }
 
   if (childFrame) {
     InvalidateCanvasIfNeeded(mPresShell, aChild);
 
     // See whether we need to remove more than just childFrame
     LAYOUT_PHASE_TEMP_EXIT();
     nsIContent* container;
-    if (MaybeRecreateContainerForFrameRemoval(childFrame, aFlags, &rv, &container)) {
+    if (MaybeRecreateContainerForFrameRemoval(childFrame, aFlags, &container)) {
       LAYOUT_PHASE_TEMP_REENTER();
       MOZ_ASSERT(container);
       *aDidReconstruct = true;
       if (aDestroyedFramesFor) {
         *aDestroyedFramesFor = container;
       }
-      return rv;
+      return;
     }
     LAYOUT_PHASE_TEMP_REENTER();
 
     // Get the childFrame's parent frame
     nsIFrame* parentFrame = childFrame->GetParent();
     nsIAtom* parentType = parentFrame->GetType();
 
     if (parentType == nsGkAtoms::frameSetFrame &&
         IsSpecialFramesetChild(aChild)) {
       // Just reframe the parent, since framesets are weird like that.
       *aDidReconstruct = true;
       LAYOUT_PHASE_TEMP_EXIT();
-      nsresult rv = RecreateFramesForContent(parentFrame->GetContent(), false,
-                                             aFlags, aDestroyedFramesFor);
+      RecreateFramesForContent(parentFrame->GetContent(), false,
+                               aFlags, aDestroyedFramesFor);
       LAYOUT_PHASE_TEMP_REENTER();
-      return rv;
+      return;
     }
 
     // If we're a child of MathML, then we should reframe the MathML content.
     // If we're non-MathML, then we would be wrapped in a block so we need to
     // check our grandparent in that case.
     nsIFrame* possibleMathMLAncestor = parentType == nsGkAtoms::blockFrame ?
          parentFrame->GetParent() : parentFrame;
     if (possibleMathMLAncestor->IsFrameOfType(nsIFrame::eMathML)) {
       *aDidReconstruct = true;
       LAYOUT_PHASE_TEMP_EXIT();
-      nsresult rv = RecreateFramesForContent(possibleMathMLAncestor->GetContent(),
-                                             false, aFlags, aDestroyedFramesFor);
+      RecreateFramesForContent(possibleMathMLAncestor->GetContent(),
+                               false, aFlags, aDestroyedFramesFor);
       LAYOUT_PHASE_TEMP_REENTER();
-      return rv;
+      return;
     }
 
     // Undo XUL wrapping if it's no longer needed.
     // (If we're in the XUL block-wrapping situation, parentFrame is the
     // wrapper frame.)
     nsIFrame* grandparentFrame = parentFrame->GetParent();
     if (grandparentFrame && grandparentFrame->IsXULBoxFrame() &&
         (grandparentFrame->GetStateBits() & NS_STATE_BOX_WRAPS_KIDS_IN_BLOCK) &&
         // check if this frame is the only one needing wrapping
         aChild == AnyKidsNeedBlockParent(parentFrame->PrincipalChildList().FirstChild()) &&
         !AnyKidsNeedBlockParent(childFrame->GetNextSibling())) {
       *aDidReconstruct = true;
       LAYOUT_PHASE_TEMP_EXIT();
-      nsresult rv = RecreateFramesForContent(grandparentFrame->GetContent(), true,
-                                             aFlags, aDestroyedFramesFor);
+      RecreateFramesForContent(grandparentFrame->GetContent(), true,
+                               aFlags, aDestroyedFramesFor);
       LAYOUT_PHASE_TEMP_REENTER();
-      return rv;
+      return;
     }
 
 #ifdef ACCESSIBILITY
     nsAccessibilityService* accService = nsIPresShell::AccService();
     if (accService) {
       accService->ContentRemoved(mPresShell, aChild);
     }
 #endif
@@ -8615,17 +8603,17 @@ nsCSSFrameConstructor::ContentRemoved(ns
       RemoveLetterFrames(mPresShell, containingBlock);
 
       // Recover childFrame and parentFrame
       childFrame = aChild->GetPrimaryFrame();
       if (!childFrame || childFrame->GetContent() != aChild) {
         // XXXbz the GetContent() != aChild check is needed due to bug 135040.
         // Remove it once that's fixed.
         ClearUndisplayedContentIn(aChild, aContainer);
-        return NS_OK;
+        return;
       }
       parentFrame = childFrame->GetParent();
       parentType = parentFrame->GetType();
 
 #ifdef NOISY_FIRST_LETTER
       printf("  ==> revised parentFrame=");
       nsFrame::ListTag(stdout, parentFrame);
       printf(" childFrame=");
@@ -8703,18 +8691,16 @@ nsCSSFrameConstructor::ContentRemoved(ns
 
 #ifdef DEBUG
     if (gReallyNoisyContentUpdates && parentFrame) {
       printf("nsCSSFrameConstructor::ContentRemoved: resulting frame model:\n");
       parentFrame->List(stdout, 0);
     }
 #endif
   }
-
-  return rv;
 }
 
 /**
  * This method invalidates the canvas when frames are removed or added for a
  * node that might have its background propagated to the canvas, i.e., a
  * document root node or an HTML BODY which is a child of the root node.
  *
  * @param aFrame a frame for a content node about to be removed or a frame that
@@ -8766,37 +8752,36 @@ nsCSSFrameConstructor::EnsureFrameForTex
     nsAutoScriptBlocker blocker;
     BeginUpdate();
     ReconstructDocElementHierarchy();
     EndUpdate();
   }
   return aContent->GetPrimaryFrame();
 }
 
-nsresult
+void
 nsCSSFrameConstructor::CharacterDataChanged(nsIContent* aContent,
                                             CharacterDataChangeInfo* aInfo)
 {
   AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
-  nsresult      rv = NS_OK;
 
   if ((aContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE) &&
        !aContent->TextIsOnlyWhitespace()) ||
       (aContent->HasFlag(NS_REFRAME_IF_WHITESPACE) &&
        aContent->TextIsOnlyWhitespace())) {
 #ifdef DEBUG
     nsIFrame* frame = aContent->GetPrimaryFrame();
     NS_ASSERTION(!frame || !frame->IsGeneratedContentFrame(),
                  "Bit should never be set on generated content");
 #endif
     LAYOUT_PHASE_TEMP_EXIT();
-    nsresult rv = RecreateFramesForContent(aContent, false,
-                                           REMOVE_FOR_RECONSTRUCTION, nullptr);
+    RecreateFramesForContent(aContent, false,
+                             REMOVE_FOR_RECONSTRUCTION, nullptr);
     LAYOUT_PHASE_TEMP_REENTER();
-    return rv;
+    return;
   }
 
   // Find the child frame
   nsIFrame* frame = aContent->GetPrimaryFrame();
 
   // Notify the first frame that maps the content. It will generate a reflow
   // command
 
@@ -8834,18 +8819,16 @@ nsCSSFrameConstructor::CharacterDataChan
     }
 
     frame->CharacterDataChanged(aInfo);
 
     if (haveFirstLetterStyle) {
       RecoverLetterFrames(block);
     }
   }
-
-  return rv;
 }
 
 void
 nsCSSFrameConstructor::BeginUpdate() {
   NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
                "Someone forgot a script blocker");
 
   nsRootPresContext* rootPresContext =
@@ -9502,22 +9485,20 @@ FindPreviousNonWhitespaceSibling(nsIFram
     f = f->GetPrevSibling();
   } while (f && IsWhitespaceFrame(f));
   return f;
 }
 
 bool
 nsCSSFrameConstructor::MaybeRecreateContainerForFrameRemoval(nsIFrame* aFrame,
                                                              RemoveFlags aFlags,
-                                                             nsresult* aResult,
                                                              nsIContent** aDestroyedFramesFor)
 {
   NS_PRECONDITION(aFrame, "Must have a frame");
   NS_PRECONDITION(aFrame->GetParent(), "Frame shouldn't be root");
-  NS_PRECONDITION(aResult, "Null out param?");
   NS_PRECONDITION(aFrame == aFrame->FirstContinuation(),
                   "aFrame not the result of GetPrimaryFrame()?");
 
   *aDestroyedFramesFor = nullptr;
 
   if (IsFramePartOfIBSplit(aFrame)) {
     // The removal functions can't handle removal of an {ib} split directly; we
     // need to rebuild the containing block.
@@ -9525,42 +9506,42 @@ nsCSSFrameConstructor::MaybeRecreateCont
     if (gNoisyContentUpdates) {
       printf("nsCSSFrameConstructor::MaybeRecreateContainerForFrameRemoval: "
              "frame=");
       nsFrame::ListTag(stdout, aFrame);
       printf(" is ib-split\n");
     }
 #endif
 
-    *aResult = ReframeContainingBlock(aFrame, aFlags, aDestroyedFramesFor);
+    ReframeContainingBlock(aFrame, aFlags, aDestroyedFramesFor);
     return true;
   }
 
   nsContainerFrame* insertionFrame = aFrame->GetContentInsertionFrame();
   if (insertionFrame && insertionFrame->GetType() == nsGkAtoms::legendFrame &&
       aFrame->GetParent()->GetType() == nsGkAtoms::fieldSetFrame) {
     // When we remove the legend for a fieldset, we should reframe
     // the fieldset to ensure another legend is used, if there is one
-    *aResult = RecreateFramesForContent(aFrame->GetParent()->GetContent(), false,
-                                        aFlags, aDestroyedFramesFor);
+    RecreateFramesForContent(aFrame->GetParent()->GetContent(), false,
+                             aFlags, aDestroyedFramesFor);
     return true;
   }
 
   if (insertionFrame &&
       aFrame->GetParent()->GetType() == nsGkAtoms::detailsFrame) {
     HTMLSummaryElement* summary =
       HTMLSummaryElement::FromContent(insertionFrame->GetContent());
 
     if (summary && summary->IsMainSummary()) {
       // When removing a summary, we should reframe the parent details frame to
       // ensure that another summary is used or the default summary is
       // generated.
-      *aResult = RecreateFramesForContent(aFrame->GetParent()->GetContent(),
-                                          false, REMOVE_FOR_RECONSTRUCTION,
-                                          aDestroyedFramesFor);
+      RecreateFramesForContent(aFrame->GetParent()->GetContent(),
+                               false, REMOVE_FOR_RECONSTRUCTION,
+                               aDestroyedFramesFor);
       return true;
     }
   }
 
   // Now check for possibly needing to reconstruct due to a pseudo parent
   nsIFrame* inFlowFrame =
     (aFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) ?
       GetPlaceholderFrameFor(aFrame) : aFrame;
@@ -9582,18 +9563,18 @@ nsCSSFrameConstructor::MaybeRecreateCont
         // not going to catch cases when we're the first child.
         (inFlowFrame->GetType() == nsGkAtoms::tableColGroupFrame &&
          parent->GetChildList(nsIFrame::kColGroupList).FirstChild() == inFlowFrame) ||
         // Similar if we're a table-caption.
         (inFlowFrame->IsTableCaption() &&
          parent->GetChildList(nsIFrame::kCaptionList).FirstChild() == inFlowFrame)) {
       // We're the first or last frame in the pseudo.  Need to reframe.
       // Good enough to recreate frames for |parent|'s content
-      *aResult = RecreateFramesForContent(parent->GetContent(), true, aFlags,
-                                          aDestroyedFramesFor);
+      RecreateFramesForContent(parent->GetContent(), true, aFlags,
+                               aDestroyedFramesFor);
       return true;
     }
   }
 
   // Might need to reconstruct things if this frame's nextSibling is a table
   // or ruby pseudo, since removal of this frame might mean that this pseudo
   // needs to get merged with the frame's prevSibling if that's also a table
   // or ruby pseudo.
@@ -9612,35 +9593,35 @@ nsCSSFrameConstructor::MaybeRecreateCont
                "frame=");
         nsFrame::ListTag(stdout, aFrame);
         printf(" has a table pseudo next sibling of different type and a "
                "table pseudo prevsibling\n");
       }
 #endif
       // Good enough to recreate frames for aFrame's parent's content; even if
       // aFrame's parent is a pseudo, that'll be the right content node.
-      *aResult = RecreateFramesForContent(parent->GetContent(), true, aFlags,
-                                          aDestroyedFramesFor);
+      RecreateFramesForContent(parent->GetContent(), true, aFlags,
+                               aDestroyedFramesFor);
       return true;
     }
   }
 
   // Check ruby containers
   nsIAtom* parentType = parent->GetType();
   if (parentType == nsGkAtoms::rubyFrame ||
       RubyUtils::IsRubyContainerBox(parentType)) {
     // In ruby containers, pseudo frames may be created from
     // whitespaces or even nothing. There are two cases we actually
     // need to handle here, but hard to check exactly:
     // 1. Status of spaces beside the frame may vary, and related
     //    frames may be constructed or destroyed accordingly.
     // 2. The type of the first child of a ruby frame determines
     //    whether a pseudo ruby base container should exist.
-    *aResult = RecreateFramesForContent(parent->GetContent(), true, aFlags,
-                                        aDestroyedFramesFor);
+    RecreateFramesForContent(parent->GetContent(), true, aFlags,
+                             aDestroyedFramesFor);
     return true;
   }
 
   // Might need to reconstruct things if the removed frame's nextSibling is an
   // anonymous flex item.  The removed frame might've been what divided two
   // runs of inline content into two anonymous flex items, which would now
   // need to be merged.
   // NOTE: It's fine that we've advanced nextSibling past whitespace (up above);
@@ -9653,18 +9634,18 @@ nsCSSFrameConstructor::MaybeRecreateCont
     if (gNoisyContentUpdates) {
       printf("nsCSSFrameConstructor::MaybeRecreateContainerForFrameRemoval: "
              "frame=");
       nsFrame::ListTag(stdout, aFrame);
       printf(" has an anonymous flex item as its next sibling\n");
     }
 #endif // DEBUG
     // Recreate frames for the flex container (the removed frame's parent)
-    *aResult = RecreateFramesForContent(parent->GetContent(), true, aFlags,
-                                        aDestroyedFramesFor);
+    RecreateFramesForContent(parent->GetContent(), true, aFlags,
+                             aDestroyedFramesFor);
     return true;
   }
 
   // Might need to reconstruct things if the removed frame's nextSibling is
   // null and its parent is an anonymous flex item. (This might be the last
   // remaining child of that anonymous flex item, which can then go away.)
   if (!nextSibling && IsAnonymousFlexOrGridItem(parent)) {
     AssertAnonymousFlexOrGridItemParent(parent, parent->GetParent());
@@ -9672,39 +9653,39 @@ nsCSSFrameConstructor::MaybeRecreateCont
     if (gNoisyContentUpdates) {
       printf("nsCSSFrameConstructor::MaybeRecreateContainerForFrameRemoval: "
              "frame=");
       nsFrame::ListTag(stdout, aFrame);
       printf(" has an anonymous flex item as its parent\n");
     }
 #endif // DEBUG
     // Recreate frames for the flex container (the removed frame's grandparent)
-    *aResult = RecreateFramesForContent(parent->GetParent()->GetContent(), true,
-                                        aFlags, aDestroyedFramesFor);
+    RecreateFramesForContent(parent->GetParent()->GetContent(), true,
+                             aFlags, aDestroyedFramesFor);
     return true;
   }
 
 #ifdef MOZ_XUL
   if (aFrame->GetType() == nsGkAtoms::popupSetFrame) {
     nsIRootBox* rootBox = nsIRootBox::GetRootBox(mPresShell);
     if (rootBox && rootBox->GetPopupSetFrame() == aFrame) {
-      *aResult = ReconstructDocElementHierarchy();
+      ReconstructDocElementHierarchy();
       return true;
     }
   }
 #endif
 
   // Reconstruct if inflowFrame is parent's only child, and parent is, or has,
   // a non-fluid continuation, i.e. it was split by bidi resolution
   if (!inFlowFrame->GetPrevSibling() &&
       !inFlowFrame->GetNextSibling() &&
       ((parent->GetPrevContinuation() && !parent->GetPrevInFlow()) ||
        (parent->GetNextContinuation() && !parent->GetNextInFlow()))) {
-    *aResult = RecreateFramesForContent(parent->GetContent(), true, aFlags,
-                                        aDestroyedFramesFor);
+    RecreateFramesForContent(parent->GetContent(), true, aFlags,
+                             aDestroyedFramesFor);
     return true;
   }
 
   // We might still need to reconstruct things if the parent of inFlowFrame is
   // ib-split, since in that case the removal of aFrame might affect the
   // splitting of its parent.
   if (!IsFramePartOfIBSplit(parent)) {
     return false;
@@ -9730,34 +9711,36 @@ nsCSSFrameConstructor::MaybeRecreateCont
   if (gNoisyContentUpdates) {
     printf("nsCSSFrameConstructor::MaybeRecreateContainerForFrameRemoval: "
            "frame=");
     nsFrame::ListTag(stdout, parent);
     printf(" is ib-split\n");
   }
 #endif
 
-  *aResult = ReframeContainingBlock(parent, aFlags, aDestroyedFramesFor);
+  ReframeContainingBlock(parent, aFlags, aDestroyedFramesFor);
   return true;
 }
 
-nsresult
+void
 nsCSSFrameConstructor::RecreateFramesForContent(nsIContent*  aContent,
                                                 bool         aAsyncInsert,
                                                 RemoveFlags  aFlags,
                                                 nsIContent** aDestroyedFramesFor)
 {
   NS_PRECONDITION(!aAsyncInsert || aContent->IsElement(),
                   "Can only insert elements async");
   // If there is no document, we don't want to recreate frames for it.  (You
   // shouldn't generally be giving this method content without a document
   // anyway).
   // Rebuilding the frame tree can have bad effects, especially if it's the
   // frame tree for chrome (see bug 157322).
-  NS_ENSURE_TRUE(aContent->GetComposedDoc(), NS_ERROR_FAILURE);
+  if (NS_WARN_IF(!aContent->GetComposedDoc())) {
+    return;
+  }
 
   // Is the frame ib-split? If so, we need to reframe the containing
   // block *here*, rather than trying to remove and re-insert the
   // content (which would otherwise result in *two* nested reframe
   // containing block from ContentRemoved() and ContentInserted(),
   // below!).  We'd really like to optimize away one of those
   // containing block reframes, hence the code here.
 
@@ -9811,25 +9794,24 @@ nsCSSFrameConstructor::RecreateFramesFor
     // with native anonymous content from the editor.
     if (parent && parent->IsLeaf() && parentContent &&
         parentContent != aContent) {
       return RecreateFramesForContent(parentContent, aAsyncInsert, aFlags,
                                       aDestroyedFramesFor);
     }
   }
 
-  nsresult rv = NS_OK;
   nsIContent* container;
-  if (frame && MaybeRecreateContainerForFrameRemoval(frame, aFlags, &rv,
+  if (frame && MaybeRecreateContainerForFrameRemoval(frame, aFlags,
                                                      &container)) {
     MOZ_ASSERT(container);
     if (aDestroyedFramesFor) {
       *aDestroyedFramesFor = container;
     }
-    return rv;
+    return;
   }
 
   nsINode* containerNode = aContent->GetParentNode();
   // XXXbz how can containerNode be null here?
   if (containerNode) {
     // Before removing the frames associated with the content object,
     // ask them to save their state onto a temporary state object.
     CaptureStateForFramesOf(aContent, mTempFrameTreeState);
@@ -9839,36 +9821,31 @@ nsCSSFrameConstructor::RecreateFramesFor
     nsCOMPtr<nsIContent> container = aContent->GetParent();
 
     // Remove the frames associated with the content object.
     bool didReconstruct;
     nsIContent* nextSibling = aContent->IsRootOfAnonymousSubtree() ?
       nullptr : aContent->GetNextSibling();
     const bool reconstruct = aFlags != REMOVE_DESTROY_FRAMES;
     RemoveFlags flags = reconstruct ? REMOVE_FOR_RECONSTRUCTION : aFlags;
-    rv = ContentRemoved(container, aContent, nextSibling, flags,
-                        &didReconstruct, aDestroyedFramesFor);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
+    ContentRemoved(container, aContent, nextSibling, flags,
+                   &didReconstruct, aDestroyedFramesFor);
     if (reconstruct && !didReconstruct) {
       // Now, recreate the frames associated with this content object. If
       // ContentRemoved triggered reconstruction, then we don't need to do this
       // because the frames will already have been built.
       if (aAsyncInsert) {
         // XXXmats doesn't frame state need to be restored in this case too?
         RestyleManager()->PostRestyleEvent(
           aContent->AsElement(), nsRestyleHint(0), nsChangeHint_ReconstructFrame);
       } else {
-        rv = ContentInserted(container, aContent, mTempFrameTreeState, false);
-      }
-    }
-  }
-
-  return rv;
+        ContentInserted(container, aContent, mTempFrameTreeState, false);
+      }
+    }
+  }
 }
 
 void
 nsCSSFrameConstructor::DestroyFramesFor(nsIContent*  aContent,
                                         nsIContent** aDestroyedFramesFor)
 {
   MOZ_ASSERT(aContent && aContent->GetParentNode());
 
@@ -11266,26 +11243,25 @@ nsCSSFrameConstructor::AppendFirstLineFr
   nsFirstLineFrame* lineFrame = static_cast<nsFirstLineFrame*>(lastBlockKid);
   WrapFramesInFirstLineFrame(aState, aBlockContent, aBlockFrame,
                              lineFrame, aFrameItems);
 }
 
 // Special routine to handle inserting a new frame into a block
 // frame's child list. Takes care of placing the new frame into the
 // right place when first-line style is present.
-nsresult
+void
 nsCSSFrameConstructor::InsertFirstLineFrames(
   nsFrameConstructorState& aState,
   nsIContent*              aContent,
   nsIFrame*                aBlockFrame,
   nsContainerFrame**       aParentFrame,
   nsIFrame*                aPrevSibling,
   nsFrameItems&            aFrameItems)
 {
-  nsresult rv = NS_OK;
   // XXXbz If you make this method actually do something, check to
   // make sure that the caller is passing what you expect.  In
   // particular, which content is aContent?  And audit the rest of
   // this code too; it makes bogus assumptions and may not build.
 #if 0
   nsIFrame* parentFrame = *aParentFrame;
   nsIFrame* newFrame = aFrameItems.childList;
   bool isInline = IsInlineOutside(newFrame);
@@ -11405,17 +11381,16 @@ nsCSSFrameConstructor::InsertFirstLineFr
           aFrameItems.childList = nullptr;
           aFrameItems.lastChild = nullptr;
         }
       }
     }
   }
 
 #endif
-  return rv;
 }
 
 //----------------------------------------------------------------------
 
 // First-letter support
 
 // Determine how many characters in the text fragment apply to the
 // first letter
@@ -11732,58 +11707,58 @@ FindFirstLetterFrame(nsIFrame* aFrame, n
   for (nsFrameList::Enumerator e(list); !e.AtEnd(); e.Next()) {
     if (nsGkAtoms::letterFrame == e.get()->GetType()) {
       return e.get();
     }
   }
   return nullptr;
 }
 
-nsresult
+void
 nsCSSFrameConstructor::RemoveFloatingFirstLetterFrames(
   nsIPresShell* aPresShell,
   nsIFrame* aBlockFrame)
 {
   // Look for the first letter frame on the kFloatList, then kPushedFloatsList.
   nsIFrame* floatFrame =
     ::FindFirstLetterFrame(aBlockFrame, nsIFrame::kFloatList);
   if (!floatFrame) {
     floatFrame =
       ::FindFirstLetterFrame(aBlockFrame, nsIFrame::kPushedFloatsList);
     if (!floatFrame) {
-      return NS_OK;
+      return;
     }
   }
 
   // Take the text frame away from the letter frame (so it isn't
   // destroyed when we destroy the letter frame).
   nsIFrame* textFrame = floatFrame->PrincipalChildList().FirstChild();
   if (!textFrame) {
-    return NS_OK;
+    return;
   }
 
   // Discover the placeholder frame for the letter frame
   nsPlaceholderFrame* placeholderFrame = GetPlaceholderFrameFor(floatFrame);
   if (!placeholderFrame) {
     // Somethings really wrong
-    return NS_OK;
+    return;
   }
   nsContainerFrame* parentFrame = placeholderFrame->GetParent();
   if (!parentFrame) {
     // Somethings really wrong
-    return NS_OK;
+    return;
   }
 
   // Create a new text frame with the right style context that maps
   // all of the content that was previously part of the letter frame
   // (and probably continued elsewhere).
   nsStyleContext* parentSC = parentFrame->StyleContext();
   nsIContent* textContent = textFrame->GetContent();
   if (!textContent) {
-    return NS_OK;
+    return;
   }
   RefPtr<nsStyleContext> newSC = aPresShell->StyleSet()->
     ResolveStyleForText(textContent, parentSC);
   nsIFrame* newTextFrame = NS_NewTextFrame(aPresShell, newSC);
   newTextFrame->Init(textContent, parentFrame, nullptr);
 
   // Destroy the old text frame's continuations (the old text frame
   // will be destroyed when its letter frame is destroyed).
@@ -11818,21 +11793,19 @@ nsCSSFrameConstructor::RemoveFloatingFir
 
   // Insert text frame in its place
   nsFrameList textList(newTextFrame, newTextFrame);
   InsertFrames(parentFrame, kPrincipalList, prevSibling, textList);
 
   if (offsetsNeedFixing) {
     prevSibling->RemoveStateBits(TEXT_OFFSETS_NEED_FIXING);
   }
-
-  return NS_OK;
-}
-
-nsresult
+}
+
+void
 nsCSSFrameConstructor::RemoveFirstLetterFrames(nsIPresShell* aPresShell,
                                                nsContainerFrame* aFrame,
                                                nsContainerFrame* aBlockFrame,
                                                bool* aStopLooking)
 {
   nsIFrame* prevSibling = nullptr;
   nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
 
@@ -11895,43 +11868,37 @@ nsCSSFrameConstructor::RemoveFirstLetter
         if (*aStopLooking) {
           break;
         }
       }
     }
     prevSibling = kid;
     kid = kid->GetNextSibling();
   }
-
-  return NS_OK;
-}
-
-nsresult
+}
+
+void
 nsCSSFrameConstructor::RemoveLetterFrames(nsIPresShell* aPresShell,
                                           nsContainerFrame* aBlockFrame)
 {
   aBlockFrame =
     static_cast<nsContainerFrame*>(aBlockFrame->FirstContinuation());
   nsContainerFrame* continuation = aBlockFrame;
 
   bool stopLooking = false;
-  nsresult rv;
   do {
-    rv = RemoveFloatingFirstLetterFrames(aPresShell, continuation);
-    if (NS_SUCCEEDED(rv)) {
-      rv = RemoveFirstLetterFrames(aPresShell,
-                                   continuation, aBlockFrame, &stopLooking);
-    }
+    RemoveFloatingFirstLetterFrames(aPresShell, continuation);
+    RemoveFirstLetterFrames(aPresShell, continuation, aBlockFrame,
+                            &stopLooking);
     if (stopLooking) {
       break;
     }
     continuation =
       static_cast<nsContainerFrame*>(continuation->GetNextContinuation());
   }  while (continuation);
-  return rv;
 }
 
 // Fixup the letter frame situation for the given block
 void
 nsCSSFrameConstructor::RecoverLetterFrames(nsContainerFrame* aBlockFrame)
 {
   aBlockFrame =
     static_cast<nsContainerFrame*>(aBlockFrame->FirstContinuation());
@@ -11964,26 +11931,24 @@ nsCSSFrameConstructor::RecoverLetterFram
     parentFrame->InsertFrames(kPrincipalList, prevFrame, letterFrames);
   }
 }
 
 //----------------------------------------------------------------------
 
 // listbox Widget Routines
 
-nsresult
+void
 nsCSSFrameConstructor::CreateListBoxContent(nsContainerFrame*      aParentFrame,
                                             nsIFrame*              aPrevFrame,
                                             nsIContent*            aChild,
                                             nsIFrame**             aNewFrame,
                                             bool                   aIsAppend)
 {
 #ifdef MOZ_XUL
-  nsresult rv = NS_OK;
-
   // Construct a new frame
   if (nullptr != aParentFrame) {
     nsFrameItems            frameItems;
     TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction);
     nsFrameConstructorState state(mPresShell,
                                   matchContext,
                                   GetAbsoluteContainingBlock(aParentFrame, FIXED_POS),
                                   GetAbsoluteContainingBlock(aParentFrame, ABS_POS),
@@ -11997,17 +11962,17 @@ nsCSSFrameConstructor::CreateListBoxCont
     styleContext = ResolveStyleContext(aParentFrame, aChild, &state);
 
     // Pre-check for display "none" - only if we find that, do we create
     // any frame at all
     const nsStyleDisplay* display = styleContext->StyleDisplay();
 
     if (StyleDisplay::None == display->mDisplay) {
       *aNewFrame = nullptr;
-      return NS_OK;
+      return;
     }
 
     BeginUpdate();
 
     FrameConstructionItemList items;
     AddFrameConstructionItemsInternal(state, aChild, aParentFrame,
                                       aChild->NodeInfo()->NameAtom(),
                                       aChild->GetNameSpaceID(),
@@ -12016,37 +11981,33 @@ nsCSSFrameConstructor::CreateListBoxCont
     ConstructFramesFromItemList(state, items, aParentFrame, frameItems);
 
     nsIFrame* newFrame = frameItems.FirstChild();
     *aNewFrame = newFrame;
 
     if (newFrame) {
       // Notify the parent frame
       if (aIsAppend)
-        rv = ((nsListBoxBodyFrame*)aParentFrame)->ListBoxAppendFrames(frameItems);
+        ((nsListBoxBodyFrame*)aParentFrame)->ListBoxAppendFrames(frameItems);
       else
-        rv = ((nsListBoxBodyFrame*)aParentFrame)->ListBoxInsertFrames(aPrevFrame, frameItems);
+        ((nsListBoxBodyFrame*)aParentFrame)->ListBoxInsertFrames(aPrevFrame, frameItems);
     }
 
     EndUpdate();
 
 #ifdef ACCESSIBILITY
     if (newFrame) {
       nsAccessibilityService* accService = nsIPresShell::AccService();
       if (accService) {
         accService->ContentRangeInserted(mPresShell, aChild->GetParent(),
                                          aChild, aChild->GetNextSibling());
       }
     }
 #endif
   }
-
-  return rv;
-#else
-  return NS_ERROR_FAILURE;
 #endif
 }
 
 //----------------------------------------
 
 void
 nsCSSFrameConstructor::ConstructBlock(nsFrameConstructorState& aState,
                                       nsIContent*              aContent,
@@ -12810,17 +12771,17 @@ nsCSSFrameConstructor::WipeContainingBlo
            static_cast<void*>(blockContent));
   }
 #endif
   RecreateFramesForContent(blockContent, true, REMOVE_FOR_RECONSTRUCTION,
                            nullptr);
   return true;
 }
 
-nsresult
+void
 nsCSSFrameConstructor::ReframeContainingBlock(nsIFrame*    aFrame,
                                               RemoveFlags  aFlags,
                                               nsIContent** aDestroyedFramesFor)
 {
 
 #ifdef DEBUG
   // ReframeContainingBlock is a NASTY routine, it causes terrible performance problems
   // so I want to see when it is happening!  Unfortunately, it is happening way to often because
@@ -12833,17 +12794,17 @@ nsCSSFrameConstructor::ReframeContaining
 #endif
 
   // XXXbz how exactly would we get here while isReflowing anyway?  Should this
   // whole test be ifdef DEBUG?
   if (mPresShell->IsReflowLocked()) {
     // don't ReframeContainingBlock, this will result in a crash
     // if we remove a tree that's in reflow - see bug 121368 for testcase
     NS_ERROR("Atemptted to nsCSSFrameConstructor::ReframeContainingBlock during a Reflow!!!");
-    return NS_OK;
+    return;
   }
 
   // Get the first "normal" ancestor of the target frame.
   nsIFrame* containingBlock = GetIBContainingBlockFor(aFrame);
   if (containingBlock) {
     // From here we look for the containing block in case the target
     // frame is already a block (which can happen when an inline frame
     // wraps some of its content in an anonymous block; see
@@ -12865,17 +12826,17 @@ nsCSSFrameConstructor::ReframeContaining
     }
   }
 
   // If we get here, we're screwed!
   return RecreateFramesForContent(mPresShell->GetDocument()->GetRootElement(),
                                   true, aFlags, nullptr);
 }
 
-nsresult
+void
 nsCSSFrameConstructor::GenerateChildFrames(nsContainerFrame* aFrame)
 {
   {
     nsAutoScriptBlocker scriptBlocker;
     BeginUpdate();
 
     nsFrameItems childItems;
     TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction);
@@ -12900,18 +12861,16 @@ nsCSSFrameConstructor::GenerateChildFram
     if (child) {
       accService->ContentRangeInserted(mPresShell, container, child, nullptr);
     }
   }
 #endif
 
   // call XBL constructors after the frames are created
   mPresShell->GetDocument()->BindingManager()->ProcessAttachedQueue();
-
-  return NS_OK;
 }
 
 //////////////////////////////////////////////////////////
 // nsCSSFrameConstructor::FrameConstructionItem methods //
 //////////////////////////////////////////////////////////
 bool
 nsCSSFrameConstructor::
 FrameConstructionItem::IsWhitespace(nsFrameConstructorState& aState) const
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -43,29 +43,29 @@ namespace mozilla {
 
 namespace dom {
 
 class FlattenedChildIterator;
 
 } // namespace dom
 } // namespace mozilla
 
-class nsCSSFrameConstructor : public nsFrameManager
+class nsCSSFrameConstructor final : public nsFrameManager
 {
 public:
   typedef mozilla::CSSPseudoElementType CSSPseudoElementType;
   typedef mozilla::dom::Element Element;
 
   friend class mozilla::RestyleManager;
   friend class mozilla::GeckoRestyleManager;
   friend class mozilla::ServoRestyleManager;
 
   nsCSSFrameConstructor(nsIDocument* aDocument, nsIPresShell* aPresShell);
   ~nsCSSFrameConstructor(void) {
-    NS_ASSERTION(mUpdateCount == 0, "Dying in the middle of our own update?");
+    MOZ_ASSERT(mUpdateCount == 0, "Dying in the middle of our own update?");
   }
 
   // get the alternate text for a content node
   static void GetAlternateTextFor(nsIContent*    aContent,
                                   nsIAtom*       aTag,  // content object's tag
                                   nsXPIDLString& aAltText);
 
 private:
@@ -73,17 +73,17 @@ private:
   nsCSSFrameConstructor& operator=(const nsCSSFrameConstructor& aCopy) = delete;
 
 public:
   mozilla::RestyleManager* RestyleManager() const
     { return mPresShell->GetPresContext()->RestyleManager(); }
 
   nsIFrame* ConstructRootFrame();
 
-  nsresult ReconstructDocElementHierarchy();
+  void ReconstructDocElementHierarchy();
 
   // Create frames for content nodes that are marked as needing frames. This
   // should be called before ProcessPendingRestyles.
   // Note: It's the caller's responsibility to make sure to wrap a
   // CreateNeededFrames call in a view update batch and a script blocker.
   void CreateNeededFrames();
 
 private:
@@ -203,45 +203,45 @@ public:
 
   // If aAllowLazyConstruction is true then frame construction of the new
   // children can be done lazily.
   //
   // When constructing frames lazily, we can keep the tree match context in a
   // much easier way than nsFrameConstructorState, and thus, we're allowed to
   // provide a TreeMatchContext to avoid calling InitAncestors repeatedly deep
   // in the DOM.
-  nsresult ContentAppended(nsIContent* aContainer,
-                           nsIContent* aFirstNewContent,
-                           bool aAllowLazyConstruction,
-                           TreeMatchContext* aProvidedTreeMatchContext = nullptr);
+  void ContentAppended(nsIContent* aContainer,
+                       nsIContent* aFirstNewContent,
+                       bool aAllowLazyConstruction,
+                       TreeMatchContext* aProvidedTreeMatchContext = nullptr);
 
   // If aAllowLazyConstruction is true then frame construction of the new child
   // can be done lazily.
-  nsresult ContentInserted(nsIContent* aContainer,
-                           nsIContent* aChild,
-                           nsILayoutHistoryState* aFrameState,
-                           bool aAllowLazyConstruction);
+  void ContentInserted(nsIContent* aContainer,
+                       nsIContent* aChild,
+                       nsILayoutHistoryState* aFrameState,
+                       bool aAllowLazyConstruction);
 
   // Like ContentInserted but handles inserting the children of aContainer in
   // the range [aStartChild, aEndChild).  aStartChild must be non-null.
   // aEndChild may be null to indicate the range includes all kids after
   // aStartChild.
   //
   // If aAllowLazyConstruction is true then frame construction of
   // the new children can be done lazily. It is only allowed to be true when
   // inserting a single node.
   //
   // See ContentAppended to see why we allow passing an already initialized
   // TreeMatchContext.
-  nsresult ContentRangeInserted(nsIContent* aContainer,
-                                nsIContent* aStartChild,
-                                nsIContent* aEndChild,
-                                nsILayoutHistoryState* aFrameState,
-                                bool aAllowLazyConstruction,
-                                TreeMatchContext* aProvidedTreeMatchContext = nullptr);
+  void ContentRangeInserted(nsIContent* aContainer,
+                            nsIContent* aStartChild,
+                            nsIContent* aEndChild,
+                            nsILayoutHistoryState* aFrameState,
+                            bool aAllowLazyConstruction,
+                            TreeMatchContext* aProvidedTreeMatchContext = nullptr);
 
   enum RemoveFlags {
     REMOVE_CONTENT, REMOVE_FOR_RECONSTRUCTION, REMOVE_DESTROY_FRAMES };
   /**
    * Recreate or destroy frames for aChild in aContainer.
    * aFlags == REMOVE_CONTENT means aChild has been removed from the document.
    * aFlags == REMOVE_FOR_RECONSTRUCTION means the caller will reconstruct the
    *   frames later.
@@ -251,35 +251,35 @@ public:
    * it.  Ancestors may have been reframed though.
    * aFlags == REMOVE_DESTROY_FRAMES is the same as REMOVE_FOR_RECONSTRUCTION
    * except it will never try to reconstruct frames.  Instead, the caller is
    * responsible for doing that, on the content returned in aDestroyedFramesFor.
    * The layout frame state is guarranted to be captured for the removed frames
    * only when aFlags == REMOVE_DESTROY_FRAMES, otherwise it will only be
    * captured if we reconstructed frames for an ancestor.
    */
-  nsresult ContentRemoved(nsIContent*  aContainer,
-                          nsIContent*  aChild,
-                          nsIContent*  aOldNextSibling,
-                          RemoveFlags  aFlags,
-                          bool*        aDidReconstruct,
-                          nsIContent** aDestroyedFramesFor = nullptr);
+  void ContentRemoved(nsIContent*  aContainer,
+                      nsIContent*  aChild,
+                      nsIContent*  aOldNextSibling,
+                      RemoveFlags  aFlags,
+                      bool*        aDidReconstruct,
+                      nsIContent** aDestroyedFramesFor = nullptr);
 
-  nsresult CharacterDataChanged(nsIContent* aContent,
-                                CharacterDataChangeInfo* aInfo);
+  void CharacterDataChanged(nsIContent* aContent,
+                            CharacterDataChangeInfo* aInfo);
 
   // If aContent is a text node that has been optimized away due to being
   // whitespace next to a block boundary (or for some other reason), stop
   // doing that and create a frame for it if it should have one. This recreates
   // frames so be careful (although this should not change actual layout).
   // Returns the frame for aContent if there is one.
   nsIFrame* EnsureFrameForTextNode(nsGenericDOMDataNode* aContent);
 
-  // generate the child frames and process bindings
-  nsresult GenerateChildFrames(nsContainerFrame* aFrame);
+  // Generate the child frames and process bindings
+  void GenerateChildFrames(nsContainerFrame* aFrame);
 
   // Should be called when a frame is going to be destroyed and
   // WillDestroyFrameTree hasn't been called yet.
   void NotifyDestroyingFrame(nsIFrame* aFrame);
 
   void BeginUpdate();
   void EndUpdate();
   void RecalcQuotesAndCounters();
@@ -311,21 +311,21 @@ public:
   // Copy over fixed frames from aParentFrame's prev-in-flow
   nsresult ReplicateFixedFrames(nsPageContentFrame* aParentFrame);
 
   /**
    * Get the XBL insertion point for aChild in aContainer.
    */
   InsertionPoint GetInsertionPoint(nsIContent* aContainer, nsIContent* aChild);
 
-  nsresult CreateListBoxContent(nsContainerFrame* aParentFrame,
-                                nsIFrame*         aPrevFrame,
-                                nsIContent*       aChild,
-                                nsIFrame**        aResult,
-                                bool              aIsAppend);
+  void CreateListBoxContent(nsContainerFrame* aParentFrame,
+                            nsIFrame*         aPrevFrame,
+                            nsIContent*       aChild,
+                            nsIFrame**        aResult,
+                            bool              aIsAppend);
 
   // GetInitialContainingBlock() is deprecated in favor of GetRootElementFrame();
   // nsIFrame* GetInitialContainingBlock() { return mRootElementFrame; }
   // This returns the outermost frame for the root element
   nsContainerFrame* GetRootElementFrame() { return mRootElementFrame; }
   // This returns the frame for the root element that does not
   // have a psuedo-element style
   nsIFrame* GetRootElementStyleFrame() { return mRootElementStyleFrame; }
@@ -427,24 +427,24 @@ private:
    * @param aAttrNamespace the namespace of the attribute in question
    * @param aAttrName the localname of the attribute
    * @param aStyleContext the style context to use
    * @param aGeneratedContent the array of generated content to append the
    *                          created content to.
    * @param [out] aNewContent the content node we create
    * @param [out] aNewFrame the new frame we create
    */
-  nsresult CreateAttributeContent(nsIContent* aParentContent,
-                                  nsIFrame* aParentFrame,
-                                  int32_t aAttrNamespace,
-                                  nsIAtom* aAttrName,
-                                  nsStyleContext* aStyleContext,
-                                  nsCOMArray<nsIContent>& aGeneratedContent,
-                                  nsIContent** aNewContent,
-                                  nsIFrame** aNewFrame);
+  void CreateAttributeContent(nsIContent* aParentContent,
+                              nsIFrame* aParentFrame,
+                              int32_t aAttrNamespace,
+                              nsIAtom* aAttrName,
+                              nsStyleContext* aStyleContext,
+                              nsCOMArray<nsIContent>& aGeneratedContent,
+                              nsIContent** aNewContent,
+                              nsIFrame** aNewFrame);
 
   /**
    * Create a text node containing the given string. If aText is non-null
    * then we also set aText to the returned node.
    */
   already_AddRefed<nsIContent> CreateGenConTextNode(nsFrameConstructorState& aState,
                                                     const nsString& aString,
                                                     RefPtr<nsTextNode>* aText,
@@ -472,21 +472,21 @@ private:
                                   CSSPseudoElementType       aPseudoElement,
                                   FrameConstructionItemList& aItems);
 
   // This method can change aFrameList: it can chop off the beginning and put
   // it in aParentFrame while putting the remainder into a ib-split sibling of
   // aParentFrame.  aPrevSibling must be the frame after which aFrameList is to
   // be placed on aParentFrame's principal child list.  It may be null if
   // aFrameList is being added at the beginning of the child list.
-  nsresult AppendFramesToParent(nsFrameConstructorState&       aState,
-                                nsContainerFrame*              aParentFrame,
-                                nsFrameItems&                  aFrameList,
-                                nsIFrame*                      aPrevSibling,
-                                bool                           aIsRecursiveCall = false);
+  void AppendFramesToParent(nsFrameConstructorState&       aState,
+                            nsContainerFrame*              aParentFrame,
+                            nsFrameItems&                  aFrameList,
+                            nsIFrame*                      aPrevSibling,
+                            bool                           aIsRecursiveCall = false);
 
   // BEGIN TABLE SECTION
   /**
    * Construct a table wrapper frame. This is the FrameConstructionData
    * callback used for the job.
    */
   nsIFrame* ConstructTable(nsFrameConstructorState& aState,
                            FrameConstructionItem&   aItem,
@@ -1698,17 +1698,17 @@ private:
   // Creates a view for the scrolledframe and makes it the child of the scrollframe.
   void
   FinishBuildingScrollFrame(nsContainerFrame* aScrollFrame,
                             nsIFrame* aScrolledFrame);
 
   // InitializeSelectFrame puts scrollFrame in aFrameItems if aBuildCombobox is false
   // aBuildCombobox indicates if we are building a combobox that has a dropdown
   // popup widget or not.
-  nsresult
+  void
   InitializeSelectFrame(nsFrameConstructorState& aState,
                         nsContainerFrame*        aScrollFrame,
                         nsContainerFrame*        aScrolledFrame,
                         nsIContent*              aContent,
                         nsContainerFrame*        aParentFrame,
                         nsStyleContext*          aStyleContext,
                         bool                     aBuildCombobox,
                         PendingBinding*          aPendingBinding,
@@ -1727,17 +1727,17 @@ private:
    * Recreate frames for aContent.
    * @param aContent the content to recreate frames for
    * @param aAsyncInsert if true then a restyle event will be posted to handle
    *   the required ContentInserted call instead of doing it immediately.
    * @param aFlags normally you want to pass REMOVE_FOR_RECONSTRUCTION here
    * @param aDestroyedFramesFor if non-null, it will contain the content that
    *   was actually reframed - it may be different than aContent.
    */
-  nsresult
+  void
   RecreateFramesForContent(nsIContent*  aContent,
                            bool         aAsyncInsert,
                            RemoveFlags  aFlags,
                            nsIContent** aDestroyedFramesFor);
 
   // If removal of aFrame from the frame tree requires reconstruction of some
   // containing block (either of aFrame or of its parent) due to {ib} splits or
   // table pseudo-frames, recreate the relevant frame subtree.  The return value
@@ -1745,17 +1745,16 @@ private:
   // the return value of ReframeContainingBlock or RecreateFramesForContent.  If
   // this method returns false, the value of *aResult is not affected.  aFrame
   // and aResult must not be null.  aFrame must be the result of a
   // GetPrimaryFrame() call on a content node (which means its parent is also
   // not null).   If this method returns true, aDestroyedFramesFor contains the
   // content that was reframed.
   bool MaybeRecreateContainerForFrameRemoval(nsIFrame*    aFrame,
                                              RemoveFlags  aFlags,
-                                             nsresult*    aResult,
                                              nsIContent** aDestroyedFramesFor);
 
   nsIFrame* CreateContinuingOuterTableFrame(nsIPresShell*     aPresShell,
                                             nsPresContext*    aPresContext,
                                             nsIFrame*         aFrame,
                                             nsContainerFrame* aParentFrame,
                                             nsIContent*       aContent,
                                             nsStyleContext*   aStyleContext);
@@ -1870,19 +1869,19 @@ private:
   // otherwise
   bool WipeContainingBlock(nsFrameConstructorState& aState,
                              nsIFrame*                aContainingBlock,
                              nsIFrame*                aFrame,
                              FrameConstructionItemList& aItems,
                              bool                     aIsAppend,
                              nsIFrame*                aPrevSibling);
 
-  nsresult ReframeContainingBlock(nsIFrame*    aFrame,
-                                  RemoveFlags  aFlags,
-                                  nsIContent** aReframeContent);
+  void ReframeContainingBlock(nsIFrame*    aFrame,
+                              RemoveFlags  aFlags,
+                              nsIContent** aReframeContent);
 
   //----------------------------------------
 
   // Methods support :first-letter style
 
   void CreateFloatingLetterFrame(nsFrameConstructorState& aState,
                                  nsIContent*              aTextContent,
                                  nsIFrame*                aTextFrame,
@@ -1926,28 +1925,28 @@ private:
                                     nsIFrame**         aTextFrame,
                                     nsIFrame**         aPrevFrame,
                                     nsFrameItems&      aLetterFrames,
                                     bool*              aStopLooking);
 
   void RecoverLetterFrames(nsContainerFrame* aBlockFrame);
 
   //
-  nsresult RemoveLetterFrames(nsIPresShell*     aPresShell,
-                              nsContainerFrame* aBlockFrame);
+  void RemoveLetterFrames(nsIPresShell*     aPresShell,
+                          nsContainerFrame* aBlockFrame);
 
   // Recursive helper for RemoveLetterFrames
-  nsresult RemoveFirstLetterFrames(nsIPresShell*     aPresShell,
-                                   nsContainerFrame* aFrame,
-                                   nsContainerFrame* aBlockFrame,
-                                   bool*             aStopLooking);
+  void RemoveFirstLetterFrames(nsIPresShell*     aPresShell,
+                               nsContainerFrame* aFrame,
+                               nsContainerFrame* aBlockFrame,
+                               bool*             aStopLooking);
 
   // Special remove method for those pesky floating first-letter frames
-  nsresult RemoveFloatingFirstLetterFrames(nsIPresShell*    aPresShell,
-                                           nsIFrame*        aBlockFrame);
+  void RemoveFloatingFirstLetterFrames(nsIPresShell*    aPresShell,
+                                       nsIFrame*        aBlockFrame);
 
   // Capture state for the frame tree rooted at the frame associated with the
   // content object, aContent
   void CaptureStateForFramesOf(nsIContent* aContent,
                                nsILayoutHistoryState* aHistoryState);
 
   //----------------------------------------
 
@@ -1968,22 +1967,22 @@ private:
 
   // Handle the case when a block with first-line style is appended to (by
   // possibly calling WrapFramesInFirstLineFrame as needed).
   void AppendFirstLineFrames(nsFrameConstructorState& aState,
                              nsIContent*              aContent,
                              nsContainerFrame*        aBlockFrame,
                              nsFrameItems&            aFrameItems);
 
-  nsresult InsertFirstLineFrames(nsFrameConstructorState& aState,
-                                 nsIContent*              aContent,
-                                 nsIFrame*                aBlockFrame,
-                                 nsContainerFrame**       aParentFrame,
-                                 nsIFrame*                aPrevSibling,
-                                 nsFrameItems&            aFrameItems);
+  void InsertFirstLineFrames(nsFrameConstructorState& aState,
+                             nsIContent*              aContent,
+                             nsIFrame*                aBlockFrame,
+                             nsContainerFrame**       aParentFrame,
+                             nsIFrame*                aPrevSibling,
+                             nsFrameItems&            aFrameItems);
 
   /**
    * Find the right frame to use for aContent when looking for sibling
    * frames for aTargetContent.  If aPrevSibling is true, this
    * will look for last continuations, etc, as necessary.  This calls
    * IsValidSibling as needed; if that returns false it returns null.
    *
    * @param aContent the content to search for frames
--- a/layout/base/nsFrameManager.cpp
+++ b/layout/base/nsFrameManager.cpp
@@ -33,23 +33,18 @@
 #include "nsAbsoluteContainingBlock.h"
 #include "ChildIterator.h"
 
 #include "nsFrameManager.h"
 #include "GeckoProfiler.h"
 #include "nsIStatefulFrame.h"
 #include "nsContainerFrame.h"
 
-  #ifdef DEBUG
-    //#define DEBUG_UNDISPLAYED_MAP
-    //#define DEBUG_DISPLAY_CONTENTS_MAP
-  #else
-    #undef DEBUG_UNDISPLAYED_MAP
-    #undef DEBUG_DISPLAY_CONTENTS_MAP
-  #endif
+// #define DEBUG_UNDISPLAYED_MAP
+// #define DEBUG_DISPLAY_CONTENTS_MAP
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 //----------------------------------------------------------------------
 
 struct PlaceholderMapEntry : public PLDHashEntryHdr {
   // key (the out of flow frame) can be obtained through placeholder frame
@@ -82,50 +77,59 @@ nsFrameManagerBase::nsFrameManagerBase()
   , mUndisplayedMap(nullptr)
   , mDisplayContentsMap(nullptr)
   , mIsDestroyingFrames(false)
 {
 }
 
 //----------------------------------------------------------------------
 
-// XXXldb This seems too complicated for what I think it's doing, and it
-// should also be using PLDHashTable rather than plhash to use less memory.
+/**
+ * The undisplayed map is a class that maps a parent content node to the
+ * undisplayed content children, and their style contexts.
+ *
+ * The linked list of nodes holds strong references to the style contexts and
+ * the content.
+ */
+class nsFrameManagerBase::UndisplayedMap :
+  private nsClassHashtable<nsPtrHashKey<nsIContent>,
+                           LinkedList<UndisplayedNode>>
+{
+  typedef nsClassHashtable<nsPtrHashKey<nsIContent>, LinkedList<UndisplayedNode>> base_type;
 
-class nsFrameManagerBase::UndisplayedMap {
 public:
-  explicit UndisplayedMap(uint32_t aNumBuckets = 16);
-  ~UndisplayedMap(void);
+  UndisplayedMap();
+  ~UndisplayedMap();
 
   UndisplayedNode* GetFirstNode(nsIContent* aParentContent);
 
-  nsresult AddNodeFor(nsIContent* aParentContent,
-                                  nsIContent* aChild, nsStyleContext* aStyle);
+  void AddNodeFor(nsIContent* aParentContent,
+                  nsIContent* aChild,
+                  nsStyleContext* aStyle);
 
-  void RemoveNodeFor(nsIContent* aParentContent,
-                                 UndisplayedNode* aNode);
+  void RemoveNodeFor(nsIContent* aParentContent, UndisplayedNode* aNode);
 
   void RemoveNodesFor(nsIContent* aParentContent);
-  UndisplayedNode* UnlinkNodesFor(nsIContent* aParentContent);
+
+  nsAutoPtr<LinkedList<UndisplayedNode>>
+    UnlinkNodesFor(nsIContent* aParentContent);
 
   // Removes all entries from the hash table
-  void  Clear(void);
+  void  Clear();
 
 protected:
+  LinkedList<UndisplayedNode>* GetListFor(nsIContent** aParentContent);
+  LinkedList<UndisplayedNode>* GetOrCreateListFor(nsIContent** aParentContent);
+  void AppendNodeFor(UndisplayedNode* aNode, nsIContent* aParentContent);
   /**
-   * Gets the entry for the provided parent content. If the content
-   * is a <xbl:children> element, |**aParentContent| is set to
-   * the parent of the children element.
+   * Get the applicable parent for the map lookup. This is almost always the
+   * provided argument, except if it's a <xbl:children> element, in which case
+   * it's the parent of the children element.
    */
-  PLHashEntry** GetEntryFor(nsIContent** aParentContent);
-  void          AppendNodeFor(UndisplayedNode* aNode,
-                                          nsIContent* aParentContent);
-
-  PLHashTable*  mTable;
-  PLHashEntry** mLastLookup;
+  nsIContent* GetApplicableParent(nsIContent* aParent);
 };
 
 //----------------------------------------------------------------------
 
 nsFrameManager::~nsFrameManager()
 {
   NS_ASSERTION(!mPresShell, "nsFrameManager::Destroy never called");
 }
@@ -140,17 +144,17 @@ nsFrameManager::Destroy()
 
   // Unregister all placeholders before tearing down the frame tree
   nsFrameManager::ClearPlaceholderFrameMap();
 
   if (mRootFrame) {
     mRootFrame->Destroy();
     mRootFrame = nullptr;
   }
-  
+
   delete mUndisplayedMap;
   mUndisplayedMap = nullptr;
   delete mDisplayContentsMap;
   mDisplayContentsMap = nullptr;
 
   mPresShell = nullptr;
 }
 
@@ -229,17 +233,17 @@ nsFrameManager::GetStyleContextInMap(Und
 nsFrameManager::GetUndisplayedNodeInMapFor(UndisplayedMap* aMap,
                                            const nsIContent* aContent)
 {
   if (!aContent) {
     return nullptr;
   }
   nsIContent* parent = ParentForUndisplayedMap(aContent);
   for (UndisplayedNode* node = aMap->GetFirstNode(parent);
-         node; node = node->mNext) {
+       node; node = node->getNext()) {
     if (node->mContent == aContent)
       return node;
   }
 
   return nullptr;
 }
 
 
@@ -256,26 +260,26 @@ nsFrameManager::GetAllUndisplayedContent
   return GetAllUndisplayedNodesInMapFor(mUndisplayedMap, aParentContent);
 }
 
 /* static */ void
 nsFrameManager::SetStyleContextInMap(UndisplayedMap* aMap,
                                      nsIContent* aContent,
                                      nsStyleContext* aStyleContext)
 {
-  NS_PRECONDITION(!aStyleContext->GetPseudo(),
-                  "Should only have actual elements here");
+  MOZ_ASSERT(!aStyleContext->GetPseudo(),
+             "Should only have actual elements here");
 
 #if defined(DEBUG_UNDISPLAYED_MAP) || defined(DEBUG_DISPLAY_BOX_CONTENTS_MAP)
   static int i = 0;
   printf("SetStyleContextInMap(%d): p=%p \n", i++, (void *)aContent);
 #endif
 
-  NS_ASSERTION(!GetStyleContextInMap(aMap, aContent),
-               "Already have an entry for aContent");
+  MOZ_ASSERT(!GetStyleContextInMap(aMap, aContent),
+             "Already have an entry for aContent");
 
   nsIContent* parent = ParentForUndisplayedMap(aContent);
 #ifdef DEBUG
   nsIPresShell* shell = aStyleContext->PresContext()->PresShell();
   NS_ASSERTION(parent || (shell && shell->GetDocument() &&
                           shell->GetDocument()->GetRootElement() == aContent),
                "undisplayed content must have a parent, unless it's the root "
                "element");
@@ -301,17 +305,17 @@ nsFrameManager::ChangeStyleContextInMap(
   MOZ_ASSERT(aMap, "expecting a map");
 
 #if defined(DEBUG_UNDISPLAYED_MAP) || defined(DEBUG_DISPLAY_BOX_CONTENTS_MAP)
    static int i = 0;
    printf("ChangeStyleContextInMap(%d): p=%p \n", i++, (void *)aContent);
 #endif
 
   for (UndisplayedNode* node = aMap->GetFirstNode(aContent->GetParent());
-         node; node = node->mNext) {
+       node; node = node->getNext()) {
     if (node->mContent == aContent) {
       node->mStyle = aStyleContext;
       return;
     }
   }
 
   MOZ_CRASH("couldn't find the entry to change");
 }
@@ -319,36 +323,36 @@ nsFrameManager::ChangeStyleContextInMap(
 void
 nsFrameManager::ClearUndisplayedContentIn(nsIContent* aContent,
                                           nsIContent* aParentContent)
 {
 #ifdef DEBUG_UNDISPLAYED_MAP
   static int i = 0;
   printf("ClearUndisplayedContent(%d): content=%p parent=%p --> ", i++, (void *)aContent, (void*)aParentContent);
 #endif
-  
-  if (mUndisplayedMap) {
-    UndisplayedNode* node = mUndisplayedMap->GetFirstNode(aParentContent);
-    while (node) {
-      if (node->mContent == aContent) {
-        mUndisplayedMap->RemoveNodeFor(aParentContent, node);
+
+  if (!mUndisplayedMap) {
+    return;
+  }
+
+  for (UndisplayedNode* node = mUndisplayedMap->GetFirstNode(aParentContent);
+       node; node = node->getNext()) {
+    if (node->mContent == aContent) {
+      mUndisplayedMap->RemoveNodeFor(aParentContent, node);
 
 #ifdef DEBUG_UNDISPLAYED_MAP
-        printf( "REMOVED!\n");
+      printf( "REMOVED!\n");
 #endif
-#ifdef DEBUG
-        // make sure that there are no more entries for the same content
-        nsStyleContext *context = GetUndisplayedContent(aContent);
-        NS_ASSERTION(context == nullptr, "Found more undisplayed content data after removal");
-#endif
-        return;
-      }
-      node = node->mNext;
+      // make sure that there are no more entries for the same content
+      MOZ_ASSERT(!GetUndisplayedContent(aContent),
+                 "Found more undisplayed content data after removal");
+      return;
     }
   }
+
 #ifdef DEBUG_UNDISPLAYED_MAP
   printf( "not found.\n");
 #endif
 }
 
 void
 nsFrameManager::ClearAllUndisplayedContentIn(nsIContent* aParentContent)
 {
@@ -394,60 +398,59 @@ nsFrameManager::GetAllDisplayContentsIn(
 void
 nsFrameManager::ClearDisplayContentsIn(nsIContent* aContent,
                                        nsIContent* aParentContent)
 {
 #ifdef DEBUG_DISPLAY_CONTENTS_MAP
   static int i = 0;
   printf("ClearDisplayContents(%d): content=%p parent=%p --> ", i++, (void *)aContent, (void*)aParentContent);
 #endif
-  
-  if (mDisplayContentsMap) {
-    UndisplayedNode* node = mDisplayContentsMap->GetFirstNode(aParentContent);
-    while (node) {
-      if (node->mContent == aContent) {
-        mDisplayContentsMap->RemoveNodeFor(aParentContent, node);
+
+  if (!mDisplayContentsMap) {
+    return;
+  }
+
+  for (UndisplayedNode* node = mDisplayContentsMap->GetFirstNode(aParentContent);
+       node; node = node->getNext()) {
+    if (node->mContent == aContent) {
+      mDisplayContentsMap->RemoveNodeFor(aParentContent, node);
 
 #ifdef DEBUG_DISPLAY_CONTENTS_MAP
-        printf( "REMOVED!\n");
+      printf( "REMOVED!\n");
 #endif
-#ifdef DEBUG
-        // make sure that there are no more entries for the same content
-        nsStyleContext* context = GetDisplayContentsStyleFor(aContent);
-        NS_ASSERTION(context == nullptr, "Found more entries for aContent after removal");
-#endif
-        ClearAllDisplayContentsIn(aContent);
-        ClearAllUndisplayedContentIn(aContent);
-        return;
-      }
-      node = node->mNext;
+      // make sure that there are no more entries for the same content
+      MOZ_ASSERT(!GetDisplayContentsStyleFor(aContent),
+                 "Found more entries for aContent after removal");
+      ClearAllDisplayContentsIn(aContent);
+      ClearAllUndisplayedContentIn(aContent);
+      return;
     }
   }
 #ifdef DEBUG_DISPLAY_CONTENTS_MAP
   printf( "not found.\n");
 #endif
 }
 
 void
 nsFrameManager::ClearAllDisplayContentsIn(nsIContent* aParentContent)
 {
 #ifdef DEBUG_DISPLAY_CONTENTS_MAP
   static int i = 0;
   printf("ClearAllDisplayContentsIn(%d): parent=%p \n", i++, (void*)aParentContent);
 #endif
 
   if (mDisplayContentsMap) {
-    UndisplayedNode* cur = mDisplayContentsMap->UnlinkNodesFor(aParentContent);
-    while (cur) {
-      UndisplayedNode* next = cur->mNext;
-      cur->mNext = nullptr;
-      ClearAllDisplayContentsIn(cur->mContent);
-      ClearAllUndisplayedContentIn(cur->mContent);
-      delete cur;
-      cur = next;
+    nsAutoPtr<LinkedList<UndisplayedNode>> list =
+      mDisplayContentsMap->UnlinkNodesFor(aParentContent);
+    if (list) {
+      while (UndisplayedNode* node = list->popFirst()) {
+        ClearAllDisplayContentsIn(node->mContent);
+        ClearAllUndisplayedContentIn(node->mContent);
+        delete node;
+      }
     }
   }
 
   // Need to look at aParentContent's content list due to XBL insertions.
   // Nodes in aParentContent's content list do not have aParentContent as a
   // parent, but are treated as children of aParentContent. We iterate over
   // the flattened content list and just ignore any nodes we don't care about.
   FlattenedChildIterator iter(aParentContent);
@@ -668,185 +671,137 @@ nsFrameManager::RestoreFrameState(nsIFra
     for (; !childFrames.AtEnd(); childFrames.Next()) {
       RestoreFrameState(childFrames.get(), aState);
     }
   }
 }
 
 //----------------------------------------------------------------------
 
-static PLHashNumber
-HashKey(void* key)
-{
-  return NS_PTR_TO_INT32(key);
-}
-
-static int
-CompareKeys(void* key1, void* key2)
-{
-  return key1 == key2;
-}
-
-//----------------------------------------------------------------------
-
-nsFrameManagerBase::UndisplayedMap::UndisplayedMap(uint32_t aNumBuckets)
+nsFrameManagerBase::UndisplayedMap::UndisplayedMap()
 {
   MOZ_COUNT_CTOR(nsFrameManagerBase::UndisplayedMap);
-  mTable = PL_NewHashTable(aNumBuckets, (PLHashFunction)HashKey,
-                           (PLHashComparator)CompareKeys,
-                           (PLHashComparator)nullptr,
-                           nullptr, nullptr);
-  mLastLookup = nullptr;
 }
 
 nsFrameManagerBase::UndisplayedMap::~UndisplayedMap(void)
 {
   MOZ_COUNT_DTOR(nsFrameManagerBase::UndisplayedMap);
   Clear();
-  PL_HashTableDestroy(mTable);
 }
 
-PLHashEntry**  
-nsFrameManagerBase::UndisplayedMap::GetEntryFor(nsIContent** aParentContent)
+void
+nsFrameManagerBase::UndisplayedMap::Clear()
 {
-  nsIContent* parentContent = *aParentContent;
+  for (auto iter = Iter(); !iter.Done(); iter.Next()) {
+    auto* list = iter.UserData();
+    while (auto* node = list->popFirst()) {
+      delete node;
+    }
+    iter.Remove();
+  }
+}
 
-  if (mLastLookup && (parentContent == (*mLastLookup)->key)) {
-    return mLastLookup;
-  }
 
+nsIContent*
+nsFrameManagerBase::UndisplayedMap::GetApplicableParent(nsIContent* aParent)
+{
   // In the case of XBL default content, <xbl:children> elements do not get a
   // frame causing a mismatch between the content tree and the frame tree.
   // |GetEntryFor| is sometimes called with the content tree parent (which may
   // be a <xbl:children> element) but the parent in the frame tree would be the
   // insertion parent (parent of the <xbl:children> element). Here the children
   // elements are normalized to the insertion parent to correct for the mismatch.
-  if (parentContent && nsContentUtils::IsContentInsertionPoint(parentContent)) {
-    parentContent = parentContent->GetParent();
-    // Change the caller's pointer for the parent content to be the insertion parent.
-    *aParentContent = parentContent;
+  if (aParent && nsContentUtils::IsContentInsertionPoint(aParent)) {
+    return aParent->GetParent();
+  }
+
+  return aParent;
+}
+
+LinkedList<UndisplayedNode>*
+nsFrameManagerBase::UndisplayedMap::GetListFor(nsIContent** aParent)
+{
+  *aParent = GetApplicableParent(*aParent);
+
+  LinkedList<UndisplayedNode>* list;
+  if (Get(*aParent, &list)) {
+    return list;
   }
 
-  PLHashNumber hashCode = NS_PTR_TO_INT32(parentContent);
-  PLHashEntry** entry = PL_HashTableRawLookup(mTable, hashCode, parentContent);
-  if (*entry && !ServoStyleSet::IsInServoTraversal()) {
-    mLastLookup = entry;
-  }
-  return entry;
+  return nullptr;
 }
 
-UndisplayedNode* 
+LinkedList<UndisplayedNode>*
+nsFrameManagerBase::UndisplayedMap::GetOrCreateListFor(nsIContent** aParent)
+{
+  *aParent = GetApplicableParent(*aParent);
+  return LookupOrAdd(*aParent);
+}
+
+
+UndisplayedNode*
 nsFrameManagerBase::UndisplayedMap::GetFirstNode(nsIContent* aParentContent)
 {
-  PLHashEntry** entry = GetEntryFor(&aParentContent);
-  if (*entry) {
-    return (UndisplayedNode*)((*entry)->value);
-  }
-  return nullptr;
+  auto* list = GetListFor(&aParentContent);
+  return list ? list->getFirst() : nullptr;
 }
 
+
 void
 nsFrameManagerBase::UndisplayedMap::AppendNodeFor(UndisplayedNode* aNode,
                                                   nsIContent* aParentContent)
 {
-  PLHashEntry** entry = GetEntryFor(&aParentContent);
-  if (*entry) {
-    UndisplayedNode*  node = (UndisplayedNode*)((*entry)->value);
-    while (node->mNext) {
-      if (node->mContent == aNode->mContent) {
-        // We actually need to check this in optimized builds because
-        // there are some callers that do this.  See bug 118014, bug
-        // 136704, etc.
-        NS_NOTREACHED("node in map twice");
-        delete aNode;
-        return;
-      }
-      node = node->mNext;
-    }
-    node->mNext = aNode;
+  LinkedList<UndisplayedNode>* list = GetOrCreateListFor(&aParentContent);
+
+#ifdef DEBUG
+  for (UndisplayedNode* node = list->getFirst(); node; node = node->getNext()) {
+    // NOTE: In the original code there was a work around for this case, I want
+    // to check it still happens before hacking around it the same way.
+    MOZ_ASSERT(node->mContent != aNode->mContent,
+               "Duplicated content in undisplayed list!");
   }
-  else {
-    PLHashNumber hashCode = NS_PTR_TO_INT32(aParentContent);
-    PL_HashTableRawAdd(mTable, entry, hashCode, aParentContent, aNode);
-    mLastLookup = nullptr; // hashtable may have shifted bucket out from under us
-  }
+#endif
+
+  list->insertBack(aNode);
 }
 
-nsresult 
+void
 nsFrameManagerBase::UndisplayedMap::AddNodeFor(nsIContent* aParentContent,
-                                               nsIContent* aChild, 
+                                               nsIContent* aChild,
                                                nsStyleContext* aStyle)
 {
   UndisplayedNode*  node = new UndisplayedNode(aChild, aStyle);
-
   AppendNodeFor(node, aParentContent);
-  return NS_OK;
 }
 
 void
 nsFrameManagerBase::UndisplayedMap::RemoveNodeFor(nsIContent* aParentContent,
                                                   UndisplayedNode* aNode)
 {
-  PLHashEntry** entry = GetEntryFor(&aParentContent);
-  NS_ASSERTION(*entry, "content not in map");
-  if (*entry) {
-    if ((UndisplayedNode*)((*entry)->value) == aNode) {  // first node
-      if (aNode->mNext) {
-        (*entry)->value = aNode->mNext;
-        aNode->mNext = nullptr;
-      }
-      else {
-        PL_HashTableRawRemove(mTable, entry, *entry);
-        mLastLookup = nullptr; // hashtable may have shifted bucket out from under us
-      }
-    }
-    else {
-      UndisplayedNode*  node = (UndisplayedNode*)((*entry)->value);
-      while (node->mNext) {
-        if (node->mNext == aNode) {
-          node->mNext = aNode->mNext;
-          aNode->mNext = nullptr;
-          break;
-        }
-        node = node->mNext;
-      }
-    }
-  }
+#ifdef DEBUG
+  auto list = GetListFor(&aParentContent);
+  MOZ_ASSERT(list, "content not in map");
+  aNode->removeFrom(*list);
+#else
+  aNode->remove();
+#endif
   delete aNode;
 }
 
 
-UndisplayedNode*
+nsAutoPtr<LinkedList<UndisplayedNode>>
 nsFrameManagerBase::UndisplayedMap::UnlinkNodesFor(nsIContent* aParentContent)
 {
-  PLHashEntry** entry = GetEntryFor(&aParentContent);
-  NS_ASSERTION(entry, "content not in map");
-  if (*entry) {
-    UndisplayedNode* node = (UndisplayedNode*)((*entry)->value);
-    NS_ASSERTION(node, "null node for non-null entry in UndisplayedMap");
-    PL_HashTableRawRemove(mTable, entry, *entry);
-    mLastLookup = nullptr; // hashtable may have shifted bucket out from under us
-    return node;
-  }
-  return nullptr;
+  nsAutoPtr<LinkedList<UndisplayedNode>> list;
+  RemoveAndForget(GetApplicableParent(aParentContent), list);
+  return list;
 }
 
 void
 nsFrameManagerBase::UndisplayedMap::RemoveNodesFor(nsIContent* aParentContent)
 {
-  delete UnlinkNodesFor(aParentContent);
+  nsAutoPtr<LinkedList<UndisplayedNode>> list = UnlinkNodesFor(aParentContent);
+  if (list) {
+    while (auto* node = list->popFirst()) {
+      delete node;
+    }
+  }
 }
-
-static int
-RemoveUndisplayedEntry(PLHashEntry* he, int i, void* arg)
-{
-  UndisplayedNode*  node = (UndisplayedNode*)(he->value);
-  delete node;
-  // Remove and free this entry and continue enumerating
-  return HT_ENUMERATE_REMOVE | HT_ENUMERATE_NEXT;
-}
-
-void
-nsFrameManagerBase::UndisplayedMap::Clear(void)
-{
-  mLastLookup = nullptr;
-  PL_HashTableEnumerateEntries(mTable, RemoveUndisplayedEntry, 0);
-}
--- a/layout/base/nsFrameManager.h
+++ b/layout/base/nsFrameManager.h
@@ -28,59 +28,45 @@
 class nsContainerFrame;
 class nsPlaceholderFrame;
 
 namespace mozilla {
 /**
  * Node in a linked list, containing the style for an element that
  * does not have a frame but whose parent does have a frame.
  */
-struct UndisplayedNode {
+struct UndisplayedNode : public LinkedListElement<UndisplayedNode>
+{
   UndisplayedNode(nsIContent* aContent, nsStyleContext* aStyle)
-    : mContent(aContent),
-      mStyle(aStyle),
-      mNext(nullptr)
+    : mContent(aContent)
+    , mStyle(aStyle)
   {
     MOZ_COUNT_CTOR(mozilla::UndisplayedNode);
   }
 
-  ~UndisplayedNode()
-  {
-    MOZ_COUNT_DTOR(mozilla::UndisplayedNode);
+  ~UndisplayedNode() { MOZ_COUNT_DTOR(mozilla::UndisplayedNode); }
 
-    // Delete mNext iteratively to avoid blowing up the stack (bug 460461).
-    UndisplayedNode* cur = mNext;
-    while (cur) {