Merge mozilla-central to autoland. a=merge on a CLOSED TREE
authorDaniel Varga <dvarga@mozilla.com>
Thu, 29 Nov 2018 11:59:02 +0200
changeset 507916 4244b98363097285c9c6f846dfe866f664ac3546
parent 507915 3bc46ad43fde17cba9203e6336718f979dddfba6 (current diff)
parent 507895 d99bf39f5223abe554a629a947eee344c3e9e29e (diff)
child 507917 42cc52da72dccf71d107d2d675c69649dcac204d
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland. a=merge on a CLOSED TREE
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1527,16 +1527,19 @@ pref("network.cookie.cookieBehavior", 4 
 
 pref("browser.contentblocking.allowlist.storage.enabled", true);
 
 #ifdef NIGHTLY_BUILD
 // Enable the Storage Access API in Nightly
 pref("dom.storage_access.enabled", true);
 #endif
 
+pref("dom.storage_access.auto_grants", true);
+pref("dom.storage_access.max_concurrent_auto_grants", 5);
+
 // Define a set of default features for the Content Blocking UI.
 pref("browser.contentblocking.trackingprotection.control-center.ui.enabled", true);
 pref("browser.contentblocking.rejecttrackers.control-center.ui.enabled", true);
 
 // Enable the Report Breakage UI on Nightly and Beta but not on Release yet.
 #ifdef EARLY_BETA_OR_EARLIER
 pref("browser.contentblocking.reportBreakage.enabled", true);
 #else
@@ -1790,8 +1793,10 @@ pref("toolkit.coverage.endpoint.base", "
 #if defined(NIGHTLY_BUILD) && defined(MOZ_LIBPRIO)
 pref("prio.enabled", true);
 #endif
 
 // Discovery prefs
 pref("browser.discovery.enabled", false);
 pref("browser.discovery.containers.enabled", true);
 pref("browser.discovery.sites", "addons.mozilla.org");
+
+pref("browser.engagement.recent_visited_origins.expiry", 86400); // 24 * 60 * 60 (24 hours in seconds)
--- a/browser/modules/BrowserUsageTelemetry.jsm
+++ b/browser/modules/BrowserUsageTelemetry.jsm
@@ -2,29 +2,36 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var EXPORTED_SYMBOLS = [
   "BrowserUsageTelemetry",
+  "URICountListener",
   "URLBAR_SELECTED_RESULT_TYPES",
   "URLBAR_SELECTED_RESULT_METHODS",
   "MINIMUM_TAB_COUNT_INTERVAL_MS",
  ];
 
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", null);
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   SearchTelemetry: "resource:///modules/SearchTelemetry.jsm",
   Services: "resource://gre/modules/Services.jsm",
+  setTimeout: "resource://gre/modules/Timer.jsm",
 });
 
+// This pref is in seconds!
+XPCOMUtils.defineLazyPreferenceGetter(this,
+  "gRecentVisitedOriginsExpiry",
+  "browser.engagement.recent_visited_origins.expiry");
+
 // The upper bound for the count of the visited unique domain names.
 const MAX_UNIQUE_VISITED_DOMAINS = 100;
 
 // Observed topic names.
 const TAB_RESTORING_TOPIC = "SSTabRestoring";
 const TELEMETRY_SUBSESSIONSPLIT_TOPIC = "internal-telemetry-after-subsession-split";
 const DOMWINDOW_OPENED_TOPIC = "domwindowopened";
 const AUTOCOMPLETE_ENTER_TEXT_TOPIC = "autocomplete-did-enter-text";
