Merge inbound to mozilla-central. a=merge
authorarthur.iakab <aiakab@mozilla.com>
Sun, 11 Mar 2018 23:46:53 +0200
changeset 462570 a6f5fb18e6bcc9bffe4a0209a22d8a25510936be
parent 462565 6d0d32f6570d875ae9191c07be0355e9083114e8 (current diff)
parent 462569 240114d8acd30222fe7d18b88b1ba25b227d3c9b (diff)
child 462574 74516fae34bdeba845d62966af43115de3f6e81d
child 462584 4421d38c3954f6e60bd8c836748d4e79d82ad0db
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone60.0a1
first release with
nightly linux32
a6f5fb18e6bc / 60.0a1 / 20180311220116 / files
nightly linux64
a6f5fb18e6bc / 60.0a1 / 20180311220116 / files
nightly mac
a6f5fb18e6bc / 60.0a1 / 20180311220116 / files
nightly win32
a6f5fb18e6bc / 60.0a1 / 20180311220116 / files
nightly win64
a6f5fb18e6bc / 60.0a1 / 20180311220116 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
dom/webauthn/tests/cbor/cbor.js
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -1362,16 +1362,20 @@ toolbarpaletteitem[place="palette"][hidd
 .popup-notification-invalid-input {
   box-shadow: 0 0 1.5px 1px red;
 }
 
 .popup-notification-invalid-input[focused] {
   box-shadow: 0 0 2px 2px rgba(255,0,0,0.4);
 }
 
+.popup-notification-description[popupid=webauthn-prompt-register-direct] {
+  white-space: pre-line;
+}
+
 .dragfeedback-tab {
   -moz-appearance: none;
   opacity: 0.65;
   -moz-window-shadow: none;
 }
 
 /* Page action panel */
 #pageAction-panel-sendToDevice-subview-body:not([state="notready"]) > #pageAction-panel-sendToDevice-notReady,
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1462,16 +1462,17 @@ var gBrowserInit = {
     Services.obs.addObserver(gXPInstallObserver, "addon-install-failed");
     Services.obs.addObserver(gXPInstallObserver, "addon-install-confirmation");
     Services.obs.addObserver(gXPInstallObserver, "addon-install-complete");
     window.messageManager.addMessageListener("Browser:URIFixup", gKeywordURIFixup);
 
     BrowserOffline.init();
     IndexedDBPromptHelper.init();
     CanvasPermissionPromptHelper.init();
+    WebAuthnPromptHelper.init();
 
     // Initialize the full zoom setting.
     // We do this before the session restore service gets initialized so we can
     // apply full zoom settings to tabs restored by the session restore service.
     FullZoom.init();
     PanelUI.init();
 
     UpdateUrlbarSearchSplitterState();
@@ -1932,16 +1933,17 @@ var gBrowserInit = {
       }
 
       if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
         MenuTouchModeObserver.uninit();
       }
       BrowserOffline.uninit();
       IndexedDBPromptHelper.uninit();
       CanvasPermissionPromptHelper.uninit();
+      WebAuthnPromptHelper.uninit();
       PanelUI.uninit();
       AutoShowBookmarksToolbar.uninit();
     }
 
     // Final window teardown, do this last.
     gBrowser.destroy();
     window.XULBrowserWindow = null;
     window.QueryInterface(Ci.nsIInterfaceRequestor)
@@ -6764,16 +6766,143 @@ var CanvasPermissionPromptHelper = {
       checkbox,
       name: uri.asciiHost,
     };
     PopupNotifications.show(browser, aTopic, message, this._notificationIcon,
                             mainAction, secondaryActions, options);
   }
 };
 
