Merge mozilla-central to mozilla-inbound
authorarthur.iakab <aiakab@mozilla.com>
Thu, 11 Oct 2018 13:10:33 +0300
changeset 496445 8940f3ccdbd00b246dc41608545855580f675a90
parent 496444 777ef3d920f01a85b677dea8914fc38df9135814 (current diff)
parent 496417 ddcd7cc2f3cdd88f5f399f4c59d530aceda0d722 (diff)
child 496446 d0c617e2d35101856d1f26431520a65187f29c78
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound
toolkit/locales/en-US/chrome/global/aboutAbout.dtd
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -695,17 +695,22 @@ const PanelUI = {
     }
 
     let doorhangers =
       notifications.filter(n => !n.dismissed && !n.options.badgeOnly);
 
     if (this.panel.state == "showing" || this.panel.state == "open") {
       // If the menu is already showing, then we need to dismiss all notifications
       // since we don't want their doorhangers competing for attention
-      doorhangers.forEach(n => { n.dismissed = true; });
+      doorhangers.forEach(n => {
+        n.dismissed = true;
+        if (n.options.onDismissed) {
+          n.options.onDismissed();
+        }
+      });
       this._hidePopup();
       this._clearBadge();
       if (!notifications[0].options.badgeOnly) {
         this._showBannerItem(notifications[0]);
       }
     } else if (doorhangers.length > 0) {
       // Only show the doorhanger if the window is focused and not fullscreen
       if ((window.fullScreen && this.autoHideToolbarInFullScreen) || Services.focus.activeWindow !== window) {
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -418,17 +418,20 @@ var ExtensionsUI = {
 
       let icon = addon.isWebExtension ?
                  AddonManager.getPreferredIconURL(addon, 32, window) || DEFAULT_EXTENSION_ICON :
                  "chrome://browser/skin/addons/addon-install-installed.svg";
       let options = {
         name: addon.name,
         message,
         popupIconURL: icon,
-        onDismissed: resolve,
+        onDismissed: () => {
+          AppMenuNotifications.removeNotification("addon-installed");
+          resolve();
+        },
       };
 
       AppMenuNotifications.showNotification("addon-installed", action, null, options);
     });
   },
 };
 
 EventEmitter.decorate(ExtensionsUI);
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -319,20 +319,16 @@ LargeAllocationNonGetRequest=A Large-All
 LargeAllocationNotOnlyToplevelInTabGroup=A Large-Allocation header was ignored due to the presence of windows which have a reference to this browsing context through the frame hierarchy or window.opener.
 # LOCALIZATION NOTE: Do not translate "Large-Allocation", as it is a literal header name
 LargeAllocationNonE10S=A Large-Allocation header was ignored due to the document not being loaded out of process.
 GeolocationInsecureRequestIsForbidden=A Geolocation request can only be fulfilled in a secure context.
 # LOCALIZATION NOTE: Do not translate "Large-Allocation", as it is a literal header name.
 LargeAllocationNonWin32=This page would be loaded in a new process due to a Large-Allocation header, however Large-Allocation process creation is disabled on non-Win32 platforms.
 # LOCALIZATION NOTE: Do not translate URL.createObjectURL(MediaStream).
 URLCreateObjectURL_MediaStreamWarning=URL.createObjectURL(MediaStream) is deprecated and will be removed soon.