@@ -123,16 +130,18 @@ function getSearchEngineId(engine) {
 function shouldRecordSearchCount(tabbrowser) {
   return !PrivateBrowsingUtils.isWindowPrivate(tabbrowser.ownerGlobal) ||
          !Services.prefs.getBoolPref("browser.engagement.search_counts.pbm", false);
 }
 
 let URICountListener = {
   // A set containing the visited domains, see bug 1271310.
   _domainSet: new Set(),
+  // A set containing the visited origins during the last 24 hours (similar to domains, but not quite the same)
+  _origin24hrSet: new Set(),
   // A map to keep track of the URIs loaded from the restored tabs.
   _restoredURIsMap: new WeakMap(),
 
   isHttpURI(uri) {
     // Only consider http(s) schemas.
     return uri.schemeIs("http") || uri.schemeIs("https");
   },
 
@@ -225,35 +234,63 @@ let URICountListener = {
 
     // We only want to count the unique domains up to MAX_UNIQUE_VISITED_DOMAINS.
     if (this._domainSet.size == MAX_UNIQUE_VISITED_DOMAINS) {
       return;
     }
 
     // Unique domains should be aggregated by (eTLD + 1): x.test.com and y.test.com
     // are counted once as test.com.
+    let baseDomain;
     try {
       // Even if only considering http(s) URIs, |getBaseDomain| could still throw
       // due to the URI containing invalid characters or the domain actually being
       // an ipv4 or ipv6 address.
-      this._domainSet.add(Services.eTLD.getBaseDomain(uri));
+      baseDomain = Services.eTLD.getBaseDomain(uri);
+      this._domainSet.add(baseDomain);
     } catch (e) {
-      return;
+      baseDomain = uri.host;
+    }
+
+    // Record the origin, but with the base domain (eTLD + 1).
+    let baseDomainURI = uri.mutate()
+                           .setHost(baseDomain)
+                           .finalize();
+    this._origin24hrSet.add(baseDomainURI.prePath);
+    if (gRecentVisitedOriginsExpiry) {
+      setTimeout(() => {
+        this._origin24hrSet.delete(baseDomainURI.prePath);
+      }, gRecentVisitedOriginsExpiry * 1000);
     }
 
     Services.telemetry.scalarSet(UNIQUE_DOMAINS_COUNT_SCALAR_NAME, this._domainSet.size);
   },
 
   /**
    * Reset the counts. This should be called when breaking a session in Telemetry.
    */
   reset() {
     this._domainSet.clear();
   },
 
+  /**
+   * Returns the number of unique origins visited in this session during the
+   * last 24 hours.
+   */
+  get uniqueOriginsVisitedInPast24Hours() {
+    return this._origin24hrSet.size;
+  },
+
+  /**
+   * Resets the number of unique origins visited in this session.
+   */
+  resetUniqueOriginsVisitedInPast24Hours() {
+    this._origin24hrSet.clear();
+  },
+
   QueryInterface: ChromeUtils.generateQI([Ci.nsIWebProgressListener,
                                           Ci.nsISupportsWeakReference]),
 };
 
 let urlbarListener = {
 
   // This is needed for recordUrlbarSelectedResultMethod().
   selectedIndex: -1,
--- a/browser/modules/PermissionUI.jsm
+++ b/browser/modules/PermissionUI.jsm
@@ -63,16 +63,18 @@ var EXPORTED_SYMBOLS = [
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "Services",
   "resource://gre/modules/Services.jsm");
 ChromeUtils.defineModuleGetter(this, "SitePermissions",
   "resource:///modules/SitePermissions.jsm");
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
+ChromeUtils.defineModuleGetter(this, "URICountListener",
+  "resource:///modules/BrowserUsageTelemetry.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
   return Services.strings
                  .createBundle("chrome://browser/locale/browser.properties");
 });
 
 var PermissionUI = {};
 
@@ -248,18 +250,22 @@ var PermissionPromptPrototype = {
     return [];
   },
 
   /**
    * If the prompt will be shown to the user, this callback will
    * be called just before. Subclasses may want to override this
    * in order to, for example, bump a counter Telemetry probe for
    * how often a particular permission request is seen.
+   *
+   * If this returns false, it cancels the process of showing the prompt.  In
+   * that case, it is the responsibility of the onBeforeShow() implementation
+   * to ensure that allow() or cancel() are called on the object appropriately.
    */
-  onBeforeShow() {},
+  onBeforeShow() { return true; },
 
   /**
    * If the prompt was shown to the user, this callback will be called just
    * after it's been shown.
    */
   onShown() {},
 
   /**
@@ -435,24 +441,25 @@ var PermissionPromptPrototype = {
       }
       // The prompt has been removed, notify the PermissionUI.
       if (topic == "removed") {
         this.onAfterShow();
       }
       return false;
     };
 
-    this.onBeforeShow();
-    chromeWin.PopupNotifications.show(this.browser,
-                                      this.notificationID,
-                                      this.message,
-                                      this.anchorID,
-                                      mainAction,
-                                      secondaryActions,
-                                      options);
+    if (this.onBeforeShow() !== false) {
+      chromeWin.PopupNotifications.show(this.browser,
+                                        this.notificationID,
+                                        this.message,
+                                        this.anchorID,
+                                        mainAction,
+                                        secondaryActions,
+                                        options);
+    }
   },
 };
 
 PermissionUI.PermissionPromptPrototype = PermissionPromptPrototype;
 
 /**
  * A subclass of PermissionPromptPrototype that assumes
  * that this.request is an nsIContentPermissionRequest
@@ -585,16 +592,17 @@ GeolocationPermissionPrompt.prototype = 
       },
     }];
   },
 
   onBeforeShow() {
     let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
     const SHOW_REQUEST = Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST;
     secHistogram.add(SHOW_REQUEST);
+    return true;
   },
 };
 
 PermissionUI.GeolocationPermissionPrompt = GeolocationPermissionPrompt;
 
 /**
  * Creates a PermissionPrompt for a nsIContentPermissionRequest for
  * the Desktop Notification API.
@@ -821,19 +829,16 @@ MIDIPermissionPrompt.prototype = {
         action: Ci.nsIPermissionManager.ALLOW_ACTION,
       },
       {
         label: gBrowserBundle.GetStringFromName("midi.DontAllow.label"),
         accessKey: gBrowserBundle.GetStringFromName("midi.DontAllow.accesskey"),
         action: Ci.nsIPermissionManager.DENY_ACTION,
     }];
   },
-
-  onBeforeShow() {
-  },
 };
 
 PermissionUI.MIDIPermissionPrompt = MIDIPermissionPrompt;
 
 function AutoplayPermissionPrompt(request) {
   this.request = request;
 }
 
@@ -906,23 +911,29 @@ AutoplayPermissionPrompt.prototype = {
       let notification = chromeWin.PopupNotifications.getNotification(
         this.notificationID, this.browser);
       if (notification) {
         chromeWin.PopupNotifications.remove(notification);
       }
     };
     this.browser.addEventListener(
       "DOMAudioPlaybackStarted", this.handlePlaybackStart);
+    return true;
   },
 };
 
 PermissionUI.AutoplayPermissionPrompt = AutoplayPermissionPrompt;
 
 function StorageAccessPermissionPrompt(request) {
   this.request = request;
+
+  XPCOMUtils.defineLazyPreferenceGetter(this, "_autoGrants",
+                                        "dom.storage_access.auto_grants");
+  XPCOMUtils.defineLazyPreferenceGetter(this, "_maxConcurrentAutoGrants",
+                                        "dom.storage_access.max_concurrent_auto_grants");
 }
 
 StorageAccessPermissionPrompt.prototype = {
   __proto__: PermissionPromptForRequestPrototype,
 
   get usePermissionManager() {
     return false;
   },
@@ -1003,11 +1014,43 @@ StorageAccessPermissionPrompt.prototype 
           self.allow({"storage-access": "allow-on-any-site"});
         },
     }];
   },
 
   get topLevelPrincipal() {
     return this.request.topLevelPrincipal;
   },
+
+  get maxConcurrentAutomaticGrants() {
+    // one percent of the number of top-levels origins visited in the current
+    // session (but not to exceed 24 hours), or the value of the
+    // dom.storage_access.max_concurrent_auto_grants preference, whichever is
+    // higher.
+    return Math.max(Math.max(Math.floor(URICountListener.uniqueOriginsVisitedInPast24Hours / 100),
+                             this._maxConcurrentAutoGrants), 0);
+  },
+
+  getOriginsThirdPartyHasAccessTo(thirdPartyOrigin) {
+    let prefix = `3rdPartyStorage^${thirdPartyOrigin}`;
+    let perms = Services.perms.getAllWithTypePrefix(prefix);
+    let origins = new Set();
+    while (perms.length) {
+      let perm = perms.shift();
+      origins.add(perm.principal.origin);
+    }
+    return origins.size;
+  },
+
+  onBeforeShow() {
+    let thirdPartyOrigin = this.request.principal.origin;
+    if (this._autoGrants &&
+        this.getOriginsThirdPartyHasAccessTo(thirdPartyOrigin) <
+          this.maxConcurrentAutomaticGrants) {
+      // Automatically accept the prompt
+      this.allow({"storage-access": "allow-auto-grant"});
+      return false;
+    }
+    return true;
+  },
 };
 
 PermissionUI.StorageAccessPermissionPrompt = StorageAccessPermissionPrompt;
--- a/browser/modules/test/browser/browser.ini
+++ b/browser/modules/test/browser/browser.ini
@@ -34,16 +34,17 @@ skip-if = !e10s
 skip-if = os != win || (os == win && bits == 64) # bug 1456807
 [browser_UnsubmittedCrashHandler.js]
 run-if = crashreporter
 [browser_urlBar_zoom.js]
 [browser_UsageTelemetry.js]
 [browser_UsageTelemetry_domains.js]
 [browser_UsageTelemetry_private_and_restore.js]
 skip-if = verify && debug
+[browser_UsageTelemetry_uniqueOriginsVisitedInPast24Hours.js]
 [browser_UsageTelemetry_urlbar.js]
 support-files =
   usageTelemetrySearchSuggestions.sjs
   usageTelemetrySearchSuggestions.xml
 [browser_UsageTelemetry_searchbar.js]
 support-files =
   usageTelemetrySearchSuggestions.sjs
   usageTelemetrySearchSuggestions.xml
--- a/browser/modules/test/browser/browser_PermissionUI.js
+++ b/browser/modules/test/browser/browser_PermissionUI.js
@@ -289,16 +289,17 @@ add_task(async function test_on_before_s
     let TestPrompt = {
       __proto__: PermissionUI.PermissionPromptForRequestPrototype,
       request: mockRequest,
       notificationID: kTestNotificationID,
       message: kTestMessage,
       promptActions: [mainAction],
       onBeforeShow() {
         beforeShown = true;
+        return true;
       },
     };
 
     let shownPromise =
       BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
     TestPrompt.prompt();
     Assert.ok(beforeShown, "Should have called onBeforeShown");
     await shownPromise;
@@ -348,16 +349,17 @@ add_task(async function test_no_request(
       __proto__: PermissionUI.PermissionPromptPrototype,
       notificationID: kTestNotificationID,
       principal,
       browser,
       message: kTestMessage,
       promptActions: [mainAction, secondaryAction],
       onBeforeShow() {
         beforeShown = true;
+        return true;
       },
     };
 
     let shownPromise =
       BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
     TestPrompt.prompt();
     Assert.ok(beforeShown, "Should have called onBeforeShown");
     await shownPromise;
--- a/browser/modules/test/browser/browser_PermissionUI_prompts.js
+++ b/browser/modules/test/browser/browser_PermissionUI_prompts.js
@@ -34,17 +34,19 @@ add_task(async function test_midi_permis
 add_task(async function test_autoplay_permission_prompt() {
   Services.prefs.setIntPref("media.autoplay.default", Ci.nsIAutoplay.PROMPT);
   await testPrompt(PermissionUI.AutoplayPermissionPrompt);
   Services.prefs.clearUserPref("media.autoplay.default");
 });
 
 // Tests that AutoplayPermissionPrompt works as expected
 add_task(async function test_storage_access_permission_prompt() {
+  Services.prefs.setBoolPref("dom.storage_access.auto_grants", false);
   await testPrompt(PermissionUI.StorageAccessPermissionPrompt);
+  Services.prefs.clearUserPref("dom.storage_access.auto_grants");
 });
 
 async function testPrompt(Prompt) {
   await BrowserTestUtils.withNewTab({
     gBrowser,
     url: "http://example.com",
   }, async function(browser) {
     let mockRequest = makeMockPermissionRequest(browser);
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/browser/browser_UsageTelemetry_uniqueOriginsVisitedInPast24Hours.js
@@ -0,0 +1,56 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+ChromeUtils.defineModuleGetter(this, "URICountListener",
+                               "resource:///modules/BrowserUsageTelemetry.jsm");
+
+add_task(async function test_uniqueOriginsVisitedInPast24Hours() {
+  registerCleanupFunction(async () => {
+    info("Cleaning up");
+    URICountListener.resetUniqueOriginsVisitedInPast24Hours();
+  });
+
+  URICountListener.resetUniqueOriginsVisitedInPast24Hours();
+  let startingCount = URICountListener.uniqueOriginsVisitedInPast24Hours;
+  is(startingCount, 0, "We should have no origins recorded in the history right after resetting");
+
+  // Add a new window and then some tabs in it.
+  let win = await BrowserTestUtils.openNewBrowserWindow();
+  await BrowserTestUtils.openNewForegroundTab(win.gBrowser, "http://example.com");
+
+  await BrowserTestUtils.openNewForegroundTab(win.gBrowser, "http://test1.example.com");
+  is(URICountListener.uniqueOriginsVisitedInPast24Hours, startingCount + 1,
+     "test1.example.com should only count as a unique visit if example.com wasn't visited before");
+
+  // http://www.exämple.test
+  await BrowserTestUtils.openNewForegroundTab(win.gBrowser, "http://xn--exmple-cua.test");
+  is(URICountListener.uniqueOriginsVisitedInPast24Hours, startingCount + 2,
+     "www.exämple.test should count as a unique visit");
+
+  // Set the expiry time to 1 second
+  await SpecialPowers.pushPrefEnv({set: [["browser.engagement.recent_visited_origins.expiry", 1]]});
+
+  await BrowserTestUtils.openNewForegroundTab(win.gBrowser, "http://127.0.0.1");
+  is(URICountListener.uniqueOriginsVisitedInPast24Hours, startingCount + 3,
+     "127.0.0.1 should count as a unique visit");
+
+  let countBefore = URICountListener.uniqueOriginsVisitedInPast24Hours;
+
+  await new Promise(resolve => {
+    setTimeout(_ => {
+      let countAfter = URICountListener.uniqueOriginsVisitedInPast24Hours;
+      is(countAfter, countBefore - 1,
+         "The expiry should work correctly");
+      resolve();
+    }, 1100);
+  });
+
+  BrowserTestUtils.removeTab(win.gBrowser.selectedTab);
+  BrowserTestUtils.removeTab(win.gBrowser.selectedTab);
+  await BrowserTestUtils.closeWindow(win);
+});
+
--- a/devtools/client/webconsole/components/message-types/PageError.js
+++ b/devtools/client/webconsole/components/message-types/PageError.js
@@ -31,16 +31,17 @@ function PageError(props) {
     open,
     repeat,
     serviceContainer,
     timestampsVisible,
     isPaused,
   } = props;
   const {
     id: messageId,
+    executionPoint,
     indent,
     source,
     type,
     level,
     messageText,
     stacktrace,
     frame,
     exceptionDocURL,
@@ -53,16 +54,17 @@ function PageError(props) {
     messageBody = messageText;
   } else if (typeof messageText === "object" && messageText.type === "longString") {
     messageBody = `${message.messageText.initial}…`;
   }
 
   return Message({
     dispatch,
     messageId,
+    executionPoint,
     isPaused,
     open,
     collapsible: Array.isArray(stacktrace),
     source,
     type,
     level,
     topLevelClasses: [],
     indent,
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -17,17 +17,16 @@
 #include "mozilla/StaticPrefs.h"
 #include "mozilla/dom/Animation.h"
 #include "mozilla/dom/Attr.h"
 #include "mozilla/dom/Flex.h"
 #include "mozilla/dom/Grid.h"
 #include "mozilla/dom/Text.h"
 #include "mozilla/gfx/Matrix.h"
 #include "nsAtom.h"
-#include "nsCSSFrameConstructor.h"
 #include "nsDOMAttributeMap.h"
 #include "nsIContentInlines.h"
 #include "mozilla/dom/NodeInfo.h"
 #include "nsIDocumentInlines.h"
 #include "mozilla/dom/DocumentTimeline.h"
 #include "nsIContentIterator.h"
 #include "nsFlexContainerFrame.h"
 #include "nsFocusManager.h"
--- a/dom/base/StorageAccessPermissionRequest.cpp
+++ b/dom/base/StorageAccessPermissionRequest.cpp
@@ -14,22 +14,24 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(Stora
 
 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(StorageAccessPermissionRequest,
                                                ContentPermissionRequestBase)
 
 StorageAccessPermissionRequest::StorageAccessPermissionRequest(
     nsPIDOMWindowInner* aWindow,
     nsIPrincipal* aNodePrincipal,
     AllowCallback&& aAllowCallback,
+    AllowAutoGrantCallback&& aAllowAutoGrantCallback,
     AllowAnySiteCallback&& aAllowAnySiteCallback,
     CancelCallback&& aCancelCallback)
   : ContentPermissionRequestBase(aNodePrincipal, false, aWindow,
                                  NS_LITERAL_CSTRING("dom.storage_access"),
                                  NS_LITERAL_CSTRING("storage-access")),
     mAllowCallback(std::move(aAllowCallback)),
+    mAllowAutoGrantCallback(std::move(aAllowAutoGrantCallback)),
     mAllowAnySiteCallback(std::move(aAllowAnySiteCallback)),
     mCancelCallback(std::move(aCancelCallback)),
     mCallbackCalled(false)
 {
   mPermissionRequests.AppendElement(PermissionRequest(mType, nsTArray<nsString>()));
 }
 
 StorageAccessPermissionRequest::~StorageAccessPermissionRequest()
@@ -56,39 +58,44 @@ StorageAccessPermissionRequest::Allow(JS
     return rv;
   }
 
   if (!mCallbackCalled) {
     mCallbackCalled = true;
     if (choices.Length() == 1 &&
         choices[0].choice().EqualsLiteral("allow-on-any-site")) {
       mAllowAnySiteCallback();
+    } else if (choices.Length() == 1 &&
+               choices[0].choice().EqualsLiteral("allow-auto-grant")) {
+      mAllowAutoGrantCallback();
     } else {
       mAllowCallback();
     }
   }
   return NS_OK;
 }
 
 already_AddRefed<StorageAccessPermissionRequest>
 StorageAccessPermissionRequest::Create(nsPIDOMWindowInner* aWindow,
                                        AllowCallback&& aAllowCallback,
+                                       AllowAutoGrantCallback&& aAllowAutoGrantCallback,
                                        AllowAnySiteCallback&& aAllowAnySiteCallback,
                                        CancelCallback&& aCancelCallback)
 {
   if (!aWindow) {
     return nullptr;
   }
   nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(aWindow);
   if (!win->GetPrincipal()) {
     return nullptr;
   }
   RefPtr<StorageAccessPermissionRequest> request =
     new StorageAccessPermissionRequest(aWindow,
                                        win->GetPrincipal(),
                                        std::move(aAllowCallback),
+                                       std::move(aAllowAutoGrantCallback),
                                        std::move(aAllowAnySiteCallback),
                                        std::move(aCancelCallback));
   return request.forget();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/StorageAccessPermissionRequest.h
+++ b/dom/base/StorageAccessPermissionRequest.h
@@ -23,34 +23,38 @@ public:
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(StorageAccessPermissionRequest,
                                            ContentPermissionRequestBase)
 
   // nsIContentPermissionRequest
   NS_IMETHOD Cancel(void) override;
   NS_IMETHOD Allow(JS::HandleValue choices) override;
 
   typedef std::function<void()> AllowCallback;
+  typedef std::function<void()> AllowAutoGrantCallback;
   typedef std::function<void()> AllowAnySiteCallback;
   typedef std::function<void()> CancelCallback;
 
   static already_AddRefed<StorageAccessPermissionRequest> Create(
     nsPIDOMWindowInner* aWindow,
     AllowCallback&& aAllowCallback,
+    AllowAutoGrantCallback&& aAllowAutoGrantCallback,
     AllowAnySiteCallback&& aAllowAnySiteCallback,
     CancelCallback&& aCancelCallback);
 
 private:
   StorageAccessPermissionRequest(nsPIDOMWindowInner* aWindow,
                                  nsIPrincipal* aNodePrincipal,
                                  AllowCallback&& aAllowCallback,
+                                 AllowAutoGrantCallback&& aAllowAutoGrantCallback,
                                  AllowAnySiteCallback&& aAllowAnySiteCallback,
                                  CancelCallback&& aCancelCallback);
   ~StorageAccessPermissionRequest();
 
   AllowCallback mAllowCallback;
+  AllowAutoGrantCallback mAllowAutoGrantCallback;
   AllowAnySiteCallback mAllowAnySiteCallback;
   CancelCallback mCancelCallback;
   nsTArray<PermissionRequest> mPermissionRequests;
   bool mCallbackCalled;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -13983,19 +13983,21 @@ nsIDocument::RequestStorageAccess(mozill
                     !isOnAllowList);
 
       auto performFinalChecks = [inner] () -> RefPtr<AntiTrackingCommon::StorageAccessFinalCheckPromise> {
           RefPtr<AntiTrackingCommon::StorageAccessFinalCheckPromise::Private> p =
             new AntiTrackingCommon::StorageAccessFinalCheckPromise::Private(__func__);
           RefPtr<StorageAccessPermissionRequest> sapr =
             StorageAccessPermissionRequest::Create(inner,
               // Allow
-              [p] { p->Resolve(false, __func__); },
+              [p] { p->Resolve(AntiTrackingCommon::eAllow, __func__); },
+              // Allow auto grant
+              [p] { p->Resolve(AntiTrackingCommon::eAllowAutoGrant, __func__); },
               // Allow on any site
-              [p] { p->Resolve(true, __func__); },
+              [p] { p->Resolve(AntiTrackingCommon::eAllowOnAnySite, __func__); },
               // Block
               [p] { p->Reject(false, __func__); });
 
           typedef ContentPermissionRequestBase::PromptResult PromptResult;
           PromptResult pr = sapr->CheckPromptPrefs();
           bool onAnySite = false;
           if (pr == PromptResult::Pending) {
             // Also check our custom pref for the "Allow on any site" case
@@ -14006,17 +14008,18 @@ nsIDocument::RequestStorageAccess(mozill
             }
           }
 
           if (pr != PromptResult::Pending) {
             MOZ_ASSERT_IF(pr != PromptResult::Granted,
                           pr == PromptResult::Denied);
             if (pr == PromptResult::Granted) {
               return AntiTrackingCommon::StorageAccessFinalCheckPromise::
-                CreateAndResolve(onAnySite, __func__);
+                CreateAndResolve(onAnySite ? AntiTrackingCommon::eAllowOnAnySite :
+                                             AntiTrackingCommon::eAllow, __func__);
             }
             return AntiTrackingCommon::StorageAccessFinalCheckPromise::
               CreateAndReject(false, __func__);
           }
 
           sapr->RequestDelayedTask(inner->EventTargetFor(TaskCategory::Other),
                                    ContentPermissionRequestBase::DelayedTaskType::Request);
           return p.forget();
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -7627,20 +7627,16 @@ nsGlobalWindowInner::GetSidebar(OwningEx
 #else
   aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
 #endif
 }
 
 void
 nsGlobalWindowInner::ClearDocumentDependentSlots(JSContext* aCx)
 {
-  if (js::GetContextCompartment(aCx) != js::GetObjectCompartment(GetWrapperPreserveColor())) {
-    MOZ_CRASH("Looks like bug 1488480/1405521, with ClearDocumentDependentSlots in a bogus compartment");
-  }
-
   // If JSAPI OOMs here, there is basically nothing we can do to recover safely.
   if (!Window_Binding::ClearCachedDocumentValue(aCx, this) ||
       !Window_Binding::ClearCachedPerformanceValue(aCx, this)) {
     MOZ_CRASH("Unhandlable OOM while clearing document dependent slots.");
   }
 }
 
 /* static */
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -2881,30 +2881,24 @@ nsINode::WrapObject(JSContext *aCx, JS::
   // Event handling is possible only if (1). If (2) event handling is
   // prevented.
   // If the document has never had a script handling object, untrusted
   // scripts (3) shouldn't touch it!
   bool hasHadScriptHandlingObject = false;
   if (!OwnerDoc()->GetScriptHandlingObject(hasHadScriptHandlingObject) &&
       !hasHadScriptHandlingObject &&
       !nsContentUtils::IsSystemCaller(aCx)) {
-    if (IsDocument()) {
-      MOZ_CRASH("Looks like bug 1488480/1405521, with a document that lost its script handling object");
-    }
     Throw(aCx, NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   JS::Rooted<JSObject*> obj(aCx, WrapNode(aCx, aGivenProto));
   MOZ_ASSERT_IF(obj && ChromeOnlyAccess(),
                 xpc::IsInContentXBLScope(obj) ||
                 !xpc::UseContentXBLScope(JS::GetObjectRealmOrNull(obj)));
-  if (!obj && IsDocument()) {
-    MOZ_CRASH("Looks like bug 1488480/1405521, with WrapNode on a document returning null");
-  }
   return obj;
 }
 
 already_AddRefed<nsINode>
 nsINode::CloneNode(bool aDeep, ErrorResult& aError)
 {
   return nsNodeUtils::CloneNodeImpl(this, aDeep, aError);
 }
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -684,58 +684,26 @@ DefineConstants(JSContext* cx, JS::Handl
     if (!ok) {
       return false;
     }
   }
   return true;
 }
 
 static inline bool
-Define(JSContext* cx, JS::Handle<JSObject*> obj, const JSFunctionSpec* spec)
-{
-  bool ok = JS_DefineFunctions(cx, obj, spec);
-  if (ok) {
-    return true;
-  }
-
-  if (!strcmp(js::GetObjectClass(obj)->name, "DocumentPrototype")) {
-    MOZ_CRASH("Bug 1405521/1488480: JS_DefineFunctions failed for Document.prototype");
-  }
-
-  return false;
+Define(JSContext* cx, JS::Handle<JSObject*> obj, const JSFunctionSpec* spec) {
+  return JS_DefineFunctions(cx, obj, spec);
 }
 static inline bool
-Define(JSContext* cx, JS::Handle<JSObject*> obj, const JSPropertySpec* spec)
-{
-  bool ok = JS_DefineProperties(cx, obj, spec);
-  if (ok) {
-    return true;
-  }
-
-  if (!strcmp(js::GetObjectClass(obj)->name, "DocumentPrototype")) {
-    MOZ_CRASH("Bug 1405521/1488480: JS_DefineProperties failed for Document.prototype");
-  }
-
-  return false;
+Define(JSContext* cx, JS::Handle<JSObject*> obj, const JSPropertySpec* spec) {
+  return JS_DefineProperties(cx, obj, spec);
 }
-
 static inline bool
-Define(JSContext* cx, JS::Handle<JSObject*> obj, const ConstantSpec* spec)
-{
-  bool ok = DefineConstants(cx, obj, spec);
-  if (ok) {
-    return true;
-  }
-
-
-  if (!strcmp(js::GetObjectClass(obj)->name, "DocumentPrototype")) {
-    MOZ_CRASH("Bug 1405521/1488480: DefineConstants failed for Document.prototype");
-  }
-
-  return false;
+Define(JSContext* cx, JS::Handle<JSObject*> obj, const ConstantSpec* spec) {
+  return DefineConstants(cx, obj, spec);
 }
 
 template<typename T>
 bool
 DefinePrefable(JSContext* cx, JS::Handle<JSObject*> obj,
                const Prefable<T>* props)
 {
   MOZ_ASSERT(props);
@@ -961,75 +929,53 @@ CreateInterfacePrototypeObject(JSContext
 {
   JS::Rooted<JSObject*> ourProto(cx,
     JS_NewObjectWithUniqueType(cx, Jsvalify(protoClass), parentProto));
   if (!ourProto ||
       // We don't try to define properties on the global's prototype; those
       // properties go on the global itself.
       (!isGlobal &&
        !DefineProperties(cx, ourProto, properties, chromeOnlyProperties))) {
-    if (!strcmp(protoClass->name, "DocumentPrototype")) {
-      if (!ourProto) {
-        MOZ_CRASH("Bug 1405521/1488480: JS_NewObjectWithUniqueType failed for Document.prototype");
-      } else {
-        MOZ_CRASH("Bug 1405521/1488480: DefineProperties failed for Document.prototype");
-      }
-    }
     return nullptr;
   }
 
   if (unscopableNames) {
     JS::Rooted<JSObject*> unscopableObj(cx,
       JS_NewObjectWithGivenProto(cx, nullptr, nullptr));
     if (!unscopableObj) {
-      if (!strcmp(protoClass->name, "DocumentPrototype")) {
-        MOZ_CRASH("Bug 1405521/1488480: Unscopable object creation failed for Document.prototype");
-      }
       return nullptr;
     }
 
     for (; *unscopableNames; ++unscopableNames) {
       if (!JS_DefineProperty(cx, unscopableObj, *unscopableNames,
                              JS::TrueHandleValue, JSPROP_ENUMERATE)) {
-        if (!strcmp(protoClass->name, "DocumentPrototype")) {
-          MOZ_CRASH("Bug 1405521/1488480: Defining property on unscopable object failed for Document.prototype");
-        }
         return nullptr;
       }
     }
 
     JS::Rooted<jsid> unscopableId(cx,
       SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, JS::SymbolCode::unscopables)));
     // Readonly and non-enumerable to match Array.prototype.
     if (!JS_DefinePropertyById(cx, ourProto, unscopableId, unscopableObj,
                                JSPROP_READONLY)) {
-      if (!strcmp(protoClass->name, "DocumentPrototype")) {
-        MOZ_CRASH("Bug 1405521/1488480: Defining @@unscopables failed for Document.prototype");
-      }
       return nullptr;
     }
   }
 
   if (toStringTag) {
     JS::Rooted<JSString*> toStringTagStr(cx,
                                          JS_NewStringCopyZ(cx, toStringTag));
     if (!toStringTagStr) {
-      if (!strcmp(protoClass->name, "DocumentPrototype")) {
-        MOZ_CRASH("Bug 1405521/1488480: Copying string tag failed for Document.prototype");
-      }
       return nullptr;
     }
 
     JS::Rooted<jsid> toStringTagId(cx,
       SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, JS::SymbolCode::toStringTag)));
     if (!JS_DefinePropertyById(cx, ourProto, toStringTagId, toStringTagStr,
                                JSPROP_READONLY)) {
-      if (!strcmp(protoClass->name, "DocumentPrototype")) {
-        MOZ_CRASH("Bug 1405521/1488480: Defining @@toStringTag failed for Document.prototype");
-      }
       return nullptr;
     }
   }
 
   return ourProto;
 }
 
 bool
@@ -1124,19 +1070,16 @@ CreateInterfaceObjects(JSContext* cx, JS
   JS::Rooted<JSObject*> proto(cx);
   if (protoClass) {
     proto =
       CreateInterfacePrototypeObject(cx, global, protoProto, protoClass,
                                      properties,
                                      isChrome ? chromeOnlyProperties : nullptr,
                                      unscopableNames, toStringTag, isGlobal);
     if (!proto) {
-      if (name && !strcmp(name, "Document")) {
-        MOZ_CRASH("Bug 1405521/1488480: CreateInterfacePrototypeObject failed for Document.prototype");
-      }
       return;
     }
 
     *protoCache = proto;
   }
   else {
     MOZ_ASSERT(!proto);
   }
@@ -1145,19 +1088,16 @@ CreateInterfaceObjects(JSContext* cx, JS
   if (constructorClass) {
     interface = CreateInterfaceObject(cx, global, constructorProto,
                                       constructorClass, ctorNargs,
                                       namedConstructors, proto, properties,
                                       chromeOnlyProperties, name,
                                       isChrome,
                                       defineOnGlobal);
     if (!interface) {
-      if (name && !strcmp(name, "Document")) {
-        MOZ_CRASH("Bug 1405521/1488480: CreateInterfaceObject failed for Document");
-      }
       if (protoCache) {
         // If we fail we need to make sure to clear the value of protoCache we
         // set above.
         *protoCache = nullptr;
       }
       return;
     }
     *constructorCache = interface;
@@ -4294,20 +4234,16 @@ JS::Handle<JSObject*>
 GetPerInterfaceObjectHandle(JSContext* aCx,
                             size_t aSlotId,
                             CreateInterfaceObjectsMethod aCreator,
                             bool aDefineOnGlobal)
 {
   /* Make sure our global is sane.  Hopefully we can remove this sometime */
   JSObject* global = JS::CurrentGlobalOrNull(aCx);
   if (!(js::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL)) {
-    if (aSlotId == prototypes::id::HTMLDocument ||
-        aSlotId == prototypes::id::Document) {
-      MOZ_CRASH("Looks like bug 1488480/1405521, with a non-DOM global in GetPerInterfaceObjectHandle");
-    }
     return nullptr;
   }
 
   /* Check to see whether the interface objects are already installed */
   ProtoAndIfaceCache& protoAndIfaceCache = *GetProtoAndIfaceCache(global);
   if (!protoAndIfaceCache.HasEntryInSlot(aSlotId)) {
     JS::Rooted<JSObject*> rootedGlobal(aCx, global);
     aCreator(aCx, rootedGlobal, protoAndIfaceCache, aDefineOnGlobal);
@@ -4322,40 +4258,16 @@ GetPerInterfaceObjectHandle(JSContext* a
    *
    * Calling address() avoids the read barrier that does gray unmarking, but
    * it's not possible for the object to be gray here.
    */
 
   const JS::Heap<JSObject*>& entrySlot =
     protoAndIfaceCache.EntrySlotMustExist(aSlotId);
   MOZ_ASSERT(JS::ObjectIsNotGray(entrySlot));
-
-  if (!entrySlot) {
-    switch (aSlotId) {
-      case prototypes::id::HTMLDocument: {
-         MOZ_CRASH("Looks like bug 1488480/1405521, with aCreator failing to create HTMLDocument.prototype");
-         break;
-      }
-      case prototypes::id::Document: {
-        MOZ_CRASH("Looks like bug 1488480/1405521, with aCreator failing to create Document.prototype");
-        break;
-      }
-      case prototypes::id::Node: {
-        MOZ_CRASH("Looks like bug 1488480/1405521, with aCreator failing to create Node.prototype");
-        break;
-      }
-      case prototypes::id::EventTarget: {
-        MOZ_CRASH("Looks like bug 1488480/1405521, with aCreator failing to create EventTarget.prototype");
-        break;
-      }
-      default:
-      break;
-    }
-  }
-
   return JS::Handle<JSObject*>::fromMarkedLocation(entrySlot.address());
 }
 
 namespace binding_detail {
 bool
 IsGetterEnabled(JSContext* aCx, JS::Handle<JSObject*> aObj,
                 JSJitGetterOp aGetter,
                 const Prefable<const JSPropertySpec>* aAttributes)
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -2974,63 +2974,49 @@ class CGCreateInterfaceObjectsMethod(CGA
         getConstructorProto += "(aCx)"
 
         needInterfaceObject = self.descriptor.interface.hasInterfaceObject()
         needInterfacePrototypeObject = self.descriptor.interface.hasInterfacePrototypeObject()
 
         # if we don't need to create anything, why are we generating this?
         assert needInterfaceObject or needInterfacePrototypeObject
 
-        def maybecrash(reason):
-            if self.descriptor.name == "Document":
-                return 'MOZ_CRASH("Bug 1405521/1488480: %s");\n' % reason
-            return ""
-
         getParentProto = fill(
             """
             JS::${type}<JSObject*> parentProto(${getParentProto});
             if (!parentProto) {
-              $*{maybeCrash}
               return;
             }
             """,
-            maybeCrash=maybecrash("Can't get Node.prototype"),
             type=parentProtoType,
             getParentProto=getParentProto)
 
         getConstructorProto = fill(
             """
             JS::${type}<JSObject*> constructorProto(${getConstructorProto});
             if (!constructorProto) {
-              $*{maybeCrash}
               return;
             }
             """,
-            maybeCrash=maybecrash("Can't get Node"),
             type=constructorProtoType,
             getConstructorProto=getConstructorProto)
 
         idsToInit = []
         # There is no need to init any IDs in bindings that don't want Xrays.
         if self.descriptor.wantsXrays:
             if self.properties.hasNonChromeOnly():
                 idsToInit.append("sNativeProperties")
             if self.properties.hasChromeOnly():
                 idsToInit.append("sChromeOnlyNativeProperties")
         if len(idsToInit) > 0:
             initIdCalls = ["!InitIds(aCx, %s.Upcast())" % (properties)
                            for properties in idsToInit]
             idsInitedFlag = CGGeneric("static bool sIdsInited = false;\n")
             setFlag = CGGeneric("sIdsInited = true;\n")
-            initIdConditionals = [CGIfWrapper(CGGeneric(fill(
-                """
-                $*{maybeCrash}
-                return;
-                """,
-                maybeCrash=maybecrash("Can't init IDs"))), call)
+            initIdConditionals = [CGIfWrapper(CGGeneric("return;\n"), call)
                                   for call in initIdCalls]
             initIds = CGList([idsInitedFlag,
                               CGIfWrapper(CGList(initIdConditionals + [setFlag]),
                                           "!sIdsInited && NS_IsMainThread()")])
         else:
             initIds = None
 
         prefCacheData = []
@@ -3107,35 +3093,31 @@ class CGCreateInterfaceObjectsMethod(CGA
                                         ${toStringTag},
                                         ${constructorProto}, ${interfaceClass}, ${constructArgs}, ${namedConstructors},
                                         interfaceCache,
                                         ${properties},
                                         ${chromeProperties},
                                         ${name}, aDefineOnGlobal,
                                         ${unscopableNames},
                                         ${isGlobal});
-            if (protoCache && !*protoCache) {
-              $*{maybeCrash}
-            }
             """,
             protoClass=protoClass,
             parentProto=parentProto,
             protoCache=protoCache,
             toStringTag=toStringTag,
             constructorProto=constructorProto,
             interfaceClass=interfaceClass,
             constructArgs=constructArgs,
             namedConstructors=namedConstructors,
             interfaceCache=interfaceCache,
             properties=properties,
             chromeProperties=chromeProperties,
             name='"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "nullptr",
             unscopableNames="unscopableNames" if self.haveUnscopables else "nullptr",
-            isGlobal=toStringBool(isGlobal),
-            maybeCrash=maybecrash("dom::CreateInterfaceObjects failed for Document"))
+            isGlobal=toStringBool(isGlobal))
 
         # If we fail after here, we must clear interface and prototype caches
         # using this code: intermediate failure must not expose the interface in
         # partially-constructed state.  Note that every case after here needs an
         # interface prototype object.
         failureCode = dedent(
             """
             *protoCache = nullptr;
@@ -3227,22 +3209,20 @@ class CGCreateInterfaceObjectsMethod(CGA
                 holderProto = "*protoCache"
             createUnforgeableHolder = CGGeneric(fill(
                 """
                 JS::Rooted<JSObject*> unforgeableHolder(aCx);
                 {
                   JS::Rooted<JSObject*> holderProto(aCx, ${holderProto});
                   unforgeableHolder = JS_NewObjectWithoutMetadata(aCx, ${holderClass}, holderProto);
                   if (!unforgeableHolder) {
-                    $*{maybeCrash}
                     $*{failureCode}
                   }
                 }
                 """,
-                maybeCrash=maybecrash("Can't create unforgeable holder"),
                 holderProto=holderProto,
                 holderClass=holderClass,
                 failureCode=failureCode))
             defineUnforgeables = InitUnforgeablePropertiesOnHolder(self.descriptor,
                                                                    self.properties,
                                                                    failureCode)
             createUnforgeableHolder = CGList(
                 [createUnforgeableHolder, defineUnforgeables])
@@ -3517,17 +3497,17 @@ class CGConstructorEnabled(CGAbstractMet
                                           reindent=True)
         else:
             conditionsWrapper = CGGeneric("return true;\n")
 
         body.append(conditionsWrapper)
         return body.define()
 
 
-def CreateBindingJSObject(descriptor, properties, failureCode = ""):
+def CreateBindingJSObject(descriptor, properties):
     objDecl = "BindingJSObjectCreator<%s> creator(aCx);\n" % descriptor.nativeType
 
     # We don't always need to root obj, but there are a variety
     # of cases where we do, so for simplicity, just always root it.
     if descriptor.proxy:
         if descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
             create = dedent(
                 """
@@ -3542,24 +3522,22 @@ def CreateBindingJSObject(descriptor, pr
                 creator.CreateProxyObject(aCx, &sClass.mBase, DOMProxyHandler::getInstance(),
                                           proto, aObject, JS::UndefinedHandleValue, aReflector);
                 """)
     else:
         create = dedent(
             """
             creator.CreateObject(aCx, sClass.ToJSClass(), proto, aObject, aReflector);
             """)
-    return objDecl + create + fill(
+    return objDecl + create + dedent(
         """
         if (!aReflector) {
-          $*{failureCode}
           return false;
         }
-        """,
-        failureCode=failureCode)
+        """)
 
 
 def InitUnforgeablePropertiesOnHolder(descriptor, properties, failureCode,
                                       holderName="unforgeableHolder"):
     """
     Define the unforgeable properties on the unforgeable holder for
     the interface represented by descriptor.
 
@@ -3568,32 +3546,22 @@ def InitUnforgeablePropertiesOnHolder(de
     """
     assert (properties.unforgeableAttrs.hasNonChromeOnly() or
             properties.unforgeableAttrs.hasChromeOnly() or
             properties.unforgeableMethods.hasNonChromeOnly() or
             properties.unforgeableMethods.hasChromeOnly())
 
     unforgeables = []
 
-    if descriptor.name == "Document":
-        maybeCrash = dedent(
-            """
-            MOZ_CRASH("Bug 1405521/1488480: Can't define unforgeable attributes");
-            """);
-    else:
-        maybeCrash = "";
-
     defineUnforgeableAttrs = fill(
         """
         if (!DefineUnforgeableAttributes(aCx, ${holderName}, %s)) {
-          $*{maybeCrash}
           $*{failureCode}
         }
         """,
-        maybeCrash=maybeCrash,
         failureCode=failureCode,
         holderName=holderName)
     defineUnforgeableMethods = fill(
         """
         if (!DefineUnforgeableMethods(aCx, ${holderName}, %s)) {
           $*{failureCode}
         }
         """,
@@ -3729,45 +3697,41 @@ def SetImmutablePrototype(descriptor, fa
         MOZ_ASSERT(succeeded,
                    "Making a fresh reflector instance have an immutable "
                    "prototype can internally fail, but it should never be "
                    "unsuccessful");
         """,
         failureCode=failureCode)
 
 
-def DeclareProto(noProto = "", wrapFail = ""):
+def DeclareProto():
     """
     Declare the canonicalProto and proto we have for our wrapping operation.
     """
-    return fill(
+    return dedent(
         """
         JS::Handle<JSObject*> canonicalProto = GetProtoObjectHandle(aCx);
         if (!canonicalProto) {
-          $*{noProto}
           return false;
         }
         JS::Rooted<JSObject*> proto(aCx);
         if (aGivenProto) {
           proto = aGivenProto;
           // Unfortunately, while aGivenProto was in the compartment of aCx
           // coming in, we changed compartments to that of "parent" so may need
           // to wrap the proto here.
           if (js::GetContextCompartment(aCx) != js::GetObjectCompartment(proto)) {
             if (!JS_WrapObject(aCx, &proto)) {
-              $*{wrapFail}
               return false;
             }
           }
         } else {
           proto = canonicalProto;
         }
-        """,
-        noProto=noProto,
-        wrapFail=wrapFail)
+        """)
 
 
 class CGWrapWithCacheMethod(CGAbstractMethod):
     """
     Create a wrapper JSObject for a given native that implements nsWrapperCache.
 
     properties should be a PropertyArrays instance.
     """
@@ -3784,73 +3748,31 @@ class CGWrapWithCacheMethod(CGAbstractMe
     def definition_body(self):
         failureCode = dedent(
             """
             aCache->ReleaseWrapper(aObject);
             aCache->ClearWrapper();
             return false;
             """)
 
-        isDocument = False
-        iface = self.descriptor.interface
-        while iface:
-            if iface.identifier.name == "Document":
-                isDocument = True
-                break
-            iface = iface.parent
-
-        if isDocument:
-            noGlobal = fill(
-                """
-                MOZ_CRASH("Looks like bug 1488480/1405521, with ${name} not having a global");
-                """,
-                name = self.descriptor.name)
-            noProto = fill(
-                """
-                MOZ_CRASH("Looks like bug 1488480/1405521, with ${name} not having a proto");
-                """,
-                name = self.descriptor.name)
-            protoWrapFail = fill(
-                """
-                MOZ_CRASH("Looks like bug 1488480/1405521, with ${name} failing to wrap a custom proto");
-                """,
-                name = self.descriptor.name)
-            createObjectFailed = fill(
-                """
-                MOZ_CRASH("Looks like bug 1488480/1405521, with ${name} failing to CreateObject/CreateProxyObject");
-                """,
-                name = self.descriptor.name)
-            expandoAllocFail = fill(
-                """
-                MOZ_CRASH("Looks like bug 1488480/1405521, with ${name} failing to EnsureExpandoObject or JS_InitializePropertiesFromCompatibleNativeObject");
-                """,
-                name = self.descriptor.name)
-        else:
-            noGlobal = ""
-            noProto = ""
-            protoWrapFail = ""
-            createObjectFailed = ""
-            expandoAllocFail = ""
-
         return fill(
             """
             static_assert(!IsBaseOf<NonRefcountedDOMObject, ${nativeType}>::value,
                           "Shouldn't have wrappercached things that are not refcounted.");
             $*{assertInheritance}
             MOZ_ASSERT_IF(aGivenProto, js::IsObjectInContextCompartment(aGivenProto, aCx));
             MOZ_ASSERT(!aCache->GetWrapper(),
                        "You should probably not be using Wrap() directly; use "
                        "GetOrCreateDOMReflector instead");
 
             MOZ_ASSERT(ToSupportsIsOnPrimaryInheritanceChain(aObject, aCache),
                        "nsISupports must be on our primary inheritance chain");
 
             JS::Rooted<JSObject*> global(aCx, FindAssociatedGlobal(aCx, aObject->GetParentObject()));
             if (!global) {
-              $*{noGlobal}
               return false;
             }
             MOZ_ASSERT(JS_IsGlobalObject(global));
             MOZ_ASSERT(JS::ObjectIsNotGray(global));
 
             // That might have ended up wrapping us already, due to the wonders
             // of XBL.  Check for that, and bail out as needed.
             aReflector.set(aCache->GetWrapper());
@@ -3881,24 +3803,21 @@ class CGWrapWithCacheMethod(CGAbstractMe
             // somewhat common) to have a non-null aGivenProto which is the
             // same as canonicalProto.
             if (proto != canonicalProto) {
               PreserveWrapper(aObject);
             }
 
             return true;
             """,
-            noGlobal=noGlobal,
             nativeType=self.descriptor.nativeType,
             assertInheritance=AssertInheritanceChain(self.descriptor),
-            declareProto=DeclareProto(noProto, protoWrapFail),
-            createObject=CreateBindingJSObject(self.descriptor, self.properties,
-                                               createObjectFailed),
+            declareProto=DeclareProto(),
+            createObject=CreateBindingJSObject(self.descriptor, self.properties),
             unforgeable=CopyUnforgeablePropertiesToInstance(self.descriptor,
-                                                            expandoAllocFail +
                                                             failureCode),
             slots=InitMemberSlots(self.descriptor, failureCode),
             setImmutablePrototype=SetImmutablePrototype(self.descriptor,
                                                         failureCode))
 
 
 class CGWrapMethod(CGAbstractMethod):
     def __init__(self, descriptor):
--- a/dom/html/ImageDocument.cpp
+++ b/dom/html/ImageDocument.cpp
@@ -198,21 +198,17 @@ ImageDocument::Init()
   mFirstResize = true;
 
   return NS_OK;
 }
 
 JSObject*
 ImageDocument::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
-  JSObject* obj = ImageDocument_Binding::Wrap(aCx, this, aGivenProto);
-  if (!obj) {
-      MOZ_CRASH("Looks like bug 1488480/1405521, with ImageDocument::WrapNode failing");
-  }
-  return obj;
+  return ImageDocument_Binding::Wrap(aCx, this, aGivenProto);
 }
 
 nsresult
 ImageDocument::StartDocumentLoad(const char*         aCommand,
                                  nsIChannel*         aChannel,
                                  nsILoadGroup*       aLoadGroup,
                                  nsISupports*        aContainer,
                                  nsIStreamListener** aDocListener,
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -198,21 +198,17 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(nsHTM
 
 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(nsHTMLDocument,
                                              nsDocument,
                                              nsIHTMLDocument)
 
 JSObject*
 nsHTMLDocument::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
-  JSObject* obj = HTMLDocument_Binding::Wrap(aCx, this, aGivenProto);
-  if (!obj) {
-      MOZ_CRASH("Looks like bug 1488480/1405521, with nsHTMLDocument::WrapNode failing");
-  }
-  return obj;
+  return HTMLDocument_Binding::Wrap(aCx, this, aGivenProto);
 }
 
 nsresult
 nsHTMLDocument::Init()
 {
   nsresult rv = nsDocument::Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -51,17 +51,16 @@
 #include "mozilla/ProcessHangMonitor.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
 #include "mozilla/Unused.h"
 #include "nsContentUtils.h"
-#include "nsCSSFrameConstructor.h"
 #include "nsDocShell.h"
 #include "nsEmbedCID.h"
 #include "nsGlobalWindow.h"
 #include <algorithm>
 #include "nsExceptionHandler.h"
 #include "nsFilePickerProxy.h"
 #include "mozilla/dom/Element.h"
 #include "nsGlobalWindow.h"
--- a/dom/xbl/nsXBLResourceLoader.cpp
+++ b/dom/xbl/nsXBLResourceLoader.cpp
@@ -1,15 +1,14 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "nsCSSFrameConstructor.h"
 #include "nsTArray.h"
 #include "nsString.h"
 #include "nsIDocument.h"
 #include "nsIContent.h"
 #include "nsIPresShell.h"
 #include "nsXBLService.h"
 #include "nsIServiceManager.h"
 #include "nsXBLResourceLoader.h"
--- a/dom/xml/XMLDocument.cpp
+++ b/dom/xml/XMLDocument.cpp
@@ -623,22 +623,17 @@ XMLDocument::Clone(dom::NodeInfo* aNodeI
 
   clone.forget(aResult);
   return NS_OK;
 }
 
 JSObject*
 XMLDocument::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
 {
-  JSObject* obj;
   if (mIsPlainDocument) {
-    obj = Document_Binding::Wrap(aCx, this, aGivenProto);
-  } else {
-    obj = XMLDocument_Binding::Wrap(aCx, this, aGivenProto);
+    return Document_Binding::Wrap(aCx, this, aGivenProto);
   }
-  if (!obj) {
-      MOZ_CRASH("Looks like bug 1488480/1405521, with XMLDocument::WrapNode failing");
-  }
-  return obj;
+
+  return XMLDocument_Binding::Wrap(aCx, this, aGivenProto);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -1643,17 +1643,13 @@ XULDocument::DirectionChanged(const char
   if (aDoc) {
       aDoc->ResetDocumentDirection();
   }
 }
 
 JSObject*
 XULDocument::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
 {
-  JSObject* obj = XULDocument_Binding::Wrap(aCx, this, aGivenProto);
-  if (!obj) {
-      MOZ_CRASH("Looks like bug 1488480/1405521, with XULDocument_Binding::Wrap failing");
-  }
-  return obj;
+  return XULDocument_Binding::Wrap(aCx, this, aGivenProto);
 }
 
 } // namespace dom
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1510684.js
@@ -0,0 +1,38 @@
+var verified = false;
+function f(a) {
+    if (a < 10000)
+        return 5;
+    assertEq(g_fwd.caller.arguments.length, 0);
+    assertEq(h_fwd.caller.arguments.length, 0);
+    verified = true;
+    return 6;
+}
+
+function g_fwd(x) {
+    with({}) {};
+    return f(x);
+}
+function g(a) {
+    var x = a;
+    function inline() {
+        return g_fwd(x);
+    }
+    return inline();
+}
+
+function h_fwd(x) {
+    with({}) {};
+    return g(x);
+}
+function h(a) {
+    var x = a;
+    function inline() {
+        return h_fwd(x);
+    }
+    return inline();
+}
+
+var i = 0;
+while (!verified) {
+    h(i++);
+}
--- a/js/src/jit/BaselineDebugModeOSR.cpp
+++ b/js/src/jit/BaselineDebugModeOSR.cpp
@@ -1099,99 +1099,124 @@ JitRuntime::getBaselineDebugModeOSRHandl
         return nullptr;
     }
     return popFrameReg
            ? baselineDebugModeOSRHandler_->raw()
            : baselineDebugModeOSRHandlerNoFrameRegPopAddr_.ref();
 }
 
 static void
+PushCallVMOutputRegisters(MacroAssembler& masm)
+{
+    // callVMs can use several different output registers, depending on the
+    // type of their outparam.
+    masm.push(ReturnReg);
+    masm.push(ReturnDoubleReg);
+    masm.Push(JSReturnOperand);
+}
+
+static void
+PopCallVMOutputRegisters(MacroAssembler& masm)
+{
+    masm.Pop(JSReturnOperand);
+    masm.pop(ReturnDoubleReg);
+    masm.pop(ReturnReg);
+}
+
+static void
+TakeCallVMOutputRegisters(AllocatableGeneralRegisterSet& regs)
+{
+    regs.take(ReturnReg);
+    regs.take(JSReturnOperand);
+}
+
+static void
 EmitBaselineDebugModeOSRHandlerTail(MacroAssembler& masm, Register temp, bool returnFromCallVM)
 {
     // Save real return address on the stack temporarily.
     //
     // If we're returning from a callVM, we don't need to worry about R0 and
-    // R1 but do need to propagate the original ReturnReg value. Otherwise we
-    // need to worry about R0 and R1 but can clobber ReturnReg. Indeed, on
-    // x86, R1 contains ReturnReg.
+    // R1 but do need to propagate the registers the call might write to.
+    // Otherwise we need to worry about R0 and R1 but can clobber ReturnReg.
+    // Indeed, on x86, R1 contains ReturnReg.
     if (returnFromCallVM) {
-        masm.push(ReturnReg);
+        PushCallVMOutputRegisters(masm);
     } else {
         masm.pushValue(Address(temp, offsetof(BaselineDebugModeOSRInfo, valueR0)));
         masm.pushValue(Address(temp, offsetof(BaselineDebugModeOSRInfo, valueR1)));
     }
     masm.push(BaselineFrameReg);
     masm.push(Address(temp, offsetof(BaselineDebugModeOSRInfo, resumeAddr)));
 
     // Call a stub to free the allocated info.
     masm.setupUnalignedABICall(temp);
     masm.loadBaselineFramePtr(BaselineFrameReg, temp);
     masm.passABIArg(temp);
     masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, FinishBaselineDebugModeOSR));
 
     // Restore saved values.
     AllocatableGeneralRegisterSet jumpRegs(GeneralRegisterSet::All());
     if (returnFromCallVM) {
-        jumpRegs.take(ReturnReg);
+        TakeCallVMOutputRegisters(jumpRegs);
     } else {
         jumpRegs.take(R0);
         jumpRegs.take(R1);
     }
     jumpRegs.take(BaselineFrameReg);
     Register target = jumpRegs.takeAny();
 
     masm.pop(target);
     masm.pop(BaselineFrameReg);
     if (returnFromCallVM) {
-        masm.pop(ReturnReg);
+        PopCallVMOutputRegisters(masm);
     } else {
         masm.popValue(R1);
         masm.popValue(R0);
     }
 
     masm.jump(target);
 }
 
 JitCode*
 JitRuntime::generateBaselineDebugModeOSRHandler(JSContext* cx, uint32_t* noFrameRegPopOffsetOut)
 {
     StackMacroAssembler masm(cx);
 
     AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
     regs.take(BaselineFrameReg);
-    regs.take(ReturnReg);
+    TakeCallVMOutputRegisters(regs);
     Register temp = regs.takeAny();
     Register syncedStackStart = regs.takeAny();
 
     // Pop the frame reg.
     masm.pop(BaselineFrameReg);
 
     // Not all patched baseline frames are returning from a situation where
     // the frame reg is already fixed up.
     CodeOffset noFrameRegPopOffset(masm.currentOffset());
 
     // Record the stack pointer for syncing.
     masm.moveStackPtrTo(syncedStackStart);
-    masm.push(ReturnReg);
+    PushCallVMOutputRegisters(masm);
     masm.push(BaselineFrameReg);
 
     // Call a stub to fully initialize the info.
     masm.setupUnalignedABICall(temp);
     masm.loadBaselineFramePtr(BaselineFrameReg, temp);
     masm.passABIArg(temp);
     masm.passABIArg(syncedStackStart);
     masm.passABIArg(ReturnReg);
     masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, SyncBaselineDebugModeOSRInfo));
 
     // Discard stack values depending on how many were unsynced, as we always
     // have a fully synced stack in the recompile handler. We arrive here via
     // a callVM, and prepareCallVM in BaselineCompiler always fully syncs the
     // stack.
     masm.pop(BaselineFrameReg);
-    masm.pop(ReturnReg);
+    PopCallVMOutputRegisters(masm);
     masm.loadPtr(Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfScratchValue()), temp);
     masm.addToStackPtr(Address(temp, offsetof(BaselineDebugModeOSRInfo, stackAdjust)));
 
     // Emit two tails for the case of returning from a callVM and all other
     // cases, as the state we need to restore differs depending on the case.
     Label returnFromCallVM, end;
     EmitBranchIsReturningFromCallVM(masm, temp, &returnFromCallVM);
 
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -1830,17 +1830,17 @@ jit::JitActivation::registerIonFrameReco
     }
 
     return true;
 }
 
 jit::RInstructionResults*
 jit::JitActivation::maybeIonFrameRecovery(JitFrameLayout* fp)
 {
-    for (RInstructionResults* it = ionRecovery_.begin(); it != ionRecovery_.end(); ) {
+    for (RInstructionResults* it = ionRecovery_.begin(); it != ionRecovery_.end(); it++) {
         if (it->frame() == fp) {
             return it;
         }
     }
 
     return nullptr;
 }
 
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -11026,23 +11026,16 @@ nsIFrame::CreateOwnLayerIfNeeded(nsDispl
         MakeDisplayItem<nsDisplayOwnLayer>(aBuilder, this, aList, aBuilder->CurrentActiveScrolledRoot()));
     if (aCreatedContainerItem) {
       *aCreatedContainerItem = true;
     }
   }
 }
 
 bool
-nsIFrame::IsSelected() const
-{
-  return (GetContent() && GetContent()->IsSelectionDescendant()) ?
-    IsFrameSelected() : false;
-}
-
-bool
 nsIFrame::IsStackingContext(EffectSet* aEffectSet,
                             const nsStyleDisplay* aStyleDisplay,
                             const nsStylePosition* aStylePosition,
                             const nsStyleEffects* aStyleEffects,
                             bool aIsPositioned)
 {
   return HasOpacity(aEffectSet) ||
          IsTransformed(aStyleDisplay) ||
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -3227,17 +3227,20 @@ public:
   virtual LogicalSides
   GetLogicalSkipSides(const ReflowInput* aReflowInput = nullptr) const {
     return LogicalSides();
   }
 
   /**
    * @returns true if this frame is selected.
    */
-  bool IsSelected() const;
+  bool IsSelected() const {
+    return (GetContent() && GetContent()->IsSelectionDescendant()) ?
+      IsFrameSelected() : false;
+  }
 
   /**
    *  called to discover where this frame, or a parent frame has user-select style
    *  applied, which affects that way that it is selected.
    *
    *  @param aSelectStyle  out param. Returns the type of selection style found
    *                        (using values defined in nsStyleConsts.h).
    *
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -67,25 +67,25 @@ public:
   friend class nsDisplayTextGeometry;
   friend class nsDisplayText;
 
   // nsQueryFrame
   NS_DECL_QUERYFRAME
 
   // nsIFrame
   void BuildDisplayList(nsDisplayListBuilder* aBuilder,
-                        const nsDisplayListSet& aLists) override;
+                        const nsDisplayListSet& aLists) final;
 
   void Init(nsIContent* aContent,
             nsContainerFrame* aParent,
             nsIFrame* aPrevInFlow) override;
 
   void DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData) override;
 
-  nsresult GetCursor(const nsPoint& aPoint, nsIFrame::Cursor& aCursor) override;
+  nsresult GetCursor(const nsPoint& aPoint, nsIFrame::Cursor& aCursor) final;
 
   nsresult CharacterDataChanged(const CharacterDataChangeInfo&) final;
 
   nsTextFrame* GetPrevContinuation() const override { return nullptr; }
   nsTextFrame* GetNextContinuation() const final { return mNextContinuation; }
   void SetNextContinuation(nsIFrame* aNextContinuation) final
   {
     NS_ASSERTION(!aNextContinuation || Type() == aNextContinuation->Type(),
@@ -98,17 +98,17 @@ public:
       aNextContinuation->RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
     // Setting a non-fluid continuation might affect our flow length (they're
     // quite rare so we assume it always does) so we delete our cached value:
     if (GetContent()->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
       GetContent()->DeleteProperty(nsGkAtoms::flowlength);
       GetContent()->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
     }
   }
-  nsIFrame* GetNextInFlowVirtual() const override { return GetNextInFlow(); }
+  nsIFrame* GetNextInFlowVirtual() const final { return GetNextInFlow(); }
   nsTextFrame* GetNextInFlow() const
   {
     return mNextContinuation &&
                (mNextContinuation->GetStateBits() &
                 NS_FRAME_IS_FLUID_CONTINUATION)
              ? mNextContinuation
              : nullptr;
   }
@@ -151,30 +151,30 @@ public:
     // a whitespace is only contained by pseudo ruby frames, its style
     // context won't have SuppressLineBreak bit set.
     if (mozilla::RubyUtils::IsRubyContentBox(GetParent()->Type())) {
       return true;
     }
     return Style()->ShouldSuppressLineBreak();
   }
 
-  void InvalidateFrame(uint32_t aDisplayItemKey = 0, bool aRebuildDisplayItems = true) override;
+  void InvalidateFrame(uint32_t aDisplayItemKey = 0, bool aRebuildDisplayItems = true) final;
   void InvalidateFrameWithRect(const nsRect& aRect,
                                uint32_t aDisplayItemKey = 0,
-                               bool aRebuildDisplayItems = true) override;
+                               bool aRebuildDisplayItems = true) final;
 
 #ifdef DEBUG_FRAME_DUMP
   void List(FILE* out = stderr,
             const char* aPrefix = "",
-            uint32_t aFlags = 0) const override;
-  nsresult GetFrameName(nsAString& aResult) const override;
+            uint32_t aFlags = 0) const final;
+  nsresult GetFrameName(nsAString& aResult) const final;
   void ToCString(nsCString& aBuf, int32_t* aTotalContentLength) const;
 #endif
 
-  ContentOffsets CalcContentOffsetsFromFramePoint(const nsPoint& aPoint) override;
+  ContentOffsets CalcContentOffsetsFromFramePoint(const nsPoint& aPoint) final;
   ContentOffsets GetCharacterOffsetAtFramePoint(const nsPoint& aPoint);
 
   /**
    * This is called only on the primary text frame. It indicates that
    * the selection state of the given character range has changed.
    * Text in the range is unconditionally invalidated
    * (Selection::Repaint depends on this).
    * @param aSelected true if the selection has been added to the range,
@@ -182,67 +182,67 @@ public:
    * @param aType the type of selection added or removed
    */
   void SetSelectedRange(uint32_t aStart,
                         uint32_t aEnd,
                         bool aSelected,
                         SelectionType aSelectionType);
 
   FrameSearchResult PeekOffsetNoAmount(bool aForward,
-                                       int32_t* aOffset) override;
+                                       int32_t* aOffset) final;
   FrameSearchResult
   PeekOffsetCharacter(bool aForward,
                       int32_t* aOffset,
                       PeekOffsetCharacterOptions aOptions =
-                        PeekOffsetCharacterOptions()) override;
+                        PeekOffsetCharacterOptions()) final;
   FrameSearchResult PeekOffsetWord(bool aForward,
                                    bool aWordSelectEatSpace,
                                    bool aIsKeyboardSelect,
                                    int32_t* aOffset,
-                                   PeekWordState* aState) override;
+                                   PeekWordState* aState) final;
 
   nsresult CheckVisibility(nsPresContext* aContext,
                            int32_t aStartIndex,
                            int32_t aEndIndex,
                            bool aRecurse,
                            bool* aFinished,
-                           bool* _retval) override;
+                           bool* _retval) final;
 
   // Flags for aSetLengthFlags
   enum
   {
     ALLOW_FRAME_CREATION_AND_DESTRUCTION = 0x01
   };
 
   // Update offsets to account for new length. This may clear mTextRun.
   void SetLength(int32_t aLength,
                  nsLineLayout* aLineLayout,
                  uint32_t aSetLengthFlags = 0);
 
-  nsresult GetOffsets(int32_t& start, int32_t& end) const override;
+  nsresult GetOffsets(int32_t& start, int32_t& end) const final;
 
-  void AdjustOffsetsForBidi(int32_t start, int32_t end) override;
+  void AdjustOffsetsForBidi(int32_t start, int32_t end) final;
 
-  nsresult GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) override;
+  nsresult GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) final;
   nsresult GetCharacterRectsInRange(int32_t aInOffset,
                                     int32_t aLength,
-                                    nsTArray<nsRect>& aRects) override;
+                                    nsTArray<nsRect>& aRects) final;
 
   nsresult GetChildFrameContainingOffset(int32_t inContentOffset,
                                          bool inHint,
                                          int32_t* outFrameContentOffset,
-                                         nsIFrame** outChildFrame) override;
+                                         nsIFrame** outChildFrame) final;
 
-  bool IsVisibleInSelection(mozilla::dom::Selection* aSelection) override;
+  bool IsVisibleInSelection(mozilla::dom::Selection* aSelection) final;
 
-  bool IsEmpty() override;
-  bool IsSelfEmpty() override { return IsEmpty(); }
+  bool IsEmpty() final;
+  bool IsSelfEmpty() final { return IsEmpty(); }
   nscoord GetLogicalBaseline(mozilla::WritingMode aWritingMode) const final;
 
-  bool HasSignificantTerminalNewline() const override;
+  bool HasSignificantTerminalNewline() const final;
 
   /**
    * Returns true if this text frame is logically adjacent to the end of the
    * line.
    */
   bool IsAtEndOfLine() const;
 
   /**
@@ -250,51 +250,51 @@ public:
    * characters are present.
    */
   bool HasNoncollapsedCharacters() const
   {
     return (GetStateBits() & TEXT_HAS_NONCOLLAPSED_CHARACTERS) != 0;
   }
 
 #ifdef ACCESSIBILITY
-  mozilla::a11y::AccType AccessibleType() override;
+  mozilla::a11y::AccType AccessibleType() final;
 #endif
 
   float GetFontSizeInflation() const;
   bool IsCurrentFontInflation(float aInflation) const;
   bool HasFontSizeInflation() const
   {
     return (GetStateBits() & TEXT_HAS_FONT_INFLATION) != 0;
   }
   void SetFontSizeInflation(float aInflation);
 
-  void MarkIntrinsicISizesDirty() override;
-  nscoord GetMinISize(gfxContext* aRenderingContext) override;
-  nscoord GetPrefISize(gfxContext* aRenderingContext) override;
+  void MarkIntrinsicISizesDirty() final;
+  nscoord GetMinISize(gfxContext* aRenderingContext) final;
+  nscoord GetPrefISize(gfxContext* aRenderingContext) final;
   void AddInlineMinISize(gfxContext* aRenderingContext,
                          InlineMinISizeData* aData) override;
   void AddInlinePrefISize(gfxContext* aRenderingContext,
                           InlinePrefISizeData* aData) override;
   mozilla::LogicalSize ComputeSize(gfxContext* aRenderingContext,
                                    mozilla::WritingMode aWritingMode,
                                    const mozilla::LogicalSize& aCBSize,
                                    nscoord aAvailableISize,
                                    const mozilla::LogicalSize& aMargin,
                                    const mozilla::LogicalSize& aBorder,
                                    const mozilla::LogicalSize& aPadding,
-                                   ComputeSizeFlags aFlags) override;
-  nsRect ComputeTightBounds(DrawTarget* aDrawTarget) const override;
+                                   ComputeSizeFlags aFlags) final;
+  nsRect ComputeTightBounds(DrawTarget* aDrawTarget) const final;
   nsresult GetPrefWidthTightBounds(gfxContext* aContext,
                                    nscoord* aX,
-                                   nscoord* aXMost) override;
+                                   nscoord* aXMost) final;
   void Reflow(nsPresContext* aPresContext,
               ReflowOutput& aMetrics,
               const ReflowInput& aReflowInput,
-              nsReflowStatus& aStatus) override;
-  bool CanContinueTextRun() const override;
+              nsReflowStatus& aStatus) final;
+  bool CanContinueTextRun() const final;
   // Method that is called for a text frame that is logically
   // adjacent to the end of the line (i.e. followed only by empty text frames,
   // placeholders or inlines containing such).
   struct TrimOutput
   {
     // true if we trimmed some space or changed metrics in some other way.
     // In this case, we should call RecomputeOverflow on this frame.
     bool mChanged;
@@ -302,17 +302,17 @@ public:
     nscoord mDeltaWidth;
   };
   TrimOutput TrimTrailingWhiteSpace(DrawTarget* aDrawTarget);
   RenderedText GetRenderedText(
     uint32_t aStartOffset = 0,
     uint32_t aEndOffset = UINT32_MAX,
     TextOffsetType aOffsetType = TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
     TrailingWhitespace aTrimTrailingWhitespace =
-      TrailingWhitespace::TRIM_TRAILING_WHITESPACE) override;
+      TrailingWhitespace::TRIM_TRAILING_WHITESPACE) final;
 
   nsOverflowAreas RecomputeOverflow(nsIFrame* aBlockFrame);
 
   enum TextRunType
   {
     // Anything in reflow (but not intrinsic width calculation) or
     // painting should use the inflated text run (i.e., with font size
     // inflation applied).
@@ -539,17 +539,17 @@ public:
   void DrawEmphasisMarks(gfxContext* aContext,
                          mozilla::WritingMode aWM,
                          const mozilla::gfx::Point& aTextBaselinePt,
                          const mozilla::gfx::Point& aFramePt,
                          Range aRange,
                          const nscolor* aDecorationOverrideColor,
                          PropertyProvider* aProvider);
 
-  nscolor GetCaretColorAt(int32_t aOffset) override;
+  nscolor GetCaretColorAt(int32_t aOffset) final;
 
   int16_t GetSelectionStatus(int16_t* aSelectionFlags);
 
   int32_t GetContentOffset() const { return mContentOffset; }
   int32_t GetContentLength() const
   {
     NS_ASSERTION(GetContentEnd() - mContentOffset >= 0, "negative length");
     return GetContentEnd() - mContentOffset;
@@ -650,24 +650,24 @@ public:
                   DrawTarget* aDrawTarget,
                   ReflowOutput& aMetrics,
                   nsReflowStatus& aStatus);
 
   bool IsFloatingFirstLetterChild() const;
 
   bool IsInitialLetterChild() const;
 
-  bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override;
+  bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) final;
 
   void AssignJustificationGaps(const mozilla::JustificationAssignment& aAssign);
   mozilla::JustificationAssignment GetJustificationAssignment() const;
 
   uint32_t CountGraphemeClusters() const;
 
-  bool HasAnyNoncollapsedCharacters() override;
+  bool HasAnyNoncollapsedCharacters() final;
 
   /**
    * Call this after you have manually changed the text node contents without
    * notifying that change.  This behaves as if all the text contents changed.
    * (You should only use this for native anonymous content.)
    */
   void NotifyNativeAnonymousTextnodeChange(uint32_t aOldLength);
 
@@ -699,17 +699,17 @@ protected:
   // start.
   int32_t mContentLengthHint;
   nscoord mAscent;
 
   /**
    * Return true if the frame is part of a Selection.
    * Helper method to implement the public IsSelected() API.
    */
-  bool IsFrameSelected() const override;
+  bool IsFrameSelected() const final;
 
   mozilla::UniquePtr<SelectionDetails> GetSelectionDetails();
 
   void UnionAdditionalOverflow(nsPresContext* aPresContext,
                                nsIFrame* aBlock,
                                PropertyProvider& aProvider,
                                nsRect* aVisualOverflowRect,
                                bool aIncludeTextDecorations);
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -85,16 +85,17 @@
 #include "FrameLayerBuilder.h"
 #include "mozilla/EventStateManager.h"
 #include "nsCaret.h"
 #include "nsDOMTokenList.h"
 #include "nsCSSProps.h"
 #include "nsSVGMaskFrame.h"
 #include "nsTableCellFrame.h"
 #include "nsTableColFrame.h"
+#include "nsTextFrame.h"
 #include "nsSliderFrame.h"
 #include "ClientLayerManager.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "mozilla/layers/WebRenderLayerManager.h"
 #include "mozilla/layers/WebRenderMessages.h"
 #include "mozilla/layers/WebRenderScrollData.h"
 
@@ -9609,16 +9610,28 @@ nsCharClipDisplayItem::ComputeInvalidati
   if (mVisIStartEdge != geometry->mVisIStartEdge ||
       mVisIEndEdge != geometry->mVisIEndEdge ||
       !oldRect.IsEqualInterior(newRect) ||
       !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) {
     aInvalidRegion->Or(oldRect, newRect);
   }
 }
 
+bool
+nsCharClipDisplayItem::IsSelected() const
+{
+  if (mIsFrameSelected.isNothing()) {
+    MOZ_ASSERT((nsTextFrame*)do_QueryFrame(mFrame));
+    auto* f = static_cast<nsTextFrame*>(mFrame);
+    mIsFrameSelected.emplace(f->IsSelected());
+  }
+
+  return mIsFrameSelected.value();
+}
+
 nsDisplayEffectsBase::nsDisplayEffectsBase(
   nsDisplayListBuilder* aBuilder,
   nsIFrame* aFrame,
   nsDisplayList* aList,
   const ActiveScrolledRoot* aActiveScrolledRoot,
   bool aClearClipChain)
   : nsDisplayWrapList(aBuilder,
                       aFrame,
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -7793,24 +7793,17 @@ public:
   {
     DisplayItemType t = aItem->GetType();
     return (t == DisplayItemType::TYPE_TEXT ||
             t == DisplayItemType::TYPE_SVG_CHAR_CLIP)
              ? static_cast<nsCharClipDisplayItem*>(aItem)
              : nullptr;
   }
 
-  bool IsSelected() const
-  {
-    if (mIsFrameSelected.isNothing()) {
-      mIsFrameSelected.emplace(mFrame->IsSelected());
-    }
-
-    return mIsFrameSelected.value();
-  }
+  bool IsSelected() const;
 
   // Lengths measured from the visual inline start and end sides
   // (i.e. left and right respectively in horizontal writing modes,
   // regardless of bidi directionality; top and bottom in vertical modes).
   nscoord mVisIStartEdge;
   nscoord mVisIEndEdge;
   // Cached result of mFrame->IsSelected().  Only initialized when needed.
   mutable mozilla::Maybe<bool> mIsFrameSelected;
--- a/toolkit/components/antitracking/AntiTrackingCommon.cpp
+++ b/toolkit/components/antitracking/AntiTrackingCommon.cpp
@@ -403,16 +403,99 @@ CompareBaseDomains(nsIURI* aTrackingURI,
     LOG(("Can't get the base domain from parent principal"));
     return false;
   }
 
   return trackingBaseDomain.Equals(parentPrincipalBaseDomain,
                                    nsCaseInsensitiveCStringComparator());
 }
 
+class TemporaryAccessGrantObserver final : public nsIObserver
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  static void
+  Create(nsIPermissionManager* aPM,
+         nsIPrincipal* aPrincipal,
+         const nsACString& aType)
+  {
+    nsCOMPtr<nsITimer> timer;
+    RefPtr<TemporaryAccessGrantObserver> observer =
+      new TemporaryAccessGrantObserver(aPM, aPrincipal, aType);
+    nsresult rv =
+      NS_NewTimerWithObserver(getter_AddRefs(timer),
+                              observer,
+                              24 * 60 * 60 * 1000, // 24 hours
+                              nsITimer::TYPE_ONE_SHOT);
+
+    if (NS_SUCCEEDED(rv)) {
+      observer->SetTimer(timer);
+    } else {
+      timer->Cancel();
+    }
+  }
+
+  void SetTimer(nsITimer* aTimer)
+  {
+    mTimer = aTimer;
+    nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+    if (observerService) {
+      observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+    }
+  }
+
+private:
+  TemporaryAccessGrantObserver(nsIPermissionManager* aPM,
+                               nsIPrincipal* aPrincipal,
+                               const nsACString& aType)
+    : mPM(aPM)
+    , mPrincipal(aPrincipal)
+    , mType(aType)
+  {
+    MOZ_ASSERT(XRE_IsParentProcess(),
+               "Enforcing temporary access grant lifetimes can only be done in "
+               "the parent process");
+  }
+
+  ~TemporaryAccessGrantObserver() = default;
+
+private:
+  nsCOMPtr<nsITimer> mTimer;
+  nsCOMPtr<nsIPermissionManager> mPM;
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+  nsCString mType;
+};
+
+NS_IMPL_ISUPPORTS(TemporaryAccessGrantObserver, nsIObserver)
+
+NS_IMETHODIMP
+TemporaryAccessGrantObserver::Observe(nsISupports* aSubject,
+                                      const char* aTopic,
+                                      const char16_t* aData)
+{
+  if (strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0) {
+    Unused << mPM->RemoveFromPrincipal(mPrincipal, mType.get());
+  } else if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+    nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+    if (observerService) {
+      observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+    }
+    if (mTimer) {
+      mTimer->Cancel();
+      mTimer = nullptr;
+    }
+  }
+
+  return NS_OK;
+}
+
 } // anonymous
 
 /* static */ RefPtr<AntiTrackingCommon::StorageAccessGrantPromise>
 AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipal,
                                                          nsPIDOMWindowInner* aParentWindow,
                                                          StorageAccessGrantedReason aReason,
                                                          const AntiTrackingCommon::PerformFinalChecks& aPerformFinalChecks)
 {
@@ -434,21 +517,21 @@ AntiTrackingCommon::AddFirstPartyStorage
 
   LOG(("Adding a first-party storage exception for %s...",
        NS_ConvertUTF16toUTF8(origin).get()));
 
   if (StaticPrefs::network_cookie_cookieBehavior() !=
         nsICookieService::BEHAVIOR_REJECT_TRACKER) {
     LOG(("Disabled by network.cookie.cookieBehavior pref (%d), bailing out early",
          StaticPrefs::network_cookie_cookieBehavior()));
-    return StorageAccessGrantPromise::CreateAndResolve(true, __func__);
+    return StorageAccessGrantPromise::CreateAndResolve(eAllowOnAnySite, __func__);
   }
 
   if (CheckContentBlockingAllowList(aParentWindow)) {
-    return StorageAccessGrantPromise::CreateAndResolve(true, __func__);
+    return StorageAccessGrantPromise::CreateAndResolve(eAllowOnAnySite, __func__);
   }
 
   nsCOMPtr<nsIPrincipal> topLevelStoragePrincipal;
   nsCOMPtr<nsIURI> trackingURI;
   nsAutoCString trackingOrigin;
   nsCOMPtr<nsIPrincipal> trackingPrincipal;
 
   RefPtr<nsGlobalWindowInner> parentWindow = nsGlobalWindowInner::Cast(aParentWindow);
@@ -529,17 +612,17 @@ AntiTrackingCommon::AddFirstPartyStorage
   if (!pwin) {
     LOG(("Couldn't get the top window"));
     return StorageAccessGrantPromise::CreateAndReject(false, __func__);
   }
 
   auto storePermission = [pwin, parentWindow, origin, trackingOrigin,
                           trackingPrincipal, trackingURI, topInnerWindow,
                           topLevelStoragePrincipal, aReason]
-                         (bool aAnySite) -> RefPtr<StorageAccessGrantPromise> {
+                         (int aAllowMode) -> RefPtr<StorageAccessGrantPromise> {
     NS_ConvertUTF16toUTF8 grantedOrigin(origin);
 
     nsAutoCString permissionKey;
     CreatePermissionKey(trackingOrigin, grantedOrigin, permissionKey);
 
     // Let's store the permission in the current parent window.
     topInnerWindow->SaveStorageAccessGranted(permissionKey);
 
@@ -557,21 +640,22 @@ AntiTrackingCommon::AddFirstPartyStorage
     if (XRE_IsParentProcess()) {
       LOG(("Saving the permission: trackingOrigin=%s, grantedOrigin=%s",
            trackingOrigin.get(), grantedOrigin.get()));
 
       return SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(topLevelStoragePrincipal,
                                                                         trackingPrincipal,
                                                                         trackingOrigin,
                                                                         grantedOrigin,
-                                                                        aAnySite)
+                                                                        aAllowMode)
         ->Then(GetCurrentThreadSerialEventTarget(), __func__,
                [] (FirstPartyStorageAccessGrantPromise::ResolveOrRejectValue&& aValue) {
                  if (aValue.IsResolve()) {
-                   return StorageAccessGrantPromise::CreateAndResolve(NS_SUCCEEDED(aValue.ResolveValue()), __func__);
+                   return StorageAccessGrantPromise::CreateAndResolve(NS_SUCCEEDED(aValue.ResolveValue()) ?
+                                                                      eAllowOnAnySite : eAllow, __func__);
                  }
                  return StorageAccessGrantPromise::CreateAndReject(false, __func__);
                });
     }
 
     ContentChild* cc = ContentChild::GetSingleton();
     MOZ_ASSERT(cc);
 
@@ -579,17 +663,17 @@ AntiTrackingCommon::AddFirstPartyStorage
          trackingOrigin.get(), grantedOrigin.get()));
 
     // This is not really secure, because here we have the content process sending
     // the request of storing a permission.
     return cc->SendFirstPartyStorageAccessGrantedForOrigin(IPC::Principal(topLevelStoragePrincipal),
                                                            IPC::Principal(trackingPrincipal),
                                                            trackingOrigin,
                                                            grantedOrigin,
-                                                           aAnySite)
+                                                           aAllowMode)
       ->Then(GetCurrentThreadSerialEventTarget(), __func__,
              [] (const ContentChild::FirstPartyStorageAccessGrantedForOriginPromise::ResolveOrRejectValue& aValue) {
                if (aValue.IsResolve()) {
                  return StorageAccessGrantPromise::CreateAndResolve(aValue.ResolveValue(), __func__);
                }
                return StorageAccessGrantPromise::CreateAndReject(false, __func__);
              });
   };
@@ -607,19 +691,22 @@ AntiTrackingCommon::AddFirstPartyStorage
   return storePermission(false);
 }
 
 /* static */ RefPtr<mozilla::AntiTrackingCommon::FirstPartyStorageAccessGrantPromise>
 AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(nsIPrincipal* aParentPrincipal,
                                                                                nsIPrincipal* aTrackingPrincipal,
                                                                                const nsCString& aTrackingOrigin,
                                                                                const nsCString& aGrantedOrigin,
-                                                                               bool aAnySite)
+                                                                               int aAllowMode)
 {
   MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(aAllowMode == eAllow ||
+             aAllowMode == eAllowAutoGrant ||
+             aAllowMode == eAllowOnAnySite);
 
   nsCOMPtr<nsIURI> parentPrincipalURI;
   Unused << aParentPrincipal->GetURI(getter_AddRefs(parentPrincipalURI));
   LOG_SPEC(("Saving a first-party storage permission on %s for trackingOrigin=%s grantedOrigin=%s",
             _spec, aTrackingOrigin.get(), aGrantedOrigin.get()), parentPrincipalURI);
 
   if (NS_WARN_IF(!aParentPrincipal)) {
     // The child process is sending something wrong. Let's ignore it.
@@ -635,17 +722,17 @@ AntiTrackingCommon::SaveFirstPartyStorag
 
   // Remember that this pref is stored in seconds!
   uint32_t expirationType = nsIPermissionManager::EXPIRE_TIME;
   uint32_t expirationTime =
     StaticPrefs::privacy_restrict3rdpartystorage_expiration() * 1000;
   int64_t when = (PR_Now() / PR_USEC_PER_MSEC) + expirationTime;
 
   nsresult rv;
-  if (aAnySite) {
+  if (aAllowMode == eAllowOnAnySite) {
     uint32_t privateBrowsingId = 0;
     rv = aTrackingPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
     if (!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) {
       // If we are coming from a private window, make sure to store a session-only
       // permission which won't get persisted to disk.
       expirationType = nsIPermissionManager::EXPIRE_SESSION;
       when = 0;
     }
@@ -654,32 +741,39 @@ AntiTrackingCommon::SaveFirstPartyStorag
          expirationTime));
 
     rv = pm->AddFromPrincipal(aTrackingPrincipal, "cookie",
                               nsICookiePermission::ACCESS_ALLOW,
                               expirationType, when);
   } else {
     uint32_t privateBrowsingId = 0;
     rv = aParentPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
-    if (!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) {
-      // If we are coming from a private window, make sure to store a session-only
-      // permission which won't get persisted to disk.
+    if ((!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) ||
+        (aAllowMode == eAllowAutoGrant)) {
+      // If we are coming from a private window or are automatically granting a
+      // permission, make sure to store a session-only permission which won't
+      // get persisted to disk.
       expirationType = nsIPermissionManager::EXPIRE_SESSION;
       when = 0;
     }
 
     nsAutoCString type;
     CreatePermissionKey(aTrackingOrigin, aGrantedOrigin, type);
 
     LOG(("Computed permission key: %s, expiry: %u, proceeding to save in the permission manager",
          type.get(), expirationTime));
 
     rv = pm->AddFromPrincipal(aParentPrincipal, type.get(),
                               nsIPermissionManager::ALLOW_ACTION,
                               expirationType, when);
+
+    if (NS_SUCCEEDED(rv) && (aAllowMode == eAllowAutoGrant)) {
+      // Make sure temporary access grants do not survive more than 24 hours.
+      TemporaryAccessGrantObserver::Create(pm, aParentPrincipal, type);
+    }
   }
   Unused << NS_WARN_IF(NS_FAILED(rv));
 
   LOG(("Result: %s", NS_SUCCEEDED(rv) ? "success" : "failure"));
   return FirstPartyStorageAccessGrantPromise::CreateAndResolve(rv, __func__);
 }
 
 // static
--- a/toolkit/components/antitracking/AntiTrackingCommon.h
+++ b/toolkit/components/antitracking/AntiTrackingCommon.h
@@ -72,35 +72,41 @@ public:
   IsFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipal);
 
   enum StorageAccessGrantedReason
   {
     eStorageAccessAPI,
     eOpenerAfterUserInteraction,
     eOpener
   };
+  enum StorageAccessPromptChoices
+  {
+    eAllow,
+    eAllowAutoGrant,
+    eAllowOnAnySite
+  };
 
   // Grant the permission for aOrigin to have access to the first party storage.
   // This method can handle 2 different scenarios:
   // - aParentWindow is a 3rd party context, it opens an aOrigin window and the
   //   user interacts with it. We want to grant the permission at the
   //   combination: top-level + aParentWindow + aOrigin.
   //   Ex: example.net loads an iframe tracker.com, which opens a popup
   //   tracker.prg and the user interacts with it. tracker.org is allowed if
   //   loaded by tracker.com when loaded by example.net.
   // - aParentWindow is a first party context and a 3rd party resource (probably
   //   becuase of a script) opens a popup and the user interacts with it. We
   //   want to grant the permission for the 3rd party context to have access to
   //   the first party stoage when loaded in aParentWindow.
   //   Ex: example.net import tracker.com/script.js which does opens a popup and
   //   the user interacts with it. tracker.com is allowed when loaded by
   //   example.net.
-  typedef MozPromise<bool, bool, true> StorageAccessFinalCheckPromise;
+  typedef MozPromise<int, bool, true> StorageAccessFinalCheckPromise;
   typedef std::function<RefPtr<StorageAccessFinalCheckPromise>()> PerformFinalChecks;
-  typedef MozPromise<bool, bool, true> StorageAccessGrantPromise;
+  typedef MozPromise<int, bool, true> StorageAccessGrantPromise;
   static MOZ_MUST_USE RefPtr<StorageAccessGrantPromise>
   AddFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipal,
                                        nsPIDOMWindowInner* aParentWindow,
                                        StorageAccessGrantedReason aReason,
                                        const PerformFinalChecks& aPerformFinalChecks = nullptr);
 
   // Returns true if the permission passed in is a storage access permission
   // for the passed in principal argument.
@@ -115,17 +121,17 @@ public:
 
   // For IPC only.
   typedef MozPromise<nsresult, bool, true> FirstPartyStorageAccessGrantPromise;
   static RefPtr<FirstPartyStorageAccessGrantPromise>
   SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(nsIPrincipal* aPrincipal,
                                                              nsIPrincipal* aTrackingPrinciapl,
                                                              const nsCString& aParentOrigin,
                                                              const nsCString& aGrantedOrigin,
-                                                             bool aAnySite);
+                                                             int aAllowMode);
 
   enum ContentBlockingAllowListPurpose {
     eStorageChecks,
     eTrackingProtection,
     eTrackingAnnotations,
   };
 
   // Check whether a top window URI is on the content blocking allow list.
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessDoorHanger.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessDoorHanger.js
@@ -1,40 +1,56 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 const CHROME_BASE = "chrome://mochitests/content/browser/browser/modules/test/browser/";
 Services.scriptloader.loadSubScript(CHROME_BASE + "head.js", this);
 /* import-globals-from ../../../../../browser/modules/test/browser/head.js */
 
-async function testDoorHanger(choice) {
-  info(`Running doorhanger test with choice #${choice}`);
+const BLOCK = 0;
+const ALLOW = 1;
+const ALLOW_ON_ANY_SITE = 2;
+
+async function testDoorHanger(choice, showPrompt, topPage, maxConcurrent) {
+  info(`Running doorhanger test with choice #${choice}, showPrompt: ${showPrompt} and ` +
+       `topPage: ${topPage}, maxConcurrent: ${maxConcurrent}`);
+
+  if (!showPrompt) {
+    is(choice, ALLOW, "When not showing a prompt, we can only auto-grant");
+  }
 
   await SpecialPowers.flushPrefEnv();
   await SpecialPowers.pushPrefEnv({"set": [
     ["browser.contentblocking.allowlist.annotations.enabled", true],
     ["browser.contentblocking.allowlist.storage.enabled", true],
     [ContentBlocking.prefIntroCount, ContentBlocking.MAX_INTROS],
+    ["dom.storage_access.auto_grants", true],
     ["dom.storage_access.enabled", true],
+    ["dom.storage_access.max_concurrent_auto_grants", maxConcurrent],
     ["dom.storage_access.prompt.testing", false],
     ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER],
     ["privacy.trackingprotection.enabled", false],
     ["privacy.trackingprotection.pbmode.enabled", false],
     ["privacy.trackingprotection.annotate_channels", true],
     ["privacy.restrict3rdpartystorage.userInteractionRequiredForHosts", "tracking.example.com,tracking.example.org"],
   ]});
 
   await UrlClassifierTestUtils.addTestTrackers();
 
-  let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
+  let tab = BrowserTestUtils.addTab(gBrowser, topPage);
   gBrowser.selectedTab = tab;
 
   let browser = gBrowser.getBrowserForTab(tab);
   await BrowserTestUtils.browserLoaded(browser);
 
   async function runChecks() {
+    // We need to repeat this constant here since runChecks is stringified
+    // and sent to the content process.
+    const BLOCK = 0;
+
     await new Promise(resolve => {
       addEventListener("message", function onMessage(e) {
         if (e.data.startsWith("choice:")) {
           window.choice = e.data.split(":")[1];
           removeEventListener("message", onMessage);
           resolve();
         }
       }, false);
@@ -56,72 +72,78 @@ async function testDoorHanger(choice) {
       is(text, "cookie-not-present", "We should not have cookies");
     });
 
     is(document.cookie, "", "Still no cookies for me");
 
     /* import-globals-from storageAccessAPIHelpers.js */
     await callRequestStorageAccess();
 
-    if (choice == 0) {
+    if (choice == BLOCK) {
       // We've said no, so cookies are still blocked
       is(document.cookie, "", "Still no cookies for me");
       document.cookie = "name=value";
       is(document.cookie, "", "No cookies for me");
     } else {
       // We've said yes, so cookies are allowed now
       is(document.cookie, "", "No cookies for me");
       document.cookie = "name=value";
       is(document.cookie, "name=value", "I have the cookies!");
     }
   }
 
+  let permChanged = TestUtils.topicObserved("perm-changed",
+    (subject, data) => {
+      let result;
+      if (choice == ALLOW) {
+        result = subject &&
+                 subject.QueryInterface(Ci.nsIPermission)
+                        .type.startsWith("3rdPartyStorage^") &&
+                 subject.principal.origin == (new URL(topPage)).origin &&
+                 data == "added";
+      } else if (choice == ALLOW_ON_ANY_SITE) {
+        result = subject &&
+                 subject.QueryInterface(Ci.nsIPermission)
+                        .type == "cookie" &&
+                 subject.principal.origin == "https://tracking.example.org" &&
+                 data == "added";
+      }
+      return result;
+    });
   let shownPromise =
     BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
   shownPromise.then(async _ => {
-    let notification =
-      PopupNotifications.getNotification("storage-access", browser);
+    if (topPage != gBrowser.currentURI.spec) {
+      return;
+    }
+    ok(showPrompt, "We shouldn't show the prompt when we don't intend to");
+    let notification = await new Promise(function poll(resolve) {
+      let notification =
+        PopupNotifications.getNotification("storage-access", browser);
+      if (notification) {
+        resolve(notification);
+        return;
+      }
+      setTimeout(poll, 10);
+    });
     Assert.ok(notification, "Should have gotten the notification");
 
-    let permChanged = TestUtils.topicObserved("perm-changed",
-      (subject, data) => {
-        let result;
-        if (choice == 1) {
-          result = subject &&
-                   subject.QueryInterface(Ci.nsIPermission)
-                          .type.startsWith("3rdPartyStorage^") &&
-                   subject.principal.origin == "http://example.net" &&
-                   data == "added";
-        } else if (choice == 2) {
-          result = subject &&
-                   subject.QueryInterface(Ci.nsIPermission)
-                          .type == "cookie" &&
-                   subject.principal.origin == "https://tracking.example.org" &&
-                   data == "added";
-        }
-        return result;
-      });
-    if (choice == 0) {
+    if (choice == BLOCK) {
       await clickMainAction();
-    } else if (choice == 1) {
+    } else if (choice == ALLOW) {
       await clickSecondaryAction(choice - 1);
-    } else if (choice == 2) {
+    } else if (choice == ALLOW_ON_ANY_SITE) {
       await clickSecondaryAction(choice - 1);
     }
-    if (choice != 0) {
+    if (choice != BLOCK) {
       await permChanged;
     }
   });
 
-  let url;
-  if (choice == 2) {
-    url = TEST_3RD_PARTY_PAGE + "?disableWaitUntilPermission";
-  } else {
-    url = TEST_3RD_PARTY_PAGE;
-  }
+  let url = TEST_3RD_PARTY_PAGE + "?disableWaitUntilPermission";
   let ct = ContentTask.spawn(browser,
                              { page: url,
                                callback: runChecks.toString(),
                                choice,
                              },
                              async function(obj) {
     await new content.Promise(resolve => {
       let ifr = content.document.createElement("iframe");
@@ -154,32 +176,150 @@ async function testDoorHanger(choice) {
 
         ok(false, "Unknown message");
       });
 
       content.document.body.appendChild(ifr);
       ifr.src = obj.page;
     });
   });
-  await Promise.all([ct, shownPromise]);
+  if (showPrompt) {
+    await Promise.all([ct, shownPromise]);
+  } else {
+    await Promise.all([ct, permChanged]);
+  }
 
   BrowserTestUtils.removeTab(tab);
 
   UrlClassifierTestUtils.cleanupTestTrackers();
 }
 
+async function preparePermissionsFromOtherSites(topPage) {
+  info("Faking permissions from other sites");
+  let type = "3rdPartyStorage^https://tracking.example.org";
+  let permission = Services.perms.ALLOW_ACTION;
+  let expireType = Services.perms.EXPIRE_SESSION;
+  if (topPage == TEST_TOP_PAGE) {
+    // For the first page, don't do anything
+  } else if (topPage == TEST_TOP_PAGE_2) {
+    // For the second page, only add the permission from the first page
+    Services.perms.add(Services.io.newURI(TEST_DOMAIN),
+                       type,
+                       permission,
+                       expireType,
+                       0);
+  } else if (topPage == TEST_TOP_PAGE_3) {
+    // For the third page, add the permissions from the first two pages
+    Services.perms.add(Services.io.newURI(TEST_DOMAIN),
+                       type,
+                       permission,
+                       expireType,
+                       0);
+    Services.perms.add(Services.io.newURI(TEST_DOMAIN_2),
+                       type,
+                       permission,
+                       expireType,
+                       0);
+  } else if (topPage == TEST_TOP_PAGE_4) {
+    // For the fourth page, add the permissions from the first three pages
+    Services.perms.add(Services.io.newURI(TEST_DOMAIN),
+                       type,
+                       permission,
+                       expireType,
+                       0);
+    Services.perms.add(Services.io.newURI(TEST_DOMAIN_2),
+                       type,
+                       permission,
+                       expireType,
+                       0);
+    Services.perms.add(Services.io.newURI(TEST_DOMAIN_3),
+                       type,
+                       permission,
+                       expireType,
+                       0);
+  } else if (topPage == TEST_TOP_PAGE_5) {
+    // For the fifth page, add the permissions from the first four pages
+    Services.perms.add(Services.io.newURI(TEST_DOMAIN),
+                       type,
+                       permission,
+                       expireType,
+                       0);
+    Services.perms.add(Services.io.newURI(TEST_DOMAIN_2),
+                       type,
+                       permission,
+                       expireType,
+                       0);
+    Services.perms.add(Services.io.newURI(TEST_DOMAIN_3),
+                       type,
+                       permission,
+                       expireType,
+                       0);
+    Services.perms.add(Services.io.newURI(TEST_DOMAIN_4),
+                       type,
+                       permission,
+                       expireType,
+                       0);
+  } else if (topPage == TEST_TOP_PAGE_6) {
+    // For the sixth page, add the permissions from the first five pages
+    Services.perms.add(Services.io.newURI(TEST_DOMAIN),
+                       type,
+                       permission,
+                       expireType,
+                       0);
+    Services.perms.add(Services.io.newURI(TEST_DOMAIN_2),
+                       type,
+                       permission,
+                       expireType,
+                       0);
+    Services.perms.add(Services.io.newURI(TEST_DOMAIN_3),
+                       type,
+                       permission,
+                       expireType,
+                       0);
+    Services.perms.add(Services.io.newURI(TEST_DOMAIN_4),
+                       type,
+                       permission,
+                       expireType,
+                       0);
+    Services.perms.add(Services.io.newURI(TEST_DOMAIN_5),
+                       type,
+                       permission,
+                       expireType,
+                       0);
+  } else {
+    ok(false, "Unexpected top page: " + topPage);
+  }
+}
+
 async function cleanUp() {
   info("Cleaning up.");
   await new Promise(resolve => {
     Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
   });
 }
 
-async function runRound(n) {
-  await testDoorHanger(n);
+async function runRound(topPage, showPrompt, maxConcurrent) {
+  if (showPrompt) {
+    await preparePermissionsFromOtherSites(topPage);
+    await testDoorHanger(BLOCK, showPrompt, topPage, maxConcurrent);
+    await cleanUp();
+    await preparePermissionsFromOtherSites(topPage);
+    await testDoorHanger(ALLOW, showPrompt, topPage, maxConcurrent);
+    await cleanUp();
+    await preparePermissionsFromOtherSites(topPage);
+    await testDoorHanger(ALLOW_ON_ANY_SITE, showPrompt, topPage, maxConcurrent);
+  } else {
+    await preparePermissionsFromOtherSites(topPage);
+    await testDoorHanger(ALLOW, showPrompt, topPage, maxConcurrent);
+  }
   await cleanUp();
 }
 
 add_task(async function() {
-  await runRound(0);
-  await runRound(1);
-  await runRound(2);
+  await runRound(TEST_TOP_PAGE, false, 1);
+  await runRound(TEST_TOP_PAGE_2, true, 1);
+  await runRound(TEST_TOP_PAGE, false, 5);
+  await runRound(TEST_TOP_PAGE_2, false, 5);
+  await runRound(TEST_TOP_PAGE_3, false, 5);
+  await runRound(TEST_TOP_PAGE_4, false, 5);
+  await runRound(TEST_TOP_PAGE_5, false, 5);
+  await runRound(TEST_TOP_PAGE_6, true, 5);
 });
--- a/toolkit/components/antitracking/test/browser/head.js
+++ b/toolkit/components/antitracking/test/browser/head.js
@@ -1,17 +1,27 @@
 const TEST_DOMAIN = "http://example.net";
+const TEST_DOMAIN_2 = "http://xn--exmple-cua.test";
+const TEST_DOMAIN_3 = "https://xn--hxajbheg2az3al.xn--jxalpdlp";
+const TEST_DOMAIN_4 = "http://prefixexample.com";
+const TEST_DOMAIN_5 = "http://test";
+const TEST_DOMAIN_6 = "http://mochi.test:8888";
 const TEST_3RD_PARTY_DOMAIN = "https://tracking.example.org";
 const TEST_3RD_PARTY_DOMAIN_TP = "https://tracking.example.com";
 const TEST_4TH_PARTY_DOMAIN = "http://not-tracking.example.com";
 const TEST_ANOTHER_3RD_PARTY_DOMAIN = "https://another-tracking.example.net";
 
 const TEST_PATH = "/browser/toolkit/components/antitracking/test/browser/";
 
 const TEST_TOP_PAGE = TEST_DOMAIN + TEST_PATH + "page.html";
+const TEST_TOP_PAGE_2 = TEST_DOMAIN_2 + TEST_PATH + "page.html";
+const TEST_TOP_PAGE_3 = TEST_DOMAIN_3 + TEST_PATH + "page.html";
+const TEST_TOP_PAGE_4 = TEST_DOMAIN_4 + TEST_PATH + "page.html";
+const TEST_TOP_PAGE_5 = TEST_DOMAIN_5 + TEST_PATH + "page.html";
+const TEST_TOP_PAGE_6 = TEST_DOMAIN_6 + TEST_PATH + "page.html";
 const TEST_EMBEDDER_PAGE = TEST_DOMAIN + TEST_PATH + "embedder.html";
 const TEST_POPUP_PAGE = TEST_DOMAIN + TEST_PATH + "popup.html";
 const TEST_3RD_PARTY_PAGE = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdParty.html";
 const TEST_3RD_PARTY_PAGE_WO = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartyWO.html";
 const TEST_3RD_PARTY_PAGE_UI = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartyUI.html";
 const TEST_3RD_PARTY_PAGE_WITH_SVG = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartySVG.html";
 const TEST_4TH_PARTY_PAGE = TEST_4TH_PARTY_DOMAIN + TEST_PATH + "3rdParty.html";
 const TEST_ANOTHER_3RD_PARTY_PAGE = TEST_ANOTHER_3RD_PARTY_DOMAIN + TEST_PATH + "3rdParty.html";
--- a/toolkit/recordreplay/MiddlemanCall.cpp
+++ b/toolkit/recordreplay/MiddlemanCall.cpp
@@ -167,22 +167,26 @@ ProcessMiddlemanCall(const char* aInputD
     call->DecodeInput(inputStream);
 
     const Redirection& redirection = GetRedirection(call->mCallId);
     MOZ_RELEASE_ASSERT(redirection.mMiddlemanCall);
 
     CallArguments arguments;
     call->mArguments.CopyTo(&arguments);
 
+    bool skipCall;
     {
       MiddlemanCallContext cx(call, &arguments, MiddlemanCallPhase::MiddlemanInput);
       redirection.mMiddlemanCall(cx);
+      skipCall = cx.mSkipCallInMiddleman;
     }
 
-    RecordReplayInvokeCall(redirection.mBaseFunction, &arguments);
+    if (!skipCall) {
+      RecordReplayInvokeCall(redirection.mBaseFunction, &arguments);
+    }
 
     {
       MiddlemanCallContext cx(call, &arguments, MiddlemanCallPhase::MiddlemanOutput);
       redirection.mMiddlemanCall(cx);
     }
 
     call->mArguments.CopyFrom(&arguments);
     call->EncodeOutput(outputStream);
--- a/toolkit/recordreplay/MiddlemanCall.h
+++ b/toolkit/recordreplay/MiddlemanCall.h
@@ -167,16 +167,20 @@ struct MiddlemanCallContext
   MiddlemanCallPhase mPhase;
 
   // During the ReplayPreface or ReplayInput phases, whether capturing input
   // data has failed. In such cases the call cannot be sent to the middleman
   // and, if the thread has diverged from the recording, an unhandled
   // divergence and associated rewind will occur.
   bool mFailed;
 
+  // This can be set in the MiddlemanInput phase to avoid performing the call
+  // in the middleman process.
+  bool mSkipCallInMiddleman;
+
   // During the ReplayInput phase, this can be used to fill in any middleman
   // calls whose output the current one depends on.
   InfallibleVector<MiddlemanCall*>* mDependentCalls;
 
   // Streams of data that can be accessed during the various phases. Streams
   // need to be read or written from at the same points in the phases which use
   // them, so that callbacks operating on these streams can be composed without
   // issues.
@@ -193,17 +197,18 @@ struct MiddlemanCallContext
 
   // During the ReplayOutput phase, this is set if the call was made sometime
   // in the past and pointers referred to in the arguments may no longer be
   // valid.
   bool mReplayOutputIsOld;
 
   MiddlemanCallContext(MiddlemanCall* aCall, CallArguments* aArguments, MiddlemanCallPhase aPhase)
     : mCall(aCall), mArguments(aArguments), mPhase(aPhase),
-      mFailed(false), mDependentCalls(nullptr), mReplayOutputIsOld(false)
+      mFailed(false), mSkipCallInMiddleman(false),
+      mDependentCalls(nullptr), mReplayOutputIsOld(false)
   {
     switch (mPhase) {
     case MiddlemanCallPhase::ReplayPreface:
       mPrefaceStream.emplace(&mCall->mPreface);
       break;
     case MiddlemanCallPhase::ReplayInput:
       mPrefaceStream.emplace(mCall->mPreface.begin(), mCall->mPreface.length());
       mInputStream.emplace(&mCall->mInput);
@@ -424,16 +429,25 @@ static inline void
 MM_StackArgumentData(MiddlemanCallContext& aCx)
 {
   if (aCx.AccessPreface()) {
     auto stack = aCx.mArguments->StackAddress<0>();
     aCx.ReadOrWritePrefaceBytes(stack, ByteSize);
   }
 }
 
+// Avoid calling a function in the middleman process.
+static inline void
+MM_SkipInMiddleman(MiddlemanCallContext& aCx)
+{
+  if (aCx.mPhase == MiddlemanCallPhase::MiddlemanInput) {
+    aCx.mSkipCallInMiddleman = true;
+  }
+}
+
 static inline void
 MM_NoOp(MiddlemanCallContext& aCx)
 {
 }
 
 template <MiddlemanCallFn Fn0,
           MiddlemanCallFn Fn1,
           MiddlemanCallFn Fn2 = MM_NoOp,
--- a/toolkit/recordreplay/ProcessRedirectDarwin.cpp
+++ b/toolkit/recordreplay/ProcessRedirectDarwin.cpp
@@ -194,16 +194,17 @@ MM_ObjCInput(MiddlemanCallContext& aCx, 
   MOZ_RELEASE_ASSERT(aCx.AccessInput());
 
   if (aCx.mPhase == MiddlemanCallPhase::ReplayInput) {
     // Try to determine where this object came from.
 
     // List of the Objective C classes which messages might be sent to directly.
     static const char* gStaticClasses[] = {
       // Standard classes.
+      "NSAutoreleasePool",
       "NSBezierPath",
       "NSButtonCell",
       "NSColor",
       "NSComboBoxCell",
       "NSDictionary",
       "NSGraphicsContext",
       "NSFont",
       "NSFontManager",
@@ -1126,41 +1127,33 @@ RR_objc_msgSend(Stream& aEvents, CallArg
     aEvents.RecordOrReplayValue(&len);
     if (IsReplaying()) {
       rval = NewLeakyArray<char>(len + 1);
     }
     aEvents.RecordOrReplayBytes(rval, len + 1);
   }
 }
 
-static PreambleResult
-MiddlemanPreamble_objc_msgSend(CallArguments* aArguments)
+static void
+MM_Alloc(MiddlemanCallContext& aCx)
 {
-  auto obj = aArguments->Arg<0, id>();
-  auto message = aArguments->Arg<1, const char*>();
-
-  // Fake object value which allows null checks in the caller to pass.
-  static const size_t FakeId = 1;
-
-  // Ignore uses of NSAutoreleasePool after diverging from the recording.
-  // These are not performed in the middleman because the middleman has its
-  // own autorelease pool, and because the middleman can process calls from
-  // multiple threads which will cause these messages to behave differently.
-  // release messages are also ignored, as for CFRelease.
-  if ((!strcmp(message, "alloc") && obj == (id) objc_lookUpClass("NSAutoreleasePool")) ||
-      (!strcmp(message, "init") && obj == (id) FakeId) ||
-      !strcmp(message, "drain") ||
-      !strcmp(message, "release")) {
-    // Fake a return value in case the caller null checks it.
-    aArguments->Rval<size_t>() = 1;
-    return PreambleResult::Veto;
+  if (aCx.mPhase == MiddlemanCallPhase::MiddlemanInput) {
+    // Refuse to allocate NSAutoreleasePools in the middleman: the order in
+    // which middleman calls happen does not guarantee the pools will be
+    // created and released in LIFO order, as the pools require. Instead,
+    // allocate an NSString instead so we have something to release later.
+    // Messages sent to NSAutoreleasePools will all be skipped in the
+    // middleman, so no one should notice this subterfuge.
+    auto& obj = aCx.mArguments->Arg<0, id>();
+    if (obj == (id) objc_lookUpClass("NSAutoreleasePool")) {
+      obj = (id) objc_lookUpClass("NSString");
+    }
   }
 
-  // Other messages will be handled by MM_objc_msgSend.
-  return PreambleResult::Redirect;
+  MM_CreateCFTypeRval(aCx);
 }
 
 static void
 MM_PerformSelector(MiddlemanCallContext& aCx)
 {
   MM_CString<2>(aCx);
   MM_CFTypeArg<3>(aCx);
 
@@ -1250,25 +1243,29 @@ struct ObjCMessageInfo
   bool mUpdatesObject;
 };
 
 // All Objective C messages that can be called in the middleman, and hooks for
 // capturing any inputs and outputs other than the object, message, and scalar
 // arguments / return values.
 static ObjCMessageInfo gObjCMiddlemanCallMessages[] = {
   // Generic
-  { "alloc", MM_CreateCFTypeRval },
+  { "alloc", MM_Alloc },
   { "init", MM_AutoreleaseCFTypeRval },
   { "performSelector:withObject:", MM_PerformSelector },
+  { "release", MM_SkipInMiddleman },
   { "respondsToSelector:", MM_CString<2> },
 
   // NSAppearance
   { "_drawInRect:context:options:",
     MM_Compose<MM_StackArgumentData<sizeof(CGRect)>, MM_CFTypeArg<2>, MM_CFTypeArg<3>> },
 
+  // NSAutoreleasePool
+  { "drain", MM_SkipInMiddleman },
+
   // NSArray
   { "count" },
   { "objectAtIndex:", MM_AutoreleaseCFTypeRval },
 
   // NSBezierPath
   { "addClip", MM_NoOp, true },
   { "bezierPathWithRoundedRect:xRadius:yRadius:", MM_AutoreleaseCFTypeRval },
 
@@ -2083,17 +2080,17 @@ static SystemRedirection gSystemRedirect
   /////////////////////////////////////////////////////////////////////////////
 
   { "class_getClassMethod", RR_ScalarRval },
   { "class_getInstanceMethod", RR_ScalarRval },
   { "method_exchangeImplementations" },
   { "objc_autoreleasePoolPop" },
   { "objc_autoreleasePoolPush", RR_ScalarRval },
   { "objc_msgSend",
-    RR_objc_msgSend, Preamble_objc_msgSend, MM_objc_msgSend, MiddlemanPreamble_objc_msgSend },
+    RR_objc_msgSend, Preamble_objc_msgSend, MM_objc_msgSend },
 
   /////////////////////////////////////////////////////////////////////////////
   // Cocoa and CoreFoundation library functions
   /////////////////////////////////////////////////////////////////////////////
 
   { "AcquireFirstMatchingEventInQueue", RR_ScalarRval },
   { "CFArrayAppendValue" },
   { "CFArrayCreate", RR_ScalarRval, nullptr, MM_CFArrayCreate },