Merge mozilla-inbound to mozilla-central. a=merge
authorDaniel Varga <dvarga@mozilla.com>
Thu, 29 Nov 2018 11:54:56 +0200
changeset 448716 d99bf39f5223abe554a629a947eee344c3e9e29e
parent 448681 c302586458e0a418e972652dfc4351380066c319 (current diff)
parent 448715 e54c1b4fa9c653c03d90600d7dd7d04279c1f889 (diff)
child 448717 4244b98363097285c9c6f846dfe866f664ac3546
child 448744 408677f045b29f5aedb53f27d14eed5908b06519
push id73973
push userdvarga@mozilla.com
push dateThu, 29 Nov 2018 10:00:56 +0000
treeherderautoland@4244b9836309 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone65.0a1
first release with
nightly linux32
d99bf39f5223 / 65.0a1 / 20181129095546 / files
nightly linux64
d99bf39f5223 / 65.0a1 / 20181129095546 / files
nightly mac
d99bf39f5223 / 65.0a1 / 20181129095546 / files
nightly win32
d99bf39f5223 / 65.0a1 / 20181129095546 / files
nightly win64
d99bf39f5223 / 65.0a1 / 20181129095546 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-inbound to mozilla-central. a=merge
browser/app/profile/firefox.js
browser/modules/BrowserUsageTelemetry.jsm
browser/modules/PermissionUI.jsm
browser/modules/test/browser/browser.ini
browser/modules/test/browser/browser_PermissionUI.js
browser/modules/test/browser/browser_PermissionUI_prompts.js
dom/base/StorageAccessPermissionRequest.cpp
dom/base/StorageAccessPermissionRequest.h
dom/base/nsDocument.cpp
toolkit/components/antitracking/AntiTrackingCommon.cpp
toolkit/components/antitracking/AntiTrackingCommon.h
toolkit/components/antitracking/test/browser/browser_storageAccessDoorHanger.js
toolkit/components/antitracking/test/browser/head.js
--- 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 },