+var WebAuthnPromptHelper = {
+  _icon: "default-notification-icon",
+  _topic: "webauthn-prompt",
+
+  // The current notification, if any. The U2F manager is a singleton, we will
+  // never allow more than one active request. And thus we'll never have more
+  // than one notification either.
+  _current: null,
+
+  // The current transaction ID. Will be checked when we're notified of the
+  // cancellation of an ongoing WebAuthhn request.
+  _tid: 0,
+
+  init() {
+    Services.obs.addObserver(this, this._topic);
+  },
+
+  uninit() {
+    Services.obs.removeObserver(this, this._topic);
+  },
+
+  observe(aSubject, aTopic, aData) {
+    let mgr = aSubject.QueryInterface(Ci.nsIU2FTokenManager);
+    let data = JSON.parse(aData);
+
+    if (data.action == "register") {
+      this.register(mgr, data);
+    } else if (data.action == "register-direct") {
+      this.registerDirect(mgr, data);
+    } else if (data.action == "sign") {
+      this.sign(mgr, data);
+    } else if (data.action == "cancel") {
+      this.cancel(data);
+    }
+  },
+
+  register(mgr, {origin, tid}) {
+    let mainAction = this.buildCancelAction(mgr, tid);
+    this.show(tid, "register", "webauthn.registerPrompt", origin, mainAction);
+  },
+
+  registerDirect(mgr, {origin, tid}) {
+    let mainAction = this.buildProceedAction(mgr, tid);
+    let secondaryActions = [this.buildCancelAction(mgr, tid)];
+
+    let learnMoreURL =
+      Services.urlFormatter.formatURLPref("app.support.baseURL") +
+      "webauthn-direct-attestation";
+
+    let options = {
+      learnMoreURL,
+      checkbox: {
+        label: gNavigatorBundle.getString("webauthn.anonymize")
+      }
+    };
+
+    this.show(tid, "register-direct", "webauthn.registerDirectPrompt",
+              origin, mainAction, secondaryActions, options);
+  },
+
+  sign(mgr, {origin, tid}) {
+    let mainAction = this.buildCancelAction(mgr, tid);
+    this.show(tid, "sign", "webauthn.signPrompt", origin, mainAction);
+  },
+
+  show(tid, id, stringId, origin, mainAction, secondaryActions = [], options = {}) {
+    this.reset();
+
+    try {
+      origin = Services.io.newURI(origin).asciiHost;
+    } catch (e) {
+      /* Might fail for arbitrary U2F RP IDs. */
+    }
+
+    let brandShortName =
+      document.getElementById("bundle_brand").getString("brandShortName");
+    let message =
+      gNavigatorBundle.getFormattedString(stringId, ["<>", brandShortName], 1);
+
+    options.name = origin;
+    options.hideClose = true;
+    options.eventCallback = event => {
+      if (event == "removed") {
+        this._current = null;
+        this._tid = 0;
+      }
+    };
+
+    this._tid = tid;
+    this._current = PopupNotifications.show(
+      gBrowser.selectedBrowser, `webauthn-prompt-${id}`, message,
+      this._icon, mainAction, secondaryActions, options);
+  },
+
+  cancel({tid}) {
+    if (this._tid == tid) {
+      this.reset();
+    }
+  },
+
+  reset() {
+    if (this._current) {
+      this._current.remove();
+    }
+  },
+
+  buildProceedAction(mgr, tid) {
+    return {
+      label: gNavigatorBundle.getString("webauthn.proceed"),
+      accessKey: gNavigatorBundle.getString("webauthn.proceed.accesskey"),
+      callback(state) {
+        mgr.resumeRegister(tid, !state.checkboxChecked);
+      }
+    };
+  },
+
+  buildCancelAction(mgr, tid) {
+    return {
+      label: gNavigatorBundle.getString("webauthn.cancel"),
+      accessKey: gNavigatorBundle.getString("webauthn.cancel.accesskey"),
+      callback() {
+        mgr.cancel(tid);
+      }
+    };
+  },
+};
+
 function CanCloseWindow() {
   // Avoid redundant calls to canClose from showing multiple
   // PermitUnload dialogs.
   if (Services.startup.shuttingDown || window.skipNextCanClose) {
     return true;
   }
 
   let timedOutProcesses = new WeakSet();
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -211,16 +211,17 @@
 @RESPATH@/components/dom_payments.xpt
 @RESPATH@/components/dom_power.xpt
 @RESPATH@/components/dom_push.xpt
 @RESPATH@/components/dom_quota.xpt
 @RESPATH@/components/dom_range.xpt
 @RESPATH@/components/dom_security.xpt
 @RESPATH@/components/dom_sidebar.xpt
 @RESPATH@/components/dom_storage.xpt
+@RESPATH@/components/dom_webauthn.xpt
 #ifdef MOZ_WEBSPEECH
 @RESPATH@/components/dom_webspeechrecognition.xpt
 #endif
 @RESPATH@/components/dom_workers.xpt
 @RESPATH@/components/dom_xul.xpt
 @RESPATH@/components/dom_presentation.xpt
 @RESPATH@/components/downloads.xpt
 @RESPATH@/components/editor.xpt
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -499,16 +499,38 @@ offlineApps.manageUsageAccessKey=S
 # LOCALIZATION NOTE (canvas.siteprompt): %S is hostname
 canvas.siteprompt=Will you allow %S to use your HTML5 canvas image data? This may be used to uniquely identify your computer.
 canvas.notAllow=Don’t Allow
 canvas.notAllow.accesskey=n
 canvas.allow=Allow Data Access
 canvas.allow.accesskey=A
 canvas.remember=Always remember my decision
 
+# WebAuthn prompts
+# LOCALIZATION NOTE (webauthn.registerPrompt): %S is hostname
+webauthn.registerPrompt=%S wants to register an account with one of your security tokens. You can connect and authorize one now, or cancel.
+# LOCALIZATION NOTE (webauthn.registerDirectPrompt):
+# %1$S is hostname. %2$S is brandShortName.
+# The website is asking for extended information about your
+# hardware authenticator that shouldn't be generally necessary. Permitting
+# this is safe if you only use one account at this website. If you have
+# multiple accounts at this website, and you use the same hardware
+# authenticator, then the website could link those accounts together.
+# And this is true even if you use a different profile / browser (or even Tor
+# Browser). To avoid this, you should use different hardware authenticators
+# for different accounts on this website.
+webauthn.registerDirectPrompt=%1$S is requesting extended information about your authenticator, which may affect your privacy.\n\n%2$S can anonymize this for you, but the website might decline this authenticator. If declined, you can try again.
+# LOCALIZATION NOTE (webauthn.signPrompt): %S is hostname
+webauthn.signPrompt=%S wants to authenticate you using a registered security token. You can connect and authorize one now, or cancel.
+webauthn.cancel=Cancel
+webauthn.cancel.accesskey=c
+webauthn.proceed=Proceed
+webauthn.proceed.accesskey=p
+webauthn.anonymize=Anonymize anyway
+
 # Spoof Accept-Language prompt
 privacy.spoof_english=Changing your language setting to English will make you more difficult to identify and enhance your privacy. Do you want to request English language versions of web pages?
 
 identity.identified.verifier=Verified by: %S
 identity.identified.verified_by_you=You have added a security exception for this site.
 identity.identified.state_and_country=%S, %S
 
 # LOCALIZATION NOTE (identity.notSecure.label):
--- a/dom/ipc/ContentPrefs.cpp
+++ b/dom/ipc/ContentPrefs.cpp
@@ -126,16 +126,17 @@ const char* mozilla::dom::ContentPrefs::
   "javascript.options.ion.unsafe_eager_compilation",
   "javascript.options.jit.full_debug_checks",
   "javascript.options.native_regexp",
   "javascript.options.parallel_parsing",
   "javascript.options.shared_memory",
   "javascript.options.spectre.index_masking",
   "javascript.options.spectre.jit_to_C++_calls",
   "javascript.options.spectre.object_mitigations.barriers",
+  "javascript.options.spectre.object_mitigations.misc",
   "javascript.options.spectre.string_mitigations",
   "javascript.options.spectre.value_masking",
   "javascript.options.streams",
   "javascript.options.strict",
   "javascript.options.strict.debug",
   "javascript.options.throw_on_asmjs_validation_failure",
   "javascript.options.throw_on_debuggee_would_run",
   "javascript.options.wasm",
--- a/dom/svg/SVGContentUtils.cpp
+++ b/dom/svg/SVGContentUtils.cpp
@@ -77,17 +77,17 @@ GetStrokeDashData(SVGContentUtils::AutoS
                   const nsStyleSVG* aStyleSVG,
                   SVGContextPaint* aContextPaint)
 {
   size_t dashArrayLength;
   Float totalLengthOfDashes = 0.0, totalLengthOfGaps = 0.0;
   Float pathScale = 1.0;
 
   if (aContextPaint && aStyleSVG->StrokeDasharrayFromObject()) {
-    const FallibleTArray<gfxFloat>& dashSrc = aContextPaint->GetStrokeDashArray();
+    const FallibleTArray<Float>& dashSrc = aContextPaint->GetStrokeDashArray();
     dashArrayLength = dashSrc.Length();
     if (dashArrayLength <= 0) {
       return eContinuousStroke;
     }
     Float* dashPattern = aStrokeOptions->InitDashPattern(dashArrayLength);
     if (!dashPattern) {
       return eContinuousStroke;
     }
--- a/dom/u2f/U2F.cpp
+++ b/dom/u2f/U2F.cpp
@@ -337,22 +337,24 @@ U2F::Register(const nsAString& aAppId,
 
   // Default values for U2F.
   WebAuthnAuthenticatorSelection authSelection(false /* requireResidentKey */,
                                                false /* requireUserVerification */,
                                                false /* requirePlatformAttachment */);
 
   uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds);
 
-  WebAuthnMakeCredentialInfo info(rpIdHash,
+  WebAuthnMakeCredentialInfo info(mOrigin,
+                                  rpIdHash,
                                   clientDataHash,
                                   adjustedTimeoutMillis,
                                   excludeList,
                                   extensions,
-                                  authSelection);
+                                  authSelection,
+                                  false /* RequestDirectAttestation */);
 
   MOZ_ASSERT(mTransaction.isNothing());
   mTransaction = Some(U2FTransaction(clientData, Move(AsVariant(callback))));
   mChild->SendRequestRegister(mTransaction.ref().mId, info);
 }
 
 void
 U2F::FinishMakeCredential(const uint64_t& aTransactionId,
@@ -478,17 +480,18 @@ U2F::Sign(const nsAString& aAppId,
 
   ListenForVisibilityEvents();
 
   // Always blank for U2F
   nsTArray<WebAuthnExtension> extensions;
 
   uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds);
 
-  WebAuthnGetAssertionInfo info(rpIdHash,
+  WebAuthnGetAssertionInfo info(mOrigin,
+                                rpIdHash,
                                 clientDataHash,
                                 adjustedTimeoutMillis,
                                 permittedList,
                                 false, /* requireUserVerification */
                                 extensions);
 
   MOZ_ASSERT(mTransaction.isNothing());
   mTransaction = Some(U2FTransaction(clientData, Move(AsVariant(callback))));
--- a/dom/webauthn/PWebAuthnTransaction.ipdl
+++ b/dom/webauthn/PWebAuthnTransaction.ipdl
@@ -42,29 +42,33 @@ struct WebAuthnExtensionResultAppId {
   bool AppId;
 };
 
 union WebAuthnExtensionResult {
   WebAuthnExtensionResultAppId;
 };
 
 struct WebAuthnMakeCredentialInfo {
+  nsString Origin;
   uint8_t[] RpIdHash;
   uint8_t[] ClientDataHash;
   uint32_t TimeoutMS;
   WebAuthnScopedCredential[] ExcludeList;
   WebAuthnExtension[] Extensions;
   WebAuthnAuthenticatorSelection AuthenticatorSelection;
+  bool RequestDirectAttestation;
 };
 
 struct WebAuthnMakeCredentialResult {
   uint8_t[] RegBuffer;
+  bool DirectAttestationPermitted;
 };
 
 struct WebAuthnGetAssertionInfo {
+  nsString Origin;
   uint8_t[] RpIdHash;
   uint8_t[] ClientDataHash;
   uint32_t TimeoutMS;
   WebAuthnScopedCredential[] AllowList;
   bool RequireUserVerification;
   WebAuthnExtension[] Extensions;
 };
 
--- a/dom/webauthn/U2FHIDTokenManager.cpp
+++ b/dom/webauthn/U2FHIDTokenManager.cpp
@@ -219,17 +219,19 @@ U2FHIDTokenManager::HandleRegisterResult
   MOZ_ASSERT(!mRegisterPromise.IsEmpty());
 
   nsTArray<uint8_t> registration;
   if (!aResult->CopyRegistration(registration)) {
     mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
     return;
   }
 
-  WebAuthnMakeCredentialResult result(registration);
+  // Will be set by the U2FTokenManager.
+  bool directAttestationPermitted = false;
+  WebAuthnMakeCredentialResult result(registration, directAttestationPermitted);
   mRegisterPromise.Resolve(Move(result), __func__);
 }
 
 void
 U2FHIDTokenManager::HandleSignResult(UniquePtr<U2FResult>&& aResult)
 {
   mozilla::ipc::AssertIsOnBackgroundThread();
 
--- a/dom/webauthn/U2FSoftTokenManager.cpp
+++ b/dom/webauthn/U2FSoftTokenManager.cpp
@@ -683,17 +683,20 @@ U2FSoftTokenManager::Register(const WebA
   }
   registrationBuf.AppendElement(0x05, mozilla::fallible);
   registrationBuf.AppendSECItem(pubKey->u.ec.publicValue);
   registrationBuf.AppendElement(keyHandleItem->len, mozilla::fallible);
   registrationBuf.AppendSECItem(keyHandleItem.get());
   registrationBuf.AppendSECItem(attestCert.get()->derCert);
   registrationBuf.AppendSECItem(signatureItem);
 
-  WebAuthnMakeCredentialResult result((nsTArray<uint8_t>(registrationBuf)));
+  // Will be set by the U2FTokenManager.
+  bool directAttestationPermitted = false;
+  WebAuthnMakeCredentialResult result((nsTArray<uint8_t>(registrationBuf)),
+                                      directAttestationPermitted);
   return U2FRegisterPromise::CreateAndResolve(Move(result), __func__);
 }
 
 bool
 U2FSoftTokenManager::FindRegisteredKeyHandle(const nsTArray<nsTArray<uint8_t>>& aAppIds,
                                              const nsTArray<WebAuthnScopedCredential>& aCredentials,
                                              /*out*/ nsTArray<uint8_t>& aKeyHandle,
                                              /*out*/ nsTArray<uint8_t>& aAppId)
--- a/dom/webauthn/U2FTokenManager.cpp
+++ b/dom/webauthn/U2FTokenManager.cpp
@@ -6,45 +6,59 @@
 
 #include "mozilla/dom/U2FTokenManager.h"
 #include "mozilla/dom/U2FTokenTransport.h"
 #include "mozilla/dom/U2FHIDTokenManager.h"
 #include "mozilla/dom/U2FSoftTokenManager.h"
 #include "mozilla/dom/PWebAuthnTransactionParent.h"
 #include "mozilla/MozPromise.h"
 #include "mozilla/dom/WebAuthnUtil.h"
+#include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Unused.h"
 #include "hasht.h"
 #include "nsICryptoHash.h"
+#include "nsTextFormatter.h"
 #include "pkix/Input.h"
 #include "pkixutil.h"
 
 // Not named "security.webauth.u2f_softtoken_counter" because setting that
 // name causes the window.u2f object to disappear until preferences get
 // reloaded, as its pref is a substring!
 #define PREF_U2F_NSSTOKEN_COUNTER "security.webauth.softtoken_counter"
 #define PREF_WEBAUTHN_SOFTTOKEN_ENABLED "security.webauth.webauthn_enable_softtoken"
 #define PREF_WEBAUTHN_USBTOKEN_ENABLED "security.webauth.webauthn_enable_usbtoken"
+#define PREF_WEBAUTHN_ALLOW_DIRECT_ATTESTATION "security.webauth.webauthn_testing_allow_direct_attestation"
 
 namespace mozilla {
 namespace dom {
 
 /***********************************************************************
  * Statics
  **********************************************************************/
 
 class U2FPrefManager;
 
 namespace {
 static mozilla::LazyLogModule gU2FTokenManagerLog("u2fkeymanager");
 StaticRefPtr<U2FTokenManager> gU2FTokenManager;
 StaticRefPtr<U2FPrefManager> gPrefManager;
+static nsIThread* gBackgroundThread;
 }
 
+// Data for WebAuthn UI prompt notifications.
+static const char16_t kRegisterPromptNotifcation[] =
+  u"{\"action\":\"register\",\"tid\":%llu,\"origin\":\"%s\"}";
+static const char16_t kRegisterDirectPromptNotifcation[] =
+  u"{\"action\":\"register-direct\",\"tid\":%llu,\"origin\":\"%s\"}";
+static const char16_t kSignPromptNotifcation[] =
+  u"{\"action\":\"sign\",\"tid\":%llu,\"origin\":\"%s\"}";
+static const char16_t kCancelPromptNotifcation[] =
+  u"{\"action\":\"cancel\",\"tid\":%llu}";
+
 class U2FPrefManager final : public nsIObserver
 {
 private:
   U2FPrefManager() :
     mPrefMutex("U2FPrefManager Mutex")
   {
     UpdateValues();
   }
@@ -56,16 +70,17 @@ public:
   static U2FPrefManager* GetOrCreate()
   {
     MOZ_ASSERT(NS_IsMainThread());
     if (!gPrefManager) {
       gPrefManager = new U2FPrefManager();
       Preferences::AddStrongObserver(gPrefManager, PREF_WEBAUTHN_SOFTTOKEN_ENABLED);
       Preferences::AddStrongObserver(gPrefManager, PREF_U2F_NSSTOKEN_COUNTER);
       Preferences::AddStrongObserver(gPrefManager, PREF_WEBAUTHN_USBTOKEN_ENABLED);
+      Preferences::AddStrongObserver(gPrefManager, PREF_WEBAUTHN_ALLOW_DIRECT_ATTESTATION);
       ClearOnShutdown(&gPrefManager, ShutdownPhase::ShutdownThreads);
     }
     return gPrefManager;
   }
 
   static U2FPrefManager* Get()
   {
     return gPrefManager;
@@ -84,61 +99,66 @@ public:
   }
 
   bool GetUsbTokenEnabled()
   {
     MutexAutoLock lock(mPrefMutex);
     return mUsbTokenEnabled;
   }
 
+  bool GetAllowDirectAttestationForTesting()
+  {
+    MutexAutoLock lock(mPrefMutex);
+    return mAllowDirectAttestation;
+  }
+
   NS_IMETHODIMP
   Observe(nsISupports* aSubject,
           const char* aTopic,
           const char16_t* aData) override
   {
     UpdateValues();
     return NS_OK;
   }
 private:
   void UpdateValues() {
     MOZ_ASSERT(NS_IsMainThread());
     MutexAutoLock lock(mPrefMutex);
     mSoftTokenEnabled = Preferences::GetBool(PREF_WEBAUTHN_SOFTTOKEN_ENABLED);
     mSoftTokenCounter = Preferences::GetUint(PREF_U2F_NSSTOKEN_COUNTER);
     mUsbTokenEnabled = Preferences::GetBool(PREF_WEBAUTHN_USBTOKEN_ENABLED);
+    mAllowDirectAttestation = Preferences::GetBool(PREF_WEBAUTHN_ALLOW_DIRECT_ATTESTATION);
   }
 
   Mutex mPrefMutex;
   bool mSoftTokenEnabled;
   int mSoftTokenCounter;
   bool mUsbTokenEnabled;
+  bool mAllowDirectAttestation;
 };
 
 NS_IMPL_ISUPPORTS(U2FPrefManager, nsIObserver);
 
 /***********************************************************************
  * U2FManager Implementation
  **********************************************************************/
 
+NS_IMPL_ISUPPORTS(U2FTokenManager, nsIU2FTokenManager);
+
 U2FTokenManager::U2FTokenManager()
   : mTransactionParent(nullptr)
   , mLastTransactionId(0)
 {
   MOZ_ASSERT(XRE_IsParentProcess());
   // Create on the main thread to make sure ClearOnShutdown() works.
   MOZ_ASSERT(NS_IsMainThread());
   // Create the preference manager while we're initializing.
   U2FPrefManager::GetOrCreate();
 }
 
-U2FTokenManager::~U2FTokenManager()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-}
-
 //static
 void
 U2FTokenManager::Initialize()
 {
   if (!XRE_IsParentProcess()) {
     return;
   }
   MOZ_ASSERT(NS_IsMainThread());
@@ -173,38 +193,85 @@ U2FTokenManager::MaybeClearTransaction(P
   if (mTransactionParent == aParent) {
     ClearTransaction();
   }
 }
 
 void
 U2FTokenManager::ClearTransaction()
 {
+  if (mLastTransactionId > 0) {
+    // Remove any prompts we might be showing for the current transaction.
+    SendPromptNotification(kCancelPromptNotifcation, mLastTransactionId);
+  }
+
   mTransactionParent = nullptr;
+
   // Drop managers at the end of all transactions
   if (mTokenManagerImpl) {
     mTokenManagerImpl->Drop();
     mTokenManagerImpl = nullptr;
   }
+
   // Forget promises, if necessary.
   mRegisterPromise.DisconnectIfExists();
   mSignPromise.DisconnectIfExists();
+
   // Clear transaction id.
   mLastTransactionId = 0;
+
+  // Forget any pending registration.
+  mPendingRegisterInfo.reset();
+}
+
+template<typename ...T> void
+U2FTokenManager::SendPromptNotification(const char16_t* aFormat, T... aArgs)
+{
+  mozilla::ipc::AssertIsOnBackgroundThread();
+
+  nsAutoString json;
+  nsTextFormatter::ssprintf(json, aFormat, aArgs...);
+
+  nsCOMPtr<nsIRunnable> r(NewRunnableMethod<nsString>(
+      "U2FTokenManager::RunSendPromptNotification", this,
+      &U2FTokenManager::RunSendPromptNotification, json));
+
+  MOZ_ALWAYS_SUCCEEDS(
+    GetMainThreadEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
+}
+
+void
+U2FTokenManager::RunSendPromptNotification(nsString aJSON)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+  if (NS_WARN_IF(!os)) {
+    return;
+  }
+
+  nsCOMPtr<nsIU2FTokenManager> self = do_QueryInterface(this);
+  MOZ_ALWAYS_SUCCEEDS(os->NotifyObservers(self, "webauthn-prompt", aJSON.get()));
 }
 
 RefPtr<U2FTokenTransport>
 U2FTokenManager::GetTokenManagerImpl()
 {
   MOZ_ASSERT(U2FPrefManager::Get());
+  mozilla::ipc::AssertIsOnBackgroundThread();
 
   if (mTokenManagerImpl) {
     return mTokenManagerImpl;
   }
 
+  if (!gBackgroundThread) {
+    gBackgroundThread = NS_GetCurrentThread();
+    MOZ_ASSERT(gBackgroundThread, "This should never be null!");
+  }
+
   auto pm = U2FPrefManager::Get();
 
   // Prefer the HW token, even if the softtoken is enabled too.
   // We currently don't support soft and USB tokens enabled at the
   // same time as the softtoken would always win the race to register.
   // We could support it for signing though...
   if (pm->GetUsbTokenEnabled()) {
     return new U2FHIDTokenManager();
@@ -241,23 +308,60 @@ U2FTokenManager::Register(PWebAuthnTrans
   // UnknownError and terminate the operation.
 
   if ((aTransactionInfo.RpIdHash().Length() != SHA256_LENGTH) ||
       (aTransactionInfo.ClientDataHash().Length() != SHA256_LENGTH)) {
     AbortTransaction(aTransactionId, NS_ERROR_DOM_UNKNOWN_ERR);
     return;
   }
 
-  uint64_t tid = mLastTransactionId = aTransactionId;
+  mLastTransactionId = aTransactionId;
+
+  // If the RP request direct attestation, ask the user for permission and
+  // store the transaction info until the user proceeds or cancels.
+  // Might be overriden by a pref for testing purposes.
+  if (aTransactionInfo.RequestDirectAttestation() &&
+      !U2FPrefManager::Get()->GetAllowDirectAttestationForTesting()) {
+    NS_ConvertUTF16toUTF8 origin(aTransactionInfo.Origin());
+    SendPromptNotification(kRegisterDirectPromptNotifcation,
+                           aTransactionId,
+                           origin.get());
+
+    MOZ_ASSERT(mPendingRegisterInfo.isNothing());
+    mPendingRegisterInfo = Some(aTransactionInfo);
+  } else {
+    DoRegister(aTransactionInfo);
+  }
+}
+
+void
+U2FTokenManager::DoRegister(const WebAuthnMakeCredentialInfo& aInfo)
+{
+  mozilla::ipc::AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mLastTransactionId > 0);
+
+  // Show a prompt that lets the user cancel the ongoing transaction.
+  NS_ConvertUTF16toUTF8 origin(aInfo.Origin());
+  SendPromptNotification(kRegisterPromptNotifcation,
+                         mLastTransactionId,
+                         origin.get());
+
+  uint64_t tid = mLastTransactionId;
   mozilla::TimeStamp startTime = mozilla::TimeStamp::Now();
+  bool requestDirectAttestation = aInfo.RequestDirectAttestation();
+
   mTokenManagerImpl
-    ->Register(aTransactionInfo)
+    ->Register(aInfo)
     ->Then(GetCurrentThreadSerialEventTarget(), __func__,
-          [tid, startTime](WebAuthnMakeCredentialResult&& aResult) {
+          [tid, startTime, requestDirectAttestation](WebAuthnMakeCredentialResult&& aResult) {
             U2FTokenManager* mgr = U2FTokenManager::Get();
+            // The token manager implementations set DirectAttestationPermitted
+            // to false by default. Override this here with information from
+            // the JS prompt.
+            aResult.DirectAttestationPermitted() = requestDirectAttestation;
             mgr->MaybeConfirmRegister(tid, aResult);
             Telemetry::ScalarAdd(
               Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
               NS_LITERAL_STRING("U2FRegisterFinish"), 1);
             Telemetry::AccumulateTimeDelta(
               Telemetry::WEBAUTHN_CREATE_CREDENTIAL_MS,
               startTime);
           },
@@ -309,18 +413,25 @@ U2FTokenManager::Sign(PWebAuthnTransacti
   }
 
   if ((aTransactionInfo.RpIdHash().Length() != SHA256_LENGTH) ||
       (aTransactionInfo.ClientDataHash().Length() != SHA256_LENGTH)) {
     AbortTransaction(aTransactionId, NS_ERROR_DOM_UNKNOWN_ERR);
     return;
   }
 
+  // Show a prompt that lets the user cancel the ongoing transaction.
+  NS_ConvertUTF16toUTF8 origin(aTransactionInfo.Origin());
+  SendPromptNotification(kSignPromptNotifcation,
+                         aTransactionId,
+                         origin.get());
+
   uint64_t tid = mLastTransactionId = aTransactionId;
   mozilla::TimeStamp startTime = mozilla::TimeStamp::Now();
+
   mTokenManagerImpl
     ->Sign(aTransactionInfo)
     ->Then(GetCurrentThreadSerialEventTarget(), __func__,
       [tid, startTime](WebAuthnGetAssertionResult&& aResult) {
         U2FTokenManager* mgr = U2FTokenManager::Get();
         mgr->MaybeConfirmSign(tid, aResult);
         Telemetry::ScalarAdd(
           Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
@@ -367,10 +478,87 @@ U2FTokenManager::Cancel(PWebAuthnTransac
   if (mTransactionParent != aParent || mLastTransactionId != aTransactionId) {
     return;
   }
 
   mTokenManagerImpl->Cancel();
   ClearTransaction();
 }
 
+// nsIU2FTokenManager
+
+NS_IMETHODIMP
+U2FTokenManager::ResumeRegister(uint64_t aTransactionId,
+                                bool aPermitDirectAttestation)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!gBackgroundThread) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIRunnable> r(NewRunnableMethod<uint64_t, bool>(
+      "U2FTokenManager::RunResumeRegister", this,
+      &U2FTokenManager::RunResumeRegister, aTransactionId,
+      aPermitDirectAttestation));
+
+  return gBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+}
+
+void
+U2FTokenManager::RunResumeRegister(uint64_t aTransactionId,
+                                   bool aPermitDirectAttestation)
+{
+  mozilla::ipc::AssertIsOnBackgroundThread();
+
+  if (NS_WARN_IF(mPendingRegisterInfo.isNothing())) {
+    return;
+  }
+
+  if (mLastTransactionId != aTransactionId) {
+    return;
+  }
+
+  // Forward whether the user opted into direct attestation.
+  mPendingRegisterInfo.ref().RequestDirectAttestation() =
+    aPermitDirectAttestation;
+
+  // Resume registration and cleanup.
+  DoRegister(mPendingRegisterInfo.ref());
+  mPendingRegisterInfo.reset();
+}
+
+NS_IMETHODIMP
+U2FTokenManager::Cancel(uint64_t aTransactionId)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!gBackgroundThread) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIRunnable> r(NewRunnableMethod<uint64_t>(
+      "U2FTokenManager::RunCancel", this,
+      &U2FTokenManager::RunCancel, aTransactionId));
+
+  return gBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+}
+
+void
+U2FTokenManager::RunCancel(uint64_t aTransactionId)
+{
+  mozilla::ipc::AssertIsOnBackgroundThread();
+
+  if (mLastTransactionId != aTransactionId) {
+    return;
+  }
+
+  // Cancel the request.
+  mTokenManagerImpl->Cancel();
+
+  // Reject the promise.
+  AbortTransaction(aTransactionId, NS_ERROR_DOM_ABORT_ERR);
+}
+
 }
 }
--- a/dom/webauthn/U2FTokenManager.h
+++ b/dom/webauthn/U2FTokenManager.h
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_U2FTokenManager_h
 #define mozilla_dom_U2FTokenManager_h
 
+#include "nsIU2FTokenManager.h"
 #include "mozilla/dom/U2FTokenTransport.h"
 #include "mozilla/dom/PWebAuthnTransaction.h"
 
 /*
  * Parent process manager for U2F and WebAuthn API transactions. Handles process
  * transactions from all content processes, make sure only one transaction is
  * live at any time. Manages access to hardware and software based key systems.
  *
@@ -21,52 +22,67 @@
  */
 
 namespace mozilla {
 namespace dom {
 
 class U2FSoftTokenManager;
 class WebAuthnTransactionParent;
 
-class U2FTokenManager final
+class U2FTokenManager final : public nsIU2FTokenManager
 {
 public:
-  NS_INLINE_DECL_REFCOUNTING(U2FTokenManager)
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIU2FTOKENMANAGER
+
   static U2FTokenManager* Get();
   void Register(PWebAuthnTransactionParent* aTransactionParent,
                 const uint64_t& aTransactionId,
                 const WebAuthnMakeCredentialInfo& aTransactionInfo);
   void Sign(PWebAuthnTransactionParent* aTransactionParent,
             const uint64_t& aTransactionId,
             const WebAuthnGetAssertionInfo& aTransactionInfo);
   void Cancel(PWebAuthnTransactionParent* aTransactionParent,
               const uint64_t& aTransactionId);
   void MaybeClearTransaction(PWebAuthnTransactionParent* aParent);
   static void Initialize();
 private:
   U2FTokenManager();
-  ~U2FTokenManager();
+  ~U2FTokenManager() { }
   RefPtr<U2FTokenTransport> GetTokenManagerImpl();
   void AbortTransaction(const uint64_t& aTransactionId, const nsresult& aError);
   void ClearTransaction();
+  // Step two of "Register", kicking off the actual transaction.
+  void DoRegister(const WebAuthnMakeCredentialInfo& aInfo);
   void MaybeConfirmRegister(const uint64_t& aTransactionId,
                             const WebAuthnMakeCredentialResult& aResult);
   void MaybeAbortRegister(const uint64_t& aTransactionId, const nsresult& aError);
   void MaybeConfirmSign(const uint64_t& aTransactionId,
                         const WebAuthnGetAssertionResult& aResult);
   void MaybeAbortSign(const uint64_t& aTransactionId, const nsresult& aError);
+  // The main thread runnable function for "nsIU2FTokenManager.ResumeRegister".
+  void RunResumeRegister(uint64_t aTransactionId, bool aPermitDirectAttestation);
+  // The main thread runnable function for "nsIU2FTokenManager.Cancel".
+  void RunCancel(uint64_t aTransactionId);
+  // Sends a "webauthn-prompt" observer notification with the given data.
+  template<typename ...T>
+  void SendPromptNotification(const char16_t* aFormat, T... aArgs);
+  // The main thread runnable function for "SendPromptNotification".
+  void RunSendPromptNotification(nsString aJSON);
   // Using a raw pointer here, as the lifetime of the IPC object is managed by
   // the PBackground protocol code. This means we cannot be left holding an
   // invalid IPC protocol object after the transaction is finished.
   PWebAuthnTransactionParent* mTransactionParent;
   RefPtr<U2FTokenTransport> mTokenManagerImpl;
   MozPromiseRequestHolder<U2FRegisterPromise> mRegisterPromise;
   MozPromiseRequestHolder<U2FSignPromise> mSignPromise;
   // The last transaction id, non-zero if there's an active transaction. This
   // guards any cancel messages to ensure we don't cancel newer transactions
   // due to a stale message.
   uint64_t mLastTransactionId;
+  // Pending registration info while we wait for user input.
+  Maybe<WebAuthnMakeCredentialInfo> mPendingRegisterInfo;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_U2FTokenManager_h
--- a/dom/webauthn/WebAuthnManager.cpp
+++ b/dom/webauthn/WebAuthnManager.cpp
@@ -403,46 +403,29 @@ WebAuthnManager::MakeCredential(const Pu
   bool requireUserVerification =
     selection.mUserVerification == UserVerificationRequirement::Required;
 
   // Does the RP desire direct attestation? Indirect attestation is not
   // implemented, and thus is equivilent to None.
   bool requestDirectAttestation =
     attestation == AttestationConveyancePreference::Direct;
 
-  // XXX Bug 1430150. Need something that allows direct attestation
-  // for tests until we implement a permission dialog we can click.
-  if (requestDirectAttestation) {
-    nsresult rv;
-    nsCOMPtr<nsIPrefService> prefService = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
-
-    if (NS_SUCCEEDED(rv)) {
-      nsCOMPtr<nsIPrefBranch> branch;
-      rv = prefService->GetBranch("security.webauth.", getter_AddRefs(branch));
-
-      if (NS_SUCCEEDED(rv)) {
-        rv = branch->GetBoolPref("webauthn_testing_allow_direct_attestation",
-                                 &requestDirectAttestation);
-      }
-    }
-
-    requestDirectAttestation &= NS_SUCCEEDED(rv);
-  }
-
   // Create and forward authenticator selection criteria.
   WebAuthnAuthenticatorSelection authSelection(selection.mRequireResidentKey,
                                                requireUserVerification,
                                                requirePlatformAttachment);
 
-  WebAuthnMakeCredentialInfo info(rpIdHash,
+  WebAuthnMakeCredentialInfo info(origin,
+                                  rpIdHash,
                                   clientDataHash,
                                   adjustedTimeout,
                                   excludeList,
                                   extensions,
-                                  authSelection);
+                                  authSelection,
+                                  requestDirectAttestation);
 
   ListenForVisibilityEvents();
 
   AbortSignal* signal = nullptr;
   if (aSignal.WasPassed()) {
     signal = &aSignal.Value();
     Follow(signal);
   }
@@ -450,16 +433,17 @@ WebAuthnManager::MakeCredential(const Pu
   MOZ_ASSERT(mTransaction.isNothing());
   mTransaction = Some(WebAuthnTransaction(promise,
                                           rpIdHash,
                                           clientDataJSON,
                                           requestDirectAttestation,
                                           signal));
 
   mChild->SendRequestRegister(mTransaction.ref().mId, info);
+
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 WebAuthnManager::GetAssertion(const PublicKeyCredentialRequestOptions& aOptions,
                               const Optional<OwningNonNull<AbortSignal>>& aSignal)
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -633,17 +617,18 @@ WebAuthnManager::GetAssertion(const Publ
       promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
       return promise.forget();
     }
 
     // Append the hash and send it to the backend.
     extensions.AppendElement(WebAuthnExtensionAppId(appIdHash));
   }
 
-  WebAuthnGetAssertionInfo info(rpIdHash,
+  WebAuthnGetAssertionInfo info(origin,
+                                rpIdHash,
                                 clientDataHash,
                                 adjustedTimeout,
                                 allowList,
                                 requireUserVerification,
                                 extensions);
 
   ListenForVisibilityEvents();
 
@@ -780,20 +765,20 @@ WebAuthnManager::FinishMakeCredential(co
   mozilla::dom::CryptoBuffer authDataBuf;
   rv = AssembleAuthenticatorData(rpIdHashBuf, FLAG_TUP, counterBuf, attDataBuf,
                                  authDataBuf);
   if (NS_FAILED(rv)) {
     RejectTransaction(rv);
     return;
   }
 
-  // Direct attestation might have been requested by the RP. mDirectAttestation
-  // will be true only if the user consented via the permission UI.
+  // Direct attestation might have been requested by the RP. This will
+  // be true only if the user consented via the permission UI.
   CryptoBuffer attObj;
-  if (mTransaction.ref().mDirectAttestation) {
+  if (aResult.DirectAttestationPermitted()) {
     rv = CBOREncodeFidoU2FAttestationObj(authDataBuf, attestationCertBuf,
                                          signatureBuf, attObj);
   } else {
     rv = CBOREncodeNoneAttestationObj(authDataBuf, attObj);
   }
 
   if (NS_FAILED(rv)) {
     RejectTransaction(rv);
--- a/dom/webauthn/moz.build
+++ b/dom/webauthn/moz.build
@@ -6,16 +6,22 @@
 
 with Files("**"):
     BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
 
 IPDL_SOURCES += [
     'PWebAuthnTransaction.ipdl'
 ]
 
+XPIDL_SOURCES += [
+    'nsIU2FTokenManager.idl'
+]
+
+XPIDL_MODULE = 'dom_webauthn'
+
 EXPORTS.mozilla.dom += [
     'AuthenticatorAssertionResponse.h',
     'AuthenticatorAttestationResponse.h',
     'AuthenticatorResponse.h',
     'PublicKeyCredential.h',
     'U2FHIDTokenManager.h',
     'U2FSoftTokenManager.h',
     'U2FTokenManager.h',
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/nsIU2FTokenManager.idl
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+
+/**
+ * nsIU2FTokenManager
+ *
+ * An interface to the U2FTokenManager singleton.
+ *
+ * This should be used only by the WebAuthn browser UI prompts.
+ */
+
+[scriptable, uuid(745e1eac-e449-4342-bca1-ee0e6ead09fc)]
+interface nsIU2FTokenManager : nsISupports
+{
+    /**
+     * Resumes the current WebAuthn/U2F transaction if that matches the given
+     * transaction ID. This is used only when direct attestation was requested
+     * and we have to wait for user input to proceed.
+     *
+     * @param aTransactionID : The ID of the transaction to resume.
+     * @param aPermitDirectAttestation : Whether direct attestation was
+     *                                   permitted by the user.
+     */
+    void resumeRegister(in uint64_t aTransactionID,
+                        in bool aPermitDirectAttestation);
+
+    /**
+     * Cancels the current WebAuthn/U2F transaction if that matches the given
+     * transaction ID.
+     *
+     * @param aTransactionID : The ID of the transaction to cancel.
+     */
+    void cancel(in uint64_t aTransactionID);
+};
--- a/dom/webauthn/tests/browser/browser.ini
+++ b/dom/webauthn/tests/browser/browser.ini
@@ -1,13 +1,14 @@
 [DEFAULT]
 support-files =
   head.js
   tab_webauthn_result.html
   tab_webauthn_success.html
-  ../cbor/*
   ../pkijs/*
+  ../cbor.js
   ../u2futil.js
 skip-if = !e10s
 
 [browser_abort_visibility.js]
 [browser_fido_appid_extension.js]
+[browser_webauthn_prompts.js]
 [browser_webauthn_telemetry.js]
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/tests/browser/browser_webauthn_prompts.js
@@ -0,0 +1,215 @@
+/* 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 TEST_URL = "https://example.com/";
+
+function promiseNotification(id) {
+  return new Promise(resolve => {
+    PopupNotifications.panel.addEventListener("popupshown", function shown() {
+      let notification = PopupNotifications.getNotification(id);
+      if (notification) {
+        ok(true, `${id} prompt visible`);
+        PopupNotifications.panel.removeEventListener("popupshown", shown);
+        resolve();
+      }
+    });
+  });
+}
+
+function arrivingHereIsBad(aResult) {
+  ok(false, "Bad result! Received a: " + aResult);
+}
+
+function expectAbortError(aResult) {
+  let expected = "AbortError";
+  is(aResult.slice(0, expected.length), expected, `Expecting a ${expected}`);
+}
+
+function verifyAnonymizedCertificate(attestationObject) {
+  return webAuthnDecodeCBORAttestation(attestationObject)
+    .then(({fmt, attStmt}) => {
+      is("none", fmt, "Is a None Attestation");
+      is("object", typeof(attStmt), "attStmt is a map");
+      is(0, Object.keys(attStmt).length, "attStmt is empty");
+    });
+}
+
+function verifyDirectCertificate(attestationObject) {
+  return webAuthnDecodeCBORAttestation(attestationObject)
+    .then(({fmt, attStmt}) => {
+      is("fido-u2f", fmt, "Is a FIDO U2F Attestation");
+      is("object", typeof(attStmt), "attStmt is a map");
+      ok(attStmt.hasOwnProperty("x5c"), "attStmt.x5c exists");
+      ok(attStmt.hasOwnProperty("sig"), "attStmt.sig exists");
+    });
+}
+
+function promiseWebAuthnRegister(tab, attestation = "indirect") {
+  return ContentTask.spawn(tab.linkedBrowser, [attestation],
+    ([attestation]) => {
+      const cose_alg_ECDSA_w_SHA256 = -7;
+
+      let challenge = content.crypto.getRandomValues(new Uint8Array(16));
+
+      let pubKeyCredParams = [{
+        type: "public-key",
+        alg: cose_alg_ECDSA_w_SHA256
+      }];
+
+      let publicKey = {
+        rp: {id: content.document.domain, name: "none", icon: "none"},
+        user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
+        pubKeyCredParams,
+        attestation,
+        challenge
+      };
+
+      return content.navigator.credentials.create({publicKey})
+        .then(cred => cred.response.attestationObject);
+    });
+}
+
+function promiseWebAuthnSign(tab) {
+  return ContentTask.spawn(tab.linkedBrowser, [], () => {
+    let challenge = content.crypto.getRandomValues(new Uint8Array(16));
+    let key_handle = content.crypto.getRandomValues(new Uint8Array(16));
+
+    let credential = {
+      id: key_handle,
+      type: "public-key",
+      transports: ["usb"]
+    };
+
+    let publicKey = {
+      challenge,
+      rpId: content.document.domain,
+      allowCredentials: [credential],
+    };
+
+    return content.navigator.credentials.get({publicKey});
+  });
+}
+
+add_task(async function test_setup_usbtoken() {
+  await SpecialPowers.pushPrefEnv({
+    "set": [
+      ["security.webauth.u2f", false],
+      ["security.webauth.webauthn", true],
+      ["security.webauth.webauthn_enable_softtoken", false],
+      ["security.webauth.webauthn_enable_usbtoken", true]
+    ]
+  });
+});
+
+add_task(async function test_register() {
+  // Open a new tab.
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+  // Request a new credential and wait for the prompt.
+  let active = true;
+  let request = promiseWebAuthnRegister(tab)
+    .then(arrivingHereIsBad)
+    .catch(expectAbortError)
+    .then(() => active = false);
+  await promiseNotification("webauthn-prompt-register");
+
+  // Cancel the request.
+  ok(active, "request should still be active");
+  PopupNotifications.panel.firstChild.button.click();
+  await request;
+
+  // Close tab.
+  await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_sign() {
+  // Open a new tab.
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+  // Request a new assertion and wait for the prompt.
+  let active = true;
+  let request = promiseWebAuthnSign(tab)
+    .then(arrivingHereIsBad)
+    .catch(expectAbortError)
+    .then(() => active = false);
+  await promiseNotification("webauthn-prompt-sign");
+
+  // Cancel the request.
+  ok(active, "request should still be active");
+  PopupNotifications.panel.firstChild.button.click();
+  await request;
+
+  // Close tab.
+  await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_register_direct_cancel() {
+  // Open a new tab.
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+  // Request a new credential with direct attestation and wait for the prompt.
+  let active = true;
+  let promise = promiseWebAuthnRegister(tab, "direct")
+    .then(arrivingHereIsBad).catch(expectAbortError)
+    .then(() => active = false);
+  await promiseNotification("webauthn-prompt-register-direct");
+
+  // Cancel the request.
+  ok(active, "request should still be active");
+  PopupNotifications.panel.firstChild.secondaryButton.click();
+  await promise;
+
+  // Close tab.
+  await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_setup_softtoken() {
+  await SpecialPowers.pushPrefEnv({
+    "set": [
+      ["security.webauth.u2f", false],
+      ["security.webauth.webauthn", true],
+      ["security.webauth.webauthn_enable_softtoken", true],
+      ["security.webauth.webauthn_enable_usbtoken", false]
+    ]
+  })
+});
+
+add_task(async function test_register_direct_proceed() {
+  // Open a new tab.
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+  // Request a new credential with direct attestation and wait for the prompt.
+  let request = promiseWebAuthnRegister(tab, "direct");
+  await promiseNotification("webauthn-prompt-register-direct");
+
+  // Proceed.
+  PopupNotifications.panel.firstChild.button.click();
+
+  // Ensure we got "direct" attestation.
+  await request.then(verifyDirectCertificate);
+
+  // Close tab.
+  await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_register_direct_proceed_anon() {
+  // Open a new tab.
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+  // Request a new credential with direct attestation and wait for the prompt.
+  let request = promiseWebAuthnRegister(tab, "direct");
+  await promiseNotification("webauthn-prompt-register-direct");
+
+  // Check "anonymize anyway" and proceed.
+  PopupNotifications.panel.firstChild.checkbox.checked = true;
+  PopupNotifications.panel.firstChild.button.click();
+
+  // Ensure we got "none" attestation.
+  await request.then(verifyAnonymizedCertificate);
+
+  // Close tab.
+  await BrowserTestUtils.removeTab(tab);
+});
--- a/dom/webauthn/tests/browser/browser_webauthn_telemetry.js
+++ b/dom/webauthn/tests/browser/browser_webauthn_telemetry.js
@@ -52,29 +52,34 @@ async function executeTestPage(aUri) {
   } catch(e) {
     ok(false, "Exception thrown executing test page: " + e);
   } finally {
     // Remove all the extra windows and tabs.
     return BrowserTestUtils.removeTab(gBrowser.selectedTab);
   }
 }
 
+add_task(async function test_setup() {
+  await SpecialPowers.pushPrefEnv({
+    "set": [
+      ["security.webauth.webauthn", true],
+      ["security.webauth.webauthn_enable_softtoken", true],
+      ["security.webauth.webauthn_enable_usbtoken", false],
+      ["security.webauth.webauthn_testing_allow_direct_attestation", true]
+    ]
+  });
+});
+
 add_task(async function test_loopback() {
   // These tests can't run simultaneously as the preference changes will race.
   // So let's run them sequentially here, but in an async function so we can
   // use await.
   const testPage = "https://example.com/browser/dom/webauthn/tests/browser/tab_webauthn_success.html";
   {
     cleanupTelemetry();
-    // Enable the soft token, and execute a simple end-to-end test
-    Services.prefs.setBoolPref("security.webauth.webauthn", true);
-    Services.prefs.setBoolPref("security.webauth.webauthn_enable_softtoken", true);
-    Services.prefs.setBoolPref("security.webauth.webauthn_enable_usbtoken", false);
-    Services.prefs.setBoolPref("security.webauth.webauthn_testing_allow_direct_attestation", true);
-
     await executeTestPage(testPage);
 
     let webauthn_used = getTelemetryForScalar("security.webauthn_used");
     ok(webauthn_used, "Scalar keys are set: " + Object.keys(webauthn_used).join(", "));
     is(webauthn_used["U2FRegisterFinish"], 1, "webauthn_used U2FRegisterFinish scalar should be 1");
     is(webauthn_used["U2FSignFinish"], 1, "webauthn_used U2FSignFinish scalar should be 1");
     is(webauthn_used["U2FSignAbort"], undefined, "webauthn_used U2FSignAbort scalar must be unset");
     is(webauthn_used["U2FRegisterAbort"], undefined, "webauthn_used U2FRegisterAbort scalar must be unset");
--- a/dom/webauthn/tests/browser/head.js
+++ b/dom/webauthn/tests/browser/head.js
@@ -1,56 +1,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/. */
 
 "use strict";
 
-function bytesToBase64(u8a){
-  let CHUNK_SZ = 0x8000;
-  let c = [];
-  for (let i = 0; i < u8a.length; i += CHUNK_SZ) {
-    c.push(String.fromCharCode.apply(null, u8a.subarray(i, i + CHUNK_SZ)));
-  }
-  return window.btoa(c.join(""));
-}
-
-function bytesToBase64UrlSafe(buf) {
-  return bytesToBase64(buf)
-                 .replace(/\+/g, "-")
-                 .replace(/\//g, "_")
-                 .replace(/=/g, "");
-}
-
-function base64ToBytes(b64encoded) {
-  return new Uint8Array(window.atob(b64encoded).split("").map(function(c) {
-    return c.charCodeAt(0);
-  }));
-}
-
-function base64ToBytesUrlSafe(str) {
-  if (!str || str.length % 4 == 1) {
-    throw "Improper b64 string";
-  }
-
-  var b64 = str.replace(/\-/g, "+").replace(/\_/g, "/");
-  while (b64.length % 4 != 0) {
-    b64 += "=";
-  }
-  return base64ToBytes(b64);
-}
-
-function buffer2string(buf) {
-  let str = "";
-  if (!(buf.constructor === Uint8Array)) {
-    buf = new Uint8Array(buf);
-  }
-  buf.map(function(x){ return str += String.fromCharCode(x) });
-  return str;
-}
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/dom/webauthn/tests/browser/cbor.js",
+  this);
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/dom/webauthn/tests/browser/u2futil.js",
+  this);
 
 function memcmp(x, y) {
   let xb = new Uint8Array(x);
   let yb = new Uint8Array(y);
 
   if (x.byteLength != y.byteLength) {
     return false;
   }
--- a/dom/webauthn/tests/browser/tab_webauthn_success.html
+++ b/dom/webauthn/tests/browser/tab_webauthn_success.html
@@ -2,17 +2,17 @@
 <meta charset=utf-8>
 <head>
   <title>Full-run test for MakeCredential/GetAssertion for W3C Web Authentication</title>
   <script type="text/javascript" src="u2futil.js"></script>
   <script type="text/javascript" src="../pkijs/common.js"></script>
   <script type="text/javascript" src="../pkijs/asn1.js"></script>
   <script type="text/javascript" src="../pkijs/x509_schema.js"></script>
   <script type="text/javascript" src="../pkijs/x509_simpl.js"></script>
-  <script type="text/javascript" src="../cbor/cbor.js"></script>
+  <script type="text/javascript" src="cbor.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <h1>Full-run test for MakeCredential/GetAssertion for W3C Web Authentication</h1>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265472">Mozilla Bug 1265472</a>
 
 <script class="testbody" type="text/javascript">
rename from dom/webauthn/tests/cbor/cbor.js
rename to dom/webauthn/tests/cbor.js
--- a/dom/webauthn/tests/mochitest.ini
+++ b/dom/webauthn/tests/mochitest.ini
@@ -1,13 +1,13 @@
 [DEFAULT]
 support-files =
-  cbor/*
+  cbor.js
+  u2futil.js
   pkijs/*
-  u2futil.js
 skip-if = !e10s
 scheme = https
 
 [test_webauthn_abort_signal.html]
 [test_webauthn_attestation_conveyance.html]
 [test_webauthn_authenticator_selection.html]
 [test_webauthn_authenticator_transports.html]
 [test_webauthn_loopback.html]
--- a/dom/webauthn/tests/test_webauthn_attestation_conveyance.html
+++ b/dom/webauthn/tests/test_webauthn_attestation_conveyance.html
@@ -4,17 +4,17 @@
   <title>W3C Web Authentication - Attestation Conveyance</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
   <script type="text/javascript" src="u2futil.js"></script>
   <script type="text/javascript" src="pkijs/common.js"></script>
   <script type="text/javascript" src="pkijs/asn1.js"></script>
   <script type="text/javascript" src="pkijs/x509_schema.js"></script>
   <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
-  <script type="text/javascript" src="cbor/cbor.js"></script>
+  <script type="text/javascript" src="cbor.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
   <h1>W3C Web Authentication - Attestation Conveyance</h1>
   <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1428916">Mozilla Bug 1428916</a>
   <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1416056">Mozilla Bug 1416056</a>
 
--- a/dom/webauthn/tests/test_webauthn_loopback.html
+++ b/dom/webauthn/tests/test_webauthn_loopback.html
@@ -3,17 +3,17 @@
 <head>
   <title>Full-run test for MakeCredential/GetAssertion for W3C Web Authentication</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="u2futil.js"></script>
   <script type="text/javascript" src="pkijs/common.js"></script>
   <script type="text/javascript" src="pkijs/asn1.js"></script>
   <script type="text/javascript" src="pkijs/x509_schema.js"></script>
   <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
-  <script type="text/javascript" src="cbor/cbor.js"></script>
+  <script type="text/javascript" src="cbor.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <h1>Full-run test for MakeCredential/GetAssertion for W3C Web Authentication</h1>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1309284">Mozilla Bug 1309284</a>
 
 <script class="testbody" type="text/javascript">
--- a/dom/webauthn/tests/u2futil.js
+++ b/dom/webauthn/tests/u2futil.js
@@ -197,17 +197,17 @@ function webAuthnDecodeAuthDataArray(aAu
   let attData = {};
   attData.aaguid = aAuthData.slice(37, 53);
   attData.credIdLen = (aAuthData[53] << 8) + aAuthData[54];
   attData.credId = aAuthData.slice(55, 55 + attData.credIdLen);
 
   console.log(":: Authenticator Data ::");
   console.log("AAGUID: " + hexEncode(attData.aaguid));
 
-  cborPubKey = aAuthData.slice(55 + attData.credIdLen);
+  let cborPubKey = aAuthData.slice(55 + attData.credIdLen);
   var pubkeyObj = CBOR.decode(cborPubKey.buffer);
   if (!(cose_kty in pubkeyObj && cose_alg in pubkeyObj && cose_crv in pubkeyObj
         && cose_crv_x in pubkeyObj && cose_crv_y in pubkeyObj)) {
     throw "Invalid CBOR Public Key Object";
   }
   if (pubkeyObj[cose_kty] != cose_kty_ec2) {
     throw "Unexpected key type";
   }
--- a/gfx/thebes/gfxContext.cpp
+++ b/gfx/thebes/gfxContext.cpp
@@ -456,63 +456,61 @@ gfxContext::SetAntialiasMode(AntialiasMo
 
 AntialiasMode
 gfxContext::CurrentAntialiasMode() const
 {
   return CurrentState().aaMode;
 }
 
 void
-gfxContext::SetDash(gfxFloat *dashes, int ndash, gfxFloat offset)
+gfxContext::SetDash(const Float *dashes, int ndash, Float offset)
 {
   CURRENTSTATE_CHANGED()
   AzureState &state = CurrentState();
 
   state.dashPattern.SetLength(ndash);
   for (int i = 0; i < ndash; i++) {
-    state.dashPattern[i] = Float(dashes[i]);
+    state.dashPattern[i] = dashes[i];
   }
   state.strokeOptions.mDashLength = ndash;
-  state.strokeOptions.mDashOffset = Float(offset);
+  state.strokeOptions.mDashOffset = offset;
   state.strokeOptions.mDashPattern = ndash ? state.dashPattern.Elements()
                                            : nullptr;
 }
 
 bool
-gfxContext::CurrentDash(FallibleTArray<gfxFloat>& dashes, gfxFloat* offset) const
+gfxContext::CurrentDash(FallibleTArray<Float>& dashes, Float* offset) const
 {
   const AzureState &state = CurrentState();
   int count = state.strokeOptions.mDashLength;
 
   if (count <= 0 || !dashes.SetLength(count, fallible)) {
     return false;
   }
 
-  for (int i = 0; i < count; i++) {
-    dashes[i] = state.dashPattern[i];
-  }
+  dashes = state.dashPattern;
 
   *offset = state.strokeOptions.mDashOffset;
 
   return true;
 }
 
-gfxFloat
+Float
 gfxContext::CurrentDashOffset() const
 {
   return CurrentState().strokeOptions.mDashOffset;
 }
 
 void
-gfxContext::SetLineWidth(gfxFloat width)
+gfxContext::SetLineWidth(Float width)
 {
-  CurrentState().strokeOptions.mLineWidth = Float(width);
+  CurrentState().strokeOptions.mLineWidth = width;
 }
 
-gfxFloat
+Float
 gfxContext::CurrentLineWidth() const
 {
   return CurrentState().strokeOptions.mLineWidth;
 }
 
 void
 gfxContext::SetOp(CompositionOp aOp)
 {
@@ -548,23 +546,23 @@ gfxContext::SetLineJoin(JoinStyle join)
 
 JoinStyle
 gfxContext::CurrentLineJoin() const
 {
   return CurrentState().strokeOptions.mLineJoin;
 }
 
 void
-gfxContext::SetMiterLimit(gfxFloat limit)
+gfxContext::SetMiterLimit(Float limit)
 {
   CURRENTSTATE_CHANGED()
-  CurrentState().strokeOptions.mMiterLimit = Float(limit);
+  CurrentState().strokeOptions.mMiterLimit = limit;
 }
 
-gfxFloat
+Float
 gfxContext::CurrentMiterLimit() const
 {
   return CurrentState().strokeOptions.mMiterLimit;
 }
 
 // clipping
 void
 gfxContext::Clip(const Rect& rect)
@@ -755,26 +753,26 @@ gfxContext::Mask(SourceSurface *surface,
   // We clip here to bind to the mask surface bounds, see above.
   mDT->MaskSurface(PatternFromState(this),
             surface,
             offset,
             DrawOptions(alpha, CurrentState().op, CurrentState().aaMode));
 }
 
 void
-gfxContext::Paint(gfxFloat alpha)
+gfxContext::Paint(Float alpha)
 {
   AUTO_PROFILER_LABEL("gfxContext::Paint", GRAPHICS);
 
   Matrix mat = mDT->GetTransform();
   mat.Invert();
   Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize())));
 
   mDT->FillRect(paintRect, PatternFromState(this),
-                DrawOptions(Float(alpha), GetOp()));
+                DrawOptions(alpha, GetOp()));
 }
 
 void
 gfxContext::PushGroupForBlendBack(gfxContentType content, Float aOpacity, SourceSurface* aMask, const Matrix& aMaskTransform)
 {
   mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask, aMaskTransform);
 }
 
--- a/gfx/thebes/gfxContext.h
+++ b/gfx/thebes/gfxContext.h
@@ -45,16 +45,17 @@ class ClipExporter;
  * Note that the gfxContext takes coordinates in device pixels,
  * as opposed to app units.
  */
 class gfxContext final {
     typedef mozilla::gfx::CapStyle CapStyle;
     typedef mozilla::gfx::CompositionOp CompositionOp;
     typedef mozilla::gfx::JoinStyle JoinStyle;
     typedef mozilla::gfx::FillRule FillRule;
+    typedef mozilla::gfx::Float Float;
     typedef mozilla::gfx::Path Path;
     typedef mozilla::gfx::Pattern Pattern;
     typedef mozilla::gfx::Rect Rect;
     typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
     typedef mozilla::gfx::Size Size;
 
     NS_INLINE_DECL_REFCOUNTING(gfxContext)
 
@@ -281,68 +282,68 @@ public:
 
     /**
      ** Painting
      **/
     /**
      * Paints the current source surface/pattern everywhere in the current
      * clip region.
      */
-    void Paint(gfxFloat alpha = 1.0);
+    void Paint(Float alpha = 1.0);
 
     /**
      ** Painting with a Mask
      **/
     /**
      * Like Paint, except that it only draws the source where pattern is
      * non-transparent.
      */
     void Mask(mozilla::gfx::SourceSurface *aSurface, mozilla::gfx::Float aAlpha, const mozilla::gfx::Matrix& aTransform);
     void Mask(mozilla::gfx::SourceSurface *aSurface, const mozilla::gfx::Matrix& aTransform) { Mask(aSurface, 1.0f, aTransform); }
     void Mask(mozilla::gfx::SourceSurface *surface, float alpha = 1.0f, const mozilla::gfx::Point& offset = mozilla::gfx::Point());
 
     /**
      ** Line Properties
      **/
 
-    void SetDash(gfxFloat *dashes, int ndash, gfxFloat offset);
+    void SetDash(const Float *dashes, int ndash, Float offset);
     // Return true if dashing is set, false if it's not enabled or the
     // context is in an error state.  |offset| can be nullptr to mean
     // "don't care".
-    bool CurrentDash(FallibleTArray<gfxFloat>& dashes, gfxFloat* offset) const;
+    bool CurrentDash(FallibleTArray<Float>& dashes, Float* offset) const;
     // Returns 0.0 if dashing isn't enabled.
-    gfxFloat CurrentDashOffset() const;
+    Float CurrentDashOffset() const;
 
     /**
      * Sets the line width that's used for line drawing.
      */
-    void SetLineWidth(gfxFloat width);
+    void SetLineWidth(Float width);
 
     /**
      * Returns the currently set line width.
      *
      * @see SetLineWidth
      */
-    gfxFloat CurrentLineWidth() const;
+    Float CurrentLineWidth() const;
 
     /**
      * Sets the line caps, i.e. how line endings are drawn.
      */
     void SetLineCap(CapStyle cap);
     CapStyle CurrentLineCap() const;
 
     /**
      * Sets the line join, i.e. how the connection between two lines is
      * drawn.
      */
     void SetLineJoin(JoinStyle join);
     JoinStyle CurrentLineJoin() const;
 
-    void SetMiterLimit(gfxFloat limit);
-    gfxFloat CurrentMiterLimit() const;
+    void SetMiterLimit(Float limit);
+    Float CurrentMiterLimit() const;
 
     /**
      * Sets the operator used for all further drawing. The operator affects
      * how drawing something will modify the destination. For example, the
      * OVER operator will do alpha blending of source and destination, while
      * SOURCE will replace the destination with the source.
      */
     void SetOp(CompositionOp op);
@@ -456,17 +457,16 @@ private:
 
   friend class PatternFromState;
   friend class GlyphBufferAzure;
 
   typedef mozilla::gfx::Matrix Matrix;
   typedef mozilla::gfx::DrawTarget DrawTarget;
   typedef mozilla::gfx::Color Color;
   typedef mozilla::gfx::StrokeOptions StrokeOptions;
-  typedef mozilla::gfx::Float Float;
   typedef mozilla::gfx::PathBuilder PathBuilder;
   typedef mozilla::gfx::SourceSurface SourceSurface;
 
   struct AzureState {
     AzureState()
       : op(mozilla::gfx::CompositionOp::OP_OVER)
       , color(0, 0, 0, 1.0f)
       , aaMode(mozilla::gfx::AntialiasMode::SUBPIXEL)
--- a/js/src/jit/AliasAnalysisShared.cpp
+++ b/js/src/jit/AliasAnalysisShared.cpp
@@ -105,17 +105,16 @@ GetObject(const MDefinition* ins)
       case MDefinition::Opcode::LoadFixedSlotAndUnbox:
       case MDefinition::Opcode::StoreFixedSlot:
       case MDefinition::Opcode::GetPropertyPolymorphic:
       case MDefinition::Opcode::SetPropertyPolymorphic:
       case MDefinition::Opcode::GuardShape:
       case MDefinition::Opcode::GuardReceiverPolymorphic:
       case MDefinition::Opcode::GuardObjectGroup:
       case MDefinition::Opcode::GuardObjectIdentity:
-      case MDefinition::Opcode::GuardClass:
       case MDefinition::Opcode::GuardUnboxedExpando:
       case MDefinition::Opcode::LoadUnboxedExpando:
       case MDefinition::Opcode::LoadSlot:
       case MDefinition::Opcode::StoreSlot:
       case MDefinition::Opcode::InArray:
       case MDefinition::Opcode::LoadElementHole:
       case MDefinition::Opcode::TypedArrayElements:
       case MDefinition::Opcode::TypedObjectElements:
--- a/js/src/jit/BaselineCacheIRCompiler.cpp
+++ b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -205,42 +205,70 @@ BaselineCacheIRCompiler::compile()
     }
 
     return newStubCode;
 }
 
 bool
 BaselineCacheIRCompiler::emitGuardShape()
 {
-    Register obj = allocator.useRegister(masm, reader.objOperandId());
-    AutoScratchRegister scratch(allocator, masm);
+    ObjOperandId objId = reader.objOperandId();
+    Register obj = allocator.useRegister(masm, objId);
+    AutoScratchRegister scratch1(allocator, masm);
+
+    bool needSpectreMitigations = objectGuardNeedsSpectreMitigations(objId);
+
+    Maybe<AutoScratchRegister> maybeScratch2;
+    if (needSpectreMitigations)
+        maybeScratch2.emplace(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
     Address addr(stubAddress(reader.stubOffset()));
-    masm.loadPtr(addr, scratch);
-    masm.branchTestObjShape(Assembler::NotEqual, obj, scratch, failure->label());
+    masm.loadPtr(addr, scratch1);
+    if (needSpectreMitigations) {
+        masm.branchTestObjShape(Assembler::NotEqual, obj, scratch1, *maybeScratch2, obj,
+                                failure->label());
+    } else {
+        masm.branchTestObjShapeNoSpectreMitigations(Assembler::NotEqual, obj, scratch1,
+                                                    failure->label());
+    }
+
     return true;
 }
 
 bool
 BaselineCacheIRCompiler::emitGuardGroup()
 {
-    Register obj = allocator.useRegister(masm, reader.objOperandId());
-    AutoScratchRegister scratch(allocator, masm);
+    ObjOperandId objId = reader.objOperandId();
+    Register obj = allocator.useRegister(masm, objId);
+    AutoScratchRegister scratch1(allocator, masm);
+
+    bool needSpectreMitigations = objectGuardNeedsSpectreMitigations(objId);
+
+    Maybe<AutoScratchRegister> maybeScratch2;
+    if (needSpectreMitigations)
+        maybeScratch2.emplace(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
     Address addr(stubAddress(reader.stubOffset()));
-    masm.loadPtr(addr, scratch);
-    masm.branchTestObjGroup(Assembler::NotEqual, obj, scratch, failure->label());
+    masm.loadPtr(addr, scratch1);
+    if (needSpectreMitigations) {
+        masm.branchTestObjGroup(Assembler::NotEqual, obj, scratch1, *maybeScratch2, obj,
+                                failure->label());
+    } else {
+        masm.branchTestObjGroupNoSpectreMitigations(Assembler::NotEqual, obj, scratch1,
+                                                    failure->label());
+    }
+
     return true;
 }
 
 bool
 BaselineCacheIRCompiler::emitGuardGroupHasUnanalyzedNewScript()
 {
     Address addr(stubAddress(reader.stubOffset()));
     AutoScratchRegister scratch1(allocator, masm);
@@ -285,25 +313,33 @@ BaselineCacheIRCompiler::emitGuardCompar
     Address addr(stubAddress(reader.stubOffset()));
     masm.branchTestObjCompartment(Assembler::NotEqual, obj, addr, scratch, failure->label());
     return true;
 }
 
 bool
 BaselineCacheIRCompiler::emitGuardAnyClass()
 {
-    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    ObjOperandId objId = reader.objOperandId();
+    Register obj = allocator.useRegister(masm, objId);
     AutoScratchRegister scratch(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
     Address testAddr(stubAddress(reader.stubOffset()));
-    masm.branchTestObjClass(Assembler::NotEqual, obj, scratch, testAddr, failure->label());
+    if (objectGuardNeedsSpectreMitigations(objId)) {
+        masm.branchTestObjClass(Assembler::NotEqual, obj, testAddr, scratch, obj,
+                                failure->label());
+    } else {
+        masm.branchTestObjClassNoSpectreMitigations(Assembler::NotEqual, obj, testAddr, scratch,
+                                                    failure->label());
+    }
+
     return true;
 }
 
 bool
 BaselineCacheIRCompiler::emitGuardHasProxyHandler()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     AutoScratchRegister scratch(allocator, masm);
@@ -398,19 +434,21 @@ BaselineCacheIRCompiler::emitGuardSpecif
 bool
 BaselineCacheIRCompiler::emitGuardXrayExpandoShapeAndDefaultProto()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     bool hasExpando = reader.readBool();
     Address shapeWrapperAddress(stubAddress(reader.stubOffset()));
 
     AutoScratchRegister scratch(allocator, masm);
-    Maybe<AutoScratchRegister> scratch2;
-    if (hasExpando)
+    Maybe<AutoScratchRegister> scratch2, scratch3;
+    if (hasExpando) {
         scratch2.emplace(allocator, masm);
+        scratch3.emplace(allocator, masm);
+    }
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
     masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), scratch);
     Address holderAddress(scratch, sizeof(Value) * GetXrayJitInfo()->xrayHolderSlot);
     Address expandoAddress(scratch, NativeObject::getFixedSlotOffset(GetXrayJitInfo()->holderExpandoSlot));
@@ -422,17 +460,18 @@ BaselineCacheIRCompiler::emitGuardXrayEx
         masm.unboxObject(expandoAddress, scratch);
 
         // Unwrap the expando before checking its shape.
         masm.loadPtr(Address(scratch, ProxyObject::offsetOfReservedSlots()), scratch);
         masm.unboxObject(Address(scratch, detail::ProxyReservedSlots::offsetOfPrivateSlot()), scratch);
 
         masm.loadPtr(shapeWrapperAddress, scratch2.ref());
         LoadShapeWrapperContents(masm, scratch2.ref(), scratch2.ref(), failure->label());
-        masm.branchTestObjShape(Assembler::NotEqual, scratch, scratch2.ref(), failure->label());
+        masm.branchTestObjShape(Assembler::NotEqual, scratch, *scratch2, *scratch3, scratch,
+                                failure->label());
 
         // The reserved slots on the expando should all be in fixed slots.
         Address protoAddress(scratch, NativeObject::getFixedSlotOffset(GetXrayJitInfo()->expandoProtoSlot));
         masm.branchTestUndefined(Assembler::NotEqual, protoAddress, failure->label());
     } else {
         Label done;
         masm.branchTestObject(Assembler::NotEqual, holderAddress, &done);
         masm.unboxObject(holderAddress, scratch);
@@ -2025,17 +2064,20 @@ BaselineCacheIRCompiler::emitGuardDOMExp
         return false;
 
     Label done;
     masm.branchTestUndefined(Assembler::Equal, val, &done);
 
     masm.debugAssertIsObject(val);
     masm.loadPtr(shapeAddr, shapeScratch);
     masm.unboxObject(val, objScratch);
-    masm.branchTestObjShape(Assembler::NotEqual, objScratch, shapeScratch, failure->label());
+    // The expando object is not used in this case, so we don't need Spectre
+    // mitigations.
+    masm.branchTestObjShapeNoSpectreMitigations(Assembler::NotEqual, objScratch, shapeScratch,
+                                                failure->label());
 
     masm.bind(&done);
     return true;
 }
 
 bool
 BaselineCacheIRCompiler::emitLoadDOMExpandoValueGuardGeneration()
 {
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -4375,17 +4375,18 @@ BaselineCompiler::emit_JSOP_SUPERFUN()
     masm.loadObjProto(callee, proto);
 
     // Use VMCall for missing or lazy proto
     Label needVMCall;
     MOZ_ASSERT(uintptr_t(TaggedProto::LazyProto) == 1);
     masm.branchPtr(Assembler::BelowOrEqual, proto, ImmWord(1), &needVMCall);
 
     // Use VMCall for non-JSFunction objects (eg. Proxy)
-    masm.branchTestObjClass(Assembler::NotEqual, proto, scratch, &JSFunction::class_, &needVMCall);
+    masm.branchTestObjClass(Assembler::NotEqual, proto, &JSFunction::class_, scratch, proto,
+                            &needVMCall);
 
     // Use VMCall if not constructor
     masm.load16ZeroExtend(Address(proto, JSFunction::offsetOfFlags()), scratch);
     masm.branchTest32(Assembler::Zero, scratch, Imm32(JSFunction::CONSTRUCTOR), &needVMCall);
 
     // Valid constructor
     Label hasSuperFun;
     masm.jump(&hasSuperFun);
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -421,20 +421,21 @@ bool
 ICTypeUpdate_ObjectGroup::Compiler::generateStubCode(MacroAssembler& masm)
 {
     MOZ_ASSERT(engine_ == Engine::Baseline);
 
     Label failure;
     masm.branchTestObject(Assembler::NotEqual, R0, &failure);
 
     // Guard on the object's ObjectGroup.
-    Register scratch = R1.scratchReg();
-    Register obj = masm.extractObject(R0, scratch);
     Address expectedGroup(ICStubReg, ICTypeUpdate_ObjectGroup::offsetOfGroup());
-    masm.branchTestObjGroup(Assembler::NotEqual, obj, expectedGroup, scratch, &failure);
+    Register scratch1 = R1.scratchReg();
+    masm.unboxObject(R0, scratch1);
+    masm.branchTestObjGroup(Assembler::NotEqual, scratch1, expectedGroup, scratch1,
+                            R0.payloadOrValueReg(), &failure);
 
     // Group matches, load true into R1.scratchReg() and return.
     masm.mov(ImmWord(1), R1.scratchReg());
     EmitReturnFromIC(masm);
 
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
@@ -2626,18 +2627,18 @@ ICCallStubCompiler::guardFunApply(MacroA
         masm.loadValue(secondArgSlot, secondArgVal);
 
         masm.branchTestObject(Assembler::NotEqual, secondArgVal, failure);
         Register secondArgObj = masm.extractObject(secondArgVal, ExtractTemp1);
 
         regsx.add(secondArgVal);
         regsx.takeUnchecked(secondArgObj);
 
-        masm.branchTestObjClass(Assembler::NotEqual, secondArgObj, regsx.getAny(),
-                                &ArrayObject::class_, failure);
+        masm.branchTestObjClass(Assembler::NotEqual, secondArgObj, &ArrayObject::class_,
+                                regsx.getAny(), secondArgObj, failure);
 
         // Get the array elements and ensure that initializedLength == length
         masm.loadPtr(Address(secondArgObj, NativeObject::offsetOfElements()), secondArgObj);
 
         Register lenReg = regsx.takeAny();
         masm.load32(Address(secondArgObj, ObjectElements::offsetOfLength()), lenReg);
 
         masm.branch32(Assembler::NotEqual,
@@ -2674,34 +2675,34 @@ ICCallStubCompiler::guardFunApply(MacroA
     // Load the callee, ensure that it's fun_apply
     ValueOperand val = regs.takeAnyValue();
     Address calleeSlot(masm.getStackPointer(), ICStackValueOffset + (3 * sizeof(Value)));
     masm.loadValue(calleeSlot, val);
 
     masm.branchTestObject(Assembler::NotEqual, val, failure);
     Register callee = masm.extractObject(val, ExtractTemp1);
 
-    masm.branchTestObjClass(Assembler::NotEqual, callee, regs.getAny(), &JSFunction::class_,
-                            failure);
+    masm.branchTestObjClass(Assembler::NotEqual, callee, &JSFunction::class_, regs.getAny(),
+                            callee, failure);
     masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrEnv()), callee);
 
     masm.branchPtr(Assembler::NotEqual, callee, ImmPtr(fun_apply), failure);
 
     // Load the |thisv|, ensure that it's a scripted function with a valid baseline or ion
     // script, or a native function.
     Address thisSlot(masm.getStackPointer(), ICStackValueOffset + (2 * sizeof(Value)));
     masm.loadValue(thisSlot, val);
 
     masm.branchTestObject(Assembler::NotEqual, val, failure);
     Register target = masm.extractObject(val, ExtractTemp1);
     regs.add(val);
     regs.takeUnchecked(target);
 
-    masm.branchTestObjClass(Assembler::NotEqual, target, regs.getAny(), &JSFunction::class_,
-                            failure);
+    masm.branchTestObjClass(Assembler::NotEqual, target, &JSFunction::class_, regs.getAny(),
+                            target, failure);
 
     Register temp = regs.takeAny();
     masm.branchIfFunctionHasNoJitEntry(target, /* constructing */ false, failure);
     masm.branchFunctionKind(Assembler::Equal, JSFunction::ClassConstructor, callee, temp, failure);
     regs.add(temp);
     return target;
 }
 
@@ -2938,18 +2939,18 @@ ICCallScriptedCompiler::generateStubCode
         // Check if the object matches this callee.
         Address expectedCallee(ICStubReg, ICCall_Scripted::offsetOfCallee());
         masm.branchPtr(Assembler::NotEqual, expectedCallee, callee, &failure);
 
         // Guard against relazification.
         masm.branchIfFunctionHasNoJitEntry(callee, isConstructing_, &failure);
     } else {
         // Ensure the object is a function.
-        masm.branchTestObjClass(Assembler::NotEqual, callee, regs.getAny(), &JSFunction::class_,
-                                &failure);
+        masm.branchTestObjClass(Assembler::NotEqual, callee, &JSFunction::class_, regs.getAny(),
+                                callee, &failure);
         if (isConstructing_) {
             masm.branchIfNotInterpretedConstructor(callee, regs.getAny(), &failure);
         } else {
             masm.branchIfFunctionHasNoJitEntry(callee, /* constructing */ false, &failure);
             masm.branchFunctionKind(Assembler::Equal, JSFunction::ClassConstructor, callee,
                                     regs.getAny(), &failure);
         }
     }
@@ -3183,18 +3184,18 @@ ICCall_ConstStringSplit::Compiler::gener
         ValueOperand calleeVal = regs.takeAnyValue();
 
         // Ensure that callee is an object.
         masm.loadValue(calleeAddr, calleeVal);
         masm.branchTestObject(Assembler::NotEqual, calleeVal, &failureRestoreArgc);
 
         // Ensure that callee is a function.
         Register calleeObj = masm.extractObject(calleeVal, ExtractTemp0);
-        masm.branchTestObjClass(Assembler::NotEqual, calleeObj, scratchReg,
-                                &JSFunction::class_, &failureRestoreArgc);
+        masm.branchTestObjClass(Assembler::NotEqual, calleeObj, &JSFunction::class_, scratchReg,
+                                calleeObj, &failureRestoreArgc);
 
         // Ensure that callee's function impl is the native intrinsic_StringSplitString.
         masm.loadPtr(Address(calleeObj, JSFunction::offsetOfNativeOrEnv()), scratchReg);
         masm.branchPtr(Assembler::NotEqual, scratchReg, ImmPtr(js::intrinsic_StringSplitString),
                        &failureRestoreArgc);
 
         regs.add(calleeVal);
     }
@@ -3273,18 +3274,18 @@ ICCall_IsSuspendedGenerator::Compiler::g
     // Check if it's an object.
     Label returnFalse;
     Register genObj = regs.takeAny();
     masm.branchTestObject(Assembler::NotEqual, argVal, &returnFalse);
     masm.unboxObject(argVal, genObj);
 
     // Check if it's a GeneratorObject.
     Register scratch = regs.takeAny();
-    masm.branchTestObjClass(Assembler::NotEqual, genObj, scratch, &GeneratorObject::class_,
-                            &returnFalse);
+    masm.branchTestObjClass(Assembler::NotEqual, genObj, &GeneratorObject::class_, scratch,
+                            genObj, &returnFalse);
 
     // If the yield index slot holds an int32 value < YIELD_AND_AWAIT_INDEX_CLOSING,
     // the generator is suspended.
     masm.loadValue(Address(genObj, GeneratorObject::offsetOfYieldAndAwaitIndexSlot()), argVal);
     masm.branchTestInt32(Assembler::NotEqual, argVal, &returnFalse);
     masm.unboxInt32(argVal, scratch);
     masm.branch32(Assembler::AboveOrEqual, scratch,
                   Imm32(GeneratorObject::YIELD_AND_AWAIT_INDEX_CLOSING),
@@ -3422,21 +3423,25 @@ ICCall_ClassHook::Compiler::generateStub
     unsigned nonArgSlots = (1 + isConstructing_) * sizeof(Value);
     BaseValueIndex calleeSlot(masm.getStackPointer(), argcReg, ICStackValueOffset + nonArgSlots);
     masm.loadValue(calleeSlot, R1);
     regs.take(R1);
 
     masm.branchTestObject(Assembler::NotEqual, R1, &failure);
 
     // Ensure the callee's class matches the one in this stub.
+    // We use |Address(ICStubReg, ICCall_ClassHook::offsetOfNative())| below
+    // instead of extracting the hook from callee. As a result the callee
+    // register is no longer used and we must use spectreRegToZero := ICStubReg
+    // instead.
     Register callee = masm.extractObject(R1, ExtractTemp0);
     Register scratch = regs.takeAny();
-    masm.branchTestObjClass(Assembler::NotEqual, callee, scratch,
+    masm.branchTestObjClass(Assembler::NotEqual, callee,
                             Address(ICStubReg, ICCall_ClassHook::offsetOfClass()),
-                            &failure);
+                            scratch, ICStubReg, &failure);
     regs.add(R1);
     regs.takeUnchecked(callee);
 
     // Push a stub frame so that we can perform a non-tail call.
     // Note that this leaves the return address in TailCallReg.
     enterStubFrame(masm, regs.getAny());
 
     regs.add(scratch);
@@ -3689,30 +3694,30 @@ ICCall_ScriptedFunCall::Compiler::genera
     BaseValueIndex calleeSlot(masm.getStackPointer(), argcReg, ICStackValueOffset + sizeof(Value));
     masm.loadValue(calleeSlot, R1);
     regs.take(R1);
 
     // Ensure callee is fun_call.
     masm.branchTestObject(Assembler::NotEqual, R1, &failure);
 
     Register callee = masm.extractObject(R1, ExtractTemp0);
-    masm.branchTestObjClass(Assembler::NotEqual, callee, regs.getAny(), &JSFunction::class_,
-                            &failure);
+    masm.branchTestObjClass(Assembler::NotEqual, callee, &JSFunction::class_, regs.getAny(),
+                            callee, &failure);
     masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrEnv()), callee);
     masm.branchPtr(Assembler::NotEqual, callee, ImmPtr(fun_call), &failure);
 
     // Ensure |this| is a function with a jit entry.
     BaseIndex thisSlot(masm.getStackPointer(), argcReg, TimesEight, ICStackValueOffset);
     masm.loadValue(thisSlot, R1);
 
     masm.branchTestObject(Assembler::NotEqual, R1, &failure);
     callee = masm.extractObject(R1, ExtractTemp0);
 
-    masm.branchTestObjClass(Assembler::NotEqual, callee, regs.getAny(), &JSFunction::class_,
-                            &failure);
+    masm.branchTestObjClass(Assembler::NotEqual, callee, &JSFunction::class_, regs.getAny(),
+                            callee, &failure);
     masm.branchIfFunctionHasNoJitEntry(callee, /* constructing */ false, &failure);
     masm.branchFunctionKind(Assembler::Equal, JSFunction::ClassConstructor,
                             callee, regs.getAny(), &failure);
 
     // Load the start of the target JitCode.
     Register code = regs.takeAny();
     masm.loadJitCodeRaw(callee, code);
 
@@ -4037,18 +4042,18 @@ ICIteratorMore_Native::Compiler::generat
     Label failure;
 
     Register obj = masm.extractObject(R0, ExtractTemp0);
 
     AllocatableGeneralRegisterSet regs(availableGeneralRegs(1));
     Register nativeIterator = regs.takeAny();
     Register scratch = regs.takeAny();
 
-    masm.branchTestObjClass(Assembler::NotEqual, obj, scratch,
-                            &PropertyIteratorObject::class_, &failure);
+    masm.branchTestObjClass(Assembler::NotEqual, obj, &PropertyIteratorObject::class_, scratch,
+                            obj, &failure);
     masm.loadObjPrivate(obj, JSObject::ITER_CLASS_NFIXED_SLOTS, nativeIterator);
 
     // If props_cursor < props_end, load the next string and advance the cursor.
     // Else, return MagicValue(JS_NO_ITER_VALUE).
     Label iterDone;
     Address cursorAddr(nativeIterator, offsetof(NativeIterator, props_cursor));
     Address cursorEndAddr(nativeIterator, offsetof(NativeIterator, props_end));
     masm.loadPtr(cursorAddr, scratch);
--- a/js/src/jit/CacheIRCompiler.cpp
+++ b/js/src/jit/CacheIRCompiler.cpp
@@ -1405,17 +1405,18 @@ CacheIRCompiler::emitGuardType()
     }
 
     return true;
 }
 
 bool
 CacheIRCompiler::emitGuardClass()
 {
-    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    ObjOperandId objId = reader.objOperandId();
+    Register obj = allocator.useRegister(masm, objId);
     AutoScratchRegister scratch(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
     const Class* clasp = nullptr;
     switch (reader.guardClassKind()) {
@@ -1430,36 +1431,42 @@ CacheIRCompiler::emitGuardClass()
         break;
       case GuardClassKind::WindowProxy:
         clasp = cx_->runtime()->maybeWindowProxyClass();
         break;
       case GuardClassKind::JSFunction:
         clasp = &JSFunction::class_;
         break;
     }
-
     MOZ_ASSERT(clasp);
-    masm.branchTestObjClass(Assembler::NotEqual, obj, scratch, clasp, failure->label());
+
+    if (objectGuardNeedsSpectreMitigations(objId)) {
+        masm.branchTestObjClass(Assembler::NotEqual, obj, clasp, scratch, obj, failure->label());
+    } else {
+        masm.branchTestObjClassNoSpectreMitigations(Assembler::NotEqual, obj, clasp, scratch,
+                                                    failure->label());
+    }
+
     return true;
 }
 
 bool
 CacheIRCompiler::emitGuardIsNativeFunction()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     JSNative nativeFunc = reinterpret_cast<JSNative>(reader.pointer());
     AutoScratchRegister scratch(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
     // Ensure obj is a function.
     const Class* clasp = &JSFunction::class_;
-    masm.branchTestObjClass(Assembler::NotEqual, obj, scratch, clasp, failure->label());
+    masm.branchTestObjClass(Assembler::NotEqual, obj, clasp, scratch, obj, failure->label());
 
     // Ensure function native matches.
     masm.branchPtr(Assembler::NotEqual, Address(obj, JSFunction::offsetOfNativeOrEnv()),
                    ImmPtr(nativeFunc), failure->label());
     return true;
 }
 
 bool
--- a/js/src/jit/CacheIRCompiler.h
+++ b/js/src/jit/CacheIRCompiler.h
@@ -374,16 +374,20 @@ class MOZ_RAII CacheRegisterAllocator
         return spilledRegs_.appendAll(regs);
     }
 
     void nextOp() {
         currentOpRegs_.clear();
         currentInstruction_++;
     }
 
+    bool isDeadAfterInstruction(OperandId opId) const {
+        return writer_.operandIsDead(opId.id(), currentInstruction_ + 1);
+    }
+
     uint32_t stackPushed() const {
         return stackPushed_;
     }
     void setStackPushed(uint32_t pushed) {
         stackPushed_ = pushed;
     }
 
     bool isAllocatable(Register reg) const {
@@ -565,16 +569,24 @@ class MOZ_RAII CacheIRCompiler
     MOZ_MUST_USE bool emitFailurePath(size_t i);
 
     // Returns the set of volatile float registers that are live. These
     // registers need to be saved when making non-GC calls with callWithABI.
     FloatRegisterSet liveVolatileFloatRegs() const {
         return FloatRegisterSet::Intersect(liveFloatRegs_.set(), FloatRegisterSet::Volatile());
     }
 
+    bool objectGuardNeedsSpectreMitigations(ObjOperandId objId) const {
+        // Instructions like GuardShape need Spectre mitigations if
+        // (1) mitigations are enabled and (2) the object is used by other
+        // instructions (if the object is *not* used by other instructions,
+        // zeroing its register is pointless).
+        return JitOptions.spectreObjectMitigationsMisc && !allocator.isDeadAfterInstruction(objId);
+    }
+
     void emitLoadTypedObjectResultShared(const Address& fieldAddr, Register scratch,
                                          uint32_t typeDescr,
                                          const AutoOutputRegister& output);
 
     void emitStoreTypedObjectReferenceProp(ValueOperand val, ReferenceTypeDescr::Type type,
                                            const Address& dest, Register scratch);
 
     void emitRegisterEnumerator(Register enumeratorsList, Register iter, Register scratch);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -955,17 +955,17 @@ CodeGenerator::visitFunctionDispatch(LFu
         lastLabel = skipTrivialBlocks(mir->getFallback())->lir()->label();
     }
 
     // Compare function pointers, except for the last case.
     for (size_t i = 0; i < casesWithFallback - 1; i++) {
         MOZ_ASSERT(i < mir->numCases());
         LBlock* target = skipTrivialBlocks(mir->getCaseBlock(i))->lir();
         if (ObjectGroup* funcGroup = mir->getCaseObjectGroup(i)) {
-            masm.branchTestObjGroup(Assembler::Equal, input, funcGroup, target->label());
+            masm.branchTestObjGroupUnsafe(Assembler::Equal, input, funcGroup, target->label());
         } else {
             JSFunction* func = mir->getCase(i);
             masm.branchPtr(Assembler::Equal, input, ImmGCPtr(func), target->label());
         }
     }
 
     // Jump to the last case.
     masm.jump(lastLabel);
@@ -2513,17 +2513,17 @@ CodeGenerator::visitRegExpPrototypeOptim
     addOutOfLineCode(ool, ins->mir());
 
     masm.loadJSContext(temp);
     masm.loadPtr(Address(temp, JSContext::offsetOfCompartment()), temp);
     size_t offset = JSCompartment::offsetOfRegExps() +
                     RegExpCompartment::offsetOfOptimizableRegExpPrototypeShape();
     masm.loadPtr(Address(temp, offset), temp);
 
-    masm.branchTestObjShape(Assembler::NotEqual, object, temp, ool->entry());
+    masm.branchTestObjShapeUnsafe(Assembler::NotEqual, object, temp, ool->entry());
     masm.move32(Imm32(0x1), output);
 
     masm.bind(ool->rejoin());
 }
 
 void
 CodeGenerator::visitOutOfLineRegExpPrototypeOptimizable(OutOfLineRegExpPrototypeOptimizable* ool)
 {
@@ -2573,17 +2573,17 @@ CodeGenerator::visitRegExpInstanceOptimi
     addOutOfLineCode(ool, ins->mir());
 
     masm.loadJSContext(temp);
     masm.loadPtr(Address(temp, JSContext::offsetOfCompartment()), temp);
     size_t offset = JSCompartment::offsetOfRegExps() +
                     RegExpCompartment::offsetOfOptimizableRegExpInstanceShape();
     masm.loadPtr(Address(temp, offset), temp);
 
-    masm.branchTestObjShape(Assembler::NotEqual, object, temp, ool->entry());
+    masm.branchTestObjShapeUnsafe(Assembler::NotEqual, object, temp, ool->entry());
     masm.move32(Imm32(0x1), output);
 
     masm.bind(ool->rejoin());
 }
 
 void
 CodeGenerator::visitOutOfLineRegExpInstanceOptimizable(OutOfLineRegExpInstanceOptimizable* ool)
 {
@@ -3463,54 +3463,58 @@ CodeGenerator::visitStoreSlotV(LStoreSlo
     if (lir->mir()->needsBarrier())
        emitPreBarrier(Address(base, offset));
 
     masm.storeValue(value, Address(base, offset));
 }
 
 static void
 GuardReceiver(MacroAssembler& masm, const ReceiverGuard& guard,
-              Register obj, Register scratch, Label* miss, bool checkNullExpando)
+              Register obj, Register expandoScratch, Register scratch, Label* miss,
+              bool checkNullExpando)
 {
     if (guard.group) {
-        masm.branchTestObjGroup(Assembler::NotEqual, obj, guard.group, miss);
+        masm.branchTestObjGroup(Assembler::NotEqual, obj, guard.group, scratch, obj, miss);
 
         Address expandoAddress(obj, UnboxedPlainObject::offsetOfExpando());
         if (guard.shape) {
-            masm.loadPtr(expandoAddress, scratch);
-            masm.branchPtr(Assembler::Equal, scratch, ImmWord(0), miss);
-            masm.branchTestObjShape(Assembler::NotEqual, scratch, guard.shape, miss);
+            masm.loadPtr(expandoAddress, expandoScratch);
+            masm.branchPtr(Assembler::Equal, expandoScratch, ImmWord(0), miss);
+            masm.branchTestObjShape(Assembler::NotEqual, expandoScratch, guard.shape, scratch,
+                                    expandoScratch, miss);
         } else if (checkNullExpando) {
             masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), miss);
         }
     } else {
-        masm.branchTestObjShape(Assembler::NotEqual, obj, guard.shape, miss);
-    }
-}
-
-void
-CodeGenerator::emitGetPropertyPolymorphic(LInstruction* ins, Register obj, Register scratch,
+        masm.branchTestObjShape(Assembler::NotEqual, obj, guard.shape, scratch, obj, miss);
+    }
+}
+
+void
+CodeGenerator::emitGetPropertyPolymorphic(LInstruction* ins, Register obj, Register expandoScratch,
+                                          Register scratch,
                                           const TypedOrValueRegister& output)
 {
     MGetPropertyPolymorphic* mir = ins->mirRaw()->toGetPropertyPolymorphic();
 
     Label done;
 
     for (size_t i = 0; i < mir->numReceivers(); i++) {
         ReceiverGuard receiver = mir->receiver(i);
 
         Label next;
         masm.comment("GuardReceiver");
-        GuardReceiver(masm, receiver, obj, scratch, &next, /* checkNullExpando = */ false);
+        GuardReceiver(masm, receiver, obj, expandoScratch, scratch, &next,
+                      /* checkNullExpando = */ false);
 
         if (receiver.shape) {
             masm.comment("loadTypedOrValue");
             // If this is an unboxed expando access, GuardReceiver loaded the
-            // expando object into scratch.
-            Register target = receiver.group ? scratch : obj;
+            // expando object into expandoScratch.
+            Register target = receiver.group ? expandoScratch : obj;
 
             Shape* shape = mir->shape(i);
             if (shape->slot() < shape->numFixedSlots()) {
                 // Fixed slot.
                 masm.loadTypedOrValue(Address(target, NativeObject::getFixedSlotOffset(shape->slot())),
                                       output);
             } else {
                 // Dynamic slot.
@@ -3538,59 +3542,62 @@ CodeGenerator::emitGetPropertyPolymorphi
     masm.bind(&done);
 }
 
 void
 CodeGenerator::visitGetPropertyPolymorphicV(LGetPropertyPolymorphicV* ins)
 {
     Register obj = ToRegister(ins->obj());
     ValueOperand output = ToOutValue(ins);
-    emitGetPropertyPolymorphic(ins, obj, output.scratchReg(), output);
+    Register temp = ToRegister(ins->temp());
+    emitGetPropertyPolymorphic(ins, obj, output.scratchReg(), temp, output);
 }
 
 void
 CodeGenerator::visitGetPropertyPolymorphicT(LGetPropertyPolymorphicT* ins)
 {
     Register obj = ToRegister(ins->obj());
     TypedOrValueRegister output(ins->mir()->type(), ToAnyRegister(ins->output()));
-    Register temp = (output.type() == MIRType::Double)
-                    ? ToRegister(ins->temp())
-                    : output.typedReg().gpr();
-    emitGetPropertyPolymorphic(ins, obj, temp, output);
+    Register temp1 = ToRegister(ins->temp1());
+    Register temp2 = (output.type() == MIRType::Double)
+                     ? ToRegister(ins->temp2())
+                     : output.typedReg().gpr();
+    emitGetPropertyPolymorphic(ins, obj, temp1, temp2, output);
 }
 
 template <typename T>
 static void
 EmitUnboxedPreBarrier(MacroAssembler &masm, T address, JSValueType type)
 {
     if (type == JSVAL_TYPE_OBJECT)
         masm.guardedCallPreBarrier(address, MIRType::Object);
     else if (type == JSVAL_TYPE_STRING)
         masm.guardedCallPreBarrier(address, MIRType::String);
     else
         MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(type));
 }
 
 void
-CodeGenerator::emitSetPropertyPolymorphic(LInstruction* ins, Register obj, Register scratch,
-                                          const ConstantOrRegister& value)
+CodeGenerator::emitSetPropertyPolymorphic(LInstruction* ins, Register obj, Register expandoScratch,
+                                          Register scratch, const ConstantOrRegister& value)
 {
     MSetPropertyPolymorphic* mir = ins->mirRaw()->toSetPropertyPolymorphic();
 
     Label done;
     for (size_t i = 0; i < mir->numReceivers(); i++) {
         ReceiverGuard receiver = mir->receiver(i);
 
         Label next;
-        GuardReceiver(masm, receiver, obj, scratch, &next, /* checkNullExpando = */ false);
+        GuardReceiver(masm, receiver, obj, expandoScratch, scratch, &next,
+                      /* checkNullExpando = */ false);
 
         if (receiver.shape) {
             // If this is an unboxed expando access, GuardReceiver loaded the
-            // expando object into scratch.
-            Register target = receiver.group ? scratch : obj;
+            // expando object into expandoScratch.
+            Register target = receiver.group ? expandoScratch : obj;
 
             Shape* shape = mir->shape(i);
             if (shape->slot() < shape->numFixedSlots()) {
                 // Fixed slot.
                 Address addr(target, NativeObject::getFixedSlotOffset(shape->slot()));
                 if (mir->needsBarrier())
                     emitPreBarrier(addr);
                 masm.storeConstantOrRegister(value, addr);
@@ -3621,34 +3628,36 @@ CodeGenerator::emitSetPropertyPolymorphi
 
     masm.bind(&done);
 }
 
 void
 CodeGenerator::visitSetPropertyPolymorphicV(LSetPropertyPolymorphicV* ins)
 {
     Register obj = ToRegister(ins->obj());
-    Register temp = ToRegister(ins->temp());
+    Register temp1 = ToRegister(ins->temp1());
+    Register temp2 = ToRegister(ins->temp2());
     ValueOperand value = ToValue(ins, LSetPropertyPolymorphicV::Value);
-    emitSetPropertyPolymorphic(ins, obj, temp, TypedOrValueRegister(value));
+    emitSetPropertyPolymorphic(ins, obj, temp1, temp2, TypedOrValueRegister(value));
 }
 
 void
 CodeGenerator::visitSetPropertyPolymorphicT(LSetPropertyPolymorphicT* ins)
 {
     Register obj = ToRegister(ins->obj());
-    Register temp = ToRegister(ins->temp());
+    Register temp1 = ToRegister(ins->temp1());
+    Register temp2 = ToRegister(ins->temp2());
 
     ConstantOrRegister value;
     if (ins->mir()->value()->isConstant())
         value = ConstantOrRegister(ins->mir()->value()->toConstant()->toJSValue());
     else
         value = TypedOrValueRegister(ins->mir()->value()->type(), ToAnyRegister(ins->value()));
 
-    emitSetPropertyPolymorphic(ins, obj, temp, value);
+    emitSetPropertyPolymorphic(ins, obj, temp1, temp2, value);
 }
 
 void
 CodeGenerator::visitElements(LElements* lir)
 {
     Address elements(ToRegister(lir->object()), NativeObject::offsetOfElements());
     masm.loadPtr(elements, ToRegister(lir->output()));
 }
@@ -3790,39 +3799,31 @@ CodeGenerator::visitCopyLexicalEnvironme
     pushArg(ToRegister(lir->env()));
     callVM(CopyLexicalEnvironmentObjectInfo, lir);
 }
 
 void
 CodeGenerator::visitGuardShape(LGuardShape* guard)
 {
     Register obj = ToRegister(guard->input());
+    Register temp = ToTempRegisterOrInvalid(guard->temp());
     Label bail;
-    masm.branchTestObjShape(Assembler::NotEqual, obj, guard->mir()->shape(), &bail);
+    masm.branchTestObjShape(Assembler::NotEqual, obj, guard->mir()->shape(), temp, obj, &bail);
     bailoutFrom(&bail, guard->snapshot());
 }
 
 void
 CodeGenerator::visitGuardObjectGroup(LGuardObjectGroup* guard)
 {
     Register obj = ToRegister(guard->input());
+    Register temp = ToTempRegisterOrInvalid(guard->temp());
     Assembler::Condition cond =
         guard->mir()->bailOnEquality() ? Assembler::Equal : Assembler::NotEqual;
     Label bail;
-    masm.branchTestObjGroup(cond, obj, guard->mir()->group(), &bail);
-    bailoutFrom(&bail, guard->snapshot());
-}
-
-void
-CodeGenerator::visitGuardClass(LGuardClass* guard)
-{
-    Register obj = ToRegister(guard->input());
-    Register tmp = ToRegister(guard->tempInt());
-    Label bail;
-    masm.branchTestObjClass(Assembler::NotEqual, obj, tmp, guard->mir()->getClass(), &bail);
+    masm.branchTestObjGroup(cond, obj, guard->mir()->group(), temp, obj, &bail);
     bailoutFrom(&bail, guard->snapshot());
 }
 
 void
 CodeGenerator::visitGuardObjectIdentity(LGuardObjectIdentity* guard)
 {
     Register input = ToRegister(guard->input());
     Register expected = ToRegister(guard->expected());
@@ -3832,25 +3833,26 @@ CodeGenerator::visitGuardObjectIdentity(
     bailoutCmpPtr(cond, input, expected, guard->snapshot());
 }
 
 void
 CodeGenerator::visitGuardReceiverPolymorphic(LGuardReceiverPolymorphic* lir)
 {
     const MGuardReceiverPolymorphic* mir = lir->mir();
     Register obj = ToRegister(lir->object());
-    Register temp = ToRegister(lir->temp());
+    Register temp1 = ToRegister(lir->temp1());
+    Register temp2 = ToRegister(lir->temp2());
 
     Label done;
 
     for (size_t i = 0; i < mir->numReceivers(); i++) {
         const ReceiverGuard& receiver = mir->receiver(i);
 
         Label next;
-        GuardReceiver(masm, receiver, obj, temp, &next, /* checkNullExpando = */ true);
+        GuardReceiver(masm, receiver, obj, temp1, temp2, &next, /* checkNullExpando = */ true);
 
         if (i == mir->numReceivers() - 1) {
             bailoutFrom(&next, lir->snapshot());
         } else {
             masm.jump(&done);
             masm.bind(&next);
         }
     }
@@ -4512,18 +4514,18 @@ CodeGenerator::visitCallGeneric(LCallGen
 
     // Known-target case is handled by LCallKnown.
     MOZ_ASSERT(!call->hasSingleTarget());
 
     masm.checkStackAlignment();
 
     // Guard that calleereg is actually a function object.
     if (call->mir()->needsClassCheck()) {
-        masm.branchTestObjClass(Assembler::NotEqual, calleereg, nargsreg, &JSFunction::class_,
-                                &invoke);
+        masm.branchTestObjClass(Assembler::NotEqual, calleereg, &JSFunction::class_, nargsreg,
+                                calleereg, &invoke);
     }
 
     // Guard that calleereg is an interpreted function with a JSScript or a
     // wasm function.
     // If we are constructing, also ensure the callee is a constructor.
     if (call->mir()->isConstructing()) {
         masm.branchIfNotInterpretedConstructor(calleereg, nargsreg, &invoke);
     } else {
@@ -4912,18 +4914,18 @@ CodeGenerator::emitApplyGeneric(T* apply
 
     // Holds the function nargs, computed in the invoker or (for
     // ApplyArray) in the argument pusher.
     Register argcreg = ToRegister(apply->getArgc());
 
     // Unless already known, guard that calleereg is actually a function object.
     if (!apply->hasSingleTarget()) {
         Label bail;
-        masm.branchTestObjClass(Assembler::NotEqual, calleereg, objreg, &JSFunction::class_,
-                                &bail);
+        masm.branchTestObjClass(Assembler::NotEqual, calleereg, &JSFunction::class_, objreg,
+                                calleereg, &bail);
         bailoutFrom(&bail, apply->snapshot());
     }
 
     // Copy the arguments of the current function.
     //
     // In the case of ApplyArray, also compute argc: the argc register
     // and the elements register are the same; argc must not be
     // referenced before the call to emitPushArguments() and elements
@@ -6994,18 +6996,20 @@ CodeGenerator::emitGetNextEntryForIterat
     Register dataLength = ToRegister(lir->temp1());
     Register range = ToRegister(lir->temp2());
     Register output = ToRegister(lir->output());
 
 #ifdef DEBUG
     // Self-hosted code is responsible for ensuring GetNextEntryForIterator is
     // only called with the correct iterator class. Assert here all self-
     // hosted callers of GetNextEntryForIterator perform this class check.
+    // No Spectre mitigations are needed because this is DEBUG-only code.
     Label success;
-    masm.branchTestObjClass(Assembler::Equal, iter, temp, &IteratorObject::class_, &success);
+    masm.branchTestObjClassNoSpectreMitigations(Assembler::Equal, iter, &IteratorObject::class_,
+                                                temp, &success);
     masm.assumeUnreachable("Iterator object should have the correct class.");
     masm.bind(&success);
 #endif
 
     masm.loadPrivate(Address(iter, NativeObject::getFixedSlotOffset(IteratorObject::RangeSlot)),
                      range);
 
     Label iterAlreadyDone, iterDone, done;
@@ -9296,30 +9300,33 @@ CodeGenerator::visitStoreUnboxedPointer(
         Address address(elements, ToInt32(index) * sizeof(uintptr_t) + offsetAdjustment);
         StoreUnboxedPointer(masm, address, type, value, preBarrier);
     } else {
         BaseIndex address(elements, ToRegister(index), ScalePointer, offsetAdjustment);
         StoreUnboxedPointer(masm, address, type, value, preBarrier);
     }
 }
 
-typedef bool (*ConvertUnboxedObjectToNativeFn)(JSContext*, JSObject*);
+typedef NativeObject* (*ConvertUnboxedObjectToNativeFn)(JSContext*, JSObject*);
 static const VMFunction ConvertUnboxedPlainObjectToNativeInfo =
     FunctionInfo<ConvertUnboxedObjectToNativeFn>(UnboxedPlainObject::convertToNative,
                                                  "UnboxedPlainObject::convertToNative");
 
 void
 CodeGenerator::visitConvertUnboxedObjectToNative(LConvertUnboxedObjectToNative* lir)
 {
     Register object = ToRegister(lir->getOperand(0));
-
+    Register temp = ToTempRegisterOrInvalid(lir->temp());
+
+    // The call will return the same object so StoreRegisterTo(object) is safe.
     OutOfLineCode* ool = oolCallVM(ConvertUnboxedPlainObjectToNativeInfo,
-                                   lir, ArgList(object), StoreNothing());
-
-    masm.branchTestObjGroup(Assembler::Equal, object, lir->mir()->group(), ool->entry());
+                                   lir, ArgList(object), StoreRegisterTo(object));
+
+    masm.branchTestObjGroup(Assembler::Equal, object, lir->mir()->group(), temp, object,
+                            ool->entry());
     masm.bind(ool->rejoin());
 }
 
 typedef bool (*ArrayPopShiftFn)(JSContext*, HandleObject, MutableHandleValue);
 static const VMFunction ArrayPopDenseInfo =
     FunctionInfo<ArrayPopShiftFn>(jit::ArrayPopDense, "ArrayPopDense");
 static const VMFunction ArrayShiftDenseInfo =
     FunctionInfo<ArrayPopShiftFn>(jit::ArrayShiftDense, "ArrayShiftDense");
@@ -9589,17 +9596,18 @@ CodeGenerator::visitGetIteratorCache(LGe
 }
 
 static void
 LoadNativeIterator(MacroAssembler& masm, Register obj, Register dest, Label* failures)
 {
     MOZ_ASSERT(obj != dest);
 
     // Test class.
-    masm.branchTestObjClass(Assembler::NotEqual, obj, dest, &PropertyIteratorObject::class_, failures);
+    masm.branchTestObjClass(Assembler::NotEqual, obj, &PropertyIteratorObject::class_, dest,
+                            obj, failures);
 
     // Load NativeIterator object.
     masm.loadObjPrivate(obj, JSObject::ITER_CLASS_NFIXED_SLOTS, dest);
 }
 
 typedef bool (*IteratorMoreFn)(JSContext*, HandleObject, MutableHandleValue);
 static const VMFunction IteratorMoreInfo =
     FunctionInfo<IteratorMoreFn>(IteratorMore, "IteratorMore");
@@ -13121,17 +13129,18 @@ CodeGenerator::visitFinishBoundFunctionI
 
     OutOfLineCode* ool = oolCallVM(FinishBoundFunctionInitInfo, lir,
                                    ArgList(bound, target, argCount), StoreNothing());
     Label* slowPath = ool->entry();
 
     const size_t boundLengthOffset = FunctionExtended::offsetOfExtendedSlot(BOUND_FUN_LENGTH_SLOT);
 
     // Take the slow path if the target is not a JSFunction.
-    masm.branchTestObjClass(Assembler::NotEqual, target, temp1, &JSFunction::class_, slowPath);
+    masm.branchTestObjClass(Assembler::NotEqual, target, &JSFunction::class_, temp1, target,
+                            slowPath);
 
     // Take the slow path if we'd need to adjust the [[Prototype]].
     masm.loadObjProto(bound, temp1);
     masm.loadObjProto(target, temp2);
     masm.branchPtr(Assembler::NotEqual, temp1, temp2, slowPath);
 
     // Get the function flags.
     masm.load16ZeroExtend(Address(target, JSFunction::offsetOfFlags()), temp1);
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -152,17 +152,16 @@ class CodeGenerator final : public CodeG
     void visitStoreSlotT(LStoreSlotT* lir);
     void visitStoreSlotV(LStoreSlotV* lir);
     void visitElements(LElements* lir);
     void visitConvertElementsToDoubles(LConvertElementsToDoubles* lir);
     void visitMaybeToDoubleElement(LMaybeToDoubleElement* lir);
     void visitMaybeCopyElementsForWrite(LMaybeCopyElementsForWrite* lir);
     void visitGuardShape(LGuardShape* guard);
     void visitGuardObjectGroup(LGuardObjectGroup* guard);
-    void visitGuardClass(LGuardClass* guard);
     void visitGuardObjectIdentity(LGuardObjectIdentity* guard);
     void visitGuardReceiverPolymorphic(LGuardReceiverPolymorphic* lir);
     void visitGuardUnboxedExpando(LGuardUnboxedExpando* lir);
     void visitLoadUnboxedExpando(LLoadUnboxedExpando* lir);
     void visitTypeBarrierV(LTypeBarrierV* lir);
     void visitTypeBarrierO(LTypeBarrierO* lir);
     void emitPostWriteBarrier(const LAllocation* obj);
     void emitPostWriteBarrier(Register objreg);
@@ -257,21 +256,21 @@ class CodeGenerator final : public CodeG
     void visitBoundsCheckRange(LBoundsCheckRange* lir);
     void visitBoundsCheckLower(LBoundsCheckLower* lir);
     void visitSpectreMaskIndex(LSpectreMaskIndex* lir);
     void visitLoadFixedSlotV(LLoadFixedSlotV* ins);
     void visitLoadFixedSlotAndUnbox(LLoadFixedSlotAndUnbox* lir);
     void visitLoadFixedSlotT(LLoadFixedSlotT* ins);
     void visitStoreFixedSlotV(LStoreFixedSlotV* ins);
     void visitStoreFixedSlotT(LStoreFixedSlotT* ins);
-    void emitGetPropertyPolymorphic(LInstruction* lir, Register obj,
+    void emitGetPropertyPolymorphic(LInstruction* lir, Register obj, Register expandoScratch,
                                     Register scratch, const TypedOrValueRegister& output);
     void visitGetPropertyPolymorphicV(LGetPropertyPolymorphicV* ins);
     void visitGetPropertyPolymorphicT(LGetPropertyPolymorphicT* ins);
-    void emitSetPropertyPolymorphic(LInstruction* lir, Register obj,
+    void emitSetPropertyPolymorphic(LInstruction* lir, Register obj, Register expandoScratch,
                                     Register scratch, const ConstantOrRegister& value);
     void visitSetPropertyPolymorphicV(LSetPropertyPolymorphicV* ins);
     void visitSetPropertyPolymorphicT(LSetPropertyPolymorphicT* ins);
     void visitAbsI(LAbsI* lir);
     void visitAtan2D(LAtan2D* lir);
     void visitHypot(LHypot* lir);
     void visitPowI(LPowI* lir);
     void visitPowD(LPowD* lir);
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -3537,18 +3537,22 @@ TryOptimizeLoadObjectOrNull(MDefinition*
 
 static inline MDefinition*
 PassthroughOperand(MDefinition* def)
 {
     if (def->isConvertElementsToDoubles())
         return def->toConvertElementsToDoubles()->elements();
     if (def->isMaybeCopyElementsForWrite())
         return def->toMaybeCopyElementsForWrite()->object();
-    if (def->isConvertUnboxedObjectToNative())
-        return def->toConvertUnboxedObjectToNative()->object();
+    if (!JitOptions.spectreObjectMitigationsMisc) {
+        // If Spectre mitigations are enabled, LConvertUnboxedObjectToNative
+        // needs to have its own def.
+        if (def->isConvertUnboxedObjectToNative())
+            return def->toConvertUnboxedObjectToNative()->object();
+    }
     return nullptr;
 }
 
 // Eliminate checks which are redundant given each other or other instructions.
 //
 // A type barrier is considered redundant if all missing types have been tested
 // for by earlier control instructions.
 //
--- a/js/src/jit/IonCacheIRCompiler.cpp
+++ b/js/src/jit/IonCacheIRCompiler.cpp
@@ -623,38 +623,66 @@ IonCacheIRCompiler::compile()
     }
 
     return newStubCode;
 }
 
 bool
 IonCacheIRCompiler::emitGuardShape()
 {
-    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    ObjOperandId objId = reader.objOperandId();
+    Register obj = allocator.useRegister(masm, objId);
     Shape* shape = shapeStubField(reader.stubOffset());
 
+    bool needSpectreMitigations = objectGuardNeedsSpectreMitigations(objId);
+
+    Maybe<AutoScratchRegister> maybeScratch;
+    if (needSpectreMitigations)
+        maybeScratch.emplace(allocator, masm);
+
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
-    masm.branchTestObjShape(Assembler::NotEqual, obj, shape, failure->label());
+    if (needSpectreMitigations) {
+        masm.branchTestObjShape(Assembler::NotEqual, obj, shape, *maybeScratch, obj,
+                                failure->label());
+    } else {
+        masm.branchTestObjShapeNoSpectreMitigations(Assembler::NotEqual, obj, shape,
+                                                    failure->label());
+    }
+
     return true;
 }
 
 bool
 IonCacheIRCompiler::emitGuardGroup()
 {
-    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    ObjOperandId objId = reader.objOperandId();
+    Register obj = allocator.useRegister(masm, objId);
     ObjectGroup* group = groupStubField(reader.stubOffset());
 
+    bool needSpectreMitigations = objectGuardNeedsSpectreMitigations(objId);
+
+    Maybe<AutoScratchRegister> maybeScratch;
+    if (needSpectreMitigations)
+        maybeScratch.emplace(allocator, masm);
+
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
-    masm.branchTestObjGroup(Assembler::NotEqual, obj, group, failure->label());
+    if (needSpectreMitigations) {
+        masm.branchTestObjGroup(Assembler::NotEqual, obj, group, *maybeScratch, obj,
+                                failure->label());
+    } else {
+        masm.branchTestObjGroupNoSpectreMitigations(Assembler::NotEqual, obj, group,
+                                                    failure->label());
+    }
+
     return true;
 }
 
 bool
 IonCacheIRCompiler::emitGuardGroupHasUnanalyzedNewScript()
 {
     ObjectGroup* group = groupStubField(reader.stubOffset());
     AutoScratchRegister scratch1(allocator, masm);
@@ -702,26 +730,33 @@ IonCacheIRCompiler::emitGuardCompartment
     masm.branchTestObjCompartment(Assembler::NotEqual, obj, compartment, scratch,
                                   failure->label());
     return true;
 }
 
 bool
 IonCacheIRCompiler::emitGuardAnyClass()
 {
-    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    ObjOperandId objId = reader.objOperandId();
+    Register obj = allocator.useRegister(masm, objId);
     AutoScratchRegister scratch(allocator, masm);
 
     const Class* clasp = classStubField(reader.stubOffset());
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
-    masm.branchTestObjClass(Assembler::NotEqual, obj, scratch, clasp, failure->label());
+    if (objectGuardNeedsSpectreMitigations(objId)) {
+        masm.branchTestObjClass(Assembler::NotEqual, obj, clasp, scratch, obj, failure->label());
+    } else {
+        masm.branchTestObjClassNoSpectreMitigations(Assembler::NotEqual, obj, clasp, scratch,
+                                                    failure->label());
+    }
+
     return true;
 }
 
 bool
 IonCacheIRCompiler::emitGuardHasProxyHandler()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     const void* handler = proxyHandlerStubField(reader.stubOffset());
@@ -812,19 +847,21 @@ bool
 IonCacheIRCompiler::emitGuardXrayExpandoShapeAndDefaultProto()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     bool hasExpando = reader.readBool();
     JSObject* shapeWrapper = objectStubField(reader.stubOffset());
     MOZ_ASSERT(hasExpando == !!shapeWrapper);
 
     AutoScratchRegister scratch(allocator, masm);
-    Maybe<AutoScratchRegister> scratch2;
-    if (hasExpando)
+    Maybe<AutoScratchRegister> scratch2, scratch3;
+    if (hasExpando) {
         scratch2.emplace(allocator, masm);
+        scratch3.emplace(allocator, masm);
+    }
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
     masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), scratch);
     Address holderAddress(scratch, sizeof(Value) * GetXrayJitInfo()->xrayHolderSlot);
     Address expandoAddress(scratch, NativeObject::getFixedSlotOffset(GetXrayJitInfo()->holderExpandoSlot));
@@ -836,17 +873,18 @@ IonCacheIRCompiler::emitGuardXrayExpando
         masm.unboxObject(expandoAddress, scratch);
 
         // Unwrap the expando before checking its shape.
         masm.loadPtr(Address(scratch, ProxyObject::offsetOfReservedSlots()), scratch);
         masm.unboxObject(Address(scratch, detail::ProxyReservedSlots::offsetOfPrivateSlot()), scratch);
 
         masm.movePtr(ImmGCPtr(shapeWrapper), scratch2.ref());
         LoadShapeWrapperContents(masm, scratch2.ref(), scratch2.ref(), failure->label());
-        masm.branchTestObjShape(Assembler::NotEqual, scratch, scratch2.ref(), failure->label());
+        masm.branchTestObjShape(Assembler::NotEqual, scratch, *scratch2, *scratch3, scratch,
+                                failure->label());
 
         // The reserved slots on the expando should all be in fixed slots.
         Address protoAddress(scratch, NativeObject::getFixedSlotOffset(GetXrayJitInfo()->expandoProtoSlot));
         masm.branchTestUndefined(Assembler::NotEqual, protoAddress, failure->label());
     } else {
         Label done;
         masm.branchTestObject(Assembler::NotEqual, holderAddress, &done);
         masm.unboxObject(holderAddress, scratch);
@@ -2358,17 +2396,20 @@ IonCacheIRCompiler::emitGuardDOMExpandoM
     if (!addFailurePath(&failure))
         return false;
 
     Label done;
     masm.branchTestUndefined(Assembler::Equal, val, &done);
 
     masm.debugAssertIsObject(val);
     masm.unboxObject(val, objScratch);
-    masm.branchTestObjShape(Assembler::NotEqual, objScratch, shape, failure->label());
+    // The expando object is not used in this case, so we don't need Spectre
+    // mitigations.
+    masm.branchTestObjShapeNoSpectreMitigations(Assembler::NotEqual, objScratch, shape,
+                                                failure->label());
 
     masm.bind(&done);
     return true;
 }
 
 bool
 IonCacheIRCompiler::emitLoadDOMExpandoValueGuardGeneration()
 {
--- a/js/src/jit/JitOptions.cpp
+++ b/js/src/jit/JitOptions.cpp
@@ -231,16 +231,17 @@ DefaultJitOptions::DefaultJitOptions()
     if (const char* env = getenv(forcedRegisterAllocatorEnv)) {
         forcedRegisterAllocator = LookupRegisterAllocator(env);
         if (!forcedRegisterAllocator.isSome())
             Warn(forcedRegisterAllocatorEnv, env);
     }
 
     SET_DEFAULT(spectreIndexMasking, true);
     SET_DEFAULT(spectreObjectMitigationsBarriers, true);
+    SET_DEFAULT(spectreObjectMitigationsMisc, false);
     SET_DEFAULT(spectreStringMitigations, true);
     SET_DEFAULT(spectreValueMasking, true);
     SET_DEFAULT(spectreJitToCxxCalls, true);
 
     // Toggles whether unboxed plain objects can be created by the VM.
     SET_DEFAULT(disableUnboxedObjects, false);
 
     // Test whether Atomics are allowed in asm.js code.
--- a/js/src/jit/JitOptions.h
+++ b/js/src/jit/JitOptions.h
@@ -95,16 +95,17 @@ struct DefaultJitOptions
     mozilla::Maybe<uint32_t> forcedDefaultIonSmallFunctionWarmUpThreshold;
     mozilla::Maybe<IonRegisterAllocator> forcedRegisterAllocator;
 
     // Spectre mitigation flags. Each mitigation has its own flag in order to
     // measure the effectiveness of each mitigation with various proof of
     // concept.
     bool spectreIndexMasking;
     bool spectreObjectMitigationsBarriers;
+    bool spectreObjectMitigationsMisc;
     bool spectreStringMitigations;
     bool spectreValueMasking;
     bool spectreJitToCxxCalls;
 
     // The options below affect the rest of the VM, and not just the JIT.
     bool disableUnboxedObjects;
 
     DefaultJitOptions();
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -3495,19 +3495,29 @@ LIRGenerator::visitStoreUnboxedString(MS
 
     LInstruction* lir = new(alloc()) LStoreUnboxedPointer(elements, index, value);
     add(lir, ins);
 }
 
 void
 LIRGenerator::visitConvertUnboxedObjectToNative(MConvertUnboxedObjectToNative* ins)
 {
-    LInstruction* check = new(alloc()) LConvertUnboxedObjectToNative(useRegister(ins->object()));
-    add(check, ins);
-    assignSafepoint(check, ins);
+    MOZ_ASSERT(ins->object()->type() == MIRType::Object);
+
+    if (JitOptions.spectreObjectMitigationsMisc) {
+        auto* lir = new(alloc()) LConvertUnboxedObjectToNative(useRegisterAtStart(ins->object()),
+                                                               temp());
+        defineReuseInput(lir, ins, 0);
+        assignSafepoint(lir, ins);
+    } else {
+        auto* lir = new(alloc()) LConvertUnboxedObjectToNative(useRegister(ins->object()),
+                                                               LDefinition::BogusTemp());
+        add(lir, ins);
+        assignSafepoint(lir, ins);
+    }
 }
 
 void
 LIRGenerator::visitEffectiveAddress(MEffectiveAddress* ins)
 {
     define(new(alloc()) LEffectiveAddress(useRegister(ins->base()), useRegister(ins->index())), ins);
 }
 
@@ -3928,45 +3938,46 @@ LIRGenerator::visitGetPropertyCache(MGet
 
 void
 LIRGenerator::visitGetPropertyPolymorphic(MGetPropertyPolymorphic* ins)
 {
     MOZ_ASSERT(ins->object()->type() == MIRType::Object);
 
     if (ins->type() == MIRType::Value) {
         LGetPropertyPolymorphicV* lir =
-            new(alloc()) LGetPropertyPolymorphicV(useRegister(ins->object()));
+            new(alloc()) LGetPropertyPolymorphicV(useRegister(ins->object()), temp());
         assignSnapshot(lir, Bailout_ShapeGuard);
         defineBox(lir, ins);
     } else {
-        LDefinition maybeTemp = (ins->type() == MIRType::Double) ? temp() : LDefinition::BogusTemp();
+        LDefinition maybeTemp2 =
+            (ins->type() == MIRType::Double) ? temp() : LDefinition::BogusTemp();
         LGetPropertyPolymorphicT* lir =
-            new(alloc()) LGetPropertyPolymorphicT(useRegister(ins->object()), maybeTemp);
+            new(alloc()) LGetPropertyPolymorphicT(useRegister(ins->object()), temp(), maybeTemp2);
         assignSnapshot(lir, Bailout_ShapeGuard);
         define(lir, ins);
     }
 }
 
 void
 LIRGenerator::visitSetPropertyPolymorphic(MSetPropertyPolymorphic* ins)
 {
     MOZ_ASSERT(ins->object()->type() == MIRType::Object);
 
     if (ins->value()->type() == MIRType::Value) {
         LSetPropertyPolymorphicV* lir =
             new(alloc()) LSetPropertyPolymorphicV(useRegister(ins->object()),
                                                   useBox(ins->value()),
-                                                  temp());
+                                                  temp(), temp());
         assignSnapshot(lir, Bailout_ShapeGuard);
         add(lir, ins);
     } else {
         LAllocation value = useRegisterOrConstant(ins->value());
         LSetPropertyPolymorphicT* lir =
             new(alloc()) LSetPropertyPolymorphicT(useRegister(ins->object()), value,
-                                                  ins->value()->type(), temp());
+                                                  ins->value()->type(), temp(), temp());
         assignSnapshot(lir, Bailout_ShapeGuard);
         add(lir, ins);
     }
 }
 
 void
 LIRGenerator::visitBindNameCache(MBindNameCache* ins)
 {
@@ -3998,39 +4009,45 @@ LIRGenerator::visitGuardObjectIdentity(M
     redefine(ins, ins->object());
 }
 
 void
 LIRGenerator::visitGuardShape(MGuardShape* ins)
 {
     MOZ_ASSERT(ins->object()->type() == MIRType::Object);
 
-    LGuardShape* guard = new(alloc()) LGuardShape(useRegisterAtStart(ins->object()));
-    assignSnapshot(guard, ins->bailoutKind());
-    add(guard, ins);
-    redefine(ins, ins->object());
+    if (JitOptions.spectreObjectMitigationsMisc) {
+        auto* lir = new(alloc()) LGuardShape(useRegisterAtStart(ins->object()), temp());
+        assignSnapshot(lir, ins->bailoutKind());
+        defineReuseInput(lir, ins, 0);
+    } else {
+        auto* lir = new(alloc()) LGuardShape(useRegister(ins->object()),
+                                             LDefinition::BogusTemp());
+        assignSnapshot(lir, ins->bailoutKind());
+        add(lir, ins);
+        redefine(ins, ins->object());
+    }
 }
 
 void
 LIRGenerator::visitGuardObjectGroup(MGuardObjectGroup* ins)
 {
     MOZ_ASSERT(ins->object()->type() == MIRType::Object);
 
-    LGuardObjectGroup* guard = new(alloc()) LGuardObjectGroup(useRegisterAtStart(ins->object()));
-    assignSnapshot(guard, ins->bailoutKind());
-    add(guard, ins);
-    redefine(ins, ins->object());
-}
-
-void
-LIRGenerator::visitGuardClass(MGuardClass* ins)
-{
-    LGuardClass* guard = new(alloc()) LGuardClass(useRegister(ins->object()), temp());
-    assignSnapshot(guard, Bailout_ObjectIdentityOrTypeGuard);
-    add(guard, ins);
+    if (JitOptions.spectreObjectMitigationsMisc) {
+        auto* lir = new(alloc()) LGuardObjectGroup(useRegisterAtStart(ins->object()), temp());
+        assignSnapshot(lir, ins->bailoutKind());
+        defineReuseInput(lir, ins, 0);
+    } else {
+        auto* lir = new(alloc()) LGuardObjectGroup(useRegister(ins->object()),
+                                                   LDefinition::BogusTemp());
+        assignSnapshot(lir, ins->bailoutKind());
+        add(lir, ins);
+        redefine(ins, ins->object());
+    }
 }
 
 void
 LIRGenerator::visitGuardObject(MGuardObject* ins)
 {
     // The type policy does all the work, so at this point the input
     // is guaranteed to be an object.
     MOZ_ASSERT(ins->input()->type() == MIRType::Object);
@@ -4064,21 +4081,28 @@ LIRGenerator::visitPolyInlineGuard(MPoly
 }
 
 void
 LIRGenerator::visitGuardReceiverPolymorphic(MGuardReceiverPolymorphic* ins)
 {
     MOZ_ASSERT(ins->object()->type() == MIRType::Object);
     MOZ_ASSERT(ins->type() == MIRType::Object);
 
-    LGuardReceiverPolymorphic* guard =
-        new(alloc()) LGuardReceiverPolymorphic(useRegister(ins->object()), temp());
-    assignSnapshot(guard, Bailout_ShapeGuard);
-    add(guard, ins);
-    redefine(ins, ins->object());
+    if (JitOptions.spectreObjectMitigationsMisc) {
+        auto* lir = new(alloc()) LGuardReceiverPolymorphic(useRegisterAtStart(ins->object()),
+                                                           temp(), temp());
+        assignSnapshot(lir, Bailout_ShapeGuard);
+        defineReuseInput(lir, ins, 0);
+    } else {
+        auto* lir = new(alloc()) LGuardReceiverPolymorphic(useRegister(ins->object()),
+                                                           temp(), temp());
+        assignSnapshot(lir, Bailout_ShapeGuard);
+        add(lir, ins);
+        redefine(ins, ins->object());
+    }
 }
 
 void
 LIRGenerator::visitGuardUnboxedExpando(MGuardUnboxedExpando* ins)
 {
     LGuardUnboxedExpando* guard =
         new(alloc()) LGuardUnboxedExpando(useRegister(ins->object()));
     assignSnapshot(guard, ins->bailoutKind());
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -261,17 +261,16 @@ class LIRGenerator : public LIRGenerator
     void visitGetPropertyCache(MGetPropertyCache* ins) override;
     void visitGetPropertyPolymorphic(MGetPropertyPolymorphic* ins) override;
     void visitSetPropertyPolymorphic(MSetPropertyPolymorphic* ins) override;
     void visitBindNameCache(MBindNameCache* ins) override;
     void visitCallBindVar(MCallBindVar* ins) override;
     void visitGuardObjectIdentity(MGuardObjectIdentity* ins) override;
     void visitGuardShape(MGuardShape* ins) override;
     void visitGuardObjectGroup(MGuardObjectGroup* ins) override;
-    void visitGuardClass(MGuardClass* ins) override;
     void visitGuardObject(MGuardObject* ins) override;
     void visitGuardString(MGuardString* ins) override;
     void visitGuardReceiverPolymorphic(MGuardReceiverPolymorphic* ins) override;
     void visitGuardUnboxedExpando(MGuardUnboxedExpando* ins) override;
     void visitLoadUnboxedExpando(MLoadUnboxedExpando* ins) override;
     void visitPolyInlineGuard(MPolyInlineGuard* ins) override;
     void visitAssertRange(MAssertRange* ins) override;
     void visitCallGetProperty(MCallGetProperty* ins) override;
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -11757,53 +11757,16 @@ class MGuardObjectIdentity
             return false;
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const override {
         return AliasSet::Load(AliasSet::ObjectFields);
     }
 };
 
-// Guard on an object's class.
-class MGuardClass
-  : public MUnaryInstruction,
-    public SingleObjectPolicy::Data
-{
-    const Class* class_;
-
-    MGuardClass(MDefinition* obj, const Class* clasp)
-      : MUnaryInstruction(classOpcode, obj),
-        class_(clasp)
-    {
-        setGuard();
-        setMovable();
-    }
-
-  public:
-    INSTRUCTION_HEADER(GuardClass)
-    TRIVIAL_NEW_WRAPPERS
-    NAMED_OPERANDS((0, object))
-
-    const Class* getClass() const {
-        return class_;
-    }
-    bool congruentTo(const MDefinition* ins) const override {
-        if (!ins->isGuardClass())
-            return false;
-        if (getClass() != ins->toGuardClass()->getClass())
-            return false;
-        return congruentIfOperandsEqual(ins);
-    }
-    AliasSet getAliasSet() const override {
-        return AliasSet::Load(AliasSet::ObjectFields);
-    }
-
-    ALLOW_CLONE(MGuardClass)
-};
-
 // Guard on the presence or absence of an unboxed object's expando.
 class MGuardUnboxedExpando
   : public MUnaryInstruction,
     public SingleObjectPolicy::Data
 {
     bool requireExpando_;
     BailoutKind bailoutKind_;
 
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -193,17 +193,16 @@ namespace jit {
     _(GetPropertyPolymorphic)                                               \
     _(SetPropertyPolymorphic)                                               \
     _(BindNameCache)                                                        \
     _(CallBindVar)                                                          \
     _(GuardShape)                                                           \
     _(GuardReceiverPolymorphic)                                             \
     _(GuardObjectGroup)                                                     \
     _(GuardObjectIdentity)                                                  \
-    _(GuardClass)                                                           \
     _(GuardUnboxedExpando)                                                  \
     _(LoadUnboxedExpando)                                                   \
     _(ArrayLength)                                                          \
     _(SetArrayLength)                                                       \
     _(GetNextEntryForIterator)                                              \
     _(TypedArrayLength)                                                     \
     _(TypedArrayElements)                                                   \
     _(SetDisjointTypedElements)                                             \
--- a/js/src/jit/MacroAssembler-inl.h
+++ b/js/src/jit/MacroAssembler-inl.h
@@ -487,54 +487,170 @@ MacroAssembler::branchFunctionKind(Condi
     int32_t mask = IMM32_16ADJ(JSFunction::FUNCTION_KIND_MASK);
     int32_t bit = IMM32_16ADJ(kind << JSFunction::FUNCTION_KIND_SHIFT);
     load32(address, scratch);
     and32(Imm32(mask), scratch);
     branch32(cond, scratch, Imm32(bit), label);
 }
 
 void
-MacroAssembler::branchTestObjClass(Condition cond, Register obj, Register scratch,
-                                   const js::Class* clasp, Label* label)
+MacroAssembler::branchTestObjClass(Condition cond, Register obj, const js::Class* clasp,
+                                   Register scratch, Register spectreRegToZero, Label* label)
+{
+    MOZ_ASSERT(obj != scratch);
+    MOZ_ASSERT(scratch != spectreRegToZero);
+
+    loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
+    branchPtr(cond, Address(scratch, ObjectGroup::offsetOfClasp()), ImmPtr(clasp), label);
+
+    if (JitOptions.spectreObjectMitigationsMisc)
+        spectreZeroRegister(cond, scratch, spectreRegToZero);
+}
+
+void
+MacroAssembler::branchTestObjClassNoSpectreMitigations(Condition cond, Register obj,
+                                                       const js::Class* clasp,
+                                                       Register scratch, Label* label)
 {
     loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
     branchPtr(cond, Address(scratch, ObjectGroup::offsetOfClasp()), ImmPtr(clasp), label);
 }
 
 void
-MacroAssembler::branchTestObjClass(Condition cond, Register obj, Register scratch,
-                                   const Address& clasp, Label* label)
+MacroAssembler::branchTestObjClass(Condition cond, Register obj, const Address& clasp,
+                                   Register scratch, Register spectreRegToZero, Label* label)
 {
+    MOZ_ASSERT(obj != scratch);
+    MOZ_ASSERT(scratch != spectreRegToZero);
+
+    loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
+    loadPtr(Address(scratch, ObjectGroup::offsetOfClasp()), scratch);
+    branchPtr(cond, clasp, scratch, label);
+
+    if (JitOptions.spectreObjectMitigationsMisc)
+        spectreZeroRegister(cond, scratch, spectreRegToZero);
+}
+
+void
+MacroAssembler::branchTestObjClassNoSpectreMitigations(Condition cond, Register obj,
+                                                       const Address& clasp, Register scratch,
+                                                       Label* label)
+{
+    MOZ_ASSERT(obj != scratch);
     loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
     loadPtr(Address(scratch, ObjectGroup::offsetOfClasp()), scratch);
     branchPtr(cond, clasp, scratch, label);
 }
 
 void
-MacroAssembler::branchTestObjShape(Condition cond, Register obj, const Shape* shape, Label* label)
+MacroAssembler::branchTestObjShape(Condition cond, Register obj, const Shape* shape, Register scratch,
+                                   Register spectreRegToZero, Label* label)
+{
+    MOZ_ASSERT(obj != scratch);
+    MOZ_ASSERT(spectreRegToZero != scratch);
+
+    if (JitOptions.spectreObjectMitigationsMisc)
+        move32(Imm32(0), scratch);
+
+    branchPtr(cond, Address(obj, ShapedObject::offsetOfShape()), ImmGCPtr(shape), label);
+
+    if (JitOptions.spectreObjectMitigationsMisc)
+        spectreMovePtr(cond, scratch, spectreRegToZero);
+}
+
+void
+MacroAssembler::branchTestObjShapeNoSpectreMitigations(Condition cond, Register obj,
+                                                       const Shape* shape, Label* label)
 {
     branchPtr(cond, Address(obj, ShapedObject::offsetOfShape()), ImmGCPtr(shape), label);
 }
 
 void
-MacroAssembler::branchTestObjShape(Condition cond, Register obj, Register shape, Label* label)
+MacroAssembler::branchTestObjShape(Condition cond, Register obj, Register shape, Register scratch,
+                                   Register spectreRegToZero, Label* label)
+{
+    MOZ_ASSERT(obj != scratch);
+    MOZ_ASSERT(obj != shape);
+    MOZ_ASSERT(spectreRegToZero != scratch);
+
+    if (JitOptions.spectreObjectMitigationsMisc)
+        move32(Imm32(0), scratch);
+
+    branchPtr(cond, Address(obj, ShapedObject::offsetOfShape()), shape, label);
+
+    if (JitOptions.spectreObjectMitigationsMisc)
+        spectreMovePtr(cond, scratch, spectreRegToZero);
+}
+
+void
+MacroAssembler::branchTestObjShapeNoSpectreMitigations(Condition cond, Register obj, Register shape,
+                                                       Label* label)
 {
     branchPtr(cond, Address(obj, ShapedObject::offsetOfShape()), shape, label);
 }
 
 void
+MacroAssembler::branchTestObjShapeUnsafe(Condition cond, Register obj, Register shape,
+                                         Label* label)
+{
+    branchTestObjShapeNoSpectreMitigations(cond, obj, shape, label);
+}
+
+void
 MacroAssembler::branchTestObjGroup(Condition cond, Register obj, const ObjectGroup* group,
-                                   Label* label)
+                                   Register scratch, Register spectreRegToZero, Label* label)
+{
+    MOZ_ASSERT(obj != scratch);
+    MOZ_ASSERT(spectreRegToZero != scratch);
+
+    if (JitOptions.spectreObjectMitigationsMisc)
+        move32(Imm32(0), scratch);
+
+    branchPtr(cond, Address(obj, JSObject::offsetOfGroup()), ImmGCPtr(group), label);
+
+    if (JitOptions.spectreObjectMitigationsMisc)
+        spectreMovePtr(cond, scratch, spectreRegToZero);
+}
+
+void
+MacroAssembler::branchTestObjGroupNoSpectreMitigations(Condition cond, Register obj,
+                                                       const ObjectGroup* group, Label* label)
 {
     branchPtr(cond, Address(obj, JSObject::offsetOfGroup()), ImmGCPtr(group), label);
 }
 
 void
-MacroAssembler::branchTestObjGroup(Condition cond, Register obj, Register group, Label* label)
+MacroAssembler::branchTestObjGroupUnsafe(Condition cond, Register obj, const ObjectGroup* group,
+                                         Label* label)
+{
+    branchTestObjGroupNoSpectreMitigations(cond, obj, group, label);
+}
+
+void
+MacroAssembler::branchTestObjGroup(Condition cond, Register obj, Register group, Register scratch,
+                                   Register spectreRegToZero, Label* label)
 {
+    MOZ_ASSERT(obj != scratch);
+    MOZ_ASSERT(obj != group);
+    MOZ_ASSERT(spectreRegToZero != scratch);
+
+    if (JitOptions.spectreObjectMitigationsMisc)
+        move32(Imm32(0), scratch);
+
+    branchPtr(cond, Address(obj, JSObject::offsetOfGroup()), group, label);
+
+    if (JitOptions.spectreObjectMitigationsMisc)
+        spectreMovePtr(cond, scratch, spectreRegToZero);
+}
+
+void
+MacroAssembler::branchTestObjGroupNoSpectreMitigations(Condition cond, Register obj, Register group,
+                                                       Label* label)
+{
+    MOZ_ASSERT(obj != group);
     branchPtr(cond, Address(obj, JSObject::offsetOfGroup()), group, label);
 }
 
 void
 MacroAssembler::branchTestClassIsProxy(bool proxy, Register clasp, Label* label)
 {
     branchTest32(proxy ? Assembler::NonZero : Assembler::Zero,
                  Address(clasp, Class::offsetOfFlags()),
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -3196,26 +3196,45 @@ MacroAssembler::branchIfNotInterpretedCo
     branchTest32(Assembler::Zero, scratch, Imm32(bits), label);
 
     // Check if the CONSTRUCTOR bit is set.
     bits = IMM32_16ADJ(JSFunction::CONSTRUCTOR);
     branchTest32(Assembler::Zero, scratch, Imm32(bits), label);
 }
 
 void
-MacroAssembler::branchTestObjGroup(Condition cond, Register obj, const Address& group,
-                                   Register scratch, Label* label)
+MacroAssembler::branchTestObjGroupNoSpectreMitigations(Condition cond, Register obj,
+                                                       const Address& group, Register scratch,
+                                                       Label* label)
 {
     // Note: obj and scratch registers may alias.
+    MOZ_ASSERT(group.base != scratch);
+    MOZ_ASSERT(group.base != obj);
 
     loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
     branchPtr(cond, group, scratch, label);
 }
 
 void
+MacroAssembler::branchTestObjGroup(Condition cond, Register obj, const Address& group,
+                                   Register scratch, Register spectreRegToZero, Label* label)
+{
+    // Note: obj and scratch registers may alias.
+    MOZ_ASSERT(group.base != scratch);
+    MOZ_ASSERT(group.base != obj);
+    MOZ_ASSERT(scratch != spectreRegToZero);
+
+    loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
+    branchPtr(cond, group, scratch, label);
+
+    if (JitOptions.spectreObjectMitigationsMisc)
+        spectreZeroRegister(cond, scratch, spectreRegToZero);
+}
+
+void
 MacroAssembler::branchTestObjCompartment(Condition cond, Register obj, const Address& compartment,
                                          Register scratch, Label* label)
 {
     MOZ_ASSERT(obj != scratch);
     loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
     loadPtr(Address(scratch, ObjectGroup::offsetOfCompartment()), scratch);
     branchPtr(cond, compartment, scratch, label);
 }
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -1172,28 +1172,62 @@ class MacroAssembler : public MacroAssem
     inline void branchFunctionKind(Condition cond, JSFunction::FunctionKind kind, Register fun,
                                    Register scratch, Label* label);
 
     void branchIfNotInterpretedConstructor(Register fun, Register scratch, Label* label);
 
     inline void branchIfObjectEmulatesUndefined(Register objReg, Register scratch, Label* slowCheck,
                                                 Label* label);
 
-    inline void branchTestObjClass(Condition cond, Register obj, Register scratch,
-                                   const js::Class* clasp, Label* label);
-    inline void branchTestObjClass(Condition cond, Register obj, Register scratch,
-                                   const Address& clasp, Label* label);
-    inline void branchTestObjShape(Condition cond, Register obj, const Shape* shape, Label* label);
-    inline void branchTestObjShape(Condition cond, Register obj, Register shape, Label* label);
+    // For all methods below: spectreRegToZero is a register that will be zeroed
+    // on speculatively executed code paths (when the branch should be taken but
+    // branch prediction speculates it isn't). Usually this will be the object
+    // register but the caller may pass a different register.
+
+    inline void branchTestObjClass(Condition cond, Register obj, const js::Class* clasp,
+                                   Register scratch, Register spectreRegToZero, Label* label);
+    inline void branchTestObjClassNoSpectreMitigations(Condition cond, Register obj,
+                                                       const js::Class* clasp, Register scratch,
+                                                       Label* label);
+
+    inline void branchTestObjClass(Condition cond, Register obj, const Address& clasp,
+                                   Register scratch, Register spectreRegToZero, Label* label);
+    inline void branchTestObjClassNoSpectreMitigations(Condition cond, Register obj,
+                                                       const Address& clasp, Register scratch,
+                                                       Label* label);
+
+    inline void branchTestObjShape(Condition cond, Register obj, const Shape* shape,
+                                   Register scratch, Register spectreRegToZero, Label* label);
+    inline void branchTestObjShapeNoSpectreMitigations(Condition cond, Register obj,
+                                                       const Shape* shape, Label* label);
+
+    inline void branchTestObjShape(Condition cond, Register obj, Register shape, Register scratch,
+                                   Register spectreRegToZero, Label* label);
+    inline void branchTestObjShapeNoSpectreMitigations(Condition cond, Register obj,
+                                                       Register shape, Label* label);
+
     inline void branchTestObjGroup(Condition cond, Register obj, const ObjectGroup* group,
-                                   Label* label);
-    inline void branchTestObjGroup(Condition cond, Register obj, Register group, Label* label);
+                                   Register scratch, Register spectreRegToZero, Label* label);
+    inline void branchTestObjGroupNoSpectreMitigations(Condition cond, Register obj,
+                                                       const ObjectGroup* group, Label* label);
+
+    inline void branchTestObjGroup(Condition cond, Register obj, Register group, Register scratch,
+                                   Register spectreRegToZero, Label* label);
+    inline void branchTestObjGroupNoSpectreMitigations(Condition cond, Register obj,
+                                                       Register group, Label* label);
 
     void branchTestObjGroup(Condition cond, Register obj, const Address& group, Register scratch,
-                            Label* label);
+                            Register spectreRegToZero, Label* label);
+    void branchTestObjGroupNoSpectreMitigations(Condition cond, Register obj, const Address& group,
+                                                Register scratch, Label* label);
+
+    // TODO: audit/fix callers to be Spectre safe.
+    inline void branchTestObjShapeUnsafe(Condition cond, Register obj, Register shape, Label* label);
+    inline void branchTestObjGroupUnsafe(Condition cond, Register obj, const ObjectGroup* group,
+                                         Label* label);
 
     void branchTestObjCompartment(Condition cond, Register obj, const Address& compartment,
                                   Register scratch, Label* label);
     void branchTestObjCompartment(Condition cond, Register obj, const JSCompartment* compartment,
                                   Register scratch, Label* label);
     void branchIfObjGroupHasNoAddendum(Register obj, Register scratch, Label* label);
     void branchIfPretenuredGroup(const ObjectGroup* group, Register scratch, Label* label);
 
@@ -1390,16 +1424,20 @@ class MacroAssembler : public MacroAssem
     inline void test32MovePtr(Condition cond, const Address& addr, Imm32 mask, Register src,
                               Register dest)
         DEFINED_ON(arm, arm64, mips_shared, x86, x64);
 
     // Conditional move for Spectre mitigations.
     inline void spectreMovePtr(Condition cond, Register src, Register dest)
         DEFINED_ON(arm, arm64, mips_shared, x86, x64);
 
+    // Zeroes dest if the condition is true.
+    inline void spectreZeroRegister(Condition cond, Register scratch, Register dest)
+        DEFINED_ON(arm, arm64, mips_shared, x86_shared);
+
     // Performs a bounds check and zeroes the index register if out-of-bounds
     // (to mitigate Spectre).
     inline void boundsCheck32ForLoad(Register index, Register length, Register scratch,
                                      Label* failure)
         DEFINED_ON(arm, arm64, mips_shared, x86_shared);
     inline void boundsCheck32ForLoad(Register index, const Address& length, Register scratch,
                                      Label* failure)
         DEFINED_ON(arm, arm64, mips_shared, x86_shared);
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -2534,21 +2534,23 @@ ICTypeMonitor_SingleObject::Compiler::ge
 
 bool
 ICTypeMonitor_ObjectGroup::Compiler::generateStubCode(MacroAssembler& masm)
 {
     Label failure;
     masm.branchTestObject(Assembler::NotEqual, R0, &failure);
     MaybeWorkAroundAmdBug(masm);
 
-    // Guard on the object's ObjectGroup.
+    // Guard on the object's ObjectGroup. No Spectre mitigations are needed
+    // here: we're just recording type information for Ion compilation and
+    // it's safe to speculatively return.
     Register obj = masm.extractObject(R0, ExtractTemp0);
     Address expectedGroup(ICStubReg, ICTypeMonitor_ObjectGroup::offsetOfGroup());
-    masm.branchTestObjGroup(Assembler::NotEqual, obj, expectedGroup, R1.scratchReg(),
-                            &failure);
+    masm.branchTestObjGroupNoSpectreMitigations(Assembler::NotEqual, obj, expectedGroup,
+                                                R1.scratchReg(), &failure);
     MaybeWorkAroundAmdBug(masm);
 
     EmitReturnFromIC(masm);
     MaybeWorkAroundAmdBug(masm);
 
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
--- a/js/src/jit/arm/MacroAssembler-arm-inl.h
+++ b/js/src/jit/arm/MacroAssembler-arm-inl.h
@@ -2183,16 +2183,22 @@ MacroAssembler::test32MovePtr(Condition 
 
 void
 MacroAssembler::spectreMovePtr(Condition cond, Register src, Register dest)
 {
     ma_mov(src, dest, LeaveCC, cond);
 }
 
 void
+MacroAssembler::spectreZeroRegister(Condition cond, Register, Register dest)
+{
+    ma_mov(Imm32(0), dest, cond);
+}
+
+void
 MacroAssembler::boundsCheck32ForLoad(Register index, Register length, Register scratch,
                                      Label* failure)
 {
     MOZ_ASSERT(index != length);
     MOZ_ASSERT(length != scratch);
     MOZ_ASSERT(index != scratch);
 
     if (JitOptions.spectreIndexMasking)
--- a/js/src/jit/arm64/MacroAssembler-arm64-inl.h
+++ b/js/src/jit/arm64/MacroAssembler-arm64-inl.h
@@ -1862,16 +1862,23 @@ MacroAssembler::test32MovePtr(Condition 
 
 void
 MacroAssembler::spectreMovePtr(Condition cond, Register src, Register dest)
 {
     Csel(ARMRegister(dest, 64), ARMRegister(src, 64), ARMRegister(dest, 64), cond);
 }
 
 void
+MacroAssembler::spectreZeroRegister(Condition cond, Register, Register dest)
+{
+    Csel(ARMRegister(dest, 64), ARMRegister(dest, 64), vixl::xzr,
+         Assembler::InvertCondition(cond));
+}
+
+void
 MacroAssembler::boundsCheck32ForLoad(Register index, Register length, Register scratch,
                                      Label* failure)
 {
     MOZ_ASSERT(index != length);
     MOZ_ASSERT(length != scratch);
     MOZ_ASSERT(index != scratch);
 
     branch32(Assembler::BelowOrEqual, length, index, failure);
--- a/js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h
+++ b/js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h
@@ -1032,16 +1032,22 @@ MacroAssembler::boundsCheck32ForLoad(Reg
 }
 
 void
 MacroAssembler::spectreMovePtr(Condition cond, Register src, Register dest)
 {
     MOZ_CRASH();
 }
 
+void
+MacroAssembler::spectreZeroRegister(Condition cond, Register scratch, Register dest)
+{
+    MOZ_CRASH();
+}
+
 // ========================================================================
 // Memory access primitives.
 void
 MacroAssembler::storeFloat32x3(FloatRegister src, const Address& dest)
 {
     MOZ_CRASH("NYI");
 }
 void
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -6586,27 +6586,30 @@ class LStoreUnboxedPointer : public LIns
     }
     const LAllocation* value() {
         return getOperand(2);
     }
 };
 
 // If necessary, convert an unboxed object in a particular group to its native
 // representation.
-class LConvertUnboxedObjectToNative : public LInstructionHelper<0, 1, 0>
+class LConvertUnboxedObjectToNative : public LInstructionHelper<1, 1, 1>
 {
   public:
     LIR_HEADER(ConvertUnboxedObjectToNative)
 
-    explicit LConvertUnboxedObjectToNative(const LAllocation& object)
+    LConvertUnboxedObjectToNative(const LAllocation& object, const LDefinition& temp)
       : LInstructionHelper(classOpcode)
     {
         setOperand(0, object);
-    }
-
+        setTemp(0, temp);
+    }
+    const LDefinition* temp() {
+        return getTemp(0);
+    }
     MConvertUnboxedObjectToNative* mir() {
         return mir_->toConvertUnboxedObjectToNative();
     }
 };
 
 class LArrayPopShiftV : public LInstructionHelper<BOX_PIECES, 1, 2>
 {
   public:
@@ -7485,45 +7488,22 @@ class LGetPropertyCacheT : public LInstr
     }
     const LDefinition* temp() {
         return getTemp(0);
     }
 };
 
 // Emit code to load a boxed value from an object's slots if its shape matches
 // one of the shapes observed by the baseline IC, else bails out.
-class LGetPropertyPolymorphicV : public LInstructionHelper<BOX_PIECES, 1, 0>
+class LGetPropertyPolymorphicV : public LInstructionHelper<BOX_PIECES, 1, 1>
 {
   public:
     LIR_HEADER(GetPropertyPolymorphicV)
 
-    explicit LGetPropertyPolymorphicV(const LAllocation& obj)
-      : LInstructionHelper(classOpcode)
-    {
-        setOperand(0, obj);
-    }
-    const LAllocation* obj() {
-        return getOperand(0);
-    }
-    const MGetPropertyPolymorphic* mir() const {
-        return mir_->toGetPropertyPolymorphic();
-    }
-    const char* extraName() const {
-        return PropertyNameToExtraName(mir()->name());
-    }
-};
-
-// Emit code to load a typed value from an object's slots if its shape matches
-// one of the shapes observed by the baseline IC, else bails out.
-class LGetPropertyPolymorphicT : public LInstructionHelper<1, 1, 1>
-{
-  public:
-    LIR_HEADER(GetPropertyPolymorphicT)
-
-    LGetPropertyPolymorphicT(const LAllocation& obj, const LDefinition& temp)
+    LGetPropertyPolymorphicV(const LAllocation& obj, const LDefinition& temp)
       : LInstructionHelper(classOpcode)
     {
         setOperand(0, obj);
         setTemp(0, temp);
     }
     const LAllocation* obj() {
         return getOperand(0);
     }
@@ -7533,72 +7513,112 @@ class LGetPropertyPolymorphicT : public 
     const MGetPropertyPolymorphic* mir() const {
         return mir_->toGetPropertyPolymorphic();
     }
     const char* extraName() const {
         return PropertyNameToExtraName(mir()->name());
     }
 };
 
+// Emit code to load a typed value from an object's slots if its shape matches
+// one of the shapes observed by the baseline IC, else bails out.
+class LGetPropertyPolymorphicT : public LInstructionHelper<1, 1, 2>
+{
+  public:
+    LIR_HEADER(GetPropertyPolymorphicT)
+
+    LGetPropertyPolymorphicT(const LAllocation& obj, const LDefinition& temp1,
+                             const LDefinition& temp2)
+      : LInstructionHelper(classOpcode)
+    {
+        setOperand(0, obj);
+        setTemp(0, temp1);
+        setTemp(1, temp2);
+    }
+    const LAllocation* obj() {
+        return getOperand(0);
+    }
+    const LDefinition* temp1() {
+        return getTemp(0);
+    }
+    const LDefinition* temp2() {
+        return getTemp(1);
+    }
+    const MGetPropertyPolymorphic* mir() const {
+        return mir_->toGetPropertyPolymorphic();
+    }
+    const char* extraName() const {
+        return PropertyNameToExtraName(mir()->name());
+    }
+};
+
 // Emit code to store a boxed value to an object's slots if its shape matches
 // one of the shapes observed by the baseline IC, else bails out.
-class LSetPropertyPolymorphicV : public LInstructionHelper<0, 1 + BOX_PIECES, 1>
+class LSetPropertyPolymorphicV : public LInstructionHelper<0, 1 + BOX_PIECES, 2>
 {
   public:
     LIR_HEADER(SetPropertyPolymorphicV)
 
     LSetPropertyPolymorphicV(const LAllocation& obj, const LBoxAllocation& value,
-                             const LDefinition& temp)
+                             const LDefinition& temp1, const LDefinition& temp2)
       : LInstructionHelper(classOpcode)
     {
         setOperand(0, obj);
         setBoxOperand(Value, value);
-        setTemp(0, temp);
+        setTemp(0, temp1);
+        setTemp(1, temp2);
     }
 
     static const size_t Value = 1;
 
     const LAllocation* obj() {
         return getOperand(0);
     }
-    const LDefinition* temp() {
-        return getTemp(0);
+    const LDefinition* temp1() {
+        return getTemp(0);
+    }
+    const LDefinition* temp2() {
+        return getTemp(1);
     }
     const MSetPropertyPolymorphic* mir() const {
         return mir_->toSetPropertyPolymorphic();
     }
 };
 
 // Emit code to store a typed value to an object's slots if its shape matches
 // one of the shapes observed by the baseline IC, else bails out.
-class LSetPropertyPolymorphicT : public LInstructionHelper<0, 2, 1>
+class LSetPropertyPolymorphicT : public LInstructionHelper<0, 2, 2>
 {
     MIRType valueType_;
 
   public:
     LIR_HEADER(SetPropertyPolymorphicT)
 
     LSetPropertyPolymorphicT(const LAllocation& obj, const LAllocation& value, MIRType valueType,
-                             const LDefinition& temp)
+                             const LDefinition& temp1, const LDefinition& temp2)
       : LInstructionHelper(classOpcode),
         valueType_(valueType)
     {
         setOperand(0, obj);
         setOperand(1, value);
-        setTemp(0, temp);
+        setTemp(0, temp1);
+        setTemp(1, temp2);
     }
 
     const LAllocation* obj() {
         return getOperand(0);
     }
     const LAllocation* value() {
         return getOperand(1);
     }
-    const LDefinition* temp() {
-        return getTemp(0);
+    const LDefinition* temp1() {
+        return getTemp(0);
+    }
+    const LDefinition* temp2() {
+        return getTemp(1);
     }
     MIRType valueType() const {
         return valueType_;
     }
     const MSetPropertyPolymorphic* mir() const {
         return mir_->toSetPropertyPolymorphic();
     }
     const char* extraName() const {
@@ -8381,32 +8401,37 @@ class LRest : public LCallInstructionHel
     const LAllocation* numActuals() {
         return getOperand(0);
     }
     MRest* mir() const {
         return mir_->toRest();
     }
 };
 
-class LGuardReceiverPolymorphic : public LInstructionHelper<0, 1, 1>
+class LGuardReceiverPolymorphic : public LInstructionHelper<1, 1, 2>
 {
   public:
     LIR_HEADER(GuardReceiverPolymorphic)
 
-    LGuardReceiverPolymorphic(const LAllocation& in, const LDefinition& temp)
+    LGuardReceiverPolymorphic(const LAllocation& in, const LDefinition& temp1,
+                              const LDefinition& temp2)
       : LInstructionHelper(classOpcode)
     {
         setOperand(0, in);
-        setTemp(0, temp);
+        setTemp(0, temp1);
+        setTemp(1, temp2);
     }
     const LAllocation* object() {
         return getOperand(0);
     }
-    const LDefinition* temp() {
-        return getTemp(0);
+    const LDefinition* temp1() {
+        return getTemp(0);
+    }
+    const LDefinition* temp2() {
+        return getTemp(1);
     }
     const MGuardReceiverPolymorphic* mir() const {
         return mir_->toGuardReceiverPolymorphic();
     }
 };
 
 class LGuardUnboxedExpando : public LInstructionHelper<0, 1, 0>
 {
@@ -8711,63 +8736,51 @@ class LGuardObjectIdentity : public LIns
     const LAllocation* expected() {
         return getOperand(1);
     }
     const MGuardObjectIdentity* mir() const {
         return mir_->toGuardObjectIdentity();
     }
 };
 
-class LGuardShape : public LInstructionHelper<0, 1, 0>
+class LGuardShape : public LInstructionHelper<1, 1, 1>
 {
   public:
     LIR_HEADER(GuardShape)
 
-    explicit LGuardShape(const LAllocation& in)
-      : LInstructionHelper(classOpcode)
-    {
-        setOperand(0, in);
-    }
-    const MGuardShape* mir() const {
-        return mir_->toGuardShape();
-    }
-};
-
-class LGuardObjectGroup : public LInstructionHelper<0, 1, 0>
-{
-  public:
-    LIR_HEADER(GuardObjectGroup)
-
-    explicit LGuardObjectGroup(const LAllocation& in)
-      : LInstructionHelper(classOpcode)
-    {
-        setOperand(0, in);
-    }
-    const MGuardObjectGroup* mir() const {
-        return mir_->toGuardObjectGroup();
-    }
-};
-
-// Guard against an object's class.
-class LGuardClass : public LInstructionHelper<0, 1, 1>
-{
-  public:
-    LIR_HEADER(GuardClass)
-
-    LGuardClass(const LAllocation& in, const LDefinition& temp)
+    LGuardShape(const LAllocation& in, const LDefinition& temp)
       : LInstructionHelper(classOpcode)
     {
         setOperand(0, in);
         setTemp(0, temp);
     }
-    const MGuardClass* mir() const {
-        return mir_->toGuardClass();
-    }
-    const LDefinition* tempInt() {
-        return getTemp(0);
+    const LDefinition* temp() {
+        return getTemp(0);
+    }
+    const MGuardShape* mir() const {
+        return mir_->toGuardShape();
+    }
+};
+
+class LGuardObjectGroup : public LInstructionHelper<1, 1, 1>
+{
+  public:
+    LIR_HEADER(GuardObjectGroup)
+
+    LGuardObjectGroup(const LAllocation& in, const LDefinition& temp)
+      : LInstructionHelper(classOpcode)
+    {
+        setOperand(0, in);
+        setTemp(0, temp);
+    }
+    const LDefinition* temp() {
+        return getTemp(0);
+    }
+    const MGuardObjectGroup* mir() const {
+        return mir_->toGuardObjectGroup();
     }
 };
 
 // Guard against the sharedness of a TypedArray's memory.
 class LGuardSharedTypedArray : public LInstructionHelper<0, 1, 1>
 {
   public:
     LIR_HEADER(GuardSharedTypedArray)
--- a/js/src/jit/shared/LOpcodes-shared.h
+++ b/js/src/jit/shared/LOpcodes-shared.h
@@ -260,17 +260,16 @@
     _(LoadSlotV)                    \
     _(LoadSlotT)                    \
     _(StoreSlotV)                   \
     _(StoreSlotT)                   \
     _(GuardShape)                   \
     _(GuardReceiverPolymorphic)     \
     _(GuardObjectGroup)             \
     _(GuardObjectIdentity)          \
-    _(GuardClass)                   \
     _(GuardUnboxedExpando)          \
     _(LoadUnboxedExpando)           \
     _(TypeBarrierV)                 \
     _(TypeBarrierO)                 \
     _(PostWriteBarrierO)            \
     _(PostWriteBarrierS)            \
     _(PostWriteBarrierV)            \
     _(PostWriteElementBarrierO)     \
--- a/js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h
+++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h
@@ -1106,16 +1106,24 @@ void
 MacroAssembler::cmp32Move32(Condition cond, Register lhs, const Address& rhs, Register src,
                             Register dest)
 {
     cmp32(lhs, Operand(rhs));
     cmovCCl(cond, src, dest);
 }
 
 void
+MacroAssembler::spectreZeroRegister(Condition cond, Register scratch, Register dest)
+{
+    // Note: use movl instead of move32/xorl to ensure flags are not clobbered.
+    movl(Imm32(0), scratch);
+    spectreMovePtr(cond, scratch, dest);
+}
+
+void
 MacroAssembler::boundsCheck32ForLoad(Register index, Register length, Register scratch,
                                      Label* failure)
 {
     MOZ_ASSERT(index != length);
     MOZ_ASSERT(length != scratch);
     MOZ_ASSERT(index != scratch);
 
     if (JitOptions.spectreIndexMasking)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -7276,16 +7276,19 @@ JS_SetGlobalJitCompilerOption(JSContext*
         jit::JitOptions.simulatorAlwaysInterrupt = !!value;
         break;
       case JSJITCOMPILER_SPECTRE_INDEX_MASKING:
         jit::JitOptions.spectreIndexMasking = !!value;
         break;
       case JSJITCOMPILER_SPECTRE_OBJECT_MITIGATIONS_BARRIERS:
         jit::JitOptions.spectreObjectMitigationsBarriers = !!value;
         break;
+      case JSJITCOMPILER_SPECTRE_OBJECT_MITIGATIONS_MISC:
+        jit::JitOptions.spectreObjectMitigationsMisc = !!value;
+        break;
       case JSJITCOMPILER_SPECTRE_STRING_MITIGATIONS:
         jit::JitOptions.spectreStringMitigations = !!value;
         break;
       case JSJITCOMPILER_SPECTRE_VALUE_MASKING:
         jit::JitOptions.spectreValueMasking = !!value;
         break;
       case JSJITCOMPILER_SPECTRE_JIT_TO_CXX_CALLS:
         jit::JitOptions.spectreJitToCxxCalls = !!value;
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -5887,16 +5887,17 @@ JS_SetOffthreadIonCompilationEnabled(JSC
     Register(ION_CHECK_RANGE_ANALYSIS, "ion.check-range-analysis")          \
     Register(BASELINE_ENABLE, "baseline.enable")                            \
     Register(OFFTHREAD_COMPILATION_ENABLE, "offthread-compilation.enable")  \
     Register(FULL_DEBUG_CHECKS, "jit.full-debug-checks")                    \
     Register(JUMP_THRESHOLD, "jump-threshold")                              \
     Register(SIMULATOR_ALWAYS_INTERRUPT, "simulator.always-interrupt")      \
     Register(SPECTRE_INDEX_MASKING, "spectre.index-masking")                \
     Register(SPECTRE_OBJECT_MITIGATIONS_BARRIERS, "spectre.object-mitigations.barriers") \
+    Register(SPECTRE_OBJECT_MITIGATIONS_MISC, "spectre.object-mitigations.misc") \
     Register(SPECTRE_STRING_MITIGATIONS, "spectre.string-mitigations")      \
     Register(SPECTRE_VALUE_MASKING, "spectre.value-masking")                \
     Register(SPECTRE_JIT_TO_CXX_CALLS, "spectre.jit-to-C++-calls")          \
     Register(ASMJS_ATOMICS_ENABLE, "asmjs.atomics.enable")                  \
     Register(WASM_FOLD_OFFSETS, "wasm.fold-offsets")                        \
     Register(WASM_DELAY_TIER2, "wasm.delay-tier2")
 
 typedef enum JSJitCompilerOption {
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -8594,22 +8594,24 @@ SetContextOptions(JSContext* cx, const O
         else
             return OptionFailure("cache-ir-stubs", str);
     }
 
     if (const char* str = op.getStringOption("spectre-mitigations")) {
         if (strcmp(str, "on") == 0) {
             jit::JitOptions.spectreIndexMasking = true;
             jit::JitOptions.spectreObjectMitigationsBarriers = true;
+            jit::JitOptions.spectreObjectMitigationsMisc = true;
             jit::JitOptions.spectreStringMitigations = true;
             jit::JitOptions.spectreValueMasking = true;
             jit::JitOptions.spectreJitToCxxCalls = true;
         } else if (strcmp(str, "off") == 0) {
             jit::JitOptions.spectreIndexMasking = false;
             jit::JitOptions.spectreObjectMitigationsBarriers = false;
+            jit::JitOptions.spectreObjectMitigationsMisc = false;
             jit::JitOptions.spectreStringMitigations = false;
             jit::JitOptions.spectreValueMasking = false;
             jit::JitOptions.spectreJitToCxxCalls = false;
         } else {
             return OptionFailure("spectre-mitigations", str);
         }
     }
 
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -533,38 +533,43 @@ UnboxedLayout::makeNativeGroup(JSContext
 
     nativeGroup->setOriginalUnboxedGroup(group);
 
     group->markStateChange(cx);
 
     return true;
 }
 
-/* static */ bool
+/* static */ NativeObject*
 UnboxedPlainObject::convertToNative(JSContext* cx, JSObject* obj)
 {
+    // This function returns the original object (instead of bool) to make sure
+    // Ion's LConvertUnboxedObjectToNative works correctly. If we return bool
+    // and use defineReuseInput, the object register is not preserved across the
+    // call.
+
     const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
     UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando();
 
     if (!layout.nativeGroup()) {
         if (!UnboxedLayout::makeNativeGroup(cx, obj->group()))
-            return false;
+            return nullptr;
 
         // makeNativeGroup can reentrantly invoke this method.
         if (obj->is<PlainObject>())
-            return true;
+            return &obj->as<PlainObject>();
     }
 
     AutoValueVector values(cx);
     for (size_t i = 0; i < layout.properties().length(); i++) {
         // We might be reading properties off the object which have not been
         // initialized yet. Make sure any double values we read here are
         // canonicalized.
         if (!values.append(obj->as<UnboxedPlainObject>().getValue(layout.properties()[i], true)))
-            return false;
+            return nullptr;
     }
 
     // We are eliminating the expando edge with the conversion, so trigger a
     // pre barrier.
     JSObject::writeBarrierPre(expando);
 
     // Additionally trigger a post barrier on the expando itself. Whole cell
     // store buffer entries can be added on the original unboxed object for
@@ -574,52 +579,53 @@ UnboxedPlainObject::convertToNative(JSCo
         cx->zone()->group()->storeBuffer().putWholeCell(expando);
 
     obj->setGroup(layout.nativeGroup());
     obj->as<PlainObject>().setLastPropertyMakeNative(cx, layout.nativeShape());
 
     for (size_t i = 0; i < values.length(); i++)
         obj->as<PlainObject>().initSlotUnchecked(i, values[i]);
 
-    if (expando) {
-        // Add properties from the expando object to the object, in order.
-        // Suppress GC here, so that callers don't need to worry about this
-        // method collecting. The stuff below can only fail due to OOM, in
-        // which case the object will not have been completely filled back in.
-        gc::AutoSuppressGC suppress(cx);
+    if (!expando)
+        return &obj->as<PlainObject>();
+
+    // Add properties from the expando object to the object, in order.
+    // Suppress GC here, so that callers don't need to worry about this
+    // method collecting. The stuff below can only fail due to OOM, in
+    // which case the object will not have been completely filled back in.
+    gc::AutoSuppressGC suppress(cx);
 
-        Vector<jsid> ids(cx);
-        for (Shape::Range<NoGC> r(expando->lastProperty()); !r.empty(); r.popFront()) {
-            if (!ids.append(r.front().propid()))
-                return false;
-        }
-        for (size_t i = 0; i < expando->getDenseInitializedLength(); i++) {
-            if (!expando->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE)) {
-                if (!ids.append(INT_TO_JSID(i)))
-                    return false;
-            }
-        }
-        ::Reverse(ids.begin(), ids.end());
-
-        RootedPlainObject nobj(cx, &obj->as<PlainObject>());
-        Rooted<UnboxedExpandoObject*> nexpando(cx, expando);
-        RootedId id(cx);
-        Rooted<PropertyDescriptor> desc(cx);
-        for (size_t i = 0; i < ids.length(); i++) {
-            id = ids[i];
-            if (!GetOwnPropertyDescriptor(cx, nexpando, id, &desc))
-                return false;
-            ObjectOpResult result;
-            if (!DefineProperty(cx, nobj, id, desc, result))
-                return false;
-            MOZ_ASSERT(result.ok());
+    Vector<jsid> ids(cx);
+    for (Shape::Range<NoGC> r(expando->lastProperty()); !r.empty(); r.popFront()) {
+        if (!ids.append(r.front().propid()))
+            return nullptr;
+    }
+    for (size_t i = 0; i < expando->getDenseInitializedLength(); i++) {
+        if (!expando->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE)) {
+            if (!ids.append(INT_TO_JSID(i)))
+                return nullptr;
         }
     }
+    ::Reverse(ids.begin(), ids.end());
 
-    return true;
+    RootedPlainObject nobj(cx, &obj->as<PlainObject>());
+    Rooted<UnboxedExpandoObject*> nexpando(cx, expando);
+    RootedId id(cx);
+    Rooted<PropertyDescriptor> desc(cx);
+    for (size_t i = 0; i < ids.length(); i++) {
+        id = ids[i];
+        if (!GetOwnPropertyDescriptor(cx, nexpando, id, &desc))
+            return nullptr;
+        ObjectOpResult result;
+        if (!DefineProperty(cx, nobj, id, desc, result))
+            return nullptr;
+        MOZ_ASSERT(result.ok());
+    }
+
+    return nobj;
 }
 
 /* static */ JS::Result<UnboxedObject*, JS::OOM&>
 UnboxedObject::createInternal(JSContext* cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
                               js::HandleObjectGroup group)
 {
     const js::Class* clasp = group->clasp();
     MOZ_ASSERT(clasp == &UnboxedPlainObject::class_);
--- a/js/src/vm/UnboxedObject.h
+++ b/js/src/vm/UnboxedObject.h
@@ -292,17 +292,17 @@ class UnboxedPlainObject : public Unboxe
 
     bool containsUnboxedOrExpandoProperty(JSContext* cx, jsid id) const;
 
     static UnboxedExpandoObject* ensureExpando(JSContext* cx, Handle<UnboxedPlainObject*> obj);
 
     bool setValue(JSContext* cx, const UnboxedLayout::Property& property, const Value& v);
     Value getValue(const UnboxedLayout::Property& property, bool maybeUninitialized = false);
 
-    static bool convertToNative(JSContext* cx, JSObject* obj);
+    static NativeObject* convertToNative(JSContext* cx, JSObject* obj);
     static UnboxedPlainObject* create(JSContext* cx, HandleObjectGroup group,
                                       NewObjectKind newKind);
     static JSObject* createWithProperties(JSContext* cx, HandleObjectGroup group,
                                           NewObjectKind newKind, IdValuePair* properties);
 
     void fillAfterConvert(JSContext* cx,
                           Handle<GCVector<Value>> values, size_t* valueCursor);
 
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -805,16 +805,18 @@ ReloadPrefsCallback(const char* pref, vo
 
     bool extraWarnings = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict");
 
     bool streams = Preferences::GetBool(JS_OPTIONS_DOT_STR "streams");
 
     bool spectreIndexMasking = Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.index_masking");
     bool spectreObjectMitigationsBarriers =
         Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.object_mitigations.barriers");
+    bool spectreObjectMitigationsMisc =
+        Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.object_mitigations.misc");
     bool spectreStringMitigations =
         Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.string_mitigations");
     bool spectreValueMasking = Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.value_masking");
     bool spectreJitToCxxCalls = Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.jit_to_C++_calls");
 
     sSharedMemoryEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "shared_memory");
 
 #ifdef DEBUG
@@ -873,16 +875,18 @@ ReloadPrefsCallback(const char* pref, vo
                                   useIonEager ? 0 : ionThreshold);
 #ifdef DEBUG
     JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_FULL_DEBUG_CHECKS, fullJitDebugChecks);
 #endif
 
     JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_INDEX_MASKING, spectreIndexMasking);
     JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_OBJECT_MITIGATIONS_BARRIERS,
                                   spectreObjectMitigationsBarriers);
+    JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_OBJECT_MITIGATIONS_MISC,
+                                  spectreObjectMitigationsMisc);
     JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_STRING_MITIGATIONS,
                                   spectreStringMitigations);
     JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_VALUE_MASKING, spectreValueMasking);
     JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_JIT_TO_CXX_CALLS,
                                   spectreJitToCxxCalls);
 }
 
 XPCJSContext::~XPCJSContext()
--- a/layout/svg/SVGContextPaint.h
+++ b/layout/svg/SVGContextPaint.h
@@ -46,16 +46,17 @@ namespace mozilla {
  * duration of a function call.
  * XXX Note: SVGImageContext doesn't actually have a SVGContextPaint member yet,
  * but it will in a later patch in the patch series that added this comment.
  */
 class SVGContextPaint : public RefCounted<SVGContextPaint>
 {
 protected:
   typedef mozilla::gfx::DrawTarget DrawTarget;
+  typedef mozilla::gfx::Float Float;
   typedef mozilla::image::imgDrawingParams imgDrawingParams;
 
   SVGContextPaint()
     : mDashOffset(0.0f)
     , mStrokeWidth(0.0f)
   {}
 
 public:
@@ -91,25 +92,25 @@ public:
 
   static SVGContextPaint* GetContextPaint(nsIContent* aContent);
 
   // XXX This gets the geometry params from the gfxContext.  We should get that
   // information from the actual paint context!
   void InitStrokeGeometry(gfxContext *aContext,
                           float devUnitsPerSVGUnit);
 
-  const FallibleTArray<gfxFloat>& GetStrokeDashArray() const {
+  const FallibleTArray<Float>& GetStrokeDashArray() const {
     return mDashes;
   }
 
-  gfxFloat GetStrokeDashOffset() const {
+  Float GetStrokeDashOffset() const {
     return mDashOffset;
   }
 
-  gfxFloat GetStrokeWidth() const {
+  Float GetStrokeWidth() const {
     return mStrokeWidth;
   }
 
   virtual uint32_t Hash() const {
     MOZ_ASSERT_UNREACHABLE("Only VectorImage needs to hash, and that should "
                            "only be operating on our SVGEmbeddingContextPaint "
                            "subclass");
     return 0;
@@ -118,19 +119,19 @@ public:
   /**
    * Returns true if image context paint is allowed to be used in an image that
    * has the given URI, else returns false.
    */
   static bool IsAllowedForImageFromURI(nsIURI* aURI);
 
 private:
   // Member-vars are initialized in InitStrokeGeometry.
-  FallibleTArray<gfxFloat> mDashes;
-  MOZ_INIT_OUTSIDE_CTOR gfxFloat mDashOffset;
-  MOZ_INIT_OUTSIDE_CTOR gfxFloat mStrokeWidth;
+  FallibleTArray<Float> mDashes;
+  MOZ_INIT_OUTSIDE_CTOR Float mDashOffset;
+  MOZ_INIT_OUTSIDE_CTOR Float mStrokeWidth;
 };
 
 /**
  * RAII class used to temporarily set and remove an SVGContextPaint while a
  * piece of SVG is being painted.  The context paint is set on the SVG's owner
  * document, as expected by SVGContextPaint::GetContextPaint.  Any pre-existing
  * context paint is restored after this class removes the context paint that it
  * set.
--- a/layout/svg/nsSVGUtils.cpp
+++ b/layout/svg/nsSVGUtils.cpp
@@ -1682,120 +1682,36 @@ nsSVGUtils::GetStrokeWidth(nsIFrame* aFr
     content = content->GetParent();
   }
 
   nsSVGElement *ctx = static_cast<nsSVGElement*>(content);
 
   return SVGContentUtils::CoordToFloat(ctx, style->mStrokeWidth);
 }
 
-static bool
-GetStrokeDashData(nsIFrame* aFrame,
-                  nsTArray<gfxFloat>& aDashes,
-                  gfxFloat* aDashOffset,
-                  SVGContextPaint* aContextPaint)
-{
-  const nsStyleSVG* style = aFrame->StyleSVG();
-  nsIContent *content = aFrame->GetContent();
-  nsSVGElement *ctx = static_cast<nsSVGElement*>
-    (content->IsNodeOfType(nsINode::eTEXT) ?
-     content->GetParent() : content);
-
-  gfxFloat totalLength = 0.0;
-  if (aContextPaint && style->StrokeDasharrayFromObject()) {
-    aDashes = aContextPaint->GetStrokeDashArray();
-
-    for (uint32_t i = 0; i < aDashes.Length(); i++) {
-      if (aDashes[i] < 0.0) {
-        return false;
-      }
-      totalLength += aDashes[i];
-    }
-
-  } else {
-    uint32_t count = style->mStrokeDasharray.Length();
-    if (!count || !aDashes.SetLength(count, fallible)) {
-      return false;
-    }
-
-    gfxFloat pathScale = 1.0;
-
-    if (content->IsSVGElement(nsGkAtoms::path)) {
-      pathScale = static_cast<SVGPathElement*>(content)->
-        GetPathLengthScale(SVGPathElement::eForStroking);
-      if (pathScale <= 0) {
-        return false;
-      }
-    }
-
-    const nsTArray<nsStyleCoord>& dasharray = style->mStrokeDasharray;
-
-    for (uint32_t i = 0; i < count; i++) {
-      aDashes[i] = SVGContentUtils::CoordToFloat(ctx,
-                                                 dasharray[i]) * pathScale;
-      if (aDashes[i] < 0.0) {
-        return false;
-      }
-      totalLength += aDashes[i];
-    }
-  }
-
-  if (aContextPaint && style->StrokeDashoffsetFromObject()) {
-    *aDashOffset = aContextPaint->GetStrokeDashOffset();
-  } else {
-    *aDashOffset = SVGContentUtils::CoordToFloat(ctx,
-                                                 style->mStrokeDashoffset);
-  }
-
-  return (totalLength > 0.0);
-}
-
 void
 nsSVGUtils::SetupStrokeGeometry(nsIFrame* aFrame,
                                 gfxContext *aContext,
                                 SVGContextPaint* aContextPaint)
 {
-  float width = GetStrokeWidth(aFrame, aContextPaint);
-  if (width <= 0)
-    return;
-  aContext->SetLineWidth(width);
-
-  const nsStyleSVG* style = aFrame->StyleSVG();
+  SVGContentUtils::AutoStrokeOptions strokeOptions;
+  SVGContentUtils::GetStrokeOptions(
+    &strokeOptions, static_cast<nsSVGElement*>(aFrame->GetContent()),
+    aFrame->StyleContext(), aContextPaint);
 
-  switch (style->mStrokeLinecap) {
-  case NS_STYLE_STROKE_LINECAP_BUTT:
-    aContext->SetLineCap(CapStyle::BUTT);
-    break;
-  case NS_STYLE_STROKE_LINECAP_ROUND:
-    aContext->SetLineCap(CapStyle::ROUND);
-    break;
-  case NS_STYLE_STROKE_LINECAP_SQUARE:
-    aContext->SetLineCap(CapStyle::SQUARE);
-    break;
+  if (strokeOptions.mLineWidth <= 0) {
+    return;
   }
 
-  aContext->SetMiterLimit(style->mStrokeMiterlimit);
-
-  switch (style->mStrokeLinejoin) {
-  case NS_STYLE_STROKE_LINEJOIN_MITER:
-    aContext->SetLineJoin(JoinStyle::MITER_OR_BEVEL);
-    break;
-  case NS_STYLE_STROKE_LINEJOIN_ROUND:
-    aContext->SetLineJoin(JoinStyle::ROUND);
-    break;
-  case NS_STYLE_STROKE_LINEJOIN_BEVEL:
-    aContext->SetLineJoin(JoinStyle::BEVEL);
-    break;
-  }
-
-  AutoTArray<gfxFloat, 10> dashes;
-  gfxFloat dashOffset;
-  if (GetStrokeDashData(aFrame, dashes, &dashOffset, aContextPaint)) {
-    aContext->SetDash(dashes.Elements(), dashes.Length(), dashOffset);
-  }
+  aContext->SetLineWidth(strokeOptions.mLineWidth);
+  aContext->SetLineCap(strokeOptions.mLineCap);
+  aContext->SetMiterLimit(strokeOptions.mMiterLimit);
+  aContext->SetLineJoin(strokeOptions.mLineJoin);
+  aContext->SetDash(strokeOptions.mDashPattern, strokeOptions.mDashLength,
+                    strokeOptions.mDashOffset);
 }
 
 uint16_t
 nsSVGUtils::GetGeometryHitTestFlags(nsIFrame* aFrame)
 {
   uint16_t flags = 0;
 
   switch (aFrame->StyleUserInterface()->mPointerEvents) {
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1562,16 +1562,17 @@ pref("javascript.options.showInConsole",
 pref("javascript.options.shared_memory", false);
 
 pref("javascript.options.throw_on_debuggee_would_run", false);
 pref("javascript.options.dump_stack_on_debuggee_would_run", false);
 
 // Spectre security vulnerability mitigations.
 pref("javascript.options.spectre.index_masking", true);
 pref("javascript.options.spectre.object_mitigations.barriers", true);
+pref("javascript.options.spectre.object_mitigations.misc", false);
 pref("javascript.options.spectre.string_mitigations", true);
 pref("javascript.options.spectre.value_masking", true);
 pref("javascript.options.spectre.jit_to_C++_calls", true);
 
 // Streams API
 pref("javascript.options.streams", false);
 
 // advanced prefs
--- a/toolkit/content/widgets/notification.xml
+++ b/toolkit/content/widgets/notification.xml
@@ -507,21 +507,22 @@
                    xbl:inherits="popupid,src=icon,class=iconclass"/>
         <xul:vbox flex="1" pack="start"
                   class="popup-notification-body" xbl:inherits="popupid">
           <xul:hbox align="start">
             <xul:vbox flex="1">
               <xul:label class="popup-notification-origin header"
                          xbl:inherits="value=origin,tooltiptext=origin"
                          crop="center"/>
-              <xul:description class="popup-notification-description"
-                               xbl:inherits="popupid">
-                <!-- These need to be on the same line to avoid creating whitespace between them (whitespace is added in the localization file, if necessary). -->
-                <html:span xbl:inherits="xbl:text=label,popupid"/><html:b xbl:inherits="xbl:text=name,popupid"/><html:span xbl:inherits="xbl:text=endlabel,popupid"/>
-              </xul:description>
+              <!-- These need to be on the same line to avoid creating
+                   whitespace between them (whitespace is added in the
+                   localization file, if necessary). -->
+              <xul:description class="popup-notification-description" xbl:inherits="popupid"><html:span
+                xbl:inherits="xbl:text=label,popupid"/><html:b xbl:inherits="xbl:text=name,popupid"/><html:span
+              xbl:inherits="xbl:text=endlabel,popupid"/></xul:description>
             </xul:vbox>
             <xul:toolbarbutton anonid="closebutton"
                                class="messageCloseButton close-icon popup-notification-closebutton tabbable"
                                xbl:inherits="oncommand=closebuttoncommand,hidden=closebuttonhidden"
                                tooltiptext="&closeNotification.tooltip;"/>
           </xul:hbox>
           <children includes="popupnotificationcontent"/>
           <xul:label class="text-link popup-notification-learnmore-link"