Merge mozilla-central to mozilla-inbound
authorarthur.iakab <aiakab@mozilla.com>
Sat, 27 Apr 2019 01:07:28 +0300
changeset 530451 f31fa29b2a08aa51ed1a917444b9780483523111
parent 530450 b7c7366733c4e59bfece960b105faba216f80c1a (current diff)
parent 530352 b13f2b24ae625d16fdeeb61cdec10978c3c75638 (diff)
child 530452 9eeeb4f28ddc43ee8b15b13ccc2ceb21b1af7bbb
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound
browser/config/mozconfigs/win32/mingw32
browser/config/mozconfigs/win32/mingw32-debug
devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_system_addons.js
devtools/shared/client/source-client.js
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -127,17 +127,17 @@ support-files =
 [browser_sessionStorage.js]
 [browser_sessionStorage_size.js]
 [browser_sizemodeBeforeMinimized.js]
 [browser_tab_label_during_restore.js]
 [browser_swapDocShells.js]
 [browser_switch_remoteness.js]
 run-if = e10s
 [browser_upgrade_backup.js]
-skip-if = debug || ((os == 'linux') && asan) || (verify && debug && (os == 'mac')) # Bug 1435394 disabled on Linux, OSX and Windows
+skip-if = debug || asan || (verify && debug && os == 'mac') # Bug 1435394 disabled on Linux, OSX and Windows
 [browser_windowRestore_perwindowpb.js]
 [browser_248970_b_perwindowpb.js]
 # Disabled because of leaks.
 # Re-enabling and rewriting this test is tracked in bug 936919.
 skip-if = true
 [browser_339445.js]
 [browser_345898.js]
 [browser_350525.js]
deleted file mode 100755
--- a/browser/config/mozconfigs/win32/mingw32
+++ /dev/null
@@ -1,60 +0,0 @@
-# Sets:
-#    MOZ_AUTOMATION flags
-#    SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE - shouldn't be used?
-#    TOOLTOOL_DIR
-#    MAKECAB - shouldn't be used?
-. "$topsrcdir/build/mozconfig.win-common"
-
-# MinGW does not have (or need) makecab
-unset MAKECAB
-
-# Sets:
-#  build/mozconfig.common
-#    AUTOCLOBBER=1
-#    --enable-crashreporter
-#    --enable-release
-#    LLVM_CONFIG
-#    MOZ_REQUIRE_SIGNING
-#    --enable-js-shell
-#  build/mozconfig.automation
-#    MOZ_AUTOMATION_ flags
-#  build/mozconfig.rust
-#    TOOLTOOL_DIR
-#    RUSTC
-#    CARGO
-. "$topsrcdir/browser/config/mozconfigs/common"
-
-# MinGW Stuff
-ac_add_options --target=i686-w64-mingw32
-ac_add_options --with-toolchain-prefix=i686-w64-mingw32-
-
-# GCC compiling for Windows exposes a lot of warnings. We are tracking them in Bug 1394433
-ac_add_options --disable-warnings-as-errors
-
-# Temporary config settings until we get these working on mingw
-ac_add_options --disable-accessibility # https://sourceforge.net/p/mingw-w64/bugs/648/
-
-# Long story
-ac_add_options --disable-stylo # Bug 1390583
-
-# These aren't supported on mingw at this time
-ac_add_options --disable-webrtc # Bug 1393901
-ac_add_options --disable-maintenance-service
-
-# Find our toolchain
-CC="$TOOLTOOL_DIR/mingw32/bin/i686-w64-mingw32-gcc"
-CXX="$TOOLTOOL_DIR/mingw32/bin/i686-w64-mingw32-g++"
-
-# We want to make sure we use binutils and other binaries in the tooltool
-# package.
-mk_add_options "export PATH=$TOOLTOOL_DIR/mingw32/bin:$TOOLTOOL_DIR/wine/bin:$TOOLTOOL_DIR/upx/bin:$TOOLTOOL_DIR/fxc2/bin:$PATH"
-
-LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$TOOLTOOL_DIR/mingw32/lib64
-mk_add_options "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH"
-
-# Do not include the visual studio related mozconfigs of course
-
-ac_add_options --with-branding=browser/branding/nightly
-
-. "$topsrcdir/build/mozconfig.common.override"
-. "$topsrcdir/build/mozconfig.cache"
deleted file mode 100755
--- a/browser/config/mozconfigs/win32/mingw32-debug
+++ /dev/null
@@ -1,4 +0,0 @@
-. "$topsrcdir/browser/config/mozconfigs/win32/mingw32"
-
-ac_add_options --enable-debug
-ac_add_options --disable-optimize
\ No newline at end of file
--- a/browser/config/mozconfigs/win32/mingwclang
+++ b/browser/config/mozconfigs/win32/mingwclang
@@ -29,16 +29,19 @@ export MOZ_PACKAGE_JSSHELL=1
 
 # MinGW Stuff
 ac_add_options --target=i686-w64-mingw32
 ac_add_options --with-toolchain-prefix=i686-w64-mingw32-
 
 ac_add_options --disable-warnings-as-errors
 MOZ_COPY_PDBS=1
 
+# This replicates Tor's configuration
+ac_add_options --enable-proxy-bypass-protection
+
 # These aren't supported on mingw at this time
 ac_add_options --disable-maintenance-service
 ac_add_options --disable-webrtc # Bug 1393901
 ac_add_options --disable-geckodriver # Bug 1489320
 
 # Find our toolchain
 HOST_CC="$TOOLTOOL_DIR/clang/bin/clang"
 HOST_CXX="$TOOLTOOL_DIR/clang/bin/clang++"
--- a/browser/config/mozconfigs/win32/mingwclang-debug
+++ b/browser/config/mozconfigs/win32/mingwclang-debug
@@ -1,6 +1,5 @@
 MOZ_AUTOMATION_L10N_CHECK=0
 
 . "$topsrcdir/browser/config/mozconfigs/win32/mingwclang"
 
 ac_add_options --enable-debug
-ac_add_options --disable-optimize
\ No newline at end of file
--- a/browser/config/mozconfigs/win64/mingwclang
+++ b/browser/config/mozconfigs/win64/mingwclang
@@ -29,16 +29,19 @@ export MOZ_PACKAGE_JSSHELL=1
 
 # MinGW Stuff
 ac_add_options --target=x86_64-w64-mingw32
 ac_add_options --with-toolchain-prefix=x86_64-w64-mingw32-
 
 ac_add_options --disable-warnings-as-errors
 MOZ_COPY_PDBS=1
 
+# This replicates Tor's configuration
+ac_add_options --enable-proxy-bypass-protection
+
 # These aren't supported on mingw at this time
 ac_add_options --disable-maintenance-service
 ac_add_options --disable-webrtc # Bug 1393901
 ac_add_options --disable-geckodriver # Bug 1489320
 
 # Find our toolchain
 HOST_CC="$TOOLTOOL_DIR/clang/bin/clang"
 HOST_CXX="$TOOLTOOL_DIR/clang/bin/clang++"
--- a/browser/config/mozconfigs/win64/mingwclang-debug
+++ b/browser/config/mozconfigs/win64/mingwclang-debug
@@ -1,6 +1,5 @@
 MOZ_AUTOMATION_L10N_CHECK=0
 
 . "$topsrcdir/browser/config/mozconfigs/win64/mingwclang"
 
 ac_add_options --enable-debug
-ac_add_options --disable-optimize
\ No newline at end of file
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -197,27 +197,38 @@ button > hbox > label {
   align-items: center;
   justify-content: space-between;
 }
 
 .header[hidden=true] {
   display: none;
 }
 
+/* All panes */
+
+.info-icon,
+.androidIcon,
+.iOSIcon,
+#updateSettingCrossUserWarning,
+.extension-controlled > description {
+  -moz-context-properties: fill;
+  fill: currentColor;
+}
+
 /* General Pane */
 
 #isDefaultLabel {
   font-weight: 600;
 }
 
 .info-panel,
 .extension-controlled {
   margin-top: 18px !important;
   margin-bottom: 18px !important;
-  background: var(--grey-20);
+  background: var(--in-content-warning-container);
   border-radius: 5px;
   padding-inline-end: 10px;
 }
 
 .info-panel > description,
 .extension-controlled > description {
   line-height: 16px;
   background: url(chrome://browser/skin/identity-icon.svg) 10px 14px no-repeat;
@@ -436,16 +447,21 @@ button > hbox > label {
 }
 
 #historyButtons {
   display: flex;
   flex-direction: column;
   justify-content: space-between;
 }
 
+#sanitizeEverythingWarningBox {
+  background-color: var(--in-content-box-background);
+  border: 1px solid var(--in-content-box-border-color);
+}
+
 #blockCookies,
 #keepRow {
   margin-top: 1.5em;
 }
 
 /* Collapse the non-active vboxes in decks to use only the height the
    active vbox needs */
 #historyPane:not([selectedIndex="1"]) > #historyDontRememberPane,
@@ -663,18 +679,16 @@ button > hbox > label {
 
 .androidIcon,
 .iOSIcon {
   margin-inline-start: 2px;
   margin-inline-end: 4px;
   width: 20px;
   height: 20px;
   vertical-align: text-bottom;
-  -moz-context-properties: fill;
-  fill: currentColor;
 }
 
 #updateDeck > hbox > label {
   margin-inline-end: 5px ! important;
 }
 
 .update-throbber {
   width: 16px;
@@ -723,26 +737,24 @@ image.update-throbber {
 
 #policies-container,
 #searchInput {
   min-height: 32px;
   margin: 20px 0 30px 0px;
 }
 
 #policies-container {
-  background-color: #ededf0;
+  background-color: var(--in-content-warning-container);
   padding: 0px 8px;
   margin-inline-end: 16px;
   border-radius: 2px;
 }
 
 .info-icon {
   list-style-image: url("chrome://browser/skin/identity-icon.svg");
-  fill: currentColor;
-  -moz-context-properties: fill;
   width: 16px;
   height: 16px;
   margin-top: calc((32px - 16px) / 2);
 }
 
 .sticky-container {
   position: sticky;
   background-color: var(--in-content-page-background);
@@ -923,17 +935,17 @@ menulist[indicator=true] > menupopup men
   padding-block-end: 8px;
   margin-block-end: 17px;
 }
 
 #updateSettingCrossUserWarning {
   padding-inline-start: 30px;
   margin-block-start: 20px;
   line-height: 20px;
-  background-image: url("chrome://global/skin/icons/info.svg");
+  background-image: url("chrome://browser/skin/identity-icon.svg");
   background-position-x: left 2px;
   background-position-y: top 2px;
   background-size: 16px 16px;
   background-repeat: no-repeat;
 }
 
 #updateSettingCrossUserWarning:-moz-locale-dir(rtl) {
   background-position-x: right 2px;
--- a/browser/themes/shared/incontentprefs/privacy.css
+++ b/browser/themes/shared/incontentprefs/privacy.css
@@ -49,17 +49,20 @@
 #contentBlockingFingerprintersCheckbox[checked] > .checkbox-label-box {
   list-style-image: url("chrome://browser/skin/controlcenter/fingerprinters-disabled.svg");
 }
 
 .content-blocking-icon,
 .permission-icon,
 .content-blocking-category .checkbox-label-box,
 .extra-information-label > image,
-.arrowhead {
+.arrowhead,
+.content-blocking-info-image,
+.reload-tabs-button,
+.content-blocking-warning-image {
   -moz-context-properties: fill;
   fill: currentColor;
 }
 
 .content-blocking-icon[disabled] {
   fill: GrayText;
 }
 
@@ -176,18 +179,16 @@
 .content-blocking-warning.reload-tabs .content-blocking-info-image {
   list-style-image: url(chrome://browser/skin/identity-icon.svg);
   margin-top: 5px;
   margin-inline-end: 8px;
   margin-inline-start: 4px;
 }
 
 .reload-tabs-button {
-  -moz-context-properties: fill;
-  fill: currentColor;
   max-height: 30px;
   min-height: 30px;
   padding: 0 20px;
   list-style-image: url("chrome://browser/skin/reload.svg");
   background-color: var(--blue-50);
 }
 
 .reload-tabs-button:not([disabled="true"]):hover {
@@ -274,18 +275,16 @@
 
 .content-blocking-fingerprinters-image {
   list-style-image: url("chrome://browser/skin/controlcenter/fingerprinters-disabled.svg");
   margin-inline-end: 5px;
 }
 
 .content-blocking-warning-image {
   list-style-image: url("chrome://global/skin/icons/warning.svg");
-  -moz-context-properties: fill;
-  fill: currentColor;
   margin-inline-end: 8px;
   margin-inline-start: 4px;
 }
 
 #blockCookiesMenu,
 #trackingProtectionMenu {
   margin: 0;
 }
--- a/devtools/client/aboutdebugging-new/src/actions/debug-targets.js
+++ b/devtools/client/aboutdebugging-new/src/actions/debug-targets.js
@@ -182,19 +182,22 @@ function requestExtensions() {
     const clientWrapper = getCurrentClient(getState().runtimes);
 
     try {
       const isIconDataURLRequired = runtime.type !== RUNTIMES.THIS_FIREFOX;
       const addons =
         await clientWrapper.listAddons({ iconDataURL: isIconDataURLRequired });
       let extensions = addons.filter(a => a.debuggable);
 
-      // Filter out system addons unless the dedicated preference is set to true.
-      if (!getState().ui.showSystemAddons) {
-        extensions = extensions.filter(e => !e.isSystem);
+      // Filter out hidden & system addons unless the dedicated preference is set to true.
+      if (!getState().ui.showHiddenAddons) {
+        // System addons should normally also have the hidden flag. However on DevTools
+        // side, `hidden` is not available on FF67 servers or older. Check both flags for
+        // backward compatibility.
+        extensions = extensions.filter(e => !e.isSystem && !e.hidden);
       }
 
       if (runtime.type !== RUNTIMES.THIS_FIREFOX) {
         // manifestURL can only be used when debugging local addons, remove this
         // information for the extension data.
         extensions.forEach(extension => {
           extension.manifestURL = null;
         });
--- a/devtools/client/aboutdebugging-new/src/components/App.css
+++ b/devtools/client/aboutdebugging-new/src/components/App.css
@@ -46,17 +46,17 @@
   padding-block-start: var(--app-top-padding);
   padding-block-end: var(--app-bottom-padding);
 
   /* we want to scroll only the main content, not the sidebar */
   overflow-y: auto;
 
   /* padding will give space for card shadow to appear and
      margin will correct the alignment */
-  margin-inline: calc(var(--card-shadow-blur-radius) * -1);
+  margin-inline-start: calc(var(--card-shadow-blur-radius) * -1);
   padding-inline: var(--card-shadow-blur-radius);
 }
 
 .page {
   max-width: var(--page-width);
   font-size: var(--body-20-font-size);
   font-weight: var(--body-20-font-weight);
 }
--- a/devtools/client/aboutdebugging-new/src/constants.js
+++ b/devtools/client/aboutdebugging-new/src/constants.js
@@ -100,18 +100,18 @@ const PAGE_TYPES = {
   CONNECT: "connect",
 };
 
 const PREFERENCES = {
   // Preference that drives the display of the "Tabs" category on This Firefox.
   LOCAL_TAB_DEBUGGING_ENABLED: "devtools.aboutdebugging.local-tab-debugging",
   // Preference that drives the display of the "Processes" debug target category.
   PROCESS_DEBUGGING_ENABLED: "devtools.aboutdebugging.process-debugging",
-  // Preference that drives the display of system addons in about:debugging.
-  SHOW_SYSTEM_ADDONS: "devtools.aboutdebugging.showSystemAddons",
+  // Preference that drives the display of hidden & system addons in about:debugging.
+  SHOW_HIDDEN_ADDONS: "devtools.aboutdebugging.showHiddenAddons",
 };
 
 const RUNTIME_PREFERENCE = {
   CHROME_DEBUG_ENABLED: "devtools.chrome.enabled",
   CONNECTION_PROMPT: "devtools.debugger.prompt-connection",
   PERMANENT_PRIVATE_BROWSING: "browser.privatebrowsing.autostart",
   REMOTE_DEBUG_ENABLED: "devtools.debugger.remote-enabled",
   SERVICE_WORKERS_ENABLED: "dom.serviceWorkers.enabled",
--- a/devtools/client/aboutdebugging-new/src/create-store.js
+++ b/devtools/client/aboutdebugging-new/src/create-store.js
@@ -44,14 +44,14 @@ function configureStore() {
                                      waitUntilService);
 
   return createStore(rootReducer, initialState, middleware);
 }
 
 function getUiState() {
   const collapsibilities = getDebugTargetCollapsibilities();
   const locations = getNetworkLocations();
-  const showSystemAddons = Services.prefs.getBoolPref(PREFERENCES.SHOW_SYSTEM_ADDONS,
+  const showHiddenAddons = Services.prefs.getBoolPref(PREFERENCES.SHOW_HIDDEN_ADDONS,
     false);
-  return new UiState(locations, collapsibilities, showSystemAddons);
+  return new UiState(locations, collapsibilities, showHiddenAddons);
 }
 
 exports.configureStore = configureStore;
--- a/devtools/client/aboutdebugging-new/src/reducers/ui-state.js
+++ b/devtools/client/aboutdebugging-new/src/reducers/ui-state.js
@@ -14,26 +14,26 @@ const {
   SHOW_PROFILER_DIALOG,
   TEMPORARY_EXTENSION_INSTALL_FAILURE,
   TEMPORARY_EXTENSION_INSTALL_SUCCESS,
   USB_RUNTIMES_SCAN_START,
   USB_RUNTIMES_SCAN_SUCCESS,
 } = require("../constants");
 
 function UiState(locations = [], debugTargetCollapsibilities = {},
-                 showSystemAddons = false) {
+                 showHiddenAddons = false) {
   return {
     adbAddonStatus: null,
     debugTargetCollapsibilities,
     isAdbReady: false,
     isScanningUsb: false,
     networkLocations: locations,
     selectedPage: null,
     showProfilerDialog: false,
-    showSystemAddons,
+    showHiddenAddons,
     temporaryInstallError: null,
   };
 }
 
 function uiReducer(state = UiState(), action) {
   switch (action.type) {
     case ADB_ADDON_STATUS_UPDATED: {
       const { adbAddonStatus } = action;
--- a/devtools/client/aboutdebugging-new/test/browser/browser.ini
+++ b/devtools/client/aboutdebugging-new/test/browser/browser.ini
@@ -1,15 +1,15 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 prefs =
-  # showSystemAddons has different values depending on the build flags,
-  # ensure consistent test behavior by always setting this to false.
-  devtools.aboutdebugging.showSystemAddons=false
+  # showHiddenAddons has different values depending on the build flags, ensure consistent
+  # test behavior by always setting it to false.
+  devtools.aboutdebugging.showHiddenAddons=false
 support-files =
   head.js
   helper-adb.js
   helper-addons.js
   helper-collapsibilities.js
   helper-mocks.js
   helper-real-usb.js
   helper-serviceworker.js
@@ -99,17 +99,17 @@ skip-if = debug || asan # Frequent inter
 [browser_aboutdebugging_sidebar_network_runtimes.js]
 [browser_aboutdebugging_sidebar_usb_runtime.js]
 [browser_aboutdebugging_sidebar_usb_runtime_connect.js]
 [browser_aboutdebugging_sidebar_usb_runtime_refresh.js]
 [browser_aboutdebugging_sidebar_usb_runtime_select.js]
 [browser_aboutdebugging_sidebar_usb_status.js]
 [browser_aboutdebugging_sidebar_usb_unavailable_runtime.js]
 [browser_aboutdebugging_sidebar_usb_unplugged_device.js]
-[browser_aboutdebugging_system_addons.js]
+[browser_aboutdebugging_hidden_addons.js]
 [browser_aboutdebugging_tab_favicons.js]
 [browser_aboutdebugging_telemetry_basic.js]
 [browser_aboutdebugging_telemetry_inspect.js]
 [browser_aboutdebugging_telemetry_navigate.js]
 [browser_aboutdebugging_telemetry_runtime_actions.js]
 [browser_aboutdebugging_telemetry_runtime_connected_details.js]
 [browser_aboutdebugging_telemetry_runtime_updates.js]
 [browser_aboutdebugging_telemetry_runtime_updates_multi.js]
rename from devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_system_addons.js
rename to devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_hidden_addons.js
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_system_addons.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_hidden_addons.js
@@ -1,56 +1,52 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Test that system addons are only displayed when the showSystemAddons preference is
-// true.
+// Test that system and hidden addons are only displayed when the showSystemAddons
+// preferences is true.
 
 const SYSTEM_ADDON =
-  createAddonData({ id: "system", name: "System Addon", isSystem: true });
-const INSTALLED_ADDON =
-  createAddonData({ id: "installed", name: "Installed Addon", isSystem: false });
+  createAddonData({ id: "system", name: "System Addon", isSystem: true, hidden: true  });
+const HIDDEN_ADDON =
+  createAddonData({ id: "hidden", name: "Hidden Addon", isSystem: false, hidden: true });
+const NORMAL_ADDON =
+  createAddonData({ id: "normal", name: "Normal Addon", isSystem: false, hidden: false });
+
+add_task(async function testShowSystemAddonsTrue() {
+  info("Test with showHiddenAddons set to true");
+  await testAddonsDisplay(true);
 
-add_task(async function testShowSystemAddonsFalse() {
+  info("Test with showHiddenAddons set to false");
+  await testAddonsDisplay(false);
+});
+
+async function testAddonsDisplay(showHidden) {
   const thisFirefoxClient = setupThisFirefoxMock();
-  thisFirefoxClient.listAddons = () => ([SYSTEM_ADDON, INSTALLED_ADDON]);
+  thisFirefoxClient.listAddons = () => ([SYSTEM_ADDON, HIDDEN_ADDON, NORMAL_ADDON]);
 
-  info("Hide system addons in aboutdebugging via preference");
-  await pushPref("devtools.aboutdebugging.showSystemAddons", false);
+  info("Set showHiddenAddons to " + showHidden);
+  await pushPref("devtools.aboutdebugging.showHiddenAddons", showHidden);
 
   const { document, tab, window } = await openAboutDebugging();
   await selectThisFirefoxPage(document, window.AboutDebugging.store);
 
   const hasSystemAddon = !!findDebugTargetByText("System Addon", document);
-  const hasInstalledAddon = !!findDebugTargetByText("Installed Addon", document);
-  ok(!hasSystemAddon, "System addon is hidden when system addon pref is false");
-  ok(hasInstalledAddon, "Installed addon is displayed when system addon pref is false");
+  const hasHiddenAddon = !!findDebugTargetByText("Hidden Addon", document);
+  const hasInstalledAddon = !!findDebugTargetByText("Normal Addon", document);
+  is(hasSystemAddon, showHidden,
+    "System addon display is correct when showHiddenAddons is " + showHidden);
+  is(hasHiddenAddon, showHidden,
+    "Hidden addon display is correct when showHiddenAddons is " + showHidden);
+  ok(hasInstalledAddon, "Installed addon is always displayed");
 
   await removeTab(tab);
-});
-
-add_task(async function testShowSystemAddonsTrue() {
-  const thisFirefoxClient = setupThisFirefoxMock();
-  thisFirefoxClient.listAddons = () => ([SYSTEM_ADDON, INSTALLED_ADDON]);
-
-  info("Show system addons in aboutdebugging via preference");
-  await pushPref("devtools.aboutdebugging.showSystemAddons", true);
-
-  const { document, tab, window } = await openAboutDebugging();
-  await selectThisFirefoxPage(document, window.AboutDebugging.store);
-
-  const hasSystemAddon = !!findDebugTargetByText("System Addon", document);
-  const hasInstalledAddon = !!findDebugTargetByText("Installed Addon", document);
-  ok(hasSystemAddon, "System addon is displayed when system addon pref is true");
-  ok(hasInstalledAddon, "Installed addon is displayed when system addon pref is true");
-
-  await removeTab(tab);
-});
+}
 
 // Create a basic mock for this-firefox client, and setup a runtime-client-factory mock
 // to return our mock client when needed.
 function setupThisFirefoxMock() {
   const runtimeClientFactoryMock = createRuntimeClientFactoryMock();
   const thisFirefoxClient = createThisFirefoxClientMock();
   runtimeClientFactoryMock.createClientForRuntime = runtime => {
     const { RUNTIMES } = require("devtools/client/aboutdebugging-new/src/constants");
@@ -66,19 +62,20 @@ function setupThisFirefoxMock() {
     disableRuntimeClientFactoryMock();
   });
 
   return thisFirefoxClient;
 }
 
 // Create basic addon data as the DebuggerClient would return it (debuggable and non
 // temporary).
-function createAddonData({ id, name, isSystem }) {
+function createAddonData({ id, name, isSystem, hidden }) {
   return {
     actor: `actorid-${id}`,
+    hidden,
     iconURL: `moz-extension://${id}/icon-url.png`,
     id,
     manifestURL: `moz-extension://${id}/manifest-url.json`,
     name,
     isSystem,
     temporarilyInstalled: false,
     debuggable: true,
   };
--- a/devtools/client/aboutdebugging/components/addons/Panel.js
+++ b/devtools/client/aboutdebugging/components/addons/Panel.js
@@ -20,17 +20,17 @@ loader.lazyRequireGetter(this, "Debugger
   "devtools/shared/client/debugger-client", true);
 
 const Strings = Services.strings.createBundle(
   "chrome://devtools/locale/aboutdebugging.properties");
 
 const ExtensionIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
 const CHROME_ENABLED_PREF = "devtools.chrome.enabled";
 const REMOTE_ENABLED_PREF = "devtools.debugger.remote-enabled";
-const SYSTEM_ENABLED_PREF = "devtools.aboutdebugging.showSystemAddons";
+const SYSTEM_ENABLED_PREF = "devtools.aboutdebugging.showHiddenAddons";
 const WEB_EXT_URL = "https://developer.mozilla.org/Add-ons" +
                     "/WebExtensions/Getting_started_with_web-ext";
 
 class AddonsPanel extends Component {
   static get propTypes() {
     return {
       client: PropTypes.instanceOf(DebuggerClient).isRequired,
       connect: PropTypes.object,
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_info.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_info.js
@@ -1,14 +1,14 @@
 "use strict";
 
 const { Preferences } = ChromeUtils.import("resource://gre/modules/Preferences.jsm");
 
 const UUID_REGEX = /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/;
-const SHOW_SYSTEM_ADDONS_PREF = "devtools.aboutdebugging.showSystemAddons";
+const SHOW_SYSTEM_ADDONS_PREF = "devtools.aboutdebugging.showHiddenAddons";
 
 function testFilePath(container, expectedFilePath) {
   // Verify that the path to the install location is shown next to its label.
   const filePath = container.querySelector(".file-path");
   ok(filePath, "file path is in DOM");
   ok(filePath.textContent.endsWith(expectedFilePath), "file path is set correctly");
   is(filePath.previousElementSibling.textContent, "Location", "file path has label");
 }
--- a/devtools/client/debugger/src/actions/file-search.js
+++ b/devtools/client/debugger/src/actions/file-search.js
@@ -92,17 +92,22 @@ export function updateSearchResults(
       matches,
       matchIndex,
       count: matches.length,
       index: characterIndex
     }
   };
 }
 
-export function searchContents(cx: Context, query: string, editor: Object) {
+export function searchContents(
+  cx: Context,
+  query: string,
+  editor: Object,
+  focusFirstResult?: boolean = true
+) {
   return async ({ getState, dispatch }: ThunkArgs) => {
     const modifiers = getFileSearchModifiers(getState());
     const selectedSourceWithContent = getSelectedSourceWithContent(getState());
 
     if (
       !editor ||
       !selectedSourceWithContent ||
       !selectedSourceWithContent.content ||
@@ -126,17 +131,17 @@ export function searchContents(cx: Conte
     if (selectedContent.type === "wasm") {
       text = renderWasmText(selectedSource.id, selectedContent).join("\n");
     } else {
       text = selectedContent.value;
     }
 
     const matches = await getMatches(query, text, _modifiers);
 
-    const res = find(ctx, query, true, _modifiers);
+    const res = find(ctx, query, true, _modifiers, focusFirstResult);
     if (!res) {
       return;
     }
 
     const { ch, line } = res;
 
     dispatch(updateSearchResults(cx, ch, line, matches));
   };
--- a/devtools/client/debugger/src/actions/ui.js
+++ b/devtools/client/debugger/src/actions/ui.js
@@ -54,17 +54,17 @@ export function setActiveSearch(activeSe
 }
 
 export function updateActiveFileSearch(cx: Context) {
   return ({ dispatch, getState }: ThunkArgs) => {
     const isFileSearchOpen = getActiveSearch(getState()) === "file";
     const fileSearchQuery = getFileSearchQuery(getState());
     if (isFileSearchOpen && fileSearchQuery) {
       const editor = getEditor();
-      dispatch(searchContents(cx, fileSearchQuery, editor));
+      dispatch(searchContents(cx, fileSearchQuery, editor, false));
     }
   };
 }
 
 export function toggleFrameworkGrouping(toggleValue: boolean) {
   return ({ dispatch, getState }: ThunkArgs) => {
     dispatch({
       type: "TOGGLE_FRAMEWORK_GROUPING",
--- a/devtools/client/debugger/src/actions/utils/middleware/log.js
+++ b/devtools/client/debugger/src/actions/utils/middleware/log.js
@@ -2,16 +2,17 @@
  * 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/>. */
 /* global window */
 
 // @flow
 
 import { isTesting } from "devtools-environment";
 import type { ThunkArgs } from "../../types";
+import { prefs } from "../../../utils/prefs";
 
 const blacklist = [
   "ADD_BREAKPOINT_POSITIONS",
   "SET_SYMBOLS",
   "OUT_OF_SCOPE_LOCATIONS",
   "MAP_SCOPES",
   "MAP_FRAMES",
   "ADD_SCOPES",
@@ -90,17 +91,17 @@ function serializeAction(action) {
 /**
  * A middleware that logs all actions coming through the system
  * to the console.
  */
 export function log({ dispatch, getState }: ThunkArgs) {
   return (next: any) => (action: any) => {
     const asyncMsg = !action.status ? "" : `[${action.status}]`;
 
-    if (isTesting()) {
+    if (isTesting() && prefs.logActions) {
       // $FlowIgnore
       dump(
         `[ACTION] ${action.type} ${asyncMsg} - ${serializeAction(action)}\n`
       );
     } else {
       console.log(action, asyncMsg);
     }
 
--- a/devtools/client/debugger/src/client/firefox/commands.js
+++ b/devtools/client/debugger/src/client/firefox/commands.js
@@ -176,18 +176,18 @@ function breakOnNext(thread: string): Pr
   return lookupThreadClient(thread).breakOnNext();
 }
 
 async function sourceContents({
   actor,
   thread
 }: SourceActor): Promise<{| source: any, contentType: ?string |}> {
   const sourceThreadClient = lookupThreadClient(thread);
-  const sourceClient = sourceThreadClient.source({ actor });
-  const { source, contentType } = await sourceClient.source();
+  const sourceFront = sourceThreadClient.source({ actor });
+  const { source, contentType } = await sourceFront.source();
   return { source, contentType };
 }
 
 function setXHRBreakpoint(path: string, method: string) {
   return threadClient.setXHRBreakpoint(path, method);
 }
 
 function removeXHRBreakpoint(path: string, method: string) {
@@ -360,21 +360,21 @@ async function pauseOnExceptions(
   );
 }
 
 async function blackBox(
   sourceActor: SourceActor,
   isBlackBoxed: boolean,
   range?: Range
 ): Promise<*> {
-  const sourceClient = threadClient.source({ actor: sourceActor.actor });
+  const sourceFront = threadClient.source({ actor: sourceActor.actor });
   if (isBlackBoxed) {
-    await sourceClient.unblackBox(range);
+    await sourceFront.unblackBox(range);
   } else {
-    await sourceClient.blackBox(range);
+    await sourceFront.blackBox(range);
   }
 }
 
 async function setSkipPausing(shouldSkip: boolean) {
   await threadClient.skipBreakpoints(shouldSkip);
   await forEachWorkerThread(thread => thread.skipBreakpoints(shouldSkip));
 }
 
@@ -459,18 +459,18 @@ function getMainThread() {
 async function getBreakpointPositions(
   actors: Array<SourceActor>,
   range: ?Range
 ): Promise<{ [string]: number[] }> {
   const sourcePositions = {};
 
   for (const { thread, actor } of actors) {
     const sourceThreadClient = lookupThreadClient(thread);
-    const sourceClient = sourceThreadClient.source({ actor });
-    const { positions } = await sourceClient.getBreakpointPositionsCompressed(
+    const sourceFront = sourceThreadClient.source({ actor });
+    const positions = await sourceFront.getBreakpointPositionsCompressed(
       range
     );
 
     for (const line of Object.keys(positions)) {
       let columns = positions[line];
       const existing = sourcePositions[line];
       if (existing) {
         columns = [...new Set([...existing, ...columns])];
--- a/devtools/client/debugger/src/utils/editor/source-search.js
+++ b/devtools/client/debugger/src/utils/editor/source-search.js
@@ -127,18 +127,25 @@ export function getMatchIndex(
 /**
  * If there's a saved search, selects the next results.
  * Otherwise, creates a new search and selects the first
  * result.
  *
  * @memberof utils/source-search
  * @static
  */
-function doSearch(ctx, rev, query, keepSelection, modifiers: SearchModifiers) {
-  const { cm } = ctx;
+function doSearch(
+  ctx,
+  rev,
+  query,
+  keepSelection,
+  modifiers: SearchModifiers,
+  focusFirstResult?: boolean = true
+) {
+  const { cm, ed } = ctx;
   if (!cm) {
     return;
   }
   const defaultIndex = { line: -1, ch: -1 };
 
   return cm.operation(function() {
     if (!query || isWhitespace(query)) {
       clearSearch(cm, query);
@@ -148,16 +155,23 @@ function doSearch(ctx, rev, query, keepS
     const state = getSearchState(cm, query);
     const isNewQuery = state.query !== query;
     state.query = query;
 
     updateOverlay(cm, state, query, modifiers);
     updateCursor(cm, state, keepSelection);
     const searchLocation = searchNext(ctx, rev, query, isNewQuery, modifiers);
 
+    // We don't want to jump the editor
+    // when we're selecting text
+    if (!cm.state.selectingText && searchLocation && focusFirstResult) {
+      ed.alignLine(searchLocation.from.line, "center");
+      cm.setSelection(searchLocation.from, searchLocation.to);
+    }
+
     return searchLocation ? searchLocation.from : defaultIndex;
   });
 }
 
 export function searchSourceForHighlight(
   ctx: Object,
   rev: boolean,
   query: string,
@@ -192,17 +206,17 @@ function getCursorPos(newQuery, rev, sta
 
 /**
  * Selects the next result of a saved search.
  *
  * @memberof utils/source-search
  * @static
  */
 function searchNext(ctx, rev, query, newQuery, modifiers) {
-  const { cm, ed } = ctx;
+  const { cm } = ctx;
   let nextMatch;
   cm.operation(function() {
     const state = getSearchState(cm, query);
     const pos = getCursorPos(newQuery, rev, state);
 
     if (!state.query) {
       return;
     }
@@ -215,23 +229,16 @@ function searchNext(ctx, rev, query, new
 
     if (!cursor.find(rev) && state.query) {
       cursor = getSearchCursor(cm, state.query, location, modifiers);
       if (!cursor.find(rev)) {
         return;
       }
     }
 
-    // We don't want to jump the editor
-    // when we're selecting text
-    if (!cm.state.selectingText) {
-      ed.alignLine(cursor.from().line, "center");
-      cm.setSelection(cursor.from(), cursor.to());
-    }
-
     nextMatch = { from: cursor.from(), to: cursor.to() };
   });
 
   return nextMatch;
 }
 
 function findNextOnLine(ctx, rev, query, newQuery, modifiers, line, ch) {
   const { cm, ed } = ctx;
@@ -291,20 +298,28 @@ export function clearSearch(cm: any, que
  *
  * @memberof utils/source-search
  * @static
  */
 export function find(
   ctx: any,
   query: string,
   keepSelection: boolean,
-  modifiers: SearchModifiers
+  modifiers: SearchModifiers,
+  focusFirstResult?: boolean
 ) {
   clearSearch(ctx.cm, query);
-  return doSearch(ctx, false, query, keepSelection, modifiers);
+  return doSearch(
+    ctx,
+    false,
+    query,
+    keepSelection,
+    modifiers,
+    focusFirstResult
+  );
 }
 
 /**
  * Finds the next item based on the currently saved search.
  *
  * @memberof utils/source-search
  * @static
  */
--- a/devtools/client/debugger/src/utils/prefs.js
+++ b/devtools/client/debugger/src/utils/prefs.js
@@ -63,16 +63,17 @@ if (isDevelopment()) {
   pref("devtools.debugger.features.autocomplete-expressions", false);
   pref("devtools.debugger.features.map-expression-bindings", true);
   pref("devtools.debugger.features.map-await-expression", true);
   pref("devtools.debugger.features.xhr-breakpoints", true);
   pref("devtools.debugger.features.original-blackbox", true);
   pref("devtools.debugger.features.windowless-workers", true);
   pref("devtools.debugger.features.event-listeners-breakpoints", true);
   pref("devtools.debugger.features.log-points", true);
+  pref("devtools.debugger.log-actions", true);
 }
 
 export const prefs = new PrefsHelper("devtools", {
   logging: ["Bool", "debugger.logging"],
   editorWrapping: ["Bool", "debugger.ui.editor-wrapping"],
   alphabetizeOutline: ["Bool", "debugger.alphabetize-outline"],
   autoPrettyPrint: ["Bool", "debugger.auto-pretty-print"],
   clientSourceMapsEnabled: ["Bool", "source-map.client-service.enabled"],
@@ -97,17 +98,18 @@ export const prefs = new PrefsHelper("de
   pendingSelectedLocation: ["Json", "debugger.pending-selected-location", {}],
   expressions: ["Json", "debugger.expressions", []],
   fileSearchCaseSensitive: ["Bool", "debugger.file-search-case-sensitive"],
   fileSearchWholeWord: ["Bool", "debugger.file-search-whole-word"],
   fileSearchRegexMatch: ["Bool", "debugger.file-search-regex-match"],
   debuggerPrefsSchemaVersion: ["Char", "debugger.prefs-schema-version"],
   projectDirectoryRoot: ["Char", "debugger.project-directory-root", ""],
   skipPausing: ["Bool", "debugger.skip-pausing"],
-  mapScopes: ["Bool", "debugger.map-scopes-enabled"]
+  mapScopes: ["Bool", "debugger.map-scopes-enabled"],
+  logActions: ["Bool", "debugger.log-actions"]
 });
 
 export const features = new PrefsHelper("devtools.debugger.features", {
   asyncStepping: ["Bool", "async-stepping"],
   wasm: ["Bool", "wasm"],
   shortcuts: ["Bool", "shortcuts"],
   root: ["Bool", "root"],
   columnBreakpoints: ["Bool", "column-breakpoints"],
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -750,16 +750,18 @@ skip-if = os == "win"
 [browser_dbg-returnvalues.js]
 [browser_dbg-reload.js]
 [browser_dbg-reloading.js]
 skip-if = true
 [browser_dbg-pause-points.js]
 [browser_dbg-scopes-mutations.js]
 [browser_dbg-search-file.js]
 skip-if = os == "win" # Bug 1393121
+[browser_dbg-search-file-paused.js]
+skip-if = os == "win" # Bug 1393121
 [browser_dbg-quick-open.js]
 skip-if = os == "win"
 [browser_dbg-search-project.js]
 [browser_dbg-blackbox-original.js]
 [browser_dbg-sourcemaps.js]
 [browser_dbg-sourcemaps-breakpoints.js]
 [browser_dbg-sourcemaps-disabled.js]
 [browser_dbg-sourcemaps-reload.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-search-file-paused.js
@@ -0,0 +1,71 @@
+/* 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/>. */
+
+// Tests the search bar correctly responds to queries, enter, shift enter
+
+function waitForSearchState(dbg) {
+  return waitForState(dbg, () => getCM(dbg).state.search);
+}
+
+function getFocusedEl(dbg) {
+  const doc = dbg.win.document;
+  return doc.activeElement;
+}
+
+function pressMouseDown(dbg, node) {
+  EventUtils.sendMouseEvent({ type: "mousedown" }, node, dbg.win);
+}
+
+add_task(async function() {
+  const dbg = await initDebugger("doc-scripts.html", "simple1.js", "simple2.js");
+  const {
+    selectors: { getBreakpoints, getBreakpoint, getActiveSearch },
+    getState
+  } = dbg;
+
+  info("Add a breakpoint, wait for pause");
+  const source = findSource(dbg, "simple2.js");
+  await selectSource(dbg, source.url);
+  await addBreakpoint(dbg, source, 5);
+  invokeInTab("main");
+  await waitForPaused(dbg);
+
+  info("Starting a search for 'bar'");
+  const cm = getCM(dbg);
+  pressKey(dbg, "fileSearch");
+  is(dbg.selectors.getActiveSearch(), "file");
+  const el = getFocusedEl(dbg);
+  type(dbg, "bar");
+  await waitForSearchState(dbg);
+
+  info("Ensuring 'bar' matches are highlighted");
+  pressKey(dbg, "Enter");
+  is(cm.state.search.posFrom.line, 1);
+  pressKey(dbg, "Enter");
+  is(cm.state.search.posFrom.line, 4);
+
+  info("Switching files via frame click");
+  const frames = findAllElements(dbg, "frames");
+  pressMouseDown(dbg, frames[1])
+
+  // Ensure that the debug line is in view, and not the first "bar" instance,
+  // which the user would have to scroll down for
+  const { top } = cm.getScrollInfo();
+  is(top, 0, "First search term is not in view");
+
+  // Change the search term and go back to the first source in stack
+  info("Switching to paused file via frame click");
+  pressKey(dbg, "fileSearch");
+  el.value = "";
+  type(dbg, "func");
+  await waitForSearchState(dbg);
+  pressMouseDown(dbg, frames[0]);
+  await waitFor(() => cm.state.search.query === "func");
+
+  // Ensure there is a match for the new term
+  pressKey(dbg, "Enter");
+  is(cm.state.search.posFrom.line, 0);
+  pressKey(dbg, "Enter");
+  is(cm.state.search.posFrom.line, 1);
+});
--- a/devtools/client/debugger/test/mochitest/helpers.js
+++ b/devtools/client/debugger/test/mochitest/helpers.js
@@ -519,50 +519,51 @@ function isSelectedFrameSelected(dbg, st
   }
 
   return source.id == sourceId;
 }
 
 /**
  * Clear all the debugger related preferences.
  */
-function clearDebuggerPreferences() {
+async function clearDebuggerPreferences() {
   asyncStorage.clear();
   Services.prefs.clearUserPref("devtools.recordreplay.enabled");
   Services.prefs.clearUserPref("devtools.debugger.pause-on-exceptions");
   Services.prefs.clearUserPref("devtools.debugger.pause-on-caught-exceptions");
   Services.prefs.clearUserPref("devtools.debugger.ignore-caught-exceptions");
   Services.prefs.clearUserPref("devtools.debugger.pending-selected-location");
   Services.prefs.clearUserPref("devtools.debugger.expressions");
   Services.prefs.clearUserPref("devtools.debugger.call-stack-visible");
   Services.prefs.clearUserPref("devtools.debugger.scopes-visible");
   Services.prefs.clearUserPref("devtools.debugger.skip-pausing");
   Services.prefs.clearUserPref("devtools.debugger.map-scopes-enabled");
+  await pushPref("devtools.debugger.log-actions", true);
 }
 
 /**
  * Intilializes the debugger.
  *
  * @memberof mochitest
  * @param {String} url
  * @return {Promise} dbg
  * @static
  */
 async function initDebugger(url, ...sources) {
-  clearDebuggerPreferences();
+  await clearDebuggerPreferences();
   const toolbox = await openNewTabAndToolbox(EXAMPLE_URL + url, "jsdebugger");
   const dbg = createDebuggerContext(toolbox);
   dbg.client.waitForWorkers(false);
 
   await waitForSources(dbg, ...sources);
   return dbg;
 }
 
 async function initPane(url, pane) {
-  clearDebuggerPreferences();
+  await clearDebuggerPreferences();
   return openNewTabAndToolbox(EXAMPLE_URL + url, pane);
 }
 
 window.resumeTest = undefined;
 registerCleanupFunction(() => {
   delete window.resumeTest;
 });
 
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -6,17 +6,17 @@
 
 const Services = require("Services");
 const osString = Services.appinfo.OS;
 
 // Panels
 loader.lazyGetter(this, "OptionsPanel", () => require("devtools/client/framework/toolbox-options").OptionsPanel);
 loader.lazyGetter(this, "InspectorPanel", () => require("devtools/client/inspector/panel").InspectorPanel);
 loader.lazyGetter(this, "WebConsolePanel", () => require("devtools/client/webconsole/panel").WebConsolePanel);
-loader.lazyGetter(this, "NewDebuggerPanel", () => require("devtools/client/debugger/panel").DebuggerPanel);
+loader.lazyGetter(this, "DebuggerPanel", () => require("devtools/client/debugger/panel").DebuggerPanel);
 loader.lazyGetter(this, "StyleEditorPanel", () => require("devtools/client/styleeditor/panel").StyleEditorPanel);
 loader.lazyGetter(this, "MemoryPanel", () => require("devtools/client/memory/panel").MemoryPanel);
 loader.lazyGetter(this, "PerformancePanel", () => require("devtools/client/performance/panel").PerformancePanel);
 loader.lazyGetter(this, "NewPerformancePanel", () => require("devtools/client/performance-new/panel").PerformancePanel);
 loader.lazyGetter(this, "NetMonitorPanel", () => require("devtools/client/netmonitor/panel").NetMonitorPanel);
 loader.lazyGetter(this, "StoragePanel", () => require("devtools/client/storage/panel").StoragePanel);
 loader.lazyGetter(this, "ScratchpadPanel", () => require("devtools/client/scratchpad/panel").ScratchpadPanel);
 loader.lazyGetter(this, "DomPanel", () => require("devtools/client/dom/panel").DomPanel);
@@ -139,17 +139,17 @@ Tools.jsdebugger = {
   get tooltip() {
     return l10n("ToolboxDebugger.tooltip3");
   },
   inMenu: true,
   isTargetSupported: function() {
     return true;
   },
   build: function(iframeWindow, toolbox) {
-    return new NewDebuggerPanel(iframeWindow, toolbox);
+    return new DebuggerPanel(iframeWindow, toolbox);
   },
 };
 
 Tools.styleEditor = {
   id: "styleeditor",
   ordinal: 4,
   visibilityswitch: "devtools.styleeditor.enabled",
   accesskey: l10n("open.accesskey"),
--- a/devtools/client/preferences/debugger.js
+++ b/devtools/client/preferences/debugger.js
@@ -49,16 +49,17 @@ pref("devtools.debugger.expressions", "[
 pref("devtools.debugger.event-listener-breakpoints", "[]");
 pref("devtools.debugger.file-search-case-sensitive", false);
 pref("devtools.debugger.file-search-whole-word", false);
 pref("devtools.debugger.file-search-regex-match", false);
 pref("devtools.debugger.project-directory-root", "");
 pref("devtools.debugger.skip-pausing", false);
 pref("devtools.debugger.logging", false);
 pref("devtools.debugger.map-scopes-enabled", false);
+pref("devtools.debugger.log-actions", false);
 
 pref("devtools.debugger.features.wasm", true);
 pref("devtools.debugger.features.shortcuts", true);
 pref("devtools.debugger.features.root", true);
 pref("devtools.debugger.features.column-breakpoints", true);
 pref("devtools.debugger.features.chrome-scopes", false);
 pref("devtools.debugger.features.map-scopes", true);
 pref("devtools.debugger.features.remove-command-bar-options", false);
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -364,17 +364,17 @@ pref("devtools.aboutdebugging.network-lo
 // Debug target pane collapse/expand settings.
 pref("devtools.aboutdebugging.collapsibilities.installedExtension", false);
 pref("devtools.aboutdebugging.collapsibilities.otherWorker", false);
 pref("devtools.aboutdebugging.collapsibilities.serviceWorker", false);
 pref("devtools.aboutdebugging.collapsibilities.sharedWorker", false);
 pref("devtools.aboutdebugging.collapsibilities.tab", false);
 pref("devtools.aboutdebugging.collapsibilities.temporaryExtension", false);
 
-// about:debugging: only show system add-ons in local builds by default.
+// about:debugging: only show system and hidden extensions in local builds by default.
 #ifdef MOZILLA_OFFICIAL
-  pref("devtools.aboutdebugging.showSystemAddons", false);
+  pref("devtools.aboutdebugging.showHiddenAddons", false);
 #else
-  pref("devtools.aboutdebugging.showSystemAddons", true);
+  pref("devtools.aboutdebugging.showHiddenAddons", true);
 #endif
 
 // Map top-level await expressions in the console
 pref("devtools.debugger.features.map-await-expression", true);
--- a/devtools/client/shared/remote-debugging/remote-client-manager.js
+++ b/devtools/client/shared/remote-debugging/remote-client-manager.js
@@ -75,31 +75,28 @@ class RemoteClientManager {
    * using getRemoteId.
    */
   getConnectionTypeByRemoteId(remoteId) {
     if (!remoteId) {
       return CONNECTION_TYPES.THIS_FIREFOX;
     }
 
     const key = decodeURIComponent(remoteId);
-    const type = this._getType(key);
-    return Object.values(CONNECTION_TYPES).includes(type)
-      ? type
-      : CONNECTION_TYPES.UNKNOWN;
+    for (const type of Object.values(CONNECTION_TYPES)) {
+      if (key.endsWith(type)) {
+        return type;
+      }
+    }
+    return CONNECTION_TYPES.UNKNOWN;
   }
 
   _getKey(id, type) {
     return id + "-" + type;
   }
 
-  _getType(key) {
-    const chunks = key.split("-");
-    return chunks[chunks.length - 1];
-  }
-
   _removeClientByKey(key) {
     const client = this._clients.get(key);
     if (client) {
       client.removeListener("closed", this._onClientClosed);
       this._clients.delete(key);
     }
   }
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/remote-debugging/test/unit/test_remote_client_manager.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { remoteClientManager } =
+  require("devtools/client/shared/remote-debugging/remote-client-manager");
+const { CONNECTION_TYPES } =
+  require("devtools/client/shared/remote-debugging/constants");
+
+add_task(async function testRemoteClientManager() {
+  for (const type of Object.values(CONNECTION_TYPES)) {
+    const fakeClient = createFakeClient();
+    const clientId = "clientId";
+    const remoteId = remoteClientManager.getRemoteId(clientId, type);
+
+    const connectionType = remoteClientManager.getConnectionTypeByRemoteId(remoteId);
+    equal(connectionType, type,
+      `[${type}]: Correct connection type was returned by getConnectionTypeByRemoteId`);
+
+    equal(remoteClientManager.hasClient(clientId, type), false,
+      `[${type}]: hasClient returns false if no client was set`);
+    equal(remoteClientManager.getClient(clientId, type), null,
+      `[${type}]: getClient returns null if no client was set`);
+    equal(remoteClientManager.getClientByRemoteId(remoteId), null,
+      `[${type}]: getClientByRemoteId returns null if no client was set`);
+
+    remoteClientManager.setClient(clientId, type, fakeClient);
+    equal(remoteClientManager.hasClient(clientId, type), true,
+      `[${type}]: hasClient returns true`);
+    equal(remoteClientManager.getClient(clientId, type), fakeClient,
+      `[${type}]: getClient returns the correct client`);
+    equal(remoteClientManager.getClientByRemoteId(remoteId), fakeClient,
+      `[${type}]: getClientByRemoteId returns the correct client`);
+
+    remoteClientManager.removeClient(clientId, type);
+    equal(remoteClientManager.hasClient(clientId, type), false,
+      `[${type}]: hasClient returns false after removing the client`);
+    equal(remoteClientManager.getClient(clientId, type), null,
+      `[${type}]: getClient returns null after removing the client`);
+    equal(remoteClientManager.getClientByRemoteId(remoteId), null,
+      `[${type}]: getClientByRemoteId returns null after removing the client`);
+  }
+});
+
+add_task(async function testRemoteClientManagerWithUnknownType() {
+  const remoteId = remoteClientManager.getRemoteId("someClientId", "NotARealType");
+  const connectionType = remoteClientManager.getConnectionTypeByRemoteId(remoteId);
+  equal(connectionType, CONNECTION_TYPES.UNKNOWN,
+    `Connection type UNKNOWN was returned by getConnectionTypeByRemoteId`);
+});
+
+function createFakeClient() {
+  const EventEmitter = require("devtools/shared/event-emitter");
+
+  const client = {};
+  EventEmitter.decorate(client);
+
+  // Define aliases expected by the remote-client-manager.
+  client.addOneTimeListener = (evt, listener) => {
+    return client.once(evt, listener);
+  };
+  client.addListener = (evt, listener) => {
+    return client.on(evt, listener);
+  };
+  client.removeListener = (evt, listener) => {
+    return client.off(evt, listener);
+  };
+
+  return client;
+}
--- a/devtools/client/shared/remote-debugging/test/unit/xpcshell.ini
+++ b/devtools/client/shared/remote-debugging/test/unit/xpcshell.ini
@@ -1,7 +1,8 @@
 [DEFAULT]
 tags = devtools
 head = xpcshell-head.js
 firefox-appdir = browser
 skip-if = toolkit == 'android'
 
+[test_remote_client_manager.js]
 [test_version_checker.js]
--- a/devtools/server/actors/addon/webextension.js
+++ b/devtools/server/actors/addon/webextension.js
@@ -65,48 +65,49 @@ const WebExtensionActor = protocol.Actor
       return {};
     });
   },
 
   form() {
     const policy = ExtensionParent.WebExtensionPolicy.getByID(this.addonId);
     return {
       actor: this.actorID,
-      id: this.addonId,
-      name: this.addon.name,
-      url: this.addon.sourceURI ? this.addon.sourceURI.spec : undefined,
+      debuggable: this.addon.isDebuggable,
+      hidden: this.addon.hidden,
       // iconDataURL is available after calling loadIconDataURL
       iconDataURL: this._iconDataURL,
       iconURL: this.addon.iconURL,
+      id: this.addonId,
+      isAPIExtension: this.addon.isAPIExtension,
       isSystem: this.addon.isSystem,
-      debuggable: this.addon.isDebuggable,
+      isWebExtension: this.addon.isWebExtension,
+      manifestURL: policy && policy.getURL("manifest.json"),
+      name: this.addon.name,
       temporarilyInstalled: this.addon.temporarilyInstalled,
       type: this.addon.type,
-      isWebExtension: this.addon.isWebExtension,
-      isAPIExtension: this.addon.isAPIExtension,
-      manifestURL: policy && policy.getURL("manifest.json"),
+      url: this.addon.sourceURI ? this.addon.sourceURI.spec : undefined,
       warnings: ExtensionParent.DebugUtils.getExtensionManifestWarnings(this.addonId),
     };
   },
 
   connect() {
     if (this._childFormPormise) {
       return this._childFormPromise;
     }
 
     const proxy = new WebExtensionTargetActorProxy(this.conn, this);
     this._childFormPromise = proxy.connect().then(form => {
       // Merge into the child actor form, some addon metadata
       // (e.g. the addon name shown in the addon debugger window title).
       return Object.assign(form, {
+        iconURL: this.addon.iconURL,
         id: this.addon.id,
-        name: this.addon.name,
-        iconURL: this.addon.iconURL,
         // Set the isOOP attribute on the connected child actor form.
         isOOP: proxy.isOOP,
+        name: this.addon.name,
       });
     });
     this._destroyProxy = () => proxy.destroy();
 
     return this._childFormPromise;
   },
 
   // This function will be called from RootActor in case that the debugger client
--- a/devtools/server/actors/source.js
+++ b/devtools/server/actors/source.js
@@ -319,17 +319,21 @@ const SourceActor = ActorClassWithSpec(s
         compressed[line] = [];
       }
       compressed[line].push(column);
     }
     return compressed;
   },
 
   /**
-   * Handler for the "source" packet.
+   * Handler for the "onSource" packet.
+   * @return Object
+   *         The return of this function contains a field `contentType`, and
+   *         a field `source`. `source` can either be an arrayBufferActor grip,
+   *         or a LongStringActor grip.
    */
   onSource: function() {
     return Promise.resolve(this._init)
       .then(this._getSourceText)
       .then(({ content, contentType }) => {
         if (typeof content === "object" && content && content.constructor &&
             content.constructor.name === "ArrayBuffer") {
           return {
--- a/devtools/server/tests/unit/head_dbg.js
+++ b/devtools/server/tests/unit/head_dbg.js
@@ -736,56 +736,56 @@ function stepOut(client, threadClient) {
 function getFrames(threadClient, first, count) {
   dumpn("Getting frames.");
   return threadClient.getFrames(first, count);
 }
 
 /**
  * Black box the specified source.
  *
- * @param SourceClient sourceClient
+ * @param SourceFront sourceFront
  * @returns Promise
  */
-async function blackBox(sourceClient, range = null) {
-  dumpn("Black boxing source: " + sourceClient.actor);
-  const { error, pausedInSource } = await sourceClient.blackBox(range);
-  Assert.ok(!error, "Should not get an error: " + error);
-  return {error, pausedInSource};
+async function blackBox(sourceFront, range = null) {
+  dumpn("Black boxing source: " + sourceFront.actor);
+  const pausedInSource = await sourceFront.blackBox(range);
+  ok(true, "blackBox didn't throw");
+  return pausedInSource;
 }
 
 /**
  * Stop black boxing the specified source.
  *
- * @param SourceClient sourceClient
+ * @param SourceFront sourceFront
  * @returns Promise
  */
-async function unBlackBox(sourceClient, range = null) {
-  dumpn("Un-black boxing source: " + sourceClient.actor);
-  const {error} = await sourceClient.unblackBox(range);
-  Assert.ok(!error, "Should not get an error: " + error);
+async function unBlackBox(sourceFront, range = null) {
+  dumpn("Un-black boxing source: " + sourceFront.actor);
+  await sourceFront.unblackBox(range);
+  ok(true, "unblackBox didn't throw");
 }
 
 /**
- * Perform a "source" RDP request with the given SourceClient to get the source
+ * Perform a "source" RDP request with the given SourceFront to get the source
  * content and content type.
  *
- * @param SourceClient sourceClient
+ * @param SourceFront sourceFront
  * @returns Promise
  */
-function getSourceContent(sourceClient) {
-  dumpn("Getting source content for " + sourceClient.actor);
-  return sourceClient.source();
+function getSourceContent(sourceFront) {
+  dumpn("Getting source content for " + sourceFront.actor);
+  return sourceFront.source();
 }
 
 /**
  * Get a source at the specified url.
  *
  * @param ThreadClient threadClient
  * @param string url
- * @returns Promise<SourceClient>
+ * @returns Promise<SourceFront>
  */
 async function getSource(threadClient, url) {
   const source = await getSourceForm(threadClient, url);
   if (source) {
     return threadClient.source(source);
   }
 
   throw new Error("source not found");
@@ -867,18 +867,18 @@ async function setupTestFromUrl(url) {
   const [, threadClient] = await attachThread(targetFront);
   await resume(threadClient);
 
   const sourceUrl = getFileUrl(url);
   const promise = waitForNewSource(threadClient, sourceUrl);
   loadSubScript(sourceUrl, global);
   const { source } = await promise;
 
-  const sourceClient = threadClient.source(source);
-  return { global, debuggerClient, threadClient, sourceClient };
+  const sourceFront = threadClient.source(source);
+  return { global, debuggerClient, threadClient, sourceFront };
 }
 
 /**
  * Run the given test function twice, one with a regular DebuggerServer,
  * testing against a fake tab. And another one against a WorkerDebuggerServer,
  * testing the worker codepath.
  *
  * @param Function test
--- a/devtools/server/tests/unit/test_blackboxing-02.js
+++ b/devtools/server/tests/unit/test_blackboxing-02.js
@@ -65,34 +65,34 @@ function test_black_box() {
     1
   );
   /* eslint-enable no-multi-spaces, no-undef */
 }
 
 function test_black_box_breakpoint() {
   gThreadClient.getSources(async function({error, sources}) {
     Assert.ok(!error, "Should not get an error: " + error);
-    const sourceClient = gThreadClient.source(
+    const sourceFront = gThreadClient.source(
       sources.filter(s => s.url == BLACK_BOXED_URL)[0]
     );
 
-    await blackBox(sourceClient);
+    await blackBox(sourceFront);
 
     gClient.addOneTimeListener("paused", function(event, packet) {
       Assert.equal(
         packet.why.type, "debuggerStatement",
         "We should pass over the breakpoint since the source is black boxed.");
-      gThreadClient.resume(test_unblack_box_breakpoint.bind(null, sourceClient));
+      gThreadClient.resume(test_unblack_box_breakpoint.bind(null, sourceFront));
     });
     gDebuggee.runTest();
   });
 }
 
-async function test_unblack_box_breakpoint(sourceClient) {
-  await unBlackBox(sourceClient);
+async function test_unblack_box_breakpoint(sourceFront) {
+  await unBlackBox(sourceFront);
   gClient.addOneTimeListener("paused", function(event, packet) {
     Assert.equal(packet.why.type, "breakpoint",
                  "We should hit the breakpoint again");
 
     // We will hit the debugger statement on resume, so do this
     // nastiness to skip over it.
     gClient.addOneTimeListener(
       "paused",
--- a/devtools/server/tests/unit/test_blackboxing-03.js
+++ b/devtools/server/tests/unit/test_blackboxing-03.js
@@ -64,36 +64,36 @@ function test_black_box() {
     1
   );
   /* eslint-enable no-multi-spaces, no-undef */
 }
 
 function test_black_box_dbg_statement() {
   gThreadClient.getSources(async function({error, sources}) {
     Assert.ok(!error, "Should not get an error: " + error);
-    const sourceClient = await getSource(gThreadClient, BLACK_BOXED_URL);
+    const sourceFront = await getSource(gThreadClient, BLACK_BOXED_URL);
 
-    await blackBox(sourceClient);
+    await blackBox(sourceFront);
 
     gThreadClient.addOneTimeListener("paused", async function(event, packet) {
       Assert.equal(packet.why.type, "breakpoint",
                    "We should pass over the debugger statement.");
 
       const source = await getSourceById(gThreadClient, packet.frame.where.actor);
       gThreadClient.removeBreakpoint({ sourceUrl: source.url, line: 4 }, {});
 
       await gThreadClient.resume();
-      await test_unblack_box_dbg_statement(sourceClient);
+      await test_unblack_box_dbg_statement(sourceFront);
     });
     gDebuggee.runTest();
   });
 }
 
-async function test_unblack_box_dbg_statement(sourceClient) {
-  await unBlackBox(sourceClient);
+async function test_unblack_box_dbg_statement(sourceFront) {
+  await unBlackBox(sourceFront);
 
   gClient.addOneTimeListener("paused", function(event, packet) {
     Assert.equal(packet.why.type, "debuggerStatement",
                  "We should stop at the debugger statement again");
     finishClient(gClient);
   });
   gDebuggee.runTest();
 }
--- a/devtools/server/tests/unit/test_blackboxing-04.js
+++ b/devtools/server/tests/unit/test_blackboxing-04.js
@@ -62,18 +62,18 @@ function test_black_box() {
     1
   );
   /* eslint-enable no-multi-spaces, no-undef */
 }
 
 function test_black_box_paused() {
   gThreadClient.getSources(async function({error, sources}) {
     Assert.ok(!error, "Should not get an error: " + error);
-    const sourceClient = gThreadClient.source(
+    const sourceFront = gThreadClient.source(
       sources.filter(s => s.url == BLACK_BOXED_URL)[0]
     );
 
-    const {pausedInSource} = await blackBox(sourceClient);
+    const pausedInSource = await blackBox(sourceFront);
     Assert.ok(pausedInSource,
       "We should be notified that we are currently paused in this source");
     finishClient(gClient);
   });
 }
--- a/devtools/server/tests/unit/test_blackboxing-05.js
+++ b/devtools/server/tests/unit/test_blackboxing-05.js
@@ -66,18 +66,18 @@ function test_black_box() {
     1
   );
   /* eslint-enable no-multi-spaces, no-unreachable, no-undef */
 }
 
 function test_black_box_exception() {
   gThreadClient.getSources(async function({error, sources}) {
     Assert.ok(!error, "Should not get an error: " + error);
-    const sourceClient = await getSource(gThreadClient, BLACK_BOXED_URL);
-    await blackBox(sourceClient);
+    const sourceFront = await getSource(gThreadClient, BLACK_BOXED_URL);
+    await blackBox(sourceFront);
     gThreadClient.pauseOnExceptions(true, false);
 
     gClient.addOneTimeListener("paused", async function(event, packet) {
       const source = await getSourceById(gThreadClient, packet.frame.where.actor);
 
       Assert.equal(source.url, SOURCE_URL,
                    "We shouldn't pause while in the black boxed source.");
       finishClient(gClient);
--- a/devtools/server/tests/unit/test_blackboxing-08.js
+++ b/devtools/server/tests/unit/test_blackboxing-08.js
@@ -24,31 +24,31 @@ async function invokeAndPause({global, d
 function run_test() {
   return (async function() {
     const dbg = await setupTestFromUrl("stepping.js");
     const { threadClient } = dbg;
 
     await invokeAndPause(dbg, `chaining()`);
 
     const { sources } = await getSources(threadClient);
-    const sourceClient = threadClient.source(sources[0]);
+    const sourceFront = threadClient.source(sources[0]);
 
-    await setBreakpoint(threadClient, { sourceUrl: sourceClient.url, line: 7 });
-    await setBreakpoint(threadClient, { sourceUrl: sourceClient.url, line: 11 });
+    await setBreakpoint(threadClient, { sourceUrl: sourceFront.url, line: 7 });
+    await setBreakpoint(threadClient, { sourceUrl: sourceFront.url, line: 11 });
 
     // 1. lets blackbox function a, and assert that we pause in b
     const range = {start: { line: 6, column: 0 }, end: { line: 8, colum: 1 }};
-    blackBox(sourceClient, range);
+    blackBox(sourceFront, range);
     resume(threadClient);
     const paused = await waitForPause(threadClient);
     equal(paused.frame.where.line, 11, "paused inside of b");
     await resume(threadClient);
 
     // 2. lets unblackbox function a, and assert that we pause in a
-    unBlackBox(sourceClient, range);
+    unBlackBox(sourceFront, range);
     await invokeAndPause(dbg, `chaining()`);
     resume(threadClient);
     const paused2 = await waitForPause(threadClient);
     equal(paused2.frame.where.line, 7, "paused inside of a");
 
     await testFinish(dbg);
   })();
 }
--- a/devtools/server/tests/unit/test_breakpoint-09.js
+++ b/devtools/server/tests/unit/test_breakpoint-09.js
@@ -17,17 +17,17 @@ add_task(threadClientTest(({ threadClien
         packet.frame.where.actor
       );
       const location = { sourceUrl: source.url, line: debuggee.line0 + 2 };
 
       threadClient.setBreakpoint(location, {});
       threadClient.addOneTimeListener("paused", function(event, packet) {
         // Check the return value.
         Assert.equal(packet.type, "paused");
-        Assert.equal(packet.frame.where.actor, source.actor);
+        Assert.equal(packet.frame.where.actor, source.actorID);
         Assert.equal(packet.frame.where.line, location.line);
         Assert.equal(packet.why.type, "breakpoint");
         // Check that the breakpoint worked.
         Assert.equal(debuggee.a, undefined);
 
         // Remove the breakpoint.
         threadClient.removeBreakpoint(location);
         done = true;
--- a/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-offsets.js
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-offsets.js
@@ -1,36 +1,36 @@
 "use strict";
 
 const SOURCE_URL = getFileUrl("setBreakpoint-on-line-with-multiple-offsets.js");
 
 add_task(threadClientTest(async ({ threadClient, debuggee, client }) => {
   const promise = waitForNewSource(threadClient, SOURCE_URL);
   loadSubScript(SOURCE_URL, debuggee);
   const { source } = await promise;
-  const sourceClient = threadClient.source(source);
+  const sourceFront = threadClient.source(source);
 
-  const location = { sourceUrl: sourceClient.url, line: 4 };
+  const location = { sourceUrl: sourceFront.url, line: 4 };
   setBreakpoint(threadClient, location);
 
   let packet = await executeOnNextTickAndWaitForPause(function() {
     Cu.evalInSandbox("f()", debuggee);
   }, client);
   Assert.equal(packet.type, "paused");
   let why = packet.why;
   Assert.equal(why.type, "breakpoint");
   Assert.equal(why.actors.length, 1);
   let frame = packet.frame;
   let where = frame.where;
   Assert.equal(where.actor, source.actor);
   Assert.equal(where.line, location.line);
   let variables = frame.environment.bindings.variables;
   Assert.equal(variables.i.value.type, "undefined");
 
-  const location2 = { sourceUrl: sourceClient.url, line: 7 };
+  const location2 = { sourceUrl: sourceFront.url, line: 7 };
   setBreakpoint(threadClient, location2);
 
   packet = await executeOnNextTickAndWaitForPause(
     () => resume(threadClient),
     client
   );
   Assert.equal(packet.type, "paused");
   why = packet.why;
--- a/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-statements.js
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-statements.js
@@ -1,19 +1,19 @@
 "use strict";
 
 const SOURCE_URL = getFileUrl("setBreakpoint-on-line-with-multiple-statements.js");
 
 add_task(threadClientTest(async ({ threadClient, debuggee, client }) => {
   const promise = waitForNewSource(threadClient, SOURCE_URL);
   loadSubScript(SOURCE_URL, debuggee);
   const { source } = await promise;
-  const sourceClient = threadClient.source(source);
+  const sourceFront = threadClient.source(source);
 
-  const location = { sourceUrl: sourceClient.url, line: 4 };
+  const location = { sourceUrl: sourceFront.url, line: 4 };
   setBreakpoint(threadClient, location);
 
   const packet = await executeOnNextTickAndWaitForPause(function() {
     Cu.evalInSandbox("f()", debuggee);
   }, client);
   Assert.equal(packet.type, "paused");
   const why = packet.why;
   Assert.equal(why.type, "breakpoint");
--- a/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets-in-gcd-script.js
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets-in-gcd-script.js
@@ -3,20 +3,20 @@
 const SOURCE_URL = getFileUrl("setBreakpoint-on-line-with-no-offsets-in-gcd-script.js");
 
 add_task(threadClientTest(async ({ threadClient, debuggee, client, targetFront }) => {
   const promise = waitForNewSource(threadClient, SOURCE_URL);
   loadSubScriptWithOptions(SOURCE_URL, {target: debuggee, ignoreCache: true});
   Cu.forceGC(); Cu.forceGC(); Cu.forceGC();
 
   const { source } = await promise;
-  const sourceClient = threadClient.source(source);
+  const sourceFront = threadClient.source(source);
 
   const location = { line: 7 };
-  let [packet, breakpointClient] = await setBreakpoint(sourceClient, location);
+  let [packet, breakpointClient] = await setBreakpoint(sourceFront, location);
   Assert.ok(packet.isPending);
   Assert.equal(false, "actualLocation" in packet);
 
   packet = await executeOnNextTickAndWaitForPause(function() {
     reload(targetFront).then(function() {
       loadSubScriptWithOptions(SOURCE_URL, {target: debuggee, ignoreCache: true});
     });
   }, client);
--- a/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets.js
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets.js
@@ -1,20 +1,20 @@
 "use strict";
 
 const SOURCE_URL = getFileUrl("setBreakpoint-on-line-with-no-offsets.js");
 
 add_task(threadClientTest(async ({ threadClient, debuggee, client }) => {
   const promise = waitForNewSource(threadClient, SOURCE_URL);
   loadSubScript(SOURCE_URL, debuggee);
   const { source } = await promise;
-  const sourceClient = threadClient.source(source);
+  const sourceFront = threadClient.source(source);
 
   const location = { line: 5 };
-  let [packet, breakpointClient] = await setBreakpoint(sourceClient, location);
+  let [packet, breakpointClient] = await setBreakpoint(sourceFront, location);
   Assert.ok(!packet.isPending);
   Assert.ok("actualLocation" in packet);
   const actualLocation = packet.actualLocation;
   Assert.equal(actualLocation.line, 6);
 
   packet = await executeOnNextTickAndWaitForPause(function() {
     Cu.evalInSandbox("f()", debuggee);
   }, client);
--- a/devtools/server/tests/unit/test_setBreakpoint-on-line.js
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-line.js
@@ -1,19 +1,19 @@
 "use strict";
 
 const SOURCE_URL = getFileUrl("setBreakpoint-on-line.js");
 
 add_task(threadClientTest(async ({ threadClient, debuggee, client }) => {
   const promise = waitForNewSource(threadClient, SOURCE_URL);
   loadSubScript(SOURCE_URL, debuggee);
   const { source } = await promise;
-  const sourceClient = threadClient.source(source);
+  const sourceFront = threadClient.source(source);
 
-  const location = { sourceUrl: sourceClient.url, line: 5 };
+  const location = { sourceUrl: sourceFront.url, line: 5 };
   setBreakpoint(threadClient, location);
 
   const packet = await executeOnNextTickAndWaitForPause(function() {
     Cu.evalInSandbox("f()", debuggee);
   }, client);
   Assert.equal(packet.type, "paused");
   const why = packet.why;
   Assert.equal(why.type, "breakpoint");
--- a/devtools/server/tests/unit/test_source-01.js
+++ b/devtools/server/tests/unit/test_source-01.js
@@ -4,17 +4,17 @@
 /* eslint-disable no-shadow, max-nested-callbacks */
 
 "use strict";
 
 var gDebuggee;
 var gClient;
 var gThreadClient;
 
-// This test ensures that we can create SourceActors and SourceClients properly,
+// This test ensures that we can create SourceActors and SourceFronts properly,
 // and that they can communicate over the protocol to fetch the source text for
 // a given script.
 
 function run_test() {
   initTestDebuggerServer();
   gDebuggee = addTestGlobal("test-grips");
   Cu.evalInSandbox(
     "" + function stopMe(arg1) {
@@ -48,18 +48,18 @@ function test_source() {
       Assert.ok(!!response.sources);
 
       const source = response.sources.filter(function(s) {
         return s.url === SOURCE_URL;
       })[0];
 
       Assert.ok(!!source);
 
-      const sourceClient = gThreadClient.source(source);
-      sourceClient.source().then(function(response) {
+      const sourceFront = gThreadClient.source(source);
+      sourceFront.source().then(function(response) {
         Assert.ok(!!response);
         Assert.ok(!!response.contentType);
         Assert.ok(response.contentType.includes("javascript"));
 
         Assert.ok(!!response.source);
         Assert.equal(SOURCE_CONTENT,
                      response.source);
 
--- a/devtools/server/tests/unit/test_source-02.js
+++ b/devtools/server/tests/unit/test_source-02.js
@@ -4,17 +4,17 @@
 /* eslint-disable no-shadow, max-nested-callbacks */
 
 "use strict";
 
 var gDebuggee;
 var gClient;
 var gThreadClient;
 
-// This test ensures that we can create SourceActors and SourceClients properly,
+// This test ensures that we can create SourceActors and SourceFronts properly,
 // and that they can communicate over the protocol to fetch the source text for
 // a given script.
 
 function run_test() {
   initTestDebuggerServer();
   gDebuggee = addTestGlobal("test-grips");
   Cu.evalInSandbox(
     "" + function stopMe(arg1) {
@@ -53,22 +53,21 @@ function test_source() {
       Assert.ok(!!response.sources);
 
       const source = response.sources.filter(function(s) {
         return s.url === SOURCE_URL;
       })[0];
 
       Assert.ok(!!source);
 
-      const sourceClient = gThreadClient.source(source);
-      response = await sourceClient.getBreakpointPositions();
+      const sourceFront = gThreadClient.source(source);
+      response = await sourceFront.getBreakpointPositions();
       Assert.ok(!!response);
-      Assert.ok(!!response.positions);
       Assert.deepEqual(
-        response.positions,
+        response,
         [{
           line: 2,
           column: 2,
         }, {
           line: 3,
           column: 14,
         }, {
           line: 3,
@@ -80,21 +79,20 @@ function test_source() {
           line: 4,
           column: 4,
         }, {
           line: 6,
           column: 0,
         }]
       );
 
-      response = await sourceClient.getBreakpointPositionsCompressed();
+      response = await sourceFront.getBreakpointPositionsCompressed();
       Assert.ok(!!response);
-      Assert.ok(!!response.positions);
       Assert.deepEqual(
-        response.positions,
+        response,
         {
           2: [2],
           3: [14, 17, 24],
           4: [4],
           6: [0],
         }
       );
 
--- a/devtools/server/tests/unit/test_wasm_source-01.js
+++ b/devtools/server/tests/unit/test_wasm_source-01.js
@@ -60,18 +60,18 @@ function test_source() {
       Assert.ok(!!response.sources);
 
       const source = response.sources.filter(function(s) {
         return s.introductionType === "wasm";
       })[0];
 
       Assert.ok(!!source);
 
-      const sourceClient = gThreadClient.source(source);
-      sourceClient.source().then(function(response) {
+      const sourceFront = gThreadClient.source(source);
+      sourceFront.source().then(function(response) {
         Assert.ok(!!response);
         Assert.ok(!!response.contentType);
         Assert.ok(response.contentType.includes("wasm"));
 
         const sourceContent = response.source;
         Assert.ok(!!sourceContent);
         Assert.equal(typeof sourceContent, "object");
         Assert.ok("binary" in sourceContent);
--- a/devtools/shared/client/moz.build
+++ b/devtools/shared/client/moz.build
@@ -9,12 +9,11 @@ DevToolsModules(
     'connection-manager.js',
     'constants.js',
     'debugger-client.js',
     'environment-client.js',
     'event-source.js',
     'long-string-client.js',
     'object-client.js',
     'property-iterator-client.js',
-    'source-client.js',
     'symbol-iterator-client.js',
     'thread-client.js',
 )
--- a/devtools/shared/client/thread-client.js
+++ b/devtools/shared/client/thread-client.js
@@ -7,17 +7,17 @@
 
 const {arg, DebuggerClient} = require("devtools/shared/client/debugger-client");
 const eventSource = require("devtools/shared/client/event-source");
 const {ThreadStateTypes} = require("devtools/shared/client/constants");
 
 loader.lazyRequireGetter(this, "ArrayBufferClient", "devtools/shared/client/array-buffer-client");
 loader.lazyRequireGetter(this, "LongStringClient", "devtools/shared/client/long-string-client");
 loader.lazyRequireGetter(this, "ObjectClient", "devtools/shared/client/object-client");
-loader.lazyRequireGetter(this, "SourceClient", "devtools/shared/client/source-client");
+loader.lazyRequireGetter(this, "SourceFront", "devtools/shared/fronts/source", true);
 
 /**
  * Creates a thread client for the remote debugging protocol server. This client
  * is a front to the thread actor created in the server side, hiding the
  * protocol details in a traditional JavaScript API.
  *
  * @param client DebuggerClient
  * @param actor string
@@ -532,24 +532,24 @@ ThreadClient.prototype = {
    * Request to set the IDs of the active event breakpoints.
    */
   setActiveEventBreakpoints: DebuggerClient.requester({
     type: "setActiveEventBreakpoints",
     ids: arg(0),
   }),
 
   /**
-   * Return an instance of SourceClient for the given source actor form.
+   * Return an instance of SourceFront for the given source actor form.
    */
   source: function(form) {
     if (form.actor in this._threadGrips) {
       return this._threadGrips[form.actor];
     }
 
-    this._threadGrips[form.actor] = new SourceClient(this, form);
+    this._threadGrips[form.actor] = new SourceFront(this.client, form, this);
     return this._threadGrips[form.actor];
   },
 
   events: ["newSource", "progress"],
 };
 
 eventSource(ThreadClient.prototype);
 
--- a/devtools/shared/fronts/moz.build
+++ b/devtools/shared/fronts/moz.build
@@ -28,14 +28,15 @@ DevToolsModules(
     'perf.js',
     'performance-recording.js',
     'performance.js',
     'preference.js',
     'promises.js',
     'reflow.js',
     'root.js',
     'screenshot.js',
+    'source.js',
     'storage.js',
     'string.js',
     'styles.js',
     'stylesheets.js',
     'webconsole.js',
 )
rename from devtools/shared/client/source-client.js
rename to devtools/shared/fronts/source.js
--- a/devtools/shared/client/source-client.js
+++ b/devtools/shared/fronts/source.js
@@ -1,102 +1,66 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const {arg, DebuggerClient} = require("devtools/shared/client/debugger-client");
+const { sourceSpec } = require("devtools/shared/specs/source");
+const { FrontClassWithSpec, registerFront } = require("devtools/shared/protocol");
 
 /**
- * A SourceClient provides a way to access the source text of a script.
+ * A SourceFront provides a way to access the source text of a script.
  *
- * @param client ThreadClient
- *        The thread client parent.
+ * @param client DebuggerClient
+ *        The Debugger Client instance.
  * @param form Object
  *        The form sent across the remote debugging protocol.
+ * @param activeThread ThreadClient
+ *        The thread client parent. Used until the SourceFront marshalls LongStringFront
+ *        and ArrayBuffer.
  */
-function SourceClient(client, form) {
-  this._form = form;
-  this._activeThread = client;
-  this._client = client.client;
-}
+class SourceFront extends FrontClassWithSpec(sourceSpec) {
+  constructor(client, form, activeThread) {
+    super(client);
+    this._url = form.url;
+    this._activeThread = activeThread;
+    // this is here for the time being, until the source front is managed
+    // via protocol.js marshalling
+    this.actorID = form.actor;
+    this.manage(this);
+  }
 
-SourceClient.prototype = {
-  get _transport() {
-    return this._client._transport;
-  },
   get actor() {
-    return this._form.actor;
-  },
-  get request() {
-    return this._client.request;
-  },
+    return this.actorID;
+  }
+
   get url() {
-    return this._form.url;
-  },
+    return this._url;
+  }
 
-  /**
-   * Black box this SourceClient's source.
-   */
-  blackBox: DebuggerClient.requester(
-    {
-      type: "blackbox",
-      range: arg(0),
-    },
-    {
-      telemetry: "BLACKBOX",
-    },
-  ),
+  // Alias for source.blackbox to avoid changing protocol.js packets
+  blackBox(range) {
+    return this.blackbox(range);
+  }
+
+  // Alias for source.unblackbox to avoid changing protocol.js packets
+  unblackBox() {
+    return this.unblackbox();
+  }
 
   /**
-   * Un-black box this SourceClient's source.
+   * Get a long string grip for this SourceFront's source.
    */
-  unblackBox: DebuggerClient.requester(
-    {
-      type: "unblackbox",
-      range: arg(0),
-    },
-    {
-      telemetry: "UNBLACKBOX",
-    },
-  ),
-
-  getBreakpointPositions: function(query) {
-    const packet = {
-      to: this._form.actor,
-      type: "getBreakpointPositions",
-      query,
-    };
-    return this._client.request(packet);
-  },
+  async source() {
+    const response = await this.onSource();
+    return this._onSourceResponse(response);
+  }
 
-  getBreakpointPositionsCompressed: function(query) {
-    const packet = {
-      to: this._form.actor,
-      type: "getBreakpointPositionsCompressed",
-      query,
-    };
-    return this._client.request(packet);
-  },
-
-  /**
-   * Get a long string grip for this SourceClient's source.
-   */
-  source: function() {
-    const packet = {
-      to: this._form.actor,
-      type: "source",
-    };
-    return this._client.request(packet).then(response => {
-      return this._onSourceResponse(response);
-    });
-  },
-
-  _onSourceResponse: function(response) {
+  _onSourceResponse(response) {
     if (typeof response.source === "string") {
       return response;
     }
 
     const { contentType, source } = response;
     if (source.type === "arrayBuffer") {
       const arrayBuffer = this._activeThread.threadArrayBuffer(source);
       return arrayBuffer.slice(0, arrayBuffer.length).then(function(resp) {
@@ -124,21 +88,13 @@ SourceClient.prototype = {
       }
 
       const newResponse = {
         source: resp.substring,
         contentType: contentType,
       };
       return newResponse;
     });
-  },
+  }
+}
 
-  setPausePoints: function(pausePoints) {
-    const packet = {
-      to: this._form.actor,
-      type: "setPausePoints",
-      pausePoints,
-    };
-    return this._client.request(packet);
-  },
-};
-
-module.exports = SourceClient;
+exports.SourceFront = SourceFront;
+registerFront(SourceFront);
--- a/devtools/shared/specs/index.js
+++ b/devtools/shared/specs/index.js
@@ -170,17 +170,17 @@ const Types = exports.__TypesForTests = 
   {
     types: ["context"],
     spec: "devtools/shared/specs/script",
     front: null,
   },
   {
     types: ["source"],
     spec: "devtools/shared/specs/source",
-    front: null,
+    front: "devtools/shared/fronts/source",
   },
   {
     types: ["cookies", "localStorage", "sessionStorage", "Cache", "indexedDB", "storage"],
     spec: "devtools/shared/specs/storage",
     front: "devtools/shared/fronts/storage",
   },
   /* longstring is special, it has a wrapper type. See its spec module */
   {
--- a/devtools/shared/specs/source.js
+++ b/devtools/shared/specs/source.js
@@ -34,16 +34,18 @@ const sourceSpec = generateActorSpec({
       request: {
         query: Arg(0, "nullable:breakpointquery"),
       },
       response: {
         positions: RetVal("json"),
       },
     },
     onSource: {
+      // we are sending the type "source" to be compatible
+      // with FF67 and older
       request: { type: "source" },
       response: RetVal("json"),
     },
     setPausePoints: {
       request: {
         pausePoints: Arg(0, "json"),
       },
     },
--- a/docshell/test/iframesandbox/mochitest.ini
+++ b/docshell/test/iframesandbox/mochitest.ini
@@ -6,16 +6,17 @@ support-files =
   file_our_auxiliary_navigation_by_location.html
   file_parent_navigation_by_location.html
   file_sibling_navigation_by_location.html
   file_top_navigation_by_location.html
   file_top_navigation_by_location_exotic.html
 
 [test_child_navigation_by_location.html]
 [test_marquee_event_handlers.html]
+skip-if = toolkit == 'android' && !is_fennec # Bug 1455996
 [test_other_auxiliary_navigation_by_location.html]
 skip-if = toolkit == 'android' && !is_fennec # Bug 1525959
 tags = openwindow
 [test_our_auxiliary_navigation_by_location.html]
 tags = openwindow
 [test_parent_navigation_by_location.html]
 tags = openwindow
 [test_sibling_navigation_by_location.html]
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -3355,16 +3355,21 @@ class nsContentUtils {
   /**
    * Returns true if the top level ancestor content document of aDocument hasn't
    * yet had the first contentful paint and there is a high priority event
    * pending in the main thread.
    */
   static bool HighPriorityEventPendingForTopLevelDocumentBeforeContentfulPaint(
       Document* aDocument);
 
+  /**
+   * Gets the global cookie lifetime policy.
+   */
+  static uint32_t GetCookieLifetimePolicy() { return sCookiesLifetimePolicy; }
+
  private:
   static bool InitializeEventTable();
 
   static nsresult EnsureStringBundle(PropertiesFile aFile);
 
   static bool CanCallerAccess(nsIPrincipal* aSubjectPrincipal,
                               nsIPrincipal* aPrincipal);
 
--- a/dom/media/PeerConnection.jsm
+++ b/dom/media/PeerConnection.jsm
@@ -1623,17 +1623,17 @@ class RTCPeerConnection {
       id = null;
     } else if (id === null) {
       throw new this._win.DOMException(
           "id is required when negotiated is true", "TypeError");
     }
     if (maxPacketLifeTime !== undefined && maxRetransmits !== undefined) {
       throw new this._win.DOMException(
           "Both maxPacketLifeTime and maxRetransmits cannot be provided",
-          "InvalidParameterError");
+          "TypeError");
     }
     if (id == 65535) {
       throw new this._win.DOMException(
           "id cannot be 65535", "TypeError");
     }
     // Must determine the type where we still know if entries are undefined.
     let type;
     if (maxPacketLifeTime !== undefined) {
--- a/dom/media/webrtc/MediaTransportParent.h
+++ b/dom/media/webrtc/MediaTransportParent.h
@@ -19,17 +19,18 @@ class MediaTransportParent : public dom:
   mozilla::ipc::IPCResult RecvGetIceLog(const nsCString& pattern,
                                         GetIceLogResolver&& aResolve);
   mozilla::ipc::IPCResult RecvClearIceLog();
   mozilla::ipc::IPCResult RecvEnterPrivateMode();
   mozilla::ipc::IPCResult RecvExitPrivateMode();
   mozilla::ipc::IPCResult RecvCreateIceCtx(
       const string& name, nsTArray<RTCIceServer>&& iceServers,
       const RTCIceTransportPolicy& icePolicy);
-  mozilla::ipc::IPCResult RecvSetProxyServer(const PBrowserOrId& browserOrId,
+  mozilla::ipc::IPCResult RecvSetProxyServer(const dom::TabId& tabId,
+                                             const net::LoadInfoArgs& args,
                                              const nsCString& alpn);
   mozilla::ipc::IPCResult RecvEnsureProvisionalTransport(
       const string& transportId, const string& localUfrag,
       const string& localPwd, const int& componentCount);
   mozilla::ipc::IPCResult RecvStartIceGathering(
       const bool& defaultRouteOnly, const net::NrIceStunAddrArray& stunAddrs);
   mozilla::ipc::IPCResult RecvActivateTransport(
       const string& transportId, const string& localUfrag,
--- a/dom/media/webrtc/PMediaTransport.ipdl
+++ b/dom/media/webrtc/PMediaTransport.ipdl
@@ -1,29 +1,30 @@
 /* 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 protocol PSocketProcessBridge;
 
 #ifdef MOZ_WEBRTC
-include PBrowserOrId;
+include NeckoChannelParams;
 
 // ParamTraits stuff for generated code and other classes we don't want to change
 include "mozilla/net/NrIceStunAddrMessageUtils.h";
 include "mozilla/media/webrtc/WebrtcIPCTraits.h";
 using StringVector from "mozilla/media/webrtc/WebrtcIPCTraits.h";
 using CandidateInfo from "mozilla/media/webrtc/WebrtcIPCTraits.h";
 using DtlsDigestList from "mozilla/media/webrtc/WebrtcIPCTraits.h";
 using std::string from "ipc/IPCMessageUtils.h";
 using struct mozilla::dom::RTCStatsReportInternal from "mozilla/dom/RTCStatsReportBinding.h";
 using struct mozilla::dom::MovableRTCStatsReportInternal from "mozilla/media/webrtc/WebrtcGlobal.h";
 using WebrtcGlobalLog from "mozilla/media/webrtc/WebrtcGlobal.h";
 using mozilla::dom::RTCIceServer from "mozilla/dom/RTCConfigurationBinding.h";
 using mozilla::dom::RTCIceTransportPolicy from "mozilla/dom/RTCConfigurationBinding.h";
+using TabId from "mozilla/dom/ipc/IdType.h";
 
 // ParamTraits stuff for our own classes
 using MediaPacket from "mtransport/mediapacket.h";
 using net::NrIceStunAddrArray from "mozilla/net/PStunAddrsParams.h";
 #endif // MOZ_WEBRTC
 
 namespace mozilla {
 namespace dom {
@@ -39,17 +40,18 @@ parent:
   async ClearIceLog();
   async EnterPrivateMode();
   async ExitPrivateMode();
 
   async CreateIceCtx(string name,
                      RTCIceServer[] iceServers,
                      RTCIceTransportPolicy icePolicy);
 
-  async SetProxyServer(PBrowserOrId browserOrId,
+  async SetProxyServer(TabId id,
+                       LoadInfoArgs args, // Does this have the id?
                        nsCString alpn);
 
   async EnsureProvisionalTransport(string transportId,
                                    string localUfrag,
                                    string localPwd,
                                    int componentCount);
 
   async StartIceGathering(bool defaultRouteOnly,
--- a/extensions/cookie/nsCookiePermission.cpp
+++ b/extensions/cookie/nsCookiePermission.cpp
@@ -1,59 +1,50 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 sw=2 et: */
 /* 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 "nsCookiePermission.h"
 
-#include "mozIThirdPartyUtil.h"
 #include "nsICookie2.h"
 #include "nsIServiceManager.h"
 #include "nsICookieManager.h"
+#include "nsICookieService.h"
 #include "nsNetUtil.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIProtocolHandler.h"
 #include "nsIURI.h"
-#include "nsIPrefService.h"
-#include "nsIPrefBranch.h"
 #include "nsIChannel.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIDOMWindow.h"
 #include "nsIPrincipal.h"
 #include "nsString.h"
 #include "nsCRT.h"
 #include "nsILoadContext.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsNetCID.h"
 #include "prtime.h"
 #include "mozilla/StaticPtr.h"
+#include "nsContentUtils.h"
 
 /****************************************************************
  ************************ nsCookiePermission ********************
  ****************************************************************/
 
-// values for mCookiesLifetimePolicy
-// 0 == accept normally
-// 1 == ask before accepting, no more supported, treated like ACCEPT_NORMALLY
-// (Bug 606655). 2 == downgrade to session 3 == limit lifetime to N days
-static const uint32_t ACCEPT_NORMALLY = 0;
-static const uint32_t ACCEPT_SESSION = 2;
-
 static const bool kDefaultPolicy = true;
-static const char kCookiesLifetimePolicy[] = "network.cookie.lifetimePolicy";
 
 static const nsLiteralCString kPermissionType(NS_LITERAL_CSTRING("cookie"));
 
 namespace {
 mozilla::StaticRefPtr<nsCookiePermission> gSingleton;
 }
 
-NS_IMPL_ISUPPORTS(nsCookiePermission, nsICookiePermission, nsIObserver)
+NS_IMPL_ISUPPORTS(nsCookiePermission, nsICookiePermission)
 
 // static
 already_AddRefed<nsICookiePermission> nsCookiePermission::GetOrCreate() {
   if (!gSingleton) {
     gSingleton = new nsCookiePermission();
   }
   return do_AddRef(gSingleton);
 }
@@ -63,44 +54,20 @@ void nsCookiePermission::Shutdown() { gS
 
 bool nsCookiePermission::Init() {
   // Initialize nsIPermissionManager and fetch relevant prefs. This is only
   // required for some methods on nsICookiePermission, so it should be done
   // lazily.
   nsresult rv;
   mPermMgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
   if (NS_FAILED(rv)) return false;
-  mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv);
-  if (NS_FAILED(rv)) return false;
-
-  // failure to access the pref service is non-fatal...
-  nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
-  if (prefBranch) {
-    prefBranch->AddObserver(kCookiesLifetimePolicy, this, false);
-    PrefChanged(prefBranch, nullptr);
-  }
 
   return true;
 }
 
-void nsCookiePermission::PrefChanged(nsIPrefBranch *aPrefBranch,
-                                     const char *aPref) {
-  int32_t val;
-
-#define PREF_CHANGED(_P) (!aPref || !strcmp(aPref, _P))
-
-  if (PREF_CHANGED(kCookiesLifetimePolicy) &&
-      NS_SUCCEEDED(aPrefBranch->GetIntPref(kCookiesLifetimePolicy, &val))) {
-    if (val != static_cast<int32_t>(ACCEPT_SESSION)) {
-      val = ACCEPT_NORMALLY;
-    }
-    mCookiesLifetimePolicy = val;
-  }
-}
-
 NS_IMETHODIMP
 nsCookiePermission::SetAccess(nsIURI *aURI, nsCookieAccess aAccess) {
   // Lazily initialize ourselves
   if (!EnsureInitialized()) return NS_ERROR_UNEXPECTED;
 
   //
   // NOTE: nsCookieAccess values conveniently match up with
   //       the permission codes used by nsIPermissionManager.
@@ -124,33 +91,16 @@ nsCookiePermission::CanAccess(nsIPrincip
       *aResult = nsICookiePermission::ACCESS_ALLOW;
     }
   }
 
   return rv;
 }
 
 NS_IMETHODIMP
-nsCookiePermission::CanAccessURI(nsIURI *aURI, nsCookieAccess *aResult) {
-  // Lazily initialize ourselves
-  if (!EnsureInitialized()) return NS_ERROR_UNEXPECTED;
-
-  // finally, check with permission manager...
-  nsresult rv =
-      mPermMgr->TestPermission(aURI, kPermissionType, (uint32_t *)aResult);
-  if (NS_SUCCEEDED(rv)) {
-    if (*aResult == nsICookiePermission::ACCESS_SESSION) {
-      *aResult = nsICookiePermission::ACCESS_ALLOW;
-    }
-  }
-
-  return rv;
-}
-
-NS_IMETHODIMP
 nsCookiePermission::CanSetCookie(nsIURI *aURI, nsIChannel *aChannel,
                                  nsICookie2 *aCookie, bool *aIsSession,
                                  int64_t *aExpiry, bool *aResult) {
   NS_ASSERTION(aURI, "null uri");
 
   *aResult = kDefaultPolicy;
 
   // Lazily initialize ourselves
@@ -171,41 +121,31 @@ nsCookiePermission::CanSetCookie(nsIURI 
       *aResult = false;
       break;
 
     default:
       // Here we can have any legacy permission value.
 
       // now we need to figure out what type of accept policy we're dealing with
       // if we accept cookies normally, just bail and return
-      if (mCookiesLifetimePolicy == ACCEPT_NORMALLY) {
+      if (nsContentUtils::GetCookieLifetimePolicy() ==
+          nsICookieService::ACCEPT_NORMALLY) {
         *aResult = true;
         return NS_OK;
       }
 
       // declare this here since it'll be used in all of the remaining cases
       int64_t currentTime = PR_Now() / PR_USEC_PER_SEC;
       int64_t delta = *aExpiry - currentTime;
 
       // We are accepting the cookie, but,
       // if it's not a session cookie, we may have to limit its lifetime.
       if (!*aIsSession && delta > 0) {
-        if (mCookiesLifetimePolicy == ACCEPT_SESSION) {
+        if (nsContentUtils::GetCookieLifetimePolicy() ==
+            nsICookieService::ACCEPT_SESSION) {
           // limit lifetime to session
           *aIsSession = true;
         }
       }
   }
 
   return NS_OK;
 }
-
-NS_IMETHODIMP
-nsCookiePermission::Observe(nsISupports *aSubject, const char *aTopic,
-                            const char16_t *aData) {
-  nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
-  NS_ASSERTION(!nsCRT::strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic),
-               "unexpected topic - we only deal with pref changes!");
-
-  if (prefBranch)
-    PrefChanged(prefBranch, NS_LossyConvertUTF16toASCII(aData).get());
-  return NS_OK;
-}
--- a/extensions/cookie/nsCookiePermission.h
+++ b/extensions/cookie/nsCookiePermission.h
@@ -2,45 +2,30 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsCookiePermission_h__
 #define nsCookiePermission_h__
 
 #include "nsICookiePermission.h"
 #include "nsIPermissionManager.h"
-#include "nsIObserver.h"
 #include "nsCOMPtr.h"
-#include "mozIThirdPartyUtil.h"
 
-class nsIPrefBranch;
-
-class nsCookiePermission final : public nsICookiePermission,
-                                 public nsIObserver {
+class nsCookiePermission final : public nsICookiePermission {
  public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSICOOKIEPERMISSION
-  NS_DECL_NSIOBSERVER
 
   // Singleton accessor
   static already_AddRefed<nsICookiePermission> GetOrCreate();
   static void Shutdown();
 
   bool Init();
-  void PrefChanged(nsIPrefBranch *, const char *);
 
  private:
-  nsCookiePermission()
-      : mCookiesLifetimePolicy(0)  // ACCEPT_NORMALLY
-  {}
-  virtual ~nsCookiePermission() {}
+  ~nsCookiePermission() = default;
 
-  bool EnsureInitialized() {
-    return (mPermMgr != nullptr && mThirdPartyUtil != nullptr) || Init();
-  };
+  bool EnsureInitialized() { return (mPermMgr != nullptr) || Init(); };
 
   nsCOMPtr<nsIPermissionManager> mPermMgr;
-  nsCOMPtr<mozIThirdPartyUtil> mThirdPartyUtil;
-
-  uint8_t mCookiesLifetimePolicy;  // pref for how long cookies are stored
 };
 
 #endif
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -375,16 +375,42 @@ static bool AsyncTransformShouldBeUnappl
     // don't intend to match.
     if (current && current.AsRefLayer() != nullptr) {
       break;
     }
   }
   return false;
 }
 
+/**
+ * Given a fixed-position layer, check if it's fixed with respect to the
+ * zoomed APZC.
+ */
+static bool IsFixedToZoomContainer(Layer* aFixedLayer) {
+  ScrollableLayerGuid::ViewID targetId =
+      aFixedLayer->GetFixedPositionScrollContainerId();
+  MOZ_ASSERT(targetId != ScrollableLayerGuid::NULL_SCROLL_ID);
+  LayerMetricsWrapper result(aFixedLayer, LayerMetricsWrapper::StartAt::BOTTOM);
+  while (result) {
+    if (Maybe<ScrollableLayerGuid::ViewID> zoomedScrollId =
+            result.IsAsyncZoomContainer()) {
+      return *zoomedScrollId == targetId;
+    }
+    // Don't ascend into another layer tree. Scroll IDs are not unique
+    // across layer trees, and in any case position:fixed doesn't reach
+    // across documents.
+    if (result.AsRefLayer() != nullptr) {
+      break;
+    }
+
+    result = result.GetParent();
+  }
+  return false;
+}
+
 // If |aLayer| is fixed or sticky, returns the scroll id of the scroll frame
 // that it's fixed or sticky to. Otherwise, returns Nothing().
 static Maybe<ScrollableLayerGuid::ViewID> IsFixedOrSticky(Layer* aLayer) {
   bool isRootOfFixedSubtree = aLayer->GetIsFixedPosition() &&
                               !aLayer->GetParent()->GetIsFixedPosition();
   if (isRootOfFixedSubtree) {
     return Some(aLayer->GetFixedPositionScrollContainerId());
   }
@@ -1079,17 +1105,17 @@ bool AsyncCompositionManager::ApplyAsync
                   // AsyncPanZoomController::NotifyLayersUpdated because the
                   // root content document is not scrollable. So update it here
                   // so it knows if the root composition size has changed.
                   if (animator && !metrics.IsRootContent()) {
                     animator->MaybeUpdateCompositionSizeAndRootFrameMetrics(
                         metrics);
                   }
                 }
-                fixedLayerMargins = mFixedLayerMargins;
+                fixedLayerMargins = GetFixedLayerMargins();
               }
             }
 #else
             *aOutFoundRoot = false;
             // Non-Android platforms still care about this flag being cleared
             // after the first call to TransformShadowTree().
             mIsFirstPaint = false;
 #endif
@@ -1192,16 +1218,37 @@ bool AsyncCompositionManager::ApplyAsync
               combinedAsyncTransform *=
                   AsyncTransformComponentMatrix(zoomTransform);
             } else {
               // TODO: Is this normal? It happens on some pages, such as
               // about:config on mobile, for just one frame or so, before the
               // scroll metadata for zoomedScrollId appears in the layer tree.
             }
           }
+
+          // Layers fixed to the RCD-RSF no longer need
+          // AdjustFixedOrStickyLayer() to scroll them by the eVisual transform,
+          // as that's now applied to the async zoom container itself. However,
+          // we still need to adjust them by the fixed layer margins to
+          // account for dynamic toolbar transitions. This is also handled by
+          // AdjustFixedOrStickyLayer(), so we now call it with empty transforms
+          // to get it to perform just the fixed margins adjustment.
+          if (zoomedMetrics && layer->GetIsFixedPosition() &&
+              !layer->GetParent()->GetIsFixedPosition() &&
+              IsFixedToZoomContainer(layer)) {
+            LayerToParentLayerMatrix4x4 emptyTransform;
+            ScreenMargin marginsForFixedLayer;
+#ifdef MOZ_WIDGET_ANDROID
+            marginsForFixedLayer = GetFixedLayerMargins();
+#endif
+            AdjustFixedOrStickyLayer(zoomContainer, layer,
+                                     sampler->GetGuid(*zoomedMetrics).mScrollId,
+                                     emptyTransform, emptyTransform,
+                                     marginsForFixedLayer, clipPartsCache);
+          }
         }
 
         bool clipChanged = (hasAsyncTransform || clipDeferredFromChildren ||
                             layer->GetScrolledClipRect());
         if (clipChanged) {
           // Intersect the two clip parts and apply them to the layer.
           // During ApplyAsyncContentTransformTree on an ancestor layer,
           // AlignFixedAndStickyLayers may overwrite this with a new clip it
@@ -1402,19 +1449,19 @@ bool AsyncCompositionManager::TransformS
 
   if (!(aSkip & CompositorBridgeParentBase::TransformsToSkip::APZ)) {
     // Apply an async content transform to any layer that has
     // an async pan zoom controller.
     bool foundRoot = false;
     if (ApplyAsyncContentTransformToTree(root, &foundRoot)) {
 #if defined(MOZ_WIDGET_ANDROID)
       MOZ_ASSERT(foundRoot);
-      if (foundRoot && mFixedLayerMargins != ScreenMargin()) {
+      if (foundRoot && GetFixedLayerMargins() != ScreenMargin()) {
         MoveScrollbarForLayerMargin(root, mRootScrollableId,
-                                    mFixedLayerMargins);
+                                    GetFixedLayerMargins());
       }
 #endif
     }
 
     bool apzAnimating = false;
     if (RefPtr<APZSampler> apz = mCompositorBridge->GetAPZSampler()) {
       apzAnimating =
           apz->SampleAnimations(LayerMetricsWrapper(root), nextFrame);
@@ -1436,12 +1483,20 @@ bool AsyncCompositionManager::TransformS
 }
 
 #if defined(MOZ_WIDGET_ANDROID)
 void AsyncCompositionManager::SetFixedLayerMargins(ScreenIntCoord aTop,
                                                    ScreenIntCoord aBottom) {
   mFixedLayerMargins.top = aTop;
   mFixedLayerMargins.bottom = aBottom;
 }
+ScreenMargin AsyncCompositionManager::GetFixedLayerMargins() const {
+  ScreenMargin result = mFixedLayerMargins;
+  if (gfxPrefs::APZFixedMarginOverrideEnabled()) {
+    result.top = gfxPrefs::APZFixedMarginOverrideTop();
+    result.bottom = gfxPrefs::APZFixedMarginOverrideBottom();
+  }
+  return result;
+}
 #endif  // defined(MOZ_WIDGET_ANDROID)
 
 }  // namespace layers
 }  // namespace mozilla
--- a/gfx/layers/composite/AsyncCompositionManager.h
+++ b/gfx/layers/composite/AsyncCompositionManager.h
@@ -232,16 +232,17 @@ class AsyncCompositionManager final {
 
   TimeStamp mPreviousFrameTimeStamp;
 
   MOZ_NON_OWNING_REF CompositorBridgeParent* mCompositorBridge;
 
 #ifdef MOZ_WIDGET_ANDROID
  public:
   void SetFixedLayerMargins(ScreenIntCoord aTop, ScreenIntCoord aBottom);
+  ScreenMargin GetFixedLayerMargins() const;
 
  private:
   // This calculates whether frame metrics should be sent to Java.
   bool FrameMetricsHaveUpdated(const FrameMetrics& aMetrics);
   // This holds the most recent scroll/zoom metrics sent to Java, and is used
   // to send new updates when it changes.
   FrameMetrics mLastMetrics;
   // The following two fields are only needed on Fennec with C++ APZ, because
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -312,16 +312,19 @@ class gfxPrefs final {
   DECL_GFX_PREF(Live, "apz.danger_zone_x",                     APZDangerZoneX, int32_t, 50);
   DECL_GFX_PREF(Live, "apz.danger_zone_y",                     APZDangerZoneY, int32_t, 100);
   DECL_GFX_PREF(Live, "apz.disable_for_scroll_linked_effects", APZDisableForScrollLinkedEffects, bool, false);
   DECL_GFX_PREF(Live, "apz.displayport_expiry_ms",             APZDisplayPortExpiryTime, uint32_t, 15000);
   DECL_GFX_PREF(Live, "apz.drag.enabled",                      APZDragEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.drag.initial.enabled",              APZDragInitiationEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.drag.touch.enabled",                APZTouchDragEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.enlarge_displayport_when_clipped",  APZEnlargeDisplayPortWhenClipped, bool, false);
+  DECL_GFX_PREF(Live, "apz.fixed-margin-override.enabled",     APZFixedMarginOverrideEnabled, bool, false);
+  DECL_GFX_PREF(Live, "apz.fixed-margin-override.bottom",      APZFixedMarginOverrideBottom, int32_t, 0);
+  DECL_GFX_PREF(Live, "apz.fixed-margin-override.top",         APZFixedMarginOverrideTop, int32_t, 0);
   DECL_GFX_PREF(Live, "apz.fling_accel_base_mult",             APZFlingAccelBaseMultiplier, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.fling_accel_interval_ms",           APZFlingAccelInterval, int32_t, 500);
   DECL_GFX_PREF(Live, "apz.fling_accel_supplemental_mult",     APZFlingAccelSupplementalMultiplier, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.fling_accel_min_velocity",          APZFlingAccelMinVelocity, float, 1.5f);
   DECL_GFX_PREF(Once, "apz.fling_curve_function_x1",           APZCurveFunctionX1, float, 0.0f);
   DECL_GFX_PREF(Once, "apz.fling_curve_function_x2",           APZCurveFunctionX2, float, 1.0f);
   DECL_GFX_PREF(Once, "apz.fling_curve_function_y1",           APZCurveFunctionY1, float, 0.0f);
   DECL_GFX_PREF(Once, "apz.fling_curve_function_y2",           APZCurveFunctionY2, float, 1.0f);
--- a/js/public/ProfilingCategory.h
+++ b/js/public/ProfilingCategory.h
@@ -42,16 +42,19 @@
     SUBCATEGORY(LAYOUT, LAYOUT_FrameConstruction, "Frame construction")       \
     SUBCATEGORY(LAYOUT, LAYOUT_Reflow, "Reflow")                              \
     SUBCATEGORY(LAYOUT, LAYOUT_CSSParsing, "CSS parsing")                     \
     SUBCATEGORY(LAYOUT, LAYOUT_SelectorQuery, "Selector query")               \
     SUBCATEGORY(LAYOUT, LAYOUT_StyleComputation, "Style computation")         \
   END_CATEGORY                                                                \
   BEGIN_CATEGORY(JS, "JavaScript", "yellow")                                  \
     SUBCATEGORY(JS, JS, "Other")                                              \
+    SUBCATEGORY(JS, JS_Parsing, "JS Parsing")                                 \
+    SUBCATEGORY(JS, JS_IonCompilation, "Ion JIT Compilation")                 \
+    SUBCATEGORY(JS, JS_BaselineCompilation, "Baseline JIT Compilation")       \
   END_CATEGORY                                                                \
   BEGIN_CATEGORY(GCCC, "GC / CC", "orange")                                   \
     SUBCATEGORY(GCCC, GCCC, "Other")                                          \
   END_CATEGORY                                                                \
   BEGIN_CATEGORY(NETWORK, "Network", "lightblue")                             \
     SUBCATEGORY(NETWORK, NETWORK, "Other")                                    \
   END_CATEGORY                                                                \
   BEGIN_CATEGORY(GRAPHICS, "Graphics", "green")                               \
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -523,26 +523,28 @@ JSScript* frontend::ScriptCompiler<Unit>
     return nullptr;
   }
 
   JSContext* cx = info.cx;
 
   for (;;) {
     ParseNode* pn;
     {
-      AutoGeckoProfilerEntry pseudoFrame(cx, "script parsing");
+      AutoGeckoProfilerEntry pseudoFrame(cx, "script parsing",
+                                         JS::ProfilingCategoryPair::JS_Parsing);
       if (sc->isEvalContext()) {
         pn = parser->evalBody(sc->asEvalContext());
       } else {
         pn = parser->globalBody(sc->asGlobalContext());
       }
     }
 
     // Successfully parsed. Emit the script.
-    AutoGeckoProfilerEntry pseudoFrame(cx, "script emit");
+    AutoGeckoProfilerEntry pseudoFrame(cx, "script emit",
+                                       JS::ProfilingCategoryPair::JS_Parsing);
     if (pn) {
       if (sc->isEvalContext() && sc->hasDebuggerStatement() &&
           !cx->helperThread()) {
         // If the eval'ed script contains any debugger statement, force
         // construction of arguments objects for the caller script and any other
         // scripts it is transitively nested inside. The debugger can access any
         // variable on the scope chain.
         if (!info.deoptimizeArgumentsInEnclosingScripts(cx, environment)) {
--- a/js/src/jit/BaselineFrameInfo.h
+++ b/js/src/jit/BaselineFrameInfo.h
@@ -367,17 +367,21 @@ class InterpreterFrameInfo : public Fram
   }
 
   void popRegsAndSync(uint32_t uses);
 
   void pop() { popn(1); }
 
   void popn(uint32_t n) { masm.addToStackPtr(Imm32(n * sizeof(Value))); }
 
-  void popn(Register reg) { masm.addToStackPtr(reg); }
+  void popn(Register reg) {
+    // sp := sp + reg * sizeof(Value)
+    Register spReg = AsRegister(masm.getStackPointer());
+    masm.computeEffectiveAddress(BaseValueIndex(spReg, reg), spReg);
+  }
 
   void popValue(ValueOperand dest) { masm.popValue(dest); }
 
   void push(const ValueOperand& val,
             JSValueType knownType = JSVAL_TYPE_UNKNOWN) {
     masm.pushValue(val);
   }
   void push(const Value& val) { masm.pushValue(val); }
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -734,23 +734,31 @@ static void TryAttachStub(const char* na
 
   if (stub->state().canAttachStub()) {
     RootedScript script(cx, frame->script());
     jsbytecode* pc = stub->icEntry()->pc(script);
 
     bool attached = false;
     IRGenerator gen(cx, script, pc, stub->state().mode(),
                     std::forward<Args>(args)...);
-    if (gen.tryAttachStub()) {
-      ICStub* newStub = AttachBaselineCacheIRStub(
-          cx, gen.writerRef(), gen.cacheKind(), kind, script, stub, &attached);
-      if (newStub) {
-        JitSpew(JitSpew_BaselineIC, "  %s %s CacheIR stub",
-                attached ? "Attached" : "Failed to attach", name);
-      }
+    switch (gen.tryAttachStub()) {
+      case AttachDecision::Attach: {
+        ICStub* newStub =
+            AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
+                                      kind, script, stub, &attached);
+        if (newStub) {
+          JitSpew(JitSpew_BaselineIC, "  Attached %s CacheIR stub", name);
+        }
+      } break;
+      case AttachDecision::NoAction:
+        break;
+      case AttachDecision::TemporarilyUnoptimizable:
+      case AttachDecision::Deferred:
+        MOZ_ASSERT_UNREACHABLE("Not expected in generic TryAttachStub");
+        break;
     }
     if (!attached) {
       stub->state().trackNotAttached();
     }
   }
 }
 
 //
@@ -2028,16 +2036,59 @@ static void StripPreliminaryObjectStubs(
       iter.unlink(cx);
     } else if (iter->isCacheIR_Updated() &&
                iter->toCacheIR_Updated()->hasPreliminaryObject()) {
       iter.unlink(cx);
     }
   }
 }
 
+static bool TryAttachGetPropStub(const char* name, JSContext* cx,
+                                 BaselineFrame* frame, ICFallbackStub* stub,
+                                 CacheKind kind, HandleValue val,
+                                 HandleValue idVal, HandleValue receiver) {
+  bool attached = false;
+
+  if (stub->state().maybeTransition()) {
+    stub->discardStubs(cx);
+  }
+
+  if (stub->state().canAttachStub()) {
+    RootedScript script(cx, frame->script());
+    jsbytecode* pc = stub->icEntry()->pc(script);
+
+    GetPropIRGenerator gen(cx, script, pc, stub->state().mode(), kind, val,
+                           idVal, receiver, GetPropertyResultFlags::All);
+    switch (gen.tryAttachStub()) {
+      case AttachDecision::Attach: {
+        ICStub* newStub = AttachBaselineCacheIRStub(
+            cx, gen.writerRef(), gen.cacheKind(),
+            BaselineCacheIRStubKind::Monitored, script, stub, &attached);
+        if (newStub) {
+          JitSpew(JitSpew_BaselineIC, "  Attached %s CacheIR stub", name);
+          if (gen.shouldNotePreliminaryObjectStub()) {
+            newStub->toCacheIR_Monitored()->notePreliminaryObject();
+          } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
+            StripPreliminaryObjectStubs(cx, stub);
+          }
+        }
+      } break;
+      case AttachDecision::NoAction:
+        break;
+      case AttachDecision::TemporarilyUnoptimizable:
+        attached = true;
+        break;
+      case AttachDecision::Deferred:
+        MOZ_ASSERT_UNREACHABLE("No deferred GetProp stubs");
+        break;
+    }
+  }
+  return attached;
+}
+
 //
 // GetElem_Fallback
 //
 
 bool DoGetElemFallback(JSContext* cx, BaselineFrame* frame,
                        ICGetElem_Fallback* stub, HandleValue lhs,
                        HandleValue rhs, MutableHandleValue res) {
   stub->incrementEnteredCount();
@@ -2061,44 +2112,18 @@ bool DoGetElemFallback(JSContext* cx, Ba
                                    &isOptimizedArgs)) {
       return false;
     }
     if (isOptimizedArgs) {
       TypeScript::Monitor(cx, script, pc, types, res);
     }
   }
 
-  bool attached = false;
-  bool isTemporarilyUnoptimizable = false;
-
-  if (stub->state().maybeTransition()) {
-    stub->discardStubs(cx);
-  }
-
-  if (stub->state().canAttachStub()) {
-    GetPropIRGenerator gen(cx, script, pc, CacheKind::GetElem,
-                           stub->state().mode(), &isTemporarilyUnoptimizable,
-                           lhs, rhs, lhs, GetPropertyResultFlags::All);
-    if (gen.tryAttachStub()) {
-      ICStub* newStub = AttachBaselineCacheIRStub(
-          cx, gen.writerRef(), gen.cacheKind(),
-          BaselineCacheIRStubKind::Monitored, script, stub, &attached);
-      if (newStub) {
-        JitSpew(JitSpew_BaselineIC, "  Attached GetElem CacheIR stub");
-        if (gen.shouldNotePreliminaryObjectStub()) {
-          newStub->toCacheIR_Monitored()->notePreliminaryObject();
-        } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
-          StripPreliminaryObjectStubs(cx, stub);
-        }
-      }
-    }
-    if (!attached && !isTemporarilyUnoptimizable) {
-      stub->state().trackNotAttached();
-    }
-  }
+  bool attached = TryAttachGetPropStub("GetElem", cx, frame, stub,
+                                       CacheKind::GetElem, lhs, rhs, lhs);
 
   if (!isOptimizedArgs) {
     if (!GetElementOperation(cx, op, lhsCopy, rhs, res)) {
       return false;
     }
     TypeScript::Monitor(cx, script, pc, types, res);
   }
 
@@ -2139,44 +2164,19 @@ bool DoGetElemSuperFallback(JSContext* c
   jsbytecode* pc = stub->icEntry()->pc(frame->script());
   StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
 
   JSOp op = JSOp(*pc);
   FallbackICSpew(cx, stub, "GetElemSuper(%s)", CodeName[op]);
 
   MOZ_ASSERT(op == JSOP_GETELEM_SUPER);
 
-  bool attached = false;
-  bool isTemporarilyUnoptimizable = false;
-
-  if (stub->state().maybeTransition()) {
-    stub->discardStubs(cx);
-  }
-
-  if (stub->state().canAttachStub()) {
-    GetPropIRGenerator gen(cx, script, pc, CacheKind::GetElemSuper,
-                           stub->state().mode(), &isTemporarilyUnoptimizable,
-                           lhs, rhs, receiver, GetPropertyResultFlags::All);
-    if (gen.tryAttachStub()) {
-      ICStub* newStub = AttachBaselineCacheIRStub(
-          cx, gen.writerRef(), gen.cacheKind(),
-          BaselineCacheIRStubKind::Monitored, script, stub, &attached);
-      if (newStub) {
-        JitSpew(JitSpew_BaselineIC, "  Attached GetElemSuper CacheIR stub");
-        if (gen.shouldNotePreliminaryObjectStub()) {
-          newStub->toCacheIR_Monitored()->notePreliminaryObject();
-        } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
-          StripPreliminaryObjectStubs(cx, stub);
-        }
-      }
-    }
-    if (!attached && !isTemporarilyUnoptimizable) {
-      stub->state().trackNotAttached();
-    }
-  }
+  bool attached =
+      TryAttachGetPropStub("GetElemSuper", cx, frame, stub,
+                           CacheKind::GetElemSuper, lhs, rhs, receiver);
 
   // |lhs| is [[HomeObject]].[[Prototype]] which must be Object
   RootedObject lhsObj(cx, &lhs.toObject());
   if (!GetObjectElementOperation(cx, op, lhsObj, receiver, rhs, res)) {
     return false;
   }
   TypeScript::Monitor(cx, script, pc, types, res);
 
@@ -2296,16 +2296,18 @@ static void SetUpdateStubData(ICCacheIR_
     stub->updateStubGroup() = info->group();
     stub->updateStubId() = info->id();
   }
 }
 
 bool DoSetElemFallback(JSContext* cx, BaselineFrame* frame,
                        ICSetElem_Fallback* stub, Value* stack, HandleValue objv,
                        HandleValue index, HandleValue rhs) {
+  using DeferType = SetPropIRGenerator::DeferType;
+
   stub->incrementEnteredCount();
 
   RootedScript script(cx, frame->script());
   RootedScript outerScript(cx, script);
   jsbytecode* pc = stub->icEntry()->pc(script);
   JSOp op = JSOp(*pc);
   FallbackICSpew(cx, stub, "SetElem(%s)", CodeName[JSOp(*pc)]);
 
@@ -2319,47 +2321,56 @@ bool DoSetElemFallback(JSContext* cx, Ba
   }
 
   RootedShape oldShape(cx, obj->shape());
   RootedObjectGroup oldGroup(cx, JSObject::getGroup(cx, obj));
   if (!oldGroup) {
     return false;
   }
 
-  bool isTemporarilyUnoptimizable = false;
-  bool canAddSlot = false;
+  DeferType deferType = DeferType::None;
   bool attached = false;
 
   if (stub->state().maybeTransition()) {
     stub->discardStubs(cx);
   }
 
   if (stub->state().canAttachStub()) {
     SetPropIRGenerator gen(cx, script, pc, CacheKind::SetElem,
-                           stub->state().mode(), &isTemporarilyUnoptimizable,
-                           &canAddSlot, objv, index, rhs);
-    if (gen.tryAttachStub()) {
-      ICStub* newStub = AttachBaselineCacheIRStub(
-          cx, gen.writerRef(), gen.cacheKind(),
-          BaselineCacheIRStubKind::Updated, frame->script(), stub, &attached);
-      if (newStub) {
-        JitSpew(JitSpew_BaselineIC, "  Attached SetElem CacheIR stub");
-
-        SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
-
-        if (gen.shouldNotePreliminaryObjectStub()) {
-          newStub->toCacheIR_Updated()->notePreliminaryObject();
-        } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
-          StripPreliminaryObjectStubs(cx, stub);
+                           stub->state().mode(), objv, index, rhs);
+    switch (gen.tryAttachStub()) {
+      case AttachDecision::Attach: {
+        ICStub* newStub = AttachBaselineCacheIRStub(
+            cx, gen.writerRef(), gen.cacheKind(),
+            BaselineCacheIRStubKind::Updated, frame->script(), stub, &attached);
+        if (newStub) {
+          JitSpew(JitSpew_BaselineIC, "  Attached SetElem CacheIR stub");
+
+          SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
+
+          if (gen.shouldNotePreliminaryObjectStub()) {
+            newStub->toCacheIR_Updated()->notePreliminaryObject();
+          } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
+            StripPreliminaryObjectStubs(cx, stub);
+          }
+
+          if (gen.attachedTypedArrayOOBStub()) {
+            stub->noteHasTypedArrayOOB();
+          }
         }
-
-        if (gen.attachedTypedArrayOOBStub()) {
-          stub->noteHasTypedArrayOOB();
-        }
-      }
+      } break;
+      case AttachDecision::NoAction:
+        break;
+      case AttachDecision::TemporarilyUnoptimizable:
+        attached = true;
+        break;
+      case AttachDecision::Deferred:
+        deferType = gen.deferType();
+        MOZ_ASSERT(deferType != DeferType::None);
+        break;
     }
   }
 
   if (op == JSOP_INITELEM || op == JSOP_INITHIDDENELEM) {
     if (!InitElemOperation(cx, pc, obj, index, rhs)) {
       return false;
     }
   } else if (op == JSOP_INITELEM_ARRAY) {
@@ -2397,44 +2408,54 @@ bool DoSetElemFallback(JSContext* cx, Ba
   }
 
   // The SetObjectElement call might have entered this IC recursively, so try
   // to transition.
   if (stub->state().maybeTransition()) {
     stub->discardStubs(cx);
   }
 
-  if (stub->state().canAttachStub()) {
+  bool canAttachStub = stub->state().canAttachStub();
+
+  if (deferType != DeferType::None && canAttachStub) {
     SetPropIRGenerator gen(cx, script, pc, CacheKind::SetElem,
-                           stub->state().mode(), &isTemporarilyUnoptimizable,
-                           &canAddSlot, objv, index, rhs);
-    if (canAddSlot && gen.tryAttachAddSlotStub(oldGroup, oldShape)) {
-      ICStub* newStub = AttachBaselineCacheIRStub(
-          cx, gen.writerRef(), gen.cacheKind(),
-          BaselineCacheIRStubKind::Updated, frame->script(), stub, &attached);
-      if (newStub) {
-        JitSpew(JitSpew_BaselineIC, "  Attached SetElem CacheIR stub");
-
-        SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
-
-        if (gen.shouldNotePreliminaryObjectStub()) {
-          newStub->toCacheIR_Updated()->notePreliminaryObject();
-        } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
-          StripPreliminaryObjectStubs(cx, stub);
+                           stub->state().mode(), objv, index, rhs);
+
+    MOZ_ASSERT(deferType == DeferType::AddSlot);
+    AttachDecision decision = gen.tryAttachAddSlotStub(oldGroup, oldShape);
+
+    switch (decision) {
+      case AttachDecision::Attach: {
+        ICStub* newStub = AttachBaselineCacheIRStub(
+            cx, gen.writerRef(), gen.cacheKind(),
+            BaselineCacheIRStubKind::Updated, frame->script(), stub, &attached);
+        if (newStub) {
+          JitSpew(JitSpew_BaselineIC, "  Attached SetElem CacheIR stub");
+
+          SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
+
+          if (gen.shouldNotePreliminaryObjectStub()) {
+            newStub->toCacheIR_Updated()->notePreliminaryObject();
+          } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
+            StripPreliminaryObjectStubs(cx, stub);
+          }
         }
-        return true;
-      }
-    } else {
-      gen.trackAttached(IRGenerator::NotAttached);
+      } break;
+      case AttachDecision::NoAction:
+        gen.trackAttached(IRGenerator::NotAttached);
+        break;
+      case AttachDecision::TemporarilyUnoptimizable:
+      case AttachDecision::Deferred:
+        MOZ_ASSERT_UNREACHABLE("Invalid attach result");
+        break;
     }
-    if (!attached && !isTemporarilyUnoptimizable) {
-      stub->state().trackNotAttached();
-    }
-  }
-
+  }
+  if (!attached && canAttachStub) {
+    stub->state().trackNotAttached();
+  }
   return true;
 }
 
 bool FallbackICCodeCompiler::emit_SetElem() {
   MOZ_ASSERT(R0 == JSReturnOperand);
 
   EmitRestoreTailCallReg(masm);
 
@@ -2829,50 +2850,20 @@ bool DoGetPropFallback(JSContext* cx, Ba
   jsbytecode* pc = stub->icEntry()->pc(script);
   JSOp op = JSOp(*pc);
   FallbackICSpew(cx, stub, "GetProp(%s)", CodeName[op]);
 
   MOZ_ASSERT(op == JSOP_GETPROP || op == JSOP_CALLPROP || op == JSOP_LENGTH ||
              op == JSOP_GETBOUNDNAME);
 
   RootedPropertyName name(cx, script->getName(pc));
-
-  // There are some reasons we can fail to attach a stub that are temporary.
-  // We want to avoid calling noteUnoptimizableAccess() if the reason we
-  // failed to attach a stub is one of those temporary reasons, since we might
-  // end up attaching a stub for the exact same access later.
-  bool isTemporarilyUnoptimizable = false;
-
-  if (stub->state().maybeTransition()) {
-    stub->discardStubs(cx);
-  }
-
-  bool attached = false;
-  if (stub->state().canAttachStub()) {
-    RootedValue idVal(cx, StringValue(name));
-    GetPropIRGenerator gen(cx, script, pc, CacheKind::GetProp,
-                           stub->state().mode(), &isTemporarilyUnoptimizable,
-                           val, idVal, val, GetPropertyResultFlags::All);
-    if (gen.tryAttachStub()) {
-      ICStub* newStub = AttachBaselineCacheIRStub(
-          cx, gen.writerRef(), gen.cacheKind(),
-          BaselineCacheIRStubKind::Monitored, script, stub, &attached);
-      if (newStub) {
-        JitSpew(JitSpew_BaselineIC, "  Attached GetProp CacheIR stub");
-        if (gen.shouldNotePreliminaryObjectStub()) {
-          newStub->toCacheIR_Monitored()->notePreliminaryObject();
-        } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
-          StripPreliminaryObjectStubs(cx, stub);
-        }
-      }
-    }
-    if (!attached && !isTemporarilyUnoptimizable) {
-      stub->state().trackNotAttached();
-    }
-  }
+  RootedValue idVal(cx, StringValue(name));
+
+  TryAttachGetPropStub("GetProp", cx, frame, stub, CacheKind::GetProp, val,
+                       idVal, val);
 
   if (!ComputeGetPropResult(cx, frame, op, name, val, res)) {
     return false;
   }
 
   StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
   TypeScript::Monitor(cx, script, pc, types, res);
 
@@ -2890,50 +2881,20 @@ bool DoGetPropSuperFallback(JSContext* c
 
   RootedScript script(cx, frame->script());
   jsbytecode* pc = stub->icEntry()->pc(script);
   FallbackICSpew(cx, stub, "GetPropSuper(%s)", CodeName[JSOp(*pc)]);
 
   MOZ_ASSERT(JSOp(*pc) == JSOP_GETPROP_SUPER);
 
   RootedPropertyName name(cx, script->getName(pc));
-
-  // There are some reasons we can fail to attach a stub that are temporary.
-  // We want to avoid calling noteUnoptimizableAccess() if the reason we
-  // failed to attach a stub is one of those temporary reasons, since we might
-  // end up attaching a stub for the exact same access later.
-  bool isTemporarilyUnoptimizable = false;
-
-  if (stub->state().maybeTransition()) {
-    stub->discardStubs(cx);
-  }
-
-  bool attached = false;
-  if (stub->state().canAttachStub()) {
-    RootedValue idVal(cx, StringValue(name));
-    GetPropIRGenerator gen(cx, script, pc, CacheKind::GetPropSuper,
-                           stub->state().mode(), &isTemporarilyUnoptimizable,
-                           val, idVal, receiver, GetPropertyResultFlags::All);
-    if (gen.tryAttachStub()) {
-      ICStub* newStub = AttachBaselineCacheIRStub(
-          cx, gen.writerRef(), gen.cacheKind(),
-          BaselineCacheIRStubKind::Monitored, script, stub, &attached);
-      if (newStub) {
-        JitSpew(JitSpew_BaselineIC, "  Attached GetPropSuper CacheIR stub");
-        if (gen.shouldNotePreliminaryObjectStub()) {
-          newStub->toCacheIR_Monitored()->notePreliminaryObject();
-        } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
-          StripPreliminaryObjectStubs(cx, stub);
-        }
-      }
-    }
-    if (!attached && !isTemporarilyUnoptimizable) {
-      stub->state().trackNotAttached();
-    }
-  }
+  RootedValue idVal(cx, StringValue(name));
+
+  TryAttachGetPropStub("GetPropSuper", cx, frame, stub, CacheKind::GetPropSuper,
+                       val, idVal, receiver);
 
   // |val| is [[HomeObject]].[[Prototype]] which must be Object
   RootedObject valObj(cx, &val.toObject());
   if (!GetProperty(cx, valObj, receiver, name, res)) {
     return false;
   }
 
   StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
@@ -3019,16 +2980,18 @@ bool FallbackICCodeCompiler::emit_GetPro
 
 //
 // SetProp_Fallback
 //
 
 bool DoSetPropFallback(JSContext* cx, BaselineFrame* frame,
                        ICSetProp_Fallback* stub, Value* stack, HandleValue lhs,
                        HandleValue rhs) {
+  using DeferType = SetPropIRGenerator::DeferType;
+
   stub->incrementEnteredCount();
 
   RootedScript script(cx, frame->script());
   jsbytecode* pc = stub->icEntry()->pc(script);
   JSOp op = JSOp(*pc);
   FallbackICSpew(cx, stub, "SetProp(%s)", CodeName[op]);
 
   MOZ_ASSERT(op == JSOP_SETPROP || op == JSOP_STRICTSETPROP ||
@@ -3045,48 +3008,52 @@ bool DoSetPropFallback(JSContext* cx, Ba
     return false;
   }
   RootedShape oldShape(cx, obj->shape());
   RootedObjectGroup oldGroup(cx, JSObject::getGroup(cx, obj));
   if (!oldGroup) {
     return false;
   }
 
-  // There are some reasons we can fail to attach a stub that are temporary.
-  // We want to avoid calling noteUnoptimizableAccess() if the reason we
-  // failed to attach a stub is one of those temporary reasons, since we might
-  // end up attaching a stub for the exact same access later.
-  bool isTemporarilyUnoptimizable = false;
-  bool canAddSlot = false;
-
+  DeferType deferType = DeferType::None;
   bool attached = false;
   if (stub->state().maybeTransition()) {
     stub->discardStubs(cx);
   }
 
   if (stub->state().canAttachStub()) {
     RootedValue idVal(cx, StringValue(name));
     SetPropIRGenerator gen(cx, script, pc, CacheKind::SetProp,
-                           stub->state().mode(), &isTemporarilyUnoptimizable,
-                           &canAddSlot, lhs, idVal, rhs);
-    if (gen.tryAttachStub()) {
-      ICStub* newStub = AttachBaselineCacheIRStub(
-          cx, gen.writerRef(), gen.cacheKind(),
-          BaselineCacheIRStubKind::Updated, frame->script(), stub, &attached);
-      if (newStub) {
-        JitSpew(JitSpew_BaselineIC, "  Attached SetProp CacheIR stub");
-
-        SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
-
-        if (gen.shouldNotePreliminaryObjectStub()) {
-          newStub->toCacheIR_Updated()->notePreliminaryObject();
-        } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
-          StripPreliminaryObjectStubs(cx, stub);
+                           stub->state().mode(), lhs, idVal, rhs);
+    switch (gen.tryAttachStub()) {
+      case AttachDecision::Attach: {
+        ICStub* newStub = AttachBaselineCacheIRStub(
+            cx, gen.writerRef(), gen.cacheKind(),
+            BaselineCacheIRStubKind::Updated, frame->script(), stub, &attached);
+        if (newStub) {
+          JitSpew(JitSpew_BaselineIC, "  Attached SetProp CacheIR stub");
+
+          SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
+
+          if (gen.shouldNotePreliminaryObjectStub()) {
+            newStub->toCacheIR_Updated()->notePreliminaryObject();
+          } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
+            StripPreliminaryObjectStubs(cx, stub);
+          }
         }
-      }
+      } break;
+      case AttachDecision::NoAction:
+        break;
+      case AttachDecision::TemporarilyUnoptimizable:
+        attached = true;
+        break;
+      case AttachDecision::Deferred:
+        deferType = gen.deferType();
+        MOZ_ASSERT(deferType != DeferType::None);
+        break;
     }
   }
 
   if (op == JSOP_INITPROP || op == JSOP_INITLOCKEDPROP ||
       op == JSOP_INITHIDDENPROP) {
     if (!InitPropertyOperation(cx, op, obj, name, rhs)) {
       return false;
     }
@@ -3125,42 +3092,54 @@ bool DoSetPropFallback(JSContext* cx, Ba
   }
 
   // The SetProperty call might have entered this IC recursively, so try
   // to transition.
   if (stub->state().maybeTransition()) {
     stub->discardStubs(cx);
   }
 
-  if (stub->state().canAttachStub()) {
+  bool canAttachStub = stub->state().canAttachStub();
+
+  if (deferType != DeferType::None && canAttachStub) {
     RootedValue idVal(cx, StringValue(name));
     SetPropIRGenerator gen(cx, script, pc, CacheKind::SetProp,
-                           stub->state().mode(), &isTemporarilyUnoptimizable,
-                           &canAddSlot, lhs, idVal, rhs);
-    if (canAddSlot && gen.tryAttachAddSlotStub(oldGroup, oldShape)) {
-      ICStub* newStub = AttachBaselineCacheIRStub(
-          cx, gen.writerRef(), gen.cacheKind(),
-          BaselineCacheIRStubKind::Updated, frame->script(), stub, &attached);
-      if (newStub) {
-        JitSpew(JitSpew_BaselineIC, "  Attached SetProp CacheIR stub");
-
-        SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
-
-        if (gen.shouldNotePreliminaryObjectStub()) {
-          newStub->toCacheIR_Updated()->notePreliminaryObject();
-        } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
-          StripPreliminaryObjectStubs(cx, stub);
+                           stub->state().mode(), lhs, idVal, rhs);
+
+    MOZ_ASSERT(deferType == DeferType::AddSlot);
+    AttachDecision decision = gen.tryAttachAddSlotStub(oldGroup, oldShape);
+
+    switch (decision) {
+      case AttachDecision::Attach: {
+        ICStub* newStub = AttachBaselineCacheIRStub(
+            cx, gen.writerRef(), gen.cacheKind(),
+            BaselineCacheIRStubKind::Updated, frame->script(), stub, &attached);
+        if (newStub) {
+          JitSpew(JitSpew_BaselineIC, "  Attached SetElem CacheIR stub");
+
+          SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
+
+          if (gen.shouldNotePreliminaryObjectStub()) {
+            newStub->toCacheIR_Updated()->notePreliminaryObject();
+          } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
+            StripPreliminaryObjectStubs(cx, stub);
+          }
         }
-      }
-    } else {
-      gen.trackAttached(IRGenerator::NotAttached);
+      } break;
+      case AttachDecision::NoAction:
+        gen.trackAttached(IRGenerator::NotAttached);
+        break;
+      case AttachDecision::TemporarilyUnoptimizable:
+      case AttachDecision::Deferred:
+        MOZ_ASSERT_UNREACHABLE("Invalid attach result");
+        break;
     }
-    if (!attached && !isTemporarilyUnoptimizable) {
-      stub->state().trackNotAttached();
-    }
+  }
+  if (!attached && canAttachStub) {
+    stub->state().trackNotAttached();
   }
 
   return true;
 }
 
 bool FallbackICCodeCompiler::emit_SetProp() {
   MOZ_ASSERT(R0 == JSReturnOperand);
 
@@ -3947,20 +3926,18 @@ bool DoCallFallback(JSContext* cx, Basel
     // optimized ConstStringSplit stub. Note that vp[0] now holds the return
     // value instead of the callee, so we pass the callee as well.
     if (!TryAttachConstStringSplit(cx, stub, script, argc, callee, vp, pc, res,
                                    &handled)) {
       return false;
     }
   }
 
-  if (!handled) {
-    if (canAttachStub) {
-      stub->state().trackNotAttached();
-    }
+  if (!handled && canAttachStub) {
+    stub->state().trackNotAttached();
   }
   return true;
 }
 
 bool DoSpreadCallFallback(JSContext* cx, BaselineFrame* frame,
                           ICCall_Fallback* stub, Value* vp,
                           MutableHandleValue res) {
   stub->incrementEnteredCount();
@@ -5696,17 +5673,17 @@ bool DoUnaryArithFallback(JSContext* cx,
     default:
       MOZ_CRASH("Unexpected op");
   }
 
   if (res.isDouble()) {
     stub->setSawDoubleResult();
   }
 
-  TryAttachStub<UnaryArithIRGenerator>("UniaryArith", cx, frame, stub,
+  TryAttachStub<UnaryArithIRGenerator>("UniryArith", cx, frame, stub,
                                        BaselineCacheIRStubKind::Regular, op,
                                        val, res);
   return true;
 }
 
 bool FallbackICCodeCompiler::emit_UnaryArith() {
   MOZ_ASSERT(R0 == JSReturnOperand);
 
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -200,17 +200,19 @@ JitExecStatus jit::EnterBaselineAtBranch
 }
 
 MethodStatus jit::BaselineCompile(JSContext* cx, JSScript* script,
                                   bool forceDebugInstrumentation) {
   cx->check(script);
   MOZ_ASSERT(!script->hasBaselineScript());
   MOZ_ASSERT(script->canBaselineCompile());
   MOZ_ASSERT(IsBaselineEnabled(cx));
-  AutoGeckoProfilerEntry pseudoFrame(cx, "Baseline script compilation");
+  AutoGeckoProfilerEntry pseudoFrame(
+      cx, "Baseline script compilation",
+      JS::ProfilingCategoryPair::JS_BaselineCompilation);
 
   script->ensureNonLazyCanonicalFunction();
 
   TempAllocator temp(&cx->tempLifoAlloc());
   JitContext jctx(cx, nullptr);
 
   BaselineCompiler compiler(cx, temp, script);
   if (!compiler.init()) {
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -109,25 +109,25 @@ IRGenerator::IRGenerator(JSContext* cx, 
                          CacheKind cacheKind, ICState::Mode mode)
     : writer(cx),
       cx_(cx),
       script_(script),
       pc_(pc),
       cacheKind_(cacheKind),
       mode_(mode) {}
 
-GetPropIRGenerator::GetPropIRGenerator(
-    JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind,
-    ICState::Mode mode, bool* isTemporarilyUnoptimizable, HandleValue val,
-    HandleValue idVal, HandleValue receiver, GetPropertyResultFlags resultFlags)
+GetPropIRGenerator::GetPropIRGenerator(JSContext* cx, HandleScript script,
+                                       jsbytecode* pc, ICState::Mode mode,
+                                       CacheKind cacheKind, HandleValue val,
+                                       HandleValue idVal, HandleValue receiver,
+                                       GetPropertyResultFlags resultFlags)
     : IRGenerator(cx, script, pc, cacheKind, mode),
       val_(val),
       idVal_(idVal),
       receiver_(receiver),
-      isTemporarilyUnoptimizable_(isTemporarilyUnoptimizable),
       resultFlags_(resultFlags),
       preliminaryObjectAction_(PreliminaryObjectAction::None) {}
 
 static void EmitLoadSlotResult(CacheIRWriter& writer, ObjOperandId holderOp,
                                NativeObject* holder, Shape* shape) {
   if (holder->isFixedSlot(shape->slot())) {
     writer.loadFixedSlotResult(holderOp,
                                NativeObject::getFixedSlotOffset(shape->slot()));
@@ -220,27 +220,27 @@ static bool ValueToNameOrSymbolId(JSCont
     id.set(JSID_VOID);
     return true;
   }
 
   *nameOrSymbol = true;
   return true;
 }
 
-bool GetPropIRGenerator::tryAttachStub() {
+AttachDecision GetPropIRGenerator::tryAttachStub() {
   // Idempotent ICs should call tryAttachIdempotentStub instead.
   MOZ_ASSERT(!idempotent());
 
   AutoAssertNoPendingException aanpe(cx_);
 
   // Non-object receivers are a degenerate case, so don't try to attach
   // stubs. The stubs we do emit will still perform runtime checks and
   // fallback as needed.
   if (isSuper() && !receiver_.isObject()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ValOperandId valId(writer.setInputOperandId(0));
   if (cacheKind_ != CacheKind::GetProp) {
     MOZ_ASSERT_IF(cacheKind_ == CacheKind::GetPropSuper,
                   getSuperReceiverValueId().id() == 1);
     MOZ_ASSERT_IF(cacheKind_ != CacheKind::GetPropSuper,
                   getElemKeyValueId().id() == 1);
@@ -250,151 +250,106 @@ bool GetPropIRGenerator::tryAttachStub()
     MOZ_ASSERT(getSuperReceiverValueId().id() == 2);
     writer.setInputOperandId(2);
   }
 
   RootedId id(cx_);
   bool nameOrSymbol;
   if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
     cx_->clearPendingException();
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (val_.isObject()) {
     RootedObject obj(cx_, &val_.toObject());
     ObjOperandId objId = writer.guardIsObject(valId);
     if (nameOrSymbol) {
-      if (tryAttachObjectLength(obj, objId, id)) {
-        return true;
-      }
-      if (tryAttachNative(obj, objId, id)) {
-        return true;
-      }
-      if (tryAttachTypedObject(obj, objId, id)) {
-        return true;
-      }
-      if (tryAttachModuleNamespace(obj, objId, id)) {
-        return true;
-      }
-      if (tryAttachWindowProxy(obj, objId, id)) {
-        return true;
-      }
-      if (tryAttachCrossCompartmentWrapper(obj, objId, id)) {
-        return true;
-      }
-      if (tryAttachXrayCrossCompartmentWrapper(obj, objId, id)) {
-        return true;
-      }
-      if (tryAttachFunction(obj, objId, id)) {
-        return true;
-      }
-      if (tryAttachProxy(obj, objId, id)) {
-        return true;
-      }
+      TRY_ATTACH(tryAttachObjectLength(obj, objId, id));
+      TRY_ATTACH(tryAttachNative(obj, objId, id));
+      TRY_ATTACH(tryAttachTypedObject(obj, objId, id));
+      TRY_ATTACH(tryAttachModuleNamespace(obj, objId, id));
+      TRY_ATTACH(tryAttachWindowProxy(obj, objId, id));
+      TRY_ATTACH(tryAttachCrossCompartmentWrapper(obj, objId, id));
+      TRY_ATTACH(tryAttachXrayCrossCompartmentWrapper(obj, objId, id));
+      TRY_ATTACH(tryAttachFunction(obj, objId, id));
+      TRY_ATTACH(tryAttachProxy(obj, objId, id));
 
       trackAttached(IRGenerator::NotAttached);
-      return false;
+      return AttachDecision::NoAction;
     }
 
     MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
                cacheKind_ == CacheKind::GetElemSuper);
 
-    if (tryAttachProxyElement(obj, objId)) {
-      return true;
-    }
+    TRY_ATTACH(tryAttachProxyElement(obj, objId));
 
     uint32_t index;
     Int32OperandId indexId;
     if (maybeGuardInt32Index(idVal_, getElemKeyValueId(), &index, &indexId)) {
-      if (tryAttachTypedElement(obj, objId, index, indexId)) {
-        return true;
-      }
-      if (tryAttachDenseElement(obj, objId, index, indexId)) {
-        return true;
-      }
-      if (tryAttachDenseElementHole(obj, objId, index, indexId)) {
-        return true;
-      }
-      if (tryAttachSparseElement(obj, objId, index, indexId)) {
-        return true;
-      }
-      if (tryAttachArgumentsObjectArg(obj, objId, indexId)) {
-        return true;
-      }
-      if (tryAttachGenericElement(obj, objId, index, indexId)) {
-        return true;
-      }
+      TRY_ATTACH(tryAttachTypedElement(obj, objId, index, indexId));
+      TRY_ATTACH(tryAttachDenseElement(obj, objId, index, indexId));
+      TRY_ATTACH(tryAttachDenseElementHole(obj, objId, index, indexId));
+      TRY_ATTACH(tryAttachSparseElement(obj, objId, index, indexId));
+      TRY_ATTACH(tryAttachArgumentsObjectArg(obj, objId, indexId));
+      TRY_ATTACH(tryAttachGenericElement(obj, objId, index, indexId));
 
       trackAttached(IRGenerator::NotAttached);
-      return false;
+      return AttachDecision::NoAction;
     }
 
     trackAttached(IRGenerator::NotAttached);
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (nameOrSymbol) {
-    if (tryAttachPrimitive(valId, id)) {
-      return true;
-    }
-    if (tryAttachStringLength(valId, id)) {
-      return true;
-    }
-    if (tryAttachMagicArgumentsName(valId, id)) {
-      return true;
-    }
+    TRY_ATTACH(tryAttachPrimitive(valId, id));
+    TRY_ATTACH(tryAttachStringLength(valId, id));
+    TRY_ATTACH(tryAttachMagicArgumentsName(valId, id));
 
     trackAttached(IRGenerator::NotAttached);
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (idVal_.isInt32()) {
     ValOperandId indexId = getElemKeyValueId();
-    if (tryAttachStringChar(valId, indexId)) {
-      return true;
-    }
-    if (tryAttachMagicArgument(valId, indexId)) {
-      return true;
-    }
+    TRY_ATTACH(tryAttachStringChar(valId, indexId));
+    TRY_ATTACH(tryAttachMagicArgument(valId, indexId));
 
     trackAttached(IRGenerator::NotAttached);
-    return false;
+    return AttachDecision::NoAction;
   }
 
   trackAttached(IRGenerator::NotAttached);
-  return false;
-}
-
-bool GetPropIRGenerator::tryAttachIdempotentStub() {
+  return AttachDecision::NoAction;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachIdempotentStub() {
   // For idempotent ICs, only attach stubs which we can be sure have no side
   // effects and produce a result which the MIR in the calling code is able
   // to handle, since we do not have a pc to explicitly monitor the result.
 
   MOZ_ASSERT(idempotent());
 
   RootedObject obj(cx_, &val_.toObject());
   RootedId id(cx_, NameToId(idVal_.toString()->asAtom().asPropertyName()));
 
   ValOperandId valId(writer.setInputOperandId(0));
   ObjOperandId objId = writer.guardIsObject(valId);
-  if (tryAttachNative(obj, objId, id)) {
-    return true;
-  }
+
+  TRY_ATTACH(tryAttachNative(obj, objId, id));
 
   // Object lengths are supported only if int32 results are allowed.
-  if (tryAttachObjectLength(obj, objId, id)) {
-    return true;
-  }
+  TRY_ATTACH(tryAttachObjectLength(obj, objId, id));
 
   // Also support native data properties on DOMProxy prototypes.
   if (GetProxyStubType(cx_, obj, id) == ProxyStubType::DOMUnshadowed) {
     return tryAttachDOMProxyUnshadowed(obj, objId, id);
   }
 
-  return false;
+  return AttachDecision::NoAction;
 }
 
 static bool IsCacheableProtoChain(JSObject* obj, JSObject* holder) {
   while (obj != holder) {
     /*
      * We cannot assume that we find the holder object on the prototype
      * chain and must check for null proto. The prototype chain can be
      * altered during the lookupProperty call.
@@ -486,16 +441,17 @@ static bool IsCacheableGetPropCallScript
   if (getter.isNativeWithJitEntry()) {
     return true;
   }
 
   if (!getter.hasScript()) {
     if (isTemporarilyUnoptimizable) {
       *isTemporarilyUnoptimizable = true;
     }
+    MOZ_ASSERT(getter.isInterpretedLazy());
     return false;
   }
 
   if (getter.isClassConstructor()) {
     return false;
   }
 
   return true;
@@ -588,22 +544,23 @@ static bool IsCacheableNoProperty(JSCont
 
   return CheckHasNoSuchProperty(cx, obj, id);
 }
 
 enum NativeGetPropCacheability {
   CanAttachNone,
   CanAttachReadSlot,
   CanAttachCallGetter,
+  CanAttachTemporarilyUnoptimizable
 };
 
 static NativeGetPropCacheability CanAttachNativeGetProp(
     JSContext* cx, HandleObject obj, HandleId id,
     MutableHandleNativeObject holder, MutableHandleShape shape, jsbytecode* pc,
-    GetPropertyResultFlags resultFlags, bool* isTemporarilyUnoptimizable) {
+    GetPropertyResultFlags resultFlags) {
   MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_SYMBOL(id));
 
   // The lookup needs to be universally pure, otherwise we risk calling hooks
   // out of turn. We don't mind doing this even when purity isn't required,
   // because we only miss out on shape hashification, which is only a temporary
   // perf cost. The limits were arbitrarily set, anyways.
   JSObject* baseHolder = nullptr;
   PropertyResult prop;
@@ -625,20 +582,24 @@ static NativeGetPropCacheability CanAtta
   }
 
   if (IsCacheableNoProperty(cx, obj, holder, shape, id, pc, resultFlags)) {
     return CanAttachReadSlot;
   }
 
   // Idempotent ICs cannot call getters, see tryAttachIdempotentStub.
   if (pc && (resultFlags & GetPropertyResultFlags::Monitored)) {
+    bool isTemporarilyUnoptimizable = false;
     if (IsCacheableGetPropCallScripted(obj, holder, shape,
-                                       isTemporarilyUnoptimizable)) {
+                                       &isTemporarilyUnoptimizable)) {
       return CanAttachCallGetter;
     }
+    if (isTemporarilyUnoptimizable) {
+      return CanAttachTemporarilyUnoptimizable;
+    }
 
     if (IsCacheableGetPropCallNative(obj, holder, shape)) {
       return CanAttachCallGetter;
     }
   }
 
   return CanAttachNone;
 }
@@ -1061,60 +1022,62 @@ void GetPropIRGenerator::attachMegamorph
                                             handleMissing);
   }
   writer.typeMonitorResult();
 
   trackAttached(handleMissing ? "MegamorphicMissingNativeSlot"
                               : "MegamorphicNativeSlot");
 }
 
-bool GetPropIRGenerator::tryAttachNative(HandleObject obj, ObjOperandId objId,
-                                         HandleId id) {
+AttachDecision GetPropIRGenerator::tryAttachNative(HandleObject obj,
+                                                   ObjOperandId objId,
+                                                   HandleId id) {
   RootedShape shape(cx_);
   RootedNativeObject holder(cx_);
 
   NativeGetPropCacheability type =
-      CanAttachNativeGetProp(cx_, obj, id, &holder, &shape, pc_, resultFlags_,
-                             isTemporarilyUnoptimizable_);
+      CanAttachNativeGetProp(cx_, obj, id, &holder, &shape, pc_, resultFlags_);
   switch (type) {
     case CanAttachNone:
-      return false;
+      return AttachDecision::NoAction;
+    case CanAttachTemporarilyUnoptimizable:
+      return AttachDecision::TemporarilyUnoptimizable;
     case CanAttachReadSlot:
       if (mode_ == ICState::Mode::Megamorphic) {
         attachMegamorphicNativeSlot(objId, id, holder == nullptr);
-        return true;
+        return AttachDecision::Attach;
       }
 
       maybeEmitIdGuard(id);
       if (holder) {
         EnsureTrackPropertyTypes(cx_, holder, id);
         // See the comment in StripPreliminaryObjectStubs.
         if (IsPreliminaryObject(obj)) {
           preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary;
         } else {
           preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
         }
       }
       EmitReadSlotResult(writer, obj, holder, shape, objId);
       EmitReadSlotReturn(writer, obj, holder, shape);
 
       trackAttached("NativeSlot");
-      return true;
+      return AttachDecision::Attach;
     case CanAttachCallGetter: {
       // |super.prop| accesses use a |this| value that differs from lookup
       // object
       MOZ_ASSERT(!idempotent());
       ObjOperandId receiverId =
           isSuper() ? writer.guardIsObject(getSuperReceiverValueId()) : objId;
       maybeEmitIdGuard(id);
       EmitCallGetterResult(writer, obj, holder, shape, objId, receiverId,
                            mode_);
 
       trackAttached("NativeGetter");
-      return true;
+      return AttachDecision::Attach;
     }
   }
 
   MOZ_CRASH("Bad NativeGetPropCacheability");
 }
 
 bool js::jit::IsWindowProxyForScriptGlobal(JSScript* script, JSObject* obj) {
   if (!IsWindowProxy(obj)) {
@@ -1150,136 +1113,139 @@ static ObjOperandId GuardAndLoadWindowPr
   // Note: update AddCacheIRGetPropFunction in BaselineInspector.cpp when making
   // changes here.
   writer.guardClass(objId, GuardClassKind::WindowProxy);
   ObjOperandId windowObjId = writer.loadWrapperTarget(objId);
   writer.guardSpecificObject(windowObjId, windowObj);
   return windowObjId;
 }
 
-bool GetPropIRGenerator::tryAttachWindowProxy(HandleObject obj,
-                                              ObjOperandId objId, HandleId id) {
+AttachDecision GetPropIRGenerator::tryAttachWindowProxy(HandleObject obj,
+                                                        ObjOperandId objId,
+                                                        HandleId id) {
   // Attach a stub when the receiver is a WindowProxy and we can do the lookup
   // on the Window (the global object).
 
   if (!IsWindowProxyForScriptGlobal(script_, obj)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // If we're megamorphic prefer a generic proxy stub that handles a lot more
   // cases.
   if (mode_ == ICState::Mode::Megamorphic) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Now try to do the lookup on the Window (the current global).
   Handle<GlobalObject*> windowObj = cx_->global();
   RootedShape shape(cx_);
   RootedNativeObject holder(cx_);
-  NativeGetPropCacheability type =
-      CanAttachNativeGetProp(cx_, windowObj, id, &holder, &shape, pc_,
-                             resultFlags_, isTemporarilyUnoptimizable_);
+  NativeGetPropCacheability type = CanAttachNativeGetProp(
+      cx_, windowObj, id, &holder, &shape, pc_, resultFlags_);
   switch (type) {
     case CanAttachNone:
-      return false;
+      return AttachDecision::NoAction;
+    case CanAttachTemporarilyUnoptimizable:
+      return AttachDecision::TemporarilyUnoptimizable;
 
     case CanAttachReadSlot: {
       maybeEmitIdGuard(id);
       ObjOperandId windowObjId =
           GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
       EmitReadSlotResult(writer, windowObj, holder, shape, windowObjId);
       EmitReadSlotReturn(writer, windowObj, holder, shape);
 
       trackAttached("WindowProxySlot");
-      return true;
+      return AttachDecision::Attach;
     }
 
     case CanAttachCallGetter: {
       if (!IsCacheableGetPropCallNative(windowObj, holder, shape)) {
-        return false;
+        return AttachDecision::NoAction;
       }
 
       // Make sure the native getter is okay with the IC passing the Window
       // instead of the WindowProxy as |this| value.
       JSFunction* callee = &shape->getterObject()->as<JSFunction>();
       MOZ_ASSERT(callee->isNative());
       if (!callee->hasJitInfo() ||
           callee->jitInfo()->needsOuterizedThisObject()) {
-        return false;
+        return AttachDecision::NoAction;
       }
 
       // If a |super| access, it is not worth the complexity to attach an IC.
       if (isSuper()) {
-        return false;
+        return AttachDecision::NoAction;
       }
 
       // Guard the incoming object is a WindowProxy and inline a getter call
       // based on the Window object.
       maybeEmitIdGuard(id);
       ObjOperandId windowObjId =
           GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
       EmitCallGetterResult(writer, windowObj, holder, shape, windowObjId,
                            mode_);
 
       trackAttached("WindowProxyGetter");
-      return true;
+      return AttachDecision::Attach;
     }
   }
 
   MOZ_CRASH("Unreachable");
 }
 
-bool GetPropIRGenerator::tryAttachCrossCompartmentWrapper(HandleObject obj,
-                                                          ObjOperandId objId,
-                                                          HandleId id) {
+AttachDecision GetPropIRGenerator::tryAttachCrossCompartmentWrapper(
+    HandleObject obj, ObjOperandId objId, HandleId id) {
   // We can only optimize this very wrapper-handler, because others might
   // have a security policy.
   if (!IsWrapper(obj) ||
       Wrapper::wrapperHandler(obj) != &CrossCompartmentWrapper::singleton) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // If we're megamorphic prefer a generic proxy stub that handles a lot more
   // cases.
   if (mode_ == ICState::Mode::Megamorphic) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   RootedObject unwrapped(cx_, Wrapper::wrappedObject(obj));
   MOZ_ASSERT(unwrapped == UnwrapOneCheckedStatic(obj));
   MOZ_ASSERT(!IsCrossCompartmentWrapper(unwrapped),
              "CCWs must not wrap other CCWs");
 
   // If we allowed different zones we would have to wrap strings.
   if (unwrapped->compartment()->zone() != cx_->compartment()->zone()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Take the unwrapped object's global, and wrap in a
   // this-compartment wrapper. This is what will be stored in the IC
   // keep the compartment alive.
   RootedObject wrappedTargetGlobal(cx_, &unwrapped->nonCCWGlobal());
   if (!cx_->compartment()->wrap(cx_, &wrappedTargetGlobal)) {
     cx_->clearPendingException();
-    return false;
+    return AttachDecision::NoAction;
   }
 
   RootedShape shape(cx_);
   RootedNativeObject holder(cx_);
 
   // Enter realm of target since some checks have side-effects
   // such as de-lazifying type info.
   {
     AutoRealm ar(cx_, unwrapped);
 
-    NativeGetPropCacheability canCache =
-        CanAttachNativeGetProp(cx_, unwrapped, id, &holder, &shape, pc_,
-                               resultFlags_, isTemporarilyUnoptimizable_);
+    NativeGetPropCacheability canCache = CanAttachNativeGetProp(
+        cx_, unwrapped, id, &holder, &shape, pc_, resultFlags_);
+    if (canCache == CanAttachTemporarilyUnoptimizable) {
+      return AttachDecision::TemporarilyUnoptimizable;
+    }
     if (canCache != CanAttachReadSlot) {
-      return false;
+      return AttachDecision::NoAction;
     }
 
     if (holder) {
       // Need to be in the compartment of the holder to
       // call EnsureTrackPropertyTypes
       EnsureTrackPropertyTypes(cx_, holder, id);
       if (unwrapped == holder) {
         // See the comment in StripPreliminaryObjectStubs.
@@ -1289,17 +1255,17 @@ bool GetPropIRGenerator::tryAttachCrossC
           preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
         }
       }
     } else {
       // UNCACHEABLE_PROTO may result in guards against specific
       // (cross-compartment) prototype objects, so don't try to attach IC if we
       // see the flag at all.
       if (UncacheableProtoOnChain(unwrapped)) {
-        return false;
+        return AttachDecision::NoAction;
       }
     }
   }
 
   maybeEmitIdGuard(id);
   writer.guardIsProxy(objId);
   writer.guardHasProxyHandler(objId, Wrapper::wrapperHandler(obj));
 
@@ -1311,17 +1277,17 @@ bool GetPropIRGenerator::tryAttachCrossC
                           unwrapped->compartment());
 
   ObjOperandId unwrappedId = wrapperTargetId;
   EmitReadSlotResult<SlotReadType::CrossCompartment>(writer, unwrapped, holder,
                                                      shape, unwrappedId);
   EmitReadSlotReturn(writer, unwrapped, holder, shape, /* wrapResult = */ true);
 
   trackAttached("CCWSlot");
-  return true;
+  return AttachDecision::Attach;
 }
 
 static bool GetXrayExpandoShapeWrapper(JSContext* cx, HandleObject xray,
                                        MutableHandleObject wrapper) {
   Value v = GetProxyReservedSlot(xray, GetXrayJitInfo()->xrayHolderSlot);
   if (v.isObject()) {
     NativeObject* holder = &v.toObject().as<NativeObject>();
     v = holder->getFixedSlot(GetXrayJitInfo()->holderExpandoSlot);
@@ -1331,77 +1297,77 @@ static bool GetXrayExpandoShapeWrapper(J
       wrapper.set(NewWrapperWithObjectShape(cx, expando));
       return wrapper != nullptr;
     }
   }
   wrapper.set(nullptr);
   return true;
 }
 
-bool GetPropIRGenerator::tryAttachXrayCrossCompartmentWrapper(
+AttachDecision GetPropIRGenerator::tryAttachXrayCrossCompartmentWrapper(
     HandleObject obj, ObjOperandId objId, HandleId id) {
   if (!IsProxy(obj)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   XrayJitInfo* info = GetXrayJitInfo();
   if (!info || !info->isCrossCompartmentXray(GetProxyHandler(obj))) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (!info->compartmentHasExclusiveExpandos(obj)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   RootedObject target(cx_, UncheckedUnwrap(obj));
 
   RootedObject expandoShapeWrapper(cx_);
   if (!GetXrayExpandoShapeWrapper(cx_, obj, &expandoShapeWrapper)) {
     cx_->recoverFromOutOfMemory();
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Look for a getter we can call on the xray or its prototype chain.
   Rooted<PropertyDescriptor> desc(cx_);
   RootedObject holder(cx_, obj);
   RootedObjectVector prototypes(cx_);
   RootedObjectVector prototypeExpandoShapeWrappers(cx_);
   while (true) {
     if (!GetOwnPropertyDescriptor(cx_, holder, id, &desc)) {
       cx_->clearPendingException();
-      return false;
+      return AttachDecision::NoAction;
     }
     if (desc.object()) {
       break;
     }
     if (!GetPrototype(cx_, holder, &holder)) {
       cx_->clearPendingException();
-      return false;
+      return AttachDecision::NoAction;
     }
     if (!holder || !IsProxy(holder) ||
         !info->isCrossCompartmentXray(GetProxyHandler(holder))) {
-      return false;
+      return AttachDecision::NoAction;
     }
     RootedObject prototypeExpandoShapeWrapper(cx_);
     if (!GetXrayExpandoShapeWrapper(cx_, holder,
                                     &prototypeExpandoShapeWrapper) ||
         !prototypes.append(holder) ||
         !prototypeExpandoShapeWrappers.append(prototypeExpandoShapeWrapper)) {
       cx_->recoverFromOutOfMemory();
-      return false;
+      return AttachDecision::NoAction;
     }
   }
   if (!desc.isAccessorDescriptor()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   RootedObject getter(cx_, desc.getterObject());
   if (!getter || !getter->is<JSFunction>() ||
       !getter->as<JSFunction>().isNative()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   maybeEmitIdGuard(id);
   writer.guardIsProxy(objId);
   writer.guardHasProxyHandler(objId, GetProxyHandler(obj));
 
   // Load the object wrapped by the CCW
   ObjOperandId wrapperTargetId = writer.loadWrapperTarget(objId);
@@ -1427,22 +1393,21 @@ bool GetPropIRGenerator::tryAttachXrayCr
     writer.guardXrayExpandoShapeAndDefaultProto(
         protoId, prototypeExpandoShapeWrappers[i]);
   }
 
   writer.callNativeGetterResult(objId, &getter->as<JSFunction>());
   writer.typeMonitorResult();
 
   trackAttached("XrayGetter");
-  return true;
-}
-
-bool GetPropIRGenerator::tryAttachGenericProxy(HandleObject obj,
-                                               ObjOperandId objId, HandleId id,
-                                               bool handleDOMProxies) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachGenericProxy(
+    HandleObject obj, ObjOperandId objId, HandleId id, bool handleDOMProxies) {
   MOZ_ASSERT(obj->is<ProxyObject>());
 
   writer.guardIsProxy(objId);
 
   if (!handleDOMProxies) {
     // Ensure that the incoming object is not a DOM proxy, so that we can get to
     // the specialized stubs
     writer.guardNotDOMProxy(objId);
@@ -1458,17 +1423,17 @@ bool GetPropIRGenerator::tryAttachGeneri
     MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
     MOZ_ASSERT(!isSuper());
     writer.callProxyGetByValueResult(objId, getElemKeyValueId());
   }
 
   writer.typeMonitorResult();
 
   trackAttached("GenericProxy");
-  return true;
+  return AttachDecision::Attach;
 }
 
 ObjOperandId IRGenerator::guardDOMProxyExpandoObjectAndShape(
     JSObject* obj, ObjOperandId objId, const Value& expandoVal,
     JSObject* expandoObj) {
   MOZ_ASSERT(IsCacheableDOMProxy(obj));
 
   TestMatchingProxyReceiver(writer, &obj->as<ProxyObject>(), objId);
@@ -1482,19 +1447,19 @@ ObjOperandId IRGenerator::guardDOMProxyE
   }
 
   // Guard the expando is an object and shape guard.
   ObjOperandId expandoObjId = writer.guardIsObject(expandoValId);
   TestMatchingHolder(writer, expandoObj, expandoObjId);
   return expandoObjId;
 }
 
-bool GetPropIRGenerator::tryAttachDOMProxyExpando(HandleObject obj,
-                                                  ObjOperandId objId,
-                                                  HandleId id) {
+AttachDecision GetPropIRGenerator::tryAttachDOMProxyExpando(HandleObject obj,
+                                                            ObjOperandId objId,
+                                                            HandleId id) {
   MOZ_ASSERT(IsCacheableDOMProxy(obj));
 
   RootedValue expandoVal(cx_, GetProxyPrivate(obj));
   RootedObject expandoObj(cx_);
   if (expandoVal.isObject()) {
     expandoObj = &expandoVal.toObject();
   } else {
     MOZ_ASSERT(!expandoVal.isUndefined(),
@@ -1503,24 +1468,26 @@ bool GetPropIRGenerator::tryAttachDOMPro
         static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
     MOZ_ASSERT(expandoAndGeneration);
     expandoObj = &expandoAndGeneration->expando.toObject();
   }
 
   // Try to do the lookup on the expando object.
   RootedNativeObject holder(cx_);
   RootedShape propShape(cx_);
-  NativeGetPropCacheability canCache =
-      CanAttachNativeGetProp(cx_, expandoObj, id, &holder, &propShape, pc_,
-                             resultFlags_, isTemporarilyUnoptimizable_);
-  if (canCache != CanAttachReadSlot && canCache != CanAttachCallGetter) {
-    return false;
+  NativeGetPropCacheability canCache = CanAttachNativeGetProp(
+      cx_, expandoObj, id, &holder, &propShape, pc_, resultFlags_);
+  if (canCache == CanAttachNone) {
+    return AttachDecision::NoAction;
+  }
+  if (canCache == CanAttachTemporarilyUnoptimizable) {
+    return AttachDecision::TemporarilyUnoptimizable;
   }
   if (!holder) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   MOZ_ASSERT(holder == expandoObj);
 
   maybeEmitIdGuard(id);
   ObjOperandId expandoObjId =
       guardDOMProxyExpandoObjectAndShape(obj, objId, expandoVal, expandoObj);
 
@@ -1533,32 +1500,32 @@ bool GetPropIRGenerator::tryAttachDOMPro
     // Call the getter. Note that we pass objId, the DOM proxy, as |this|
     // and not the expando object.
     MOZ_ASSERT(canCache == CanAttachCallGetter);
     EmitCallGetterResultNoGuards(writer, expandoObj, expandoObj, propShape,
                                  objId);
   }
 
   trackAttached("DOMProxyExpando");
-  return true;
-}
-
-bool GetPropIRGenerator::tryAttachDOMProxyShadowed(HandleObject obj,
-                                                   ObjOperandId objId,
-                                                   HandleId id) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachDOMProxyShadowed(HandleObject obj,
+                                                             ObjOperandId objId,
+                                                             HandleId id) {
   MOZ_ASSERT(!isSuper());
   MOZ_ASSERT(IsCacheableDOMProxy(obj));
 
   maybeEmitIdGuard(id);
   TestMatchingProxyReceiver(writer, &obj->as<ProxyObject>(), objId);
   writer.callProxyGetResult(objId, id);
   writer.typeMonitorResult();
 
   trackAttached("DOMProxyShadowed");
-  return true;
+  return AttachDecision::Attach;
 }
 
 // Callers are expected to have already guarded on the shape of the
 // object, which guarantees the object is a DOM proxy.
 static void CheckDOMProxyExpandoDoesNotShadow(CacheIRWriter& writer,
                                               JSObject* obj, jsid id,
                                               ObjOperandId objId) {
   MOZ_ASSERT(IsCacheableDOMProxy(obj));
@@ -1586,33 +1553,34 @@ static void CheckDOMProxyExpandoDoesNotS
     MOZ_ASSERT(!expandoObj.containsPure(id));
     writer.guardDOMExpandoMissingOrGuardShape(expandoId,
                                               expandoObj.lastProperty());
   } else {
     MOZ_CRASH("Invalid expando value");
   }
 }
 
-bool GetPropIRGenerator::tryAttachDOMProxyUnshadowed(HandleObject obj,
-                                                     ObjOperandId objId,
-                                                     HandleId id) {
+AttachDecision GetPropIRGenerator::tryAttachDOMProxyUnshadowed(
+    HandleObject obj, ObjOperandId objId, HandleId id) {
   MOZ_ASSERT(IsCacheableDOMProxy(obj));
 
   RootedObject checkObj(cx_, obj->staticPrototype());
   if (!checkObj) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   RootedNativeObject holder(cx_);
   RootedShape shape(cx_);
-  NativeGetPropCacheability canCache =
-      CanAttachNativeGetProp(cx_, checkObj, id, &holder, &shape, pc_,
-                             resultFlags_, isTemporarilyUnoptimizable_);
+  NativeGetPropCacheability canCache = CanAttachNativeGetProp(
+      cx_, checkObj, id, &holder, &shape, pc_, resultFlags_);
   if (canCache == CanAttachNone) {
-    return false;
+    return AttachDecision::NoAction;
+  }
+  if (canCache == CanAttachTemporarilyUnoptimizable) {
+    return AttachDecision::TemporarilyUnoptimizable;
   }
 
   maybeEmitIdGuard(id);
 
   // Guard that our expando object hasn't started shadowing this property.
   TestMatchingProxyReceiver(writer, &obj->as<ProxyObject>(), objId);
   CheckDOMProxyExpandoDoesNotShadow(writer, obj, id, objId);
 
@@ -1641,57 +1609,46 @@ bool GetPropIRGenerator::tryAttachDOMPro
     // Property was not found on the prototype chain. Deoptimize down to
     // proxy get call.
     MOZ_ASSERT(!isSuper());
     writer.callProxyGetResult(objId, id);
     writer.typeMonitorResult();
   }
 
   trackAttached("DOMProxyUnshadowed");
-  return true;
-}
-
-bool GetPropIRGenerator::tryAttachProxy(HandleObject obj, ObjOperandId objId,
-                                        HandleId id) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachProxy(HandleObject obj,
+                                                  ObjOperandId objId,
+                                                  HandleId id) {
   ProxyStubType type = GetProxyStubType(cx_, obj, id);
   if (type == ProxyStubType::None) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // The proxy stubs don't currently support |super| access.
   if (isSuper()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (mode_ == ICState::Mode::Megamorphic) {
     return tryAttachGenericProxy(obj, objId, id, /* handleDOMProxies = */ true);
   }
 
   switch (type) {
     case ProxyStubType::None:
       break;
     case ProxyStubType::DOMExpando:
-      if (tryAttachDOMProxyExpando(obj, objId, id)) {
-        return true;
-      }
-      if (*isTemporarilyUnoptimizable_) {
-        // Scripted getter without JIT code. Just wait.
-        return false;
-      }
+      TRY_ATTACH(tryAttachDOMProxyExpando(obj, objId, id));
       MOZ_FALLTHROUGH;  // Fall through to the generic shadowed case.
     case ProxyStubType::DOMShadowed:
       return tryAttachDOMProxyShadowed(obj, objId, id);
     case ProxyStubType::DOMUnshadowed:
-      if (tryAttachDOMProxyUnshadowed(obj, objId, id)) {
-        return true;
-      }
-      if (*isTemporarilyUnoptimizable_) {
-        // Scripted getter without JIT code. Just wait.
-        return false;
-      }
+      TRY_ATTACH(tryAttachDOMProxyUnshadowed(obj, objId, id));
       return tryAttachGenericProxy(obj, objId, id,
                                    /* handleDOMProxies = */ true);
     case ProxyStubType::Generic:
       return tryAttachGenericProxy(obj, objId, id,
                                    /* handleDOMProxies = */ false);
   }
 
   MOZ_CRASH("Unexpected ProxyStubType");
@@ -1705,40 +1662,41 @@ static TypedThingLayout GetTypedThingLay
     return Layout_OutlineTypedObject;
   }
   if (IsInlineTypedObjectClass(clasp)) {
     return Layout_InlineTypedObject;
   }
   MOZ_CRASH("Bad object class");
 }
 
-bool GetPropIRGenerator::tryAttachTypedObject(HandleObject obj,
-                                              ObjOperandId objId, HandleId id) {
+AttachDecision GetPropIRGenerator::tryAttachTypedObject(HandleObject obj,
+                                                        ObjOperandId objId,
+                                                        HandleId id) {
   if (!obj->is<TypedObject>()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (cx_->zone()->detachedTypedObjects) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   TypedObject* typedObj = &obj->as<TypedObject>();
   if (!typedObj->typeDescr().is<StructTypeDescr>()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   StructTypeDescr* structDescr = &typedObj->typeDescr().as<StructTypeDescr>();
   size_t fieldIndex;
   if (!structDescr->fieldIndex(id, &fieldIndex)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   TypeDescr* fieldDescr = &structDescr->fieldDescr(fieldIndex);
   if (!fieldDescr->is<SimpleTypeDescr>()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   TypedThingLayout layout = GetTypedThingLayout(obj->getClass());
 
   uint32_t fieldOffset = structDescr->fieldOffset(fieldIndex);
   uint32_t typeDescr = SimpleTypeDescrKey(&fieldDescr->as<SimpleTypeDescr>());
 
   maybeEmitIdGuard(id);
@@ -1758,148 +1716,150 @@ bool GetPropIRGenerator::tryAttachTypedO
 
   if (monitorLoad) {
     writer.typeMonitorResult();
   } else {
     writer.returnFromIC();
   }
 
   trackAttached("TypedObject");
-  return true;
-}
-
-bool GetPropIRGenerator::tryAttachObjectLength(HandleObject obj,
-                                               ObjOperandId objId,
-                                               HandleId id) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachObjectLength(HandleObject obj,
+                                                         ObjOperandId objId,
+                                                         HandleId id) {
   if (!JSID_IS_ATOM(id, cx_->names().length)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (!(resultFlags_ & GetPropertyResultFlags::AllowInt32)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (obj->is<ArrayObject>()) {
     // Make sure int32 is added to the TypeSet before we attach a stub, so
     // the stub can return int32 values without monitoring the result.
     if (obj->as<ArrayObject>().length() > INT32_MAX) {
-      return false;
+      return AttachDecision::NoAction;
     }
 
     maybeEmitIdGuard(id);
     writer.guardClass(objId, GuardClassKind::Array);
     writer.loadInt32ArrayLengthResult(objId);
     writer.returnFromIC();
 
     trackAttached("ArrayLength");
-    return true;
+    return AttachDecision::Attach;
   }
 
   if (obj->is<ArgumentsObject>() &&
       !obj->as<ArgumentsObject>().hasOverriddenLength()) {
     maybeEmitIdGuard(id);
     if (obj->is<MappedArgumentsObject>()) {
       writer.guardClass(objId, GuardClassKind::MappedArguments);
     } else {
       MOZ_ASSERT(obj->is<UnmappedArgumentsObject>());
       writer.guardClass(objId, GuardClassKind::UnmappedArguments);
     }
     writer.loadArgumentsObjectLengthResult(objId);
     writer.returnFromIC();
 
     trackAttached("ArgumentsObjectLength");
-    return true;
-  }
-
-  return false;
-}
-
-bool GetPropIRGenerator::tryAttachFunction(HandleObject obj, ObjOperandId objId,
-                                           HandleId id) {
+    return AttachDecision::Attach;
+  }
+
+  return AttachDecision::NoAction;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachFunction(HandleObject obj,
+                                                     ObjOperandId objId,
+                                                     HandleId id) {
   // Function properties are lazily resolved so they might not be defined yet.
   // And we might end up in a situation where we always have a fresh function
   // object during the IC generation.
   if (!obj->is<JSFunction>()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   JSObject* holder = nullptr;
   PropertyResult prop;
   // This property exists already, don't attach the stub.
   if (LookupPropertyPure(cx_, obj, id, &holder, &prop)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   JSFunction* fun = &obj->as<JSFunction>();
 
   if (JSID_IS_ATOM(id, cx_->names().length)) {
     // length was probably deleted from the function.
     if (fun->hasResolvedLength()) {
-      return false;
+      return AttachDecision::NoAction;
     }
 
     // Lazy functions don't store the length.
     if (fun->isInterpretedLazy()) {
-      return false;
+      return AttachDecision::NoAction;
     }
 
     maybeEmitIdGuard(id);
     writer.guardClass(objId, GuardClassKind::JSFunction);
     writer.loadFunctionLengthResult(objId);
     writer.returnFromIC();
 
     trackAttached("FunctionLength");
-    return true;
-  }
-
-  return false;
-}
-
-bool GetPropIRGenerator::tryAttachModuleNamespace(HandleObject obj,
-                                                  ObjOperandId objId,
-                                                  HandleId id) {
+    return AttachDecision::Attach;
+  }
+
+  return AttachDecision::NoAction;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachModuleNamespace(HandleObject obj,
+                                                            ObjOperandId objId,
+                                                            HandleId id) {
   if (!obj->is<ModuleNamespaceObject>()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   Rooted<ModuleNamespaceObject*> ns(cx_, &obj->as<ModuleNamespaceObject>());
   RootedModuleEnvironmentObject env(cx_);
   RootedShape shape(cx_);
   if (!ns->bindings().lookup(id, env.address(), shape.address())) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Don't emit a stub until the target binding has been initialized.
   if (env->getSlot(shape->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (IsIonEnabled(cx_)) {
     EnsureTrackPropertyTypes(cx_, env, shape->propid());
   }
 
   // Check for the specific namespace object.
   maybeEmitIdGuard(id);
   writer.guardSpecificObject(objId, ns);
 
   ObjOperandId envId = writer.loadObject(env);
   EmitLoadSlotResult(writer, envId, env, shape);
   writer.typeMonitorResult();
 
   trackAttached("ModuleNamespace");
-  return true;
-}
-
-bool GetPropIRGenerator::tryAttachPrimitive(ValOperandId valId, HandleId id) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachPrimitive(ValOperandId valId,
+                                                      HandleId id) {
   JSProtoKey protoKey;
   switch (val_.type()) {
     case ValueType::String:
       if (JSID_IS_ATOM(id, cx_->names().length)) {
         // String length is special-cased, see js::GetProperty.
-        return false;
+        return AttachDecision::NoAction;
       }
       protoKey = JSProto_String;
       break;
     case ValueType::Int32:
     case ValueType::Double:
       protoKey = JSProto_Number;
       break;
     case ValueType::Boolean:
@@ -1909,34 +1869,36 @@ bool GetPropIRGenerator::tryAttachPrimit
       protoKey = JSProto_Symbol;
       break;
     case ValueType::BigInt:
       protoKey = JSProto_BigInt;
       break;
     case ValueType::Null:
     case ValueType::Undefined:
     case ValueType::Magic:
-      return false;
+      return AttachDecision::NoAction;
     case ValueType::Object:
     case ValueType::PrivateGCThing:
       MOZ_CRASH("unexpected type");
   }
 
   RootedObject proto(cx_, cx_->global()->maybeGetPrototype(protoKey));
   if (!proto) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   RootedShape shape(cx_);
   RootedNativeObject holder(cx_);
-  NativeGetPropCacheability type =
-      CanAttachNativeGetProp(cx_, proto, id, &holder, &shape, pc_, resultFlags_,
-                             isTemporarilyUnoptimizable_);
+  NativeGetPropCacheability type = CanAttachNativeGetProp(
+      cx_, proto, id, &holder, &shape, pc_, resultFlags_);
+  if (type == CanAttachTemporarilyUnoptimizable) {
+    return AttachDecision::TemporarilyUnoptimizable;
+  }
   if (type != CanAttachReadSlot) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (holder) {
     // Instantiate this property, for use during Ion compilation.
     if (IsIonEnabled(cx_)) {
       EnsureTrackPropertyTypes(cx_, holder, id);
     }
   }
@@ -1948,171 +1910,169 @@ bool GetPropIRGenerator::tryAttachPrimit
   }
   maybeEmitIdGuard(id);
 
   ObjOperandId protoId = writer.loadObject(proto);
   EmitReadSlotResult(writer, proto, holder, shape, protoId);
   EmitReadSlotReturn(writer, proto, holder, shape);
 
   trackAttached("Primitive");
-  return true;
-}
-
-bool GetPropIRGenerator::tryAttachStringLength(ValOperandId valId,
-                                               HandleId id) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachStringLength(ValOperandId valId,
+                                                         HandleId id) {
   if (!val_.isString() || !JSID_IS_ATOM(id, cx_->names().length)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   StringOperandId strId = writer.guardIsString(valId);
   maybeEmitIdGuard(id);
   writer.loadStringLengthResult(strId);
   writer.returnFromIC();
 
   trackAttached("StringLength");
-  return true;
-}
-
-bool GetPropIRGenerator::tryAttachStringChar(ValOperandId valId,
-                                             ValOperandId indexId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachStringChar(ValOperandId valId,
+                                                       ValOperandId indexId) {
   MOZ_ASSERT(idVal_.isInt32());
 
   if (!val_.isString()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   int32_t index = idVal_.toInt32();
   if (index < 0) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   JSString* str = val_.toString();
   if (size_t(index) >= str->length()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // This follows JSString::getChar, otherwise we fail to attach getChar in a
   // lot of cases.
   if (str->isRope()) {
     JSRope* rope = &str->asRope();
 
     // Make sure the left side contains the index.
     if (size_t(index) >= rope->leftChild()->length()) {
-      return false;
+      return AttachDecision::NoAction;
     }
 
     str = rope->leftChild();
   }
 
   if (!str->isLinear() || str->asLinear().latin1OrTwoByteChar(index) >=
                               StaticStrings::UNIT_STATIC_LIMIT) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   StringOperandId strId = writer.guardIsString(valId);
   Int32OperandId int32IndexId = writer.guardIsInt32Index(indexId);
   writer.loadStringCharResult(strId, int32IndexId);
   writer.returnFromIC();
 
   trackAttached("StringChar");
-  return true;
-}
-
-bool GetPropIRGenerator::tryAttachMagicArgumentsName(ValOperandId valId,
-                                                     HandleId id) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachMagicArgumentsName(
+    ValOperandId valId, HandleId id) {
   if (!val_.isMagic(JS_OPTIMIZED_ARGUMENTS)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (!JSID_IS_ATOM(id, cx_->names().length) &&
       !JSID_IS_ATOM(id, cx_->names().callee)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   maybeEmitIdGuard(id);
   writer.guardMagicValue(valId, JS_OPTIMIZED_ARGUMENTS);
   writer.guardFrameHasNoArgumentsObject();
 
   if (JSID_IS_ATOM(id, cx_->names().length)) {
     writer.loadFrameNumActualArgsResult();
     writer.returnFromIC();
   } else {
     MOZ_ASSERT(JSID_IS_ATOM(id, cx_->names().callee));
     writer.loadFrameCalleeResult();
     writer.typeMonitorResult();
   }
 
   trackAttached("MagicArgumentsName");
-  return true;
-}
-
-bool GetPropIRGenerator::tryAttachMagicArgument(ValOperandId valId,
-                                                ValOperandId indexId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachMagicArgument(
+    ValOperandId valId, ValOperandId indexId) {
   MOZ_ASSERT(idVal_.isInt32());
 
   if (!val_.isMagic(JS_OPTIMIZED_ARGUMENTS)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   writer.guardMagicValue(valId, JS_OPTIMIZED_ARGUMENTS);
   writer.guardFrameHasNoArgumentsObject();
 
   Int32OperandId int32IndexId = writer.guardIsInt32Index(indexId);
   writer.loadFrameArgumentResult(int32IndexId);
   writer.typeMonitorResult();
 
   trackAttached("MagicArgument");
-  return true;
-}
-
-bool GetPropIRGenerator::tryAttachArgumentsObjectArg(HandleObject obj,
-                                                     ObjOperandId objId,
-                                                     Int32OperandId indexId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectArg(
+    HandleObject obj, ObjOperandId objId, Int32OperandId indexId) {
   if (!obj->is<ArgumentsObject>() ||
       obj->as<ArgumentsObject>().hasOverriddenElement()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (!(resultFlags_ & GetPropertyResultFlags::Monitored)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (obj->is<MappedArgumentsObject>()) {
     writer.guardClass(objId, GuardClassKind::MappedArguments);
   } else {
     MOZ_ASSERT(obj->is<UnmappedArgumentsObject>());
     writer.guardClass(objId, GuardClassKind::UnmappedArguments);
   }
 
   writer.loadArgumentsObjectArgResult(objId, indexId);
   writer.typeMonitorResult();
 
   trackAttached("ArgumentsObjectArg");
-  return true;
-}
-
-bool GetPropIRGenerator::tryAttachDenseElement(HandleObject obj,
-                                               ObjOperandId objId,
-                                               uint32_t index,
-                                               Int32OperandId indexId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachDenseElement(
+    HandleObject obj, ObjOperandId objId, uint32_t index,
+    Int32OperandId indexId) {
   if (!obj->isNative()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   NativeObject* nobj = &obj->as<NativeObject>();
   if (!nobj->containsDenseElement(index)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   TestMatchingNativeReceiver(writer, nobj, objId);
   writer.loadDenseElementResult(objId, indexId);
   writer.typeMonitorResult();
 
   trackAttached("DenseElement");
-  return true;
+  return AttachDecision::Attach;
 }
 
 static bool CanAttachDenseElementHole(NativeObject* obj, bool ownProp,
                                       bool allowIndexedReceiver = false) {
   // Make sure the objects on the prototype don't have any indexed properties
   // or that such properties can't appear without a shape change.
   // Otherwise returning undefined for holes would obviously be incorrect,
   // because we would have to lookup a property on the prototype instead.
@@ -2147,79 +2107,77 @@ static bool CanAttachDenseElementHole(Na
     }
 
     obj = &proto->as<NativeObject>();
   } while (true);
 
   return true;
 }
 
-bool GetPropIRGenerator::tryAttachDenseElementHole(HandleObject obj,
-                                                   ObjOperandId objId,
-                                                   uint32_t index,
-                                                   Int32OperandId indexId) {
+AttachDecision GetPropIRGenerator::tryAttachDenseElementHole(
+    HandleObject obj, ObjOperandId objId, uint32_t index,
+    Int32OperandId indexId) {
   if (!obj->isNative()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   NativeObject* nobj = &obj->as<NativeObject>();
   if (nobj->containsDenseElement(index)) {
-    return false;
+    return AttachDecision::NoAction;
   }
   if (!CanAttachDenseElementHole(nobj, false)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Guard on the shape, to prevent non-dense elements from appearing.
   TestMatchingNativeReceiver(writer, nobj, objId);
   GeneratePrototypeHoleGuards(writer, nobj, objId,
                               /* alwaysGuardFirstProto = */ false);
   writer.loadDenseElementHoleResult(objId, indexId);
   writer.typeMonitorResult();
 
   trackAttached("DenseElementHole");
-  return true;
-}
-
-bool GetPropIRGenerator::tryAttachSparseElement(HandleObject obj,
-                                                ObjOperandId objId,
-                                                uint32_t index,
-                                                Int32OperandId indexId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachSparseElement(
+    HandleObject obj, ObjOperandId objId, uint32_t index,
+    Int32OperandId indexId) {
   if (!obj->isNative()) {
-    return false;
+    return AttachDecision::NoAction;
   }
   NativeObject* nobj = &obj->as<NativeObject>();
 
   // Stub doesn't handle negative indices.
   if (index > INT_MAX) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // We also need to be past the end of the dense capacity, to ensure sparse.
   if (index < nobj->getDenseInitializedLength()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Only handle Array objects in this stub.
   if (!nobj->is<ArrayObject>()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Here, we ensure that the prototype chain does not define any sparse
   // indexed properties on the shape lineage. This allows us to guard on
   // the shapes up the prototype chain to ensure that no indexed properties
   // exist outside of the dense elements.
   //
   // The `GeneratePrototypeHoleGuards` call below will guard on the shapes,
   // as well as ensure that no prototypes contain dense elements, allowing
   // us to perform a pure shape-search for out-of-bounds integer-indexed
   // properties on the recevier object.
   if ((nobj->staticPrototype() != nullptr) &&
       ObjectMayHaveExtraIndexedProperties(nobj->staticPrototype())) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Ensure that obj is an Array.
   writer.guardClass(objId, GuardClassKind::Array);
 
   // The helper we are going to call only applies to non-dense elements.
   writer.guardIndexGreaterThanDenseInitLength(objId, indexId);
 
@@ -2235,17 +2193,17 @@ bool GetPropIRGenerator::tryAttachSparse
   // At this point, we are guaranteed that the indexed property will not
   // be found on one of the prototypes. We are assured that we only have
   // to check that the receiving object has the property.
 
   writer.callGetSparseElementResult(objId, indexId);
   writer.typeMonitorResult();
 
   trackAttached("GetSparseElement");
-  return true;
+  return AttachDecision::Attach;
 }
 
 static bool IsPrimitiveArrayTypedObject(JSObject* obj) {
   if (!obj->is<TypedObject>()) {
     return false;
   }
   TypeDescr& descr = obj->as<TypedObject>().typeDescr();
   return descr.is<ArrayTypeDescr>() &&
@@ -2258,34 +2216,33 @@ static Scalar::Type PrimitiveArrayTypedO
   return descr.as<ArrayTypeDescr>().elementType().as<ScalarTypeDescr>().type();
 }
 
 static Scalar::Type TypedThingElementType(JSObject* obj) {
   return obj->is<TypedArrayObject>() ? obj->as<TypedArrayObject>().type()
                                      : PrimitiveArrayTypedObjectType(obj);
 }
 
-bool GetPropIRGenerator::tryAttachTypedElement(HandleObject obj,
-                                               ObjOperandId objId,
-                                               uint32_t index,
-                                               Int32OperandId indexId) {
+AttachDecision GetPropIRGenerator::tryAttachTypedElement(
+    HandleObject obj, ObjOperandId objId, uint32_t index,
+    Int32OperandId indexId) {
   if (!obj->is<TypedArrayObject>() && !IsPrimitiveArrayTypedObject(obj)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Ensure the index is in-bounds so the element type gets monitored.
   if (obj->is<TypedArrayObject>() &&
       index >= obj->as<TypedArrayObject>().length()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Don't attach typed object stubs if the underlying storage could be
   // detached, as the stub will always bail out.
   if (IsPrimitiveArrayTypedObject(obj) && cx_->zone()->detachedTypedObjects) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   TypedThingLayout layout = GetTypedThingLayout(obj->getClass());
 
   if (IsPrimitiveArrayTypedObject(obj)) {
     writer.guardNoDetachedTypedObjects();
     writer.guardGroupForLayout(objId, obj->group());
   } else {
@@ -2299,25 +2256,24 @@ bool GetPropIRGenerator::tryAttachTypedE
   // later, so ensure we monitor the result.
   if (TypedThingElementType(obj) == Scalar::Type::Uint32) {
     writer.typeMonitorResult();
   } else {
     writer.returnFromIC();
   }
 
   trackAttached("TypedElement");
-  return true;
-}
-
-bool GetPropIRGenerator::tryAttachGenericElement(HandleObject obj,
-                                                 ObjOperandId objId,
-                                                 uint32_t index,
-                                                 Int32OperandId indexId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachGenericElement(
+    HandleObject obj, ObjOperandId objId, uint32_t index,
+    Int32OperandId indexId) {
   if (!obj->isNative()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // To allow other types to attach in the non-megamorphic case we test the
   // specific matching native receiver; however, once megamorphic we can attach
   // for any native
   if (mode_ == ICState::Mode::Megamorphic) {
     writer.guardIsNativeObject(objId);
   } else {
@@ -2326,44 +2282,44 @@ bool GetPropIRGenerator::tryAttachGeneri
   }
   writer.guardIndexGreaterThanDenseInitLength(objId, indexId);
   writer.callNativeGetElementResult(objId, indexId);
   writer.typeMonitorResult();
 
   trackAttached(mode_ == ICState::Mode::Megamorphic
                     ? "GenericElementMegamorphic"
                     : "GenericElement");
-  return true;
-}
-
-bool GetPropIRGenerator::tryAttachProxyElement(HandleObject obj,
-                                               ObjOperandId objId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachProxyElement(HandleObject obj,
+                                                         ObjOperandId objId) {
   if (!obj->is<ProxyObject>()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // The proxy stubs don't currently support |super| access.
   if (isSuper()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   writer.guardIsProxy(objId);
 
   // We are not guarding against DOM proxies here, because there is no other
   // specialized DOM IC we could attach.
   // We could call maybeEmitIdGuard here and then emit CallProxyGetResult,
   // but for GetElem we prefer to attach a stub that can handle any Value
   // so we don't attach a new stub for every id.
   MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
   MOZ_ASSERT(!isSuper());
   writer.callProxyGetByValueResult(objId, getElemKeyValueId());
   writer.typeMonitorResult();
 
   trackAttached("ProxyElement");
-  return true;
+  return AttachDecision::Attach;
 }
 
 void GetPropIRGenerator::trackAttached(const char* name) {
 #ifdef JS_CACHEIR_SPEW
   if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
     sp.valueProperty("base", val_);
     sp.valueProperty("property", idVal_);
   }
@@ -2408,36 +2364,30 @@ void SetPropIRGenerator::maybeEmitIdGuar
 GetNameIRGenerator::GetNameIRGenerator(JSContext* cx, HandleScript script,
                                        jsbytecode* pc, ICState::Mode mode,
                                        HandleObject env,
                                        HandlePropertyName name)
     : IRGenerator(cx, script, pc, CacheKind::GetName, mode),
       env_(env),
       name_(name) {}
 
-bool GetNameIRGenerator::tryAttachStub() {
+AttachDecision GetNameIRGenerator::tryAttachStub() {
   MOZ_ASSERT(cacheKind_ == CacheKind::GetName);
 
   AutoAssertNoPendingException aanpe(cx_);
 
   ObjOperandId envId(writer.setInputOperandId(0));
   RootedId id(cx_, NameToId(name_));
 
-  if (tryAttachGlobalNameValue(envId, id)) {
-    return true;
-  }
-  if (tryAttachGlobalNameGetter(envId, id)) {
-    return true;
-  }
-  if (tryAttachEnvironmentName(envId, id)) {
-    return true;
-  }
+  TRY_ATTACH(tryAttachGlobalNameValue(envId, id));
+  TRY_ATTACH(tryAttachGlobalNameGetter(envId, id));
+  TRY_ATTACH(tryAttachEnvironmentName(envId, id));
 
   trackAttached(IRGenerator::NotAttached);
-  return false;
+  return AttachDecision::NoAction;
 }
 
 bool CanAttachGlobalName(JSContext* cx,
                          Handle<LexicalEnvironmentObject*> globalLexical,
                          HandleId id, MutableHandleNativeObject holder,
                          MutableHandleShape shape) {
   // The property must be found, and it must be found as a normal data property.
   RootedNativeObject current(cx, globalLexical);
@@ -2463,40 +2413,40 @@ bool CanAttachGlobalName(JSContext* cx,
       current = &proto->as<NativeObject>();
     }
   }
 
   holder.set(current);
   return true;
 }
 
-bool GetNameIRGenerator::tryAttachGlobalNameValue(ObjOperandId objId,
-                                                  HandleId id) {
+AttachDecision GetNameIRGenerator::tryAttachGlobalNameValue(ObjOperandId objId,
+                                                            HandleId id) {
   if (!IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   Handle<LexicalEnvironmentObject*> globalLexical =
       env_.as<LexicalEnvironmentObject>();
   MOZ_ASSERT(globalLexical->isGlobal());
 
   RootedNativeObject holder(cx_);
   RootedShape shape(cx_);
   if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &shape)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // The property must be found, and it must be found as a normal data property.
   if (!shape->isDataProperty()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // This might still be an uninitialized lexical.
   if (holder->getSlot(shape->slot()).isMagic()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Instantiate this global property, for use during Ion compilation.
   if (IsIonEnabled(cx_)) {
     EnsureTrackPropertyTypes(cx_, holder, id);
   }
 
   if (holder == globalLexical) {
@@ -2507,17 +2457,17 @@ bool GetNameIRGenerator::tryAttachGlobal
     writer.loadDynamicSlotResult(objId, dynamicSlotOffset);
   } else {
     // Check the prototype chain from the global to the holder
     // prototype. Ignore the global lexical scope as it doesn't figure
     // into the prototype chain. We guard on the global lexical
     // scope's shape independently.
     if (!IsCacheableGetPropReadSlot(&globalLexical->global(), holder,
                                     PropertyResult(shape))) {
-      return false;
+      return AttachDecision::NoAction;
     }
 
     // Shape guard for global lexical.
     writer.guardShape(objId, globalLexical->lastProperty());
 
     // Guard on the shape of the GlobalObject.
     ObjOperandId globalId = writer.loadEnclosingEnvironment(objId);
     writer.guardShape(globalId, globalLexical->global().lastProperty());
@@ -2530,41 +2480,41 @@ bool GetNameIRGenerator::tryAttachGlobal
     }
 
     EmitLoadSlotResult(writer, holderId, holder, shape);
   }
 
   writer.typeMonitorResult();
 
   trackAttached("GlobalNameValue");
-  return true;
-}
-
-bool GetNameIRGenerator::tryAttachGlobalNameGetter(ObjOperandId objId,
-                                                   HandleId id) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision GetNameIRGenerator::tryAttachGlobalNameGetter(ObjOperandId objId,
+                                                             HandleId id) {
   if (!IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   Handle<LexicalEnvironmentObject*> globalLexical =
       env_.as<LexicalEnvironmentObject>();
   MOZ_ASSERT(globalLexical->isGlobal());
 
   RootedNativeObject holder(cx_);
   RootedShape shape(cx_);
   if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &shape)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (holder == globalLexical) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (!IsCacheableGetPropCallNative(&globalLexical->global(), holder, shape)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (IsIonEnabled(cx_)) {
     EnsureTrackPropertyTypes(cx_, holder, id);
   }
 
   // Shape guard for global lexical.
   writer.guardShape(objId, globalLexical->lastProperty());
@@ -2578,17 +2528,17 @@ bool GetNameIRGenerator::tryAttachGlobal
     ObjOperandId holderId = writer.loadObject(holder);
     writer.guardShape(holderId, holder->lastProperty());
   }
 
   EmitCallGetterResultNoGuards(writer, &globalLexical->global(), holder, shape,
                                globalId);
 
   trackAttached("GlobalNameGetter");
-  return true;
+  return AttachDecision::Attach;
 }
 
 static bool NeedEnvironmentShapeGuard(JSObject* envObj) {
   if (!envObj->is<CallObject>()) {
     return true;
   }
 
   // We can skip a guard on the call object if the script's bindings are
@@ -2599,37 +2549,37 @@ static bool NeedEnvironmentShapeGuard(JS
   JSFunction* fun = &callObj->callee();
   if (!fun->hasScript() || fun->nonLazyScript()->funHasExtensibleScope()) {
     return true;
   }
 
   return false;
 }
 
-bool GetNameIRGenerator::tryAttachEnvironmentName(ObjOperandId objId,
-                                                  HandleId id) {
+AttachDecision GetNameIRGenerator::tryAttachEnvironmentName(ObjOperandId objId,
+                                                            HandleId id) {
   if (IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   RootedObject env(cx_, env_);
   RootedShape shape(cx_);
   RootedNativeObject holder(cx_);
 
   while (env) {
     if (env->is<GlobalObject>()) {
       shape = env->as<GlobalObject>().lookup(cx_, id);
       if (shape) {
         break;
       }
-      return false;
+      return AttachDecision::NoAction;
     }
 
     if (!env->is<EnvironmentObject>() || env->is<WithEnvironmentObject>()) {
-      return false;
+      return AttachDecision::NoAction;
     }
 
     MOZ_ASSERT(!env->hasUncacheableProto());
 
     // Check for an 'own' property on the env. There is no need to
     // check the prototype as non-with scopes do not inherit properties
     // from any prototype.
     shape = env->as<NativeObject>().lookup(cx_, id);
@@ -2637,20 +2587,20 @@ bool GetNameIRGenerator::tryAttachEnviro
       break;
     }
 
     env = env->enclosingEnvironment();
   }
 
   holder = &env->as<NativeObject>();
   if (!IsCacheableGetPropReadSlot(holder, holder, PropertyResult(shape))) {
-    return false;
+    return AttachDecision::NoAction;
   }
   if (holder->getSlot(shape->slot()).isMagic()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ObjOperandId lastObjId = objId;
   env = env_;
   while (env) {
     if (NeedEnvironmentShapeGuard(env)) {
       writer.guardShape(lastObjId, env->shape());
     }
@@ -2669,17 +2619,17 @@ bool GetNameIRGenerator::tryAttachEnviro
   } else {
     size_t dynamicSlotOffset =
         holder->dynamicSlotIndex(shape->slot()) * sizeof(Value);
     writer.loadEnvironmentDynamicSlotResult(lastObjId, dynamicSlotOffset);
   }
   writer.typeMonitorResult();
 
   trackAttached("EnvironmentName");
-  return true;
+  return AttachDecision::Attach;
 }
 
 void GetNameIRGenerator::trackAttached(const char* name) {
 #ifdef JS_CACHEIR_SPEW
   if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
     sp.valueProperty("base", ObjectValue(*env_));
     sp.valueProperty("property", StringValue(name_));
   }
@@ -2689,50 +2639,47 @@ void GetNameIRGenerator::trackAttached(c
 BindNameIRGenerator::BindNameIRGenerator(JSContext* cx, HandleScript script,
                                          jsbytecode* pc, ICState::Mode mode,
                                          HandleObject env,
                                          HandlePropertyName name)
     : IRGenerator(cx, script, pc, CacheKind::BindName, mode),
       env_(env),
       name_(name) {}
 
-bool BindNameIRGenerator::tryAttachStub() {
+AttachDecision BindNameIRGenerator::tryAttachStub() {
   MOZ_ASSERT(cacheKind_ == CacheKind::BindName);
 
   AutoAssertNoPendingException aanpe(cx_);
 
   ObjOperandId envId(writer.setInputOperandId(0));
   RootedId id(cx_, NameToId(name_));
 
-  if (tryAttachGlobalName(envId, id)) {
-    return true;
-  }
-  if (tryAttachEnvironmentName(envId, id)) {
-    return true;
-  }
+  TRY_ATTACH(tryAttachGlobalName(envId, id));
+  TRY_ATTACH(tryAttachEnvironmentName(envId, id));
 
   trackAttached(IRGenerator::NotAttached);
-  return false;
-}
-
-bool BindNameIRGenerator::tryAttachGlobalName(ObjOperandId objId, HandleId id) {
+  return AttachDecision::NoAction;
+}
+
+AttachDecision BindNameIRGenerator::tryAttachGlobalName(ObjOperandId objId,
+                                                        HandleId id) {
   if (!IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   Handle<LexicalEnvironmentObject*> globalLexical =
       env_.as<LexicalEnvironmentObject>();
   MOZ_ASSERT(globalLexical->isGlobal());
 
   JSObject* result = nullptr;
   if (Shape* shape = globalLexical->lookup(cx_, id)) {
     // If this is an uninitialized lexical or a const, we need to return a
     // RuntimeLexicalErrorObject.
     if (globalLexical->getSlot(shape->slot()).isMagic() || !shape->writable()) {
-      return false;
+      return AttachDecision::NoAction;
     }
     result = globalLexical;
   } else {
     result = &globalLexical->global();
   }
 
   if (result == globalLexical) {
     // Lexical bindings are non-configurable so we can just return the
@@ -2747,33 +2694,33 @@ bool BindNameIRGenerator::tryAttachGloba
       writer.guardShape(objId, globalLexical->lastProperty());
     }
     ObjOperandId globalId = writer.loadEnclosingEnvironment(objId);
     writer.loadObjectResult(globalId);
   }
   writer.returnFromIC();
 
   trackAttached("GlobalName");
-  return true;
-}
-
-bool BindNameIRGenerator::tryAttachEnvironmentName(ObjOperandId objId,
-                                                   HandleId id) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision BindNameIRGenerator::tryAttachEnvironmentName(ObjOperandId objId,
+                                                             HandleId id) {
   if (IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   RootedObject env(cx_, env_);
   RootedShape shape(cx_);
   while (true) {
     if (!env->is<GlobalObject>() && !env->is<EnvironmentObject>()) {
-      return false;
+      return AttachDecision::NoAction;
     }
     if (env->is<WithEnvironmentObject>()) {
-      return false;
+      return AttachDecision::NoAction;
     }
 
     MOZ_ASSERT(!env->hasUncacheableProto());
 
     // When we reach an unqualified variables object (like the global) we
     // have to stop looking and return that object.
     if (env->isUnqualifiedVarObj()) {
       break;
@@ -2790,17 +2737,17 @@ bool BindNameIRGenerator::tryAttachEnvir
     env = env->enclosingEnvironment();
   }
 
   // If this is an uninitialized lexical or a const, we need to return a
   // RuntimeLexicalErrorObject.
   RootedNativeObject holder(cx_, &env->as<NativeObject>());
   if (shape && holder->is<EnvironmentObject>() &&
       (holder->getSlot(shape->slot()).isMagic() || !shape->writable())) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ObjOperandId lastObjId = objId;
   env = env_;
   while (env) {
     if (NeedEnvironmentShapeGuard(env) && !env->is<GlobalObject>()) {
       writer.guardShape(lastObjId, env->shape());
     }
@@ -2811,17 +2758,17 @@ bool BindNameIRGenerator::tryAttachEnvir
 
     lastObjId = writer.loadEnclosingEnvironment(lastObjId);
     env = env->enclosingEnvironment();
   }
   writer.loadObjectResult(lastObjId);
   writer.returnFromIC();
 
   trackAttached("EnvironmentName");
-  return true;
+  return AttachDecision::Attach;
 }
 
 void BindNameIRGenerator::trackAttached(const char* name) {
 #ifdef JS_CACHEIR_SPEW
   if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
     sp.valueProperty("base", ObjectValue(*env_));
     sp.valueProperty("property", StringValue(name_));
   }
@@ -2829,52 +2776,54 @@ void BindNameIRGenerator::trackAttached(
 }
 
 HasPropIRGenerator::HasPropIRGenerator(JSContext* cx, HandleScript script,
                                        jsbytecode* pc, ICState::Mode mode,
                                        CacheKind cacheKind, HandleValue idVal,
                                        HandleValue val)
     : IRGenerator(cx, script, pc, cacheKind, mode), val_(val), idVal_(idVal) {}
 
-bool HasPropIRGenerator::tryAttachDense(HandleObject obj, ObjOperandId objId,
-                                        uint32_t index,
-                                        Int32OperandId indexId) {
+AttachDecision HasPropIRGenerator::tryAttachDense(HandleObject obj,
+                                                  ObjOperandId objId,
+                                                  uint32_t index,
+                                                  Int32OperandId indexId) {
   if (!obj->isNative()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   NativeObject* nobj = &obj->as<NativeObject>();
   if (!nobj->containsDenseElement(index)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Guard shape to ensure object class is NativeObject.
   TestMatchingNativeReceiver(writer, nobj, objId);
   writer.loadDenseElementExistsResult(objId, indexId);
   writer.returnFromIC();
 
   trackAttached("DenseHasProp");
-  return true;
-}
-
-bool HasPropIRGenerator::tryAttachDenseHole(HandleObject obj,
-                                            ObjOperandId objId, uint32_t index,
-                                            Int32OperandId indexId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachDenseHole(HandleObject obj,
+                                                      ObjOperandId objId,
+                                                      uint32_t index,
+                                                      Int32OperandId indexId) {
   bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
 
   if (!obj->isNative()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   NativeObject* nobj = &obj->as<NativeObject>();
   if (nobj->containsDenseElement(index)) {
-    return false;
+    return AttachDecision::NoAction;
   }
   if (!CanAttachDenseElementHole(nobj, hasOwn)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Guard shape to ensure class is NativeObject and to prevent non-dense
   // elements being added. Also ensures prototype doesn't change if dynamic
   // checks aren't emitted.
   TestMatchingNativeReceiver(writer, nobj, objId);
 
   // Generate prototype guards if needed. This includes monitoring that
@@ -2883,32 +2832,33 @@ bool HasPropIRGenerator::tryAttachDenseH
     GeneratePrototypeHoleGuards(writer, nobj, objId,
                                 /* alwaysGuardFirstProto = */ false);
   }
 
   writer.loadDenseElementHoleExistsResult(objId, indexId);
   writer.returnFromIC();
 
   trackAttached("DenseHasPropHole");
-  return true;
-}
-
-bool HasPropIRGenerator::tryAttachSparse(HandleObject obj, ObjOperandId objId,
-                                         Int32OperandId indexId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachSparse(HandleObject obj,
+                                                   ObjOperandId objId,
+                                                   Int32OperandId indexId) {
   bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
 
   if (!obj->isNative()) {
-    return false;
+    return AttachDecision::NoAction;
   }
   if (!obj->as<NativeObject>().isIndexed()) {
-    return false;
+    return AttachDecision::NoAction;
   }
   if (!CanAttachDenseElementHole(&obj->as<NativeObject>(), hasOwn,
                                  /* allowIndexedReceiver = */ true)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Guard that this is a native object.
   writer.guardIsNativeObject(objId);
 
   // Generate prototype guards if needed. This includes monitoring that
   // properties were not added in the chain.
   if (!hasOwn) {
@@ -2917,259 +2867,239 @@ bool HasPropIRGenerator::tryAttachSparse
   }
 
   // Because of the prototype guard we know that the prototype chain
   // does not include any dense or sparse (i.e indexed) properties.
   writer.callObjectHasSparseElementResult(objId, indexId);
   writer.returnFromIC();
 
   trackAttached("Sparse");
-  return true;
-}
-
-bool HasPropIRGenerator::tryAttachNamedProp(HandleObject obj,
-                                            ObjOperandId objId, HandleId key,
-                                            ValOperandId keyId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachNamedProp(HandleObject obj,
+                                                      ObjOperandId objId,
+                                                      HandleId key,
+                                                      ValOperandId keyId) {
   bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
 
   JSObject* holder = nullptr;
   PropertyResult prop;
 
   if (hasOwn) {
     if (!LookupOwnPropertyPure(cx_, obj, key, &prop)) {
-      return false;
+      return AttachDecision::NoAction;
     }
 
     holder = obj;
   } else {
     if (!LookupPropertyPure(cx_, obj, key, &holder, &prop)) {
-      return false;
+      return AttachDecision::NoAction;
     }
   }
   if (!prop) {
-    return false;
-  }
-
-  if (tryAttachMegamorphic(objId, keyId)) {
-    return true;
-  }
-  if (tryAttachNative(obj, objId, key, keyId, prop, holder)) {
-    return true;
-  }
-  if (tryAttachTypedObject(obj, objId, key, keyId)) {
-    return true;
-  }
-
-  return false;
-}
-
-bool HasPropIRGenerator::tryAttachMegamorphic(ObjOperandId objId,
-                                              ValOperandId keyId) {
+    return AttachDecision::NoAction;
+  }
+
+  TRY_ATTACH(tryAttachMegamorphic(objId, keyId));
+  TRY_ATTACH(tryAttachNative(obj, objId, key, keyId, prop, holder));
+  TRY_ATTACH(tryAttachTypedObject(obj, objId, key, keyId));
+
+  return AttachDecision::NoAction;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachMegamorphic(ObjOperandId objId,
+                                                        ValOperandId keyId) {
   bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
 
   if (mode_ != ICState::Mode::Megamorphic) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   writer.megamorphicHasPropResult(objId, keyId, hasOwn);
   writer.returnFromIC();
   trackAttached("MegamorphicHasProp");
-  return true;
-}
-
-bool HasPropIRGenerator::tryAttachNative(JSObject* obj, ObjOperandId objId,
-                                         jsid key, ValOperandId keyId,
-                                         PropertyResult prop,
-                                         JSObject* holder) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachNative(JSObject* obj,
+                                                   ObjOperandId objId, jsid key,
+                                                   ValOperandId keyId,
+                                                   PropertyResult prop,
+                                                   JSObject* holder) {
   if (!prop.isNativeProperty()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (!IsCacheableProtoChain(obj, holder)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   Maybe<ObjOperandId> tempId;
   emitIdGuard(keyId, key);
   EmitReadSlotGuard(writer, obj, holder, objId, &tempId);
   writer.loadBooleanResult(true);
   writer.returnFromIC();
 
   trackAttached("NativeHasProp");
-  return true;
-}
-
-bool HasPropIRGenerator::tryAttachTypedArray(HandleObject obj,
-                                             ObjOperandId objId,
-                                             Int32OperandId indexId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachTypedArray(HandleObject obj,
+                                                       ObjOperandId objId,
+                                                       Int32OperandId indexId) {
   if (!obj->is<TypedArrayObject>() && !IsPrimitiveArrayTypedObject(obj)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   TypedThingLayout layout = GetTypedThingLayout(obj->getClass());
 
   if (IsPrimitiveArrayTypedObject(obj)) {
     writer.guardGroupForLayout(objId, obj->group());
   } else {
     writer.guardShapeForClass(objId, obj->as<TypedArrayObject>().shape());
   }
 
   writer.loadTypedElementExistsResult(objId, indexId, layout);
 
   writer.returnFromIC();
 
   trackAttached("TypedArrayObject");
-  return true;
-}
-
-bool HasPropIRGenerator::tryAttachTypedObject(JSObject* obj, ObjOperandId objId,
-                                              jsid key, ValOperandId keyId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachTypedObject(JSObject* obj,
+                                                        ObjOperandId objId,
+                                                        jsid key,
+                                                        ValOperandId keyId) {
   if (!obj->is<TypedObject>()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (!obj->as<TypedObject>().typeDescr().hasProperty(cx_->names(), key)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   emitIdGuard(keyId, key);
   writer.guardGroupForLayout(objId, obj->group());
   writer.loadBooleanResult(true);
   writer.returnFromIC();
 
   trackAttached("TypedObjectHasProp");
-  return true;
-}
-
-bool HasPropIRGenerator::tryAttachSlotDoesNotExist(JSObject* obj,
-                                                   ObjOperandId objId, jsid key,
-                                                   ValOperandId keyId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachSlotDoesNotExist(
+    JSObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId) {
   bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
 
   emitIdGuard(keyId, key);
   if (hasOwn) {
     TestMatchingReceiver(writer, obj, objId);
   } else {
     Maybe<ObjOperandId> tempId;
     EmitReadSlotGuard(writer, obj, nullptr, objId, &tempId);
   }
   writer.loadBooleanResult(false);
   writer.returnFromIC();
 
   trackAttached("DoesNotExist");
-  return true;
-}
-
-bool HasPropIRGenerator::tryAttachDoesNotExist(HandleObject obj,
-                                               ObjOperandId objId, HandleId key,
-                                               ValOperandId keyId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachDoesNotExist(HandleObject obj,
+                                                         ObjOperandId objId,
+                                                         HandleId key,
+                                                         ValOperandId keyId) {
   bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
 
   // Check that property doesn't exist on |obj| or it's prototype chain. These
   // checks allow Native/Typed objects with a NativeObject prototype
-  // chain. They return false if unknown such as resolve hooks or proxies.
+  // chain. They return NoAction if unknown such as resolve hooks or proxies.
   if (hasOwn) {
     if (!CheckHasNoSuchOwnProperty(cx_, obj, key)) {
-      return false;
+      return AttachDecision::NoAction;
     }
   } else {
     if (!CheckHasNoSuchProperty(cx_, obj, key)) {
-      return false;
+      return AttachDecision::NoAction;
     }
   }
 
-  if (tryAttachMegamorphic(objId, keyId)) {
-    return true;
-  }
-  if (tryAttachSlotDoesNotExist(obj, objId, key, keyId)) {
-    return true;
-  }
-
-  return false;
-}
-
-bool HasPropIRGenerator::tryAttachProxyElement(HandleObject obj,
-                                               ObjOperandId objId,
-                                               ValOperandId keyId) {
+  TRY_ATTACH(tryAttachMegamorphic(objId, keyId));
+  TRY_ATTACH(tryAttachSlotDoesNotExist(obj, objId, key, keyId));
+
+  return AttachDecision::NoAction;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachProxyElement(HandleObject obj,
+                                                         ObjOperandId objId,
+                                                         ValOperandId keyId) {
   bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
 
   if (!obj->is<ProxyObject>()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   writer.guardIsProxy(objId);
   writer.callProxyHasPropResult(objId, keyId, hasOwn);
   writer.returnFromIC();
 
   trackAttached("ProxyHasProp");
-  return true;
-}
-
-bool HasPropIRGenerator::tryAttachStub() {
+  return AttachDecision::Attach;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachStub() {
   MOZ_ASSERT(cacheKind_ == CacheKind::In || cacheKind_ == CacheKind::HasOwn);
 
   AutoAssertNoPendingException aanpe(cx_);
 
   // NOTE: Argument order is PROPERTY, OBJECT
   ValOperandId keyId(writer.setInputOperandId(0));
   ValOperandId valId(writer.setInputOperandId(1));
 
   if (!val_.isObject()) {
     trackAttached(IRGenerator::NotAttached);
-    return false;
+    return AttachDecision::NoAction;
   }
   RootedObject obj(cx_, &val_.toObject());
   ObjOperandId objId = writer.guardIsObject(valId);
 
   // Optimize Proxies
-  if (tryAttachProxyElement(obj, objId, keyId)) {
-    return true;
-  }
+  TRY_ATTACH(tryAttachProxyElement(obj, objId, keyId));
 
   RootedId id(cx_);
   bool nameOrSymbol;
   if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
     cx_->clearPendingException();
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (nameOrSymbol) {
-    if (tryAttachNamedProp(obj, objId, id, keyId)) {
-      return true;
-    }
-    if (tryAttachDoesNotExist(obj, objId, id, keyId)) {
-      return true;
-    }
+    TRY_ATTACH(tryAttachNamedProp(obj, objId, id, keyId));
+    TRY_ATTACH(tryAttachDoesNotExist(obj, objId, id, keyId));
 
     trackAttached(IRGenerator::NotAttached);
-    return false;
+    return AttachDecision::NoAction;
   }
 
   uint32_t index;
   Int32OperandId indexId;
   if (maybeGuardInt32Index(idVal_, keyId, &index, &indexId)) {
-    if (tryAttachDense(obj, objId, index, indexId)) {
-      return true;
-    }
-    if (tryAttachDenseHole(obj, objId, index, indexId)) {
-      return true;
-    }
-    if (tryAttachTypedArray(obj, objId, indexId)) {
-      return true;
-    }
-    if (tryAttachSparse(obj, objId, indexId)) {
-      return true;
-    }
+    TRY_ATTACH(tryAttachDense(obj, objId, index, indexId));
+    TRY_ATTACH(tryAttachDenseHole(obj, objId, index, indexId));
+    TRY_ATTACH(tryAttachTypedArray(obj, objId, indexId));
+    TRY_ATTACH(tryAttachSparse(obj, objId, indexId));
 
     trackAttached(IRGenerator::NotAttached);
-    return false;
+    return AttachDecision::NoAction;
   }
 
   trackAttached(IRGenerator::NotAttached);
-  return false;
+  return AttachDecision::NoAction;
 }
 
 void HasPropIRGenerator::trackAttached(const char* name) {
 #ifdef JS_CACHEIR_SPEW
   if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
     sp.valueProperty("base", val_);
     sp.valueProperty("property", idVal_);
   }
@@ -3209,33 +3139,32 @@ bool IRGenerator::maybeGuardInt32Index(c
     *int32Index = uint32_t(indexSigned);
     *int32IndexId = writer.guardAndGetIndexFromString(strId);
     return true;
   }
 
   return false;
 }
 
-SetPropIRGenerator::SetPropIRGenerator(
-    JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind,
-    ICState::Mode mode, bool* isTemporarilyUnoptimizable, bool* canAddSlot,
-    HandleValue lhsVal, HandleValue idVal, HandleValue rhsVal,
-    bool needsTypeBarrier, bool maybeHasExtraIndexedProps)
+SetPropIRGenerator::SetPropIRGenerator(JSContext* cx, HandleScript script,
+                                       jsbytecode* pc, CacheKind cacheKind,
+                                       ICState::Mode mode, HandleValue lhsVal,
+                                       HandleValue idVal, HandleValue rhsVal,
+                                       bool needsTypeBarrier,
+                                       bool maybeHasExtraIndexedProps)
     : IRGenerator(cx, script, pc, cacheKind, mode),
       lhsVal_(lhsVal),
       idVal_(idVal),
       rhsVal_(rhsVal),
-      isTemporarilyUnoptimizable_(isTemporarilyUnoptimizable),
-      canAddSlot_(canAddSlot),
       typeCheckInfo_(cx, needsTypeBarrier),
       preliminaryObjectAction_(PreliminaryObjectAction::None),
       attachedTypedArrayOOBStub_(false),
       maybeHasExtraIndexedProps_(maybeHasExtraIndexedProps) {}
 
-bool SetPropIRGenerator::tryAttachStub() {
+AttachDecision SetPropIRGenerator::tryAttachStub() {
   AutoAssertNoPendingException aanpe(cx_);
 
   ValOperandId objValId(writer.setInputOperandId(0));
   ValOperandId rhsValId;
   if (cacheKind_ == CacheKind::SetProp) {
     rhsValId = ValOperandId(writer.setInputOperandId(1));
   } else {
     MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
@@ -3243,82 +3172,60 @@ bool SetPropIRGenerator::tryAttachStub()
     writer.setInputOperandId(1);
     rhsValId = ValOperandId(writer.setInputOperandId(2));
   }
 
   RootedId id(cx_);
   bool nameOrSymbol;
   if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
     cx_->clearPendingException();
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (lhsVal_.isObject()) {
     RootedObject obj(cx_, &lhsVal_.toObject());
 
     ObjOperandId objId = writer.guardIsObject(objValId);
     if (IsPropertySetOp(JSOp(*pc_))) {
-      if (tryAttachMegamorphicSetElement(obj, objId, rhsValId)) {
-        return true;
-      }
+      TRY_ATTACH(tryAttachMegamorphicSetElement(obj, objId, rhsValId));
     }
     if (nameOrSymbol) {
-      if (tryAttachNativeSetSlot(obj, objId, id, rhsValId)) {
-        return true;
-      }
-      if (tryAttachTypedObjectProperty(obj, objId, id, rhsValId)) {
-        return true;
-      }
+      TRY_ATTACH(tryAttachNativeSetSlot(obj, objId, id, rhsValId));
+      TRY_ATTACH(tryAttachTypedObjectProperty(obj, objId, id, rhsValId));
       if (IsPropertySetOp(JSOp(*pc_))) {
-        if (tryAttachSetArrayLength(obj, objId, id, rhsValId)) {
-          return true;
-        }
-        if (tryAttachSetter(obj, objId, id, rhsValId)) {
-          return true;
-        }
-        if (tryAttachWindowProxy(obj, objId, id, rhsValId)) {
-          return true;
-        }
-        if (tryAttachProxy(obj, objId, id, rhsValId)) {
-          return true;
-        }
+        TRY_ATTACH(tryAttachSetArrayLength(obj, objId, id, rhsValId));
+        TRY_ATTACH(tryAttachSetter(obj, objId, id, rhsValId));
+        TRY_ATTACH(tryAttachWindowProxy(obj, objId, id, rhsValId));
+        TRY_ATTACH(tryAttachProxy(obj, objId, id, rhsValId));
       }
       if (canAttachAddSlotStub(obj, id)) {
-        *canAddSlot_ = true;
+        deferType_ = DeferType::AddSlot;
+        return AttachDecision::Deferred;
       }
-      return false;
+      return AttachDecision::NoAction;
     }
 
     if (IsPropertySetOp(JSOp(*pc_))) {
-      if (tryAttachProxyElement(obj, objId, rhsValId)) {
-        return true;
-      }
+      TRY_ATTACH(tryAttachProxyElement(obj, objId, rhsValId));
     }
 
     uint32_t index;
     Int32OperandId indexId;
     if (maybeGuardInt32Index(idVal_, setElemKeyValueId(), &index, &indexId)) {
-      if (tryAttachSetDenseElement(obj, objId, index, indexId, rhsValId)) {
-        return true;
-      }
-      if (tryAttachSetDenseElementHole(obj, objId, index, indexId, rhsValId)) {
-        return true;
-      }
-      if (tryAttachSetTypedElement(obj, objId, index, indexId, rhsValId)) {
-        return true;
-      }
-      if (tryAttachAddOrUpdateSparseElement(obj, objId, index, indexId,
-                                            rhsValId)) {
-        return true;
-      }
-      return false;
+      TRY_ATTACH(
+          tryAttachSetDenseElement(obj, objId, index, indexId, rhsValId));
+      TRY_ATTACH(
+          tryAttachSetDenseElementHole(obj, objId, index, indexId, rhsValId));
+      TRY_ATTACH(
+          tryAttachSetTypedElement(obj, objId, index, indexId, rhsValId));
+      TRY_ATTACH(tryAttachAddOrUpdateSparseElement(obj, objId, index, indexId,
+                                                   rhsValId));
     }
-    return false;
-  }
-  return false;
+  }
+  return AttachDecision::NoAction;
 }
 
 static void EmitStoreSlotAndReturn(CacheIRWriter& writer, ObjOperandId objId,
                                    NativeObject* nobj, Shape* shape,
                                    ValOperandId rhsId) {
   if (nobj->isFixedSlot(shape->slot())) {
     size_t offset = NativeObject::getFixedSlotOffset(shape->slot());
     writer.storeFixedSlot(objId, offset, rhsId);
@@ -3373,33 +3280,36 @@ static bool CanAttachNativeSetSlot(JSCon
   if (!PropertyHasBeenMarkedNonConstant(obj, id)) {
     *isTemporarilyUnoptimizable = true;
     return false;
   }
 
   return true;
 }
 
-bool SetPropIRGenerator::tryAttachNativeSetSlot(HandleObject obj,
-                                                ObjOperandId objId, HandleId id,
-                                                ValOperandId rhsId) {
+AttachDecision SetPropIRGenerator::tryAttachNativeSetSlot(HandleObject obj,
+                                                          ObjOperandId objId,
+                                                          HandleId id,
+                                                          ValOperandId rhsId) {
   RootedShape propShape(cx_);
+  bool isTemporarilyUnoptimizable = false;
   if (!CanAttachNativeSetSlot(cx_, JSOp(*pc_), obj, id,
-                              isTemporarilyUnoptimizable_, &propShape)) {
-    return false;
+                              &isTemporarilyUnoptimizable, &propShape)) {
+    return isTemporarilyUnoptimizable ? AttachDecision::TemporarilyUnoptimizable
+                                      : AttachDecision::NoAction;
   }
 
   // Don't attach a megamorphic store slot stub for ops like JSOP_INITELEM.
   if (mode_ == ICState::Mode::Megamorphic && cacheKind_ == CacheKind::SetProp &&
       IsPropertySetOp(JSOp(*pc_))) {
     writer.megamorphicStoreSlot(objId, JSID_TO_ATOM(id)->asPropertyName(),
                                 rhsId, typeCheckInfo_.needsTypeBarrier());
     writer.returnFromIC();
     trackAttached("MegamorphicNativeSlot");
-    return true;
+    return AttachDecision::Attach;
   }
 
   maybeEmitIdGuard(id);
 
   // If we need a property type barrier (always in Baseline, sometimes in
   // Ion), guard on both the shape and the group. If Ion knows the property
   // types match, we don't need the group guard.
   NativeObject* nobj = &obj->as<NativeObject>();
@@ -3413,54 +3323,52 @@ bool SetPropIRGenerator::tryAttachNative
   } else {
     preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
   }
 
   typeCheckInfo_.set(nobj->group(), id);
   EmitStoreSlotAndReturn(writer, objId, nobj, propShape, rhsId);
 
   trackAttached("NativeSlot");
-  return true;
-}
-
-bool SetPropIRGenerator::tryAttachTypedObjectProperty(HandleObject obj,
-                                                      ObjOperandId objId,
-                                                      HandleId id,
-                                                      ValOperandId rhsId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachTypedObjectProperty(
+    HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) {
   if (!obj->is<TypedObject>()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (cx_->zone()->detachedTypedObjects) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (!obj->as<TypedObject>().typeDescr().is<StructTypeDescr>()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   StructTypeDescr* structDescr =
       &obj->as<TypedObject>().typeDescr().as<StructTypeDescr>();
   size_t fieldIndex;
   if (!structDescr->fieldIndex(id, &fieldIndex)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   TypeDescr* fieldDescr = &structDescr->fieldDescr(fieldIndex);
   if (!fieldDescr->is<SimpleTypeDescr>()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (fieldDescr->is<ReferenceTypeDescr>() &&
       fieldDescr->as<ReferenceTypeDescr>().type() ==
           ReferenceType::TYPE_WASM_ANYREF) {
     // TODO/AnyRef-boxing: we can probably do better, in particular, code
     // that stores object pointers and null in an anyref slot should be able
     // to get a fast path.
-    return false;
+    return AttachDecision::NoAction;
   }
 
   uint32_t fieldOffset = structDescr->fieldOffset(fieldIndex);
   TypedThingLayout layout = GetTypedThingLayout(obj->getClass());
 
   maybeEmitIdGuard(id);
   writer.guardNoDetachedTypedObjects();
   writer.guardGroupForLayout(objId, obj->group());
@@ -3470,17 +3378,17 @@ bool SetPropIRGenerator::tryAttachTypedO
   // Scalar types can always be stored without a type update stub.
   if (fieldDescr->is<ScalarTypeDescr>()) {
     Scalar::Type type = fieldDescr->as<ScalarTypeDescr>().type();
     writer.storeTypedObjectScalarProperty(objId, fieldOffset, layout, type,
                                           rhsId);
     writer.returnFromIC();
 
     trackAttached("TypedObject");
-    return true;
+    return AttachDecision::Attach;
   }
 
   // For reference types, guard on the RHS type first, so that
   // StoreTypedObjectReferenceProperty is infallible.
   ReferenceType type = fieldDescr->as<ReferenceTypeDescr>().type();
   switch (type) {
     case ReferenceType::TYPE_ANY:
       break;
@@ -3494,17 +3402,17 @@ bool SetPropIRGenerator::tryAttachTypedO
       MOZ_CRASH();
   }
 
   writer.storeTypedObjectReferenceProperty(objId, fieldOffset, layout, type,
                                            rhsId);
   writer.returnFromIC();
 
   trackAttached("TypedObject");
-  return true;
+  return AttachDecision::Attach;
 }
 
 void SetPropIRGenerator::trackAttached(const char* name) {
 #ifdef JS_CACHEIR_SPEW
   if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
     sp.opcodeProperty("op", JSOp(*pc_));
     sp.valueProperty("base", lhsVal_);
     sp.valueProperty("property", idVal_);
@@ -3626,23 +3534,27 @@ static void EmitCallSetterNoGuards(Cache
   MOZ_ASSERT(IsCacheableSetPropCallScripted(obj, holder, shape));
 
   JSFunction* target = &shape->setterValue().toObject().as<JSFunction>();
   MOZ_ASSERT(target->hasJitEntry());
   writer.callScriptedSetter(objId, target, rhsId);
   writer.returnFromIC();
 }
 
-bool SetPropIRGenerator::tryAttachSetter(HandleObject obj, ObjOperandId objId,
-                                         HandleId id, ValOperandId rhsId) {
+AttachDecision SetPropIRGenerator::tryAttachSetter(HandleObject obj,
+                                                   ObjOperandId objId,
+                                                   HandleId id,
+                                                   ValOperandId rhsId) {
   RootedObject holder(cx_);
   RootedShape propShape(cx_);
+  bool isTemporarilyUnoptimizable = false;
   if (!CanAttachSetter(cx_, pc_, obj, id, &holder, &propShape,
-                       isTemporarilyUnoptimizable_)) {
-    return false;
+                       &isTemporarilyUnoptimizable)) {
+    return isTemporarilyUnoptimizable ? AttachDecision::TemporarilyUnoptimizable
+                                      : AttachDecision::NoAction;
   }
 
   maybeEmitIdGuard(id);
 
   // Use the megamorphic guard if we're in megamorphic mode, except if |obj|
   // is a Window as GuardHasGetterSetter doesn't support this yet (Window may
   // require outerizing).
   if (mode_ == ICState::Mode::Specialized || IsWindow(obj)) {
@@ -3657,76 +3569,74 @@ bool SetPropIRGenerator::tryAttachSetter
     }
   } else {
     writer.guardHasGetterSetter(objId, propShape);
   }
 
   EmitCallSetterNoGuards(writer, obj, holder, propShape, objId, rhsId);
 
   trackAttached("Setter");
-  return true;
-}
-
-bool SetPropIRGenerator::tryAttachSetArrayLength(HandleObject obj,
-                                                 ObjOperandId objId,
-                                                 HandleId id,
-                                                 ValOperandId rhsId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachSetArrayLength(HandleObject obj,
+                                                           ObjOperandId objId,
+                                                           HandleId id,
+                                                           ValOperandId rhsId) {
   // Don't attach an array length stub for ops like JSOP_INITELEM.
   MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
 
   if (!obj->is<ArrayObject>() || !JSID_IS_ATOM(id, cx_->names().length) ||
       !obj->as<ArrayObject>().lengthIsWritable()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   maybeEmitIdGuard(id);
   writer.guardClass(objId, GuardClassKind::Array);
   writer.callSetArrayLength(objId, IsStrictSetPC(pc_), rhsId);
   writer.returnFromIC();
 
   trackAttached("SetArrayLength");
-  return true;
-}
-
-bool SetPropIRGenerator::tryAttachSetDenseElement(HandleObject obj,
-                                                  ObjOperandId objId,
-                                                  uint32_t index,
-                                                  Int32OperandId indexId,
-                                                  ValOperandId rhsId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachSetDenseElement(
+    HandleObject obj, ObjOperandId objId, uint32_t index,
+    Int32OperandId indexId, ValOperandId rhsId) {
   if (!obj->isNative()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   NativeObject* nobj = &obj->as<NativeObject>();
   if (!nobj->containsDenseElement(index) ||
       nobj->getElementsHeader()->isFrozen()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Don't optimize INITELEM (DefineProperty) on non-extensible objects: when
   // the elements are sealed, we have to throw an exception. Note that we have
   // to check !isExtensible instead of denseElementsAreSealed because sealing
   // a (non-extensible) object does not necessarily trigger a Shape change.
   if (IsPropertyInitOp(JSOp(*pc_)) && !nobj->isExtensible()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (typeCheckInfo_.needsTypeBarrier()) {
     writer.guardGroupForTypeBarrier(objId, nobj->group());
   }
   TestMatchingNativeReceiver(writer, nobj, objId);
 
   writer.storeDenseElement(objId, indexId, rhsId);
   writer.returnFromIC();
 
   // Type inference uses JSID_VOID for the element types.
   typeCheckInfo_.set(nobj->group(), JSID_VOID);
 
   trackAttached("SetDenseElement");
-  return true;
+  return AttachDecision::Attach;
 }
 
 static bool CanAttachAddElement(NativeObject* obj, bool isInit) {
   // Make sure the objects on the prototype don't have any indexed properties
   // or that such properties can't appear without a shape change.
   do {
     // The first two checks are also relevant to the receiver object.
     if (obj->isIndexed()) {
@@ -3771,68 +3681,66 @@ static bool CanAttachAddElement(NativeOb
     }
 
     obj = nproto;
   } while (true);
 
   return true;
 }
 
-bool SetPropIRGenerator::tryAttachSetDenseElementHole(HandleObject obj,
-                                                      ObjOperandId objId,
-                                                      uint32_t index,
-                                                      Int32OperandId indexId,
-                                                      ValOperandId rhsId) {
+AttachDecision SetPropIRGenerator::tryAttachSetDenseElementHole(
+    HandleObject obj, ObjOperandId objId, uint32_t index,
+    Int32OperandId indexId, ValOperandId rhsId) {
   if (!obj->isNative() || rhsVal_.isMagic(JS_ELEMENTS_HOLE)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   JSOp op = JSOp(*pc_);
   MOZ_ASSERT(IsPropertySetOp(op) || IsPropertyInitOp(op));
 
   if (op == JSOP_INITHIDDENELEM) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   NativeObject* nobj = &obj->as<NativeObject>();
   if (!nobj->isExtensible()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   MOZ_ASSERT(!nobj->getElementsHeader()->isFrozen(),
              "Extensible objects should not have frozen elements");
 
   uint32_t initLength = nobj->getDenseInitializedLength();
 
   // Optimize if we're adding an element at initLength or writing to a hole.
   //
   // In the case where index > initLength, we need noteHasDenseAdd to be called
   // to ensure Ion is aware that writes have occurred to-out-of-bound indexes
   // before.
   bool isAdd = index == initLength;
   bool isHoleInBounds =
       index < initLength && !nobj->containsDenseElement(index);
   if (!isAdd && !isHoleInBounds) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Can't add new elements to arrays with non-writable length.
   if (isAdd && nobj->is<ArrayObject>() &&
       !nobj->as<ArrayObject>().lengthIsWritable()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Typed arrays don't have dense elements.
   if (nobj->is<TypedArrayObject>()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Check for other indexed properties or class hooks.
   if (!CanAttachAddElement(nobj, IsPropertyInitOp(op))) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (typeCheckInfo_.needsTypeBarrier()) {
     writer.guardGroupForTypeBarrier(objId, nobj->group());
   }
   TestMatchingNativeReceiver(writer, nobj, objId);
 
   // Also shape guard the proto chain, unless this is an INITELEM or we know
@@ -3843,66 +3751,66 @@ bool SetPropIRGenerator::tryAttachSetDen
 
   writer.storeDenseElementHole(objId, indexId, rhsId, isAdd);
   writer.returnFromIC();
 
   // Type inference uses JSID_VOID for the element types.
   typeCheckInfo_.set(nobj->group(), JSID_VOID);
 
   trackAttached(isAdd ? "AddDenseElement" : "StoreDenseElementHole");
-  return true;
+  return AttachDecision::Attach;
 }
 
 // Add an IC for adding or updating a sparse array element.
-bool SetPropIRGenerator::tryAttachAddOrUpdateSparseElement(
+AttachDecision SetPropIRGenerator::tryAttachAddOrUpdateSparseElement(
     HandleObject obj, ObjOperandId objId, uint32_t index,
     Int32OperandId indexId, ValOperandId rhsId) {
   JSOp op = JSOp(*pc_);
   MOZ_ASSERT(IsPropertySetOp(op) || IsPropertyInitOp(op));
 
   if (op != JSOP_SETELEM && op != JSOP_STRICTSETELEM) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (!obj->isNative()) {
-    return false;
+    return AttachDecision::NoAction;
   }
   NativeObject* nobj = &obj->as<NativeObject>();
 
   // We cannot attach a stub to a non-extensible object
   if (!nobj->isExtensible()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Stub doesn't handle negative indices.
   if (index > INT_MAX) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // We also need to be past the end of the dense capacity, to ensure sparse.
   if (index < nobj->getDenseInitializedLength()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Only handle Array objects in this stub.
   if (!nobj->is<ArrayObject>()) {
-    return false;
+    return AttachDecision::NoAction;
   }
   ArrayObject* aobj = &nobj->as<ArrayObject>();
 
   // Don't attach if we're adding to an array with non-writable length.
   bool isAdd = (index >= aobj->length());
   if (isAdd && !aobj->lengthIsWritable()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Indexed properties on the prototype chain aren't handled by the helper.
   if ((aobj->staticPrototype() != nullptr) &&
       ObjectMayHaveExtraIndexedProperties(aobj->staticPrototype())) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Ensure we are still talking about an array class.
   writer.guardClass(objId, GuardClassKind::Array);
 
   // The helper we are going to call only applies to non-dense elements.
   writer.guardIndexGreaterThanDenseInitLength(objId, indexId);
 
@@ -3929,46 +3837,44 @@ bool SetPropIRGenerator::tryAttachAddOrU
   writer.guardIndexIsValidUpdateOrAdd(objId, indexId);
 
   writer.callAddOrUpdateSparseElementHelper(
       objId, indexId, rhsId,
       /* strict = */ op == JSOP_STRICTSETELEM);
   writer.returnFromIC();
 
   trackAttached("AddOrUpdateSparseElement");
-  return true;
-}
-
-bool SetPropIRGenerator::tryAttachSetTypedElement(HandleObject obj,
-                                                  ObjOperandId objId,
-                                                  uint32_t index,
-                                                  Int32OperandId indexId,
-                                                  ValOperandId rhsId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachSetTypedElement(
+    HandleObject obj, ObjOperandId objId, uint32_t index,
+    Int32OperandId indexId, ValOperandId rhsId) {
   if (!obj->is<TypedArrayObject>() && !IsPrimitiveArrayTypedObject(obj)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (!rhsVal_.isNumber()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   bool handleOutOfBounds = false;
   if (obj->is<TypedArrayObject>()) {
     handleOutOfBounds = (index >= obj->as<TypedArrayObject>().length());
   } else {
     // Typed objects throw on out of bounds accesses. Don't attach
     // a stub in this case.
     if (index >= obj->as<TypedObject>().length()) {
-      return false;
+      return AttachDecision::NoAction;
     }
 
     // Don't attach stubs if the underlying storage for typed objects
     // in the zone could be detached, as the stub will always bail out.
     if (cx_->zone()->detachedTypedObjects) {
-      return false;
+      return AttachDecision::NoAction;
     }
   }
 
   Scalar::Type elementType = TypedThingElementType(obj);
   TypedThingLayout layout = GetTypedThingLayout(obj->getClass());
 
   if (IsPrimitiveArrayTypedObject(obj)) {
     writer.guardNoDetachedTypedObjects();
@@ -3981,23 +3887,22 @@ bool SetPropIRGenerator::tryAttachSetTyp
                            handleOutOfBounds);
   writer.returnFromIC();
 
   if (handleOutOfBounds) {
     attachedTypedArrayOOBStub_ = true;
   }
 
   trackAttached(handleOutOfBounds ? "SetTypedElementOOB" : "SetTypedElement");
-  return true;
-}
-
-bool SetPropIRGenerator::tryAttachGenericProxy(HandleObject obj,
-                                               ObjOperandId objId, HandleId id,
-                                               ValOperandId rhsId,
-                                               bool handleDOMProxies) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachGenericProxy(
+    HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId,
+    bool handleDOMProxies) {
   MOZ_ASSERT(obj->is<ProxyObject>());
 
   writer.guardIsProxy(objId);
 
   if (!handleDOMProxies) {
     // Ensure that the incoming object is not a DOM proxy, so that we can
     // get to the specialized stubs. If handleDOMProxies is true, we were
     // unable to attach a specialized DOM stub, so we just handle all
@@ -4014,50 +3919,48 @@ bool SetPropIRGenerator::tryAttachGeneri
     MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
     writer.callProxySetByValue(objId, setElemKeyValueId(), rhsId,
                                IsStrictSetPC(pc_));
   }
 
   writer.returnFromIC();
 
   trackAttached("GenericProxy");
-  return true;
-}
-
-bool SetPropIRGenerator::tryAttachDOMProxyShadowed(HandleObject obj,
-                                                   ObjOperandId objId,
-                                                   HandleId id,
-                                                   ValOperandId rhsId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachDOMProxyShadowed(
+    HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) {
   MOZ_ASSERT(IsCacheableDOMProxy(obj));
 
   maybeEmitIdGuard(id);
   TestMatchingProxyReceiver(writer, &obj->as<ProxyObject>(), objId);
   writer.callProxySet(objId, id, rhsId, IsStrictSetPC(pc_));
   writer.returnFromIC();
 
   trackAttached("DOMProxyShadowed");
-  return true;
-}
-
-bool SetPropIRGenerator::tryAttachDOMProxyUnshadowed(HandleObject obj,
-                                                     ObjOperandId objId,
-                                                     HandleId id,
-                                                     ValOperandId rhsId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachDOMProxyUnshadowed(
+    HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) {
   MOZ_ASSERT(IsCacheableDOMProxy(obj));
 
   RootedObject proto(cx_, obj->staticPrototype());
   if (!proto) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   RootedObject holder(cx_);
   RootedShape propShape(cx_);
+  bool isTemporarilyUnoptimizable = false;
   if (!CanAttachSetter(cx_, pc_, proto, id, &holder, &propShape,
-                       isTemporarilyUnoptimizable_)) {
-    return false;
+                       &isTemporarilyUnoptimizable)) {
+    return isTemporarilyUnoptimizable ? AttachDecision::TemporarilyUnoptimizable
+                                      : AttachDecision::NoAction;
   }
 
   maybeEmitIdGuard(id);
 
   // Guard that our expando object hasn't started shadowing this property.
   TestMatchingProxyReceiver(writer, &obj->as<ProxyObject>(), objId);
   CheckDOMProxyExpandoDoesNotShadow(writer, obj, id, objId);
 
@@ -4068,203 +3971,196 @@ bool SetPropIRGenerator::tryAttachDOMPro
   TestMatchingHolder(writer, holder, holderId);
 
   // EmitCallSetterNoGuards expects |obj| to be the object the property is
   // on to do some checks. Since we actually looked at proto, and no extra
   // guards will be generated, we can just pass that instead.
   EmitCallSetterNoGuards(writer, proto, holder, propShape, objId, rhsId);
 
   trackAttached("DOMProxyUnshadowed");
-  return true;
-}
-
-bool SetPropIRGenerator::tryAttachDOMProxyExpando(HandleObject obj,
-                                                  ObjOperandId objId,
-                                                  HandleId id,
-                                                  ValOperandId rhsId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachDOMProxyExpando(
+    HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) {
   MOZ_ASSERT(IsCacheableDOMProxy(obj));
 
   RootedValue expandoVal(cx_, GetProxyPrivate(obj));
   RootedObject expandoObj(cx_);
   if (expandoVal.isObject()) {
     expandoObj = &expandoVal.toObject();
   } else {
     MOZ_ASSERT(!expandoVal.isUndefined(),
                "How did a missing expando manage to shadow things?");
     auto expandoAndGeneration =
         static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
     MOZ_ASSERT(expandoAndGeneration);
     expandoObj = &expandoAndGeneration->expando.toObject();
   }
 
+  bool isTemporarilyUnoptimizable = false;
+
   RootedShape propShape(cx_);
   if (CanAttachNativeSetSlot(cx_, JSOp(*pc_), expandoObj, id,
-                             isTemporarilyUnoptimizable_, &propShape)) {
+                             &isTemporarilyUnoptimizable, &propShape)) {
     maybeEmitIdGuard(id);
     ObjOperandId expandoObjId =
         guardDOMProxyExpandoObjectAndShape(obj, objId, expandoVal, expandoObj);
 
     NativeObject* nativeExpandoObj = &expandoObj->as<NativeObject>();
     writer.guardGroupForTypeBarrier(expandoObjId, nativeExpandoObj->group());
     typeCheckInfo_.set(nativeExpandoObj->group(), id);
 
     EmitStoreSlotAndReturn(writer, expandoObjId, nativeExpandoObj, propShape,
                            rhsId);
     trackAttached("DOMProxyExpandoSlot");
-    return true;
+    return AttachDecision::Attach;
   }
 
   RootedObject holder(cx_);
   if (CanAttachSetter(cx_, pc_, expandoObj, id, &holder, &propShape,
-                      isTemporarilyUnoptimizable_)) {
+                      &isTemporarilyUnoptimizable)) {
     // Note that we don't actually use the expandoObjId here after the
     // shape guard. The DOM proxy (objId) is passed to the setter as
     // |this|.
     maybeEmitIdGuard(id);
     guardDOMProxyExpandoObjectAndShape(obj, objId, expandoVal, expandoObj);
 
     MOZ_ASSERT(holder == expandoObj);
     EmitCallSetterNoGuards(writer, expandoObj, expandoObj, propShape, objId,
                            rhsId);
     trackAttached("DOMProxyExpandoSetter");
-    return true;
-  }
-
-  return false;
-}
-
-bool SetPropIRGenerator::tryAttachProxy(HandleObject obj, ObjOperandId objId,
-                                        HandleId id, ValOperandId rhsId) {
+    return AttachDecision::Attach;
+  }
+
+  return isTemporarilyUnoptimizable ? AttachDecision::TemporarilyUnoptimizable
+                                    : AttachDecision::NoAction;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachProxy(HandleObject obj,
+                                                  ObjOperandId objId,
+                                                  HandleId id,
+                                                  ValOperandId rhsId) {
   // Don't attach a proxy stub for ops like JSOP_INITELEM.
   MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
 
   ProxyStubType type = GetProxyStubType(cx_, obj, id);
   if (type == ProxyStubType::None) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (mode_ == ICState::Mode::Megamorphic) {
     return tryAttachGenericProxy(obj, objId, id, rhsId,
                                  /* handleDOMProxies = */ true);
   }
 
   switch (type) {
     case ProxyStubType::None:
       break;
     case ProxyStubType::DOMExpando:
-      if (tryAttachDOMProxyExpando(obj, objId, id, rhsId)) {
-        return true;
-      }
-      if (*isTemporarilyUnoptimizable_) {
-        // Scripted setter without JIT code. Just wait.
-        return false;
-      }
+      TRY_ATTACH(tryAttachDOMProxyExpando(obj, objId, id, rhsId));
       MOZ_FALLTHROUGH;  // Fall through to the generic shadowed case.
     case ProxyStubType::DOMShadowed:
       return tryAttachDOMProxyShadowed(obj, objId, id, rhsId);
     case ProxyStubType::DOMUnshadowed:
-      if (tryAttachDOMProxyUnshadowed(obj, objId, id, rhsId)) {
-        return true;
-      }
-      if (*isTemporarilyUnoptimizable_) {
-        // Scripted setter without JIT code. Just wait.
-        return false;
-      }
+      TRY_ATTACH(tryAttachDOMProxyUnshadowed(obj, objId, id, rhsId));
       return tryAttachGenericProxy(obj, objId, id, rhsId,
                                    /* handleDOMProxies = */ true);
     case ProxyStubType::Generic:
       return tryAttachGenericProxy(obj, objId, id, rhsId,
                                    /* handleDOMProxies = */ false);
   }
 
   MOZ_CRASH("Unexpected ProxyStubType");
 }
 
-bool SetPropIRGenerator::tryAttachProxyElement(HandleObject obj,
-                                               ObjOperandId objId,
-                                               ValOperandId rhsId) {
+AttachDecision SetPropIRGenerator::tryAttachProxyElement(HandleObject obj,
+                                                         ObjOperandId objId,
+                                                         ValOperandId rhsId) {
   // Don't attach a proxy stub for ops like JSOP_INITELEM.
   MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
 
   if (!obj->is<ProxyObject>()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   writer.guardIsProxy(objId);
 
   // Like GetPropIRGenerator::tryAttachProxyElement, don't check for DOM
   // proxies here as we don't have specialized DOM stubs for this.
   MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
   writer.callProxySetByValue(objId, setElemKeyValueId(), rhsId,
                              IsStrictSetPC(pc_));
   writer.returnFromIC();
 
   trackAttached("ProxyElement");
-  return true;
-}
-
-bool SetPropIRGenerator::tryAttachMegamorphicSetElement(HandleObject obj,
-                                                        ObjOperandId objId,
-                                                        ValOperandId rhsId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachMegamorphicSetElement(
+    HandleObject obj, ObjOperandId objId, ValOperandId rhsId) {
   MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
 
   if (mode_ != ICState::Mode::Megamorphic || cacheKind_ != CacheKind::SetElem) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // The generic proxy stubs are faster.
   if (obj->is<ProxyObject>()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   writer.megamorphicSetElement(objId, setElemKeyValueId(), rhsId,
                                IsStrictSetPC(pc_));
   writer.returnFromIC();
 
   trackAttached("MegamorphicSetElement");
-  return true;
-}
-
-bool SetPropIRGenerator::tryAttachWindowProxy(HandleObject obj,
-                                              ObjOperandId objId, HandleId id,
-                                              ValOperandId rhsId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachWindowProxy(HandleObject obj,
+                                                        ObjOperandId objId,
+                                                        HandleId id,
+                                                        ValOperandId rhsId) {
   // Attach a stub when the receiver is a WindowProxy and we can do the set
   // on the Window (the global object).
 
   if (!IsWindowProxyForScriptGlobal(script_, obj)) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // If we're megamorphic prefer a generic proxy stub that handles a lot more
   // cases.
   if (mode_ == ICState::Mode::Megamorphic) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Now try to do the set on the Window (the current global).
   Handle<GlobalObject*> windowObj = cx_->global();
 
   RootedShape propShape(cx_);
+  bool isTemporarilyUnoptimizable = false;
   if (!CanAttachNativeSetSlot(cx_, JSOp(*pc_), windowObj, id,
-                              isTemporarilyUnoptimizable_, &propShape)) {
-    return false;
+                              &isTemporarilyUnoptimizable, &propShape)) {
+    return isTemporarilyUnoptimizable ? AttachDecision::TemporarilyUnoptimizable
+                                      : AttachDecision::NoAction;
   }
 
   maybeEmitIdGuard(id);
 
   ObjOperandId windowObjId =
       GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
   writer.guardShape(windowObjId, windowObj->lastProperty());
   writer.guardGroupForTypeBarrier(windowObjId, windowObj->group());
   typeCheckInfo_.set(windowObj->group(), id);
 
   EmitStoreSlotAndReturn(writer, windowObjId, windowObj, propShape, rhsId);
 
   trackAttached("WindowProxySlot");
-  return true;
+  return AttachDecision::Attach;
 }
 
 bool SetPropIRGenerator::canAttachAddSlotStub(HandleObject obj, HandleId id) {
   // Special-case JSFunction resolve hook to allow redefining the 'prototype'
   // property without triggering lazy expansion of property and object
   // allocation.
   if (obj->is<JSFunction>() && JSID_IS_ATOM(id, cx_->names().prototype)) {
     MOZ_ASSERT(ClassMayResolveId(cx_->names(), obj->getClass(), id, obj));
@@ -4330,73 +4226,73 @@ bool SetPropIRGenerator::canAttachAddSlo
         !proto->is<JSFunction>()) {
       return false;
     }
   }
 
   return true;
 }
 
-bool SetPropIRGenerator::tryAttachAddSlotStub(HandleObjectGroup oldGroup,
-                                              HandleShape oldShape) {
+AttachDecision SetPropIRGenerator::tryAttachAddSlotStub(
+    HandleObjectGroup oldGroup, HandleShape oldShape) {
   ValOperandId objValId(writer.setInputOperandId(0));
   ValOperandId rhsValId;
   if (cacheKind_ == CacheKind::SetProp) {
     rhsValId = ValOperandId(writer.setInputOperandId(1));
   } else {
     MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
     MOZ_ASSERT(setElemKeyValueId().id() == 1);
     writer.setInputOperandId(1);
     rhsValId = ValOperandId(writer.setInputOperandId(2));
   }
 
   RootedId id(cx_);
   bool nameOrSymbol;
   if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
     cx_->clearPendingException();
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (!lhsVal_.isObject() || !nameOrSymbol) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   RootedObject obj(cx_, &lhsVal_.toObject());
 
   PropertyResult prop;
   if (!LookupOwnPropertyPure(cx_, obj, id, &prop)) {
-    return false;
+    return AttachDecision::NoAction;
   }
   if (!prop) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (!obj->isNative()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   Shape* propShape = prop.shape();
   NativeObject* holder = &obj->as<NativeObject>();
 
   MOZ_ASSERT(propShape);
 
   // The property must be the last added property of the object.
   MOZ_RELEASE_ASSERT(holder->lastProperty() == propShape);
 
   // Old shape should be parent of new shape. Object flag updates may make this
   // false even for simple data properties. It may be possible to support these
   // transitions in the future, but ignore now for simplicity.
   if (propShape->previous() != oldShape) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Basic shape checks.
   if (propShape->inDictionary() || !propShape->isDataProperty() ||
       !propShape->writable()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ObjOperandId objId = writer.guardIsObject(objValId);
   maybeEmitIdGuard(id);
 
   // In addition to guarding for type barrier, we need this group guard (or
   // shape guard below) to ensure class is unchanged. This group guard may also
   // imply maybeInterpretedFunction() for the special-case of function
@@ -4448,77 +4344,77 @@ bool SetPropIRGenerator::tryAttachAddSlo
       writer.allocateAndStoreDynamicSlot(holderId, offset, rhsValId, propShape,
                                          changeGroup, newGroup, numNewSlots);
       trackAttached("AllocateSlot");
     }
   }
   writer.returnFromIC();
 
   typeCheckInfo_.set(oldGroup, id);
-  return true;
+  return AttachDecision::Attach;
 }
 
 InstanceOfIRGenerator::InstanceOfIRGenerator(JSContext* cx, HandleScript script,
                                              jsbytecode* pc, ICState::Mode mode,
                                              HandleValue lhs, HandleObject rhs)
     : IRGenerator(cx, script, pc, CacheKind::InstanceOf, mode),
       lhsVal_(lhs),
       rhsObj_(rhs) {}
 
-bool InstanceOfIRGenerator::tryAttachStub() {
+AttachDecision InstanceOfIRGenerator::tryAttachStub() {
   MOZ_ASSERT(cacheKind_ == CacheKind::InstanceOf);
   AutoAssertNoPendingException aanpe(cx_);
 
   // Ensure RHS is a function -- could be a Proxy, which the IC isn't prepared
   // to handle.
   if (!rhsObj_->is<JSFunction>()) {
     trackAttached(IRGenerator::NotAttached);
-    return false;
+    return AttachDecision::NoAction;
   }
 
   HandleFunction fun = rhsObj_.as<JSFunction>();
 
   if (fun->isBoundFunction()) {
     trackAttached(IRGenerator::NotAttached);
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // If the user has supplied their own @@hasInstance method we shouldn't
   // clobber it.
   if (!js::FunctionHasDefaultHasInstance(fun, cx_->wellKnownSymbols())) {
     trackAttached(IRGenerator::NotAttached);
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Refuse to optimize any function whose [[Prototype]] isn't
   // Function.prototype.
   if (!fun->hasStaticPrototype() || fun->hasUncacheableProto()) {
     trackAttached(IRGenerator::NotAttached);
-    return false;
+    return AttachDecision::NoAction;
   }
 
   Value funProto = cx_->global()->getPrototype(JSProto_Function);
   if (!funProto.isObject() || fun->staticPrototype() != &funProto.toObject()) {
     trackAttached(IRGenerator::NotAttached);
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Ensure that the function's prototype slot is the same.
   Shape* shape = fun->lookupPure(cx_->names().prototype);
   if (!shape || !shape->isDataProperty()) {
     trackAttached(IRGenerator::NotAttached);
-    return false;
+    return AttachDecision::NoAction;
   }
 
   uint32_t slot = shape->slot();
 
   MOZ_ASSERT(fun->numFixedSlots() == 0, "Stub code relies on this");
   if (!fun->getSlot(slot).isObject()) {
     trackAttached(IRGenerator::NotAttached);
-    return false;
+    return AttachDecision::NoAction;
   }
 
   JSObject* prototypeObject = &fun->getSlot(slot).toObject();
 
   // Abstract Objects
   ValOperandId lhs(writer.setInputOperandId(0));
   ValOperandId rhs(writer.setInputOperandId(1));
 
@@ -4530,17 +4426,17 @@ bool InstanceOfIRGenerator::tryAttachStu
   // Ensure that rhs[slot] == prototypeObject.
   writer.guardFunctionPrototype(rhsId, slot, protoId);
 
   // Needn't guard LHS is object, because the actual stub can handle that
   // and correctly return false.
   writer.loadInstanceOfObjectResult(lhs, protoId, slot);
   writer.returnFromIC();
   trackAttached("InstanceOf");
-  return true;
+  return AttachDecision::Attach;
 }
 
 void InstanceOfIRGenerator::trackAttached(const char* name) {
 #ifdef JS_CACHEIR_SPEW
   if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
     sp.valueProperty("lhs", lhsVal_);
     sp.valueProperty("rhs", ObjectValue(*rhsObj_));
   }
@@ -4555,100 +4451,96 @@ TypeOfIRGenerator::TypeOfIRGenerator(JSC
 void TypeOfIRGenerator::trackAttached(const char* name) {
 #ifdef JS_CACHEIR_SPEW
   if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
     sp.valueProperty("val", val_);
   }
 #endif
 }
 
-bool TypeOfIRGenerator::tryAttachStub() {
+AttachDecision TypeOfIRGenerator::tryAttachStub() {
   MOZ_ASSERT(cacheKind_ == CacheKind::TypeOf);
 
   AutoAssertNoPendingException aanpe(cx_);
 
   ValOperandId valId(writer.setInputOperandId(0));
 
-  if (tryAttachPrimitive(valId)) {
-    return true;
-  }
-
-  MOZ_ALWAYS_TRUE(tryAttachObject(valId));
-  return true;
-}
-
-bool TypeOfIRGenerator::tryAttachPrimitive(ValOperandId valId) {
+  TRY_ATTACH(tryAttachPrimitive(valId));
+  TRY_ATTACH(tryAttachObject(valId));
+
+  MOZ_ASSERT_UNREACHABLE("Failed to attach TypeOf");
+  return AttachDecision::NoAction;
+}
+
+AttachDecision TypeOfIRGenerator::tryAttachPrimitive(ValOperandId valId) {
   if (!val_.isPrimitive()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (val_.isNumber()) {
     writer.guardIsNumber(valId);
   } else {
     writer.guardType(valId, val_.type());
   }
 
   writer.loadStringResult(TypeName(js::TypeOfValue(val_), cx_->names()));
   writer.returnFromIC();
   trackAttached("Primitive");
-  return true;
-}
-
-bool TypeOfIRGenerator::tryAttachObject(ValOperandId valId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision TypeOfIRGenerator::tryAttachObject(ValOperandId valId) {
   if (!val_.isObject()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ObjOperandId objId = writer.guardIsObject(valId);
   writer.loadTypeOfObjectResult(objId);
   writer.returnFromIC();
   trackAttached("Object");
-  return true;
+  return AttachDecision::Attach;
 }
 
 GetIteratorIRGenerator::GetIteratorIRGenerator(JSContext* cx,
                                                HandleScript script,
                                                jsbytecode* pc,
                                                ICState::Mode mode,
                                                HandleValue value)
     : IRGenerator(cx, script, pc, CacheKind::GetIterator, mode), val_(value) {}
 
-bool GetIteratorIRGenerator::tryAttachStub() {
+AttachDecision GetIteratorIRGenerator::tryAttachStub() {
   MOZ_ASSERT(cacheKind_ == CacheKind::GetIterator);
 
   AutoAssertNoPendingException aanpe(cx_);
 
   if (mode_ == ICState::Mode::Megamorphic) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ValOperandId valId(writer.setInputOperandId(0));
   if (!val_.isObject()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   RootedObject obj(cx_, &val_.toObject());
 
   ObjOperandId objId = writer.guardIsObject(valId);
-  if (tryAttachNativeIterator(objId, obj)) {
-    trackAttached("GetIterator");
-    return true;
-  }
+  TRY_ATTACH(tryAttachNativeIterator(objId, obj));
 
   trackAttached(IRGenerator::NotAttached);
-  return false;
-}
-
-bool GetIteratorIRGenerator::tryAttachNativeIterator(ObjOperandId objId,
-                                                     HandleObject obj) {
+  return AttachDecision::NoAction;
+}
+
+AttachDecision GetIteratorIRGenerator::tryAttachNativeIterator(
+    ObjOperandId objId, HandleObject obj) {
   MOZ_ASSERT(JSOp(*pc_) == JSOP_ITER);
 
   PropertyIteratorObject* iterobj = LookupInIteratorCache(cx_, obj);
   if (!iterobj) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   MOZ_ASSERT(obj->isNative());
 
   // Guard on the receiver's shape.
   TestMatchingNativeReceiver(writer, &obj->as<NativeObject>(), objId);
 
   // Ensure the receiver has no dense elements.
@@ -4658,17 +4550,18 @@ bool GetIteratorIRGenerator::tryAttachNa
   GeneratePrototypeHoleGuards(writer, obj, objId,
                               /* alwaysGuardFirstProto = */ false);
 
   ObjOperandId iterId = writer.guardAndGetIterator(
       objId, iterobj, &ObjectRealm::get(obj).enumerators);
   writer.loadObjectResult(iterId);
   writer.returnFromIC();
 
-  return true;
+  trackAttached("GetIterator");
+  return AttachDecision::Attach;
 }
 
 void GetIteratorIRGenerator::trackAttached(const char* name) {
 #ifdef JS_CACHEIR_SPEW
   if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
     sp.valueProperty("val", val_);
   }
 #endif
@@ -5371,20 +5264,17 @@ AttachDecision CallIRGenerator::tryAttac
   CallFlags flags(isConstructing, isSpread, isSameRealm);
 
   if (isConstructing && !calleeFunc->isConstructor()) {
     return AttachDecision::NoAction;
   }
 
   // Check for specific native-function optimizations.
   if (isSpecialized) {
-    AttachDecision decision = tryAttachSpecialCaseCallNative(calleeFunc);
-    if (decision != AttachDecision::NoAction) {
-      return decision;
-    }
+    TRY_ATTACH(tryAttachSpecialCaseCallNative(calleeFunc));
   }
   if (JitOptions.disableCacheIRCalls) {
     return AttachDecision::NoAction;
   }
 
   RootedObject templateObj(cx_);
   if (isConstructing && isSpecialized &&
       !getTemplateObjectForNative(calleeFunc, &templateObj)) {
@@ -5745,191 +5635,191 @@ CompareIRGenerator::CompareIRGenerator(J
                                        jsbytecode* pc, ICState::Mode mode,
                                        JSOp op, HandleValue lhsVal,
                                        HandleValue rhsVal)
     : IRGenerator(cx, script, pc, CacheKind::Compare, mode),
       op_(op),
       lhsVal_(lhsVal),
       rhsVal_(rhsVal) {}
 
-bool CompareIRGenerator::tryAttachString(ValOperandId lhsId,
-                                         ValOperandId rhsId) {
+AttachDecision CompareIRGenerator::tryAttachString(ValOperandId lhsId,
+                                                   ValOperandId rhsId) {
   if (!lhsVal_.isString() || !rhsVal_.isString()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   StringOperandId lhsStrId = writer.guardIsString(lhsId);
   StringOperandId rhsStrId = writer.guardIsString(rhsId);
   writer.compareStringResult(op_, lhsStrId, rhsStrId);
   writer.returnFromIC();
 
   trackAttached("String");
-  return true;
-}
-
-bool CompareIRGenerator::tryAttachObject(ValOperandId lhsId,
-                                         ValOperandId rhsId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachObject(ValOperandId lhsId,
+                                                   ValOperandId rhsId) {
   MOZ_ASSERT(IsEqualityOp(op_));
 
   if (!lhsVal_.isObject() || !rhsVal_.isObject()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ObjOperandId lhsObjId = writer.guardIsObject(lhsId);
   ObjOperandId rhsObjId = writer.guardIsObject(rhsId);
   writer.compareObjectResult(op_, lhsObjId, rhsObjId);
   writer.returnFromIC();
 
   trackAttached("Object");
-  return true;
-}
-
-bool CompareIRGenerator::tryAttachSymbol(ValOperandId lhsId,
-                                         ValOperandId rhsId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachSymbol(ValOperandId lhsId,
+                                                   ValOperandId rhsId) {
   MOZ_ASSERT(IsEqualityOp(op_));
 
   if (!lhsVal_.isSymbol() || !rhsVal_.isSymbol()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   SymbolOperandId lhsSymId = writer.guardIsSymbol(lhsId);
   SymbolOperandId rhsSymId = writer.guardIsSymbol(rhsId);
   writer.compareSymbolResult(op_, lhsSymId, rhsSymId);
   writer.returnFromIC();
 
   trackAttached("Symbol");
-  return true;
-}
-
-bool CompareIRGenerator::tryAttachStrictDifferentTypes(ValOperandId lhsId,
-                                                       ValOperandId rhsId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachStrictDifferentTypes(
+    ValOperandId lhsId, ValOperandId rhsId) {
   MOZ_ASSERT(IsEqualityOp(op_));
 
   if (op_ != JSOP_STRICTEQ && op_ != JSOP_STRICTNE) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Probably can't hit some of these.
   if (SameType(lhsVal_, rhsVal_) ||
       (lhsVal_.isNumber() && rhsVal_.isNumber())) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Compare tags
   ValueTagOperandId lhsTypeId = writer.loadValueTag(lhsId);
   ValueTagOperandId rhsTypeId = writer.loadValueTag(rhsId);
   writer.guardTagNotEqual(lhsTypeId, rhsTypeId);
 
   // Now that we've passed the guard, we know differing types, so return the
   // bool result.
   writer.loadBooleanResult(op_ == JSOP_STRICTNE ? true : false);
   writer.returnFromIC();
 
   trackAttached("StrictDifferentTypes");
-  return true;
-}
-
-bool CompareIRGenerator::tryAttachInt32(ValOperandId lhsId,
-                                        ValOperandId rhsId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachInt32(ValOperandId lhsId,
+                                                  ValOperandId rhsId) {
   if ((!lhsVal_.isInt32() && !lhsVal_.isBoolean()) ||
       (!rhsVal_.isInt32() && !rhsVal_.isBoolean())) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   Int32OperandId lhsIntId = lhsVal_.isBoolean() ? writer.guardIsBoolean(lhsId)
                                                 : writer.guardIsInt32(lhsId);
   Int32OperandId rhsIntId = rhsVal_.isBoolean() ? writer.guardIsBoolean(rhsId)
                                                 : writer.guardIsInt32(rhsId);
 
   // Strictly different types should have been handed by
   // tryAttachStrictDifferentTypes
   MOZ_ASSERT_IF(op_ == JSOP_STRICTEQ || op_ == JSOP_STRICTNE,
                 lhsVal_.isInt32() == rhsVal_.isInt32());
 
   writer.compareInt32Result(op_, lhsIntId, rhsIntId);
   writer.returnFromIC();
 
   trackAttached(lhsVal_.isBoolean() ? "Boolean" : "Int32");
-  return true;
-}
-
-bool CompareIRGenerator::tryAttachNumber(ValOperandId lhsId,
-                                         ValOperandId rhsId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachNumber(ValOperandId lhsId,
+                                                   ValOperandId rhsId) {
   if (!lhsVal_.isNumber() || !rhsVal_.isNumber()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   writer.guardIsNumber(lhsId);
   writer.guardIsNumber(rhsId);
   writer.compareDoubleResult(op_, lhsId, rhsId);
   writer.returnFromIC();
 
   trackAttached("Number");
-  return true;
-}
-
-bool CompareIRGenerator::tryAttachObjectUndefined(ValOperandId lhsId,
-                                                  ValOperandId rhsId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachObjectUndefined(
+    ValOperandId lhsId, ValOperandId rhsId) {
   if (!(lhsVal_.isNullOrUndefined() && rhsVal_.isObject()) &&
       !(rhsVal_.isNullOrUndefined() && lhsVal_.isObject()))
-    return false;
+    return AttachDecision::NoAction;
 
   if (op_ != JSOP_EQ && op_ != JSOP_NE) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ValOperandId obj = rhsVal_.isObject() ? rhsId : lhsId;
   ValOperandId undefOrNull = rhsVal_.isObject() ? lhsId : rhsId;
 
   writer.guardIsNullOrUndefined(undefOrNull);
   ObjOperandId objOperand = writer.guardIsObject(obj);
   writer.compareObjectUndefinedNullResult(op_, objOperand);
   writer.returnFromIC();
 
   trackAttached("ObjectUndefined");
-  return true;
+  return AttachDecision::Attach;
 }
 
 // Handle NumberUndefined comparisons
-bool CompareIRGenerator::tryAttachNumberUndefined(ValOperandId lhsId,
-                                                  ValOperandId rhsId) {
+AttachDecision CompareIRGenerator::tryAttachNumberUndefined(
+    ValOperandId lhsId, ValOperandId rhsId) {
   if (!(lhsVal_.isUndefined() && rhsVal_.isNumber()) &&
       !(rhsVal_.isUndefined() && lhsVal_.isNumber())) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   lhsVal_.isNumber() ? writer.guardIsNumber(lhsId)
                      : writer.guardIsUndefined(lhsId);
   rhsVal_.isNumber() ? writer.guardIsNumber(rhsId)
                      : writer.guardIsUndefined(rhsId);
 
   // Comparing a number with undefined will always be true for NE/STRICTNE,
   // and always be false for other compare ops.
   writer.loadBooleanResult(op_ == JSOP_NE || op_ == JSOP_STRICTNE);
   writer.returnFromIC();
 
   trackAttached("NumberUndefined");
-  return true;
+  return AttachDecision::Attach;
 }
 
 // Handle Primitive x {undefined,null} equality comparisons
-bool CompareIRGenerator::tryAttachPrimitiveUndefined(ValOperandId lhsId,
-                                                     ValOperandId rhsId) {
+AttachDecision CompareIRGenerator::tryAttachPrimitiveUndefined(
+    ValOperandId lhsId, ValOperandId rhsId) {
   MOZ_ASSERT(IsEqualityOp(op_));
 
   // The set of primitive cases we want to handle here (excluding null,
   // undefined)
   auto isPrimitive = [](HandleValue& x) {
     return x.isString() || x.isSymbol() || x.isBoolean() || x.isNumber() ||
            x.isBigInt();
   };
 
   if (!(lhsVal_.isNullOrUndefined() && isPrimitive(rhsVal_)) &&
       !(rhsVal_.isNullOrUndefined() && isPrimitive(lhsVal_))) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   auto guardPrimitive = [&](HandleValue v, ValOperandId id) {
     if (v.isNumber()) {
       writer.guardIsNumber(id);
       return;
     }
     switch (v.extractNonDoubleType()) {
@@ -5957,24 +5847,24 @@ bool CompareIRGenerator::tryAttachPrimit
                        : writer.guardIsNullOrUndefined(rhsId);
 
   // Comparing a primitive with undefined/null will always be true for
   // NE/STRICTNE, and always be false for other compare ops.
   writer.loadBooleanResult(op_ == JSOP_NE || op_ == JSOP_STRICTNE);
   writer.returnFromIC();
 
   trackAttached("PrimitiveUndefined");
-  return true;
+  return AttachDecision::Attach;
 }
 
 // Handle {null/undefined} x {null,undefined} equality comparisons
-bool CompareIRGenerator::tryAttachNullUndefined(ValOperandId lhsId,
-                                                ValOperandId rhsId) {
+AttachDecision CompareIRGenerator::tryAttachNullUndefined(ValOperandId lhsId,
+                                                          ValOperandId rhsId) {
   if (!lhsVal_.isNullOrUndefined() || !rhsVal_.isNullOrUndefined()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (op_ == JSOP_EQ || op_ == JSOP_NE) {
     writer.guardIsNullOrUndefined(lhsId);
     writer.guardIsNullOrUndefined(rhsId);
     // Sloppy equality means we actually only care about the op:
     writer.loadBooleanResult(op_ == JSOP_EQ);
     trackAttached("SloppyNullUndefined");
@@ -5987,25 +5877,25 @@ bool CompareIRGenerator::tryAttachNullUn
                      : writer.guardIsUndefined(lhsId);
     rhsVal_.isNull() ? writer.guardIsNull(rhsId)
                      : writer.guardIsUndefined(rhsId);
     writer.loadBooleanResult(op_ == JSOP_STRICTEQ);
     trackAttached("StrictNullUndefinedEquality");
   }
 
   writer.returnFromIC();
-  return true;
-}
-
-bool CompareIRGenerator::tryAttachStringNumber(ValOperandId lhsId,
-                                               ValOperandId rhsId) {
+  return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachStringNumber(ValOperandId lhsId,
+                                                         ValOperandId rhsId) {
   // Ensure String x Number
   if (!(lhsVal_.isString() && rhsVal_.isNumber()) &&
       !(rhsVal_.isString() && lhsVal_.isNumber())) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Case should have been handled by tryAttachStrictlDifferentTypes
   MOZ_ASSERT(op_ != JSOP_STRICTEQ && op_ != JSOP_STRICTNE);
 
   auto createGuards = [&](HandleValue v, ValOperandId vId) {
     if (v.isString()) {
       StringOperandId strId = writer.guardIsString(vId);
@@ -6017,20 +5907,20 @@ bool CompareIRGenerator::tryAttachString
   };
 
   ValOperandId lhsGuardedId = createGuards(lhsVal_, lhsId);
   ValOperandId rhsGuardedId = createGuards(rhsVal_, rhsId);
   writer.compareDoubleResult(op_, lhsGuardedId, rhsGuardedId);
   writer.returnFromIC();
 
   trackAttached("StringNumber");
-  return true;
-}
-
-bool CompareIRGenerator::tryAttachStub() {
+  return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachStub() {
   MOZ_ASSERT(cacheKind_ == CacheKind::Compare);
   MOZ_ASSERT(IsEqualityOp(op_) || IsRelationalOp(op_));
 
   AutoAssertNoPendingException aanpe(cx_);
 
   constexpr uint8_t lhsIndex = 0;
   constexpr uint8_t rhsIndex = 1;
 
@@ -6041,72 +5931,50 @@ bool CompareIRGenerator::tryAttachStub()
   ValOperandId rhsId(writer.setInputOperandId(rhsIndex));
 
   // For sloppy equality ops, there are cases this IC does not handle:
   // - {Symbol} x {Null, Undefined, String, Bool, Number}.
   // - {String} x {Null, Undefined, Symbol, Bool, Number}.
   // - {Bool} x {Double}.
   // - {Object} x {String, Symbol, Bool, Number}.
   if (IsEqualityOp(op_)) {
-    if (tryAttachObject(lhsId, rhsId)) {
-      return true;
-    }
-    if (tryAttachSymbol(lhsId, rhsId)) {
-      return true;
-    }
+    TRY_ATTACH(tryAttachObject(lhsId, rhsId));
+    TRY_ATTACH(tryAttachSymbol(lhsId, rhsId));
 
     // Handle the special case of Object compared to null/undefined.
-    // This is special due to the IsHTMLDDA internal slot semantic,
-    if (tryAttachObjectUndefined(lhsId, rhsId)) {
-      return true;
-    }
+    // This is special due to the IsHTMLDDA internal slot semantics.
+    TRY_ATTACH(tryAttachObjectUndefined(lhsId, rhsId));
 
     // This covers -strict- equality/inequality using a type tag check, so
     // catches all different type pairs outside of Numbers, which cannot be
     // checked on tags alone.
-    if (tryAttachStrictDifferentTypes(lhsId, rhsId)) {
-      return true;
-    }
+    TRY_ATTACH(tryAttachStrictDifferentTypes(lhsId, rhsId));
 
     // These checks should come after tryAttachStrictDifferentTypes since it
     // handles strict inequality with a more generic IC.
-    if (tryAttachPrimitiveUndefined(lhsId, rhsId)) {
-      return true;
-    }
-
-    if (tryAttachNullUndefined(lhsId, rhsId)) {
-      return true;
-    }
+    TRY_ATTACH(tryAttachPrimitiveUndefined(lhsId, rhsId));
+
+    TRY_ATTACH(tryAttachNullUndefined(lhsId, rhsId));
   }
 
   // This should preceed the Int32/Number cases to allow
   // them to not concern themselves with handling undefined
   // or null.
-  if (tryAttachNumberUndefined(lhsId, rhsId)) {
-    return true;
-  }
+  TRY_ATTACH(tryAttachNumberUndefined(lhsId, rhsId));
 
   // We want these to be last, to allow us to bypass the
   // strictly-different-types cases in the below attachment code
-  if (tryAttachInt32(lhsId, rhsId)) {
-    return true;
-  }
-  if (tryAttachNumber(lhsId, rhsId)) {
-    return true;
-  }
-  if (tryAttachString(lhsId, rhsId)) {
-    return true;
-  }
-
-  if (tryAttachStringNumber(lhsId, rhsId)) {
-    return true;
-  }
+  TRY_ATTACH(tryAttachInt32(lhsId, rhsId));
+  TRY_ATTACH(tryAttachNumber(lhsId, rhsId));
+  TRY_ATTACH(tryAttachString(lhsId, rhsId));
+
+  TRY_ATTACH(tryAttachStringNumber(lhsId, rhsId));
 
   trackAttached(IRGenerator::NotAttached);
-  return false;
+  return AttachDecision::NoAction;
 }
 
 void CompareIRGenerator::trackAttached(const char* name) {
 #ifdef JS_CACHEIR_SPEW
   if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
     sp.valueProperty("lhs", lhsVal_);
     sp.valueProperty("rhs", rhsVal_);
     sp.opcodeProperty("op", op_);
@@ -6122,118 +5990,106 @@ ToBoolIRGenerator::ToBoolIRGenerator(JSC
 void ToBoolIRGenerator::trackAttached(const char* name) {
 #ifdef JS_CACHEIR_SPEW
   if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
     sp.valueProperty("val", val_);
   }
 #endif
 }
 
-bool ToBoolIRGenerator::tryAttachStub() {
+AttachDecision ToBoolIRGenerator::tryAttachStub() {
   AutoAssertNoPendingException aanpe(cx_);
 
-  if (tryAttachInt32()) {
-    return true;
-  }
-  if (tryAttachDouble()) {
-    return true;
-  }
-  if (tryAttachString()) {
-    return true;
-  }
-  if (tryAttachNullOrUndefined()) {
-    return true;
-  }
-  if (tryAttachObject()) {
-    return true;
-  }
-  if (tryAttachSymbol()) {
-    return true;
-  }
+  TRY_ATTACH(tryAttachInt32());
+  TRY_ATTACH(tryAttachDouble());
+  TRY_ATTACH(tryAttachString());
+  TRY_ATTACH(tryAttachNullOrUndefined());
+  TRY_ATTACH(tryAttachObject());
+  TRY_ATTACH(tryAttachSymbol());
 
   trackAttached(IRGenerator::NotAttached);
-  return false;
-}
-
-bool ToBoolIRGenerator::tryAttachInt32() {
+  return AttachDecision::NoAction;
+}
+
+AttachDecision ToBoolIRGenerator::tryAttachInt32() {
   if (!val_.isInt32()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ValOperandId valId(writer.setInputOperandId(0));
   writer.guardType(valId, ValueType::Int32);
   writer.loadInt32TruthyResult(valId);
   writer.returnFromIC();
   trackAttached("ToBoolInt32");
-  return true;
-}
-
-bool ToBoolIRGenerator::tryAttachDouble() {
+  return AttachDecision::Attach;
+}
+
+AttachDecision ToBoolIRGenerator::tryAttachDouble() {
   if (!val_.isDouble()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ValOperandId valId(writer.setInputOperandId(0));
   writer.guardType(valId, ValueType::Double);
   writer.loadDoubleTruthyResult(valId);
   writer.returnFromIC();
   trackAttached("ToBoolDouble");
-  return true;
-}
-
-bool ToBoolIRGenerator::tryAttachSymbol() {
+  return AttachDecision::Attach;
+}
+
+AttachDecision ToBoolIRGenerator::tryAttachSymbol() {
   if (!val_.isSymbol()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ValOperandId valId(writer.setInputOperandId(0));
   writer.guardType(valId, ValueType::Symbol);
   writer.loadBooleanResult(true);
   writer.returnFromIC();
   trackAttached("ToBoolSymbol");
-  return true;
-}
-
-bool ToBoolIRGenerator::tryAttachString() {
+  return AttachDecision::Attach;
+}
+
+AttachDecision ToBoolIRGenerator::tryAttachString() {
   if (!val_.isString()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ValOperandId valId(writer.setInputOperandId(0));
   StringOperandId strId = writer.guardIsString(valId);
   writer.loadStringTruthyResult(strId);
   writer.returnFromIC();
   trackAttached("ToBoolString");
-  return true;
-}
-
-bool ToBoolIRGenerator::tryAttachNullOrUndefined() {
+  return AttachDecision::Attach;
+}
+
+AttachDecision ToBoolIRGenerator::tryAttachNullOrUndefined() {
   if (!val_.isNullOrUndefined()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ValOperandId valId(writer.setInputOperandId(0));
   writer.guardIsNullOrUndefined(valId);
   writer.loadBooleanResult(false);
   writer.returnFromIC();
   trackAttached("ToBoolNullOrUndefined");
-  return true;
-}
-
-bool ToBoolIRGenerator::tryAttachObject() {
+  return AttachDecision::Attach;
+}
+
+AttachDecision ToBoolIRGenerator::tryAttachObject() {
   if (!val_.isObject()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ValOperandId valId(writer.setInputOperandId(0));
   ObjOperandId objId = writer.guardIsObject(valId);
   writer.loadObjectTruthyResult(objId);
   writer.returnFromIC();
   trackAttached("ToBoolObject");
-  return true;
+  return AttachDecision::Attach;
 }
 
 GetIntrinsicIRGenerator::GetIntrinsicIRGenerator(JSContext* cx,
                                                  HandleScript script,
                                                  jsbytecode* pc,
                                                  ICState::Mode mode,
                                                  HandleValue val)
     : IRGenerator(cx, script, pc, CacheKind::GetIntrinsic, mode), val_(val) {}
@@ -6241,22 +6097,22 @@ GetIntrinsicIRGenerator::GetIntrinsicIRG
 void GetIntrinsicIRGenerator::trackAttached(const char* name) {
 #ifdef JS_CACHEIR_SPEW
   if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
     sp.valueProperty("val", val_);
   }
 #endif
 }
 
-bool GetIntrinsicIRGenerator::tryAttachStub() {
+AttachDecision GetIntrinsicIRGenerator::tryAttachStub() {
   AutoAssertNoPendingException aanpe(cx_);
   writer.loadValueResult(val_);
   writer.returnFromIC();
   trackAttached("GetIntrinsic");
-  return true;
+  return AttachDecision::Attach;
 }
 
 UnaryArithIRGenerator::UnaryArithIRGenerator(JSContext* cx, HandleScript script,
                                              jsbytecode* pc, ICState::Mode mode,
                                              JSOp op, HandleValue val,
                                              HandleValue res)
     : IRGenerator(cx, script, pc, CacheKind::UnaryArith, mode),
       op_(op),
@@ -6267,32 +6123,28 @@ void UnaryArithIRGenerator::trackAttache
 #ifdef JS_CACHEIR_SPEW
   if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
     sp.valueProperty("val", val_);
     sp.valueProperty("res", res_);
   }
 #endif
 }
 
-bool UnaryArithIRGenerator::tryAttachStub() {
+AttachDecision UnaryArithIRGenerator::tryAttachStub() {
   AutoAssertNoPendingException aanpe(cx_);
-  if (tryAttachInt32()) {
-    return true;
-  }
-  if (tryAttachNumber()) {
-    return true;
-  }
+  TRY_ATTACH(tryAttachInt32());
+  TRY_ATTACH(tryAttachNumber());
 
   trackAttached(IRGenerator::NotAttached);
-  return false;
-}
-
-bool UnaryArithIRGenerator::tryAttachInt32() {
+  return AttachDecision::NoAction;
+}
+
+AttachDecision UnaryArithIRGenerator::tryAttachInt32() {
   if (!val_.isInt32() || !res_.isInt32()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ValOperandId valId(writer.setInputOperandId(0));
 
   Int32OperandId intId = writer.guardIsInt32(valId);
   switch (op_) {
     case JSOP_BITNOT:
       writer.int32NotResult(intId);
@@ -6310,22 +6162,22 @@ bool UnaryArithIRGenerator::tryAttachInt
       writer.int32DecResult(intId);
       trackAttached("UnaryArith.Int32Dec");
       break;
     default:
       MOZ_CRASH("unexpected OP");
   }
 
   writer.returnFromIC();
-  return true;
-}
-
-bool UnaryArithIRGenerator::tryAttachNumber() {
+  return AttachDecision::Attach;
+}
+
+AttachDecision UnaryArithIRGenerator::tryAttachNumber() {
   if (!val_.isNumber() || !res_.isNumber()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ValOperandId valId(writer.setInputOperandId(0));
   writer.guardIsNumber(valId);
   Int32OperandId truncatedId;
   switch (op_) {
     case JSOP_BITNOT:
       truncatedId = writer.truncateDoubleToUInt32(valId);
@@ -6344,17 +6196,17 @@ bool UnaryArithIRGenerator::tryAttachNum
       writer.doubleDecResult(valId);
       trackAttached("UnaryArith.DoubleDec");
       break;
     default:
       MOZ_CRASH("Unexpected OP");
   }
 
   writer.returnFromIC();
-  return true;
+  return AttachDecision::Attach;
 }
 
 BinaryArithIRGenerator::BinaryArithIRGenerator(
     JSContext* cx, HandleScript script, jsbytecode* pc, ICState::Mode mode,
     JSOp op, HandleValue lhs, HandleValue rhs, HandleValue res)
     : IRGenerator(cx, script, pc, CacheKind::BinaryArith, mode),
       op_(op),
       lhs_(lhs),
@@ -6366,68 +6218,55 @@ void BinaryArithIRGenerator::trackAttach
   if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
     sp.opcodeProperty("op", op_);
     sp.valueProperty("rhs", rhs_);
     sp.valueProperty("lhs", lhs_);
   }
 #endif
 }
 
-bool BinaryArithIRGenerator::tryAttachStub() {
+AttachDecision BinaryArithIRGenerator::tryAttachStub() {
   AutoAssertNoPendingException aanpe(cx_);
   // Arithmetic operations with Int32 operands
-  if (tryAttachInt32()) {
-    return true;
-  }
+  TRY_ATTACH(tryAttachInt32());
+
   // Bitwise operations with Int32 operands
-  if (tryAttachBitwise()) {
-    return true;
-  }
+  TRY_ATTACH(tryAttachBitwise());
 
   // Arithmetic operations with Double operands. This needs to come after
   // tryAttachInt32, as the guards overlap, and we'd prefer to attach the
   // more specialized Int32 IC if it is possible.
-  if (tryAttachDouble()) {
-    return true;
-  }
+  TRY_ATTACH(tryAttachDouble());
 
   // String x String
-  if (tryAttachStringConcat()) {
-    return true;
-  }
+  TRY_ATTACH(tryAttachStringConcat());
 
   // String x Object
-  if (tryAttachStringObjectConcat()) {
-    return true;
-  }
-
-  if (tryAttachStringNumberConcat()) {
-    return true;
-  }
+  TRY_ATTACH(tryAttachStringObjectConcat());
+
+  TRY_ATTACH(tryAttachStringNumberConcat());
 
   // String + Boolean
-  if (tryAttachStringBooleanConcat()) {
-    return true;
-  }
+  TRY_ATTACH(tryAttachStringBooleanConcat());
 
   trackAttached(IRGenerator::NotAttached);
-  return false;
-}
-
-bool BinaryArithIRGenerator::tryAttachBitwise() {
+  return AttachDecision::NoAction;
+}
+
+AttachDecision BinaryArithIRGenerator::tryAttachBitwise() {
   // Only bit-wise and shifts.
   if (op_ != JSOP_BITOR && op_ != JSOP_BITXOR && op_ != JSOP_BITAND &&
       op_ != JSOP_LSH && op_ != JSOP_RSH && op_ != JSOP_URSH) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Check guard conditions
   if (!(lhs_.isNumber() || lhs_.isBoolean()) ||
       !(rhs_.isNumber() || rhs_.isBoolean())) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // All ops, with the exception of URSH produce Int32 values.
   MOZ_ASSERT_IF(op_ != JSOP_URSH, res_.isInt32());
 
   ValOperandId lhsId(writer.setInputOperandId(0));
   ValOperandId rhsId(writer.setInputOperandId(1));
 
@@ -6472,29 +6311,29 @@ bool BinaryArithIRGenerator::tryAttachBi
       writer.int32URightShiftResult(lhsIntId, rhsIntId, res_.isDouble());
       trackAttached("BinaryArith.Bitwise.UnsignedRightShift");
       break;
     default:
       MOZ_CRASH("Unhandled op in tryAttachBitwise");
   }
 
   writer.returnFromIC();
-  return true;
-}
-
-bool BinaryArithIRGenerator::tryAttachDouble() {
+  return AttachDecision::Attach;
+}
+
+AttachDecision BinaryArithIRGenerator::tryAttachDouble() {
   // Check valid opcodes
   if (op_ != JSOP_ADD && op_ != JSOP_SUB && op_ != JSOP_MUL &&
       op_ != JSOP_DIV && op_ != JSOP_MOD) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Check guard conditions
   if (!lhs_.isNumber() || !rhs_.isNumber()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ValOperandId lhsId(writer.setInputOperandId(0));
   ValOperandId rhsId(writer.setInputOperandId(1));
 
   writer.guardIsNumber(lhsId);
   writer.guardIsNumber(rhsId);
 
@@ -6518,35 +6357,35 @@ bool BinaryArithIRGenerator::tryAttachDo
     case JSOP_MOD:
       writer.doubleModResult(lhsId, rhsId);
       trackAttached("BinaryArith.Double.Mod");
       break;
     default:
       MOZ_CRASH("Unhandled Op");
   }
   writer.returnFromIC();
-  return true;
-}
-
-bool BinaryArithIRGenerator::tryAttachInt32() {
+  return AttachDecision::Attach;
+}
+
+AttachDecision BinaryArithIRGenerator::tryAttachInt32() {
   // Check guard conditions
   if (!(lhs_.isInt32() || lhs_.isBoolean()) ||
       !(rhs_.isInt32() || rhs_.isBoolean())) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // These ICs will failure() if result can't be encoded in an Int32:
   // If sample result is not Int32, we should avoid IC.
   if (!res_.isInt32()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (op_ != JSOP_ADD && op_ != JSOP_SUB && op_ != JSOP_MUL &&
       op_ != JSOP_DIV && op_ != JSOP_MOD) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ValOperandId lhsId(writer.setInputOperandId(0));
   ValOperandId rhsId(writer.setInputOperandId(1));
 
   auto guardToInt32 = [&](ValOperandId id, HandleValue v) {
     if (v.isInt32()) {
       return writer.guardIsInt32(id);
@@ -6579,28 +6418,28 @@ bool BinaryArithIRGenerator::tryAttachIn
       writer.int32ModResult(lhsIntId, rhsIntId);
       trackAttached("BinaryArith.Int32.Mod");
       break;
     default:
       MOZ_CRASH("Unhandled op in tryAttachInt32");
   }
 
   writer.returnFromIC();
-  return true;
-}
-
-bool BinaryArithIRGenerator::tryAttachStringNumberConcat() {
+  return AttachDecision::Attach;
+}
+
+AttachDecision BinaryArithIRGenerator::tryAttachStringNumberConcat() {
   // Only Addition
   if (op_ != JSOP_ADD) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if (!(lhs_.isString() && rhs_.isNumber()) &&
       !(lhs_.isNumber() && rhs_.isString())) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ValOperandId lhsId(writer.setInputOperandId(0));
   ValOperandId rhsId(writer.setInputOperandId(1));
 
   auto guardToString = [&](ValOperandId id, HandleValue v) {
     if (v.isString()) {
       return writer.guardIsString(id);
@@ -6618,28 +6457,28 @@ bool BinaryArithIRGenerator::tryAttachSt
 
   StringOperandId lhsStrId = guardToString(lhsId, lhs_);
   StringOperandId rhsStrId = guardToString(rhsId, rhs_);
 
   writer.callStringConcatResult(lhsStrId, rhsStrId);
 
   writer.returnFromIC();
   trackAttached("BinaryArith.StringNumberConcat");
-  return true;
-}
-
-bool BinaryArithIRGenerator::tryAttachStringBooleanConcat() {
+  return AttachDecision::Attach;
+}
+
+AttachDecision BinaryArithIRGenerator::tryAttachStringBooleanConcat() {
   // Only Addition
   if (op_ != JSOP_ADD) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   if ((!lhs_.isString() || !rhs_.isBoolean()) &&
       (!lhs_.isBoolean() || !rhs_.isString())) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ValOperandId lhsId(writer.setInputOperandId(0));
   ValOperandId rhsId(writer.setInputOperandId(1));
 
   auto guardToString = [&](ValOperandId id, HandleValue v) {
     if (v.isString()) {
       return writer.guardIsString(id);
@@ -6651,53 +6490,53 @@ bool BinaryArithIRGenerator::tryAttachSt
 
   StringOperandId lhsStrId = guardToString(lhsId, lhs_);
   StringOperandId rhsStrId = guardToString(rhsId, rhs_);
 
   writer.callStringConcatResult(lhsStrId, rhsStrId);
 
   writer.returnFromIC();
   trackAttached("BinaryArith.StringBooleanConcat");
-  return true;
-}
-
-bool BinaryArithIRGenerator::tryAttachStringConcat() {
+  return AttachDecision::Attach;
+}
+
+AttachDecision BinaryArithIRGenerator::tryAttachStringConcat() {
   // Only Addition
   if (op_ != JSOP_ADD) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Check guards
   if (!lhs_.isString() || !rhs_.isString()) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   ValOperandId lhsId(writer.setInputOperandId(0));
   ValOperandId rhsId(writer.setInputOperandId(1));
 
   StringOperandId lhsStrId = writer.guardIsString(lhsId);
   StringOperandId rhsStrId = writer.guardIsString(rhsId);
 
   writer.callStringConcatResult(lhsStrId, rhsStrId);
 
   writer.returnFromIC();
   trackAttached("BinaryArith.StringConcat");
-  return true;
-}
-
-bool BinaryArithIRGenerator::tryAttachStringObjectConcat() {
+  return AttachDecision::Attach;
+}
+
+AttachDecision BinaryArithIRGenerator::tryAttachStringObjectConcat() {
   // Only Addition
   if (op_ != JSOP_ADD) {
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Check Guards
   if (!(lhs_.isObject() && rhs_.isString()) &&
       !(lhs_.isString() && rhs_.isObject()))
-    return false;
+    return AttachDecision::NoAction;
 
   ValOperandId lhsId(writer.setInputOperandId(0));
   ValOperandId rhsId(writer.setInputOperandId(1));
 
   // This guard is actually overly tight, as the runtime
   // helper can handle lhs or rhs being a string, so long
   // as the other is an object.
   if (lhs_.isString()) {
@@ -6707,17 +6546,17 @@ bool BinaryArithIRGenerator::tryAttachSt
     writer.guardIsObject(lhsId);
     writer.guardIsString(rhsId);
   }
 
   writer.callStringObjectConcatResult(lhsId, rhsId);
 
   writer.returnFromIC();
   trackAttached("BinaryArith.StringObjectConcat");
-  return true;
+  return AttachDecision::Attach;
 }
 
 NewObjectIRGenerator::NewObjectIRGenerator(JSContext* cx, HandleScript script,
                                            jsbytecode* pc, ICState::Mode mode,
                                            JSOp op, HandleObject templateObj)
     : IRGenerator(cx, script, pc, CacheKind::NewObject, mode),
 #ifdef JS_CACHEIR_SPEW
       op_(op),
@@ -6729,43 +6568,43 @@ NewObjectIRGenerator::NewObjectIRGenerat
 void NewObjectIRGenerator::trackAttached(const char* name) {
 #ifdef JS_CACHEIR_SPEW
   if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
     sp.opcodeProperty("op", op_);
   }
 #endif
 }
 
-bool NewObjectIRGenerator::tryAttachStub() {
+AttachDecision NewObjectIRGenerator::tryAttachStub() {
   AutoAssertNoPendingException aanpe(cx_);
   if (templateObject_->as<PlainObject>().hasDynamicSlots()) {
     trackAttached(IRGenerator::NotAttached);
-    return false;
+    return AttachDecision::NoAction;
   }
 
   // Don't attach stub if group is pretenured, as the stub
   // won't succeed.
   AutoSweepObjectGroup sweep(templateObject_->group());
   if (templateObject_->group()->shouldPreTenure(sweep)) {
     trackAttached(IRGenerator::NotAttached);
-    return false;
+    return AttachDecision::NoAction;
   }
   // Stub doesn't support metadata builder
   if (cx_->realm()->hasAllocationMetadataBuilder()) {
     trackAttached(IRGenerator::NotAttached);
-    return false;
+    return AttachDecision::NoAction;
   }
 
   writer.guardNoAllocationMetadataBuilder();
   writer.guardObjectGroupNotPretenured(templateObject_->group());
   writer.loadNewObjectFromTemplateResult(templateObject_);
   writer.returnFromIC();
 
   trackAttached("NewObjectWithTemplate");
-  return true;
+  return AttachDecision::Attach;
 }
 
 #ifdef JS_SIMULATOR
 bool js::jit::CallAnyNative(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   RootedObject calleeObj(cx, &args.callee());
 
   MOZ_ASSERT(calleeObj->is<JSFunction>());
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -506,22 +506,47 @@ class CallFlags {
   static const uint8_t IsConstructing = 1 << 5;
   static const uint8_t IsSameRealm = 1 << 6;
 
   friend class CacheIRReader;
   friend class CacheIRWriter;
 };
 
 enum class AttachDecision {
+  // We cannot attach a stub.
   NoAction,
+
+  // We can attach a stub.
   Attach,
+
+  // We cannot currently attach a stub, but we expect to be able to do so in the
+  // future. In this case, we do not call trackNotAttached().
   TemporarilyUnoptimizable,
+
+  // We want to attach a stub, but the result of the operation is
+  // needed to generate that stub. For example, AddSlot needs to know
+  // the resulting shape. Note: the attached stub will inspect the
+  // inputs to the operation, so most input checks should be done
+  // before the actual operation, with only minimal checks remaining
+  // for the deferred portion. This prevents arbitrary scripted code
+  // run by the operation from interfering with the conditions being
+  // checked.
   Deferred
 };
 
+// If the input expression evaluates to an AttachDecision other than NoAction,
+// return that AttachDecision. If it is NoAction, do nothing.
+#define TRY_ATTACH(expr)                      \
+  do {                                        \
+    AttachDecision result = expr;             \
+    if (result != AttachDecision::NoAction) { \
+      return result;                          \
+    }                                         \
+  } while (0)
+
 // Set of arguments supported by GetIndexOfArgument.
 // Support for Arg2 and up can be added easily, but is currently unneeded.
 enum class ArgumentKind : uint8_t { Callee, This, NewTarget, Arg0, Arg1 };
 
 // This function calculates the index of an argument based on the call flags.
 // addArgc is an out-parameter, indicating whether the value of argc should
 // be added to the return value to find the actual index.
 inline int32_t GetIndexOfArgument(ArgumentKind kind, CallFlags flags,
@@ -1936,69 +1961,81 @@ static inline GetPropertyResultFlags& op
   return lhs;
 }
 
 // GetPropIRGenerator generates CacheIR for a GetProp IC.
 class MOZ_RAII GetPropIRGenerator : public IRGenerator {
   HandleValue val_;
   HandleValue idVal_;
   HandleValue receiver_;
-  bool* isTemporarilyUnoptimizable_;
   GetPropertyResultFlags resultFlags_;
 
   enum class PreliminaryObjectAction { None, Unlink, NotePreliminary };
   PreliminaryObjectAction preliminaryObjectAction_;
 
-  bool tryAttachNative(HandleObject obj, ObjOperandId objId, HandleId id);
-  bool tryAttachUnboxed(HandleObject obj, ObjOperandId objId, HandleId id);
-  bool tryAttachUnboxedExpando(HandleObject obj, ObjOperandId objId,
-                               HandleId id);
-  bool tryAttachTypedObject(HandleObject obj, ObjOperandId objId, HandleId id);
-  bool tryAttachObjectLength(HandleObject obj, ObjOperandId objId, HandleId id);
-  bool tryAttachModuleNamespace(HandleObject obj, ObjOperandId objId,
-                                HandleId id);
-  bool tryAttachWindowProxy(HandleObject obj, ObjOperandId objId, HandleId id);
-  bool tryAttachCrossCompartmentWrapper(HandleObject obj, ObjOperandId objId,
-                                        HandleId id);
-  bool tryAttachXrayCrossCompartmentWrapper(HandleObject obj,
-                                            ObjOperandId objId, HandleId id);
-  bool tryAttachFunction(HandleObject obj, ObjOperandId objId, HandleId id);
-
-  bool tryAttachGenericProxy(HandleObject obj, ObjOperandId objId, HandleId id,
-                             bool handleDOMProxies);
-  bool tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objId,
-                                HandleId id);
-  bool tryAttachDOMProxyShadowed(HandleObject obj, ObjOperandId objId,
+  AttachDecision tryAttachNative(HandleObject obj, ObjOperandId objId,
                                  HandleId id);
-  bool tryAttachDOMProxyUnshadowed(HandleObject obj, ObjOperandId objId,
+  AttachDecision tryAttachUnboxed(HandleObject obj, ObjOperandId objId,
+                                  HandleId id);
+  AttachDecision tryAttachUnboxedExpando(HandleObject obj, ObjOperandId objId,
+                                         HandleId id);
+  AttachDecision tryAttachTypedObject(HandleObject obj, ObjOperandId objId,
+                                      HandleId id);
+  AttachDecision tryAttachObjectLength(HandleObject obj, ObjOperandId objId,
+                                       HandleId id);
+  AttachDecision tryAttachModuleNamespace(HandleObject obj, ObjOperandId objId,
+                                          HandleId id);
+  AttachDecision tryAttachWindowProxy(HandleObject obj, ObjOperandId objId,
+                                      HandleId id);
+  AttachDecision tryAttachCrossCompartmentWrapper(HandleObject obj,
+                                                  ObjOperandId objId,
+                                                  HandleId id);
+  AttachDecision tryAttachXrayCrossCompartmentWrapper(HandleObject obj,
+                                                      ObjOperandId objId,
+                                                      HandleId id);
+  AttachDecision tryAttachFunction(HandleObject obj, ObjOperandId objId,
                                    HandleId id);
-  bool tryAttachProxy(HandleObject obj, ObjOperandId objId, HandleId id);
 
-  bool tryAttachPrimitive(ValOperandId valId, HandleId id);
-  bool tryAttachStringChar(ValOperandId valId, ValOperandId indexId);
-  bool tryAttachStringLength(ValOperandId valId, HandleId id);
-  bool tryAttachMagicArgumentsName(ValOperandId valId, HandleId id);
+  AttachDecision tryAttachGenericProxy(HandleObject obj, ObjOperandId objId,
+                                       HandleId id, bool handleDOMProxies);
+  AttachDecision tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objId,
+                                          HandleId id);
+  AttachDecision tryAttachDOMProxyShadowed(HandleObject obj, ObjOperandId objId,
+                                           HandleId id);
+  AttachDecision tryAttachDOMProxyUnshadowed(HandleObject obj,
+                                             ObjOperandId objId, HandleId id);
+  AttachDecision tryAttachProxy(HandleObject obj, ObjOperandId objId,
+                                HandleId id);
 
-  bool tryAttachMagicArgument(ValOperandId valId, ValOperandId indexId);
-  bool tryAttachArgumentsObjectArg(HandleObject obj, ObjOperandId objId,
-                                   Int32OperandId indexId);
+  AttachDecision tryAttachPrimitive(ValOperandId valId, HandleId id);
+  AttachDecision tryAttachStringChar(ValOperandId valId, ValOperandId indexId);
+  AttachDecision tryAttachStringLength(ValOperandId valId, HandleId id);
+  AttachDecision tryAttachMagicArgumentsName(ValOperandId valId, HandleId id);
 
-  bool tryAttachDenseElement(HandleObject obj, ObjOperandId objId,
-                             uint32_t index, Int32OperandId indexId);
-  bool tryAttachDenseElementHole(HandleObject obj, ObjOperandId objId,
-                                 uint32_t index, Int32OperandId indexId);
-  bool tryAttachSparseElement(HandleObject obj, ObjOperandId objId,
-                              uint32_t index, Int32OperandId indexId);
-  bool tryAttachTypedElement(HandleObject obj, ObjOperandId objId,
-                             uint32_t index, Int32OperandId indexId);
+  AttachDecision tryAttachMagicArgument(ValOperandId valId,
+                                        ValOperandId indexId);
+  AttachDecision tryAttachArgumentsObjectArg(HandleObject obj,
+                                             ObjOperandId objId,
+                                             Int32OperandId indexId);
 
-  bool tryAttachGenericElement(HandleObject obj, ObjOperandId objId,
-                               uint32_t index, Int32OperandId indexId);
+  AttachDecision tryAttachDenseElement(HandleObject obj, ObjOperandId objId,
+                                       uint32_t index, Int32OperandId indexId);
+  AttachDecision tryAttachDenseElementHole(HandleObject obj, ObjOperandId objId,
+                                           uint32_t index,
+                                           Int32OperandId indexId);
+  AttachDecision tryAttachSparseElement(HandleObject obj, ObjOperandId objId,
+                                        uint32_t index, Int32OperandId indexId);
+  AttachDecision tryAttachTypedElement(HandleObject obj, ObjOperandId objId,
+                                       uint32_t index, Int32OperandId indexId);
 
-  bool tryAttachProxyElement(HandleObject obj, ObjOperandId objId);
+  AttachDecision tryAttachGenericElement(HandleObject obj, ObjOperandId objId,
+                                         uint32_t index,
+                                         Int32OperandId indexId);
+
+  AttachDecision tryAttachProxyElement(HandleObject obj, ObjOperandId objId);
 
   void attachMegamorphicNativeSlot(ObjOperandId objId, jsid id,
                                    bool handleMissing);
 
   ValOperandId getElemKeyValueId() const {
     MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
                cacheKind_ == CacheKind::GetElemSuper);
     return ValOperandId(1);
@@ -2025,67 +2062,66 @@ class MOZ_RAII GetPropIRGenerator : publ
   // If this is a GetElem cache, emit instructions to guard the incoming Value
   // matches |id|.
   void maybeEmitIdGuard(jsid id);
 
   void trackAttached(const char* name);
 
  public:
   GetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
-                     CacheKind cacheKind, ICState::Mode mode,
-                     bool* isTemporarilyUnoptimizable, HandleValue val,
+                     ICState::Mode mode, CacheKind cacheKind, HandleValue val,
                      HandleValue idVal, HandleValue receiver,
                      GetPropertyResultFlags resultFlags);
 
-  bool tryAttachStub();
-  bool tryAttachIdempotentStub();
+  AttachDecision tryAttachStub();
+  AttachDecision tryAttachIdempotentStub();
 
   bool shouldUnlinkPreliminaryObjectStubs() const {
     return preliminaryObjectAction_ == PreliminaryObjectAction::Unlink;
   }
   bool shouldNotePreliminaryObjectStub() const {
     return preliminaryObjectAction_ == PreliminaryObjectAction::NotePreliminary;
   }
 };
 
 // GetNameIRGenerator generates CacheIR for a GetName IC.
 class MOZ_RAII GetNameIRGenerator : public IRGenerator {
   HandleObject env_;
   HandlePropertyName name_;
 
-  bool tryAttachGlobalNameValue(ObjOperandId objId, HandleId id);
-  bool tryAttachGlobalNameGetter(ObjOperandId objId, HandleId id);
-  bool tryAttachEnvironmentName(ObjOperandId objId, HandleId id);
+  AttachDecision tryAttachGlobalNameValue(ObjOperandId objId, HandleId id);
+  AttachDecision tryAttachGlobalNameGetter(ObjOperandId objId, HandleId id);
+  AttachDecision tryAttachEnvironmentName(ObjOperandId objId, HandleId id);
 
   void trackAttached(const char* name);
 
  public:
   GetNameIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
                      ICState::Mode mode, HandleObject env,
                      HandlePropertyName name);
 
-  bool tryAttachStub();
+  AttachDecision tryAttachStub();
 };
 
 // BindNameIRGenerator generates CacheIR for a BindName IC.
 class MOZ_RAII BindNameIRGenerator : public IRGenerator {
   HandleObject env_;
   HandlePropertyName name_;
 
-  bool tryAttachGlobalName(ObjOperandId objId, HandleId id);
-  bool tryAttachEnvironmentName(ObjOperandId objId, HandleId id);
+  AttachDecision tryAttachGlobalName(ObjOperandId objId, HandleId id);
+  AttachDecision tryAttachEnvironmentName(ObjOperandId objId, HandleId id);
 
   void trackAttached(const char* name);
 
  public:
   BindNameIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
                       ICState::Mode mode, HandleObject env,
                       HandlePropertyName name);
 
-  bool tryAttachStub();
+  AttachDecision tryAttachStub();
 };
 
 // Information used by SetProp/SetElem stubs to check/update property types.
 class MOZ_RAII PropertyTypeCheckInfo {
   RootedObjectGroup group_;
   RootedId id_;
   bool needsTypeBarrier_;
 
@@ -2117,194 +2153,211 @@ class MOZ_RAII PropertyTypeCheckInfo {
   }
 };
 
 // SetPropIRGenerator generates CacheIR for a SetProp IC.
 class MOZ_RAII SetPropIRGenerator : public IRGenerator {
   HandleValue lhsVal_;
   HandleValue idVal_;
   HandleValue rhsVal_;
-  bool* isTemporarilyUnoptimizable_;
-  bool* canAddSlot_;
   PropertyTypeCheckInfo typeCheckInfo_;
 
   enum class PreliminaryObjectAction { None, Unlink, NotePreliminary };
   PreliminaryObjectAction preliminaryObjectAction_;
   bool attachedTypedArrayOOBStub_;
 
   bool maybeHasExtraIndexedProps_;
 
+ public:
+  enum class DeferType { None, AddSlot };
+
+ private:
+  DeferType deferType_ = DeferType::None;
+
   ValOperandId setElemKeyValueId() const {
     MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
     return ValOperandId(1);
   }
   ValOperandId rhsValueId() const {
     if (cacheKind_ == CacheKind::SetProp) {
       return ValOperandId(1);
     }
     MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
     return ValOperandId(2);
   }
 
   // If this is a SetElem cache, emit instructions to guard the incoming Value
   // matches |id|.
   void maybeEmitIdGuard(jsid id);
 
-  bool tryAttachNativeSetSlot(HandleObject obj, ObjOperandId objId, HandleId id,
-                              ValOperandId rhsId);
-  bool tryAttachUnboxedExpandoSetSlot(HandleObject obj, ObjOperandId objId,
+  AttachDecision tryAttachNativeSetSlot(HandleObject obj, ObjOperandId objId,
+                                        HandleId id, ValOperandId rhsId);
+  AttachDecision tryAttachUnboxedExpandoSetSlot(HandleObject obj,
+                                                ObjOperandId objId, HandleId id,
+                                                ValOperandId rhsId);
+  AttachDecision tryAttachUnboxedProperty(HandleObject obj, ObjOperandId objId,
+                                          HandleId id, ValOperandId rhsId);
+  AttachDecision tryAttachTypedObjectProperty(HandleObject obj,
+                                              ObjOperandId objId, HandleId id,
+                                              ValOperandId rhsId);
+  AttachDecision tryAttachSetter(HandleObject obj, ObjOperandId objId,
+                                 HandleId id, ValOperandId rhsId);
+  AttachDecision tryAttachSetArrayLength(HandleObject obj, ObjOperandId objId,
+                                         HandleId id, ValOperandId rhsId);
+  AttachDecision tryAttachWindowProxy(HandleObject obj, ObjOperandId objId,
                                       HandleId id, ValOperandId rhsId);
-  bool tryAttachUnboxedProperty(HandleObject obj, ObjOperandId objId,
-                                HandleId id, ValOperandId rhsId);
-  bool tryAttachTypedObjectProperty(HandleObject obj, ObjOperandId objId,
-                                    HandleId id, ValOperandId rhsId);
-  bool tryAttachSetter(HandleObject obj, ObjOperandId objId, HandleId id,
-                       ValOperandId rhsId);
-  bool tryAttachSetArrayLength(HandleObject obj, ObjOperandId objId,
-                               HandleId id, ValOperandId rhsId);
-  bool tryAttachWindowProxy(HandleObject obj, ObjOperandId objId, HandleId id,
-                            ValOperandId rhsId);
 
-  bool tryAttachSetDenseElement(HandleObject obj, ObjOperandId objId,
-                                uint32_t index, Int32OperandId indexId,
-                                ValOperandId rhsId);
-  bool tryAttachSetTypedElement(HandleObject obj, ObjOperandId objId,
-                                uint32_t index, Int32OperandId indexId,
-                                ValOperandId rhsId);
+  AttachDecision tryAttachSetDenseElement(HandleObject obj, ObjOperandId objId,
+                                          uint32_t index,
+                                          Int32OperandId indexId,
+                                          ValOperandId rhsId);
+  AttachDecision tryAttachSetTypedElement(HandleObject obj, ObjOperandId objId,
+                                          uint32_t index,
+                                          Int32OperandId indexId,
+                                          ValOperandId rhsId);
 
-  bool tryAttachSetDenseElementHole(HandleObject obj, ObjOperandId objId,
-                                    uint32_t index, Int32OperandId indexId,
-                                    ValOperandId rhsId);
+  AttachDecision tryAttachSetDenseElementHole(HandleObject obj,
+                                              ObjOperandId objId,
+                                              uint32_t index,
+                                              Int32OperandId indexId,
+                                              ValOperandId rhsId);
 
-  bool tryAttachAddOrUpdateSparseElement(HandleObject obj, ObjOperandId objId,
-                                         uint32_t index, Int32OperandId indexId,
-                                         ValOperandId rhsId);
+  AttachDecision tryAttachAddOrUpdateSparseElement(HandleObject obj,
+                                                   ObjOperandId objId,
+                                                   uint32_t index,
+                                                   Int32OperandId indexId,
+                                                   ValOperandId rhsId);
 
-  bool tryAttachGenericProxy(HandleObject obj, ObjOperandId objId, HandleId id,
-                             ValOperandId rhsId, bool handleDOMProxies);
-  bool tryAttachDOMProxyShadowed(HandleObject obj, ObjOperandId objId,
-                                 HandleId id, ValOperandId rhsId);
-  bool tryAttachDOMProxyUnshadowed(HandleObject obj, ObjOperandId objId,
-                                   HandleId id, ValOperandId rhsId);
-  bool tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objId,
+  AttachDecision tryAttachGenericProxy(HandleObject obj, ObjOperandId objId,
+                                       HandleId id, ValOperandId rhsId,
+                                       bool handleDOMProxies);
+  AttachDecision tryAttachDOMProxyShadowed(HandleObject obj, ObjOperandId objId,
+                                           HandleId id, ValOperandId rhsId);
+  AttachDecision tryAttachDOMProxyUnshadowed(HandleObject obj,
+                                             ObjOperandId objId, HandleId id,
+                                             ValOperandId rhsId);
+  AttachDecision tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objId,
+                                          HandleId id, ValOperandId rhsId);
+  AttachDecision tryAttachProxy(HandleObject obj, ObjOperandId objId,
                                 HandleId id, ValOperandId rhsId);
-  bool tryAttachProxy(HandleObject obj, ObjOperandId objId, HandleId id,
-                      ValOperandId rhsId);
-  bool tryAttachProxyElement(HandleObject obj, ObjOperandId objId,
-                             ValOperandId rhsId);
-  bool tryAttachMegamorphicSetElement(HandleObject obj, ObjOperandId objId,
-                                      ValOperandId rhsId);
+  AttachDecision tryAttachProxyElement(HandleObject obj, ObjOperandId objId,
+                                       ValOperandId rhsId);
+  AttachDecision tryAttachMegamorphicSetElement(HandleObject obj,
+                                                ObjOperandId objId,
+                                                ValOperandId rhsId);
 
   bool canAttachAddSlotStub(HandleObject obj, HandleId id);
 
  public:
   SetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
                      CacheKind cacheKind, ICState::Mode mode,
-                     bool* isTemporarilyUnoptimizable, bool* canAddSlot,
                      HandleValue lhsVal, HandleValue idVal, HandleValue rhsVal,
                      bool needsTypeBarrier = true,
                      bool maybeHasExtraIndexedProps = true);
 
-  bool tryAttachStub();
-  bool tryAttachAddSlotStub(HandleObjectGroup oldGroup, HandleShape oldShape);
+  AttachDecision tryAttachStub();
+  AttachDecision tryAttachAddSlotStub(HandleObjectGroup oldGroup,
+                                      HandleShape oldShape);
   void trackAttached(const char* name);
 
   bool shouldUnlinkPreliminaryObjectStubs() const {
     return preliminaryObjectAction_ == PreliminaryObjectAction::Unlink;
   }
   bool shouldNotePreliminaryObjectStub() const {
     return preliminaryObjectAction_ == PreliminaryObjectAction::NotePreliminary;
   }
 
   const PropertyTypeCheckInfo* typeCheckInfo() const { return &typeCheckInfo_; }
 
   bool attachedTypedArrayOOBStub() const { return attachedTypedArrayOOBStub_; }
+
+  DeferType deferType() const { return deferType_; }
 };
 
 // HasPropIRGenerator generates CacheIR for a HasProp IC. Used for
 // CacheKind::In / CacheKind::HasOwn.
 class MOZ_RAII HasPropIRGenerator : public IRGenerator {
   HandleValue val_;
   HandleValue idVal_;
 
-  bool tryAttachDense(HandleObject obj, ObjOperandId objId, uint32_t index,
-                      Int32OperandId indexId);
-  bool tryAttachDenseHole(HandleObject obj, ObjOperandId objId, uint32_t index,
-                          Int32OperandId indexId);
-  bool tryAttachTypedArray(HandleObject obj, ObjOperandId objId,
-                           Int32OperandId indexId);
-  bool tryAttachSparse(HandleObject obj, ObjOperandId objId,
-                       Int32OperandId indexId);
-  bool tryAttachNamedProp(HandleObject obj, ObjOperandId objId, HandleId key,
-                          ValOperandId keyId);
-  bool tryAttachMegamorphic(ObjOperandId objId, ValOperandId keyId);
-  bool tryAttachNative(JSObject* obj, ObjOperandId objId, jsid key,
-                       ValOperandId keyId, PropertyResult prop,
-                       JSObject* holder);
-  bool tryAttachUnboxed(JSObject* obj, ObjOperandId objId, jsid key,
-                        ValOperandId keyId);
-  bool tryAttachUnboxedExpando(JSObject* obj, ObjOperandId objId, jsid key,
-                               ValOperandId keyId);
-  bool tryAttachTypedObject(JSObject* obj, ObjOperandId objId, jsid key,
-                            ValOperandId keyId);
-  bool tryAttachSlotDoesNotExist(JSObject* obj, ObjOperandId objId, jsid key,
-                                 ValOperandId keyId);
-  bool tryAttachDoesNotExist(HandleObject obj, ObjOperandId objId, HandleId key,
-                             ValOperandId keyId);
-  bool tryAttachProxyElement(HandleObject obj, ObjOperandId objId,
-                             ValOperandId keyId);
+  AttachDecision tryAttachDense(HandleObject obj, ObjOperandId objId,
+                                uint32_t index, Int32OperandId indexId);
+  AttachDecision tryAttachDenseHole(HandleObject obj, ObjOperandId objId,
+                                    uint32_t index, Int32OperandId indexId);
+  AttachDecision tryAttachTypedArray(HandleObject obj, ObjOperandId objId,
+                                     Int32OperandId indexId);
+  AttachDecision tryAttachSparse(HandleObject obj, ObjOperandId objId,
+                                 Int32OperandId indexId);
+  AttachDecision tryAttachNamedProp(HandleObject obj, ObjOperandId objId,
+                                    HandleId key, ValOperandId keyId);
+  AttachDecision tryAttachMegamorphic(ObjOperandId objId, ValOperandId keyId);
+  AttachDecision tryAttachNative(JSObject* obj, ObjOperandId objId, jsid key,
+                                 ValOperandId keyId, PropertyResult prop,
+                                 JSObject* holder);
+  AttachDecision tryAttachUnboxed(JSObject* obj, ObjOperandId objId, jsid key,
+                                  ValOperandId keyId);
+  AttachDecision tryAttachUnboxedExpando(JSObject* obj, ObjOperandId objId,
+                                         jsid key, ValOperandId keyId);
+  AttachDecision tryAttachTypedObject(JSObject* obj, ObjOperandId objId,
+                                      jsid key, ValOperandId keyId);
+  AttachDecision tryAttachSlotDoesNotExist(JSObject* obj, ObjOperandId objId,
+                                           jsid key, ValOperandId keyId);
+  AttachDecision tryAttachDoesNotExist(HandleObject obj, ObjOperandId objId,
+                                       HandleId key, ValOperandId keyId);
+  AttachDecision tryAttachProxyElement(HandleObject obj, ObjOperandId objId,
+                                       ValOperandId keyId);
 
   void trackAttached(const char* name);
 
  public:
   // NOTE: Argument order is PROPERTY, OBJECT
   HasPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
                      ICState::Mode mode, CacheKind cacheKind, HandleValue idVal,
                      HandleValue val);
 
-  bool tryAttachStub();
+  AttachDecision tryAttachStub();
 };
 
 class MOZ_RAII InstanceOfIRGenerator : public IRGenerator {
   HandleValue lhsVal_;
   HandleObject rhsObj_;
 
   void trackAttached(const char* name);
 
  public:
   InstanceOfIRGenerator(JSContext*, HandleScript, jsbytecode*, ICState::Mode,
                         HandleValue, HandleObject);
 
-  bool tryAttachStub();
+  AttachDecision tryAttachStub();
 };
 
 class MOZ_RAII TypeOfIRGenerator : public IRGenerator {
   HandleValue val_;
 
-  bool tryAttachPrimitive(ValOperandId valId);
-  bool tryAttachObject(ValOperandId valId);
+  AttachDecision tryAttachPrimitive(ValOperandId valId);
+  AttachDecision tryAttachObject(ValOperandId valId);
   void trackAttached(const char* name);
 
  public:
   TypeOfIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc,
                     ICState::Mode mode, HandleValue value);
 
-  bool tryAttachStub();
+  AttachDecision tryAttachStub();
 };
 
 class MOZ_RAII GetIteratorIRGenerator : public IRGenerator {
   HandleValue val_;
 
-  bool tryAttachNativeIterator(ObjOperandId objId, HandleObject obj);
+  AttachDecision tryAttachNativeIterator(ObjOperandId objId, HandleObject obj);
 
  public:
   GetIteratorIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc,
                          ICState::Mode mode, HandleValue value);
 
-  bool tryAttachStub();
+  AttachDecision tryAttachStub();
 
   void trackAttached(const char* name);
 };
 
 class MOZ_RAII CallIRGenerator : public IRGenerator {
  private:
   JSOp op_;
   uint32_t argc_;
@@ -2357,124 +2410,128 @@ class MOZ_RAII CallIRGenerator : public 
   const PropertyTypeCheckInfo* typeCheckInfo() const { return &typeCheckInfo_; }
 };
 
 class MOZ_RAII CompareIRGenerator : public IRGenerator {
   JSOp op_;
   HandleValue lhsVal_;
   HandleValue rhsVal_;
 
-  bool tryAttachString(ValOperandId lhsId, ValOperandId rhsId);
-  bool tryAttachObject(ValOperandId lhsId, ValOperandId rhsId);
-  bool tryAttachSymbol(ValOperandId lhsId, ValOperandId rhsId);
-  bool tryAttachStrictDifferentTypes(ValOperandId lhsId, ValOperandId rhsId);
-  bool tryAttachInt32(ValOperandId lhsId, ValOperandId rhsId);
-  bool tryAttachNumber(ValOperandId lhsId, ValOperandId rhsId);
-  bool tryAttachNumberUndefined(ValOperandId lhsId, ValOperandId rhsId);
-  bool tryAttachPrimitiveUndefined(ValOperandId lhsId, ValOperandId rhsId);
-  bool tryAttachObjectUndefined(ValOperandId lhsId, ValOperandId rhsId);
-  bool tryAttachNullUndefined(ValOperandId lhsId, ValOperandId rhsId);
-  bool tryAttachStringNumber(ValOperandId lhsId, ValOperandId rhsId);
+  AttachDecision tryAttachString(ValOperandId lhsId, ValOperandId rhsId);
+  AttachDecision tryAttachObject(ValOperandId lhsId, ValOperandId rhsId);
+  AttachDecision tryAttachSymbol(ValOperandId lhsId, ValOperandId rhsId);
+  AttachDecision tryAttachStrictDifferentTypes(ValOperandId lhsId,
+                                               ValOperandId rhsId);
+  AttachDecision tryAttachInt32(ValOperandId lhsId, ValOperandId rhsId);
+  AttachDecision tryAttachNumber(ValOperandId lhsId, ValOperandId rhsId);
+  AttachDecision tryAttachNumberUndefined(ValOperandId lhsId,
+                                          ValOperandId rhsId);
+  AttachDecision tryAttachPrimitiveUndefined(ValOperandId lhsId,
+                                             ValOperandId rhsId);
+  AttachDecision tryAttachObjectUndefined(ValOperandId lhsId,
+                                          ValOperandId rhsId);
+  AttachDecision tryAttachNullUndefined(ValOperandId lhsId, ValOperandId rhsId);
+  AttachDecision tryAttachStringNumber(ValOperandId lhsId, ValOperandId rhsId);
 
   void trackAttached(const char* name);
 
  public:
   CompareIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc,
                      ICState::Mode mode, JSOp op, HandleValue lhsVal,
                      HandleValue rhsVal);
 
-  bool tryAttachStub();
+  AttachDecision tryAttachStub();
 };
 
 class MOZ_RAII ToBoolIRGenerator : public IRGenerator {
   HandleValue val_;
 
-  bool tryAttachInt32();
-  bool tryAttachDouble();
-  bool tryAttachString();
-  bool tryAttachSymbol();
-  bool tryAttachNullOrUndefined();
-  bool tryAttachObject();
+  AttachDecision tryAttachInt32();
+  AttachDecision tryAttachDouble();
+  AttachDecision tryAttachString();
+  AttachDecision tryAttachSymbol();
+  AttachDecision tryAttachNullOrUndefined();
+  AttachDecision tryAttachObject();
 
   void trackAttached(const char* name);
 
  public:
   ToBoolIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc,
                     ICState::Mode mode, HandleValue val);
 
-  bool tryAttachStub();
+  AttachDecision tryAttachStub();
 };
 
 class MOZ_RAII GetIntrinsicIRGenerator : public IRGenerator {
   HandleValue val_;
 
   void trackAttached(const char* name);
 
  public:
   GetIntrinsicIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc,
                           ICState::Mode, HandleValue val);
 
-  bool tryAttachStub();
+  AttachDecision tryAttachStub();
 };
 
 class MOZ_RAII UnaryArithIRGenerator : public IRGenerator {
   JSOp op_;
   HandleValue val_;
   HandleValue res_;
 
-  bool tryAttachInt32();
-  bool tryAttachNumber();
+  AttachDecision tryAttachInt32();
+  AttachDecision tryAttachNumber();
 
   void trackAttached(const char* name);
 
  public:
   UnaryArithIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc,
                         ICState::Mode mode, JSOp op, HandleValue val,
                         HandleValue res);
 
-  bool tryAttachStub();
+  AttachDecision tryAttachStub();
 };
 
 class MOZ_RAII BinaryArithIRGenerator : public IRGenerator {
   JSOp op_;
   HandleValue lhs_;
   HandleValue rhs_;
   HandleValue res_;
 
   void trackAttached(const char* name);
 
-  bool tryAttachInt32();
-  bool tryAttachDouble();
-  bool tryAttachBitwise();
-  bool tryAttachStringConcat();
-  bool tryAttachStringObjectConcat();
-  bool tryAttachStringNumberConcat();
-  bool tryAttachStringBooleanConcat();
+  AttachDecision tryAttachInt32();
+  AttachDecision tryAttachDouble();
+  AttachDecision tryAttachBitwise();
+  AttachDecision tryAttachStringConcat();
+  AttachDecision tryAttachStringObjectConcat();
+  AttachDecision tryAttachStringNumberConcat();
+  AttachDecision tryAttachStringBooleanConcat();
 
  public:
   BinaryArithIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc,
                          ICState::Mode, JSOp op, HandleValue lhs,
                          HandleValue rhs, HandleValue res);
 
-  bool tryAttachStub();
+  AttachDecision tryAttachStub();
 };
 
 class MOZ_RAII NewObjectIRGenerator : public IRGenerator {
 #ifdef JS_CACHEIR_SPEW
   JSOp op_;
 #endif
   HandleObject templateObject_;
 
   void trackAttached(const char* name);
 
  public:
   NewObjectIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc,
                        ICState::Mode, JSOp op, HandleObject templateObj);
 
-  bool tryAttachStub();
+  AttachDecision tryAttachStub();
 };
 
 static inline uint32_t SimpleTypeDescrKey(SimpleTypeDescr* descr) {
   if (descr->is<ScalarTypeDescr>()) {
     return uint32_t(descr->as<ScalarTypeDescr>().type()) << 1;
   }
   return (uint32_t(descr->as<ReferenceTypeDescr>().type()) << 1) | 1;
 }
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -1453,60 +1453,70 @@ void CodeGenerator::visitObjectGroupDisp
   MacroAssembler::BranchGCPtr lastBranch;
   LBlock* lastBlock = nullptr;
   InlinePropertyTable* propTable = mir->propTable();
   for (size_t i = 0; i < mir->numCases(); i++) {
     JSFunction* func = mir->getCase(i);
     LBlock* target = skipTrivialBlocks(mir->getCaseBlock(i))->lir();
 
     DebugOnly<bool> found = false;
+    // Find the function in the prop table.
     for (size_t j = 0; j < propTable->numEntries(); j++) {
       if (propTable->getFunction(j) != func) {
         continue;
       }
 
+      // Emit the previous prop's jump.
       if (lastBranch.isInitialized()) {
         lastBranch.emit(masm);
       }
 
+      // Setup jump for next iteration.
       ObjectGroup* group = propTable->getObjectGroup(j);
       lastBranch = MacroAssembler::BranchGCPtr(
           Assembler::Equal, temp, ImmGCPtr(group), target->label());
       lastBlock = target;
       found = true;
     }
     MOZ_ASSERT(found);
   }
 
+  // At this point the final case branch hasn't been emitted.
+
   // Jump to fallback block if we have an unknown ObjectGroup. If there's no
   // fallback block, we should have handled all cases.
-
   if (!mir->hasFallback()) {
     MOZ_ASSERT(lastBranch.isInitialized());
 
     Label ok;
+    // Change the target of the branch to OK.
     lastBranch.relink(&ok);
     lastBranch.emit(masm);
     masm.assumeUnreachable("Unexpected ObjectGroup");
     masm.bind(&ok);
 
+    // If we don't naturally fall through to the target,
+    // then jump to the target.
     if (!isNextBlock(lastBlock)) {
       masm.jump(lastBlock->label());
     }
     return;
   }
 
   LBlock* fallback = skipTrivialBlocks(mir->getFallback())->lir();
+  // This should only happen if we have zero cases. We're done then.
   if (!lastBranch.isInitialized()) {
     if (!isNextBlock(fallback)) {
       masm.jump(fallback->label());
     }
     return;
   }
 
+  // If we don't match the last object group and we have a fallback,
+  // we should jump to it.
   lastBranch.invertCondition();
   lastBranch.relink(fallback->label());
   lastBranch.emit(masm);
 
   if (!isNextBlock(lastBlock)) {
     masm.jump(lastBlock->label());
   }
 }
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -2211,17 +2211,19 @@ static OptimizationLevel GetOptimization
 }
 
 static MethodStatus Compile(JSContext* cx, HandleScript script,
                             BaselineFrame* osrFrame, jsbytecode* osrPc,
                             bool forceRecompile = false) {
   MOZ_ASSERT(jit::IsIonEnabled(cx));
   MOZ_ASSERT(jit::IsBaselineEnabled(cx));
   MOZ_ASSERT_IF(osrPc != nullptr, LoopEntryCanIonOsr(osrPc));
-  AutoGeckoProfilerEntry pseudoFrame(cx, "Ion script compilation");
+  AutoGeckoProfilerEntry pseudoFrame(
+      cx, "Ion script compilation",
+      JS::ProfilingCategoryPair::JS_IonCompilation);
 
   if (!script->hasBaselineScript()) {
     return Method_Skipped;
   }
 
   if (script->isDebuggee() || (osrFrame && osrFrame->isDebuggee())) {
     TrackAndSpewIonAbort(cx, script, "debugging");
     return Method_Skipped;
--- a/js/src/jit/IonIC.cpp
+++ b/js/src/jit/IonIC.cpp
@@ -110,16 +110,50 @@ void IonIC::trace(JSTracer* trc) {
     TraceCacheIRStub(trc, stub, stub->stubInfo());
 
     nextCodeRaw = stub->nextCodeRaw();
   }
 
   MOZ_ASSERT(nextCodeRaw == fallbackLabel_.raw());
 }
 
+// This helper handles ICState updates/transitions while attaching CacheIR
+// stubs.
+template <typename IRGenerator, typename IC, typename... Args>
+static void TryAttachIonStub(JSContext* cx, IC* ic, IonScript* ionScript,
+                             Args&&... args) {
+  if (ic->state().maybeTransition()) {
+    ic->discardStubs(cx->zone());
+  }
+
+  if (ic->state().canAttachStub()) {
+    RootedScript script(cx, ic->script());
+    bool attached = false;
+    IRGenerator gen(cx, script, ic->pc(), ic->state().mode(),
+                    std::forward<Args>(args)...);
+    switch (gen.tryAttachStub()) {
+      case AttachDecision::Attach:
+        ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript,
+                              &attached);
+        break;
+      case AttachDecision::NoAction:
+        break;
+      case AttachDecision::TemporarilyUnoptimizable:
+        attached = true;
+        break;
+      case AttachDecision::Deferred:
+        MOZ_ASSERT_UNREACHABLE("Not expected in generic TryAttachIonStub");
+        break;
+    }
+    if (!attached) {
+      ic->state().trackNotAttached();
+    }
+  }
+}
+
 /* static */
 bool IonGetPropertyIC::update(JSContext* cx, HandleScript outerScript,
                               IonGetPropertyIC* ic, HandleValue val,
                               HandleValue idVal, MutableHandleValue res) {
   // Override the return value if we are invalidated (bug 728188).
   IonScript* ionScript = outerScript->ionScript();
   AutoDetectInvalidation adi(cx, res, ionScript);
 
@@ -129,32 +163,35 @@ bool IonGetPropertyIC::update(JSContext*
   }
 
   if (ic->state().maybeTransition()) {
     ic->discardStubs(cx->zone());
   }
 
   bool attached = false;
   if (ic->state().canAttachStub()) {
-    // IonBuilder calls PropertyReadNeedsTypeBarrier to determine if it
-    // needs a type barrier. Unfortunately, PropertyReadNeedsTypeBarrier
-    // does not account for getters, so we should only attach a getter
-    // stub if we inserted a type barrier.
     jsbytecode* pc = ic->idempotent() ? nullptr : ic->pc();
-    bool isTemporarilyUnoptimizable = false;
-    GetPropIRGenerator gen(cx, outerScript, pc, ic->kind(), ic->state().mode(),
-                           &isTemporarilyUnoptimizable, val, idVal, val,
-                           ic->resultFlags());
-    if (ic->idempotent() ? gen.tryAttachIdempotentStub()
-                         : gen.tryAttachStub()) {
-      ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript,
-                            &attached);
+    GetPropIRGenerator gen(cx, outerScript, pc, ic->state().mode(), ic->kind(),
+                           val, idVal, val, ic->resultFlags());
+    switch (ic->idempotent() ? gen.tryAttachIdempotentStub()
+                             : gen.tryAttachStub()) {
+      case AttachDecision::Attach:
+        ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript,
+                              &attached);
+        break;
+      case AttachDecision::NoAction:
+        break;
+      case AttachDecision::TemporarilyUnoptimizable:
+        attached = true;
+        break;
+      case AttachDecision::Deferred:
+        MOZ_ASSERT_UNREACHABLE("No deferred GetProp stubs");
+        break;
     }
-
-    if (!attached && !isTemporarilyUnoptimizable) {
+    if (!attached) {
       ic->state().trackNotAttached();
     }
   }
 
   if (!attached && ic->idempotent()) {
     // Invalidate the cache if the property was not found, or was found on
     // a non-native object. This ensures:
     // 1) The property read has no observable side-effects.
@@ -206,32 +243,20 @@ bool IonGetPropSuperIC::update(JSContext
   // Override the return value if we are invalidated (bug 728188).
   IonScript* ionScript = outerScript->ionScript();
   AutoDetectInvalidation adi(cx, res, ionScript);
 
   if (ic->state().maybeTransition()) {
     ic->discardStubs(cx->zone());
   }
 
-  bool attached = false;
-  if (ic->state().canAttachStub()) {
-    RootedValue val(cx, ObjectValue(*obj));
-    bool isTemporarilyUnoptimizable = false;
-    GetPropIRGenerator gen(cx, outerScript, ic->pc(), ic->kind(),
-                           ic->state().mode(), &isTemporarilyUnoptimizable, val,
-                           idVal, receiver, GetPropertyResultFlags::All);
-    if (gen.tryAttachStub()) {
-      ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript,
-                            &attached);
-    }
-
-    if (!attached && !isTemporarilyUnoptimizable) {
-      ic->state().trackNotAttached();
-    }
-  }
+  RootedValue val(cx, ObjectValue(*obj));
+  TryAttachIonStub<GetPropIRGenerator, IonGetPropSuperIC>(
+      cx, ic, ionScript, ic->kind(), val, idVal, receiver,
+      GetPropertyResultFlags::All);
 
   RootedId id(cx);
   if (!ValueToId<CanGC>(cx, idVal, &id)) {
     return false;
   }
 
   if (!GetProperty(cx, obj, receiver, id, res)) {
     return false;
@@ -241,45 +266,56 @@ bool IonGetPropSuperIC::update(JSContext
   TypeScript::Monitor(cx, ic->script(), ic->pc(), res);
   return true;
 }
 
 /* static */
 bool IonSetPropertyIC::update(JSContext* cx, HandleScript outerScript,
                               IonSetPropertyIC* ic, HandleObject obj,
                               HandleValue idVal, HandleValue rhs) {
+  using DeferType = SetPropIRGenerator::DeferType;
+
   RootedShape oldShape(cx);
   RootedObjectGroup oldGroup(cx);
   IonScript* ionScript = outerScript->ionScript();
 
   bool attached = false;
-  bool isTemporarilyUnoptimizable = false;
-  bool canAddSlot = false;
+  DeferType deferType = DeferType::None;
 
   if (ic->state().maybeTransition()) {
     ic->discardStubs(cx->zone());
   }
 
   if (ic->state().canAttachStub()) {
     oldShape = obj->shape();
     oldGroup = JSObject::getGroup(cx, obj);
     if (!oldGroup) {
       return false;
     }
 
     RootedValue objv(cx, ObjectValue(*obj));
     RootedScript script(cx, ic->script());
     jsbytecode* pc = ic->pc();
-    SetPropIRGenerator gen(cx, script, pc, ic->kind(), ic->state().mode(),
-                           &isTemporarilyUnoptimizable, &canAddSlot, objv,
+    SetPropIRGenerator gen(cx, script, pc, ic->kind(), ic->state().mode(), objv,
                            idVal, rhs, ic->needsTypeBarrier(),
                            ic->guardHoles());
-    if (gen.tryAttachStub()) {
-      ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript,
-                            &attached, gen.typeCheckInfo());
+    switch (gen.tryAttachStub()) {
+      case AttachDecision::Attach:
+        ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript,
+                              &attached, gen.typeCheckInfo());
+        break;
+      case AttachDecision::NoAction:
+        break;
+      case AttachDecision::TemporarilyUnoptimizable:
+        attached = true;
+        break;
+      case AttachDecision::Deferred:
+        deferType = gen.deferType();
+        MOZ_ASSERT(deferType != DeferType::None);
+        break;
     }
   }
 
   jsbytecode* pc = ic->pc();
   if (ic->kind() == CacheKind::SetElem) {
     if (*pc == JSOP_INITELEM_INC) {
       if (!InitArrayElemOperation(cx, pc, obj, idVal.toInt32(), rhs)) {
         return false;
@@ -324,63 +360,48 @@ bool IonSetPropertyIC::update(JSContext*
   }
 
   // The SetProperty call might have entered this IC recursively, so try
   // to transition.
   if (ic->state().maybeTransition()) {
     ic->discardStubs(cx->zone());
   }
 
-  if (ic->state().canAttachStub()) {
+  bool canAttachStub = ic->state().canAttachStub();
+  if (deferType != DeferType::None && canAttachStub) {
     RootedValue objv(cx, ObjectValue(*obj));
     RootedScript script(cx, ic->script());
     jsbytecode* pc = ic->pc();
-    SetPropIRGenerator gen(cx, script, pc, ic->kind(), ic->state().mode(),
-                           &isTemporarilyUnoptimizable, &canAddSlot, objv,
+    SetPropIRGenerator gen(cx, script, pc, ic->kind(), ic->state().mode(), objv,
                            idVal, rhs, ic->needsTypeBarrier(),
                            ic->guardHoles());
-    if (canAddSlot && gen.tryAttachAddSlotStub(oldGroup, oldShape)) {
-      ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript,
-                            &attached, gen.typeCheckInfo());
-    } else {
-      gen.trackAttached(nullptr);
+    MOZ_ASSERT(deferType == DeferType::AddSlot);
+    AttachDecision decision = gen.tryAttachAddSlotStub(oldGroup, oldShape);
+
+    switch (decision) {
+      case AttachDecision::Attach:
+        ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript,
+                              &attached, gen.typeCheckInfo());
+        break;
+      case AttachDecision::NoAction:
+        gen.trackAttached(IRGenerator::NotAttached);
+        break;
+      case AttachDecision::TemporarilyUnoptimizable:
+      case AttachDecision::Deferred:
+        MOZ_ASSERT_UNREACHABLE("Invalid attach result");
+        break;
     }
-
-    if (!attached && !isTemporarilyUnoptimizable) {
-      ic->state().trackNotAttached();
-    }
+  }
+  if (!attached && canAttachStub) {
+    ic->state().trackNotAttached();
   }
 
   return true;
 }
 
-// This helper handles ICState updates/transitions while attaching CacheIR
-// stubs.
-template <typename IRGenerator, typename IC, typename... Args>
-static void TryAttachIonStub(JSContext* cx, IC* ic, IonScript* ionScript,
-                             Args&&... args) {
-  if (ic->state().maybeTransition()) {
-    ic->discardStubs(cx->zone());
-  }
-
-  if (ic->state().canAttachStub()) {
-    RootedScript script(cx, ic->script());
-    bool attached = false;
-    IRGenerator gen(cx, script, ic->pc(), ic->state().mode(),
-                    std::forward<Args>(args)...);
-    if (gen.tryAttachStub()) {
-      ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript,
-                            &attached);
-    }
-    if (!attached) {
-      ic->state().trackNotAttached();
-    }
-  }
-}
-
 /* static */
 bool IonGetNameIC::update(JSContext* cx, HandleScript outerScript,
                           IonGetNameIC* ic, HandleObject envChain,
                           MutableHandleValue res) {
   IonScript* ionScript = outerScript->ionScript();
   jsbytecode* pc = ic->pc();
   RootedPropertyName name(cx, ic->script()->getName(pc));
 
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -6668,19 +6668,20 @@ static void DrawTextRun(const gfxTextRun
                 params.drawMode |= DrawMode::GLYPH_STROKE_UNDERNEATH;
                 paintOrder = 0;
                 break;
             }
             paintOrder >>= NS_STYLE_PAINT_ORDER_BITWIDTH;
           }
         }
       }
-      StrokeOptions strokeOpts;
+      // Use ROUND joins as they are less likely to produce ugly artifacts
+      // when stroking glyphs with sharp angles (see bug 1546985).
+      StrokeOptions strokeOpts(aParams.textStrokeWidth, JoinStyle::ROUND);
       params.textStrokeColor = aParams.textStrokeColor;
-      strokeOpts.mLineWidth = aParams.textStrokeWidth;
       params.strokeOpts = &strokeOpts;
       aTextRun->Draw(aRange, aTextBaselinePt, params);
     } else {
       aTextRun->Draw(aRange, aTextBaselinePt, params);
     }
   }
 }
 
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/dynamic-toolbar-fixed-bottom-1-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<meta name="viewport" content="width=device-width">
+<style>
+html {
+    scrollbar-width: none;
+}
+#scrolled {
+    height: 2000px;
+    width: 100%;
+}
+#fixed {
+    width: 100%;
+    height: 200px;
+    position: fixed;
+    bottom: 0;
+    background: red;
+    margin-bottom: 50px;
+}
+</style>
+<body>
+  <div id="scrolled"></div>
+  <div id="fixed"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/dynamic-toolbar-fixed-bottom-1.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<!--
+  Tests that setting a fixed bottom margin in AsyncCompositionManager results
+  in the margin being applied to an element fixed to the bottom.
+
+  The fixed margin is specified as a test-pref in reftest.list.
+
+  The purpose of the fixed margin is to compensate for the transform that the
+  dynamic toolbar applies to the entire content area. We don't have a way of
+  simulating that transform in a reftest, so the fixed margin in isolation will
+  cause the fixed element to be offset from the bottom of the screen, and in
+  the ref page we use a regular CSS "margin-bottom" to match the rendering.
+-->
+<html>
+<meta name="viewport" content="width=device-width">
+<style>
+html {
+    scrollbar-width: none;
+}
+#scrolled {
+    height: 2000px;
+    width: 100%;
+}
+#fixed {
+    width: 100%;
+    height: 200px;
+    position: fixed;
+    bottom: 0;
+    background: red;
+}
+</style>
+<body>
+  <div id="scrolled"></div>
+  <div id="fixed"></div>
+</body>
+</html>
--- a/layout/reftests/async-scrolling/reftest.list
+++ b/layout/reftests/async-scrolling/reftest.list
@@ -88,8 +88,12 @@ skip-if(!asyncPan) == checkerboard-3.htm
 default-preferences
 
 skip-if(!Android) pref(apz.allow_zooming,true) == position-fixed-async-zoom-1.html position-fixed-async-zoom-1-ref.html
 skip-if(!Android) pref(apz.allow_zooming,true) == position-fixed-async-zoom-2.html position-fixed-async-zoom-2-ref.html
 skip-if(!Android) pref(apz.allow_zooming,true) == position-fixed-async-zoom-3.html position-fixed-async-zoom-3-ref.html
 skip-if(!Android) pref(apz.allow_zooming,true) == position-fixed-async-zoom-4.html position-fixed-async-zoom-4-ref.html
 skip-if(!Android) pref(apz.allow_zooming,true) == position-sticky-async-zoom-1.html position-sticky-async-zoom-1-ref.html
 skip-if(!Android) pref(apz.allow_zooming,true) == position-sticky-async-zoom-2.html position-sticky-async-zoom-2-ref.html
+
+# for this test, apz.allow_zooming is needed to ensure we take the containerless scrolling codepath that creates
+# an async zoom container (since we are testing a regression in that codepath)
+skip-if(!Android) pref(apz.allow_zooming,true) test-pref(apz.fixed-margin-override.enabled,true) test-pref(apz.fixed-margin-override.bottom,50) == dynamic-toolbar-fixed-bottom-1.html dynamic-toolbar-fixed-bottom-1-ref.html
--- a/layout/reftests/text-stroke/reftest.list
+++ b/layout/reftests/text-stroke/reftest.list
@@ -1,10 +1,11 @@
 # basic tests for webkit-text-stroke
 # fuzzy is needed here for platform dependent backends
 default-preferences pref(layout.css.prefixes.webkit,true)
 
-fuzzy-if(gtkWidget,0-255,0-20) fuzzy-if(winWidget,0-20,0-10) fails-if(skiaContent&&(gtkWidget||winWidget)) fuzzy-if(cocoaWidget&&webrender,48-48,44-44) == webkit-text-stroke-property-001.html webkit-text-stroke-property-001-ref.html
-fuzzy-if(gtkWidget,0-255,0-20) fuzzy-if(winWidget,0-20,0-10) fails-if(skiaContent&&!webrender&&gtkWidget) fuzzy-if(webrender,3-4,4-24) == webkit-text-stroke-property-002.html webkit-text-stroke-property-002-ref.html
-fuzzy-if(gtkWidget,0-255,0-20) fuzzy-if(winWidget,0-20,0-10) fuzzy-if(webrender,32-48,26-26) fails-if(skiaContent&&gtkWidget) == webkit-text-stroke-property-003.html webkit-text-stroke-property-003-ref.html
-fuzzy-if(gtkWidget,0-255,0-20) fuzzy-if(winWidget,0-20,0-10) fuzzy-if(webrender,48-64,21-33) fails-if(skiaContent&&gtkWidget) == webkit-text-stroke-property-004.html webkit-text-stroke-property-004-ref.html
-fuzzy-if(gtkWidget,0-255,0-20) fuzzy-if(winWidget,0-20,0-10) fails-if(skiaContent&&(gtkWidget||winWidget)) fuzzy-if(cocoaWidget&&webrender,48-48,44-44) == webkit-text-stroke-property-005.html webkit-text-stroke-property-005-ref.html
-fuzzy-if(gtkWidget,0-255,0-392) fuzzy-if(winWidget&&!d2d,0-48,0-372) fuzzy-if(winWidget&&d2d,0-71,0-10) == webkit-text-stroke-property-006.html webkit-text-stroke-property-006-ref.html
+# These fail on Linux without webrender due to lack of antialiasing of the HTML text stroke
+fuzzy(0-64,0-44) fails-if(gtkWidget&&!webrender) == webkit-text-stroke-property-001.html webkit-text-stroke-property-001-ref.html
+fuzzy(0-4,0-24) fails-if(gtkWidget&&!webrender) == webkit-text-stroke-property-002.html webkit-text-stroke-property-002-ref.html
+fuzzy(0-48,0-26) fails-if(gtkWidget&&!webrender) == webkit-text-stroke-property-003.html webkit-text-stroke-property-003-ref.html
+fuzzy(0-64,0-33) fails-if(gtkWidget&&!webrender) == webkit-text-stroke-property-004.html webkit-text-stroke-property-004-ref.html
+fuzzy(0-64,0-44) fails-if(gtkWidget&&!webrender) == webkit-text-stroke-property-005.html webkit-text-stroke-property-005-ref.html
+fuzzy(0-71,0-10) fails-if(gtkWidget&&!webrender) == webkit-text-stroke-property-006.html webkit-text-stroke-property-006-ref.html
--- a/layout/reftests/text-stroke/webkit-text-stroke-property-001-ref.html
+++ b/layout/reftests/text-stroke/webkit-text-stroke-property-001-ref.html
@@ -3,13 +3,14 @@
 <meta charset="utf-8">
 <title>webkit-text-stroke: SVG text reference</title>
 <link rel="author" title="Jeremy Chen" href="jeremychen@mozilla.com">
 <link rel="author" title="Mozilla" href="https://www.mozilla.org">
 <body>
 <div style="width: 500px; height: 200px;">
 <!-- set overflow to visible so that stroking near boundary won't be hidden -->
 <svg xmlns="http://www.w3.org/2000/svg" style="width: 100%; height: 100%; overflow: visible;">
-<text x="0" y="100" font-size="64px" fill="transparent" stroke="green" stroke-width="2px">TEXT stroke</text>
+<text x="0" y="100" font-size="64px" fill="transparent" stroke="green"
+      stroke-width="2px" stroke-linejoin="round">TEXT stroke</text>
 </svg>
 </div>
 </body>
 </html>
--- a/layout/reftests/text-stroke/webkit-text-stroke-property-002-ref.html
+++ b/layout/reftests/text-stroke/webkit-text-stroke-property-002-ref.html
@@ -3,13 +3,14 @@
 <meta charset="utf-8">
 <title>webkit-text-stroke: SVG text reference</title>
 <link rel="author" title="Jeremy Chen" href="jeremychen@mozilla.com">
 <link rel="author" title="Mozilla" href="https://www.mozilla.org">
 <body>
 <div style="width: 500px; height: 200px;">
 <!-- set overflow to visible so that stroking near boundary won't be hidden -->
 <svg xmlns="http://www.w3.org/2000/svg" style="width: 100%; height: 100%; overflow: visible;">
-<text x="0" y="100" font-size="64px" fill="transparent" stroke="black" stroke-width="1px">TEXT stroke</text>
+<text x="0" y="100" font-size="64px" fill="transparent" stroke="black"
+      stroke-width="1px" stroke-linejoin="round">TEXT stroke</text>
 </svg>
 </div>
 </body>
 </html>
--- a/layout/reftests/text-stroke/webkit-text-stroke-property-003-ref.html
+++ b/layout/reftests/text-stroke/webkit-text-stroke-property-003-ref.html
@@ -3,13 +3,14 @@
 <meta charset="utf-8">
 <title>webkit-text-stroke: SVG text reference</title>
 <link rel="author" title="Jeremy Chen" href="jeremychen@mozilla.com">
 <link rel="author" title="Mozilla" href="https://www.mozilla.org">
 <body>
 <div style="width: 500px; height: 200px;">
 <!-- set overflow to visible so that stroking near boundary won't be hidden -->
 <svg xmlns="http://www.w3.org/2000/svg" style="width: 100%; height: 100%; overflow: visible;">
-<text x="0" y="100" font-size="64px" fill="transparent" stroke="black" stroke-width="3px">TEXT stroke</text>
+<text x="0" y="100" font-size="64px" fill="transparent" stroke="black"
+      stroke-width="3px" stroke-linejoin="round">TEXT stroke</text>
 </svg>
 </div>
 </body>
 </html>
--- a/layout/reftests/text-stroke/webkit-text-stroke-property-004-ref.html
+++ b/layout/reftests/text-stroke/webkit-text-stroke-property-004-ref.html
@@ -3,13 +3,14 @@
 <meta charset="utf-8">
 <title>webkit-text-stroke: SVG text reference</title>
 <link rel="author" title="Jeremy Chen" href="jeremychen@mozilla.com">
 <link rel="author" title="Mozilla" href="https://www.mozilla.org">
 <body>
 <div style="width: 500px; height: 200px;">
 <!-- set overflow to visible so that stroking near boundary won't be hidden -->
 <svg xmlns="http://www.w3.org/2000/svg" style="width: 100%; height: 100%; overflow: visible;">
-<text x="0" y="100" font-size="64px" fill="transparent" stroke="black" stroke-width="5px">TEXT stroke</text>
+<text x="0" y="100" font-size="64px" fill="transparent" stroke="black"
+      stroke-width="5px" stroke-linejoin="round">TEXT stroke</text>
 </svg>
 </div>
 </body>
 </html>
--- a/layout/reftests/text-stroke/webkit-text-stroke-property-005-ref.html
+++ b/layout/reftests/text-stroke/webkit-text-stroke-property-005-ref.html
@@ -3,13 +3,14 @@
 <meta charset="utf-8">
 <title>webkit-text-stroke: SVG text reference</title>
 <link rel="author" title="Jeremy Chen" href="jeremychen@mozilla.com">
 <link rel="author" title="Mozilla" href="https://www.mozilla.org">
 <body>
 <div style="width: 500px; height: 200px;">
 <!-- set overflow to visible so that stroking near boundary won't be hidden -->
 <svg xmlns="http://www.w3.org/2000/svg" style="width: 100%; height: 100%; overflow: visible;">
-<text x="0" y="100" font-size="64px" fill="transparent" stroke="green" stroke-width="2px">TEXT stroke</text>
+<text x="0" y="100" font-size="64px" fill="transparent" stroke="green"
+      stroke-width="2px" stroke-linejoin="round">TEXT stroke</text>
 </svg>
 </div>
 </body>
 </html>
--- a/layout/reftests/text-stroke/webkit-text-stroke-property-006-ref.html
+++ b/layout/reftests/text-stroke/webkit-text-stroke-property-006-ref.html
@@ -7,13 +7,13 @@
 <style>
 @font-face { font-family: test; src: url(../fonts/sil/CharisSIL-R.ttf); }
 body { font-family: test; }
 </style>
 <body>
 <div style="width: 500px; height: 400px;">
 <svg xmlns="http://www.w3.org/2000/svg" style="width: 100%; height: 100%;">
 <text x="50" y="200" font-size="64px" stroke="green"
-      stroke-width="10px">g&#x324;&#x326;&#x308;&#x302;</text>
+      stroke-width="10px" stroke-linejoin="round">g&#x324;&#x326;&#x308;&#x302;</text>
 </svg>
 </div>
 </body>
 </html>
--- a/media/mtransport/WebrtcProxyChannelWrapper.cpp
+++ b/media/mtransport/WebrtcProxyChannelWrapper.cpp
@@ -2,16 +2,18 @@
 /* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
 /* 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 "WebrtcProxyChannelWrapper.h"
 
 #include "mozilla/net/WebrtcProxyChannelChild.h"
+#include "ipc/WebrtcProxyChannel.h"
+#include "mozilla/LoadInfo.h"
 
 #include "nsIEventTarget.h"
 #include "nsNetCID.h"
 #include "nsProxyRelease.h"
 
 #include "nr_socket_proxy_config.h"
 
 namespace mozilla {
@@ -49,19 +51,19 @@ void WebrtcProxyChannelWrapper::AsyncOpe
                           const shared_ptr<NrSocketProxyConfig>>(
             "WebrtcProxyChannelWrapper::AsyncOpen", this,
             &WebrtcProxyChannelWrapper::AsyncOpen, aHost, aPort, aConfig)));
     return;
   }
 
   MOZ_ASSERT(!mWebrtcProxyChannel, "wrapper already open");
   mWebrtcProxyChannel = new WebrtcProxyChannelChild(this);
-  mWebrtcProxyChannel->AsyncOpen(aHost, aPort, aConfig->GetBrowser(),
-                                 nsContentUtils::GetSystemPrincipal(),
-                                 aConfig->GetAlpn());
+  mWebrtcProxyChannel->AsyncOpen(aHost, aPort, aConfig->GetLoadInfoArgs(),
+                                 aConfig->GetAlpn(),
+                                 dom::TabId(aConfig->GetTabId()));
 }
 
 void WebrtcProxyChannelWrapper::SendWrite(nsTArray<uint8_t>&& aReadData) {
   if (!NS_IsMainThread()) {
     MOZ_ALWAYS_SUCCEEDS(
         mMainThread->Dispatch(NewRunnableMethod<nsTArray<uint8_t>&&>(
             "WebrtcProxyChannelWrapper::SendWrite", this,
             &WebrtcProxyChannelWrapper::SendWrite, std::move(aReadData))));
--- a/media/mtransport/ipc/PWebrtcProxyChannel.ipdl
+++ b/media/mtransport/ipc/PWebrtcProxyChannel.ipdl
@@ -4,28 +4,29 @@
 /* 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 protocol PNecko;
 include protocol PSocketProcess;
 
 include NeckoChannelParams;
+using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
 
 namespace mozilla {
 namespace net {
 
 async protocol PWebrtcProxyChannel
 {
   manager PNecko or PSocketProcess;
 
 parent:
   async AsyncOpen(nsCString aHost,
                   int32_t aPort,
-                  LoadInfoArgs? aLoadInfoArgs,
+                  LoadInfoArgs aLoadInfoArgs,
                   nsCString aAlpn);
   async Write(uint8_t[] aWriteData);
   async Close();
 
 child:
   async OnClose(nsresult aReason);
   async OnConnected();
   async OnRead(uint8_t[] aReadData);
--- a/media/mtransport/ipc/WebrtcProxyChannel.cpp
+++ b/media/mtransport/ipc/WebrtcProxyChannel.cpp
@@ -12,16 +12,18 @@
 #include "nsIContentPolicy.h"
 #include "nsIEventTarget.h"
 #include "nsIIOService.h"
 #include "nsILoadInfo.h"
 #include "nsIProtocolProxyService.h"
 #include "nsIURIMutator.h"
 #include "nsProxyRelease.h"
 #include "nsString.h"
+#include "mozilla/dom/ContentProcessManager.h"
+#include "mozilla/dom/BrowserParent.h"
 
 #include "WebrtcProxyChannelCallback.h"
 #include "WebrtcProxyLog.h"
 
 namespace mozilla {
 namespace net {
 
 class WebrtcProxyData {
@@ -38,23 +40,22 @@ class WebrtcProxyData {
   nsTArray<uint8_t> mData;
 };
 
 NS_IMPL_ISUPPORTS(WebrtcProxyChannel, nsIAuthPromptProvider,
                   nsIHttpUpgradeListener, nsIInputStreamCallback,
                   nsIInterfaceRequestor, nsIOutputStreamCallback,
                   nsIRequestObserver, nsIStreamListener)
 
-WebrtcProxyChannel::WebrtcProxyChannel(nsIAuthPromptProvider* aAuthProvider,
-                                       WebrtcProxyChannelCallback* aCallbacks)
+WebrtcProxyChannel::WebrtcProxyChannel(WebrtcProxyChannelCallback* aCallbacks)
     : mProxyCallbacks(aCallbacks),
       mClosed(false),
       mOpened(false),
       mWriteOffset(0),
-      mAuthProvider(aAuthProvider),
+      mAuthProvider(nullptr),
       mTransport(nullptr),
       mSocketIn(nullptr),
       mSocketOut(nullptr) {
   LOG(("WebrtcProxyChannel::WebrtcProxyChannel %p\n", this));
   mMainThread = GetMainThreadEventTarget();
   mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
   MOZ_RELEASE_ASSERT(mMainThread, "no main thread");
   MOZ_RELEASE_ASSERT(mSocketThread, "no socket thread");
@@ -62,16 +63,22 @@ WebrtcProxyChannel::WebrtcProxyChannel(n
 
 WebrtcProxyChannel::~WebrtcProxyChannel() {
   LOG(("WebrtcProxyChannel::~WebrtcProxyChannel %p\n", this));
 
   NS_ProxyRelease("WebrtcProxyChannel::CleanUpAuthProvider", mMainThread,
                   mAuthProvider.forget());
 }
 
+void WebrtcProxyChannel::SetTabId(dom::TabId aTabId) {
+  dom::ContentProcessManager* cpm = dom::ContentProcessManager::GetSingleton();
+  dom::ContentParentId cpId = cpm->GetTabProcessId(aTabId);
+  mAuthProvider = cpm->GetBrowserParentByProcessAndTabId(cpId, aTabId);
+}
+
 nsresult WebrtcProxyChannel::Write(nsTArray<uint8_t>&& aWriteData) {
   LOG(("WebrtcProxyChannel::Write %p\n", this));
   MOZ_ALWAYS_SUCCEEDS(
       mSocketThread->Dispatch(NewRunnableMethod<nsTArray<uint8_t>&&>(
           "WebrtcProxyChannel::Write", this,
           &WebrtcProxyChannel::EnqueueWrite_s, std::move(aWriteData))));
 
   return NS_OK;
@@ -123,17 +130,17 @@ void WebrtcProxyChannel::CloseWithReason
   }
 
   NS_ProxyRelease("WebrtcProxyChannel::CleanUpAuthProvider", mMainThread,
                   mAuthProvider.forget());
   InvokeOnClose(aReason);
 }
 
 nsresult WebrtcProxyChannel::Open(const nsCString& aHost, const int& aPort,
-                                  nsILoadInfo* aLoadInfo,
+                                  const net::LoadInfoArgs& aArgs,
                                   const nsCString& aAlpn) {
   LOG(("WebrtcProxyChannel::AsyncOpen %p\n", this));
 
   if (mOpened) {
     LOG(("WebrtcProxyChannel %p: proxy channel already open\n", this));
     CloseWithReason(NS_ERROR_FAILURE);
     return NS_ERROR_FAILURE;
   }
@@ -158,27 +165,36 @@ nsresult WebrtcProxyChannel::Open(const 
   nsCOMPtr<nsIIOService> ioService;
   ioService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
   if (NS_FAILED(rv)) {
     LOG(("WebrtcProxyChannel %p: io service missing\n", this));
     CloseWithReason(rv);
     return rv;
   }
 
+  nsCOMPtr<nsILoadInfo> loadInfo;
+  Maybe<net::LoadInfoArgs> loadInfoArgs = Some(aArgs);
+  rv = LoadInfoArgsToLoadInfo(loadInfoArgs, getter_AddRefs(loadInfo));
+  if (NS_FAILED(rv)) {
+    LOG(("WebrtcProxyChannel %p: could not init load info\n", this));
+    CloseWithReason(rv);
+    return rv;
+  }
+
   // -need to always tunnel since we're using a proxy
   // -there shouldn't be an opportunity to send cookies, but explicitly disallow
   // them anyway.
   // -the previous proxy tunnel didn't support redirects e.g. 307. don't need to
   // introduce new behavior. can't follow redirects on connect anyway.
   nsCOMPtr<nsIChannel> localChannel;
   rv = ioService->NewChannelFromURIWithProxyFlags(
       uri, nullptr,
       // Proxy flags are overridden by SetConnectOnly()
-      0, aLoadInfo->LoadingNode(), aLoadInfo->LoadingPrincipal(),
-      aLoadInfo->TriggeringPrincipal(),
+      0, loadInfo->LoadingNode(), loadInfo->LoadingPrincipal(),
+      loadInfo->TriggeringPrincipal(),
       nsILoadInfo::SEC_DONT_FOLLOW_REDIRECTS | nsILoadInfo::SEC_COOKIES_OMIT |
           // We need this flag to allow loads from any origin since this channel
           // is being used to CONNECT to an HTTP proxy.
           nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
       nsIContentPolicy::TYPE_OTHER, getter_AddRefs(localChannel));
   if (NS_FAILED(rv)) {
     LOG(("WebrtcProxyChannel %p: bad open channel\n", this));
     CloseWithReason(rv);
--- a/media/mtransport/ipc/WebrtcProxyChannel.h
+++ b/media/mtransport/ipc/WebrtcProxyChannel.h
@@ -13,22 +13,23 @@
 #include "nsIAsyncInputStream.h"
 #include "nsIAsyncOutputStream.h"
 #include "nsIAuthPromptProvider.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIStreamListener.h"
 #include "nsStringFwd.h"
 #include "nsTArray.h"
+#include "mozilla/dom/ipc/IdType.h"  // TabId
 
-class nsILoadInfo;
 class nsISocketTransport;
 
 namespace mozilla {
 namespace net {
+class LoadInfoArgs;
 
 class WebrtcProxyChannelCallback;
 class WebrtcProxyData;
 
 class WebrtcProxyChannel : public nsIHttpUpgradeListener,
                            public nsIStreamListener,
                            public nsIInputStreamCallback,
                            public nsIOutputStreamCallback,
@@ -39,21 +40,21 @@ class WebrtcProxyChannel : public nsIHtt
   NS_DECL_NSIINPUTSTREAMCALLBACK
   NS_DECL_NSIINTERFACEREQUESTOR
   NS_DECL_NSIOUTPUTSTREAMCALLBACK
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_FORWARD_SAFE_NSIAUTHPROMPTPROVIDER(mAuthProvider)
 
-  WebrtcProxyChannel(nsIAuthPromptProvider* aAuthProvider,
-                     WebrtcProxyChannelCallback* aProxyCallbacks);
+  explicit WebrtcProxyChannel(WebrtcProxyChannelCallback* aCallbacks);
 
+  void SetTabId(dom::TabId aTabId);
   nsresult Open(const nsCString& aHost, const int& aPort,
-                nsILoadInfo* aLoadInfo, const nsCString& aAlpn);
+                const net::LoadInfoArgs& aArgs, const nsCString& aAlpn);
   nsresult Write(nsTArray<uint8_t>&& aBytes);
   nsresult Close();
 
   size_t CountUnwrittenBytes() const;
 
  protected:
   virtual ~WebrtcProxyChannel();
 
--- a/media/mtransport/ipc/WebrtcProxyChannelChild.cpp
+++ b/media/mtransport/ipc/WebrtcProxyChannelChild.cpp
@@ -1,28 +1,26 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
 /* 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 "WebrtcProxyChannelChild.h"
 
-#include "mozilla/dom/PBrowserOrId.h"
 #include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/SocketProcessChild.h"
 
 #include "LoadInfo.h"
 
 #include "WebrtcProxyLog.h"
 #include "WebrtcProxyChannelCallback.h"
 
 using namespace mozilla::ipc;
 
-using mozilla::dom::PBrowserOrId;
-
 namespace mozilla {
 namespace net {
 
 mozilla::ipc::IPCResult WebrtcProxyChannelChild::RecvOnClose(
     const nsresult& aReason) {
   LOG(("WebrtcProxyChannelChild::RecvOnClose %p\n", this));
 
   MOZ_ASSERT(mProxyCallbacks, "webrtc proxy callbacks should be non-null");
@@ -62,32 +60,35 @@ WebrtcProxyChannelChild::WebrtcProxyChan
 WebrtcProxyChannelChild::~WebrtcProxyChannelChild() {
   MOZ_COUNT_DTOR(WebrtcProxyChannelChild);
 
   LOG(("WebrtcProxyChannelChild::~WebrtcProxyChannelChild %p\n", this));
 }
 
 void WebrtcProxyChannelChild::AsyncOpen(const nsCString& aHost,
                                         const int& aPort,
-                                        const PBrowserOrId& aBrowser,
-                                        nsIPrincipal* aLoadingPrincipal,
-                                        const nsCString& aAlpn) {
+                                        const net::LoadInfoArgs& aArgs,
+                                        const nsCString& aAlpn,
+                                        const dom::TabId& aTabId) {
   LOG(("WebrtcProxyChannelChild::AsyncOpen %p %s:%d\n", this, aHost.get(),
        aPort));
 
   MOZ_ASSERT(NS_IsMainThread(), "not main thread");
 
   AddIPDLReference();
 
-  gNeckoChild->SetEventTargetForActor(this, GetMainThreadEventTarget());
-  gNeckoChild->SendPWebrtcProxyChannelConstructor(this, aBrowser);
+  if (IsNeckoChild()) {
+    // We're on a content process
+    gNeckoChild->SetEventTargetForActor(this, GetMainThreadEventTarget());
+    gNeckoChild->SendPWebrtcProxyChannelConstructor(this, aTabId);
+  } else if (IsSocketProcessChild()) {
+    // We're on a socket process
+    SocketProcessChild::GetSingleton()->SetEventTargetForActor(
+        this, GetMainThreadEventTarget());
+    SocketProcessChild::GetSingleton()->SendPWebrtcProxyChannelConstructor(
+        this, aTabId);
+  }
 
-  nsCOMPtr<nsILoadInfo> loadInfo =
-      new LoadInfo(aLoadingPrincipal, nullptr, nullptr, 0, 0);
-
-  Maybe<LoadInfoArgs> loadInfoArgs;
-  MOZ_ALWAYS_SUCCEEDS(LoadInfoToLoadInfoArgs(loadInfo, &loadInfoArgs));
-
-  SendAsyncOpen(aHost, aPort, loadInfoArgs, aAlpn);
+  SendAsyncOpen(aHost, aPort, aArgs, aAlpn);
 }
 
 }  // namespace net
 }  // namespace mozilla
--- a/media/mtransport/ipc/WebrtcProxyChannelChild.h
+++ b/media/mtransport/ipc/WebrtcProxyChannelChild.h
@@ -3,23 +3,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_net_WebrtcProxyChannelChild_h
 #define mozilla_net_WebrtcProxyChannelChild_h
 
 #include "mozilla/net/PWebrtcProxyChannelChild.h"
+#include "mozilla/dom/ipc/IdType.h"
 
 namespace mozilla {
 
-namespace dom {
-class PBrowserOrId;
-}  // namespace dom
-
 namespace net {
 
 class WebrtcProxyChannelCallback;
 
 class WebrtcProxyChannelChild : public PWebrtcProxyChannelChild {
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebrtcProxyChannelChild)
 
@@ -27,18 +24,18 @@ class WebrtcProxyChannelChild : public P
 
   mozilla::ipc::IPCResult RecvOnConnected() override;
 
   mozilla::ipc::IPCResult RecvOnRead(nsTArray<uint8_t>&& aReadData) override;
 
   explicit WebrtcProxyChannelChild(WebrtcProxyChannelCallback* aProxyCallbacks);
 
   void AsyncOpen(const nsCString& aHost, const int& aPort,
-                 const dom::PBrowserOrId& aBrowser,
-                 nsIPrincipal* aLoadingPrincipal, const nsCString& aAlpn);
+                 const net::LoadInfoArgs& aArgs, const nsCString& aAlpn,
+                 const dom::TabId& aTabId);
 
   void AddIPDLReference() { AddRef(); }
   void ReleaseIPDLReference() { Release(); }
 
  protected:
   virtual ~WebrtcProxyChannelChild();
 
   RefPtr<WebrtcProxyChannelCallback> mProxyCallbacks;
--- a/media/mtransport/ipc/WebrtcProxyChannelParent.cpp
+++ b/media/mtransport/ipc/WebrtcProxyChannelParent.cpp
@@ -13,34 +13,23 @@
 
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace net {
 
 mozilla::ipc::IPCResult WebrtcProxyChannelParent::RecvAsyncOpen(
-    const nsCString& aHost, const int& aPort,
-    const Maybe<LoadInfoArgs>& aLoadInfoArgs, const nsCString& aAlpn) {
+    const nsCString& aHost, const int& aPort, const LoadInfoArgs& aLoadInfoArgs,
+    const nsCString& aAlpn) {
   LOG(("WebrtcProxyChannelParent::RecvAsyncOpen %p to %s:%d\n", this,
        aHost.get(), aPort));
 
-  nsresult rv;
-
-  nsCOMPtr<nsILoadInfo> loadInfo;
-
-  rv = LoadInfoArgsToLoadInfo(aLoadInfoArgs, getter_AddRefs(loadInfo));
-  if (NS_FAILED(rv)) {
-    IProtocol* mgr = Manager();
-    OnClose(rv);
-    return IPC_FAIL_NO_REASON(mgr);
-  }
-
   MOZ_ASSERT(mChannel, "webrtc proxy channel should be non-null");
-  mChannel->Open(aHost, aPort, loadInfo, aAlpn);
+  mChannel->Open(aHost, aPort, aLoadInfoArgs, aAlpn);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult WebrtcProxyChannelParent::RecvWrite(
     nsTArray<uint8_t>&& aWriteData) {
   LOG(("WebrtcProxyChannelParent::RecvWrite %p for %zu\n", this,
        aWriteData.Length()));
@@ -67,23 +56,23 @@ mozilla::ipc::IPCResult WebrtcProxyChann
 }
 
 void WebrtcProxyChannelParent::ActorDestroy(ActorDestroyReason aWhy) {
   LOG(("WebrtcProxyChannelParent::ActorDestroy %p for %d\n", this, aWhy));
 
   CleanupChannel();
 }
 
-WebrtcProxyChannelParent::WebrtcProxyChannelParent(
-    nsIAuthPromptProvider* aAuthProvider) {
+WebrtcProxyChannelParent::WebrtcProxyChannelParent(dom::TabId aTabId) {
   MOZ_COUNT_CTOR(WebrtcProxyChannelParent);
 
   LOG(("WebrtcProxyChannelParent::WebrtcProxyChannelParent %p\n", this));
 
-  mChannel = new WebrtcProxyChannel(aAuthProvider, this);
+  mChannel = new WebrtcProxyChannel(this);
+  mChannel->SetTabId(aTabId);
 }
 
 WebrtcProxyChannelParent::~WebrtcProxyChannelParent() {
   MOZ_COUNT_DTOR(WebrtcProxyChannelParent);
 
   LOG(("WebrtcProxyChannelParent::~WebrtcProxyChannelParent %p\n", this));
 
   CleanupChannel();
--- a/media/mtransport/ipc/WebrtcProxyChannelParent.h
+++ b/media/mtransport/ipc/WebrtcProxyChannelParent.h
@@ -18,28 +18,28 @@ namespace net {
 
 class WebrtcProxyChannel;
 
 class WebrtcProxyChannelParent : public PWebrtcProxyChannelParent,
                                  public WebrtcProxyChannelCallback {
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebrtcProxyChannelParent, override)
 
-  mozilla::ipc::IPCResult RecvAsyncOpen(
-      const nsCString& aHost, const int& aPort,
-      const Maybe<LoadInfoArgs>& aLoadInfoArgs,
-      const nsCString& aAlpn) override;
+  mozilla::ipc::IPCResult RecvAsyncOpen(const nsCString& aHost,
+                                        const int& aPort,
+                                        const LoadInfoArgs& aLoadInfoArgs,
+                                        const nsCString& aAlpn) override;
 
   mozilla::ipc::IPCResult RecvWrite(nsTArray<uint8_t>&& aWriteData) override;
 
   mozilla::ipc::IPCResult RecvClose() override;
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
-  explicit WebrtcProxyChannelParent(nsIAuthPromptProvider* aAuthProvider);
+  explicit WebrtcProxyChannelParent(dom::TabId aTabId);
 
   // WebrtcProxyChannelCallback
   void OnClose(nsresult aReason) override;
   void OnConnected() override;
   void OnRead(nsTArray<uint8_t>&& bytes) override;
 
   void AddIPDLReference() { AddRef(); }
   void ReleaseIPDLReference() { Release(); }
--- a/media/mtransport/nr_socket_proxy_config.cpp
+++ b/media/mtransport/nr_socket_proxy_config.cpp
@@ -1,36 +1,41 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
 /* 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 "nr_socket_proxy_config.h"
 
-#include "mozilla/dom/PBrowserOrId.h"
+#include "mozilla/dom/ipc/IdType.h"
+#include "mozilla/net/NeckoChannelParams.h"
 
 namespace mozilla {
 
 class NrSocketProxyConfig::Private {
  public:
-  dom::PBrowserOrId mBrowser;
+  uint64_t mTabId;
   nsCString mAlpn;
+  net::LoadInfoArgs mLoadInfoArgs;
 };
 
-NrSocketProxyConfig::NrSocketProxyConfig(const dom::PBrowserOrId& aBrowser,
-                                         const nsCString& aAlpn)
-    : mPrivate(new Private({aBrowser, aAlpn})) {}
+NrSocketProxyConfig::NrSocketProxyConfig(uint64_t aTabId,
+                                         const nsCString& aAlpn,
+                                         const net::LoadInfoArgs& aArgs)
+    : mPrivate(new Private({aTabId, aAlpn, aArgs})) {}
 
 NrSocketProxyConfig::NrSocketProxyConfig(NrSocketProxyConfig&& aOrig)
     : mPrivate(std::move(aOrig.mPrivate)) {}
 
 NrSocketProxyConfig::~NrSocketProxyConfig() {}
 
-const dom::PBrowserOrId& NrSocketProxyConfig::GetBrowser() const {
-  return mPrivate->mBrowser;
-}
+uint64_t NrSocketProxyConfig::GetTabId() const { return mPrivate->mTabId; }
 
 const nsCString& NrSocketProxyConfig::GetAlpn() const {
   return mPrivate->mAlpn;
 }
 
+const net::LoadInfoArgs& NrSocketProxyConfig::GetLoadInfoArgs() const {
+  return mPrivate->mLoadInfoArgs;
+}
+
 }  // namespace mozilla
--- a/media/mtransport/nr_socket_proxy_config.h
+++ b/media/mtransport/nr_socket_proxy_config.h
@@ -8,36 +8,37 @@
 #define nr_socket_proxy_config__
 
 #include <memory>
 #include "nsString.h"
 
 class nsIPrincipal;
 
 namespace mozilla {
-namespace dom {
-class PBrowserOrId;
+namespace net {
+class LoadInfoArgs;
 }
 
 class NrSocketProxyConfig {
  public:
-  NrSocketProxyConfig(const dom::PBrowserOrId& aBrowser,
-                      const nsCString& aAlpn);
+  NrSocketProxyConfig(uint64_t aTabId, const nsCString& aAlpn,
+                      const net::LoadInfoArgs& aArgs);
   // We need to actually write the default impl ourselves, because the compiler
   // needs to know how to destroy mPrivate in case an exception is thrown, even
   // though we disable exceptions in our build.
   NrSocketProxyConfig(NrSocketProxyConfig&& aOrig);
 
   ~NrSocketProxyConfig();
 
-  const dom::PBrowserOrId& GetBrowser() const;
+  uint64_t GetTabId() const;
   const nsCString& GetAlpn() const;
+  const net::LoadInfoArgs& GetLoadInfoArgs() const;
 
  private:
-  // PBrowserOrId includes stuff that conflicts with nICEr includes.
+  // LoadInfoArgs includes stuff that conflicts with nICEr includes.
   // Make it possible to include this header file without tripping over this
   // problem.
   class Private;
   std::unique_ptr<Private> mPrivate;
 };
 
 }  // namespace mozilla
 
--- a/media/mtransport/nr_socket_prsock.cpp
+++ b/media/mtransport/nr_socket_prsock.cpp
@@ -2093,18 +2093,24 @@ int NrSocketBase::CreateSocket(
     const std::shared_ptr<NrSocketProxyConfig> &config) {
   int r, _status;
 
   if (IsForbiddenAddress(addr)) {
     ABORT(R_REJECTED);
   }
 
   // create IPC bridge for content process
-  if (XRE_IsParentProcess() || XRE_IsSocketProcess()) {
+  if (XRE_IsParentProcess()) {
     *sock = new NrSocket();
+  } else if (XRE_IsSocketProcess()) {
+    if (addr->protocol == IPPROTO_TCP && config) {
+      *sock = new NrSocketProxy(config);
+    } else {
+      *sock = new NrSocket();
+    }
   } else {
     switch (addr->protocol) {
       case IPPROTO_UDP:
         *sock = new NrUdpSocketIpc();
         break;
       case IPPROTO_TCP:
 #if defined(MOZILLA_INTERNAL_API)
         if (!config) {
--- a/media/mtransport/test/ice_unittest.cpp
+++ b/media/mtransport/test/ice_unittest.cpp
@@ -2964,17 +2964,17 @@ void AddNonPairableCandidates(
         break;
       case 4:
         candidates.push_back(new SchedulableTrickleCandidate(
             peer, stream,
             "candidate:0 1 UDP 2113601793 100.64.1.1 12345 typ host", "",
             test_utils_));
         break;
       default:
-        UNIMPLEMENTED;
+        NR_UNIMPLEMENTED;
     }
   }
 
   for (auto i = candidates.rbegin(); i != candidates.rend(); ++i) {
     std::cerr << "Scheduling candidate: " << (*i)->Candidate().c_str()
               << std::endl;
     (*i)->Schedule(0);
   }
--- a/media/mtransport/test/proxy_tunnel_socket_unittest.cpp
+++ b/media/mtransport/test/proxy_tunnel_socket_unittest.cpp
@@ -4,48 +4,45 @@
  * 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/. */
 
 // Original authors: ekr@rtfm.com; ryan@tokbox.com
 
 #include <vector>
 #include <numeric>
 
-#include "mozilla/dom/PBrowserOrId.h"
-
+#include "mozilla/net/NeckoChannelParams.h"
 #include "nr_socket_proxy.h"
 #include "nr_socket_proxy_config.h"
 #include "WebrtcProxyChannelWrapper.h"
 
 #define GTEST_HAS_RTTI 0
 #include "gtest/gtest.h"
 #include "gtest_utils.h"
 
 using namespace mozilla;
 
 // update TestReadMultipleSizes if you change this
 const std::string kHelloMessage = "HELLO IS IT ME YOU'RE LOOKING FOR?";
 
-typedef mozilla::dom::PBrowserOrId PBrowserOrId;
-
 class NrSocketProxyTest : public MtransportTest {
  public:
   NrSocketProxyTest()
       : mSProxy(nullptr),
         nr_socket_(nullptr),
         mEmptyArray(0),
         mReadChunkSize(0),
         mReadChunkSizeIncrement(1),
         mReadAllowance(-1),
         mConnected(false) {}
 
   void SetUp() override {
     nsCString alpn = NS_LITERAL_CSTRING("webrtc");
     std::shared_ptr<NrSocketProxyConfig> config;
-    config.reset(new NrSocketProxyConfig(PBrowserOrId(), alpn));
+    config.reset(new NrSocketProxyConfig(0, alpn, net::LoadInfoArgs()));
     // config is never used but must be non-null
     mSProxy = new NrSocketProxy(config);
     int r = nr_socket_create_int((void *)mSProxy.get(), mSProxy->vtbl(),
                                  &nr_socket_);
     ASSERT_EQ(0, r);
 
     // fake calling AsyncOpen() due to IPC calls. must be non-null
     mSProxy->AssignChannel_DoNotUse(new WebrtcProxyChannelWrapper(nullptr));
--- a/media/mtransport/test/webrtcproxychannel_unittest.cpp
+++ b/media/mtransport/test/webrtcproxychannel_unittest.cpp
@@ -449,17 +449,17 @@ const std::string WebrtcProxyChannelTest
   std::lock_guard<std::mutex> guard(mDataMutex);
   return std::string((char *)mData.Elements(), mData.Length());
 }
 
 // Fake as in not the real WebrtcProxyChannel but real enough
 class FakeWebrtcProxyChannel : public WebrtcProxyChannel {
  public:
   explicit FakeWebrtcProxyChannel(WebrtcProxyChannelCallback *aCallback)
-      : WebrtcProxyChannel(nullptr, aCallback) {}
+      : WebrtcProxyChannel(aCallback) {}
 
  protected:
   virtual ~FakeWebrtcProxyChannel() = default;
 
   void InvokeOnClose(nsresult aReason) override;
   void InvokeOnConnected() override;
   void InvokeOnRead(nsTArray<uint8_t> &&aReadData) override;
 };
--- a/media/mtransport/third_party/nICEr/src/net/transport_addr.c
+++ b/media/mtransport/third_party/nICEr/src/net/transport_addr.c
@@ -392,27 +392,27 @@ int nr_transport_addr_is_loopback(nr_tra
     switch(addr->ip_version){
       case NR_IPV4:
         switch(addr->u.addr4.sin_family){
           case AF_INET:
             if (((ntohl(addr->u.addr4.sin_addr.s_addr)>>24)&0xff)==0x7f)
               return 1;
             break;
           default:
-            UNIMPLEMENTED;
+            NR_UNIMPLEMENTED;
             break;
         }
         break;
 
       case NR_IPV6:
         if(!memcmp(addr->u.addr6.sin6_addr.s6_addr,in6addr_loopback.s6_addr,sizeof(struct in6_addr)))
           return(1);
         break;
       default:
-        UNIMPLEMENTED;
+        NR_UNIMPLEMENTED;
     }
 
     return(0);
   }
 
 int nr_transport_addr_is_link_local(nr_transport_addr *addr)
   {
     switch(addr->ip_version){
@@ -424,17 +424,17 @@ int nr_transport_addr_is_link_local(nr_t
       case NR_IPV6:
         {
           UINT4* addrTop = (UINT4*)(addr->u.addr6.sin6_addr.s6_addr);
           if ((*addrTop & htonl(0xFFC00000)) == htonl(0xFE800000))
             return(2);
         }
         break;
       default:
-        UNIMPLEMENTED;
+        NR_UNIMPLEMENTED;
     }
 
     return(0);
   }
 
 int nr_transport_addr_is_mac_based(nr_transport_addr *addr)
   {
     switch(addr->ip_version){
@@ -449,17 +449,17 @@ int nr_transport_addr_is_mac_based(nr_tr
           UINT4* macExt = (UINT4*)(addr->u.addr6.sin6_addr.s6_addr + 12);
           if ((*macCom & htonl(0x020000FF)) == htonl(0x020000FF) &&
               (*macExt & htonl(0xFF000000)) == htonl(0xFE000000)) {
             return(1);
           }
         }
         break;
       default:
-        UNIMPLEMENTED;
+        NR_UNIMPLEMENTED;
     }
     return(0);
   }
 
 int nr_transport_addr_is_teredo(nr_transport_addr *addr)
   {
     switch(addr->ip_version){
       case NR_IPV4:
@@ -467,17 +467,17 @@ int nr_transport_addr_is_teredo(nr_trans
       case NR_IPV6:
         {
           UINT4* addrTop = (UINT4*)(addr->u.addr6.sin6_addr.s6_addr);
           if ((*addrTop & htonl(0xFFFFFFFF)) == htonl(0x20010000))
             return(1);
         }
         break;
       default:
-        UNIMPLEMENTED;
+        NR_UNIMPLEMENTED;
     }
 
     return(0);
   }
 
 int nr_transport_addr_check_compatibility(nr_transport_addr *addr1, nr_transport_addr *addr2)
   {
     // first make sure we're comparing the same ip versions and protocols
@@ -504,17 +504,17 @@ int nr_transport_addr_is_wildcard(nr_tra
         break;
       case NR_IPV6:
         if(!memcmp(addr->u.addr6.sin6_addr.s6_addr,in6addr_any.s6_addr,sizeof(struct in6_addr)))
           return(1);
         if(addr->u.addr6.sin6_port==0)
           return(1);
         break;
       default:
-        UNIMPLEMENTED;
+        NR_UNIMPLEMENTED;
     }
 
     return(0);
   }
 
 nr_transport_addr_mask nr_private_ipv4_addrs[] = {
   /* RFC1918: 10/8 */
   {0x0A000000, 0xFF000000},
@@ -536,17 +536,17 @@ int nr_transport_addr_get_private_addr_r
             if ((ip & nr_private_ipv4_addrs[i].mask) == nr_private_ipv4_addrs[i].addr)
               return i + 1;
           }
         }
         break;
       case NR_IPV6:
         return(0);
       default:
-        UNIMPLEMENTED;
+        NR_UNIMPLEMENTED;
     }
 
     return(0);
   }
 
 int nr_transport_addr_is_reliable_transport(nr_transport_addr *addr)
   {
     return addr->protocol == IPPROTO_TCP;
--- a/media/mtransport/third_party/nICEr/src/net/transport_addr_reg.c
+++ b/media/mtransport/third_party/nICEr/src/net/transport_addr_reg.c
@@ -173,17 +173,17 @@ nr_reg_set_transport_addr(NR_registry pr
         if ((r=NR_reg_set2_string(prefix, "protocol", "tcp")))
           ABORT(r);
         break;
       case IPPROTO_UDP:
         if ((r=NR_reg_set2_string(prefix, "protocol", "udp")))
           ABORT(r);
         break;
       default:
-        UNIMPLEMENTED;
+        NR_UNIMPLEMENTED;
         break;
     }
 
     if (strlen(addr->ifname) > 0) {
       if ((r=NR_reg_set2_string(prefix, "ifname", addr->ifname)))
         ABORT(r);
     }
 
--- a/media/mtransport/third_party/nrappkit/src/util/libekr/r_macros.h