-# LOCALIZATION NOTE: Do not translate MozAutoGainControl or autoGainControl.
-MozAutoGainControlWarning=mozAutoGainControl is deprecated. Use autoGainControl instead.
-# LOCALIZATION NOTE: Do not translate mozNoiseSuppression or noiseSuppression.
-MozNoiseSuppressionWarning=mozNoiseSuppression is deprecated. Use noiseSuppression instead.
 # LOCALIZATION NOTE: Do not translate xml:base.
 XMLBaseAttributeWarning=Use of xml:base attribute is deprecated and will be removed soon. Please remove any use of it.
 # LOCALIZATION NOTE: Do not translate "content", "Window", and "window.top"
 WindowContentUntrustedWarning=The ‘content’ attribute of Window objects is deprecated.  Please use ‘window.top’ instead.
 # LOCALIZATION NOTE: The first %S is the tag name of the element that starts the loop, the second %S is the element's ID.
 SVGRefLoopWarning=The SVG <%S> with ID “%S” has a reference loop.
 # LOCALIZATION NOTE: The first %S is the tag name of the element in the chain where the chain was broken, the second %S is the element's ID.
 SVGRefChainLengthExceededWarning=An SVG <%S> reference chain which is too long was abandoned at the element with ID “%S”.
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -2790,24 +2790,16 @@ MediaManager::GetUserMedia(nsPIDOMWindow
   } else if (IsOn(c.mVideo)) {
     videoType = MediaSourceEnum::Camera;
     Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_TYPE,
                           (uint32_t) videoType);
   }
 
   if (c.mAudio.IsMediaTrackConstraints()) {
     auto& ac = c.mAudio.GetAsMediaTrackConstraints();
-    MediaConstraintsHelper::ConvertOldWithWarning(ac.mMozAutoGainControl,
-                                                  ac.mAutoGainControl,
-                                                  "MozAutoGainControlWarning",
-                                                  aWindow);
-    MediaConstraintsHelper::ConvertOldWithWarning(ac.mMozNoiseSuppression,
-                                                  ac.mNoiseSuppression,
-                                                  "MozNoiseSuppressionWarning",
-                                                  aWindow);
     audioType = StringToEnum(dom::MediaSourceEnumValues::strings,
                              ac.mMediaSource,
                              MediaSourceEnum::Other);
     // Work around WebIDL default since spec uses same dictionary w/audio & video.
     if (audioType == MediaSourceEnum::Camera) {
       audioType = MediaSourceEnum::Microphone;
       ac.mMediaSource.AssignASCII(EnumToASCII(dom::MediaSourceEnumValues::strings,
                                               audioType));
@@ -4766,59 +4758,49 @@ SourceListener::CapturingSource(MediaSou
 
   return CaptureState::Disabled;
 }
 
 RefPtr<SourceListener::ApplyConstraintsPromise>
 SourceListener::ApplyConstraintsToTrack(
     nsPIDOMWindowInner* aWindow,
     TrackID aTrackID,
-    const MediaTrackConstraints& aConstraintsPassedIn,
+    const MediaTrackConstraints& aConstraints,
     dom::CallerType aCallerType)
 {
   MOZ_ASSERT(NS_IsMainThread());
   DeviceState& state = GetDeviceStateFor(aTrackID);
 
   if (mStopped || state.mStopped) {
     LOG(("gUM %s track %d applyConstraints, but source is stopped",
          aTrackID == kAudioTrack ? "audio" : "video", aTrackID));
     return ApplyConstraintsPromise::CreateAndResolve(false, __func__);
   }
 
-  MediaTrackConstraints c(aConstraintsPassedIn); // use a modifiable copy
-  MediaConstraintsHelper::ConvertOldWithWarning(c.mMozAutoGainControl,
-                                                c.mAutoGainControl,
-                                                "MozAutoGainControlWarning",
-                                                aWindow);
-  MediaConstraintsHelper::ConvertOldWithWarning(c.mMozNoiseSuppression,
-                                                c.mNoiseSuppression,
-                                                "MozNoiseSuppressionWarning",
-                                                aWindow);
-
   MediaManager* mgr = MediaManager::GetIfExists();
   if (!mgr) {
     return ApplyConstraintsPromise::CreateAndResolve(false, __func__);
   }
 
   return MediaManager::PostTask<ApplyConstraintsPromise>(__func__,
-      [device = state.mDevice, c,
+      [device = state.mDevice, aConstraints,
        isChrome = aCallerType == dom::CallerType::System]
       (MozPromiseHolder<ApplyConstraintsPromise>& aHolder) mutable {
     MOZ_ASSERT(MediaManager::IsInMediaThread());
     MediaManager* mgr = MediaManager::GetIfExists();
     MOZ_RELEASE_ASSERT(mgr); // Must exist while media thread is alive
     const char* badConstraint = nullptr;
-    nsresult rv = device->Reconfigure(c, mgr->mPrefs, &badConstraint);
+    nsresult rv = device->Reconfigure(aConstraints, mgr->mPrefs, &badConstraint);
     if (rv == NS_ERROR_INVALID_ARG) {
       // Reconfigure failed due to constraints
       if (!badConstraint) {
         nsTArray<RefPtr<MediaDevice>> devices;
         devices.AppendElement(device);
         badConstraint = MediaConstraintsHelper::SelectSettings(
-            NormalizedConstraints(c), devices, isChrome);
+            NormalizedConstraints(aConstraints), devices, isChrome);
       }
 
       aHolder.Reject(Some(NS_ConvertASCIItoUTF16(badConstraint)), __func__);
       return;
     }
 
     if (NS_FAILED(rv)) {
       // Reconfigure failed unexpectedly
--- a/dom/media/webrtc/MediaEngineDefault.cpp
+++ b/dom/media/webrtc/MediaEngineDefault.cpp
@@ -419,18 +419,19 @@ MediaEngineDefaultAudioSource::Allocate(
                                         AllocationHandle** aOutHandle,
                                         const char** aOutBadConstraint)
 {
   AssertIsOnOwningThread();
 
   MOZ_ASSERT(mState == kReleased);
 
   // Mock failure for automated tests.
-  if (aConstraints.mDeviceId.IsString() &&
-      aConstraints.mDeviceId.GetAsString().EqualsASCII("bad device")) {
+  if (aConstraints.mDeviceId.WasPassed() &&
+      aConstraints.mDeviceId.Value().IsString() &&
+      aConstraints.mDeviceId.Value().GetAsString().EqualsASCII("bad device")) {
     return NS_ERROR_FAILURE;
   }
 
   mFreq = aPrefs.mFreq ? aPrefs.mFreq : 1000;
   *aOutHandle = nullptr;
 
   MutexAutoLock lock(mMutex);
   mState = kAllocated;
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
@@ -986,19 +986,19 @@ MediaEngineRemoteVideoSource::ChooseCapa
 
   TrimLessFitCandidates(candidateSet);
 
   // Any remaining multiples all have the same distance. A common case of this
   // occurs when no ideal is specified. Lean toward defaults.
   uint32_t sameDistance = candidateSet[0].mDistance;
   {
     MediaTrackConstraintSet prefs;
-    prefs.mWidth.SetAsLong() = aPrefs.GetWidth();
-    prefs.mHeight.SetAsLong() = aPrefs.GetHeight();
-    prefs.mFrameRate.SetAsDouble() = aPrefs.mFPS;
+    prefs.mWidth.Construct().SetAsLong() = aPrefs.GetWidth();
+    prefs.mHeight.Construct().SetAsLong() = aPrefs.GetHeight();
+    prefs.mFrameRate.Construct().SetAsDouble() = aPrefs.mFPS;
     NormalizedConstraintSet normPrefs(prefs, false);
 
     for (auto& candidate : candidateSet) {
       candidate.mDistance =
         GetDistance(candidate.mCapability, normPrefs, aDeviceId, aCalculate);
     }
     TrimLessFitCandidates(candidateSet);
   }
--- a/dom/media/webrtc/MediaTrackConstraints.cpp
+++ b/dom/media/webrtc/MediaTrackConstraints.cpp
@@ -16,17 +16,16 @@
 
 mozilla::LogModule* GetMediaManagerLog();
 #undef LOG
 #define LOG(msg, ...) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, (msg, ##__VA_ARGS__))
 
 namespace mozilla {
 
 using dom::ConstrainBooleanParameters;
-using dom::OwningLongOrConstrainLongRange;
 
 template<class ValueType>
 template<class ConstrainRange>
 void
 NormalizedConstraintSet::Range<ValueType>::SetFrom(const ConstrainRange& aOther)
 {
   if (aOther.mIdeal.WasPassed()) {
     mIdeal.emplace(aOther.mIdeal.Value());
@@ -86,31 +85,35 @@ NormalizedConstraintSet::Range<bool>::Fi
     *mIdeal = !!(counter / denominator);
     mMergeDenominator = 0;
   }
 }
 
 NormalizedConstraintSet::LongRange::LongRange(
     LongPtrType aMemberPtr,
     const char* aName,
-    const dom::OwningLongOrConstrainLongRange& aOther,
+    const dom::Optional<dom::OwningLongOrConstrainLongRange>& aOther,
     bool advanced,
     nsTArray<MemberPtrType>* aList)
 : Range<int32_t>((MemberPtrType)aMemberPtr, aName,
                  1 + INT32_MIN, INT32_MAX, // +1 avoids Windows compiler bug
                  aList)
 {
-  if (aOther.IsLong()) {
+  if (!aOther.WasPassed()) {
+    return;
+  }
+  auto& other = aOther.Value();
+  if (other.IsLong()) {
     if (advanced) {
-      mMin = mMax = aOther.GetAsLong();
+      mMin = mMax = other.GetAsLong();
     } else {
-      mIdeal.emplace(aOther.GetAsLong());
+      mIdeal.emplace(other.GetAsLong());
     }
   } else {
-    SetFrom(aOther.GetAsConstrainLongRange());
+    SetFrom(other.GetAsConstrainLongRange());
   }
 }
 
 NormalizedConstraintSet::LongLongRange::LongLongRange(
     LongLongPtrType aMemberPtr,
     const char* aName,
     const long long& aOther,
     nsTArray<MemberPtrType>* aList)
@@ -119,87 +122,100 @@ NormalizedConstraintSet::LongLongRange::
                  aList)
 {
   mIdeal.emplace(aOther);
 }
 
 NormalizedConstraintSet::DoubleRange::DoubleRange(
     DoublePtrType aMemberPtr,
     const char* aName,
-    const dom::OwningDoubleOrConstrainDoubleRange& aOther, bool advanced,
+    const dom::Optional<dom::OwningDoubleOrConstrainDoubleRange>& aOther,
+    bool advanced,
     nsTArray<MemberPtrType>* aList)
 : Range<double>((MemberPtrType)aMemberPtr, aName,
                 -std::numeric_limits<double>::infinity(),
                 std::numeric_limits<double>::infinity(), aList)
 {
-  if (aOther.IsDouble()) {
+  if (!aOther.WasPassed()) {
+    return;
+  }
+  auto& other = aOther.Value();
+  if (other.IsDouble()) {
     if (advanced) {
-      mMin = mMax = aOther.GetAsDouble();
+      mMin = mMax = other.GetAsDouble();
     } else {
-      mIdeal.emplace(aOther.GetAsDouble());
+      mIdeal.emplace(other.GetAsDouble());
     }
   } else {
-    SetFrom(aOther.GetAsConstrainDoubleRange());
+    SetFrom(other.GetAsConstrainDoubleRange());
   }
 }
 
 NormalizedConstraintSet::BooleanRange::BooleanRange(
     BooleanPtrType aMemberPtr,
     const char* aName,
-    const dom::OwningBooleanOrConstrainBooleanParameters& aOther,
+    const dom::Optional<dom::OwningBooleanOrConstrainBooleanParameters>& aOther,
     bool advanced,
     nsTArray<MemberPtrType>* aList)
 : Range<bool>((MemberPtrType)aMemberPtr, aName, false, true, aList)
 {
-  if (aOther.IsBoolean()) {
+  if (!aOther.WasPassed()) {
+    return;
+  }
+  auto& other = aOther.Value();
+  if (other.IsBoolean()) {
     if (advanced) {
-      mMin = mMax = aOther.GetAsBoolean();
+      mMin = mMax = other.GetAsBoolean();
     } else {
-      mIdeal.emplace(aOther.GetAsBoolean());
+      mIdeal.emplace(other.GetAsBoolean());
     }
   } else {
-    const dom::ConstrainBooleanParameters& r = aOther.GetAsConstrainBooleanParameters();
+    auto& r = other.GetAsConstrainBooleanParameters();
     if (r.mIdeal.WasPassed()) {
       mIdeal.emplace(r.mIdeal.Value());
     }
     if (r.mExact.WasPassed()) {
       mMin = r.mExact.Value();
       mMax = r.mExact.Value();
     }
   }
 }
 
 NormalizedConstraintSet::StringRange::StringRange(
     StringPtrType aMemberPtr,
     const char* aName,
-    const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aOther,
+    const dom::Optional<dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters>& aOther,
     bool advanced,
     nsTArray<MemberPtrType>* aList)
   : BaseRange((MemberPtrType)aMemberPtr, aName, aList)
 {
-  if (aOther.IsString()) {
+  if (!aOther.WasPassed()) {
+    return;
+  }
+  auto& other = aOther.Value();
+  if (other.IsString()) {
     if (advanced) {
-      mExact.insert(aOther.GetAsString());
+      mExact.insert(other.GetAsString());
     } else {
-      mIdeal.insert(aOther.GetAsString());
+      mIdeal.insert(other.GetAsString());
     }
-  } else if (aOther.IsStringSequence()) {
+  } else if (other.IsStringSequence()) {
     if (advanced) {
       mExact.clear();
-      for (auto& str : aOther.GetAsStringSequence()) {
+      for (auto& str : other.GetAsStringSequence()) {
         mExact.insert(str);
       }
     } else {
       mIdeal.clear();
-      for (auto& str : aOther.GetAsStringSequence()) {
+      for (auto& str : other.GetAsStringSequence()) {
         mIdeal.insert(str);
       }
     }
   } else {
-    SetFrom(aOther.GetAsConstrainDOMStringParameters());
+    SetFrom(other.GetAsConstrainDOMStringParameters());
   }
 }
 
 void
 NormalizedConstraintSet::StringRange::SetFrom(
     const dom::ConstrainDOMStringParameters& aOther)
 {
   if (aOther.mIdeal.WasPassed()) {
@@ -603,43 +619,16 @@ MediaConstraintsHelper::FindBadConstrain
 {
   AutoTArray<RefPtr<MediaDevice>, 1> devices;
   devices.AppendElement(MakeRefPtr<MediaDevice>(aMediaEngineSource,
                                                 aMediaEngineSource->GetName(),
                                                 aDeviceId));
   return FindBadConstraint(aConstraints, devices);
 }
 
-/* static */ void
-MediaConstraintsHelper::ConvertOldWithWarning(
-    const dom::OwningBooleanOrConstrainBooleanParameters& old,
-    dom::OwningBooleanOrConstrainBooleanParameters& to,
-    const char* aMessageName,
-    nsPIDOMWindowInner* aWindow) {
-  if ((old.IsBoolean() ||
-       old.GetAsConstrainBooleanParameters().mExact.WasPassed() ||
-       old.GetAsConstrainBooleanParameters().mIdeal.WasPassed()) &&
-      !(to.IsBoolean() ||
-        to.GetAsConstrainBooleanParameters().mExact.WasPassed() ||
-        to.GetAsConstrainBooleanParameters().mIdeal.WasPassed())) {
-    nsCOMPtr<nsIDocument> doc = aWindow->GetDoc();
-    if (doc) {
-      nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
-                                      NS_LITERAL_CSTRING("DOM"), doc,
-                                      nsContentUtils::eDOM_PROPERTIES,
-                                      aMessageName);
-    }
-    if (old.IsBoolean()) {
-      to.SetAsBoolean() = old.GetAsBoolean();
-    } else {
-      to.SetAsConstrainBooleanParameters() = old.GetAsConstrainBooleanParameters();
-    }
-  }
-}
-
 static void
 LogConstraintStringRange(const NormalizedConstraintSet::StringRange& aRange)
 {
   if (aRange.mExact.size() <= 1 && aRange.mIdeal.size() <= 1) {
     LOG("  %s: { exact: [%s], ideal: [%s] }",
         aRange.mName,
         (aRange.mExact.size()? NS_ConvertUTF16toUTF8(*aRange.mExact.begin()).get() : ""),
         (aRange.mIdeal.size()? NS_ConvertUTF16toUTF8(*aRange.mIdeal.begin()).get() : ""));
--- a/dom/media/webrtc/MediaTrackConstraints.h
+++ b/dom/media/webrtc/MediaTrackConstraints.h
@@ -145,17 +145,18 @@ public:
     uint32_t mMergeDenominator;
   };
 
   struct LongRange : public Range<int32_t>
   {
     typedef LongRange NormalizedConstraintSet::* LongPtrType;
 
     LongRange(LongPtrType aMemberPtr, const char* aName,
-              const dom::OwningLongOrConstrainLongRange& aOther, bool advanced,
+              const dom::Optional<dom::OwningLongOrConstrainLongRange>& aOther,
+              bool advanced,
               nsTArray<MemberPtrType>* aList);
   };
 
   struct LongLongRange : public Range<int64_t>
   {
     typedef LongLongRange NormalizedConstraintSet::* LongLongPtrType;
 
     LongLongRange(LongLongPtrType aMemberPtr, const char* aName,
@@ -164,27 +165,27 @@ public:
   };
 
   struct DoubleRange : public Range<double>
   {
     typedef DoubleRange NormalizedConstraintSet::* DoublePtrType;
 
     DoubleRange(DoublePtrType aMemberPtr,
                 const char* aName,
-                const dom::OwningDoubleOrConstrainDoubleRange& aOther,
+                const dom::Optional<dom::OwningDoubleOrConstrainDoubleRange>& aOther,
                 bool advanced,
                 nsTArray<MemberPtrType>* aList);
   };
 
   struct BooleanRange : public Range<bool>
   {
     typedef BooleanRange NormalizedConstraintSet::* BooleanPtrType;
 
     BooleanRange(BooleanPtrType aMemberPtr, const char* aName,
-                 const dom::OwningBooleanOrConstrainBooleanParameters& aOther,
+                 const dom::Optional<dom::OwningBooleanOrConstrainBooleanParameters>& aOther,
                  bool advanced,
                  nsTArray<MemberPtrType>* aList);
 
     BooleanRange(BooleanPtrType aMemberPtr, const char* aName, const bool& aOther,
                  nsTArray<MemberPtrType>* aList)
       : Range<bool>((MemberPtrType)aMemberPtr, aName, false, true, aList) {
       mIdeal.emplace(aOther);
     }
@@ -193,17 +194,17 @@ public:
   struct StringRange : public BaseRange
   {
     typedef std::set<nsString> ValueType;
     ValueType mExact, mIdeal;
 
     typedef StringRange NormalizedConstraintSet::* StringPtrType;
 
     StringRange(StringPtrType aMemberPtr,  const char* aName,
-        const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aOther,
+        const dom::Optional<dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters>& aOther,
         bool advanced,
         nsTArray<MemberPtrType>* aList);
 
     StringRange(StringPtrType aMemberPtr, const char* aName,
                 const nsString& aOther, nsTArray<MemberPtrType>* aList)
       : BaseRange((MemberPtrType)aMemberPtr, aName, aList) {
       mIdeal.insert(aOther);
     }
@@ -332,22 +333,14 @@ public:
   FindBadConstraint(const NormalizedConstraints& aConstraints,
                     const nsTArray<RefPtr<MediaDevice>>& aDevices);
 
   static const char*
   FindBadConstraint(const NormalizedConstraints& aConstraints,
                     const RefPtr<MediaEngineSource>& aMediaEngineSource,
                     const nsString& aDeviceId);
 
-  // Warn on and convert use of deprecated constraints to new ones
-  static void
-  ConvertOldWithWarning(
-      const dom::OwningBooleanOrConstrainBooleanParameters& old,
-      dom::OwningBooleanOrConstrainBooleanParameters& to,
-      const char* aMessageName,
-      nsPIDOMWindowInner* aWindow);
-
   static void LogConstraints(const NormalizedConstraintSet& aConstraints);
 };
 
 } // namespace mozilla
 
 #endif /* MEDIATRACKCONSTRAINTS_H_ */
--- a/dom/webidl/MediaStreamTrack.webidl
+++ b/dom/webidl/MediaStreamTrack.webidl
@@ -37,51 +37,32 @@ typedef (long or ConstrainLongRange) Con
 typedef (double or ConstrainDoubleRange) ConstrainDouble;
 typedef (boolean or ConstrainBooleanParameters) ConstrainBoolean;
 typedef (DOMString or sequence<DOMString> or ConstrainDOMStringParameters) ConstrainDOMString;
 
 // Note: When adding new constraints, remember to update the SelectSettings()
 // function in MediaManager.cpp to make OverconstrainedError's constraint work!
 
 dictionary MediaTrackConstraintSet {
-    // FIXME: bug 1493860 or bug 1493798: should this "= null" be here?
-    ConstrainLong width = null;
-    // FIXME: bug 1493860 or bug 1493798: should this "= null" be here?
-    ConstrainLong height = null;
-    // FIXME: bug 1493860 or bug 1493798: should this "= null" be here?
-    ConstrainDouble frameRate = null;
-    // FIXME: bug 1493860 or bug 1493798: should this "= null" be here?
-    ConstrainDOMString facingMode = null;
+    ConstrainLong width;
+    ConstrainLong height;
+    ConstrainDouble frameRate;
+    ConstrainDOMString facingMode;
     DOMString mediaSource = "camera";
     long long browserWindow;
     boolean scrollWithPage;
-    // FIXME: bug 1493860 or bug 1493798: should this "= null" be here?
-    ConstrainDOMString deviceId = null;
-    // FIXME: bug 1493860 or bug 1493798: should this "= null" be here?
-    ConstrainLong viewportOffsetX = null;
-    // FIXME: bug 1493860 or bug 1493798: should this "= null" be here?
-    ConstrainLong viewportOffsetY = null;
-    // FIXME: bug 1493860 or bug 1493798: should this "= null" be here?
-    ConstrainLong viewportWidth = null;
-    // FIXME: bug 1493860 or bug 1493798: should this "= null" be here?
-    ConstrainLong viewportHeight = null;
-    // FIXME: bug 1493860 or bug 1493798: should this "= null" be here?
-    ConstrainBoolean echoCancellation = null;
-    // FIXME: bug 1493860 or bug 1493798: should this "= null" be here?
-    ConstrainBoolean noiseSuppression = null;
-    // FIXME: bug 1493860 or bug 1493798: should this "= null" be here?
-    ConstrainBoolean autoGainControl = null;
-    // FIXME: bug 1493860 or bug 1493798: should this "= null" be here?
-    ConstrainLong channelCount = null;
-
-    // Deprecated with warnings:
-    // FIXME: bug 1493860 or bug 1493798: should this "= null" be here?
-    ConstrainBoolean mozNoiseSuppression = null;
-    // FIXME: bug 1493860 or bug 1493798: should this "= null" be here?
-    ConstrainBoolean mozAutoGainControl = null;
+    ConstrainDOMString deviceId;
+    ConstrainLong viewportOffsetX;
+    ConstrainLong viewportOffsetY;
+    ConstrainLong viewportWidth;
+    ConstrainLong viewportHeight;
+    ConstrainBoolean echoCancellation;
+    ConstrainBoolean noiseSuppression;
+    ConstrainBoolean autoGainControl;
+    ConstrainLong channelCount;
 };
 
 dictionary MediaTrackConstraints : MediaTrackConstraintSet {
     sequence<MediaTrackConstraintSet> advanced;
 };
 
 enum MediaStreamTrackState {
     "live",
--- a/js/src/jit-test/tests/ion/bug913749.js
+++ b/js/src/jit-test/tests/ion/bug913749.js
@@ -11,11 +11,11 @@ this.toSource();
 y = undefined;
 
 for (var i = 0; i < 3; i++) {
     try {
 	x.toString();
 	assertEq(0, 1);
     } catch (e) {
 	assertEq(e.message === `y is undefined; can't access its "length" property` ||
-		 e.message === "undefined has no properties", true);
+		 e.message === `can't access property "length" of undefined`, true);
     }
 }
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -331,16 +331,43 @@ ICStub::trace(JSTracer* trc)
         TraceCacheIRStub(trc, this, stub->stubInfo());
         break;
       }
       default:
         break;
     }
 }
 
+// This helper handles ICState updates/transitions while attaching CacheIR stubs.
+template<typename IRGenerator, typename... Args>
+static void
+TryAttachStub(const char *name, JSContext* cx, BaselineFrame* frame, ICFallbackStub* stub, BaselineCacheIRStubKind kind, Args&&... args)
+{
+    if (stub->state().maybeTransition()) {
+        stub->discardStubs(cx);
+    }
+
+    if (stub->state().canAttachStub()) {
+        RootedScript script(cx, frame->script());
+        jsbytecode* pc = stub->icEntry()->pc(script);
+
+        bool attached = false;
+        IRGenerator gen(cx, script, pc, stub->state().mode(), std::forward<Args>(args)...);
+        if (gen.tryAttachStub()) {
+            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
+                                                        kind, script, stub, &attached);
+            if (newStub) {
+                JitSpew(JitSpew_BaselineIC, "  %s %s CacheIR stub", attached ? "Attached" : "Failed to attach", name);
+            }
+        }
+        if (!attached) {
+            stub->state().trackNotAttached();
+        }
+    }
+}
 
 
 //
 // WarmUpCounter_Fallback
 //
 
 
 //
@@ -1579,40 +1606,17 @@ ICTypeUpdate_AnyValue::Compiler::generat
 static bool
 DoToBoolFallback(JSContext* cx, BaselineFrame* frame, ICToBool_Fallback* stub, HandleValue arg,
                  MutableHandleValue ret)
 {
     FallbackICSpew(cx, stub, "ToBool");
 
     MOZ_ASSERT(!arg.isBoolean());
 
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-
-        RootedScript script(cx, frame->script());
-        jsbytecode* pc = stub->icEntry()->pc(script);
-
-        ToBoolIRGenerator gen(cx, script, pc, stub->state().mode(),
-                              arg);
-        bool attached = false;
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached ToBool CacheIR stub, attached is now %d", attached);
-            }
-        }
-        if (!attached) {
-            stub->state().trackNotAttached();
-        }
-    }
+    TryAttachStub<ToBoolIRGenerator>("ToBool", cx, frame, stub, BaselineCacheIRStubKind::Regular, arg);
 
     bool cond = ToBoolean(arg);
     ret.setBoolean(cond);
 
     return true;
 }
 
 typedef bool (*pf)(JSContext*, BaselineFrame*, ICToBool_Fallback*, HandleValue,
@@ -1781,20 +1785,16 @@ DoGetElemFallback(JSContext* cx, Baselin
 
     // GetElem operations which could access negative indexes generally can't
     // be optimized without the potential for bailouts, as we can't statically
     // determine that an object has no properties on such indexes.
     if (rhs.isNumber() && rhs.toNumber() < 0) {
         stub->noteNegativeIndex();
     }
 
-    if (!attached && !isTemporarilyUnoptimizable) {
-        stub->noteUnoptimizableAccess();
-    }
-
     return true;
 }
 
 static bool
 DoGetElemSuperFallback(JSContext* cx, BaselineFrame* frame, ICGetElem_Fallback* stub_,
                        HandleValue lhs, HandleValue rhs, HandleValue receiver,
                        MutableHandleValue res)
 {
@@ -1862,20 +1862,16 @@ DoGetElemSuperFallback(JSContext* cx, Ba
 
     // GetElem operations which could access negative indexes generally can't
     // be optimized without the potential for bailouts, as we can't statically
     // determine that an object has no properties on such indexes.
     if (rhs.isNumber() && rhs.toNumber() < 0) {
         stub->noteNegativeIndex();
     }
 
-    if (!attached && !isTemporarilyUnoptimizable) {
-        stub->noteUnoptimizableAccess();
-    }
-
     return true;
 }
 
 typedef bool (*DoGetElemFallbackFn)(JSContext*, BaselineFrame*, ICGetElem_Fallback*,
                                     HandleValue, HandleValue, MutableHandleValue);
 static const VMFunction DoGetElemFallbackInfo =
     FunctionInfo<DoGetElemFallbackFn>(DoGetElemFallback, "DoGetElemFallback", TailCall,
                                       PopValues(2));
@@ -2058,24 +2054,25 @@ DoSetElemFallback(JSContext* cx, Baselin
     if (stub->state().canAttachStub()) {
         SetPropIRGenerator gen(cx, script, pc, CacheKind::SetElem, stub->state().mode(),
                                &isTemporarilyUnoptimizable, objv, index, rhs);
         if (gen.tryAttachAddSlotStub(oldGroup, oldShape)) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         BaselineCacheIRStubKind::Updated,
                                                         frame->script(), stub, &attached);
             if (newStub) {
+                JitSpew(JitSpew_BaselineIC, "  Attached SetElem CacheIR stub");
+
+                SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
+
                 if (gen.shouldNotePreliminaryObjectStub()) {
                     newStub->toCacheIR_Updated()->notePreliminaryObject();
                 } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
                     StripPreliminaryObjectStubs(cx, stub);
                 }
-
-                JitSpew(JitSpew_BaselineIC, "  Attached SetElem CacheIR stub");
-                SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
                 return true;
             }
         } else {
             gen.trackAttached(IRGenerator::NotAttached);
         }
         if (!attached && !isTemporarilyUnoptimizable) {
             stub->state().trackNotAttached();
         }
@@ -2243,38 +2240,17 @@ DoInFallback(JSContext* cx, BaselineFram
 
     FallbackICSpew(cx, stub, "In");
 
     if (!objValue.isObject()) {
         ReportInNotObjectError(cx, key, -2, objValue, -1);
         return false;
     }
 
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        RootedScript script(cx, frame->script());
-        jsbytecode* pc = stub->icEntry()->pc(script);
-
-        HasPropIRGenerator gen(cx, script, pc, CacheKind::In, stub->state().mode(), key, objValue);
-        bool attached = false;
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached In CacheIR stub");
-            }
-        }
-        if (!attached) {
-            stub->state().trackNotAttached();
-        }
-    }
+    TryAttachStub<HasPropIRGenerator>("In", cx, frame, stub, BaselineCacheIRStubKind::Regular, CacheKind::In, key, objValue);
 
     RootedObject obj(cx, &objValue.toObject());
     bool cond = false;
     if (!OperatorIn(cx, key, obj, &cond)) {
         return false;
     }
     res.setBoolean(cond);
 
@@ -2312,39 +2288,19 @@ static bool
 DoHasOwnFallback(JSContext* cx, BaselineFrame* frame, ICHasOwn_Fallback* stub_,
                  HandleValue keyValue, HandleValue objValue, MutableHandleValue res)
 {
     // This fallback stub may trigger debug mode toggling.
     DebugModeOSRVolatileStub<ICIn_Fallback*> stub(frame, stub_);
 
     FallbackICSpew(cx, stub, "HasOwn");
 
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        RootedScript script(cx, frame->script());
-        jsbytecode* pc = stub->icEntry()->pc(script);
-
-        HasPropIRGenerator gen(cx, script, pc, CacheKind::HasOwn,
-                               stub->state().mode(), keyValue, objValue);
-        bool attached = false;
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached HasOwn CacheIR stub");
-            }
-        }
-        if (!attached) {
-            stub->state().trackNotAttached();
-        }
-    }
+    TryAttachStub<HasPropIRGenerator>("HasOwn", cx, frame, stub,
+        BaselineCacheIRStubKind::Regular, CacheKind::HasOwn,
+        keyValue, objValue);
 
     bool found;
     if (!HasOwnProperty(cx, objValue, keyValue, &found)) {
         return false;
     }
 
     res.setBoolean(found);
     return true;
@@ -2388,36 +2344,18 @@ DoGetNameFallback(JSContext* cx, Baselin
     RootedScript script(cx, frame->script());
     jsbytecode* pc = stub->icEntry()->pc(script);
     mozilla::DebugOnly<JSOp> op = JSOp(*pc);
     FallbackICSpew(cx, stub, "GetName(%s)", CodeName[JSOp(*pc)]);
 
     MOZ_ASSERT(op == JSOP_GETNAME || op == JSOP_GETGNAME);
 
     RootedPropertyName name(cx, script->getName(pc));
-    bool attached = false;
-
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        GetNameIRGenerator gen(cx, script, pc, stub->state().mode(), envChain, name);
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Monitored,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached GetName CacheIR stub");
-            }
-        }
-        if (!attached) {
-            stub->state().trackNotAttached();
-        }
-    }
+
+    TryAttachStub<GetNameIRGenerator>("GetName", cx, frame, stub, BaselineCacheIRStubKind::Monitored, envChain, name);
 
     static_assert(JSOP_GETGNAME_LENGTH == JSOP_GETNAME_LENGTH,
                   "Otherwise our check for JSOP_TYPEOF isn't ok");
     if (JSOp(pc[JSOP_GETGNAME_LENGTH]) == JSOP_TYPEOF) {
         if (!GetEnvironmentName<GetNameMode::TypeOf>(cx, envChain, name, res)) {
             return false;
         }
     } else {
@@ -2434,19 +2372,16 @@ DoGetNameFallback(JSContext* cx, Baselin
         return true;
     }
 
     // Add a type monitor stub for the resulting value.
     if (!stub->addMonitorStubForValue(cx, frame, types, res)) {
         return false;
     }
 
-    if (!attached) {
-        stub->noteUnoptimizableAccess();
-    }
     return true;
 }
 
 typedef bool (*DoGetNameFallbackFn)(JSContext*, BaselineFrame*, ICGetName_Fallback*,
                                     HandleObject, MutableHandleValue);
 static const VMFunction DoGetNameFallbackInfo =
     FunctionInfo<DoGetNameFallbackFn>(DoGetNameFallback, "DoGetNameFallback", TailCall);
 
@@ -2475,36 +2410,17 @@ DoBindNameFallback(JSContext* cx, Baseli
     jsbytecode* pc = stub->icEntry()->pc(frame->script());
     mozilla::DebugOnly<JSOp> op = JSOp(*pc);
     FallbackICSpew(cx, stub, "BindName(%s)", CodeName[JSOp(*pc)]);
 
     MOZ_ASSERT(op == JSOP_BINDNAME || op == JSOP_BINDGNAME);
 
     RootedPropertyName name(cx, frame->script()->getName(pc));
 
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        bool attached = false;
-        RootedScript script(cx, frame->script());
-        BindNameIRGenerator gen(cx, script, pc, stub->state().mode(), envChain, name);
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached BindName CacheIR stub");
-            }
-        }
-        if (!attached) {
-            stub->state().trackNotAttached();
-        }
-    }
+    TryAttachStub<BindNameIRGenerator>("BindName", cx, frame, stub, BaselineCacheIRStubKind::Regular, envChain, name);
 
     RootedObject scope(cx);
     if (!LookupNameUnqualified(cx, name, envChain, &scope)) {
         return false;
     }
 
     res.setObject(*scope);
     return true;
@@ -2557,36 +2473,17 @@ DoGetIntrinsicFallback(JSContext* cx, Ba
 
     TypeScript::Monitor(cx, script, pc, res);
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid()) {
         return true;
     }
 
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        bool attached = false;
-        RootedScript script(cx, frame->script());
-        GetIntrinsicIRGenerator gen(cx, script, pc, stub->state().mode(), res);
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached GetIntrinsic CacheIR stub");
-            }
-        }
-        if (!attached) {
-            stub->state().trackNotAttached();
-        }
-    }
+    TryAttachStub<GetIntrinsicIRGenerator>("GetIntrinsic", cx, frame, stub, BaselineCacheIRStubKind::Regular, res);
 
     return true;
 }
 
 typedef bool (*DoGetIntrinsicFallbackFn)(JSContext*, BaselineFrame*, ICGetIntrinsic_Fallback*,
                                          MutableHandleValue);
 static const VMFunction DoGetIntrinsicFallbackInfo =
     FunctionInfo<DoGetIntrinsicFallbackFn>(DoGetIntrinsicFallback, "DoGetIntrinsicFallback",
@@ -2674,17 +2571,17 @@ DoGetPropFallback(JSContext* cx, Baselin
         GetPropIRGenerator gen(cx, script, pc, CacheKind::GetProp, stub->state().mode(),
                                &isTemporarilyUnoptimizable, val, idVal, val,
                                GetPropertyResultFlags::All);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         BaselineCacheIRStubKind::Monitored,
                                                         script, stub, &attached);
             if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
+                JitSpew(JitSpew_BaselineIC, "  Attached GetProp CacheIR stub");
                 if (gen.shouldNotePreliminaryObjectStub()) {
                     newStub->toCacheIR_Monitored()->notePreliminaryObject();
                 } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
                     StripPreliminaryObjectStubs(cx, stub);
                 }
             }
         }
         if (!attached && !isTemporarilyUnoptimizable) {
@@ -2703,26 +2600,16 @@ DoGetPropFallback(JSContext* cx, Baselin
     if (stub.invalid()) {
         return true;
     }
 
     // Add a type monitor stub for the resulting value.
     if (!stub->addMonitorStubForValue(cx, frame, types, res)) {
         return false;
     }
-
-    if (attached) {
-        return true;
-    }
-
-    MOZ_ASSERT(!attached);
-    if (!isTemporarilyUnoptimizable) {
-        stub->noteUnoptimizableAccess();
-    }
-
     return true;
 }
 
 static bool
 DoGetPropSuperFallback(JSContext* cx, BaselineFrame* frame, ICGetProp_Fallback* stub_,
                        HandleValue receiver, MutableHandleValue val, MutableHandleValue res)
 {
     // This fallback stub may trigger debug mode toggling.
@@ -2752,17 +2639,17 @@ DoGetPropSuperFallback(JSContext* cx, Ba
         GetPropIRGenerator gen(cx, script, pc, CacheKind::GetPropSuper, stub->state().mode(),
                                &isTemporarilyUnoptimizable, val, idVal, receiver,
                                GetPropertyResultFlags::All);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         BaselineCacheIRStubKind::Monitored,
                                                         script, stub, &attached);
             if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
+                JitSpew(JitSpew_BaselineIC, "  Attached GetPropSuper CacheIR stub");
                 if (gen.shouldNotePreliminaryObjectStub()) {
                     newStub->toCacheIR_Monitored()->notePreliminaryObject();
                 } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
                     StripPreliminaryObjectStubs(cx, stub);
                 }
             }
         }
         if (!attached && !isTemporarilyUnoptimizable) {
@@ -2784,25 +2671,16 @@ DoGetPropSuperFallback(JSContext* cx, Ba
         return true;
     }
 
     // Add a type monitor stub for the resulting value.
     if (!stub->addMonitorStubForValue(cx, frame, types, res)) {
         return false;
     }
 
-    if (attached) {
-        return true;
-    }
-
-    MOZ_ASSERT(!attached);
-    if (!isTemporarilyUnoptimizable) {
-        stub->noteUnoptimizableAccess();
-    }
-
     return true;
 }
 
 typedef bool (*DoGetPropFallbackFn)(JSContext*, BaselineFrame*, ICGetProp_Fallback*,
                                     MutableHandleValue, MutableHandleValue);
 static const VMFunction DoGetPropFallbackInfo =
     FunctionInfo<DoGetPropFallbackFn>(DoGetPropFallback, "DoGetPropFallback", TailCall,
                                       PopValues(1));
@@ -3020,37 +2898,34 @@ DoSetPropFallback(JSContext* cx, Baselin
         RootedValue idVal(cx, StringValue(name));
         SetPropIRGenerator gen(cx, script, pc, CacheKind::SetProp, stub->state().mode(),
                                &isTemporarilyUnoptimizable, lhs, idVal, rhs);
         if (gen.tryAttachAddSlotStub(oldGroup, oldShape)) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                                         BaselineCacheIRStubKind::Updated,
                                                         frame->script(), stub, &attached);
             if (newStub) {
+                JitSpew(JitSpew_BaselineIC, "  Attached SetProp CacheIR stub");
+
+                SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
+
                 if (gen.shouldNotePreliminaryObjectStub()) {
                     newStub->toCacheIR_Updated()->notePreliminaryObject();
                 } else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
                     StripPreliminaryObjectStubs(cx, stub);
                 }
-
-                JitSpew(JitSpew_BaselineIC, "  Attached SetProp CacheIR stub");
-                SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
             }
         } else {
             gen.trackAttached(IRGenerator::NotAttached);
         }
         if (!attached && !isTemporarilyUnoptimizable) {
             stub->state().trackNotAttached();
         }
     }
 
-    if (!attached && !isTemporarilyUnoptimizable) {
-        stub->noteUnoptimizableAccess();
-    }
-
     return true;
 }
 
 typedef bool (*DoSetPropFallbackFn)(JSContext*, BaselineFrame*, ICSetProp_Fallback*, Value*,
                                     HandleValue, HandleValue);
 static const VMFunction DoSetPropFallbackInfo =
     FunctionInfo<DoSetPropFallbackFn>(DoSetPropFallback, "DoSetPropFallback", TailCall,
                                       PopValues(1));
@@ -3838,17 +3713,16 @@ DoCallFallback(JSContext* cx, BaselineFr
         // optimized ConstStringSplit stub. Note that vp[0] now holds the return value
         // instead of the callee, so we pass the callee as well.
         if (!TryAttachConstStringSplit(cx, stub, script, argc, callee, vp, pc, res, &handled)) {
             return false;
         }
     }
 
     if (!handled) {
-        stub->noteUnoptimizableCall();
         if (canAttachStub) {
             stub->state().trackNotAttached();
         }
     }
     return true;
 }
 
 static bool
@@ -3891,19 +3765,16 @@ DoSpreadCallFallback(JSContext* cx, Base
     }
 
     // Add a type monitor stub for the resulting value.
     StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
     if (!stub->addMonitorStubForValue(cx, frame, types, res)) {
         return false;
     }
 
-    if (!handled) {
-        stub->noteUnoptimizableCall();
-    }
     return true;
 }
 
 void
 ICCallStubCompiler::pushCallArguments(MacroAssembler& masm, AllocatableGeneralRegisterSet regs,
                                       Register argcReg, bool isJitCall, bool isConstructing)
 {
     MOZ_ASSERT(!regs.has(argcReg));
@@ -5390,38 +5261,17 @@ ICTableSwitch::fixupJumpTable(JSScript* 
 //
 
 static bool
 DoGetIteratorFallback(JSContext* cx, BaselineFrame* frame, ICGetIterator_Fallback* stub,
                       HandleValue value, MutableHandleValue res)
 {
     FallbackICSpew(cx, stub, "GetIterator");
 
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        RootedScript script(cx, frame->script());
-        jsbytecode* pc = stub->icEntry()->pc(script);
-
-        GetIteratorIRGenerator gen(cx, script, pc, stub->state().mode(), value);
-        bool attached = false;
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached GetIterator CacheIR stub");
-            }
-        }
-        if (!attached) {
-            stub->state().trackNotAttached();
-        }
-    }
+    TryAttachStub<GetIteratorIRGenerator>("GetIterator", cx, frame, stub, BaselineCacheIRStubKind::Regular, value);
 
     JSObject* iterobj = ValueToIterator(cx, value);
     if (!iterobj) {
         return false;
     }
 
     res.setObject(*iterobj);
     return true;
@@ -5581,51 +5431,16 @@ ICIteratorClose_Fallback::Compiler::gene
     return tailCallVM(DoIteratorCloseFallbackInfo, masm);
 }
 
 //
 // InstanceOf_Fallback
 //
 
 static bool
-TryAttachInstanceOfStub(JSContext* cx, BaselineFrame* frame, ICInstanceOf_Fallback* stub,
-                        HandleValue lhs, HandleObject rhs, bool* attached)
-{
-    MOZ_ASSERT(!*attached);
-    FallbackICSpew(cx, stub, "InstanceOf");
-
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        RootedScript script(cx, frame->script());
-        jsbytecode* pc = stub->icEntry()->pc(script);
-
-        InstanceOfIRGenerator gen(cx, script, pc, stub->state().mode(),
-                                  lhs,
-                                  rhs);
-
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached InstanceOf CacheIR stub, attached is now %d", *attached);
-            }
-        }
-        if (!attached) {
-            stub->state().trackNotAttached();
-        }
-    }
-
-    return true;
-}
-
-static bool
 DoInstanceOfFallback(JSContext* cx, BaselineFrame* frame, ICInstanceOf_Fallback* stub_,
                      HandleValue lhs, HandleValue rhs, MutableHandleValue res)
 {
     // This fallback stub may trigger debug mode toggling.
     DebugModeOSRVolatileStub<ICInstanceOf_Fallback*> stub(frame, stub_);
 
     FallbackICSpew(cx, stub, "InstanceOf");
 
@@ -5643,31 +5458,28 @@ DoInstanceOfFallback(JSContext* cx, Base
     res.setBoolean(cond);
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid()) {
         return true;
     }
 
     if (!obj->is<JSFunction>()) {
-        stub->noteUnoptimizableAccess();
+        // ensure we've recorded at least one failure, so we can detect there was a non-optimizable case
+        if (!stub->state().hasFailures()) {
+            stub->state().trackNotAttached();
+        }
         return true;
     }
 
     // For functions, keep track of the |prototype| property in type information,
     // for use during Ion compilation.
     EnsureTrackPropertyTypes(cx, obj, NameToId(cx->names().prototype));
 
-    bool attached = false;
-    if (!TryAttachInstanceOfStub(cx, frame, stub, lhs, obj, &attached)) {
-        return false;
-    }
-    if (!attached) {
-        stub->noteUnoptimizableAccess();
-    }
+    TryAttachStub<InstanceOfIRGenerator>("InstanceOf", cx, frame, stub, BaselineCacheIRStubKind::Regular, lhs, obj);
     return true;
 }
 
 typedef bool (*DoInstanceOfFallbackFn)(JSContext*, BaselineFrame*, ICInstanceOf_Fallback*,
                                        HandleValue, HandleValue, MutableHandleValue);
 static const VMFunction DoInstanceOfFallbackInfo =
     FunctionInfo<DoInstanceOfFallbackFn>(DoInstanceOfFallback, "DoInstanceOfFallback", TailCall,
                                          PopValues(2));
@@ -5694,38 +5506,17 @@ ICInstanceOf_Fallback::Compiler::generat
 //
 
 static bool
 DoTypeOfFallback(JSContext* cx, BaselineFrame* frame, ICTypeOf_Fallback* stub, HandleValue val,
                  MutableHandleValue res)
 {
     FallbackICSpew(cx, stub, "TypeOf");
 
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        RootedScript script(cx, frame->script());
-        jsbytecode* pc = stub->icEntry()->pc(script);
-
-        TypeOfIRGenerator gen(cx, script, pc, stub->state().mode(), val);
-        bool attached = false;
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached TypeOf CacheIR stub");
-            }
-        }
-        if (!attached) {
-            stub->state().trackNotAttached();
-        }
-    }
+    TryAttachStub<TypeOfIRGenerator>("TypeOf", cx, frame, stub, BaselineCacheIRStubKind::Regular, val);
 
     JSType type = js::TypeOfValue(val);
     RootedString string(cx, TypeName(type, cx->names()));
     res.setString(string);
     return true;
 }
 
 typedef bool (*DoTypeOfFallbackFn)(JSContext*, BaselineFrame* frame, ICTypeOf_Fallback*,
@@ -6056,34 +5847,17 @@ DoUnaryArithFallback(JSContext* cx, Base
     if (debug_stub.invalid()) {
         return true;
     }
 
     if (res.isDouble()) {
         stub->setSawDoubleResult();
     }
 
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        UnaryArithIRGenerator gen(cx, script, pc, stub->state().mode(),
-                                    op, val, res);
-        if (gen.tryAttachStub()) {
-            bool attached = false;
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached UnaryArith CacheIR stub for %s", CodeName[op]);
-            }
-        }
-    }
-
+    TryAttachStub<UnaryArithIRGenerator>("UniaryArith", cx, frame, stub, BaselineCacheIRStubKind::Regular, op, val, res);
     return true;
 }
 
 typedef bool (*DoUnaryArithFallbackFn)(JSContext*, BaselineFrame*, ICUnaryArith_Fallback*,
                                        HandleValue, MutableHandleValue);
 static const VMFunction DoUnaryArithFallbackInfo =
     FunctionInfo<DoUnaryArithFallbackFn>(DoUnaryArithFallback, "DoUnaryArithFallback", TailCall,
                                          PopValues(1));
@@ -6207,45 +5981,17 @@ DoBinaryArithFallback(JSContext* cx, Bas
     if (stub.invalid()) {
         return true;
     }
 
     if (ret.isDouble()) {
         stub->setSawDoubleResult();
     }
 
-    // Check if debug mode toggling made the stub invalid.
-    if (stub.invalid()) {
-        return true;
-    }
-
-    if (ret.isDouble()) {
-        stub->setSawDoubleResult();
-    }
-
-    if (stub->state().maybeTransition()) {
-        stub->discardStubs(cx);
-    }
-
-    if (stub->state().canAttachStub()) {
-        BinaryArithIRGenerator gen(cx, script, pc, stub->state().mode(),
-                                   op, lhs, rhs, ret);
-        if (gen.tryAttachStub()) {
-            bool attached = false;
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached BinaryArith CacheIR stub for %s", CodeName[op]);
-            }
-
-        } else {
-            stub->noteUnoptimizableOperands();
-        }
-    }
+    TryAttachStub<BinaryArithIRGenerator>("BinaryArith", cx, frame, stub, BaselineCacheIRStubKind::Regular, op, lhs, rhs, ret);
     return true;
 }
 
 typedef bool (*DoBinaryArithFallbackFn)(JSContext*, BaselineFrame*, ICBinaryArith_Fallback*,
                                         HandleValue, HandleValue, MutableHandleValue);
 static const VMFunction DoBinaryArithFallbackInfo =
     FunctionInfo<DoBinaryArithFallbackFn>(DoBinaryArithFallback, "DoBinaryArithFallback",
                                           TailCall, PopValues(2));
@@ -6347,38 +6093,17 @@ DoCompareFallback(JSContext* cx, Baselin
 
     ret.setBoolean(out);
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid()) {
         return true;
     }
 
-    // Check to see if a new stub should be generated.
-    if (stub->numOptimizedStubs() >= ICCompare_Fallback::MAX_OPTIMIZED_STUBS) {
-        // TODO: Discard all stubs in this IC and replace with inert megamorphic stub.
-        // But for now we just bail.
-        return true;
-    }
-
-    if (stub->state().canAttachStub()) {
-        CompareIRGenerator gen(cx, script, pc, stub->state().mode(), op, lhs, rhs);
-        bool attached = false;
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        BaselineCacheIRStubKind::Regular,
-                                                        script, stub, &attached);
-            if (newStub) {
-                    JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
-            }
-            return true;
-        }
-    }
-
-    stub->noteUnoptimizableAccess();
+    TryAttachStub<CompareIRGenerator>("Compare", cx, frame, stub, BaselineCacheIRStubKind::Regular, op, lhs, rhs);
     return true;
 }
 
 typedef bool (*DoCompareFallbackFn)(JSContext*, BaselineFrame*, ICCompare_Fallback*,
                                     HandleValue, HandleValue, MutableHandleValue);
 static const VMFunction DoCompareFallbackInfo =
     FunctionInfo<DoCompareFallbackFn>(DoCompareFallback, "DoCompareFallback", TailCall,
                                       PopValues(2));
@@ -6481,28 +6206,18 @@ DoNewObject(JSContext* cx, BaselineFrame
         if (obj && !obj->isSingleton() &&
             !obj->group()->maybePreliminaryObjectsDontCheckGeneration())
         {
             templateObject = NewObjectOperation(cx, script, pc, TenuredObject);
             if (!templateObject) {
                 return false;
             }
 
-            if (!JitOptions.disableCacheIR) {
-                bool attached = false;
-                NewObjectIRGenerator gen(cx, script, pc, stub->state().mode(), JSOp(*pc), templateObject);
-                if (gen.tryAttachStub()) {
-                    ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                                BaselineCacheIRStubKind::Regular,
-                                                                script, stub, &attached);
-                    if (newStub) {
-                        JitSpew(JitSpew_BaselineIC, "  NewObject Attached CacheIR stub");
-                    }
-                }
-            }
+            TryAttachStub<NewObjectIRGenerator>("NewObject", cx, frame, stub, BaselineCacheIRStubKind::Regular, JSOp(*pc), templateObject);
+
             stub->setTemplateObject(templateObject);
         }
     }
 
     if (!obj) {
         return false;
     }
 
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -1792,31 +1792,24 @@ class ICGetElem_Fallback : public ICMoni
 {
     friend class ICStubSpace;
 
     explicit ICGetElem_Fallback(JitCode* stubCode)
       : ICMonitoredFallbackStub(ICStub::GetElem_Fallback, stubCode)
     { }
 
     static const uint16_t EXTRA_NEGATIVE_INDEX = 0x1;
-    static const uint16_t EXTRA_UNOPTIMIZABLE_ACCESS = 0x2;
 
   public:
     void noteNegativeIndex() {
         extra_ |= EXTRA_NEGATIVE_INDEX;
     }
     bool hasNegativeIndex() const {
         return extra_ & EXTRA_NEGATIVE_INDEX;
     }
-    void noteUnoptimizableAccess() {
-        extra_ |= EXTRA_UNOPTIMIZABLE_ACCESS;
-    }
-    bool hadUnoptimizableAccess() const {
-        return extra_ & EXTRA_UNOPTIMIZABLE_ACCESS;
-    }
 
     // Compiler for this stub kind.
     class Compiler : public ICStubCompiler {
       protected:
         bool hasReceiver_;
         MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm) override;
 
         virtual int32_t getKey() const override {
@@ -1933,25 +1926,16 @@ class ICGetName_Fallback : public ICMoni
 {
     friend class ICStubSpace;
 
     explicit ICGetName_Fallback(JitCode* stubCode)
       : ICMonitoredFallbackStub(ICStub::GetName_Fallback, stubCode)
     { }
 
   public:
-    static const size_t UNOPTIMIZABLE_ACCESS_BIT = 0;
-
-    void noteUnoptimizableAccess() {
-        extra_ |= (1u << UNOPTIMIZABLE_ACCESS_BIT);
-    }
-    bool hadUnoptimizableAccess() const {
-        return extra_ & (1u << UNOPTIMIZABLE_ACCESS_BIT);
-    }
-
     class Compiler : public ICStubCompiler {
       protected:
         MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm) override;
 
       public:
         explicit Compiler(JSContext* cx)
           : ICStubCompiler(cx, ICStub::GetName_Fallback)
         { }
@@ -2022,26 +2006,18 @@ class ICGetProp_Fallback : public ICMoni
 {
     friend class ICStubSpace;
 
     explicit ICGetProp_Fallback(JitCode* stubCode)
       : ICMonitoredFallbackStub(ICStub::GetProp_Fallback, stubCode)
     { }
 
   public:
-    static const size_t UNOPTIMIZABLE_ACCESS_BIT = 0;
     static const size_t ACCESSED_GETTER_BIT = 1;
 
-    void noteUnoptimizableAccess() {
-        extra_ |= (1u << UNOPTIMIZABLE_ACCESS_BIT);
-    }
-    bool hadUnoptimizableAccess() const {
-        return extra_ & (1u << UNOPTIMIZABLE_ACCESS_BIT);
-    }
-
     void noteAccessedGetter() {
         extra_ |= (1u << ACCESSED_GETTER_BIT);
     }
     bool hasAccessedGetter() const {
         return extra_ & (1u << ACCESSED_GETTER_BIT);
     }
 
     class Compiler : public ICStubCompiler {
@@ -2079,24 +2055,16 @@ class ICSetProp_Fallback : public ICFall
 {
     friend class ICStubSpace;
 
     explicit ICSetProp_Fallback(JitCode* stubCode)
       : ICFallbackStub(ICStub::SetProp_Fallback, stubCode)
     { }
 
   public:
-    static const size_t UNOPTIMIZABLE_ACCESS_BIT = 0;
-    void noteUnoptimizableAccess() {
-        extra_ |= (1u << UNOPTIMIZABLE_ACCESS_BIT);
-    }
-    bool hadUnoptimizableAccess() const {
-        return extra_ & (1u << UNOPTIMIZABLE_ACCESS_BIT);
-    }
-
     class Compiler : public ICStubCompiler {
       protected:
         CodeOffset bailoutReturnOffset_;
         MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm) override;
         void postGenerateStubCode(MacroAssembler& masm, Handle<JitCode*> code) override;
 
       public:
         explicit Compiler(JSContext* cx)
@@ -2144,33 +2112,24 @@ class ICCallStubCompiler : public ICStub
     void pushArrayArguments(MacroAssembler& masm, Address arrayVal,
                             AllocatableGeneralRegisterSet regs);
 };
 
 class ICCall_Fallback : public ICMonitoredFallbackStub
 {
     friend class ICStubSpace;
   public:
-    static const unsigned UNOPTIMIZABLE_CALL_FLAG = 0x1;
-
     static const uint32_t MAX_OPTIMIZED_STUBS = 16;
 
   private:
     explicit ICCall_Fallback(JitCode* stubCode)
       : ICMonitoredFallbackStub(ICStub::Call_Fallback, stubCode)
     {}
 
   public:
-    void noteUnoptimizableCall() {
-        extra_ |= UNOPTIMIZABLE_CALL_FLAG;
-    }
-    bool hadUnoptimizableCall() const {
-        return extra_ & UNOPTIMIZABLE_CALL_FLAG;
-    }
-
     bool scriptedStubsAreGeneralized() const {
         return hasStub(Call_AnyScripted);
     }
     bool nativeStubsAreGeneralized() const {
         // Return hasStub(Call_AnyNative) after Call_AnyNative stub is added.
         return false;
     }
 
@@ -2851,27 +2810,17 @@ class ICIteratorClose_Fallback : public 
 class ICInstanceOf_Fallback : public ICFallbackStub
 {
     friend class ICStubSpace;
 
     explicit ICInstanceOf_Fallback(JitCode* stubCode)
       : ICFallbackStub(ICStub::InstanceOf_Fallback, stubCode)
     { }
 
-    static const uint16_t UNOPTIMIZABLE_ACCESS_BIT = 0x1;
-
   public:
-
-    void noteUnoptimizableAccess() {
-        extra_ |= UNOPTIMIZABLE_ACCESS_BIT;
-    }
-    bool hadUnoptimizableAccess() const {
-        return extra_ & UNOPTIMIZABLE_ACCESS_BIT;
-    }
-
     class Compiler : public ICStubCompiler {
       protected:
         MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm) override;
 
       public:
         explicit Compiler(JSContext* cx)
           : ICStubCompiler(cx, ICStub::InstanceOf_Fallback)
         { }
@@ -3066,26 +3015,16 @@ class ICUnaryArith_Fallback : public ICF
 class ICCompare_Fallback : public ICFallbackStub
 {
     friend class ICStubSpace;
 
     explicit ICCompare_Fallback(JitCode* stubCode)
       : ICFallbackStub(ICStub::Compare_Fallback, stubCode) {}
 
   public:
-    static const uint32_t MAX_OPTIMIZED_STUBS = 8;
-
-    static const size_t UNOPTIMIZABLE_ACCESS_BIT = 0;
-    void noteUnoptimizableAccess() {
-        extra_ |= (1u << UNOPTIMIZABLE_ACCESS_BIT);
-    }
-    bool hadUnoptimizableAccess() const {
-        return extra_ & (1u << UNOPTIMIZABLE_ACCESS_BIT);
-    }
-
     // Compiler for this stub kind.
     class Compiler : public ICStubCompiler {
       protected:
         MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm) override;
 
       public:
         explicit Compiler(JSContext* cx)
           : ICStubCompiler(cx, ICStub::Compare_Fallback) {}
@@ -3108,33 +3047,26 @@ class ICBinaryArith_Fallback : public IC
 
     explicit ICBinaryArith_Fallback(JitCode* stubCode)
       : ICFallbackStub(BinaryArith_Fallback, stubCode)
     {
         extra_ = 0;
     }
 
     static const uint16_t SAW_DOUBLE_RESULT_BIT = 0x1;
-    static const uint16_t UNOPTIMIZABLE_OPERANDS_BIT = 0x2;
 
   public:
     static const uint32_t MAX_OPTIMIZED_STUBS = 8;
 
     bool sawDoubleResult() const {
         return extra_ & SAW_DOUBLE_RESULT_BIT;
     }
     void setSawDoubleResult() {
         extra_ |= SAW_DOUBLE_RESULT_BIT;
     }
-    bool hadUnoptimizableOperands() const {
-        return extra_ & UNOPTIMIZABLE_OPERANDS_BIT;
-    }
-    void noteUnoptimizableOperands() {
-        extra_ |= UNOPTIMIZABLE_OPERANDS_BIT;
-    }
 
     // Compiler for this stub kind.
     class Compiler : public ICStubCompiler {
       protected:
         MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm) override;
 
       public:
         explicit Compiler(JSContext* cx)
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -279,24 +279,18 @@ BaselineInspector::maybeInfoForPropertyO
 
         if (!AddReceiver(receiver, receivers, convertUnboxedGroups)) {
             return false;
         }
 
         stub = stub->next();
     }
 
-    if (stub->isGetProp_Fallback()) {
-        if (stub->toGetProp_Fallback()->hadUnoptimizableAccess()) {
-            receivers.clear();
-        }
-    } else {
-        if (stub->toSetProp_Fallback()->hadUnoptimizableAccess()) {
-            receivers.clear();
-        }
+    if (stub->toFallbackStub()->state().hasFailures()) {
+        receivers.clear();
     }
 
     // Don't inline if there are more than 5 receivers.
     if (receivers.length() > 5) {
         receivers.clear();
     }
 
     return true;
@@ -614,17 +608,17 @@ BaselineInspector::expectedCompareType(j
     ICStub* first = monomorphicStub(pc);
     ICStub* second = nullptr;
     if (!first && !dimorphicStub(pc, &first, &second)) {
         return MCompare::Compare_Unknown;
     }
 
     if (ICStub* fallback = second ? second->next() : first->next()) {
         MOZ_ASSERT(fallback->isFallback());
-        if (fallback->toCompare_Fallback()->hadUnoptimizableAccess()) {
+        if (fallback->toFallbackStub()->state().hasFailures()) {
             return MCompare::Compare_Unknown;
         }
     }
 
     MCompare::CompareType first_type = ParseCacheIRStubForCompareType(first->toCacheIR_Regular());
     if (!second) {
         return first_type;
     }
@@ -692,19 +686,18 @@ BaselineInspector::expectedBinaryArithSp
     if (!hasBaselineScript()) {
         return MIRType::None;
     }
 
     MIRType result;
     ICStub* stubs[2];
 
     const ICEntry& entry = icEntryFromPC(pc);
-    ICStub* stub = entry.fallbackStub();
-    if (stub->isBinaryArith_Fallback() &&
-        stub->toBinaryArith_Fallback()->hadUnoptimizableOperands())
+    ICFallbackStub* stub = entry.fallbackStub();
+    if (stub->state().hasFailures())
     {
         return MIRType::None;
     }
 
     stubs[0] = monomorphicStub(pc);
     if (stubs[0]) {
         if (TryToSpecializeBinaryArithOp(stubs, 1, &result)) {
             return result;
@@ -842,17 +835,17 @@ BaselineInspector::getSingleCallee(jsbyt
 
     if (!hasBaselineScript()) {
         return nullptr;
     }
 
     const ICEntry& entry = icEntryFromPC(pc);
     ICStub* stub = entry.firstStub();
 
-    if (entry.fallbackStub()->toCall_Fallback()->hadUnoptimizableCall()) {
+    if (entry.fallbackStub()->state().hasFailures()) {
         return nullptr;
     }
 
     if (!stub->isCall_Scripted() || stub->next() != entry.fallbackStub()) {
         return nullptr;
     }
 
     return stub->toCall_Scripted()->callee();
@@ -1244,23 +1237,19 @@ BaselineInspector::commonGetPropFunction
         if (stub->isCacheIR_Monitored()) {
             if (!AddCacheIRGetPropFunction(stub->toCacheIR_Monitored(), innerized,
                                            holder, holderShape,
                                            commonGetter, globalShape, isOwnProperty, receivers,
                                            convertUnboxedGroups, script))
             {
                 return false;
             }
-        } else if (stub->isGetProp_Fallback()) {
+        } else if (stub->isFallback()) {
             // If we have an unoptimizable access, don't try to optimize.
-            if (stub->toGetProp_Fallback()->hadUnoptimizableAccess()) {
-                return false;
-            }
-        } else if (stub->isGetName_Fallback()) {
-            if (stub->toGetName_Fallback()->hadUnoptimizableAccess()) {
+            if (stub->toFallbackStub()->state().hasFailures()) {
                 return false;
             }
         } else {
             return false;
         }
     }
 
     if (!*commonGetter) {
@@ -1329,30 +1318,21 @@ BaselineInspector::megamorphicGetterSett
                                                    stub->toCacheIR_Updated()->stubInfo(),
                                                    isGetter);
             if (!setter || (*getterOrSetter && *getterOrSetter != setter)) {
                 return false;
             }
             *getterOrSetter = setter;
             continue;
         }
-        if (stub->isGetProp_Fallback()) {
-            if (stub->toGetProp_Fallback()->hadUnoptimizableAccess()) {
-                return false;
-            }
-            if (stub->toGetProp_Fallback()->state().mode() != ICState::Mode::Megamorphic) {
+        if (stub->isFallback()) {
+            if (stub->toFallbackStub()->state().hasFailures()) {
                 return false;
             }
-            continue;
-        }
-        if (stub->isSetProp_Fallback()) {
-            if (stub->toSetProp_Fallback()->hadUnoptimizableAccess()) {
-                return false;
-            }
-            if (stub->toSetProp_Fallback()->state().mode() != ICState::Mode::Megamorphic) {
+            if (stub->toFallbackStub()->state().mode() != ICState::Mode::Megamorphic) {
                 return false;
             }
             continue;
         }
 
         return false;
     }
 
@@ -1482,18 +1462,18 @@ BaselineInspector::commonSetPropFunction
         if (stub->isCacheIR_Updated()) {
             if (!AddCacheIRSetPropFunction(stub->toCacheIR_Updated(),
                                            holder, holderShape,
                                            commonSetter, isOwnProperty, receivers,
                                            convertUnboxedGroups))
             {
                 return false;
             }
-        } else if (!stub->isSetProp_Fallback() ||
-                   stub->toSetProp_Fallback()->hadUnoptimizableAccess())
+        } else if (!stub->isFallback() ||
+                   stub->toFallbackStub()->state().hasFailures())
         {
             // We have an unoptimizable access, so don't try to optimize.
             return false;
         }
     }
 
     if (!*commonSetter) {
         return false;
@@ -1592,17 +1572,17 @@ BaselineInspector::maybeInfoForProtoRead
 
         if (!AddReceiver(receiver, receivers, convertUnboxedGroups)) {
             return false;
         }
 
         stub = stub->next();
     }
 
-    if (stub->toGetProp_Fallback()->hadUnoptimizableAccess()) {
+    if (stub->toFallbackStub()->state().hasFailures()) {
         receivers.clear();
     }
 
     // Don't inline if there are more than 5 receivers.
     if (receivers.length() > 5) {
         receivers.clear();
     }
 
@@ -1636,38 +1616,28 @@ BaselineInspector::expectedPropertyAcces
     if (!hasBaselineScript()) {
         return MIRType::Value;
     }
 
     const ICEntry& entry = icEntryFromPC(pc);
     MIRType type = MIRType::None;
 
     for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
-        MIRType stubType;
-        switch (stub->kind()) {
-          case ICStub::GetProp_Fallback:
-            if (stub->toGetProp_Fallback()->hadUnoptimizableAccess()) {
-                return MIRType::Value;
-            }
-            continue;
-
-          case ICStub::GetElem_Fallback:
-            if (stub->toGetElem_Fallback()->hadUnoptimizableAccess()) {
-                return MIRType::Value;
-            }
-            continue;
-
-          case ICStub::CacheIR_Monitored:
+        MIRType stubType = MIRType::None;
+        if (stub->isCacheIR_Monitored()) {
             stubType = GetCacheIRExpectedInputType(stub->toCacheIR_Monitored());
             if (stubType == MIRType::Value) {
                 return MIRType::Value;
             }
-            break;
-
-          default:
+        } else if (stub->isGetElem_Fallback() || stub->isGetProp_Fallback()) {
+            // If we have an unoptimizable access, don't try to optimize.
+            if (stub->toFallbackStub()->state().hasFailures()) {
+                return MIRType::Value;
+            }
+        } else {
             MOZ_CRASH("Unexpected stub");
         }
 
         if (type != MIRType::None) {
             if (type != stubType) {
                 return MIRType::Value;
             }
         } else {
@@ -1689,17 +1659,17 @@ BaselineInspector::instanceOfData(jsbyte
 
     const ICEntry& entry = icEntryFromPC(pc);
     ICStub* firstStub = entry.firstStub();
 
     // Ensure singleton instanceof stub
     if (!firstStub->next() ||
         !firstStub->isCacheIR_Regular() ||
         !firstStub->next()->isInstanceOf_Fallback() ||
-         firstStub->next()->toInstanceOf_Fallback()->hadUnoptimizableAccess())
+         firstStub->next()->toInstanceOf_Fallback()->state().hasFailures())
          {
              return false;
          }
 
     ICCacheIR_Regular* stub = entry.firstStub()->toCacheIR_Regular();
     CacheIRReader reader(stub->stubInfo());
 
     ObjOperandId rhsId = ObjOperandId(1);
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -2877,17 +2877,17 @@ BindNameIRGenerator::trackAttached(const
     if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
         sp.valueProperty("base", ObjectValue(*env_));
         sp.valueProperty("property", StringValue(name_));
     }
 #endif
 }
 
 HasPropIRGenerator::HasPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
-                                       CacheKind cacheKind, ICState::Mode mode,
+                                       ICState::Mode mode, CacheKind cacheKind,
                                        HandleValue idVal, HandleValue val)
   : IRGenerator(cx, script, pc, cacheKind, mode),
     val_(val),
     idVal_(idVal)
 { }
 
 bool
 HasPropIRGenerator::tryAttachDense(HandleObject obj, ObjOperandId objId,
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -1819,18 +1819,18 @@ class MOZ_RAII HasPropIRGenerator : publ
                                HandleId key, ValOperandId keyId);
     bool tryAttachProxyElement(HandleObject obj, ObjOperandId objId,
                                ValOperandId keyId);
 
     void trackAttached(const char* name);
 
   public:
     // NOTE: Argument order is PROPERTY, OBJECT
-    HasPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind,
-                       ICState::Mode mode, HandleValue idVal, HandleValue val);
+    HasPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, ICState::Mode mode,
+                       CacheKind cacheKind, HandleValue idVal, HandleValue val);
 
     bool tryAttachStub();
 };
 
 class MOZ_RAII InstanceOfIRGenerator : public IRGenerator
 {
     HandleValue lhsVal_;
     HandleObject rhsObj_;
--- a/js/src/jit/ICState.h
+++ b/js/src/jit/ICState.h
@@ -59,16 +59,17 @@ class ICState
     ICState()
       : invalid_(false)
     {
         reset();
     }
 
     Mode mode() const { return mode_; }
     size_t numOptimizedStubs() const { return numOptimizedStubs_; }
+    bool hasFailures() const { return (numFailures_ != 0); }
 
     MOZ_ALWAYS_INLINE bool canAttachStub() const {
         // Note: we cannot assert that numOptimizedStubs_ <= MaxOptimizedStubs
         // because old-style baseline ICs may attach more stubs than
         // MaxOptimizedStubs allows.
         if (mode_ == Mode::Generic || JitOptions.disableCacheIR) {
             return false;
         }
@@ -105,17 +106,20 @@ class ICState
     }
     void trackAttached() {
         // We'd like to assert numOptimizedStubs_ < MaxOptimizedStubs, but
         // since this code is also used for non-CacheIR Baseline stubs, assert
         // < 16 for now. Note that we do have the stronger assert in other
         // methods, because they are only used by CacheIR ICs.
         MOZ_ASSERT(numOptimizedStubs_ < 16);
         numOptimizedStubs_++;
-        numFailures_ = 0;
+        // As a heuristic, reduce the failure count after each successful attach
+        // to delay hitting Generic mode. Reset to 1 instead of 0 so that
+        // BaselineInspector can distinguish no-failures from rare-failures.
+        numFailures_ = std::min(numFailures_, static_cast<uint8_t>(1));
     }
     void trackNotAttached() {
         // Note: we can't assert numFailures_ < maxFailures() because
         // maxFailures() depends on numOptimizedStubs_ and it's possible a
         // GC discarded stubs before we got here.
         numFailures_++;
         MOZ_ASSERT(numFailures_ > 0, "numFailures_ should not overflow");
     }
--- a/js/src/jit/IonIC.cpp
+++ b/js/src/jit/IonIC.cpp
@@ -475,17 +475,17 @@ IonHasOwnIC::update(JSContext* cx, Handl
         ic->discardStubs(cx->zone());
     }
 
     jsbytecode* pc = ic->pc();
 
     if (ic->state().canAttachStub()) {
         bool attached = false;
         RootedScript script(cx, ic->script());
-        HasPropIRGenerator gen(cx, script, pc, CacheKind::HasOwn, ic->state().mode(), idVal, val);
+        HasPropIRGenerator gen(cx, script, pc, ic->state().mode(), CacheKind::HasOwn, idVal, val);
         if (gen.tryAttachStub()) {
             ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript, &attached);
         }
 
         if (!attached) {
             ic->state().trackNotAttached();
         }
     }
@@ -509,17 +509,17 @@ IonInIC::update(JSContext* cx, HandleScr
         ic->discardStubs(cx->zone());
     }
 
     if (ic->state().canAttachStub()) {
         bool attached = false;
         RootedScript script(cx, ic->script());
         RootedValue objV(cx, ObjectValue(*obj));
         jsbytecode* pc = ic->pc();
-        HasPropIRGenerator gen(cx, script, pc, CacheKind::In, ic->state().mode(), key, objV);
+        HasPropIRGenerator gen(cx, script, pc, ic->state().mode(), CacheKind::In, key, objV);
         if (gen.tryAttachStub()) {
             ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript, &attached);
         }
 
         if (!attached) {
             ic->state().trackNotAttached();
         }
     }
--- a/js/src/jsexn.h
+++ b/js/src/jsexn.h
@@ -112,30 +112,16 @@ class AutoClearPendingException
       : cx(cxArg)
     { }
 
     ~AutoClearPendingException() {
         JS_ClearPendingException(cx);
     }
 };
 
-class AutoAssertNoPendingException
-{
-    mozilla::DebugOnly<JSContext*> cx;
-
-  public:
-    explicit AutoAssertNoPendingException(JSContext* cxArg)
-      : cx(cxArg)
-    { }
-
-    ~AutoAssertNoPendingException() {
-        MOZ_ASSERT(!JS_IsExceptionPending(cx));
-    }
-};
-
 extern const char*
 ValueToSourceForError(JSContext* cx, HandleValue val, JS::UniqueChars& bytes);
 
 bool
 GetInternalError(JSContext* cx, unsigned errorNumber, MutableHandleValue error);
 bool
 GetTypeError(JSContext* cx, unsigned errorNumber, MutableHandleValue error);
 
--- a/js/src/jsmath.cpp
+++ b/js/src/jsmath.cpp
@@ -230,17 +230,17 @@ bool
 js::math_atan(JSContext* cx, unsigned argc, Value* vp)
 {
     return math_function<math_atan_impl>(cx, argc, vp);
 }
 
 double
 js::ecmaAtan2(double y, double x)
 {
-    AutoUnsafeCallWithABI unsafe;
+    AutoUnsafeCallWithABI unsafe(UnsafeABIStrictness::AllowPendingExceptions);
     return fdlibm::atan2(y, x);
 }
 
 bool
 js::math_atan2_handle(JSContext* cx, HandleValue y, HandleValue x, MutableHandleValue res)
 {
     double dy;
     if (!ToNumber(cx, y, &dy)) {
@@ -263,17 +263,17 @@ js::math_atan2(JSContext* cx, unsigned a
     CallArgs args = CallArgsFromVp(argc, vp);
 
     return math_atan2_handle(cx, args.get(0), args.get(1), args.rval());
 }
 
 double
 js::math_ceil_impl(double x)
 {
-    AutoUnsafeCallWithABI unsafe;
+    AutoUnsafeCallWithABI unsafe(UnsafeABIStrictness::AllowPendingExceptions);
     return fdlibm::ceil(x);
 }
 
 bool
 js::math_ceil_handle(JSContext* cx, HandleValue v, MutableHandleValue res)
 {
     double d;
     if(!ToNumber(cx, v, &d))
@@ -345,17 +345,17 @@ bool
 js::math_exp(JSContext* cx, unsigned argc, Value* vp)
 {
     return math_function<math_exp_impl>(cx, argc, vp);
 }
 
 double
 js::math_floor_impl(double x)
 {
-    AutoUnsafeCallWithABI unsafe;
+    AutoUnsafeCallWithABI unsafe(UnsafeABIStrictness::AllowPendingExceptions);
     return fdlibm::floor(x);
 }
 
 bool
 js::math_floor_handle(JSContext* cx, HandleValue v, MutableHandleValue r)
 {
     double d;
     if (!ToNumber(cx, v, &d)) {
@@ -438,17 +438,17 @@ js::math_fround(JSContext* cx, unsigned 
 
     return RoundFloat32(cx, args[0], args.rval());
 }
 
 
 double
 js::math_log_impl(double x)
 {
-    AutoUnsafeCallWithABI unsafe;
+    AutoUnsafeCallWithABI unsafe(UnsafeABIStrictness::AllowPendingExceptions);
     return fdlibm::log(x);
 }
 
 bool
 js::math_log_handle(JSContext* cx, HandleValue val, MutableHandleValue res)
 {
     return math_function<math_log_impl>(cx, val, res);
 }
@@ -725,17 +725,17 @@ js::GetBiggestNumberLessThan(T x)
 }
 
 template double js::GetBiggestNumberLessThan<>(double x);
 template float js::GetBiggestNumberLessThan<>(float x);
 
 double
 js::math_round_impl(double x)
 {
-    AutoUnsafeCallWithABI unsafe;
+    AutoUnsafeCallWithABI unsafe(UnsafeABIStrictness::AllowPendingExceptions);
 
     int32_t ignored;
     if (NumberEqualsInt32(x, &ignored)) {
         return x;
     }
 
     /* Some numbers are so big that adding 0.5 would give the wrong number. */
     if (ExponentComponent(x) >= int_fast16_t(FloatingPoint<double>::kExponentShift)) {
@@ -776,17 +776,17 @@ js::math_round(JSContext* cx, unsigned a
     }
 
     return math_round_handle(cx, args[0], args.rval());
 }
 
 double
 js::math_sin_impl(double x)
 {
-    AutoUnsafeCallWithABI unsafe;
+    AutoUnsafeCallWithABI unsafe(UnsafeABIStrictness::AllowPendingExceptions);
     return sin(x);
 }
 
 bool
 js::math_sin_handle(JSContext* cx, HandleValue val, MutableHandleValue res)
 {
     return math_function<math_sin_impl>(cx, val, res);
 }
@@ -809,17 +809,17 @@ js::math_sincos_impl(double x, double *s
     *sin = js::math_sin_impl(x);
     *cos = js::math_cos_impl(x);
 #endif
 }
 
 double
 js::math_sqrt_impl(double x)
 {
-    AutoUnsafeCallWithABI unsafe;
+    AutoUnsafeCallWithABI unsafe(UnsafeABIStrictness::AllowPendingExceptions);
     return sqrt(x);
 }
 
 bool
 js::math_sqrt_handle(JSContext* cx, HandleValue number, MutableHandleValue result)
 {
     return math_function<math_sqrt_impl>(cx, number, result);
 }
@@ -1080,17 +1080,17 @@ js::math_hypot_handle(JSContext* cx, Han
                     scale * sqrt(sumsq);
     res.setDouble(result);
     return true;
 }
 
 double
 js::math_trunc_impl(double x)
 {
-    AutoUnsafeCallWithABI unsafe;
+    AutoUnsafeCallWithABI unsafe(UnsafeABIStrictness::AllowPendingExceptions);
     return fdlibm::trunc(x);
 }
 
 float
 js::math_truncf_impl(float x)
 {
     AutoUnsafeCallWithABI unsafe;
     return fdlibm::truncf(x);
--- a/js/src/vm/JSContext.cpp
+++ b/js/src/vm/JSContext.cpp
@@ -1672,25 +1672,39 @@ AutoEnterOOMUnsafeRegion::crash(size_t s
         if (annotateOOMSizeCallback) {
             annotateOOMSizeCallback(size);
         }
     }
     crash(reason);
 }
 
 #ifdef DEBUG
-AutoUnsafeCallWithABI::AutoUnsafeCallWithABI()
+AutoUnsafeCallWithABI::AutoUnsafeCallWithABI(UnsafeABIStrictness strictness)
   : cx_(TlsContext.get()),
     nested_(cx_->hasAutoUnsafeCallWithABI),
     nogc(cx_)
 {
+    switch(strictness) {
+      case UnsafeABIStrictness::NoExceptions:
+        MOZ_ASSERT(!JS_IsExceptionPending(cx_));
+        checkForPendingException_ = true;
+        break;
+      case UnsafeABIStrictness::AllowPendingExceptions:
+        checkForPendingException_ = !JS_IsExceptionPending(cx_);
+        break;
+      case UnsafeABIStrictness::AllowThrownExceptions:
+        checkForPendingException_ = false;
+        break;
+    }
+
     cx_->hasAutoUnsafeCallWithABI = true;
 }
 
 AutoUnsafeCallWithABI::~AutoUnsafeCallWithABI()
 {
     MOZ_ASSERT(cx_->hasAutoUnsafeCallWithABI);
     if (!nested_) {
         cx_->hasAutoUnsafeCallWithABI = false;
         cx_->inUnsafeCallWithABI = false;
     }
+    MOZ_ASSERT_IF(checkForPendingException_, !JS_IsExceptionPending(cx_));
 }
 #endif
--- a/js/src/vm/JSContext.h
+++ b/js/src/vm/JSContext.h
@@ -1145,36 +1145,37 @@ class MOZ_RAII AutoArrayRooter : private
     friend void JS::AutoGCRooter::trace(JSTracer* trc);
 
   private:
     Value* array_;
     size_t length_;
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
-class AutoAssertNoException
+
+class AutoAssertNoPendingException
 {
 #ifdef DEBUG
-    JSContext* cx;
-    bool hadException;
-#endif
+    JSContext* cx_;
 
   public:
-    explicit AutoAssertNoException(JSContext* cx)
-#ifdef DEBUG
-      : cx(cx),
-        hadException(cx->isExceptionPending())
-#endif
+    explicit AutoAssertNoPendingException(JSContext* cxArg)
+      : cx_(cxArg)
     {
+        MOZ_ASSERT(!JS_IsExceptionPending(cx_));
     }
 
-    ~AutoAssertNoException()
-    {
-        MOZ_ASSERT_IF(!hadException, !cx->isExceptionPending());
+    ~AutoAssertNoPendingException() {
+        MOZ_ASSERT(!JS_IsExceptionPending(cx_));
     }
+#else
+  public:
+    explicit AutoAssertNoPendingException(JSContext* cxArg)
+    {}
+#endif
 };
 
 class MOZ_RAII AutoLockScriptData
 {
     JSRuntime* runtime;
 
   public:
     explicit AutoLockScriptData(JSRuntime* rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
@@ -1256,30 +1257,52 @@ class MOZ_RAII AutoEnterIonCompilation
         cx->ionCompiling = false;
         cx->ionCompilingSafeForMinorGC = false;
 #endif
     }
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
+enum UnsafeABIStrictness {
+    NoExceptions,
+    AllowPendingExceptions,
+    AllowThrownExceptions
+};
+
 // Should be used in functions called directly from JIT code (with
 // masm.callWithABI) to assert invariants in debug builds.
+// In debug mode, masm.callWithABI inserts code to verify that the
+// callee function uses AutoUnsafeCallWithABI.
+// While this object is live:
+// 1. cx->hasAutoUnsafeCallWithABI must be true.
+// 2. We can't GC.
+// 3. Exceptions should not be pending/thrown.
+//
+// Note that #3 is a precaution, not a requirement. By default, we
+// assert that the function is not called with a pending exception,
+// and that it does not throw an exception itself.
 class MOZ_RAII AutoUnsafeCallWithABI
 {
 #ifdef DEBUG
     JSContext* cx_;
     bool nested_;
+    bool checkForPendingException_;
 #endif
     JS::AutoCheckCannotGC nogc;
 
   public:
 #ifdef DEBUG
-    AutoUnsafeCallWithABI();
+    explicit AutoUnsafeCallWithABI(UnsafeABIStrictness strictness =
+                                   UnsafeABIStrictness::NoExceptions);
     ~AutoUnsafeCallWithABI();
+#else
+    explicit AutoUnsafeCallWithABI(UnsafeABIStrictness unused_ =
+                                   UnsafeABIStrictness::NoExceptions)
+    {}
 #endif
 };
 
 namespace gc {
 
 // In debug builds, set/unset the performing GC flag for the current thread.
 struct MOZ_RAII AutoSetThreadIsPerformingGC
 {
--- a/js/src/vm/JSObject.cpp
+++ b/js/src/vm/JSObject.cpp
@@ -2462,17 +2462,17 @@ js::LookupName(JSContext* cx, HandleProp
     propp.setNotFound();
     return true;
 }
 
 bool
 js::LookupNameNoGC(JSContext* cx, PropertyName* name, JSObject* envChain,
                    JSObject** objp, JSObject** pobjp, PropertyResult* propp)
 {
-    AutoAssertNoException nogc(cx);
+    AutoAssertNoPendingException nogc(cx);
 
     MOZ_ASSERT(!*objp && !*pobjp && !*propp);
 
     for (JSObject* env = envChain; env; env = env->enclosingEnvironment()) {
         if (env->getOpsLookupProperty()) {
             return false;
         }
         if (!LookupPropertyInline<NoGC>(cx, &env->as<NativeObject>(), NameToId(name), pobjp, propp)) {
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -2560,17 +2560,17 @@ js::NativeGetProperty(JSContext* cx, Han
                       MutableHandleValue vp)
 {
     return NativeGetPropertyInline<CanGC>(cx, obj, receiver, id, NotNameLookup, vp);
 }
 
 bool
 js::NativeGetPropertyNoGC(JSContext* cx, NativeObject* obj, const Value& receiver, jsid id, Value* vp)
 {
-    AutoAssertNoException noexc(cx);
+    AutoAssertNoPendingException noexc(cx);
     return NativeGetPropertyInline<NoGC>(cx, obj, receiver, id, NotNameLookup, vp);
 }
 
 bool
 js::NativeGetElement(JSContext* cx, HandleNativeObject obj, HandleValue receiver,
                      int32_t index, MutableHandleValue vp)
 {
     RootedId id(cx);
--- a/mobile/android/chrome/jar.mn
+++ b/mobile/android/chrome/jar.mn
@@ -56,17 +56,16 @@ chrome.jar:
 
 % content branding %content/branding/
 
 % override chrome://global/content/config.xul chrome://browser/content/config.xhtml
 % override chrome://global/content/netError.xhtml chrome://browser/content/netError.xhtml
 % override chrome://mozapps/content/extensions/extensions.xul chrome://browser/content/aboutAddons.xhtml
 
 # L10n resource overrides.
-% override chrome://global/locale/aboutAbout.dtd chrome://browser/locale/overrides/aboutAbout.dtd
 % override chrome://global/locale/aboutReader.properties chrome://browser/locale/overrides/aboutReader.properties
 % override chrome://global/locale/aboutRights.dtd chrome://browser/locale/overrides/aboutRights.dtd
 % override chrome://global/locale/charsetMenu.properties chrome://browser/locale/overrides/charsetMenu.properties
 % override chrome://global/locale/commonDialogs.properties chrome://browser/locale/overrides/commonDialogs.properties
 % override chrome://global/locale/intl.properties chrome://browser/locale/overrides/intl.properties
 % override chrome://global/locale/intl.css chrome://browser/locale/overrides/intl.css
 % override chrome://global/locale/search/search.properties chrome://browser/locale/overrides/search/search.properties
 % override chrome://pluginproblem/locale/pluginproblem.dtd chrome://browser/locale/overrides/plugins/pluginproblem.dtd
--- a/mobile/android/locales/filter.py
+++ b/mobile/android/locales/filter.py
@@ -13,17 +13,16 @@ def test(mod, path, entity=None):
     # ignore anything but mobile, which is our local repo checkout name
     if mod not in ("dom", "toolkit", "mobile",
                    "mobile/android/base",  "mobile/android"):
         return "ignore"
 
     if mod == "toolkit":
         # keep this file list in sync with jar.mn
         if path in (
-            "chrome/global/aboutAbout.dtd",
             "chrome/global/aboutReader.properties",
             "chrome/global/aboutRights.dtd",
             "chrome/global/charsetMenu.properties",
             "chrome/global/commonDialogs.properties",
             "chrome/global/intl.properties",
             "chrome/global/intl.css",
             "chrome/search/search.properties",
             "chrome/pluginproblem/pluginproblem.dtd",
@@ -33,16 +32,19 @@ def test(mod, path, entity=None):
             "chrome/global/aboutTelemetry.dtd",
             "chrome/global/aboutTelemetry.properties",
             "chrome/global/aboutWebrtc.properties",
         ):
             return "error"
         if re.match(r"crashreporter/[^/]*.ftl", path):
             # error on crashreporter/*.ftl
             return "error"
+        if re.match(r"toolkit/about/[^/]*About.ftl", path):
+            # error on toolkit/about/*About.ftl
+            return "error"
         return "ignore"
 
     if mod == "dom":
         # keep this file list in sync with jar.mn
         if path in (
             "chrome/global.dtd",
             "chrome/accessibility/AccessFu.properties",
             "chrome/dom/dom.properties",
--- a/mobile/android/locales/jar.mn
+++ b/mobile/android/locales/jar.mn
@@ -32,17 +32,16 @@
   locale/@AB_CD@/browser/passwordmgr.properties   (%chrome/passwordmgr.properties)
 #if MOZ_UPDATE_CHANNEL != release && MOZ_UPDATE_CHANNEL != esr
   locale/@AB_CD@/browser/webcompatReporter.properties (%chrome/webcompatReporter.properties)
 #endif
 
 # overrides for toolkit l10n, also for en-US
 # keep this file list in sync with l10n.toml and filter.py
 relativesrcdir toolkit/locales:
-  locale/@AB_CD@/browser/overrides/aboutAbout.dtd                  (%chrome/global/aboutAbout.dtd)
   locale/@AB_CD@/browser/overrides/aboutReader.properties          (%chrome/global/aboutReader.properties)
   locale/@AB_CD@/browser/overrides/aboutRights.dtd                 (%chrome/global/aboutRights.dtd)
   locale/@AB_CD@/browser/overrides/charsetMenu.properties          (%chrome/global/charsetMenu.properties)
   locale/@AB_CD@/browser/overrides/commonDialogs.properties        (%chrome/global/commonDialogs.properties)
   locale/@AB_CD@/browser/overrides/intl.properties                 (%chrome/global/intl.properties)
   locale/@AB_CD@/browser/overrides/intl.css                        (%chrome/global/intl.css)
   locale/@AB_CD@/browser/overrides/search/search.properties        (%chrome/search/search.properties)
 # plugins
@@ -74,8 +73,10 @@ relativesrcdir dom/locales:
 
 #define EN_US en-US
 #if AB_CD != EN_US
 [localization] @AB_CD@.jar:
 relativesrcdir toolkit/locales:
 #about:crashes
   crashreporter                                    (%crashreporter/**/*.ftl)
 #endif
+#about:about
+  toolkit                                          (%toolkit/about/*About.ftl)
--- a/mobile/android/locales/l10n.toml
+++ b/mobile/android/locales/l10n.toml
@@ -153,16 +153,20 @@ exclude-multi-locale = [
     reference = "dom/locales/en-US/chrome/dom/dom.properties"
     l10n = "{l}dom/chrome/dom/dom.properties"
 
 [[paths]]
     reference = "dom/locales/en-US/chrome/plugins.properties"
     l10n = "{l}dom/chrome/plugins.properties"
 
 [[paths]]
+    reference = "toolkit/locales/en-US/toolkit/about/*About.ftl"
+    l10n = "{l}toolkit/toolkit/about/*About.ftl"
+
+[[paths]]
     reference = "toolkit/locales/en-US/chrome/global/aboutAbout.dtd"
     l10n = "{l}toolkit/chrome/global/aboutAbout.dtd"
 
 [[paths]]
     reference = "toolkit/locales/en-US/chrome/global/aboutReader.properties"
     l10n = "{l}toolkit/chrome/global/aboutReader.properties"
 
 [[paths]]
--- a/mobile/locales/filter.py
+++ b/mobile/locales/filter.py
@@ -13,17 +13,16 @@ def test(mod, path, entity=None):
     # ignore anything but mobile, which is our local repo checkout name
     if mod not in ("dom", "toolkit", "mobile",
                    "mobile/android/base",  "mobile/android"):
         return "ignore"
 
     if mod == "toolkit":
         # keep this file list in sync with jar.mn
         if path in (
-            "chrome/global/aboutAbout.dtd",
             "chrome/global/aboutReader.properties",
             "chrome/global/aboutRights.dtd",
             "chrome/global/charsetMenu.properties",
             "chrome/global/commonDialogs.properties",
             "chrome/global/intl.properties",
             "chrome/global/intl.css",
             "chrome/search/search.properties",
             "chrome/pluginproblem/pluginproblem.dtd",
@@ -33,16 +32,20 @@ def test(mod, path, entity=None):
             "chrome/global/aboutTelemetry.dtd",
             "chrome/global/aboutTelemetry.properties",
             "chrome/global/aboutWebrtc.properties",
         ):
             return "error"
         if re.match(r"crashreporter/[^/]*.ftl", path):
             # error on crashreporter/*.ftl
             return "error"
+
+        if re.match(r"toolkit/about/[^/]*About.ftl", path):
+            # error on toolkit/about/*About.ftl
+            return "error"
         return "ignore"
 
     if mod == "dom":
         # keep this file list in sync with jar.mn
         if path in (
             "chrome/global.dtd",
             "chrome/accessibility/AccessFu.properties",
             "chrome/dom/dom.properties",
--- a/netwerk/dns/nsHostResolver.cpp
+++ b/netwerk/dns/nsHostResolver.cpp
@@ -233,16 +233,20 @@ nsHostRecord::CheckExpiration(const mozi
 
     return nsHostRecord::EXP_EXPIRED;
 }
 
 void
 nsHostRecord::SetExpiration(const mozilla::TimeStamp& now, unsigned int valid, unsigned int grace)
 {
     mValidStart = now;
+    if ((valid + grace) < 60) {
+        grace = 60 - valid;
+        LOG(("SetExpiration: artificially bumped grace to %d\n", grace));
+    }
     mGraceStart = now + TimeDuration::FromSeconds(valid);
     mValidEnd = now + TimeDuration::FromSeconds(valid + grace);
 }
 
 void
 nsHostRecord::CopyExpirationTimesAndFlagsFrom(const nsHostRecord *aFromHostRecord)
 {
     // This is used to copy information from a cache entry to a record. All
new file mode 100644
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1486934_aboutAbout.py
@@ -0,0 +1,21 @@
+# coding=utf8
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from __future__ import absolute_import
+import fluent.syntax.ast as FTL
+from fluent.migrate.helpers import transforms_from
+
+def migrate(ctx):
+    """Bug 1486934 - Modify about:about to use fluent for localization, part {index}."""
+
+    ctx.add_transforms(
+        "toolkit/toolkit/about/aboutAbout.ftl",
+        "toolkit/toolkit/about/aboutAbout.ftl",
+        transforms_from(
+"""
+about-about-title = { COPY("toolkit/chrome/global/aboutAbout.dtd", "aboutAbout.title") }
+about-about-note = { COPY("toolkit/chrome/global/aboutAbout.dtd", "aboutAbout.note", trim:"True") }
+""")
+)
--- a/taskcluster/ci/beetmover-l10n/kind.yml
+++ b/taskcluster/ci/beetmover-l10n/kind.yml
@@ -1,29 +1,32 @@
 # 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/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
-   - taskgraph.transforms.beetmover_l10n:transforms
-   - taskgraph.transforms.name_sanity:transforms
-   - taskgraph.transforms.beetmover:transforms
-   - taskgraph.transforms.task:transforms
+    - taskgraph.transforms.beetmover_l10n:transforms
+    - taskgraph.transforms.name_sanity:transforms
+    - taskgraph.transforms.beetmover:transforms
+    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
-   - nightly-l10n-signing
+    - nightly-l10n-signing
 
 only-for-attributes:
-   - nightly
+    - nightly
 
 not-for-build-platforms:
-   - linux-nightly/opt
-   - linux64-nightly/opt
-   - macosx64-nightly/opt
-   - win32-nightly/opt
-   - win64-nightly/opt
-   - linux-devedition-nightly/opt
-   - linux64-devedition-nightly/opt
-   - macosx64-devedition-nightly/opt
-   - win32-devedition-nightly/opt
-   - win64-devedition-nightly/opt
+    - linux-nightly/opt
+    - linux64-nightly/opt
+    - macosx64-nightly/opt
+    - win32-nightly/opt
+    - win64-nightly/opt
+    - linux-devedition-nightly/opt
+    - linux64-devedition-nightly/opt
+    - macosx64-devedition-nightly/opt
+    - win32-devedition-nightly/opt
+    - win64-devedition-nightly/opt
+
+job-template:
+    shipping-phase: promote
--- a/taskcluster/ci/beetmover/kind.yml
+++ b/taskcluster/ci/beetmover/kind.yml
@@ -1,30 +1,33 @@
 # 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/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
-   - taskgraph.transforms.name_sanity:transforms
-   - taskgraph.transforms.beetmover:transforms
-   - taskgraph.transforms.task:transforms
+    - taskgraph.transforms.name_sanity:transforms
+    - taskgraph.transforms.beetmover:transforms
+    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
-   - build-signing
+    - build-signing
 
 only-for-attributes:
-   - nightly
+    - nightly
 
 not-for-build-platforms:
-   - linux-nightly/opt
-   - linux64-nightly/opt
-   - macosx64-nightly/opt
-   - win32-nightly/opt
-   - win64-nightly/opt
-   - linux-devedition-nightly/opt
-   - linux64-devedition-nightly/opt
-   - macosx64-devedition-nightly/opt
-   - win32-devedition-nightly/opt
-   - win64-devedition-nightly/opt
-   - linux64-asan-reporter-nightly/opt
-   - win64-asan-reporter-nightly/opt
+    - linux-nightly/opt
+    - linux64-nightly/opt
+    - macosx64-nightly/opt
+    - win32-nightly/opt
+    - win64-nightly/opt
+    - linux-devedition-nightly/opt
+    - linux64-devedition-nightly/opt
+    - macosx64-devedition-nightly/opt
+    - win32-devedition-nightly/opt
+    - win64-devedition-nightly/opt
+    - linux64-asan-reporter-nightly/opt
+    - win64-asan-reporter-nightly/opt
+
+job-template:
+    shipping-phase: promote
--- a/taskcluster/ci/build/android.yml
+++ b/taskcluster/ci/build/android.yml
@@ -224,17 +224,17 @@ android-x86-fuzzing/debug:
         - linux64-sccache
         - linux64-node
 
 android-x86-nightly/opt:
     description: "Android 4.2 x86 Nightly"
     attributes:
         nightly: true
         enable-full-crashsymbols: true
-    shipping-phase: promote
+    shipping-phase: build
     shipping-product: fennec
     index:
         product: mobile
         job-name: android-x86-opt
         type: nightly
     treeherder:
         platform: android-4-2-x86/opt
         symbol: N
@@ -397,17 +397,17 @@ android-api-16-without-google-play-servi
         - linux64-sccache
         - linux64-node
 
 android-api-16-nightly/opt:
     description: "Android 4.0 api-16+ Nightly"
     attributes:
         nightly: true
         enable-full-crashsymbols: true
-    shipping-phase: promote
+    shipping-phase: build
     shipping-product: fennec
     index:
         product: mobile
         job-name: android-api-16-opt
         type: nightly-with-multi-l10n
     treeherder:
         platform: android-4-0-armv7-api16/opt
         symbol: N
@@ -516,17 +516,17 @@ android-aarch64/opt:
         - linux64-sccache
         - linux64-node
 
 android-aarch64-nightly/opt:
     description: "Android 5.0 AArch64 Nightly"
     attributes:
         nightly: true
         enable-full-crashsymbols: true
-    shipping-phase: promote
+    shipping-phase: build
     shipping-product: fennec
     index:
         product: mobile
         job-name: android-aarch64-opt
         type: nightly
     treeherder:
         platform: android-5-0-aarch64/opt
         symbol: N
@@ -635,17 +635,17 @@ android-x86_64/opt:
         - linux64-sccache
         - linux64-node
 
 android-x86_64-nightly/opt:
     description: "Android 5.0 x86-64 Nightly"
     attributes:
         nightly: true
         enable-full-crashsymbols: true
-    shipping-phase: promote
+    shipping-phase: build
     shipping-product: fennec
     index:
         product: mobile
         job-name: android-x86_64-opt
         type: nightly
     treeherder:
         platform: android-5-0-x86_64/opt
         symbol: N
--- a/taskcluster/ci/release-final-verify/kind.yml
+++ b/taskcluster/ci/release-final-verify/kind.yml
@@ -20,21 +20,17 @@ job-defaults:
    worker:
       implementation: docker-worker
       os: linux
       docker-image:
          in-tree: "update-verify"
       max-run-time: 7200
       retry-exit-status: [1]
       env:
-         BUILD_TOOLS_REPO:
-            by-project:
-               jamun: https://hg.mozilla.org/users/stage-ffxbld/tools
-               maple: https://hg.mozilla.org/users/asasaki_mozilla.com/tools
-               default: https://hg.mozilla.org/build/tools
+         BUILD_TOOLS_REPO: https://hg.mozilla.org/build/tools
 
 jobs:
    firefox:
       description: final verify
       shipping-phase: push
       shipping-product: firefox
       treeherder:
          platform: firefox-release/opt
--- a/taskcluster/ci/release-secondary-final-verify/kind.yml
+++ b/taskcluster/ci/release-secondary-final-verify/kind.yml
@@ -22,21 +22,17 @@ job-defaults:
    worker:
       implementation: docker-worker
       os: linux
       docker-image:
          in-tree: "update-verify"
       max-run-time: 7200
       retry-exit-status: [1]
       env:
-         BUILD_TOOLS_REPO:
-            by-project:
-               jamun: https://hg.mozilla.org/users/stage-ffxbld/tools
-               maple: https://hg.mozilla.org/users/asasaki_mozilla.com/tools
-               default: https://hg.mozilla.org/build/tools
+         BUILD_TOOLS_REPO: https://hg.mozilla.org/build/tools
 
 jobs:
    firefox:
       description: secondary final verify
       shipping-phase: promote
       shipping-product: firefox
       treeherder:
          platform: linux64/opt
--- a/taskcluster/ci/release-secondary-update-verify/kind.yml
+++ b/taskcluster/ci/release-secondary-update-verify/kind.yml
@@ -29,22 +29,17 @@ job-defaults:
       os: linux
       docker-image:
          in-tree: "update-verify"
       max-run-time: 7200
       retry-exit-status:
          - 255
       env:
          NO_BBCONFIG: "1"
-         BUILD_TOOLS_REPO:
-            by-project:
-               birch: https://hg.mozilla.org/users/bhearsum_mozilla.com/tools
-               jamun: https://hg.mozilla.org/users/stage-ffxbld/tools
-               maple: https://hg.mozilla.org/users/asasaki_mozilla.com/tools
-               default: https://hg.mozilla.org/build/tools
+         BUILD_TOOLS_REPO: https://hg.mozilla.org/build/tools
          CHANNEL: "beta-localtest"
    extra:
       chunks: 12
 
 jobs:
    firefox-secondary-linux64:
       description: linux64 secondary channel update verify
       shipping-product: firefox
--- a/taskcluster/ci/release-update-verify/kind.yml
+++ b/taskcluster/ci/release-update-verify/kind.yml
@@ -29,22 +29,17 @@ job-defaults:
       os: linux
       docker-image:
          in-tree: "update-verify"
       max-run-time: 3600
       retry-exit-status:
          - 255
       env:
          NO_BBCONFIG: "1"
-         BUILD_TOOLS_REPO:
-            by-project:
-               birch: https://hg.mozilla.org/users/bhearsum_mozilla.com/tools
-               jamun: https://hg.mozilla.org/users/stage-ffxbld/tools
-               maple: https://hg.mozilla.org/users/asasaki_mozilla.com/tools
-               default: https://hg.mozilla.org/build/tools
+         BUILD_TOOLS_REPO: https://hg.mozilla.org/build/tools
    extra:
       chunks: 12
 
 jobs:
    firefox-linux64:
       description: linux64 update verify
       shipping-product: firefox
       worker:
--- a/taskcluster/taskgraph/target_tasks.py
+++ b/taskcluster/taskgraph/target_tasks.py
@@ -57,18 +57,16 @@ def filter_beta_release_tasks(task, para
             # On beta, Nightly builds are already PGOs
             'linux-pgo', 'linux64-pgo',
             'win32-pgo', 'win64-pgo',
             # ASAN is central-only
             'linux64-asan-reporter-nightly',
             'win64-asan-reporter-nightly',
             ):
         return False
-    if str(platform).startswith('android') and 'nightly' in str(platform):
-        return False
 
     if platform in (
             'linux', 'linux64',
             'macosx64',
             'win32', 'win64',
             ):
         if task.attributes['kind'] == 'l10n':
             # This is on-change l10n
@@ -305,33 +303,20 @@ def target_tasks_mozilla_esr60(full_task
 
 
 @_target_task('promote_desktop')
 def target_tasks_promote_desktop(full_task_graph, parameters, graph_config):
     """Select the superset of tasks required to promote a beta or release build
     of a desktop product. This should include all non-android
     mozilla_{beta,release} tasks, plus l10n, beetmover, balrog, etc."""
 
-    beta_release_tasks = [l for l, t in full_task_graph.tasks.iteritems() if
-                          filter_beta_release_tasks(t, parameters,
-                                                    ignore_kinds=[],
-                                                    allow_l10n=True)]
-
     def filter(task):
         if task.attributes.get('shipping_product') != parameters['release_product']:
             return False
 
-        # Allow for {beta,release}_tasks; these will get optimized out to point
-        # to the previous graph using ``previous_graph_ids`` and
-        # ``previous_graph_kinds``.
-        # At some point this should filter by shipping_phase == 'build' and
-        # shipping_product matches.
-        if task.label in beta_release_tasks:
-            return True
-
         # 'secondary' balrog/update verify/final verify tasks only run for RCs
         if parameters.get('release_type') != 'release-rc':
             if 'secondary' in task.kind:
                 return False
 
         if task.attributes.get('shipping_phase') == 'promote':
             return True
 
@@ -390,27 +375,22 @@ def target_tasks_ship_desktop(full_task_
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(t)]
 
 
 @_target_task('promote_fennec')
 def target_tasks_promote_fennec(full_task_graph, parameters, graph_config):
     """Select the set of tasks required for a candidates build of fennec. The
     nightly build process involves a pipeline of builds, signing,
     and, eventually, uploading the tasks to balrog."""
-    filtered_for_project = target_tasks_nightly_fennec(full_task_graph, parameters, graph_config)
 
     def filter(task):
         attr = task.attributes.get
         # Don't ship single locale fennec anymore - Bug 1408083
         if attr("locale") or attr("chunk_locales"):
             return False
-        if task.label in filtered_for_project:
-            if task.kind not in ('balrog', 'push-apk'):
-                if task.attributes.get('nightly'):
-                    return True
         if task.attributes.get('shipping_product') == 'fennec' and \
                 task.attributes.get('shipping_phase') == 'promote':
             return True
 
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(full_task_graph[l])]
 
 
 @_target_task('ship_fennec')
--- a/taskcluster/taskgraph/transforms/beetmover.py
+++ b/taskcluster/taskgraph/transforms/beetmover.py
@@ -7,17 +7,16 @@ Transform the beetmover task into an act
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.schema import validate_schema, Schema
 from taskgraph.util.scriptworker import (get_beetmover_bucket_scope,
                                          get_beetmover_action_scope,
-                                         get_phase,
                                          get_worker_type_for_scope)
 from taskgraph.util.taskcluster import get_artifact_prefix
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Any, Required, Optional
 
 
 # Until bug 1331141 is fixed, if you are adding any new artifacts here that
 # need to be transfered to S3, please be aware you also need to follow-up
@@ -135,17 +134,17 @@ beetmover_description_schema = Schema({
     # treeherder is allowed here to override any defaults we use for beetmover.  See
     # taskcluster/taskgraph/transforms/task.py for the schema details, and the
     # below transforms for defaults of various values.
     Optional('treeherder'): task_description_schema['treeherder'],
 
     # locale is passed only for l10n beetmoving
     Optional('locale'): basestring,
 
-    Optional('shipping-phase'): task_description_schema['shipping-phase'],
+    Required('shipping-phase'): task_description_schema['shipping-phase'],
     Optional('shipping-product'): task_description_schema['shipping-product'],
 })
 
 
 @transforms.add
 def validate(config, jobs):
     for job in jobs:
         label = job.get('dependent-task', object).__dict__.get('label', '?no-label?')
@@ -193,28 +192,27 @@ def make_task_description(config, jobs):
 
         attributes = copy_attributes_from_dependent_job(dep_job)
 
         if job.get('locale'):
             attributes['locale'] = job['locale']
 
         bucket_scope = get_beetmover_bucket_scope(config)
         action_scope = get_beetmover_action_scope(config)
-        phase = get_phase(config)
 
         task = {
             'label': label,
             'description': description,
             'worker-type': get_worker_type_for_scope(config, bucket_scope),
             'scopes': [bucket_scope, action_scope],
             'dependencies': dependencies,
             'attributes': attributes,
             'run-on-projects': dep_job.attributes.get('run_on_projects'),
             'treeherder': treeherder,
-            'shipping-phase': phase,
+            'shipping-phase': job['shipping-phase'],
         }
 
         yield task
 
 
 def generate_upstream_artifacts(job, signing_task_ref, build_task_ref, platform,
                                 locale=None):
     build_mapping = UPSTREAM_ARTIFACT_UNSIGNED_PATHS
--- a/taskcluster/taskgraph/transforms/beetmover_geckoview.py
+++ b/taskcluster/taskgraph/transforms/beetmover_geckoview.py
@@ -7,18 +7,17 @@ Transform the beetmover task into an act
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.transforms.beetmover import \
     craft_release_properties as beetmover_craft_release_properties
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.schema import validate_schema, Schema, resolve_keyed_by, optionally_keyed_by
-from taskgraph.util.scriptworker import (get_phase,
-                                         get_worker_type_for_scope)
+from taskgraph.util.scriptworker import get_worker_type_for_scope
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Required, Optional
 
 
 _ARTIFACT_ID_PER_PLATFORM = {
     'android-aarch64': 'geckoview{update_channel}-arm64-v8a',
     'android-api-16': 'geckoview{update_channel}-armeabi-v7a',
     'android-x86': 'geckoview{update_channel}-x86',
@@ -99,17 +98,17 @@ def make_task_description(config, jobs):
             'label': label,
             'description': description,
             'worker-type': get_worker_type_for_scope(config, job['bucket-scope']),
             'scopes': [job['bucket-scope'], 'project:releng:beetmover:action:push-to-maven'],
             'dependencies': dependencies,
             'attributes': attributes,
             'run-on-projects': ['mozilla-central'],
             'treeherder': treeherder,
-            'shipping-phase': job.get('shipping-phase', get_phase(config)),
+            'shipping-phase': job['shipping-phase'],
         }
 
         yield task
 
 
 def generate_upstream_artifacts(build_task_ref):
     return [{
         'taskId': {'task-reference': build_task_ref},
--- a/taskcluster/taskgraph/transforms/beetmover_l10n.py
+++ b/taskcluster/taskgraph/transforms/beetmover_l10n.py
@@ -27,10 +27,11 @@ def make_beetmover_description(config, j
             treeherder = {
                 'symbol': join_symbol(group, symbol),
             }
 
             beet_description = {
                 'dependent-task': dep_job,
                 'treeherder': treeherder,
                 'locale': locale,
+                'shipping-phase': job['shipping-phase'],
             }
             yield beet_description
--- a/taskcluster/taskgraph/transforms/beetmover_push_to_release.py
+++ b/taskcluster/taskgraph/transforms/beetmover_push_to_release.py
@@ -7,17 +7,17 @@ Transform the beetmover-push-to-release 
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.schema import (
      validate_schema, Schema,
 )
 from taskgraph.util.scriptworker import (
-    get_beetmover_bucket_scope, get_beetmover_action_scope,
+    get_beetmover_bucket_scope, add_scope_prefix,
     get_worker_type_for_scope,
 )
 from taskgraph.transforms.job import job_description_schema
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Any, Required, Optional
 
 # Voluptuous uses marker objects as dictionary *keys*, but they are not
 # comparable, so we cast all of the keys back to regular strings
@@ -69,17 +69,17 @@ def make_beetmover_push_to_release_descr
         label = job['name']
         description = (
             "Beetmover push to release for '{product}'".format(
                 product=job['product']
             )
         )
 
         bucket_scope = get_beetmover_bucket_scope(config)
-        action_scope = get_beetmover_action_scope(config)
+        action_scope = add_scope_prefix(config, 'beetmover:action:push-to-releases')
 
         task = {
             'label': label,
             'description': description,
             'worker-type': get_worker_type_for_scope(config, bucket_scope),
             'scopes': [bucket_scope, action_scope],
             'product': job['product'],
             'dependencies': job['dependencies'],
--- a/taskcluster/taskgraph/transforms/beetmover_repackage.py
+++ b/taskcluster/taskgraph/transforms/beetmover_repackage.py
@@ -11,17 +11,16 @@ from taskgraph.transforms.base import Tr
 from taskgraph.transforms.beetmover import craft_release_properties
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.partials import (get_balrog_platform_name,
                                      get_partials_artifacts,
                                      get_partials_artifact_map)
 from taskgraph.util.schema import validate_schema, Schema
 from taskgraph.util.scriptworker import (get_beetmover_bucket_scope,
                                          get_beetmover_action_scope,
-                                         get_phase,
                                          get_worker_type_for_scope)
 from taskgraph.util.taskcluster import get_artifact_prefix
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Any, Required, Optional
 
 import logging
 import re
 
@@ -166,17 +165,17 @@ beetmover_description_schema = Schema({
 
     # treeherder is allowed here to override any defaults we use for beetmover.  See
     # taskcluster/taskgraph/transforms/task.py for the schema details, and the
     # below transforms for defaults of various values.
     Optional('treeherder'): task_description_schema['treeherder'],
 
     # locale is passed only for l10n beetmoving
     Optional('locale'): basestring,
-    Optional('shipping-phase'): task_description_schema['shipping-phase'],
+    Required('shipping-phase'): task_description_schema['shipping-phase'],
     Optional('shipping-product'): task_description_schema['shipping-product'],
 })
 
 
 @transforms.add
 def validate(config, jobs):
     for job in jobs:
         label = job.get('dependent-task', object).__dict__.get('label', '?no-label?')
@@ -236,28 +235,27 @@ def make_task_description(config, jobs):
             dependencies["repackage-signing"] = job['grandparent-tasks'][repackage_signing_name]
 
         attributes = copy_attributes_from_dependent_job(dep_job)
         if job.get('locale'):
             attributes['locale'] = job['locale']
 
         bucket_scope = get_beetmover_bucket_scope(config)
         action_scope = get_beetmover_action_scope(config)
-        phase = get_phase(config)
 
         task = {
             'label': label,
             'description': description,
             'worker-type': get_worker_type_for_scope(config, bucket_scope),
             'scopes': [bucket_scope, action_scope],
             'dependencies': dependencies,
             'attributes': attributes,
             'run-on-projects': dep_job.attributes.get('run_on_projects'),
             'treeherder': treeherder,
-            'shipping-phase': job.get('shipping-phase', phase),
+            'shipping-phase': job['shipping-phase'],
             'shipping-product': job.get('shipping-product'),
         }
 
         yield task
 
 
 def generate_upstream_artifacts(job, dependencies, platform, locale=None, project=None):
 
--- a/taskcluster/taskgraph/transforms/release_generate_checksums_beetmover.py
+++ b/taskcluster/taskgraph/transforms/release_generate_checksums_beetmover.py
@@ -6,17 +6,16 @@ Transform the `release-generate-checksum
 """
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.schema import validate_schema, Schema
 from taskgraph.util.scriptworker import (get_beetmover_bucket_scope,
                                          get_beetmover_action_scope,
-                                         get_phase,
                                          get_worker_type_for_scope,
                                          )
 from taskgraph.util.taskcluster import get_artifact_prefix
 from taskgraph.transforms.beetmover import craft_release_properties
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Required, Optional
 
 transforms = TransformSequence()
@@ -99,28 +98,27 @@ def make_task_description(config, jobs):
         if len(dep_job.dependencies) > 1:
             raise NotImplementedError(
                 "Can't beetmove a signing task with multiple dependencies")
         # update the dependencies with the dependencies of the signing task
         dependencies.update(dep_job.dependencies)
 
         bucket_scope = get_beetmover_bucket_scope(config)
         action_scope = get_beetmover_action_scope(config)
-        phase = get_phase(config)
 
         task = {
             'label': label,
             'description': description,
             'worker-type': get_worker_type_for_scope(config, bucket_scope),
             'scopes': [bucket_scope, action_scope],
             'dependencies': dependencies,
             'attributes': attributes,
             'run-on-projects': dep_job.attributes.get('run_on_projects'),
             'treeherder': treeherder,
-            'shipping-phase': phase,
+            'shipping-phase': 'promote',
         }
 
         yield task
 
 
 def generate_upstream_artifacts(job, signing_task_ref, build_task_ref):
     build_mapping = CHECKSUMS_BUILD_ARTIFACTS
     signing_mapping = CHECKSUMS_SIGNING_ARTIFACTS
--- a/taskcluster/taskgraph/util/scriptworker.py
+++ b/taskcluster/taskgraph/util/scriptworker.py
@@ -71,101 +71,44 @@ DEVEDITION_SIGNING_CERT_SCOPES = {
     'default': 'signing:cert:dep-signing',
 }
 
 """Map beetmover scope aliases to sets of projects.
 """
 BEETMOVER_SCOPE_ALIAS_TO_PROJECT = [[
     'all-nightly-branches', set([
         'mozilla-central',
-        'mozilla-beta',
-        'mozilla-release',
-        'mozilla-esr60',
         'comm-central',
     ])
 ], [
     'all-release-branches', set([
         'mozilla-beta',
         'mozilla-release',
         'mozilla-esr60',
         'comm-beta',
         'comm-esr60',
     ])
 ]]
 
-"""The set of all beetmover release target tasks.
-
-Used for both `BEETMOVER_SCOPE_ALIAS_TO_TARGET_TASK` and `get_release_build_number`
-"""
-BEETMOVER_CANDIDATES_TARGET_TASKS = set([
-    'promote_fennec',
-    'promote_desktop',
-])
-BEETMOVER_PUSH_TARGET_TASKS = set([
-    'push_fennec',
-    'ship_fennec',
-    'push_desktop',
-    'ship_desktop',
-])
-BEETMOVER_RELEASE_TARGET_TASKS = BEETMOVER_CANDIDATES_TARGET_TASKS | BEETMOVER_PUSH_TARGET_TASKS
-
-"""Map beetmover tasks aliases to sets of target task methods.
-
-This is a list of list-pairs, for ordering.
-"""
-BEETMOVER_SCOPE_ALIAS_TO_TARGET_TASK = [[
-    'all-nightly-tasks', set([
-        'nightly_fennec',
-        'nightly_linux',
-        'nightly_macosx',
-        'nightly_win32',
-        'nightly_win64',
-        'nightly_desktop',
-        'mozilla_beta_tasks',
-        'mozilla_release_tasks',
-    ])
-], [
-    'all-candidates-tasks', BEETMOVER_CANDIDATES_TARGET_TASKS
-], [
-    'all-push-tasks', BEETMOVER_PUSH_TARGET_TASKS
-]]
-
 """Map the beetmover scope aliases to the actual scopes.
 """
 BEETMOVER_BUCKET_SCOPES = {
-    'all-candidates-tasks': {
-        'all-release-branches': 'beetmover:bucket:release',
-    },
-    'all-push-tasks': {
-        'all-release-branches': 'beetmover:bucket:release',
-    },
-    'all-nightly-tasks': {
-        'all-nightly-branches': 'beetmover:bucket:nightly',
-    },
+    'all-release-branches': 'beetmover:bucket:release',
+    'all-nightly-branches': 'beetmover:bucket:nightly',
     'default': 'beetmover:bucket:dep',
 }
 
 """Map the beetmover tasks aliases to the actual action scopes.
 """
 BEETMOVER_ACTION_SCOPES = {
-    'all-candidates-tasks': 'beetmover:action:push-to-candidates',
-    'all-push-tasks': 'beetmover:action:push-to-releases',
-    'all-nightly-tasks': 'beetmover:action:push-to-nightly',
-    'default': 'beetmover:action:push-to-nightly',
+    'nightly': 'beetmover:action:push-to-nightly',
+    'default': 'beetmover:action:push-to-candidates',
 }
 
 
-"""Map the beetmover tasks aliases to phases.
-"""
-PHASES = {
-    'all-candidates-tasks': 'promote',
-    'all-push-tasks': 'push',
-    'default': None,
-}
-
 """Known balrog actions."""
 BALROG_ACTIONS = ('submit-locale', 'submit-toplevel', 'schedule')
 
 """Map balrog scope aliases to sets of projects.
 
 This is a list of list-pairs, for ordering.
 """
 BALROG_SCOPE_ALIAS_TO_PROJECT = [[
@@ -291,63 +234,30 @@ def get_scope_from_project(config, alias
     """
     for alias, projects in alias_to_project_map:
         if config.params['project'] in projects and alias in alias_to_scope_map:
             return alias_to_scope_map[alias]
     return alias_to_scope_map['default']
 
 
 @with_scope_prefix
-def get_scope_from_target_method(config, alias_to_tasks_map, alias_to_scope_map):
+def get_scope_from_release_type(config, release_type_to_scope_map):
     """Determine the restricted scope from `config.params['target_tasks_method']`.
 
     Args:
         config (TransformConfig): The configuration for the kind being transformed.
-        alias_to_tasks_map (list of lists): each list pair contains the
-            alias and the set of target methods that match. This is ordered.
-        alias_to_scope_map (dict): the alias alias to scope
+        release_type_to_scope_map (dict): the maps release types to scopes
 
     Returns:
         string: the scope to use.
     """
-    for alias, tasks in alias_to_tasks_map:
-        if config.params['target_tasks_method'] in tasks and alias in alias_to_scope_map:
-            return alias_to_scope_map[alias]
-    return alias_to_scope_map['default']
-
-
-@with_scope_prefix
-def get_scope_from_target_method_and_project(config, alias_to_tasks_map,
-                                             alias_to_project_map, aliases_to_scope_map):
-    """Determine the restricted scope from both `target_tasks_method` and `project`.
-
-    On certain branches, we'll need differing restricted scopes based on
-    `target_tasks_method`.  However, we can't key solely on that, since that
-    `target_tasks_method` might be run on an unprivileged branch.  This method
-    checks both.
-
-    Args:
-        config (TransformConfig): The configuration for the kind being transformed.
-        alias_to_tasks_map (list of lists): each list pair contains the
-            alias and the set of target methods that match. This is ordered.
-        alias_to_project_map (list of lists): each list pair contains the
-            alias and the set of projects that match.  This is ordered.
-        aliases_to_scope_map (dict of dicts): the task alias to project alias to scope
-
-    Returns:
-        string: the scope to use.
-    """
-    project = config.params['project']
-    target = config.params['target_tasks_method']
-    for alias1, tasks in alias_to_tasks_map:
-        for alias2, projects in alias_to_project_map:
-            if target in tasks and project in projects and \
-                    aliases_to_scope_map.get(alias1, {}).get(alias2):
-                return aliases_to_scope_map[alias1][alias2]
-    return aliases_to_scope_map['default']
+    return release_type_to_scope_map.get(
+        config.params['release_type'],
+        release_type_to_scope_map['default']
+    )
 
 
 def get_phase_from_target_method(config, alias_to_tasks_map, alias_to_phase_map):
     """Determine the phase from `config.params['target_tasks_method']`.
 
     Args:
         config (TransformConfig): The configuration for the kind being transformed.
         alias_to_tasks_map (list of lists): each list pair contains the
@@ -377,32 +287,24 @@ get_signing_cert_scope = functools.parti
 
 get_devedition_signing_cert_scope = functools.partial(
     get_scope_from_project,
     alias_to_project_map=DEVEDITION_SIGNING_SCOPE_ALIAS_TO_PROJECT,
     alias_to_scope_map=DEVEDITION_SIGNING_CERT_SCOPES,
 )
 
 get_beetmover_bucket_scope = functools.partial(
-    get_scope_from_target_method_and_project,
-    alias_to_tasks_map=BEETMOVER_SCOPE_ALIAS_TO_TARGET_TASK,
+    get_scope_from_project,
     alias_to_project_map=BEETMOVER_SCOPE_ALIAS_TO_PROJECT,
-    aliases_to_scope_map=BEETMOVER_BUCKET_SCOPES,
+    alias_to_scope_map=BEETMOVER_BUCKET_SCOPES,
 )
 
 get_beetmover_action_scope = functools.partial(
-    get_scope_from_target_method,
-    alias_to_tasks_map=BEETMOVER_SCOPE_ALIAS_TO_TARGET_TASK,
-    alias_to_scope_map=BEETMOVER_ACTION_SCOPES,
-)
-
-get_phase = functools.partial(
-    get_phase_from_target_method,
-    alias_to_tasks_map=BEETMOVER_SCOPE_ALIAS_TO_TARGET_TASK,
-    alias_to_phase_map=PHASES,
+    get_scope_from_release_type,
+    release_type_to_scope_map=BEETMOVER_ACTION_SCOPES,
 )
 
 get_balrog_server_scope = functools.partial(
     get_scope_from_project,
     alias_to_project_map=BALROG_SCOPE_ALIAS_TO_PROJECT,
     alias_to_scope_map=BALROG_SERVER_SCOPES,
 )
 
--- a/toolkit/content/aboutAbout.xhtml
+++ b/toolkit/content/aboutAbout.xhtml
@@ -1,27 +1,24 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
-<!ENTITY % aboutAboutDTD SYSTEM "chrome://global/locale/aboutAbout.dtd" >
-%aboutAboutDTD;
-<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
-%globalDTD;
-]>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
 
 <!-- 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/. -->
 
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
-  <title>&aboutAbout.title;</title>
+  <title data-l10n-id="about-about-title"></title>
   <link rel="stylesheet" href="chrome://global/skin/in-content/info-pages.css" type="text/css"/>
+  <link rel="localization" href="toolkit/about/aboutAbout.ftl"/>
   <script type="application/javascript" src="chrome://global/content/aboutAbout.js"></script>
+
 </head>
 
-<body dir="&locale.dir;">
+<body>
   <div class="container">
-    <h1>&aboutAbout.title;</h1>
-    <p><em>&aboutAbout.note;</em></p>
+    <h1 data-l10n-id="about-about-title"></h1>
+    <p><em data-l10n-id="about-about-note"></em></p>
     <ul id="abouts" class="columns"></ul>
   </div>
 </body>
 </html>
deleted file mode 100644
--- a/toolkit/locales/en-US/chrome/global/aboutAbout.dtd
+++ /dev/null
@@ -1,8 +0,0 @@
-<!-- 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/. -->
-
-<!ENTITY aboutAbout.title  "About About">
-<!ENTITY aboutAbout.note   "This is a list of “about” pages for your convenience.<br/>
-                            Some of them might be confusing. Some are for diagnostic purposes only.<br/>
-                            And some are omitted because they require query strings.">
new file mode 100644
--- /dev/null
+++ b/toolkit/locales/en-US/toolkit/about/aboutAbout.ftl
@@ -0,0 +1,9 @@
+# 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/.
+
+about-about-title = About About
+about-about-note =
+    This is a list of “about” pages for your convenience.<br/>
+    Some of them might be confusing. Some are for diagnostic purposes only.<br/>
+    And some are omitted because they require query strings.
--- a/toolkit/locales/jar.mn
+++ b/toolkit/locales/jar.mn
@@ -4,17 +4,16 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 [localization] @AB_CD@.jar:
   crashreporter                                    (%crashreporter/**/*.ftl)
   toolkit                                          (%toolkit/**/*.ftl)
 
 @AB_CD@.jar:
 % locale global @AB_CD@ %locale/@AB_CD@/global/
-  locale/@AB_CD@/global/aboutAbout.dtd                  (%chrome/global/aboutAbout.dtd)
   locale/@AB_CD@/global/aboutReader.properties          (%chrome/global/aboutReader.properties)
   locale/@AB_CD@/global/aboutRights.dtd                 (%chrome/global/aboutRights.dtd)
   locale/@AB_CD@/global/aboutNetworking.dtd             (%chrome/global/aboutNetworking.dtd)
   locale/@AB_CD@/global/aboutStudies.properties         (%chrome/global/aboutStudies.properties)
   locale/@AB_CD@/global/aboutServiceWorkers.dtd         (%chrome/global/aboutServiceWorkers.dtd)
   locale/@AB_CD@/global/aboutServiceWorkers.properties  (%chrome/global/aboutServiceWorkers.properties)
   locale/@AB_CD@/global/aboutSupport.dtd                (%chrome/global/aboutSupport.dtd)
   locale/@AB_CD@/global/aboutSupport.properties         (%chrome/global/aboutSupport.properties)
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -1130,17 +1130,20 @@ var gViewController = {
             let subject = {
               wrappedJSObject: {
                 target: getBrowserElement(),
                 info: {
                   type: "sideload",
                   addon: aAddon,
                   icon: aAddon.iconURL,
                   permissions: perms,
-                  resolve() { aAddon.enable(); },
+                  resolve() {
+                    aAddon.markAsSeen();
+                    aAddon.enable();
+                  },
                   reject() {},
                 },
               },
             };
             Services.obs.notifyObservers(subject, "webextension-permission-prompt");
             return;
           }
         }
--- a/toolkit/mozapps/extensions/test/browser/browser.ini
+++ b/toolkit/mozapps/extensions/test/browser/browser.ini
@@ -52,16 +52,17 @@ skip-if = os == "linux" && !debug # Bug 
 [browser_bug618502.js]
 [browser_bug679604.js]
 [browser_bug590347.js]
 [browser_checkAddonCompatibility.js]
 [browser_details.js]
 [browser_discovery.js]
 [browser_dragdrop.js]
 [browser_dragdrop_incompat.js]
+[browser_extension_sideloading_permission.js]
 [browser_file_xpi_no_process_switch.js]
 skip-if = true # Bug 1449071 - Frequent failures
 [browser_getmorethemes.js]
 [browser_globalwarnings.js]
 [browser_gmpProvider.js]
 skip-if = os == 'linux' && !debug # Bug 1398766
 [browser_inlinesettings_browser.js]
 skip-if = os == 'mac' || os == 'linux' # Bug 1483347
--- a/toolkit/mozapps/extensions/test/browser/browser_bug567127.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug567127.js
@@ -4,42 +4,16 @@
 
 // Tests bug 567127 - Add install button to the add-ons manager
 
 var MockFilePicker = SpecialPowers.MockFilePicker;
 MockFilePicker.init(window);
 
 var gManagerWindow;
 
-/**
- * Wait for the given PopupNotification to display
- *
- * @param {string} name
- *        The name of the notification to wait for.
- *
- * @returns {Promise}
- *          Resolves with the notification window.
- */
-function promisePopupNotificationShown(name) {
-  return new Promise(resolve => {
-    function popupshown() {
-      let notification = PopupNotifications.getNotification(name);
-      if (!notification) { return; }
-
-      ok(notification, `${name} notification shown`);
-      ok(PopupNotifications.isPanelOpen, "notification panel open");
-
-      PopupNotifications.panel.removeEventListener("popupshown", popupshown);
-      resolve(PopupNotifications.panel.firstChild);
-    }
-
-    PopupNotifications.panel.addEventListener("popupshown", popupshown);
-  });
-}
-
 async function checkInstallConfirmation(...names) {
   let notificationCount = 0;
   let observer = {
     observe(aSubject, aTopic, aData) {
       var installInfo = aSubject.wrappedJSObject;
       isnot(installInfo.browser, null, "Notification should have non-null browser");
       Assert.deepEqual(installInfo.installs[0].installTelemetryInfo, {
         source: "about:addons",
--- a/toolkit/mozapps/extensions/test/browser/browser_dragdrop.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_dragdrop.js
@@ -9,42 +9,16 @@
 // this automatically.
 
 // Instead of loading EventUtils.js into the test scope in browser-test.js for all tests,
 // we only need EventUtils.js for a few files which is why we are using loadSubScript.
 var gManagerWindow;
 var EventUtils = {};
 Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
 
-/**
- * Wait for the given PopupNotification to display
- *
- * @param {string} name
- *        The name of the notification to wait for.
- *
- * @returns {Promise}
- *          Resolves with the notification window.
- */
-function promisePopupNotificationShown(name) {
-  return new Promise(resolve => {
-    function popupshown() {
-      let notification = PopupNotifications.getNotification(name);
-      if (!notification) { return; }
-
-      ok(notification, `${name} notification shown`);
-      ok(PopupNotifications.isPanelOpen, "notification panel open");
-
-      PopupNotifications.panel.removeEventListener("popupshown", popupshown);
-      resolve(PopupNotifications.panel.firstElementChild);
-    }
-
-    PopupNotifications.panel.addEventListener("popupshown", popupshown);
-  });
-}
-
 async function checkInstallConfirmation(...names) {
   let notificationCount = 0;
   let observer = {
     observe(aSubject, aTopic, aData) {
       var installInfo = aSubject.wrappedJSObject;
       isnot(installInfo.browser, null, "Notification should have non-null browser");
 
       is(installInfo.installs.length, 1, "Got one AddonInstall instance as expected");
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_extension_sideloading_permission.js
@@ -0,0 +1,94 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/*
+* Test Permission Popup for Sideloaded Extensions.
+*/
+const {AddonTestUtils} = ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm", {});
+const ADDON_ID = "addon1@test.mozilla.org";
+
+AddonTestUtils.initMochitest(this);
+
+// Loading extension by sideloading method
+add_task(async function test() {
+
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["xpinstall.signatures.required", false],
+      ["extensions.autoDisableScopes", 15],
+      ["extensions.ui.ignoreUnsigned", true],
+    ],
+  });
+
+  let options = {
+    manifest: {
+      applications: {gecko: {id: ADDON_ID}},
+      name: "Test 1",
+      permissions: ["history", "https://*/*"],
+      icons: {"64": "foo-icon.png"},
+    },
+  };
+
+  let xpi = AddonTestUtils.createTempWebExtensionFile(options);
+  await AddonTestUtils.manuallyInstall(xpi);
+
+  let changePromise = new Promise(resolve => ExtensionsUI.once("change", resolve));
+  ExtensionsUI._checkForSideloaded();
+  await changePromise;
+
+  // Test click event on permission cancel option.
+  let manager = await open_manager("addons://list/extension");
+  let addon = get_addon_element(manager, ADDON_ID);
+
+  Assert.notEqual(addon, null, "Found sideloaded addon in about:addons");
+  let el = addon.ownerDocument.getAnonymousElementByAttribute(addon, "anonid", "disable-btn");
+  is_element_hidden(el, "Disable button not visible.");
+  el = addon.ownerDocument.getAnonymousElementByAttribute(addon, "anonid", "enable-btn");
+  is_element_visible(el, "Enable button visible");
+
+  let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
+  EventUtils.synthesizeMouseAtCenter(
+    el,
+    { clickCount: 1 },
+    manager
+  );
+
+  let panel = await popupPromise;
+  ok(PopupNotifications.isPanelOpen, "Permission popup should be visible");
+  panel.secondaryButton.click();
+  ok(!PopupNotifications.isPanelOpen, "Permission popup should be closed / closing");
+
+  addon = await AddonManager.getAddonByID(ADDON_ID);
+  ok(!addon.seen, "Seen flag should remain false after permissions are refused");
+
+  // Test click event on permission accept option.
+  addon = get_addon_element(manager, ADDON_ID);
+  Assert.notEqual(addon, null, "Found sideloaded addon in about:addons");
+
+  el = addon.ownerDocument.getAnonymousElementByAttribute(addon, "anonid", "disable-btn");
+  is_element_hidden(el, "Disable button not visible.");
+  el = addon.ownerDocument.getAnonymousElementByAttribute(addon, "anonid", "enable-btn");
+  is_element_visible(el, "Enable button visible");
+
+  popupPromise = promisePopupNotificationShown("addon-webext-permissions");
+  EventUtils.synthesizeMouseAtCenter(
+    manager.document.getAnonymousElementByAttribute(addon, "anonid", "enable-btn"),
+    { clickCount: 1 },
+    manager
+  );
+
+  panel = await popupPromise;
+  ok(PopupNotifications.isPanelOpen, "Permission popup should be visible");
+  panel.button.click();
+  ok(!PopupNotifications.isPanelOpen, "Permission popup should be closed / closing");
+
+  addon = await AddonManager.getAddonByID(ADDON_ID);
+  ok(addon.seen, "Seen flag should be true after permissions are accepted");
+
+  ok(!PopupNotifications.isPanelOpen, "Permission popup should not be visible");
+
+  await addon.uninstall();
+
+  await close_manager(manager);
+});
--- a/toolkit/mozapps/extensions/test/browser/head.js
+++ b/toolkit/mozapps/extensions/test/browser/head.js
@@ -1383,16 +1383,41 @@ function promiseNotification(id = "addon
         PopupNotifications.panel.firstElementChild.button.click();
         resolve();
       }
     }
     PopupNotifications.panel.addEventListener("popupshown", popupshown);
   });
 }
 
+/**
+ * Wait for the given PopupNotification to display
+ *
+ * @param {string} name
+ *        The name of the notification to wait for.
+ *
+ * @returns {Promise}
+ *          Resolves with the notification window.
+ */
+function promisePopupNotificationShown(name = "addon-webext-permissions") {
+  return new Promise(resolve => {
+    function popupshown() {
+      let notification = PopupNotifications.getNotification(name);
+      if (!notification) { return; }
+
+      ok(notification, `${name} notification shown`);
+      ok(PopupNotifications.isPanelOpen, "notification panel open");
+
+      PopupNotifications.panel.removeEventListener("popupshown", popupshown);
+      resolve(PopupNotifications.panel.firstChild);
+    }
+    PopupNotifications.panel.addEventListener("popupshown", popupshown);
+  });
+}
+
 function acceptAppMenuNotificationWhenShown(id) {
   ChromeUtils.import("resource://gre/modules/AppMenuNotifications.jsm");
   return new Promise(resolve => {
     function popupshown() {
       let notification = AppMenuNotifications.activeNotification;
       if (!notification) { return; }
 
       is(notification.id, id, `${id} notification shown`);
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_doorhanger_installs.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_doorhanger_installs.js
@@ -70,32 +70,52 @@ async function waitForProgressNotificati
     ok(notification, `Should have seen the right notification`);
     is(notification.button.hasAttribute("disabled"), wantDisabled,
        "The install button should be disabled?");
   }
 
   return PopupNotifications.panel;
 }
 
-function acceptAppMenuNotificationWhenShown(id) {
+function acceptAppMenuNotificationWhenShown(id, dismiss = false) {
   ChromeUtils.import("resource://gre/modules/AppMenuNotifications.jsm");
   return new Promise(resolve => {
+    function appMenuPopupHidden() {
+      PanelUI.panel.removeEventListener("popuphidden", appMenuPopupHidden);
+      is(PanelUI.menuButton.getAttribute("badge-status"), false, "badge is not set after addon-installed");
+      resolve();
+    }
+    function appMenuPopupShown() {
+      PanelUI.panel.removeEventListener("popupshown", appMenuPopupShown);
+      PanelUI.menuButton.click();
+    }
     function popupshown() {
       let notification = AppMenuNotifications.activeNotification;
-      if (!notification) { return; }
+      if (!notification) {
+        return;
+      }
 
       is(notification.id, id, `${id} notification shown`);
       ok(PanelUI.isNotificationPanelOpen, "notification panel open");
 
       PanelUI.notificationPanel.removeEventListener("popupshown", popupshown);
 
+      if (dismiss) {
+        // Dismiss the panel by clicking on the appMenu button.
+        PanelUI.panel.addEventListener("popupshown", appMenuPopupShown);
+        PanelUI.panel.addEventListener("popuphidden", appMenuPopupHidden);
+        PanelUI.menuButton.click();
+        return;
+      }
+
+      // Dismiss the panel by clicking the primary button.
       let popupnotificationID = PanelUI._getPopupId(notification);
       let popupnotification = document.getElementById(popupnotificationID);
+
       popupnotification.button.click();
-
       resolve();
     }
     PanelUI.notificationPanel.addEventListener("popupshown", popupshown);
   });
 }
 
 async function waitForNotification(aId, aExpectedCount = 1) {
   info("Waiting for " + aId + " notification");
@@ -283,17 +303,17 @@ async function test_whitelistedInstall()
                                                 + triggers).then(newTab => tab = newTab);
   await progressPromise;
   let installDialog = await dialogPromise;
   await BrowserTestUtils.waitForCondition(() => !!tab, "tab should be present");
 
   is(gBrowser.selectedTab, tab,
      "tab selected in response to the addon-install-confirmation notification");
 
-  let notificationPromise = acceptAppMenuNotificationWhenShown("addon-installed");
+  let notificationPromise = acceptAppMenuNotificationWhenShown("addon-installed", true);
   acceptInstallDialog(installDialog);
   await notificationPromise;
 
   let installs = await AddonManager.getAllInstalls();
   is(installs.length, 0, "Should be no pending installs");
 
   let addon = await AddonManager.getAddonByID("amosigned-xpi@tests.mozilla.org");
   addon.uninstall();