Merge m-c to autoland, a=merge
authorWes Kocher <wkocher@mozilla.com>
Tue, 25 Jul 2017 19:16:09 -0700
changeset 419671 296d24a175b6abfd094e1ebfb1e6b2033908c3fa
parent 419670 ffda123f367942f0e15a4ebe1728bcccb57539e1 (current diff)
parent 419605 9eddb0a92820c6445f9d1e680e4c239e888e93f0 (diff)
child 419672 8a5c4e5a0a6b65621d81ad4506e688c289a914e9
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone56.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 m-c to autoland, a=merge MozReview-Commit-ID: F1X8wKqbkg
browser/components/preferences/in-content-new/privacy.js
dom/base/test/mochitest.ini
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
modules/libpref/init/all.js
testing/web-platform/meta/service-workers/service-worker/controller-on-reload.https.html.ini
testing/web-platform/meta/service-workers/service-worker/multi-globals/url-parsing.https.html.ini
testing/web-platform/meta/service-workers/service-worker/multiple-register.https.html.ini
testing/web-platform/meta/service-workers/service-worker/registration-iframe.https.html.ini
tools/profiler/core/platform.cpp
tools/profiler/public/GeckoProfiler.h
tools/profiler/tests/gtest/GeckoProfiler.cpp
tools/tryselect/selectors/syntax.py
--- a/browser/components/contextualidentity/test/browser/browser_count_and_remove.js
+++ b/browser/components/contextualidentity/test/browser/browser_count_and_remove.js
@@ -30,18 +30,18 @@ add_task(async function test() {
   is(ContextualIdentityService.countContainerTabs(1), 2, "2 container tabs created with id 1");
   is(ContextualIdentityService.countContainerTabs(2), 0, "0 container tabs created with id 2");
 
   openTabInUserContext(2);
   is(ContextualIdentityService.countContainerTabs(), 3, "3 container tab created");
   is(ContextualIdentityService.countContainerTabs(1), 2, "2 container tabs created with id 1");
   is(ContextualIdentityService.countContainerTabs(2), 1, "1 container tab created with id 2");
 
-  ContextualIdentityService.closeContainerTabs(1);
+  await ContextualIdentityService.closeContainerTabs(1);
   is(ContextualIdentityService.countContainerTabs(), 1, "1 container tab created");
   is(ContextualIdentityService.countContainerTabs(1), 0, "0 container tabs created with id 1");
   is(ContextualIdentityService.countContainerTabs(2), 1, "1 container tab created with id 2");
 
-  ContextualIdentityService.closeContainerTabs();
+  await ContextualIdentityService.closeContainerTabs();
   is(ContextualIdentityService.countContainerTabs(), 0, "0 container tabs at the end.");
   is(ContextualIdentityService.countContainerTabs(1), 0, "0 container tabs at the end with id 1.");
   is(ContextualIdentityService.countContainerTabs(2), 0, "0 container tabs at the end with id 2.");
 });
--- a/browser/components/preferences/in-content-new/containers.js
+++ b/browser/components/preferences/in-content-new/containers.js
@@ -35,17 +35,17 @@ let gContainersPane = {
       item.setAttribute("containerIcon", container.icon);
       item.setAttribute("containerColor", container.color);
       item.setAttribute("userContextId", container.userContextId);
 
       this._list.appendChild(item);
     }
   },
 
-  onRemoveClick(button) {
+  async onRemoveClick(button) {
     let userContextId = parseInt(button.getAttribute("value"), 10);
 
     let count = ContextualIdentityService.countContainerTabs(userContextId);
     if (count > 0) {
       let bundlePreferences = document.getElementById("bundlePreferences");
 
       let title = bundlePreferences.getString("removeContainerAlertTitle");
       let message = PluralForm.get(count, bundlePreferences.getString("removeContainerMsg"))
@@ -57,17 +57,17 @@ let gContainersPane = {
                         (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1);
 
       let rv = Services.prompt.confirmEx(window, title, message, buttonFlags,
                                          okButton, cancelButton, null, null, {});
       if (rv != 0) {
         return;
       }
 
-      ContextualIdentityService.closeContainerTabs(userContextId);
+      await ContextualIdentityService.closeContainerTabs(userContextId);
     }
 
     ContextualIdentityService.remove(userContextId);
     this._rebuildView();
   },
 
   onPreferenceClick(button) {
     this.openPreferenceDialog(button.getAttribute("value"));
--- a/browser/components/preferences/in-content-new/privacy.js
+++ b/browser/components/preferences/in-content-new/privacy.js
@@ -2,18 +2,16 @@
  * 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/. */
 
 /* import-globals-from preferences.js */
 
 Components.utils.import("resource://gre/modules/AppConstants.jsm");
 Components.utils.import("resource://gre/modules/PluralForm.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
-                                  "resource://gre/modules/ContextualIdentityService.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
                                   "resource://gre/modules/LoginHelper.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SiteDataManager",
                                   "resource:///modules/SiteDataManager.jsm");
 
 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
--- a/browser/components/preferences/in-content/containers.js
+++ b/browser/components/preferences/in-content/containers.js
@@ -35,17 +35,17 @@ let gContainersPane = {
       item.setAttribute("containerIcon", container.icon);
       item.setAttribute("containerColor", container.color);
       item.setAttribute("userContextId", container.userContextId);
 
       this._list.appendChild(item);
     }
   },
 
-  onRemoveClick(button) {
+  async onRemoveClick(button) {
     let userContextId = parseInt(button.getAttribute("value"), 10);
 
     let count = ContextualIdentityService.countContainerTabs(userContextId);
     if (count > 0) {
       let bundlePreferences = document.getElementById("bundlePreferences");
 
       let title = bundlePreferences.getString("removeContainerAlertTitle");
       let message = PluralForm.get(count, bundlePreferences.getString("removeContainerMsg"))
@@ -57,17 +57,17 @@ let gContainersPane = {
                         (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1);
 
       let rv = Services.prompt.confirmEx(window, title, message, buttonFlags,
                                          okButton, cancelButton, null, null, {});
       if (rv != 0) {
         return;
       }
 
-      ContextualIdentityService.closeContainerTabs(userContextId);
+      await ContextualIdentityService.closeContainerTabs(userContextId);
     }
 
     ContextualIdentityService.remove(userContextId);
     this._rebuildView();
   },
 
   onPreferenceClick(button) {
     this.openPreferenceDialog(button.getAttribute("value"));
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -82,16 +82,17 @@ var gPrivacyPane = {
     let checkbox = document.getElementById("browserContainersCheckbox");
     if (checkbox.checked) {
       Services.prefs.setBoolPref("privacy.userContext.enabled", true);
       return;
     }
 
     let count = ContextualIdentityService.countContainerTabs();
     if (count == 0) {
+      ContextualIdentityService.notifyAllContainersCleared();
       Services.prefs.setBoolPref("privacy.userContext.enabled", false);
       return;
     }
 
     let bundlePreferences = document.getElementById("bundlePreferences");
 
     let title = bundlePreferences.getString("disableContainersAlertTitle");
     let message = PluralForm.get(count, bundlePreferences.getString("disableContainersMsg"))
@@ -101,18 +102,20 @@ var gPrivacyPane = {
     let cancelButton = bundlePreferences.getString("disableContainersButton2");
 
     let buttonFlags = (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
                       (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1);
 
     let rv = Services.prompt.confirmEx(window, title, message, buttonFlags,
                                        okButton, cancelButton, null, null, {});
     if (rv == 0) {
-      ContextualIdentityService.closeContainerTabs();
       Services.prefs.setBoolPref("privacy.userContext.enabled", false);
+      ContextualIdentityService.closeContainerTabs().then(() => {
+        ContextualIdentityService.notifyAllContainersCleared();
+      });
       return;
     }
 
     checkbox.checked = true;
   },
 
   /**
    * Sets up the UI for the number of days of history to keep, and updates the
--- a/dom/base/Timeout.cpp
+++ b/dom/base/Timeout.cpp
@@ -7,26 +7,25 @@
 #include "Timeout.h"
 
 #include "mozilla/dom/TimeoutManager.h"
 
 namespace mozilla {
 namespace dom {
 
 Timeout::Timeout()
-  : mCleared(false),
+  : mTimeoutId(0),
+    mFiringId(TimeoutManager::InvalidFiringId),
+    mPopupState(openAllowed),
+    mReason(Reason::eTimeoutOrInterval),
+    mNestingLevel(0),
+    mCleared(false),
     mRunning(false),
     mIsInterval(false),
-    mIsTracking(false),
-    mReason(Reason::eTimeoutOrInterval),
-    mTimeoutId(0),
-    mInterval(0),
-    mFiringId(TimeoutManager::InvalidFiringId),
-    mNestingLevel(0),
-    mPopupState(openAllowed)
+    mIsTracking(false)
 {
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(Timeout)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Timeout)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mScriptHandler)
--- a/dom/base/Timeout.h
+++ b/dom/base/Timeout.h
@@ -30,78 +30,86 @@ class Timeout final
   : public LinkedListElement<RefPtr<Timeout>>
 {
 public:
   Timeout();
 
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(Timeout)
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(Timeout)
 
-  enum class Reason
+  enum class Reason : uint8_t
   {
     eTimeoutOrInterval,
     eIdleCallbackTimeout,
   };
 
   void SetWhenOrTimeRemaining(const TimeStamp& aBaseTime,
                               const TimeDuration& aDelay);
 
   // Can only be called when not frozen.
   const TimeStamp& When() const;
 
   // Can only be called when frozen.
   const TimeDuration& TimeRemaining() const;
 
+private:
+  // mWhen and mTimeRemaining can't be in a union, sadly, because they
+  // have constructors.
+  // Nominal time to run this timeout.  Use only when timeouts are not
+  // frozen.
+  TimeStamp mWhen;
+
+  // Remaining time to wait.  Used only when timeouts are frozen.
+  TimeDuration mTimeRemaining;
+
+  ~Timeout() = default;
+
+public:
+  // Public member variables in this section.  Please don't add to this list
+  // or mix methods with these.  The interleaving public/private sections
+  // is necessary as we migrate members to private while still trying to
+  // keep decent binary packing.
+
   // Window for which this timeout fires
   RefPtr<nsGlobalWindow> mWindow;
 
+  // The language-specific information about the callback.
+  nsCOMPtr<nsITimeoutHandler> mScriptHandler;
+
+  // Interval
+  TimeDuration mInterval;
+
+  // Returned as value of setTimeout()
+  uint32_t mTimeoutId;
+
+  // Identifies which firing level this Timeout is being processed in
+  // when sync loops trigger nested firing.
+  uint32_t mFiringId;
+
+  // The popup state at timeout creation time if not created from
+  // another timeout
+  PopupControlState mPopupState;
+
+  // Used to allow several reasons for setting a timeout, where each
+  // 'Reason' value is using a possibly overlapping set of id:s.
+  Reason mReason;
+
+  // Between 0 and DOM_CLAMP_TIMEOUT_NESTING_LEVEL.  Currently we don't
+  // care about nesting levels beyond that value.
+  uint8_t mNestingLevel;
+
   // True if the timeout was cleared
   bool mCleared;
 
   // True if this is one of the timeouts that are currently running
   bool mRunning;
 
   // True if this is a repeating/interval timer
   bool mIsInterval;
 
   // True if this is a timeout coming from a tracking script
   bool mIsTracking;
-
-  // Used to allow several reasons for setting a timeout, where each
-  // 'Reason' value is using a possibly overlapping set of id:s.
-  Reason mReason;
-
-  // Returned as value of setTimeout()
-  uint32_t mTimeoutId;
-
-  // Interval
-  TimeDuration mInterval;
-
-  // Identifies which firing level this Timeout is being processed in
-  // when sync loops trigger nested firing.
-  uint32_t mFiringId;
-
-  uint32_t mNestingLevel;
-
-  // The popup state at timeout creation time if not created from
-  // another timeout
-  PopupControlState mPopupState;
-
-  // The language-specific information about the callback.
-  nsCOMPtr<nsITimeoutHandler> mScriptHandler;
-
-private:
-  // mWhen and mTimeRemaining can't be in a union, sadly, because they
-  // have constructors.
-  // Nominal time to run this timeout.  Use only when timeouts are not
-  // frozen.
-  TimeStamp mWhen;
-
-  // Remaining time to wait.  Used only when timeouts are frozen.
-  TimeDuration mTimeRemaining;
-
-  ~Timeout() = default;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_timeout_h
--- a/dom/base/TimeoutManager.cpp
+++ b/dom/base/TimeoutManager.cpp
@@ -304,25 +304,24 @@ TimeoutManager::IsInvalidFiringId(uint32
   // should be rare.  It can only happen with deeply nested event
   // loop spinning.  For example, a page that does a lot of timers
   // and a lot of sync XHRs within those timers could be slow here.
   return !mFiringIdStack.Contains(aFiringId);
 }
 
 // The number of nested timeouts before we start clamping. HTML5 says 1, WebKit
 // uses 5.
-#define DOM_CLAMP_TIMEOUT_NESTING_LEVEL 5
+#define DOM_CLAMP_TIMEOUT_NESTING_LEVEL 5u
 
 TimeDuration
 TimeoutManager::CalculateDelay(Timeout* aTimeout) const {
   MOZ_DIAGNOSTIC_ASSERT(aTimeout);
   TimeDuration result = aTimeout->mInterval;
 
-  if (aTimeout->mIsInterval ||
-      aTimeout->mNestingLevel >= DOM_CLAMP_TIMEOUT_NESTING_LEVEL) {
+  if (aTimeout->mNestingLevel >= DOM_CLAMP_TIMEOUT_NESTING_LEVEL) {
     result = TimeDuration::Max(
       result, TimeDuration::FromMilliseconds(gMinClampTimeoutValue));
   }
 
   if (aTimeout->mIsTracking && mThrottleTrackingTimeouts) {
     result = TimeDuration::Max(
       result, TimeDuration::FromMilliseconds(gMinTrackingTimeoutValue));
   }
@@ -528,19 +527,18 @@ TimeoutManager::SetTimeout(nsITimeoutHan
 {
   // If we don't have a document (we could have been unloaded since
   // the call to setTimeout was made), do nothing.
   nsCOMPtr<nsIDocument> doc = mWindow.GetExtantDoc();
   if (!doc) {
     return NS_OK;
   }
 
-  // Disallow negative intervals.  If aIsInterval also disallow 0,
-  // because we use that as a "don't repeat" flag.
-  interval = std::max(aIsInterval ? 1 : 0, interval);
+  // Disallow negative intervals.
+  interval = std::max(0, interval);
 
   // Make sure we don't proceed with an interval larger than our timer
   // code can handle. (Note: we already forced |interval| to be non-negative,
   // so the uint32_t cast (to avoid compiler warnings) is ok.)
   uint32_t maxTimeoutMs = PR_IntervalToMilliseconds(DOM_MAX_TIMEOUT_VALUE);
   if (static_cast<uint32_t>(interval) > maxTimeoutMs) {
     interval = maxTimeoutMs;
   }
@@ -587,20 +585,18 @@ TimeoutManager::SetTimeout(nsITimeoutHan
     timeout->mIsTracking = (rand() % 2) == 0;
 
     MOZ_LOG(gLog, LogLevel::Debug,
             ("Classified timeout %p as %stracking (random mode)\n",
              timeout.get(), timeout->mIsTracking ? "" : "non-"));
     break;
   }
 
-  uint32_t nestingLevel = sNestingLevel + 1;
-  if (!aIsInterval) {
-    timeout->mNestingLevel = nestingLevel;
-  }
+  timeout->mNestingLevel = sNestingLevel < DOM_CLAMP_TIMEOUT_NESTING_LEVEL
+                         ? sNestingLevel + 1 : sNestingLevel;
 
   // Now clamp the actual interval we will use for the timer based on
   TimeDuration realInterval = CalculateDelay(timeout);
   TimeStamp now = TimeStamp::Now();
   timeout->SetWhenOrTimeRemaining(now, realInterval);
 
   // If we're not suspended, then set the timer.
   if (!mWindow.IsSuspended()) {
@@ -981,16 +977,22 @@ TimeoutManager::RescheduleTimeout(Timeou
                                   const TimeStamp& aCurrentNow)
 {
   MOZ_DIAGNOSTIC_ASSERT(aLastCallbackTime <= aCurrentNow);
 
   if (!aTimeout->mIsInterval) {
     return false;
   }
 
+  // Automatically increase the nesting level when a setInterval()
+  // is rescheduled just as if it was using a chained setTimeout().
+  if (aTimeout->mNestingLevel < DOM_CLAMP_TIMEOUT_NESTING_LEVEL) {
+    aTimeout->mNestingLevel += 1;
+  }
+
   // Compute time to next timeout for interval timer.
   // Make sure nextInterval is at least CalculateDelay().
   TimeDuration nextInterval = CalculateDelay(aTimeout);
 
   TimeStamp firingTime = aLastCallbackTime + nextInterval;
   TimeDuration delay = firingTime - aCurrentNow;
 
   // And make sure delay is nonnegative; that might happen if the timer
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -10540,27 +10540,27 @@ nsContentUtils::HtmlObjectContentTypeFor
       // ShouldPlay will handle checking for disabled plugins
       return nsIObjectLoadingContent::TYPE_PLUGIN;
     }
   }
 
   return nsIObjectLoadingContent::TYPE_NULL;
 }
 
-/* static */ already_AddRefed<nsIEventTarget>
+/* static */ already_AddRefed<nsISerialEventTarget>
 nsContentUtils::GetEventTargetByLoadInfo(nsILoadInfo* aLoadInfo, TaskCategory aCategory)
 {
   if (NS_WARN_IF(!aLoadInfo)) {
     return nullptr;
   }
 
   nsCOMPtr<nsIDOMDocument> domDoc;
   aLoadInfo->GetLoadingDocument(getter_AddRefs(domDoc));
   nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
-  nsCOMPtr<nsIEventTarget> target;
+  nsCOMPtr<nsISerialEventTarget> target;
   if (doc) {
     if (DocGroup* group = doc->GetDocGroup()) {
       target = group->EventTargetFor(aCategory);
     }
   } else {
     // There's no document yet, but this might be a top-level load where we can
     // find a TabGroup.
     uint64_t outerWindowId;
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -3023,17 +3023,17 @@ public:
    *                 nullptr in which case the docshell's plugin permissions
    *                 will not be checked.
    */
   static uint32_t
   HtmlObjectContentTypeForMIMEType(const nsCString& aMIMEType,
                                    bool aNoFakePlugin,
                                    nsIContent* aContent);
 
-  static already_AddRefed<nsIEventTarget>
+  static already_AddRefed<nsISerialEventTarget>
   GetEventTargetByLoadInfo(nsILoadInfo* aLoadInfo, mozilla::TaskCategory aCategory);
 
   /**
    * Detect whether a string is a local-url.
    * https://drafts.csswg.org/css-values/#local-urls
    */
   static bool
   IsLocalRefURL(const nsString& aString);
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -2593,18 +2593,28 @@ SetMemoryHighWaterMarkPrefChangedCallbac
                  highwatermark * 1024L * 1024L);
 }
 
 static void
 SetMemoryMaxPrefChangedCallback(const char* aPrefName, void* aClosure)
 {
   int32_t pref = Preferences::GetInt(aPrefName, -1);
   // handle overflow and negative pref values
-  uint32_t max = (pref <= 0 || pref >= 0x1000) ? -1 : (uint32_t)pref * 1024 * 1024;
-  SetGCParameter(JSGC_MAX_BYTES, max);
+  CheckedInt<uint32_t> max = CheckedInt<uint32_t>(pref) * 1024 * 1024;
+  SetGCParameter(JSGC_MAX_BYTES, max.isValid() ? max.value() : -1);
+}
+
+static void
+SetMemoryNurseryMaxPrefChangedCallback(const char* aPrefName, void* aClosure)
+{
+  int32_t pref = Preferences::GetInt(aPrefName, -1);
+  // handle overflow and negative pref values
+  CheckedInt<uint32_t> max = CheckedInt<uint32_t>(pref) * 1024;
+  SetGCParameter(JSGC_MAX_NURSERY_BYTES,
+    max.isValid() ? max.value() : JS::DefaultNurseryBytes);
 }
 
 static void
 SetMemoryGCModePrefChangedCallback(const char* aPrefName, void* aClosure)
 {
   bool enableZoneGC = Preferences::GetBool("javascript.options.mem.gc_per_zone");
   bool enableIncrementalGC = Preferences::GetBool("javascript.options.mem.gc_incremental");
   JSGCMode mode;
@@ -2787,16 +2797,18 @@ nsJSContext::EnsureStatics()
   JS::SetAsyncTaskCallbacks(jsapi.cx(), StartAsyncTaskCallback, FinishAsyncTaskCallback);
 
   // Set these global xpconnect options...
   Preferences::RegisterCallbackAndCall(SetMemoryHighWaterMarkPrefChangedCallback,
                                        "javascript.options.mem.high_water_mark");
 
   Preferences::RegisterCallbackAndCall(SetMemoryMaxPrefChangedCallback,
                                        "javascript.options.mem.max");
+  Preferences::RegisterCallbackAndCall(SetMemoryNurseryMaxPrefChangedCallback,
+                                       "javascript.options.mem.nursery.max_kb");
 
   Preferences::RegisterCallbackAndCall(SetMemoryGCModePrefChangedCallback,
                                        "javascript.options.mem.gc_per_zone");
 
   Preferences::RegisterCallbackAndCall(SetMemoryGCModePrefChangedCallback,
                                        "javascript.options.mem.gc_incremental");
 
   Preferences::RegisterCallbackAndCall(SetMemoryGCSliceTimePrefChangedCallback,
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -776,16 +776,18 @@ skip-if = debug == false
 [test_settimeout_inner.html]
 [test_setTimeoutWith0.html]
 [test_setting_opener.html]
 [test_simplecontentpolicy.html]
 skip-if = e10s # Bug 1156489.
 [test_text_wholeText.html]
 [test_textnode_normalize_in_selection.html]
 [test_textnode_split_in_selection.html]
+[test_timeout_clamp.html]
+skip-if = debug == true && toolkit == 'android' # Timing dependent, skip slow debug android builds
 [test_timer_flood.html]
 [test_title.html]
 [test_treewalker_nextsibling.xml]
 [test_user_select.html]
 skip-if = toolkit == 'android'
 [test_viewport_scroll.html]
 [test_viewsource_forbidden_in_object.html]
 [test_w3element_traversal.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_timeout_clamp.html
@@ -0,0 +1,122 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1378586
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1378586</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1378586">Mozilla Bug 1378586</a>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+// We need to clear our nesting level periodically.  We do this by firing
+// a postMessage() to get a runnable on the event loop without any setTimeout()
+// nesting.
+function clearNestingLevel() {
+  return new Promise(resolve => {
+    window.addEventListener('message', function onMessage() {
+      window.removeEventListener('message', onMessage);
+      resolve();
+    });
+    postMessage('done', '*');
+  });
+}
+
+function delayByTimeoutChain(iterations) {
+  return new Promise(resolve => {
+    let count = 0;
+    function tick() {
+      count += 1;
+      if (count >= iterations) {
+        resolve();
+        return;
+      }
+      setTimeout(tick, 0);
+    }
+    setTimeout(tick, 0);
+  });
+}
+
+function delayByInterval(iterations) {
+  return new Promise(resolve => {
+    let count = 0;
+    function tick() {
+      count += 1;
+      if (count >= iterations) {
+        resolve();
+        return;
+      }
+    }
+    setInterval(tick, 0);
+  });
+}
+
+// Use a very long clamp delay to make it easier to measure the change
+// in automation.  Some of our test servers are very slow and noisy.
+const clampDelayMS = 10000;
+
+// We expect that we will clamp on the 5th callback.  This should
+// be the same for both setTimeout() chains and setInterval().
+const expectedClampIteration = 5;
+
+async function runTests() {
+  // Things like pushPrefEnv() can use setTimeout() internally which may give
+  // us a nesting level.  Clear the nesting level to start so this doesn't
+  // confuse the test.
+  await clearNestingLevel();
+
+  // Verify a setTimeout() chain clamps correctly
+  let start = performance.now();
+  await delayByTimeoutChain(expectedClampIteration);
+  let delta = performance.now() - start;
+
+  ok(delta >= clampDelayMS, "setTimeout() chain clamped");
+  ok(delta < (2*clampDelayMS), "setTimeout() chain did not clamp twice");
+
+  await clearNestingLevel();
+
+  // Verify setInterval() clamps correctly
+  start = performance.now();
+  await delayByInterval(expectedClampIteration);
+  delta = performance.now() - start;
+
+  ok(delta >= clampDelayMS, "setInterval() clamped");
+  ok(delta < (2*clampDelayMS), "setInterval() did not clamp twice");
+
+  await clearNestingLevel();
+
+  // Verfy a setTimeout() chain will continue to clamp past the first
+  // expected iteration.
+  const expectedDelay = (1 + expectedClampIteration) * clampDelayMS;
+
+  start = performance.now();
+  await delayByTimeoutChain(2 * expectedClampIteration);
+  delta = performance.now() - start;
+
+  ok(delta >= expectedDelay, "setTimeout() chain continued to clamp");
+
+  await clearNestingLevel();
+
+  // Verfy setInterval() will continue to clamp past the first expected
+  // iteration.
+  start = performance.now();
+  await delayByTimeoutChain(2 * expectedClampIteration);
+  delta = performance.now() - start;
+
+  ok(delta >= expectedDelay, "setInterval() continued to clamp");
+
+  SimpleTest.finish();
+}
+
+SpecialPowers.pushPrefEnv({ 'set': [["dom.min_timeout_value", clampDelayMS]]},
+                          runTests);
+</script>
+
+</body>
+</html>
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -680,17 +680,17 @@ DefineUnforgeableAttributes(JSContext* c
 
 // We should use JSFunction objects for interface objects, but we need a custom
 // hasInstance hook because we have new interface objects on prototype chains of
 // old (XPConnect-based) bindings. We also need Xrays and arbitrary numbers of
 // reserved slots (e.g. for named constructors).  So we define a custom
 // funToString ObjectOps member for interface objects.
 JSString*
 InterfaceObjectToString(JSContext* aCx, JS::Handle<JSObject*> aObject,
-                        unsigned /* indent */)
+                        bool /* isToSource */)
 {
   const js::Class* clasp = js::GetObjectClass(aObject);
   MOZ_ASSERT(IsDOMIfaceAndProtoClass(clasp));
 
   const DOMIfaceAndProtoJSClass* ifaceAndProtoJSClass =
     DOMIfaceAndProtoJSClass::FromJSClass(clasp);
   return JS_NewStringCopyZ(aCx, ifaceAndProtoJSClass->mToString);
 }
--- a/dom/bindings/Makefile.in
+++ b/dom/bindings/Makefile.in
@@ -36,17 +36,17 @@ CSS2Properties.webidl: $(css2properties_
 # changes to .webidl or .py files should result in code generation being
 # performed. But we do pull in file-lists.jon to catch file additions.
 codegen_dependencies := \
   file-lists.json \
   $(nonstatic_webidl_files) \
   $(GLOBAL_DEPS) \
   $(NULL)
 
-include codegen.pp
+-include codegen.pp
 
 codegen.pp: $(codegen_dependencies)
 	$(call py_action,webidl,$(srcdir))
 	@$(TOUCH) $@
 
 .PHONY: compiletests
 compiletests:
 	$(call SUBMAKE,libs,test)
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -2457,16 +2457,20 @@ TabChild::InternalSetDocShellIsActive(bo
     }
 
     docShell->SetIsActive(aIsActive);
   }
 
   if (aIsActive) {
     MakeVisible();
 
+    if (!docShell) {
+      return;
+    }
+
     // We don't use TabChildBase::GetPresShell() here because that would create
     // a content viewer if one doesn't exist yet. Creating a content viewer can
     // cause JS to run, which we want to avoid. nsIDocShell::GetPresShell
     // returns null if no content viewer exists yet.
     if (nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell()) {
       if (nsIFrame* root = presShell->FrameConstructor()->GetRootFrame()) {
         FrameLayerBuilder::InvalidateAllLayersForFrame(
           nsLayoutUtils::GetDisplayRootFrame(root));
--- a/dom/security/test/general/mochitest.ini
+++ b/dom/security/test/general/mochitest.ini
@@ -6,8 +6,9 @@ support-files =
   file_block_toplevel_data_navigation.html
   file_block_toplevel_data_navigation2.html
   file_block_toplevel_data_navigation3.html
 
 [test_contentpolicytype_targeted_link_iframe.html]
 [test_nosniff.html]
 [test_block_script_wrong_mime.html]
 [test_block_toplevel_data_navigation.html]
+skip-if = toolkit == 'android' # intermittent failure
--- a/dom/workers/ServiceWorkerContainer.cpp
+++ b/dom/workers/ServiceWorkerContainer.cpp
@@ -138,30 +138,22 @@ ServiceWorkerContainer::Register(const n
 
   nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
   if (!swm) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   nsCOMPtr<nsIURI> baseURI;
-
-  nsIDocument* doc = GetEntryDocument();
-  if (doc) {
-    baseURI = doc->GetBaseURI();
+  nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
+  if (window) {
+    baseURI = window->GetDocBaseURI();
   } else {
-    // XXXnsm. One of our devtools browser test calls register() from a content
-    // script where there is no valid entry document. Use the window to resolve
-    // the uri in that case.
-    nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
-    nsCOMPtr<nsPIDOMWindowOuter> outerWindow;
-    if (window && (outerWindow = window->GetOuterWindow()) &&
-        outerWindow->GetServiceWorkersTestingEnabled()) {
-      baseURI = window->GetDocBaseURI();
-    }
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
   }
 
   nsresult rv;
   nsCOMPtr<nsIURI> scriptURI;
   rv = NS_NewURI(getter_AddRefs(scriptURI), aScriptURL, nullptr, baseURI);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.ThrowTypeError<MSG_INVALID_URL>(aScriptURL);
     return nullptr;
--- a/ipc/mscom/Ptr.h
+++ b/ipc/mscom/Ptr.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_mscom_Ptr_h
 #define mozilla_mscom_Ptr_h
 
 #include "mozilla/Assertions.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/mscom/EnsureMTA.h"
+#include "mozilla/SystemGroup.h"
 #include "mozilla/UniquePtr.h"
 #include "nsError.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 
 /**
  * The glue code in mozilla::mscom often needs to pass around interface pointers
  * belonging to a different apartment from the current one. We must not touch
@@ -36,19 +37,20 @@ struct MainThreadRelease
     if (!aPtr) {
       return;
     }
     if (NS_IsMainThread()) {
       aPtr->Release();
       return;
     }
     DebugOnly<nsresult> rv =
-      NS_DispatchToMainThread(NewNonOwningRunnableMethod("mscom::MainThreadRelease",
-                                                         aPtr,
-                                                         &T::Release));
+      SystemGroup::Dispatch("mscom::MainThreadRelease",
+                            TaskCategory::Other,
+                            NewNonOwningRunnableMethod("mscom::MainThreadRelease",
+                                                       aPtr, &T::Release));
     MOZ_ASSERT(NS_SUCCEEDED(rv));
   }
 };
 
 template <typename T>
 struct MTADelete
 {
   void operator()(T* aPtr)
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -431,17 +431,17 @@ typedef bool
 (* JSEnumerateOp)(JSContext* cx, JS::HandleObject obj);
 
 /**
  * The type of ObjectOps::funToString.  This callback allows an object to
  * provide a custom string to use when Function.prototype.toString is invoked on
  * that object.  A null return value means OOM.
  */
 typedef JSString*
-(* JSFunToStringOp)(JSContext* cx, JS::HandleObject obj, unsigned indent);
+(* JSFunToStringOp)(JSContext* cx, JS::HandleObject obj, bool isToSource);
 
 /**
  * Resolve a lazy property named by id in obj by defining it directly in obj.
  * Lazy properties are those reflected from some peer native property space
  * (e.g., the DOM attributes for a given node reflected as obj) on demand.
  *
  * JS looks for a property in an object, and if not found, tries to resolve
  * the given id. *resolvedp should be set to true iff the property was defined
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -60,17 +60,17 @@ namespace JS {
     D(LAST_DITCH)                               \
     D(TOO_MUCH_MALLOC)                          \
     D(ALLOC_TRIGGER)                            \
     D(DEBUG_GC)                                 \
     D(COMPARTMENT_REVIVED)                      \
     D(RESET)                                    \
     D(OUT_OF_NURSERY)                           \
     D(EVICT_NURSERY)                            \
-    D(UNUSED0)                                  \
+    D(DELAYED_ATOMS_GC)                         \
     D(SHARED_MEMORY_LIMIT)                      \
     D(UNUSED1)                                  \
     D(INCREMENTAL_TOO_SLOW)                     \
     D(ABORT_GC)                                 \
     D(FULL_WHOLE_CELL_BUFFER)                   \
     D(FULL_GENERIC_BUFFER)                      \
     D(FULL_VALUE_BUFFER)                        \
     D(FULL_CELL_PTR_BUFFER)                     \
--- a/js/public/HeapAPI.h
+++ b/js/public/HeapAPI.h
@@ -96,20 +96,24 @@ AssertGCThingHasType(js::gc::Cell* cell,
 MOZ_ALWAYS_INLINE bool IsInsideNursery(const js::gc::Cell* cell);
 
 } /* namespace gc */
 } /* namespace js */
 
 namespace JS {
 struct Zone;
 
-/* Default size for the generational nursery in bytes. */
+/*
+ * Default size for the generational nursery in bytes.
+ * This is the initial nursery size, when running in the browser this is
+ * updated by JS_SetGCParameter().
+ */
 const uint32_t DefaultNurseryBytes = 16 * js::gc::ChunkSize;
 
-/* Default maximum heap size in bytes to pass to JS_NewRuntime(). */
+/* Default maximum heap size in bytes to pass to JS_NewContext(). */
 const uint32_t DefaultHeapMaxBytes = 32 * 1024 * 1024;
 
 namespace shadow {
 
 struct Zone
 {
     enum GCState : uint8_t {
         NoGC,
--- a/js/public/Initialization.h
+++ b/js/public/Initialization.h
@@ -45,17 +45,17 @@ typedef void (*JS_ICUFreeFn)(const void*
  */
 extern JS_PUBLIC_API(bool)
 JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn,
                          JS_ICUReallocFn reallocFn,
                          JS_ICUFreeFn freeFn);
 
 /**
  * Initialize SpiderMonkey, returning true only if initialization succeeded.
- * Once this method has succeeded, it is safe to call JS_NewRuntime and other
+ * Once this method has succeeded, it is safe to call JS_NewContext and other
  * JSAPI methods.
  *
  * This method must be called before any other JSAPI method is used on any
  * thread.  Once it has been used, it is safe to call any JSAPI method, and it
  * remains safe to do so until JS_ShutDown is correctly called.
  *
  * It is currently not possible to initialize SpiderMonkey multiple times (that
  * is, calling JS_Init/JSAPI methods/JS_ShutDown in that order, then doing so
--- a/js/public/Proxy.h
+++ b/js/public/Proxy.h
@@ -325,17 +325,17 @@ class JS_FRIEND_API(BaseProxyHandler)
                                               AutoIdVector& props) const;
     virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
                             const CallArgs& args) const;
     virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp) const;
     virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy,
                                  ESClass* cls) const;
     virtual bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const;
     virtual const char* className(JSContext* cx, HandleObject proxy) const;
-    virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const;
+    virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const;
     virtual RegExpShared* regexp_toShared(JSContext* cx, HandleObject proxy) const;
     virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const;
     virtual void trace(JSTracer* trc, JSObject* proxy) const;
     virtual void finalize(JSFreeOp* fop, JSObject* proxy) const;
     virtual void objectMoved(JSObject* proxy, const JSObject* old) const;
 
     // Allow proxies, wrappers in particular, to specify callability at runtime.
     // Note: These do not take const JSObject*, but they do in spirit.
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -135,17 +135,21 @@ class GCSchedulingTunables
     /*
      * Soft limit on the number of bytes we are allowed to allocate in the GC
      * heap. Attempts to allocate gcthings over this limit will return null and
      * subsequently invoke the standard OOM machinery, independent of available
      * physical memory.
      */
     UnprotectedData<size_t> gcMaxBytes_;
 
-    /* Maximum nursery size for each zone group. */
+    /*
+     * Maximum nursery size for each zone group.
+     * Initially DefaultNurseryBytes and can be set by
+     * javascript.options.mem.nursery.max_kb
+     */
     ActiveThreadData<size_t> gcMaxNurseryBytes_;
 
     /*
      * The base value used to compute zone->trigger.gcBytes(). When
      * usage.gcBytes() surpasses threshold.gcBytes() for a zone, the zone may
      * be scheduled for a GC, depending on the exact circumstances.
      */
     ActiveThreadOrGCTaskData<size_t> gcZoneAllocThresholdBase_;
@@ -704,21 +708,17 @@ class GCRuntime
     void gcSlice(JS::gcreason::Reason reason, int64_t millis = 0);
     void finishGC(JS::gcreason::Reason reason);
     void abortGC();
     void startDebugGC(JSGCInvocationKind gckind, SliceBudget& budget);
     void debugGCSlice(SliceBudget& budget);
 
     bool canChangeActiveContext(JSContext* cx);
 
-    void triggerFullGCForAtoms() {
-        MOZ_ASSERT(fullGCForAtomsRequested_);
-        fullGCForAtomsRequested_ = false;
-        MOZ_RELEASE_ASSERT(triggerGC(JS::gcreason::ALLOC_TRIGGER));
-    }
+    void triggerFullGCForAtoms(JSContext* cx);
 
     void runDebugGC();
     void notifyRootsRemoved();
 
     enum TraceOrMarkRuntime {
         TraceRuntime,
         MarkRuntime
     };
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -641,16 +641,21 @@ js::Nursery::collect(JS::gcreason::Reaso
     }
     endProfile(ProfileKey::Pretenure);
 
     // We ignore gcMaxBytes when allocating for minor collection. However, if we
     // overflowed, we disable the nursery. The next time we allocate, we'll fail
     // because gcBytes >= gcMaxBytes.
     if (rt->gc.usage.gcBytes() >= rt->gc.tunables.gcMaxBytes())
         disable();
+    // Disable the nursery if the user changed the configuration setting.  The
+    // nursery can only be re-enabled by resetting the configurationa and
+    // restarting firefox.
+    if (maxNurseryChunks_ == 0)
+        disable();
 
     endProfile(ProfileKey::Total);
     minorGcCount_++;
 
     TimeDuration totalTime = profileDurations_[ProfileKey::Total];
     rt->addTelemetry(JS_TELEMETRY_GC_MINOR_US, totalTime.ToMicroseconds());
     rt->addTelemetry(JS_TELEMETRY_GC_MINOR_REASON, reason);
     if (totalTime.ToMilliseconds() > 1.0)
@@ -919,46 +924,67 @@ js::Nursery::setStartPosition()
     currentStartPosition_ = position();
 }
 
 void
 js::Nursery::maybeResizeNursery(JS::gcreason::Reason reason, double promotionRate)
 {
     static const double GrowThreshold   = 0.05;
     static const double ShrinkThreshold = 0.01;
+    unsigned newMaxNurseryChunks;
 
     // Shrink the nursery to its minimum size of we ran out of memory or
     // received a memory pressure event.
     if (gc::IsOOMReason(reason)) {
         minimizeAllocableSpace();
         return;
     }
 
+#ifdef JS_GC_ZEAL
+    // This zeal mode disabled nursery resizing.
+    if (runtime()->hasZealMode(ZealMode::GenerationalGC))
+        return;
+#endif
+
+    newMaxNurseryChunks = runtime()->gc.tunables.gcMaxNurseryBytes() >> ChunkShift;
+    if (newMaxNurseryChunks != maxNurseryChunks_) {
+        maxNurseryChunks_ = newMaxNurseryChunks;
+        /* The configured maximum nursery size is changing */
+        int extraChunks = numChunks() - newMaxNurseryChunks;
+        if (extraChunks > 0) {
+            /* We need to shrink the nursery */
+            shrinkAllocableSpace(extraChunks);
+
+            previousPromotionRate_ = promotionRate;
+            return;
+        }
+    }
+
     if (promotionRate > GrowThreshold)
         growAllocableSpace();
     else if (promotionRate < ShrinkThreshold && previousPromotionRate_ < ShrinkThreshold)
-        shrinkAllocableSpace();
+        shrinkAllocableSpace(1);
 
     previousPromotionRate_ = promotionRate;
 }
 
 void
 js::Nursery::growAllocableSpace()
 {
     updateNumChunks(Min(numChunks() * 2, maxNurseryChunks_));
 }
 
 void
-js::Nursery::shrinkAllocableSpace()
+js::Nursery::shrinkAllocableSpace(unsigned removeNumChunks)
 {
 #ifdef JS_GC_ZEAL
     if (runtime()->hasZealMode(ZealMode::GenerationalGC))
         return;
 #endif
-    updateNumChunks(Max(numChunks() - 1, 1u));
+    updateNumChunks(Max(numChunks() - removeNumChunks, 1u));
 }
 
 void
 js::Nursery::minimizeAllocableSpace()
 {
 #ifdef JS_GC_ZEAL
     if (runtime()->hasZealMode(ZealMode::GenerationalGC))
         return;
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -463,17 +463,17 @@ class Nursery
      */
     void sweep();
 
     void sweepDictionaryModeObjects();
 
     /* Change the allocable space provided by the nursery. */
     void maybeResizeNursery(JS::gcreason::Reason reason, double promotionRate);
     void growAllocableSpace();
-    void shrinkAllocableSpace();
+    void shrinkAllocableSpace(unsigned removeNumChunks);
     void minimizeAllocableSpace();
 
     /* Profile recording and printing. */
     void maybeClearProfileDurations();
     void startProfile(ProfileKey key);
     void endProfile(ProfileKey key);
     static void printProfileDurations(const ProfileDurations& times);
 
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -38,16 +38,17 @@ JS::Zone::Zone(JSRuntime* rt, ZoneGroup*
     weakCaches_(group),
     gcWeakKeys_(group, SystemAllocPolicy(), rt->randomHashCodeScrambler()),
     gcSweepGroupEdges_(group),
     typeDescrObjects_(group, this),
     regExps(this),
     markedAtoms_(group),
     atomCache_(group),
     externalStringCache_(group),
+    functionToStringCache_(group),
     usage(&rt->gc.usage),
     threshold(),
     gcDelayBytes(0),
     propertyTree_(group, this),
     baseShapes_(group, this),
     initialShapes_(group, this),
     nurseryShapes_(group),
     data(group, nullptr),
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -102,16 +102,41 @@ class MOZ_NON_TEMPORARY_CLASS ExternalSt
   public:
     ExternalStringCache() { purge(); }
     void purge() { mozilla::PodArrayZero(entries_); }
 
     MOZ_ALWAYS_INLINE JSString* lookup(const char16_t* chars, size_t len) const;
     MOZ_ALWAYS_INLINE void put(JSString* s);
 };
 
+class MOZ_NON_TEMPORARY_CLASS FunctionToStringCache
+{
+    struct Entry {
+        JSScript* script;
+        JSString* string;
+
+        void set(JSScript* scriptArg, JSString* stringArg) {
+            script = scriptArg;
+            string = stringArg;
+        }
+    };
+    static const size_t NumEntries = 2;
+    mozilla::Array<Entry, NumEntries> entries_;
+
+    FunctionToStringCache(const FunctionToStringCache&) = delete;
+    void operator=(const FunctionToStringCache&) = delete;
+
+  public:
+    FunctionToStringCache() { purge(); }
+    void purge() { mozilla::PodArrayZero(entries_); }
+
+    MOZ_ALWAYS_INLINE JSString* lookup(JSScript* script) const;
+    MOZ_ALWAYS_INLINE void put(JSScript* script, JSString* string);
+};
+
 } // namespace js
 
 namespace JS {
 
 // A zone is a collection of compartments. Every compartment belongs to exactly
 // one zone. In Firefox, there is roughly one zone per tab along with a system
 // zone for everything else. Zones mainly serve as boundaries for garbage
 // collection. Unlike compartments, they have no special security properties.
@@ -205,21 +230,23 @@ struct Zone : public JS::shadow::Zone,
     void reportAllocationOverflow() { js::ReportAllocationOverflow(nullptr); }
 
     void beginSweepTypes(js::FreeOp* fop, bool releaseTypes);
 
     bool hasMarkedCompartments();
 
     void scheduleGC() { MOZ_ASSERT(!CurrentThreadIsHeapBusy()); gcScheduled_ = true; }
     void unscheduleGC() { gcScheduled_ = false; }
-    bool isGCScheduled() { return gcScheduled_ && canCollect(); }
+    bool isGCScheduled() { return gcScheduled_; }
 
     void setPreservingCode(bool preserving) { gcPreserveCode_ = preserving; }
     bool isPreservingCode() const { return gcPreserveCode_; }
 
+    // Whether this zone can currently be collected. This doesn't take account
+    // of AutoKeepAtoms for the atoms zone.
     bool canCollect();
 
     void changeGCState(GCState prev, GCState next) {
         MOZ_ASSERT(CurrentThreadIsHeapBusy());
         MOZ_ASSERT(gcState() == prev);
         MOZ_ASSERT_IF(next != NoGC, canCollect());
         gcState_ = next;
     }
@@ -438,23 +465,28 @@ struct Zone : public JS::shadow::Zone,
     js::ZoneGroupOrGCTaskData<js::SparseBitmap> markedAtoms_;
 
     // Set of atoms recently used by this Zone. Purged on GC.
     js::ZoneGroupOrGCTaskData<js::AtomSet> atomCache_;
 
     // Cache storing allocated external strings. Purged on GC.
     js::ZoneGroupOrGCTaskData<js::ExternalStringCache> externalStringCache_;
 
+    // Cache for Function.prototype.toString. Purged on GC.
+    js::ZoneGroupOrGCTaskData<js::FunctionToStringCache> functionToStringCache_;
+
   public:
     js::SparseBitmap& markedAtoms() { return markedAtoms_.ref(); }
 
     js::AtomSet& atomCache() { return atomCache_.ref(); }
 
     js::ExternalStringCache& externalStringCache() { return externalStringCache_.ref(); };
 
+    js::FunctionToStringCache& functionToStringCache() { return functionToStringCache_.ref(); }
+
     // Track heap usage under this Zone.
     js::gc::HeapUsage usage;
 
     // Thresholds used to trigger GC.
     js::gc::ZoneHeapThreshold threshold;
 
     // Amount of data to allocate before triggering a new incremental slice for
     // the current GC.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/gc/bug-1374797.js
@@ -0,0 +1,27 @@
+// Exercise triggering GC of atoms zone while off-thread parsing is happening.
+
+if (helperThreadCount() === 0)
+   quit();
+
+gczeal(0);
+
+// Reduce some GC parameters so that we can trigger a GC more easily.
+gcparam('lowFrequencyHeapGrowth', 120);
+gcparam('highFrequencyHeapGrowthMin', 120);
+gcparam('highFrequencyHeapGrowthMax', 120);
+gcparam('allocationThreshold', 1);
+gc();
+
+// Start an off-thread parse.
+offThreadCompileScript("print('Finished')");
+
+// Allocate lots of atoms, parsing occasionally.
+for (let i = 0; i < 10; i++) {
+    print(i);
+    for (let j = 0; j < 10000; j++)
+        Symbol.for(i + 10 * j);
+    eval(`${i}`);
+}
+
+// Finish the off-thread parse.
+runOffThreadScript();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1383591.js
@@ -0,0 +1,20 @@
+function test() {
+    var count = 0;
+    function f(x) {
+        "use strict";
+        if (x) {
+            Object.seal(this);
+        }
+        this[0] = 1;
+    }
+    for (var y of [1, 0, arguments, 1]) {
+        try {
+            var o = new f(y);
+        } catch (e) {
+            count++;
+        }
+    }
+    assertEq(count, 3);
+}
+test();
+test();
--- a/js/src/jit/BaselineCacheIRCompiler.cpp
+++ b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -1502,16 +1502,129 @@ BaselineCacheIRCompiler::emitStoreDenseE
     masm.bind(&doStore);
     masm.storeValue(val, element);
 
     emitPostBarrierElement(obj, val, scratch, index);
     return true;
 }
 
 bool
+BaselineCacheIRCompiler::emitArrayPush()
+{
+    ObjOperandId objId = reader.objOperandId();
+    ValOperandId rhsId = reader.valOperandId();
+
+    // Allocate the fixed registers first. These need to be fixed for
+    // callTypeUpdateIC.
+    AutoScratchRegister scratch(allocator, masm, R1.scratchReg());
+    ValueOperand val = allocator.useFixedValueRegister(masm, rhsId, R0);
+
+    Register obj = allocator.useRegister(masm, objId);
+    AutoScratchRegister scratchLength(allocator, masm);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure))
+        return false;
+
+    // Load obj->elements in scratch.
+    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
+    masm.load32(Address(scratch, ObjectElements::offsetOfLength()), scratchLength);
+
+    BaseObjectElementIndex element(scratch, scratchLength);
+    Address initLength(scratch, ObjectElements::offsetOfInitializedLength());
+    Address elementsFlags(scratch, ObjectElements::offsetOfFlags());
+
+    // Check for copy-on-write or frozen elements.
+    masm.branchTest32(Assembler::NonZero, elementsFlags,
+                      Imm32(ObjectElements::COPY_ON_WRITE |
+                            ObjectElements::FROZEN),
+                      failure->label());
+
+    // Fail if length != initLength.
+    masm.branch32(Assembler::NotEqual, initLength, scratchLength, failure->label());
+
+    // If scratchLength < capacity, we can add a dense element inline. If not we
+    // need to allocate more elements.
+    Label capacityOk;
+    Address capacity(scratch, ObjectElements::offsetOfCapacity());
+    masm.branch32(Assembler::Above, capacity, scratchLength, &capacityOk);
+
+    // Check for non-writable array length. We only have to do this if
+    // index >= capacity.
+    masm.branchTest32(Assembler::NonZero, elementsFlags,
+                      Imm32(ObjectElements::NONWRITABLE_ARRAY_LENGTH),
+                      failure->label());
+
+    LiveRegisterSet save(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs());
+    save.takeUnchecked(scratch);
+    masm.PushRegsInMask(save);
+
+    masm.setupUnalignedABICall(scratch);
+    masm.loadJSContext(scratch);
+    masm.passABIArg(scratch);
+    masm.passABIArg(obj);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, NativeObject::addDenseElementDontReportOOM));
+    masm.mov(ReturnReg, scratch);
+
+    masm.PopRegsInMask(save);
+    masm.branchIfFalseBool(scratch, failure->label());
+
+    // Load the reallocated elements pointer.
+    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
+
+    masm.bind(&capacityOk);
+
+    // Check if we have to convert a double element.
+    Label noConversion;
+    masm.branchTest32(Assembler::Zero, elementsFlags,
+                      Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS),
+                      &noConversion);
+
+    // We need to convert int32 values being stored into doubles. Note that
+    // double arrays are only created by IonMonkey, so if we have no FP support
+    // Ion is disabled and there should be no double arrays.
+    if (cx_->runtime()->jitSupportsFloatingPoint) {
+        // It's fine to convert the value in place in Baseline. We can't do
+        // this in Ion.
+        masm.convertInt32ValueToDouble(val);
+    } else {
+        masm.assumeUnreachable("There shouldn't be double arrays when there is no FP support.");
+    }
+
+    masm.bind(&noConversion);
+
+    // Call the type update IC. After this everything must be infallible as we
+    // don't save all registers here.
+    LiveGeneralRegisterSet saveRegs;
+    saveRegs.add(obj);
+    saveRegs.add(val);
+    if (!callTypeUpdateIC(obj, val, scratch, saveRegs))
+        return false;
+
+    // Reload obj->elements as callTypeUpdateIC used the scratch register.
+    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
+
+    // Increment initLength and length.
+    Address length(scratch, ObjectElements::offsetOfLength());
+    masm.add32(Imm32(1), initLength);
+    masm.load32(length, scratchLength);
+    masm.add32(Imm32(1), length);
+
+    // Store the value.
+    masm.storeValue(val, element);
+    emitPostBarrierElement(obj, val, scratch, scratchLength);
+
+    // Return value is new length.
+    masm.add32(Imm32(1), scratchLength);
+    masm.tagValue(JSVAL_TYPE_INT32, scratchLength, val);
+
+    return true;
+}
+
+bool
 BaselineCacheIRCompiler::emitStoreTypedElement()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     Register index = allocator.useRegister(masm, reader.int32OperandId());
     ValueOperand val = allocator.useValueRegister(masm, reader.valOperandId());
 
     TypedThingLayout layout = reader.typedThingLayout();
     Scalar::Type type = reader.scalarType();
@@ -2093,60 +2206,44 @@ BaselineCacheIRCompiler::init(CacheKind 
     allocator.initAvailableRegs(available);
     outputUnchecked_.emplace(R0);
     return true;
 }
 
 static const size_t MaxOptimizedCacheIRStubs = 16;
 
 ICStub*
-jit::AttachBaselineCacheIRStub(JSContext* cx, const CacheIRWriter& writer,
-                               CacheKind kind, ICStubEngine engine, JSScript* outerScript,
-                               ICFallbackStub* stub, bool* attached)
+js::jit::AttachBaselineCacheIRStub(JSContext* cx, const CacheIRWriter& writer,
+                                   CacheKind kind, BaselineCacheIRStubKind stubKind,
+                                   ICStubEngine engine, JSScript* outerScript,
+                                   ICFallbackStub* stub, bool* attached)
 {
     // We shouldn't GC or report OOM (or any other exception) here.
     AutoAssertNoPendingException aanpe(cx);
     JS::AutoCheckCannotGC nogc;
 
     MOZ_ASSERT(!*attached);
 
     if (writer.failed())
         return nullptr;
 
     // Just a sanity check: the caller should ensure we don't attach an
     // unlimited number of stubs.
     MOZ_ASSERT(stub->numOptimizedStubs() < MaxOptimizedCacheIRStubs);
 
-    enum class CacheIRStubKind { Regular, Monitored, Updated };
-
-    uint32_t stubDataOffset;
-    CacheIRStubKind stubKind;
-    switch (kind) {
-      case CacheKind::Compare:
-      case CacheKind::In:
-      case CacheKind::HasOwn:
-      case CacheKind::BindName:
-      case CacheKind::TypeOf:
-      case CacheKind::GetIterator:
-        stubDataOffset = sizeof(ICCacheIR_Regular);
-        stubKind = CacheIRStubKind::Regular;
+    uint32_t stubDataOffset = 0;
+    switch (stubKind) {
+      case BaselineCacheIRStubKind::Monitored:
+        stubDataOffset = sizeof(ICCacheIR_Monitored);
         break;
-      case CacheKind::GetProp:
-      case CacheKind::GetElem:
-      case CacheKind::GetName:
-      case CacheKind::GetPropSuper:
-      case CacheKind::GetElemSuper:
-      case CacheKind::Call:
-        stubDataOffset = sizeof(ICCacheIR_Monitored);
-        stubKind = CacheIRStubKind::Monitored;
+      case BaselineCacheIRStubKind::Regular:
+        stubDataOffset = sizeof(ICCacheIR_Regular);
         break;
-      case CacheKind::SetProp:
-      case CacheKind::SetElem:
+      case BaselineCacheIRStubKind::Updated:
         stubDataOffset = sizeof(ICCacheIR_Updated);
-        stubKind = CacheIRStubKind::Updated;
         break;
     }
 
     JitZone* jitZone = cx->zone()->jitZone();
 
     // Check if we already have JitCode for this stub.
     CacheIRStubInfo* stubInfo;
     CacheIRStubKey::Lookup lookup(kind, engine, writer.codeStart(), writer.codeLength());
@@ -2181,37 +2278,37 @@ jit::AttachBaselineCacheIRStub(JSContext
     MOZ_ASSERT(stubInfo->stubDataSize() == writer.stubDataSize());
 
     // Ensure we don't attach duplicate stubs. This can happen if a stub failed
     // for some reason and the IR generator doesn't check for exactly the same
     // conditions.
     for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
         bool updated = false;
         switch (stubKind) {
-          case CacheIRStubKind::Regular: {
+          case BaselineCacheIRStubKind::Regular: {
             if (!iter->isCacheIR_Regular())
                 continue;
             auto otherStub = iter->toCacheIR_Regular();
             if (otherStub->stubInfo() != stubInfo)
                 continue;
             if (!writer.stubDataEqualsMaybeUpdate(otherStub->stubDataStart(), &updated))
                 continue;
             break;
           }
-          case CacheIRStubKind::Monitored: {
+          case BaselineCacheIRStubKind::Monitored: {
             if (!iter->isCacheIR_Monitored())
                 continue;
             auto otherStub = iter->toCacheIR_Monitored();
             if (otherStub->stubInfo() != stubInfo)
                 continue;
             if (!writer.stubDataEqualsMaybeUpdate(otherStub->stubDataStart(), &updated))
                 continue;
             break;
           }
-          case CacheIRStubKind::Updated: {
+          case BaselineCacheIRStubKind::Updated: {
             if (!iter->isCacheIR_Updated())
                 continue;
             auto otherStub = iter->toCacheIR_Updated();
             if (otherStub->stubInfo() != stubInfo)
                 continue;
             if (!writer.stubDataEqualsMaybeUpdate(otherStub->stubDataStart(), &updated))
                 continue;
             break;
@@ -2232,33 +2329,33 @@ jit::AttachBaselineCacheIRStub(JSContext
 
     ICStubSpace* stubSpace = ICStubCompiler::StubSpaceForStub(stubInfo->makesGCCalls(),
                                                               outerScript, engine);
     void* newStubMem = stubSpace->alloc(bytesNeeded);
     if (!newStubMem)
         return nullptr;
 
     switch (stubKind) {
-      case CacheIRStubKind::Regular: {
+      case BaselineCacheIRStubKind::Regular: {
         auto newStub = new(newStubMem) ICCacheIR_Regular(code, stubInfo);
         writer.copyStubData(newStub->stubDataStart());
         stub->addNewStub(newStub);
         *attached = true;
         return newStub;
       }
-      case CacheIRStubKind::Monitored: {
+      case BaselineCacheIRStubKind::Monitored: {
         ICStub* monitorStub =
             stub->toMonitoredFallbackStub()->fallbackMonitorStub()->firstMonitorStub();
         auto newStub = new(newStubMem) ICCacheIR_Monitored(code, monitorStub, stubInfo);
         writer.copyStubData(newStub->stubDataStart());
         stub->addNewStub(newStub);
         *attached = true;
         return newStub;
       }
-      case CacheIRStubKind::Updated: {
+      case BaselineCacheIRStubKind::Updated: {
         auto newStub = new(newStubMem) ICCacheIR_Updated(code, stubInfo);
         if (!newStub->initUpdatingChain(cx, stubSpace)) {
             cx->recoverFromOutOfMemory();
             return nullptr;
         }
         writer.copyStubData(newStub->stubDataStart());
         stub->addNewStub(newStub);
         *attached = true;
--- a/js/src/jit/BaselineCacheIRCompiler.h
+++ b/js/src/jit/BaselineCacheIRCompiler.h
@@ -12,16 +12,19 @@
 #include "jit/CacheIRCompiler.h"
 
 namespace js {
 namespace jit {
 
 class ICFallbackStub;
 class ICStub;
 
+enum class BaselineCacheIRStubKind { Regular, Monitored, Updated };
+
 ICStub* AttachBaselineCacheIRStub(JSContext* cx, const CacheIRWriter& writer,
-                                  CacheKind kind, ICStubEngine engine, JSScript* outerScript,
+                                  CacheKind kind, BaselineCacheIRStubKind stubKind,
+                                  ICStubEngine engine, JSScript* outerScript,
                                   ICFallbackStub* stub, bool* attached);
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_BaselineCacheIRCompiler_h */
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -809,16 +809,17 @@ DoGetElemFallback(JSContext* cx, Baselin
         stub->discardStubs(cx);
 
     if (stub->state().canAttachStub()) {
         ICStubEngine engine = ICStubEngine::Baseline;
         GetPropIRGenerator gen(cx, script, pc, CacheKind::GetElem, stub->state().mode(),
                                &isTemporarilyUnoptimizable, lhs, rhs, lhs, CanAttachGetter::Yes);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
+                                                        BaselineCacheIRStubKind::Monitored,
                                                         engine, script, stub, &attached);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
                 if (gen.shouldNotePreliminaryObjectStub())
                     newStub->toCacheIR_Monitored()->notePreliminaryObject();
                 else if (gen.shouldUnlinkPreliminaryObjectStubs())
                     StripPreliminaryObjectStubs(cx, stub);
             }
@@ -881,16 +882,17 @@ DoGetElemSuperFallback(JSContext* cx, Ba
 
     if (stub->state().canAttachStub()) {
         ICStubEngine engine = ICStubEngine::Baseline;
         GetPropIRGenerator gen(cx, script, pc, CacheKind::GetElemSuper, stub->state().mode(),
                                &isTemporarilyUnoptimizable, lhs, rhs, receiver,
                                CanAttachGetter::Yes);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
+                                                        BaselineCacheIRStubKind::Monitored,
                                                         engine, script, stub, &attached);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
                 if (gen.shouldNotePreliminaryObjectStub())
                     newStub->toCacheIR_Monitored()->notePreliminaryObject();
                 else if (gen.shouldUnlinkPreliminaryObjectStubs())
                     StripPreliminaryObjectStubs(cx, stub);
             }
@@ -1046,16 +1048,17 @@ DoSetElemFallback(JSContext* cx, Baselin
     if (stub->state().maybeTransition())
         stub->discardStubs(cx);
 
     if (stub->state().canAttachStub()) {
         SetPropIRGenerator gen(cx, script, pc, CacheKind::SetElem, stub->state().mode(),
                                &isTemporarilyUnoptimizable, objv, index, rhs);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
+                                                        BaselineCacheIRStubKind::Updated,
                                                         ICStubEngine::Baseline, frame->script(),
                                                         stub, &attached);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
 
                 SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
 
                 if (gen.shouldNotePreliminaryObjectStub())
@@ -1109,16 +1112,17 @@ DoSetElemFallback(JSContext* cx, Baselin
     if (stub->state().maybeTransition())
         stub->discardStubs(cx);
 
     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,
                                                         ICStubEngine::Baseline, frame->script(),
                                                         stub, &attached);
             if (newStub) {
                 if (gen.shouldNotePreliminaryObjectStub())
                     newStub->toCacheIR_Updated()->notePreliminaryObject();
                 else if (gen.shouldUnlinkPreliminaryObjectStubs())
                     StripPreliminaryObjectStubs(cx, stub);
 
@@ -1306,16 +1310,17 @@ DoInFallback(JSContext* cx, BaselineFram
         RootedScript script(cx, frame->script());
         jsbytecode* pc = stub->icEntry()->pc(script);
 
         ICStubEngine engine = ICStubEngine::Baseline;
         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,
                                                         engine, script, stub, &attached);
             if (newStub)
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
         }
         if (!attached)
             stub->state().trackNotAttached();
     }
 
@@ -1374,16 +1379,17 @@ DoHasOwnFallback(JSContext* cx, Baseline
         jsbytecode* pc = stub->icEntry()->pc(script);
 
         ICStubEngine engine = ICStubEngine::Baseline;
         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,
                                                         engine, script, stub, &attached);
             if (newStub)
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
         }
         if (!attached)
             stub->state().trackNotAttached();
     }
 
@@ -1445,16 +1451,17 @@ DoGetNameFallback(JSContext* cx, Baselin
     if (stub->state().maybeTransition())
         stub->discardStubs(cx);
 
     if (stub->state().canAttachStub()) {
         ICStubEngine engine = ICStubEngine::Baseline;
         GetNameIRGenerator gen(cx, script, pc, stub->state().mode(), envChain, name);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
+                                                        BaselineCacheIRStubKind::Monitored,
                                                         engine, script, stub, &attached);
             if (newStub)
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
         }
         if (!attached)
             stub->state().trackNotAttached();
     }
 
@@ -1524,16 +1531,17 @@ DoBindNameFallback(JSContext* cx, Baseli
         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,
                                                         ICStubEngine::Baseline, script, stub,
                                                         &attached);
             if (newStub)
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
         }
         if (!attached)
             stub->state().trackNotAttached();
     }
@@ -1698,16 +1706,17 @@ DoSetPropFallback(JSContext* cx, Baselin
         stub->discardStubs(cx);
 
     if (stub->state().canAttachStub()) {
         RootedValue idVal(cx, StringValue(name));
         SetPropIRGenerator gen(cx, script, pc, CacheKind::SetProp, stub->state().mode(),
                                &isTemporarilyUnoptimizable, lhs, idVal, rhs);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
+                                                        BaselineCacheIRStubKind::Updated,
                                                         ICStubEngine::Baseline, frame->script(),
                                                         stub, &attached);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
 
                 SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
 
                 if (gen.shouldNotePreliminaryObjectStub())
@@ -1769,16 +1778,17 @@ DoSetPropFallback(JSContext* cx, Baselin
         stub->discardStubs(cx);
 
     if (stub->state().canAttachStub()) {
         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,
                                                         ICStubEngine::Baseline, frame->script(),
                                                         stub, &attached);
             if (newStub) {
                 if (gen.shouldNotePreliminaryObjectStub())
                     newStub->toCacheIR_Updated()->notePreliminaryObject();
                 else if (gen.shouldUnlinkPreliminaryObjectStubs())
                     StripPreliminaryObjectStubs(cx, stub);
 
@@ -2505,26 +2515,50 @@ DoCallFallback(JSContext* cx, BaselineFr
     RootedValue callee(cx, vp[0]);
 
     // Handle funapply with JSOP_ARGUMENTS
     if (op == JSOP_FUNAPPLY && argc == 2 && callArgs[1].isMagic(JS_OPTIMIZED_ARGUMENTS)) {
         if (!GuardFunApplyArgumentsOptimization(cx, frame, callArgs))
             return false;
     }
 
-    CallIRGenerator gen(cx, script, pc, stub->state().mode(), argc,
-                        callee, callArgs.thisv(),
-                        HandleValueArray::fromMarkedLocation(argc, vp+2));
-    bool optimizeAfterCall = false;
-    CallIRGenerator::OptStrategy optStrategy = gen.getOptStrategy(&optimizeAfterCall);
-
-    // Try attaching a call stub, if the CallIRGenerator has determined that this
-    // operation cannot be optimized after the call.
+    // Transition stub state to megamorphic or generic if warranted.
+    if (stub->state().maybeTransition())
+        stub->discardStubs(cx);
+
+    bool canAttachStub = stub->state().canAttachStub();
     bool handled = false;
-    if (!optimizeAfterCall) {
+
+    // Only bother to try optimizing JSOP_CALL with CacheIR if the chain is still
+    // allowed to attach stubs.
+    if (canAttachStub) {
+        CallIRGenerator gen(cx, script, pc, stub, stub->state().mode(), argc,
+                            callee, callArgs.thisv(),
+                            HandleValueArray::fromMarkedLocation(argc, vp+2));
+        if (gen.tryAttachStub()) {
+            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
+                                                        gen.cacheIRStubKind(),
+                                                        ICStubEngine::Baseline,
+                                                        script, stub, &handled);
+
+            if (newStub) {
+                JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
+
+                // If it's an updated stub, initialize it.
+                if (gen.cacheIRStubKind() == BaselineCacheIRStubKind::Updated)
+                    SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo());
+            }
+        }
+        if (!handled)
+            stub->state().trackNotAttached();
+    }
+
+    // Try attaching a regular call stub, but only if the CacheIR attempt didn't add
+    // any stubs.
+    if (!handled) {
         bool createSingleton = ObjectGroup::useSingletonForNewObject(cx, script, pc);
         if (!TryAttachCallStub(cx, stub, script, pc, op, argc, vp, constructing, false,
                                createSingleton, &handled))
         {
             return false;
         }
     }
 
@@ -2563,27 +2597,16 @@ DoCallFallback(JSContext* cx, BaselineFr
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid())
         return true;
 
     // Add a type monitor stub for the resulting value.
     if (!stub->addMonitorStubForValue(cx, frame, types, res))
         return false;
 
-    if (optimizeAfterCall && !handled && optStrategy != CallIRGenerator::OptStrategy::None) {
-        if (gen.tryAttachStub()) {
-            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        ICStubEngine::Baseline, script, stub,
-                                                        &handled);
-            if (newStub) {
-                JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
-            }
-        }
-    }
-
     if (!handled) {
         // If 'callee' is a potential Call_ConstStringSplit, try to attach an
         // 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;
     }
 
@@ -4167,16 +4190,17 @@ DoGetIteratorFallback(JSContext* cx, Bas
         RootedScript script(cx, frame->script());
         jsbytecode* pc = stub->icEntry()->pc(script);
 
         ICStubEngine engine = ICStubEngine::Baseline;
         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,
                                                         engine, script, stub, &attached);
             if (newStub)
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
         }
         if (!attached)
             stub->state().trackNotAttached();
     }
 
@@ -4557,16 +4581,17 @@ DoTypeOfFallback(JSContext* cx, Baseline
         RootedScript script(cx, frame->script());
         jsbytecode* pc = stub->icEntry()->pc(script);
 
         ICStubEngine engine = ICStubEngine::Baseline;
         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,
                                                         engine, script, stub, &attached);
             if (newStub)
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
         }
         if (!attached)
             stub->state().trackNotAttached();
     }
 
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -742,21 +742,21 @@ class ICCall_Fallback : public ICMonitor
 {
     friend class ICStubSpace;
   public:
     static const unsigned UNOPTIMIZABLE_CALL_FLAG = 0x1;
 
     static const uint32_t MAX_OPTIMIZED_STUBS = 16;
     static const uint32_t MAX_SCRIPTED_STUBS = 7;
     static const uint32_t MAX_NATIVE_STUBS = 7;
+
   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;
     }
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/CacheIR.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/FloatingPoint.h"
 
+#include "jit/BaselineCacheIRCompiler.h"
 #include "jit/BaselineIC.h"
 #include "jit/CacheIRSpewer.h"
 #include "jit/IonCaches.h"
 
 #include "vm/SelfHosting.h"
 #include "jsobjinlines.h"
 
 #include "jit/MacroAssembler-inl.h"
@@ -3742,140 +3743,233 @@ GetIteratorIRGenerator::tryAttachNativeI
         writer.guardAndGetIterator(objId, iterobj, &cx_->compartment()->enumerators);
     writer.loadObjectResult(iterId);
     writer.returnFromIC();
 
     return true;
 }
 
 CallIRGenerator::CallIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
-                                 ICState::Mode mode, uint32_t argc,
+                                 ICCall_Fallback* stub, ICState::Mode mode, uint32_t argc,
                                  HandleValue callee, HandleValue thisval, HandleValueArray args)
   : IRGenerator(cx, script, pc, CacheKind::Call, mode),
     argc_(argc),
     callee_(callee),
     thisval_(thisval),
     args_(args),
-    cachedStrategy_()
+    typeCheckInfo_(cx, /* needsTypeBarrier = */ true),
+    cacheIRStubKind_(BaselineCacheIRStubKind::Regular)
 { }
 
-CallIRGenerator::OptStrategy
-CallIRGenerator::canOptimize()
+bool
+CallIRGenerator::tryAttachStringSplit()
 {
-    // Ensure callee is a function.
-    if (!callee_.isObject() || !callee_.toObject().is<JSFunction>())
-        return OptStrategy::None;
-
-    RootedFunction calleeFunc(cx_, &callee_.toObject().as<JSFunction>());
-
-    OptStrategy strategy;
-    if ((strategy = canOptimizeStringSplit(calleeFunc)) != OptStrategy::None) {
-        return strategy;
-    }
-
-    return OptStrategy::None;
-}
-
-CallIRGenerator::OptStrategy
-CallIRGenerator::canOptimizeStringSplit(HandleFunction calleeFunc)
-{
+    // Only optimize StringSplitString(str, str)
     if (argc_ != 2 || !args_[0].isString() || !args_[1].isString())
-        return OptStrategy::None;
+        return false;
 
     // Just for now: if they're both atoms, then do not optimize using
     // CacheIR and allow the legacy "ConstStringSplit" BaselineIC optimization
     // to proceed.
     if (args_[0].toString()->isAtom() && args_[1].toString()->isAtom())
-        return OptStrategy::None;
-
-    if (!calleeFunc->isNative())
-        return OptStrategy::None;
-
-    if (calleeFunc->native() != js::intrinsic_StringSplitString)
-        return OptStrategy::None;
-
-    return OptStrategy::StringSplit;
-}
-
-bool
-CallIRGenerator::tryAttachStringSplit()
-{
+        return false;
+
     // Get the object group to use for this location.
     RootedObjectGroup group(cx_, ObjectGroupCompartment::getStringSplitStringGroup(cx_));
-    if (!group) {
+    if (!group)
         return false;
-    }
 
     AutoAssertNoPendingException aanpe(cx_);
     Int32OperandId argcId(writer.setInputOperandId(0));
 
     // Ensure argc == 1.
     writer.guardSpecificInt32Immediate(argcId, 2);
 
-    // 1 argument only.  Stack-layout here is (bottom to top):
+    // 2 arguments.  Stack-layout here is (bottom to top):
     //
     //  3: Callee
     //  2: ThisValue
     //  1: Arg0
     //  0: Arg1 <-- Top of stack
 
-    // Ensure callee is an object and is the function that matches the callee optimized
-    // against during stub generation (i.e. the String_split function object).
+    // Ensure callee is the |String_split| native function.
     ValOperandId calleeValId = writer.loadStackValue(3);
     ObjOperandId calleeObjId = writer.guardIsObject(calleeValId);
     writer.guardIsNativeFunction(calleeObjId, js::intrinsic_StringSplitString);
 
     // Ensure arg0 is a string.
     ValOperandId arg0ValId = writer.loadStackValue(1);
     StringOperandId arg0StrId = writer.guardIsString(arg0ValId);
 
     // Ensure arg1 is a string.
     ValOperandId arg1ValId = writer.loadStackValue(0);
     StringOperandId arg1StrId = writer.guardIsString(arg1ValId);
 
     // Call custom string splitter VM-function.
     writer.callStringSplitResult(arg0StrId, arg1StrId, group);
     writer.typeMonitorResult();
 
+    cacheIRStubKind_ = BaselineCacheIRStubKind::Monitored;
+    trackAttached("StringSplitString");
+
+    TypeScript::Monitor(cx_, script_, pc_, TypeSet::ObjectType(group));
+
     return true;
 }
 
-CallIRGenerator::OptStrategy
-CallIRGenerator::getOptStrategy(bool* optimizeAfterCall)
+bool
+CallIRGenerator::tryAttachArrayPush()
 {
-    if (!cachedStrategy_) {
-        cachedStrategy_ = mozilla::Some(canOptimize());
-    }
-    if (optimizeAfterCall != nullptr) {
-        MOZ_ASSERT(cachedStrategy_.isSome());
-        switch (cachedStrategy_.value()) {
-          case OptStrategy::StringSplit:
-            *optimizeAfterCall = true;
-            break;
-
-          default:
-            *optimizeAfterCall = false;
-        }
-    }
-    return cachedStrategy_.value();
+    // Only optimize on obj.push(val);
+    if (argc_ != 1 || !thisval_.isObject())
+        return false;
+
+    // Where |obj| is a native array.
+    RootedObject thisobj(cx_, &thisval_.toObject());
+    if (!thisobj->is<ArrayObject>())
+        return false;
+
+    RootedArrayObject thisarray(cx_, &thisobj->as<ArrayObject>());
+
+    // And the object group for the array is not collecting preliminary objects.
+    if (thisobj->group()->maybePreliminaryObjects())
+        return false;
+
+    // Check for other indexed properties or class hooks.
+    if (!CanAttachAddElement(thisobj, /* isInit = */ false))
+        return false;
+
+    // Can't add new elements to arrays with non-writable length.
+    if (!thisarray->lengthIsWritable())
+        return false;
+
+    // Check that array is extensible.
+    if (!thisarray->nonProxyIsExtensible())
+        return false;
+
+    MOZ_ASSERT(!thisarray->getElementsHeader()->isFrozen(),
+               "Extensible arrays should not have frozen elements");
+    MOZ_ASSERT(thisarray->lengthIsWritable());
+
+    // After this point, we can generate code fine.
+
+    // Generate code.
+    AutoAssertNoPendingException aanpe(cx_);
+    Int32OperandId argcId(writer.setInputOperandId(0));
+
+    // Ensure argc == 1.
+    writer.guardSpecificInt32Immediate(argcId, 1);
+
+    // 1 argument only.  Stack-layout here is (bottom to top):
+    //
+    //  2: Callee
+    //  1: ThisValue
+    //  0: Arg0 <-- Top of stack.
+
+    // Guard callee is the |js::array_push| native function.
+    ValOperandId calleeValId = writer.loadStackValue(2);
+    ObjOperandId calleeObjId = writer.guardIsObject(calleeValId);
+    writer.guardIsNativeFunction(calleeObjId, js::array_push);
+
+    // Guard this is an array object.
+    ValOperandId thisValId = writer.loadStackValue(1);
+    ObjOperandId thisObjId = writer.guardIsObject(thisValId);
+    writer.guardClass(thisObjId, GuardClassKind::Array);
+
+    // This is a soft assert, documenting the fact that we pass 'true'
+    // for needsTypeBarrier when constructing typeCheckInfo_ for CallIRGenerator.
+    // Can be removed safely if the assumption becomes false.
+    MOZ_ASSERT(typeCheckInfo_.needsTypeBarrier());
+
+    // Guard that the group and shape matches.
+    if (typeCheckInfo_.needsTypeBarrier())
+        writer.guardGroup(thisObjId, thisobj->group());
+    writer.guardShape(thisObjId, thisarray->shape());
+
+    // Guard proto chain shapes.
+    ShapeGuardProtoChain(writer, thisobj, thisObjId);
+
+    // arr.push(x) is equivalent to arr[arr.length] = x for regular arrays.
+    ValOperandId argId = writer.loadStackValue(0);
+    writer.arrayPush(thisObjId, argId);
+
+    writer.returnFromIC();
+
+    // Set the type-check info, and the stub kind to Updated
+    typeCheckInfo_.set(thisobj->group(), JSID_VOID);
+
+    cacheIRStubKind_ = BaselineCacheIRStubKind::Updated;
+
+    trackAttached("ArrayPush");
+    return true;
 }
 
 bool
 CallIRGenerator::tryAttachStub()
 {
-    OptStrategy strategy = getOptStrategy();
-
-    if (strategy == OptStrategy::StringSplit) {
-        return tryAttachStringSplit();
+    // Only optimize when the mode is Specialized.
+    if (mode_ != ICState::Mode::Specialized)
+        return false;
+
+    // Ensure callee is a function.
+    if (!callee_.isObject() || !callee_.toObject().is<JSFunction>())
+        return false;
+
+    RootedFunction calleeFunc(cx_, &callee_.toObject().as<JSFunction>());
+
+    // Check for native-function optimizations.
+    if (calleeFunc->isNative()) {
+
+        if (calleeFunc->native() == js::intrinsic_StringSplitString) {
+            if (tryAttachStringSplit())
+                return true;
+        }
+
+        if (calleeFunc->native() == js::array_push) {
+            if (tryAttachArrayPush())
+                return true;
+        }
     }
 
-    MOZ_ASSERT(strategy == OptStrategy::None);
     return false;
 }
 
+void
+CallIRGenerator::trackAttached(const char* name)
+{
+#ifdef JS_CACHEIR_SPEW
+    CacheIRSpewer& sp = CacheIRSpewer::singleton();
+    if (sp.enabled()) {
+        LockGuard<Mutex> guard(sp.lock());
+        sp.beginCache(guard, *this);
+        sp.valueProperty(guard, "callee", callee_);
+        sp.valueProperty(guard, "thisval", thisval_);
+        sp.valueProperty(guard, "argc", Int32Value(argc_));
+        sp.attached(guard, name);
+        sp.endCache(guard);
+    }
+#endif
+}
+
+void
+CallIRGenerator::trackNotAttached()
+{
+#ifdef JS_CACHEIR_SPEW
+    CacheIRSpewer& sp = CacheIRSpewer::singleton();
+    if (sp.enabled()) {
+        LockGuard<Mutex> guard(sp.lock());
+        sp.beginCache(guard, *this);
+        sp.valueProperty(guard, "callee", callee_);
+        sp.valueProperty(guard, "thisval", thisval_);
+        sp.valueProperty(guard, "argc", Int32Value(argc_));
+        sp.endCache(guard);
+    }
+#endif
+}
+
 CompareIRGenerator::CompareIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
                                        ICState::Mode mode, JSOp op,
                                        HandleValue lhsVal, HandleValue rhsVal)
   : IRGenerator(cx, script, pc, CacheKind::Compare, mode),
     op_(op), lhsVal_(lhsVal), rhsVal_(rhsVal)
 { }
 
 bool
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -14,16 +14,19 @@
 #include "gc/Rooting.h"
 #include "jit/CompactBuffer.h"
 #include "jit/ICState.h"
 #include "jit/SharedIC.h"
 
 namespace js {
 namespace jit {
 
+
+enum class BaselineCacheIRStubKind;
+
 // CacheIR is an (extremely simple) linear IR language for inline caches.
 // From this IR, we can generate machine code for Baseline or Ion IC stubs.
 //
 // IRWriter
 // --------
 // CacheIR bytecode is written using IRWriter. This class also records some
 // metadata that's used by the Baseline and Ion code generators to generate
 // (efficient) machine code.
@@ -213,16 +216,17 @@ extern const char* CacheKindNames[];
     _(AddAndStoreFixedSlot)               \
     _(AddAndStoreDynamicSlot)             \
     _(AllocateAndStoreDynamicSlot)        \
     _(StoreTypedObjectReferenceProperty)  \
     _(StoreTypedObjectScalarProperty)     \
     _(StoreUnboxedProperty)               \
     _(StoreDenseElement)                  \
     _(StoreDenseElementHole)              \
+    _(ArrayPush)                          \
     _(StoreTypedElement)                  \
     _(StoreUnboxedArrayElement)           \
     _(StoreUnboxedArrayElementHole)       \
     _(CallNativeSetter)                   \
     _(CallScriptedSetter)                 \
     _(CallSetArrayLength)                 \
     _(CallProxySet)                       \
     _(CallProxySetByValue)                \
@@ -815,16 +819,20 @@ class MOZ_RAII CacheIRWriter : public JS
     void storeDenseElementHole(ObjOperandId obj, Int32OperandId index, ValOperandId rhs,
                                bool handleAdd)
     {
         writeOpWithOperandId(CacheOp::StoreDenseElementHole, obj);
         writeOperandId(index);
         writeOperandId(rhs);
         buffer_.writeByte(handleAdd);
     }
+    void arrayPush(ObjOperandId obj, ValOperandId rhs) {
+        writeOpWithOperandId(CacheOp::ArrayPush, obj);
+        writeOperandId(rhs);
+    }
     void callScriptedSetter(ObjOperandId obj, JSFunction* setter, ValOperandId rhs) {
         writeOpWithOperandId(CacheOp::CallScriptedSetter, obj);
         addStubField(uintptr_t(setter), StubField::Type::JSObject);
         writeOperandId(rhs);
     }
     void callNativeSetter(ObjOperandId obj, JSFunction* setter, ValOperandId rhs) {
         writeOpWithOperandId(CacheOp::CallNativeSetter, obj);
         addStubField(uintptr_t(setter), StubField::Type::JSObject);
@@ -1455,41 +1463,45 @@ class MOZ_RAII GetIteratorIRGenerator : 
     GetIteratorIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc, ICState::Mode mode,
                            HandleValue value);
 
     bool tryAttachStub();
 };
 
 class MOZ_RAII CallIRGenerator : public IRGenerator
 {
-  public:
-    enum class OptStrategy {
-        None = 0,
-        StringSplit
-    };
-
   private:
     uint32_t argc_;
     HandleValue callee_;
     HandleValue thisval_;
     HandleValueArray args_;
-
-    mozilla::Maybe<OptStrategy> cachedStrategy_;
+    PropertyTypeCheckInfo typeCheckInfo_;
+    BaselineCacheIRStubKind cacheIRStubKind_;
 
-    OptStrategy canOptimize();
-    OptStrategy canOptimizeStringSplit(HandleFunction calleeFunc);
     bool tryAttachStringSplit();
+    bool tryAttachArrayPush();
+
+    void trackAttached(const char* name);
+    void trackNotAttached();
 
   public:
-    CallIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc, ICState::Mode mode,
+    CallIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
+                    ICCall_Fallback* stub, ICState::Mode mode,
                     uint32_t argc, HandleValue callee, HandleValue thisval,
                     HandleValueArray args);
 
-    OptStrategy getOptStrategy(bool* optimizeAfterCall = nullptr);
     bool tryAttachStub();
+
+    BaselineCacheIRStubKind cacheIRStubKind() const {
+        return cacheIRStubKind_;
+    }
+
+    const PropertyTypeCheckInfo* typeCheckInfo() const {
+        return &typeCheckInfo_;
+    }
 };
 
 class MOZ_RAII CompareIRGenerator : public IRGenerator
 {
     JSOp op_;
     HandleValue lhsVal_;
     HandleValue rhsVal_;
 
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -8468,34 +8468,38 @@ CodeGenerator::visitBoundsCheckLower(LBo
     bailoutCmp32(Assembler::LessThan, ToRegister(lir->index()), Imm32(min),
                  lir->snapshot());
 }
 
 class OutOfLineStoreElementHole : public OutOfLineCodeBase<CodeGenerator>
 {
     LInstruction* ins_;
     Label rejoinStore_;
+    bool strict_;
 
   public:
-    explicit OutOfLineStoreElementHole(LInstruction* ins)
-      : ins_(ins)
+    explicit OutOfLineStoreElementHole(LInstruction* ins, bool strict)
+      : ins_(ins), strict_(strict)
     {
         MOZ_ASSERT(ins->isStoreElementHoleV() || ins->isStoreElementHoleT() ||
                    ins->isFallibleStoreElementV() || ins->isFallibleStoreElementT());
     }
 
     void accept(CodeGenerator* codegen) {
         codegen->visitOutOfLineStoreElementHole(this);
     }
     LInstruction* ins() const {
         return ins_;
     }
     Label* rejoinStore() {
         return &rejoinStore_;
     }
+    bool strict() const {
+        return strict_;
+    }
 };
 
 void
 CodeGenerator::emitStoreHoleCheck(Register elements, const LAllocation* index,
                                   int32_t offsetAdjustment, LSnapshot* snapshot)
 {
     Label bail;
     if (index->isConstant()) {
@@ -8574,17 +8578,18 @@ CodeGenerator::visitStoreElementV(LStore
 }
 
 template <typename T> void
 CodeGenerator::emitStoreElementHoleT(T* lir)
 {
     static_assert(std::is_same<T, LStoreElementHoleT>::value || std::is_same<T, LFallibleStoreElementT>::value,
                   "emitStoreElementHoleT called with unexpected argument type");
 
-    OutOfLineStoreElementHole* ool = new(alloc()) OutOfLineStoreElementHole(lir);
+    OutOfLineStoreElementHole* ool =
+        new(alloc()) OutOfLineStoreElementHole(lir, current->mir()->strict());
     addOutOfLineCode(ool, lir->mir());
 
     Register obj = ToRegister(lir->object());
     Register elements = ToRegister(lir->elements());
     const LAllocation* index = lir->index();
     RegisterOrInt32Constant key = ToRegisterOrInt32Constant(index);
 
     JSValueType unboxedType = lir->mir()->unboxedType();
@@ -8633,17 +8638,18 @@ CodeGenerator::visitStoreElementHoleT(LS
 }
 
 template <typename T> void
 CodeGenerator::emitStoreElementHoleV(T* lir)
 {
     static_assert(std::is_same<T, LStoreElementHoleV>::value || std::is_same<T, LFallibleStoreElementV>::value,
                   "emitStoreElementHoleV called with unexpected parameter type");
 
-    OutOfLineStoreElementHole* ool = new(alloc()) OutOfLineStoreElementHole(lir);
+    OutOfLineStoreElementHole* ool =
+        new(alloc()) OutOfLineStoreElementHole(lir, current->mir()->strict());
     addOutOfLineCode(ool, lir->mir());
 
     Register obj = ToRegister(lir->object());
     Register elements = ToRegister(lir->elements());
     const LAllocation* index = lir->index();
     const ValueOperand value = ToValue(lir, T::Value);
     RegisterOrInt32Constant key = ToRegisterOrInt32Constant(index);
 
@@ -8893,17 +8899,17 @@ CodeGenerator::visitOutOfLineStoreElemen
     } else {
         // Jump to the inline path where we will store the value.
         masm.jump(ool->rejoinStore());
     }
 
     masm.bind(&callStub);
     saveLive(ins);
 
-    pushArg(Imm32(current->mir()->strict()));
+    pushArg(Imm32(ool->strict()));
     pushArg(value);
     if (index->isConstant())
         pushArg(Imm32(ToInt32(index)));
     else
         pushArg(ToRegister(index));
     pushArg(object);
     callVM(SetDenseOrUnboxedArrayElementInfo, ins);
 
--- a/js/src/jit/ICState.h
+++ b/js/src/jit/ICState.h
@@ -61,29 +61,33 @@ class ICState
     {
         reset();
     }
 
     Mode mode() const { return mode_; }
     size_t numOptimizedStubs() const { return numOptimizedStubs_; }
 
     MOZ_ALWAYS_INLINE bool canAttachStub() const {
-        MOZ_ASSERT(numOptimizedStubs_ <= MaxOptimizedStubs);
+        // 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;
         return true;
     }
 
     bool invalid() const { return invalid_; }
     void setInvalid() { invalid_ = true; }
 
     // If this returns true, we transitioned to a new mode and the caller
     // should discard all stubs.
     MOZ_MUST_USE MOZ_ALWAYS_INLINE bool maybeTransition() {
-        MOZ_ASSERT(numOptimizedStubs_ <= MaxOptimizedStubs);
+        // Note: we cannot assert that numOptimizedStubs_ <= MaxOptimizedStubs
+        // because old-style baseline ICs may attach more stubs than
+        // MaxOptimizedStubs allows.
         if (mode_ == Mode::Generic)
             return false;
         if (numOptimizedStubs_ < MaxOptimizedStubs && numFailures_ < maxFailures())
             return false;
         if (numFailures_ == maxFailures() || mode_ == Mode::Megamorphic) {
             transition(Mode::Generic);
             return true;
         }
--- a/js/src/jit/IonCacheIRCompiler.cpp
+++ b/js/src/jit/IonCacheIRCompiler.cpp
@@ -1778,16 +1778,23 @@ IonCacheIRCompiler::emitStoreDenseElemen
     masm.bind(&doStore);
     EmitIonStoreDenseElement(masm, val, scratch, element);
     if (needsPostBarrier())
         emitPostBarrierElement(obj, val, scratch, index);
     return true;
 }
 
 bool
+IonCacheIRCompiler::emitArrayPush()
+{
+    MOZ_ASSERT_UNREACHABLE("emitArrayPush not supported for IonCaches.");
+    return false;
+}
+
+bool
 IonCacheIRCompiler::emitStoreTypedElement()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     Register index = allocator.useRegister(masm, reader.int32OperandId());
     ConstantOrRegister val = allocator.useConstantOrRegister(masm, reader.valOperandId());
 
     TypedThingLayout layout = reader.typedThingLayout();
     Scalar::Type arrayType = reader.scalarType();
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -1457,16 +1457,17 @@ DoCompareFallback(JSContext* cx, void* p
     }
 
     if (engine ==  ICStubEngine::Baseline) {
         RootedScript script(cx, info.outerScript(cx));
         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,
                                                         engine, script, stub, &attached);
             if (newStub)
                  JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
             return true;
         }
     }
 
     // Try to generate new stubs.
@@ -2066,16 +2067,17 @@ DoGetPropFallback(JSContext* cx, Baselin
 
     bool attached = false;
     if (stub->state().canAttachStub()) {
         RootedValue idVal(cx, StringValue(name));
         GetPropIRGenerator gen(cx, script, pc, CacheKind::GetProp, stub->state().mode(),
                                &isTemporarilyUnoptimizable, val, idVal, val, CanAttachGetter::Yes);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
+                                                        BaselineCacheIRStubKind::Monitored,
                                                         ICStubEngine::Baseline, script,
                                                         stub, &attached);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
                 if (gen.shouldNotePreliminaryObjectStub())
                     newStub->toCacheIR_Monitored()->notePreliminaryObject();
                 else if (gen.shouldUnlinkPreliminaryObjectStubs())
                     StripPreliminaryObjectStubs(cx, stub);
@@ -2136,16 +2138,17 @@ DoGetPropSuperFallback(JSContext* cx, Ba
     bool attached = false;
     if (stub->state().canAttachStub()) {
         RootedValue idVal(cx, StringValue(name));
         GetPropIRGenerator gen(cx, script, pc, CacheKind::GetPropSuper, stub->state().mode(),
                                &isTemporarilyUnoptimizable, val, idVal, receiver,
                                CanAttachGetter::Yes);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
+                                                        BaselineCacheIRStubKind::Monitored,
                                                         ICStubEngine::Baseline, script,
                                                         stub, &attached);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
                 if (gen.shouldNotePreliminaryObjectStub())
                     newStub->toCacheIR_Monitored()->notePreliminaryObject();
                 else if (gen.shouldUnlinkPreliminaryObjectStubs())
                     StripPreliminaryObjectStubs(cx, stub);
--- a/js/src/jit/shared/CodeGenerator-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-shared.cpp
@@ -158,16 +158,20 @@ CodeGeneratorShared::generateEpilogue()
     // On systems that use a constant pool, this is a good time to emit.
     masm.flushBuffer();
     return true;
 }
 
 bool
 CodeGeneratorShared::generateOutOfLineCode()
 {
+    // OOL paths should not attempt to use |current| as it's the last block
+    // instead of the block corresponding to the OOL path.
+    current = nullptr;
+
     for (size_t i = 0; i < outOfLineCode_.length(); i++) {
         // Add native => bytecode mapping entries for OOL sites.
         // Not enabled on wasm yet since it doesn't contain bytecode mappings.
         if (!gen->compilingWasm()) {
             if (!addNativeToBytecodeEntry(outOfLineCode_[i]->bytecodeSite()))
                 return false;
         }
 
--- a/js/src/jsapi-tests/testXDR.cpp
+++ b/js/src/jsapi-tests/testXDR.cpp
@@ -111,17 +111,17 @@ BEGIN_TEST(testXDR_source)
     for (const char** s = samples; *s; s++) {
         JS::CompileOptions options(cx);
         options.setFileAndLine(__FILE__, __LINE__);
         JS::RootedScript script(cx);
         CHECK(JS_CompileScript(cx, *s, strlen(*s), options, &script));
         CHECK(script);
         script = FreezeThaw(cx, script);
         CHECK(script);
-        JSString* out = JS_DecompileScript(cx, script, "testing", 0);
+        JSString* out = JS_DecompileScript(cx, script);
         CHECK(out);
         bool equal;
         CHECK(JS_StringEqualsAscii(cx, out, *s, &equal));
         CHECK(equal);
     }
     return true;
 }
 END_TEST(testXDR_source)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4594,41 +4594,41 @@ JS::CompileFunction(JSContext* cx, AutoO
     if (!chars)
         return false;
 
     return CompileFunction(cx, envChain, options, name, nargs, argnames,
                            chars.get(), length, fun);
 }
 
 JS_PUBLIC_API(JSString*)
-JS_DecompileScript(JSContext* cx, HandleScript script, const char* name, unsigned indent)
+JS_DecompileScript(JSContext* cx, HandleScript script)
 {
     MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
 
     AssertHeapIsIdle();
     CHECK_REQUEST(cx);
     script->ensureNonLazyCanonicalFunction();
     RootedFunction fun(cx, script->functionNonDelazifying());
     if (fun)
-        return JS_DecompileFunction(cx, fun, indent);
+        return JS_DecompileFunction(cx, fun);
     bool haveSource = script->scriptSource()->hasSourceData();
     if (!haveSource && !JSScript::loadSource(cx, script->scriptSource(), &haveSource))
         return nullptr;
     return haveSource ? JSScript::sourceData(cx, script)
                       : NewStringCopyZ<CanGC>(cx, "[no source]");
 }
 
 JS_PUBLIC_API(JSString*)
-JS_DecompileFunction(JSContext* cx, HandleFunction fun, unsigned indent)
+JS_DecompileFunction(JSContext* cx, HandleFunction fun)
 {
     MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
     AssertHeapIsIdle();
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, fun);
-    return FunctionToString(cx, fun, !(indent & JS_DONT_PRETTY_PRINT));
+    return FunctionToString(cx, fun, /* isToSource = */ false);
 }
 
 MOZ_NEVER_INLINE static bool
 ExecuteScript(JSContext* cx, HandleObject scope, HandleScript script, Value* rval)
 {
     MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
     AssertHeapIsIdle();
     CHECK_REQUEST(cx);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -994,17 +994,17 @@ JS_IsBuiltinFunctionConstructor(JSFuncti
 /************************************************************************/
 
 /*
  * Locking, contexts, and memory allocation.
  *
  * It is important that SpiderMonkey be initialized, and the first context
  * be created, in a single-threaded fashion.  Otherwise the behavior of the
  * library is undefined.
- * See: http://developer.mozilla.org/en/docs/Category:JSAPI_Reference
+ * See: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference
  */
 
 // Create a new runtime, with a single cooperative context for this thread.
 // On success, the new context will be the active context for the runtime.
 extern JS_PUBLIC_API(JSContext*)
 JS_NewContext(uint32_t maxbytes,
               uint32_t maxNurseryBytes = JS::DefaultNurseryBytes,
               JSRuntime* parentRuntime = nullptr);
@@ -4354,26 +4354,20 @@ extern JS_PUBLIC_API(bool)
 CompileFunction(JSContext* cx, AutoObjectVector& envChain,
                 const ReadOnlyCompileOptions& options,
                 const char* name, unsigned nargs, const char* const* argnames,
                 const char* bytes, size_t length, JS::MutableHandleFunction fun);
 
 } /* namespace JS */
 
 extern JS_PUBLIC_API(JSString*)
-JS_DecompileScript(JSContext* cx, JS::Handle<JSScript*> script, const char* name, unsigned indent);
-
-/*
- * API extension: OR this into indent to avoid pretty-printing the decompiled
- * source resulting from JS_DecompileFunction.
- */
-#define JS_DONT_PRETTY_PRINT    ((unsigned)0x8000)
+JS_DecompileScript(JSContext* cx, JS::Handle<JSScript*> script);
 
 extern JS_PUBLIC_API(JSString*)
-JS_DecompileFunction(JSContext* cx, JS::Handle<JSFunction*> fun, unsigned indent);
+JS_DecompileFunction(JSContext* cx, JS::Handle<JSFunction*> fun);
 
 
 /*
  * NB: JS_ExecuteScript and the JS::Evaluate APIs come in two flavors: either
  * they use the global as the scope, or they take an AutoObjectVector of objects
  * to use as the scope chain.  In the former case, the global is also used as
  * the "this" keyword value and the variables object (ECMA parlance for where
  * 'var' and 'function' bind names) of the execution context for script.  In the
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -303,16 +303,17 @@ struct JSContext : public JS::RootingCon
     }
 
     // Methods specific to any HelperThread for the context.
     bool addPendingCompileError(js::CompileError** err);
     void addPendingOverRecursed();
     void addPendingOutOfMemory();
 
     JSRuntime* runtime() { return runtime_; }
+    const JSRuntime* runtime() const { return runtime_; }
 
     static size_t offsetOfCompartment() {
         return offsetof(JSContext, compartment_);
     }
 
     friend class JS::AutoSaveExceptionState;
     friend class js::jit::DebugModeOSRVolatileJitFrameIterator;
     friend void js::ReportOverRecursed(JSContext*, unsigned errorNumber);
@@ -562,16 +563,20 @@ struct JSContext : public JS::RootingCon
     // of any such threads also inhibits collection of atoms. We don't scan the
     // stacks of exclusive threads, so we need to avoid collecting their
     // objects in another way. The only GC thing pointers they have are to
     // their exclusive compartment (which is not collected) or to the atoms
     // compartment. Therefore, we avoid collecting the atoms compartment when
     // exclusive threads are running.
     js::ThreadLocalData<unsigned> keepAtoms;
 
+    bool canCollectAtoms() const {
+        return !keepAtoms && !runtime()->hasHelperThreadZones();
+    }
+
   private:
     // Pools used for recycling name maps and vectors when parsing and
     // emitting bytecode. Purged on GC when there are no active script
     // compilations.
     js::ThreadLocalData<js::frontend::NameCollectionPool> frontendCollectionPool_;
   public:
 
     js::frontend::NameCollectionPool& frontendCollectionPool() {
@@ -1251,18 +1256,18 @@ class MOZ_RAII AutoKeepAtoms
         cx->keepAtoms++;
     }
     ~AutoKeepAtoms() {
         MOZ_ASSERT(cx->keepAtoms);
         cx->keepAtoms--;
 
         JSRuntime* rt = cx->runtime();
         if (!cx->helperThread()) {
-            if (rt->gc.fullGCForAtomsRequested() && !cx->keepAtoms)
-                rt->gc.triggerFullGCForAtoms();
+            if (rt->gc.fullGCForAtomsRequested() && cx->canCollectAtoms())
+                rt->gc.triggerFullGCForAtoms(cx);
         }
     }
 };
 
 // Debugging RAII class which marks the current thread as performing an Ion
 // compilation, for use by CurrentThreadCan{Read,Write}CompilationData
 class MOZ_RAII AutoEnterIonCompilation
 {
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -986,68 +986,97 @@ const Class JSFunction::class_ = {
     JSCLASS_HAS_CACHED_PROTO(JSProto_Function),
     &JSFunctionClassOps,
     &JSFunctionClassSpec
 };
 
 const Class* const js::FunctionClassPtr = &JSFunction::class_;
 
 JSString*
-js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint)
+js::FunctionToStringCache::lookup(JSScript* script) const
+{
+    for (size_t i = 0; i < NumEntries; i++) {
+        if (entries_[i].script == script)
+            return entries_[i].string;
+    }
+    return nullptr;
+}
+
+void
+js::FunctionToStringCache::put(JSScript* script, JSString* string)
+{
+    for (size_t i = NumEntries - 1; i > 0; i--)
+        entries_[i] = entries_[i - 1];
+
+    entries_[0].set(script, string);
+}
+
+JSString*
+js::FunctionToString(JSContext* cx, HandleFunction fun, bool isToSource)
 {
     if (fun->isInterpretedLazy() && !JSFunction::getOrCreateScript(cx, fun))
         return nullptr;
 
     if (IsAsmJSModule(fun))
-        return AsmJSModuleToString(cx, fun, !prettyPrint);
+        return AsmJSModuleToString(cx, fun, isToSource);
     if (IsAsmJSFunction(fun))
         return AsmJSFunctionToString(cx, fun);
 
     if (IsWrappedAsyncFunction(fun)) {
         RootedFunction unwrapped(cx, GetUnwrappedAsyncFunction(fun));
-        return FunctionToString(cx, unwrapped, prettyPrint);
+        return FunctionToString(cx, unwrapped, isToSource);
     }
     if (IsWrappedAsyncGenerator(fun)) {
         RootedFunction unwrapped(cx, GetUnwrappedAsyncGenerator(fun));
-        return FunctionToString(cx, unwrapped, prettyPrint);
+        return FunctionToString(cx, unwrapped, isToSource);
     }
 
-    StringBuffer out(cx);
     RootedScript script(cx);
 
     if (fun->hasScript()) {
         script = fun->nonLazyScript();
-        if (script->isGeneratorExp()) {
-            if (!out.append("function genexp() {") ||
-                !out.append("\n    [generator expression]\n") ||
-                !out.append("}"))
-            {
-                return nullptr;
-            }
-            return out.finishString();
-        }
+        if (MOZ_UNLIKELY(script->isGeneratorExp()))
+            return NewStringCopyZ<CanGC>(cx, "function genexp() {\n    [generator expression]\n}");
     }
 
     // Default class constructors are self-hosted, but have their source
     // objects overridden to refer to the span of the class statement or
     // expression. Non-default class constructors are never self-hosted. So,
     // all class constructors always have source.
     bool haveSource = fun->isInterpreted() && (fun->isClassConstructor() ||
                                                !fun->isSelfHostedBuiltin());
 
-    // If we're not in pretty mode, put parentheses around lambda functions
-    // so that eval returns lambda, not function statement.
-    bool addParentheses = haveSource && !prettyPrint && (fun->isLambda() && !fun->isArrow());
+    // If we're in toSource mode, put parentheses around lambda functions so
+    // that eval returns lambda, not function statement.
+    bool addParentheses = haveSource && isToSource && (fun->isLambda() && !fun->isArrow());
 
     if (haveSource && !script->scriptSource()->hasSourceData() &&
         !JSScript::loadSource(cx, script->scriptSource(), &haveSource))
     {
         return nullptr;
     }
 
+    // Fast path for the common case, to avoid StringBuffer overhead.
+    if (!addParentheses && haveSource) {
+        FunctionToStringCache& cache = cx->zone()->functionToStringCache();
+        if (JSString* str = cache.lookup(script))
+            return str;
+
+        size_t start = script->toStringStart(), end = script->toStringEnd();
+        JSString* str = (end - start <= ScriptSource::SourceDeflateLimit)
+            ? script->scriptSource()->substring(cx, start, end)
+            : script->scriptSource()->substringDontDeflate(cx, start, end);
+        if (!str)
+            return nullptr;
+
+        cache.put(script, str);
+        return str;
+    }
+
+    StringBuffer out(cx);
     if (addParentheses) {
         if (!out.append('('))
             return nullptr;
     }
 
     if (haveSource) {
         if (!script->appendSourceDataForToString(cx, out))
             return nullptr;
@@ -1105,30 +1134,30 @@ js::FunctionToString(JSContext* cx, Hand
         if (!out.append(')'))
             return nullptr;
     }
 
     return out.finishString();
 }
 
 JSString*
-fun_toStringHelper(JSContext* cx, HandleObject obj, unsigned indent)
+fun_toStringHelper(JSContext* cx, HandleObject obj, bool isToSource)
 {
     if (!obj->is<JSFunction>()) {
         if (JSFunToStringOp op = obj->getOpsFunToString())
-            return op(cx, obj, indent);
+            return op(cx, obj, isToSource);
 
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                   JSMSG_INCOMPATIBLE_PROTO,
                                   js_Function_str, js_toString_str, "object");
         return nullptr;
     }
 
     RootedFunction fun(cx, &obj->as<JSFunction>());
-    return FunctionToString(cx, fun, indent != JS_DONT_PRETTY_PRINT);
+    return FunctionToString(cx, fun, isToSource);
 }
 
 bool
 js::FunctionHasDefaultHasInstance(JSFunction* fun, const WellKnownSymbols& symbols)
 {
     jsid id = SYMBOL_TO_JSID(symbols.hasInstance);
     Shape* shape = fun->lookupPure(id);
     if (shape) {
@@ -1141,26 +1170,21 @@ js::FunctionHasDefaultHasInstance(JSFunc
 }
 
 bool
 js::fun_toString(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(IsFunctionObject(args.calleev()));
 
-    uint32_t indent = 0;
-
-    if (args.length() != 0 && !ToUint32(cx, args[0], &indent))
-        return false;
-
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
-    RootedString str(cx, fun_toStringHelper(cx, obj, indent));
+    JSString* str = fun_toStringHelper(cx, obj, /* isToSource = */ false);
     if (!str)
         return false;
 
     args.rval().setString(str);
     return true;
 }
 
 #if JS_HAS_TOSOURCE
@@ -1171,22 +1195,22 @@ fun_toSource(JSContext* cx, unsigned arg
     MOZ_ASSERT(IsFunctionObject(args.calleev()));
 
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     RootedString str(cx);
     if (obj->isCallable())
-        str = fun_toStringHelper(cx, obj, JS_DONT_PRETTY_PRINT);
+        str = fun_toStringHelper(cx, obj, /* isToSource = */ true);
     else
         str = ObjectToSource(cx, obj);
-
     if (!str)
         return false;
+
     args.rval().setString(str);
     return true;
 }
 #endif
 
 bool
 js::fun_call(JSContext* cx, unsigned argc, Value* vp)
 {
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -668,17 +668,17 @@ class JSFunction : public js::NativeObje
         return kind;
     }
 };
 
 static_assert(sizeof(JSFunction) == sizeof(js::shadow::Function),
               "shadow interface must match actual interface");
 
 extern JSString*
-fun_toStringHelper(JSContext* cx, js::HandleObject obj, unsigned indent);
+fun_toStringHelper(JSContext* cx, js::HandleObject obj, bool isToSource);
 
 namespace js {
 
 extern bool
 Function(JSContext* cx, unsigned argc, Value* vp);
 
 extern bool
 Generator(JSContext* cx, unsigned argc, Value* vp);
@@ -878,17 +878,17 @@ inline const js::Value&
 JSFunction::getExtendedSlot(size_t which) const
 {
     MOZ_ASSERT(which < mozilla::ArrayLength(toExtended()->extendedSlots));
     return toExtended()->extendedSlots[which];
 }
 
 namespace js {
 
-JSString* FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPring);
+JSString* FunctionToString(JSContext* cx, HandleFunction fun, bool isToSource);
 
 template<XDRMode mode>
 bool
 XDRInterpretedFunction(XDRState<mode>* xdr, HandleScope enclosingScope,
                        HandleScriptSource sourceObject, MutableHandleFunction objp);
 
 /*
  * Report an error that call.thisv is not compatible with the specified class,
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2584,16 +2584,17 @@ GCRuntime::updateZonePointersToRelocated
 
     zone->fixupAfterMovingGC();
 
     // Fixup compartment global pointers as these get accessed during marking.
     for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
         comp->fixupAfterMovingGC();
 
     zone->externalStringCache().purge();
+    zone->functionToStringCache().purge();
 
     // Iterate through all cells that can contain relocatable pointers to update
     // them. Since updating each cell is independent we try to parallelize this
     // as much as possible.
     updateAllCellPointers(&trc, zone);
 
     // Mark roots to update them.
     {
@@ -3125,16 +3126,28 @@ GCRuntime::maybeGC(Zone* zone)
         !isIncrementalGCInProgress() && !isBackgroundSweeping())
     {
         stats().recordTrigger(usedBytes, threshold);
         PrepareZoneForGC(zone);
         startGC(GC_NORMAL, JS::gcreason::EAGER_ALLOC_TRIGGER);
     }
 }
 
+void
+GCRuntime::triggerFullGCForAtoms(JSContext* cx)
+{
+    MOZ_ASSERT(fullGCForAtomsRequested_);
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
+    MOZ_ASSERT(!JS::CurrentThreadIsHeapCollecting());
+    MOZ_ASSERT(!cx->keepAtoms);
+    MOZ_ASSERT(!rt->hasHelperThreadZones());
+    fullGCForAtomsRequested_ = false;
+    MOZ_RELEASE_ASSERT(triggerGC(JS::gcreason::DELAYED_ATOMS_GC));
+}
+
 // Do all possible decommit immediately from the current thread without
 // releasing the GC lock or allocating any memory.
 void
 GCRuntime::decommitAllWithoutUnlocking(const AutoLockGC& lock)
 {
     MOZ_ASSERT(emptyChunks(lock).count() == 0);
     for (ChunkPool::Iter chunk(availableChunks(lock)); !chunk.done(); chunk.next())
         chunk->decommitAllArenasWithoutUnlocking(lock);
@@ -3691,16 +3704,17 @@ GCRuntime::purgeRuntime(AutoLockForExclu
     gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::PURGE);
 
     for (GCCompartmentsIter comp(rt); !comp.done(); comp.next())
         comp->purge();
 
     for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
         zone->atomCache().clearAndShrink();
         zone->externalStringCache().purge();
+        zone->functionToStringCache().purge();
     }
 
     for (const CooperatingContext& target : rt->cooperatingContexts()) {
         freeUnusedLifoBlocksAfterSweeping(&target.context()->tempLifoAlloc());
         target.context()->interpreterStack().purge(rt);
         target.context()->frontendCollectionPool().purge();
     }
 
@@ -3850,28 +3864,50 @@ RelazifyFunctions(Zone* zone, AllocKind 
         if (fun->hasScript())
             fun->maybeRelazify(rt);
     }
 }
 
 static bool
 ShouldCollectZone(Zone* zone, JS::gcreason::Reason reason)
 {
-    // Normally we collect all scheduled zones.
-    if (reason != JS::gcreason::COMPARTMENT_REVIVED)
-        return zone->isGCScheduled();
-
-    // If we are repeating a GC becuase we noticed dead compartments haven't
+    // If we are repeating a GC because we noticed dead compartments haven't
     // been collected, then only collect zones contianing those compartments.
-    for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
-        if (comp->scheduledForDestruction)
-            return true;
-    }
-
-    return false;
+    if (reason == JS::gcreason::COMPARTMENT_REVIVED) {
+        for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
+            if (comp->scheduledForDestruction)
+                return true;
+        }
+
+        return false;
+    }
+
+    // Otherwise we only collect scheduled zones.
+    if (!zone->isGCScheduled())
+        return false;
+
+    // If canCollectAtoms() is false then either an instance of AutoKeepAtoms is
+    // currently on the stack or parsing is currently happening on another
+    // thread. In either case we don't have information about which atoms are
+    // roots, so we must skip collecting atoms.
+    //
+    // Note that only affects the first slice of an incremental GC since root
+    // marking is completed before we return to the mutator.
+    //
+    // Off-thread parsing is inhibited after the start of GC which prevents
+    // races between creating atoms during parsing and sweeping atoms on the
+    // active thread.
+    //
+    // Otherwise, we always schedule a GC in the atoms zone so that atoms which
+    // the other collected zones are using are marked, and we can update the
+    // set of atoms in use by the other collected zones at the end of the GC.
+    if (zone->isAtomsZone())
+        return TlsContext.get()->canCollectAtoms();
+
+    return true;
 }
 
 bool
 GCRuntime::prepareZonesForCollection(JS::gcreason::Reason reason, bool* isFullOut,
                                      AutoLockForExclusiveAccess& lock)
 {
 #ifdef DEBUG
     /* Assert that zone state is as we expect */
@@ -3886,20 +3922,19 @@ GCRuntime::prepareZonesForCollection(JS:
     *isFullOut = true;
     bool any = false;
 
     int64_t currentTime = PRMJ_Now();
 
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         /* Set up which zones will be collected. */
         if (ShouldCollectZone(zone, reason)) {
-            if (!zone->isAtomsZone()) {
-                any = true;
-                zone->changeGCState(Zone::NoGC, Zone::Mark);
-            }
+            MOZ_ASSERT(zone->canCollect());
+            any = true;
+            zone->changeGCState(Zone::NoGC, Zone::Mark);
         } else {
             *isFullOut = false;
         }
 
         zone->setPreservingCode(false);
     }
 
     // Discard JIT code more aggressively if the process is approaching its
@@ -3916,40 +3951,20 @@ GCRuntime::prepareZonesForCollection(JS:
 
     if (!cleanUpEverything && canAllocateMoreCode) {
         jit::JitActivationIterator activation(TlsContext.get());
         if (!activation.done())
             activation->compartment()->zone()->setPreservingCode(true);
     }
 
     /*
-     * If keepAtoms() is true then either an instance of AutoKeepAtoms is
-     * currently on the stack or parsing is currently happening on another
-     * thread. In either case we don't have information about which atoms are
-     * roots, so we must skip collecting atoms.
-     *
-     * Note that only affects the first slice of an incremental GC since root
-     * marking is completed before we return to the mutator.
-     *
-     * Off-thread parsing is inhibited after the start of GC which prevents
-     * races between creating atoms during parsing and sweeping atoms on the
-     * active thread.
-     *
-     * Otherwise, we always schedule a GC in the atoms zone so that atoms which
-     * the other collected zones are using are marked, and we can update the
-     * set of atoms in use by the other collected zones at the end of the GC.
+     * Check that we do collect the atoms zone if we triggered a GC for that
+     * purpose.
      */
-    if (!TlsContext.get()->keepAtoms || rt->hasHelperThreadZones()) {
-        Zone* atomsZone = rt->atomsCompartment(lock)->zone();
-        if (atomsZone->isGCScheduled()) {
-            MOZ_ASSERT(!atomsZone->isCollecting());
-            atomsZone->changeGCState(Zone::NoGC, Zone::Mark);
-            any = true;
-        }
-    }
+    MOZ_ASSERT_IF(reason == JS::gcreason::DELAYED_ATOMS_GC, atomsZone->isGCMarking());
 
     /* Check that at least one zone is scheduled for collection. */
     return any;
 }
 
 static void
 DiscardJITCodeForIncrementalGC(JSRuntime* rt)
 {
@@ -6673,27 +6688,29 @@ GCRuntime::budgetIncrementalGC(bool noni
     if (isTooMuchMalloc()) {
         budget.makeUnlimited();
         stats().nonincremental(AbortReason::MallocBytesTrigger);
     }
 
     bool reset = false;
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         if (zone->usage.gcBytes() >= zone->threshold.gcTriggerBytes()) {
+            MOZ_ASSERT(zone->isGCScheduled());
             budget.makeUnlimited();
             stats().nonincremental(AbortReason::GCBytesTrigger);
         }
 
+        if (zone->isTooMuchMalloc()) {
+            MOZ_ASSERT(zone->isGCScheduled());
+            budget.makeUnlimited();
+            stats().nonincremental(AbortReason::MallocBytesTrigger);
+        }
+
         if (isIncrementalGCInProgress() && zone->isGCScheduled() != zone->wasGCStarted())
             reset = true;
-
-        if (zone->isTooMuchMalloc()) {
-            budget.makeUnlimited();
-            stats().nonincremental(AbortReason::MallocBytesTrigger);
-        }
     }
 
     if (reset)
         return resetIncrementalGC(AbortReason::ZoneChange, lock);
 
     return IncrementalResult::Ok;
 }
 
@@ -6704,26 +6721,32 @@ class AutoScheduleZonesForGC
     JSRuntime* rt_;
 
   public:
     explicit AutoScheduleZonesForGC(JSRuntime* rt) : rt_(rt) {
         for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
             if (rt->gc.gcMode() == JSGC_MODE_GLOBAL)
                 zone->scheduleGC();
 
-            /* This is a heuristic to avoid resets. */
+            // This is a heuristic to avoid resets.
             if (rt->gc.isIncrementalGCInProgress() && zone->needsIncrementalBarrier())
                 zone->scheduleGC();
 
-            /* This is a heuristic to reduce the total number of collections. */
+            // This is a heuristic to reduce the total number of collections.
             if (zone->usage.gcBytes() >=
                 zone->threshold.allocTrigger(rt->gc.schedulingState.inHighFrequencyGCMode()))
             {
                 zone->scheduleGC();
             }
+
+            // This ensures we collect zones that have reached the malloc limit.
+            // TODO: Start collecting these zones earlier like we do for the GC
+            // bytes trigger above (bug 1384049).
+            if (zone->isTooMuchMalloc())
+                zone->scheduleGC();
         }
     }
 
     ~AutoScheduleZonesForGC() {
         for (ZonesIter zone(rt_, WithAtoms); !zone.done(); zone.next())
             zone->unscheduleGC();
     }
 };
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -1204,17 +1204,17 @@ ToDisassemblySource(JSContext* cx, Handl
         return true;
     }
 
     if (v.isObject()) {
         JSObject& obj = v.toObject();
 
         if (obj.is<JSFunction>()) {
             RootedFunction fun(cx, &obj.as<JSFunction>());
-            JSString* str = JS_DecompileFunction(cx, fun, JS_DONT_PRETTY_PRINT);
+            JSString* str = JS_DecompileFunction(cx, fun);
             if (!str)
                 return false;
             return bytes->encodeLatin1(cx, str);
         }
 
         if (obj.is<RegExpObject>()) {
             JSString* source = obj.as<RegExpObject>().toString(cx);
             if (!source)
@@ -2724,17 +2724,17 @@ GetPCCountJSON(JSContext* cx, const Scri
 {
     RootedScript script(cx, sac.script);
 
     if (!buf.append('{'))
         return false;
     if (!AppendJSONProperty(buf, "text", NO_COMMA))
         return false;
 
-    JSString* str = JS_DecompileScript(cx, script, nullptr, 0);
+    JSString* str = JS_DecompileScript(cx, script);
     if (!str || !(str = StringToSource(cx, str)))
         return false;
 
     if (!buf.append(str))
         return false;
 
     if (!AppendJSONProperty(buf, "line"))
         return false;
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -1829,19 +1829,17 @@ bool
 ScriptSource::appendSubstring(JSContext* cx, StringBuffer& buf, size_t start, size_t stop)
 {
     MOZ_ASSERT(start <= stop);
     size_t len = stop - start;
     UncompressedSourceCache::AutoHoldEntry holder;
     PinnedChars chars(cx, this, holder, start, len);
     if (!chars.get())
         return false;
-    // Sources can be large and we don't want to check "is this char Latin1"
-    // for each source code character, so inflate the buffer here.
-    if (len > 100 && !buf.ensureTwoByteChars())
+    if (len > SourceDeflateLimit && !buf.ensureTwoByteChars())
         return false;
     return buf.append(chars.get(), len);
 }
 
 JSFlatString*
 ScriptSource::functionBodyString(JSContext* cx)
 {
     MOZ_ASSERT(isFunctionBody());
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -497,16 +497,20 @@ class ScriptSource
     // Warning: this is *not* GC-safe! Any chars to be handed out should use
     // PinnedChars. See comment below.
     const char16_t* chars(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& asp,
                           size_t begin, size_t len);
 
     void movePendingCompressedSource();
 
   public:
+    // When creating a JSString* from TwoByte source characters, we don't try to
+    // to deflate to Latin1 for longer strings, because this can be slow.
+    static const size_t SourceDeflateLimit = 100;
+
     explicit ScriptSource()
       : refs(0),
         data(SourceType(Missing())),
         pinnedCharsStack_(nullptr),
         filename_(nullptr),
         displayURL_(nullptr),
         sourceMapURL_(nullptr),
         mutedErrors_(false),
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -110,17 +110,17 @@ class JS_FRIEND_API(Wrapper) : public Ba
                             const CallArgs& args) const override;
     virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
                              bool* bp) const override;
     virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const override;
     virtual bool isArray(JSContext* cx, HandleObject proxy,
                          JS::IsArrayAnswer* answer) const override;
     virtual const char* className(JSContext* cx, HandleObject proxy) const override;
     virtual JSString* fun_toString(JSContext* cx, HandleObject proxy,
-                                   unsigned indent) const override;
+                                   bool isToSource) const override;
     virtual RegExpShared* regexp_toShared(JSContext* cx, HandleObject proxy) const override;
     virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy,
                                   MutableHandleValue vp) const override;
     virtual bool isCallable(JSObject* obj) const override;
     virtual bool isConstructor(JSObject* obj) const override;
     virtual JSObject* weakmapKeyDelegate(JSObject* proxy) const override;
 
   public:
@@ -204,17 +204,17 @@ class JS_FRIEND_API(CrossCompartmentWrap
     virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject wrapper,
                                               AutoIdVector& props) const override;
     virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
                             const CallArgs& args) const override;
     virtual bool hasInstance(JSContext* cx, HandleObject wrapper, MutableHandleValue v,
                              bool* bp) const override;
     virtual const char* className(JSContext* cx, HandleObject proxy) const override;
     virtual JSString* fun_toString(JSContext* cx, HandleObject wrapper,
-                                   unsigned indent) const override;
+                                   bool isToSource) const override;
     virtual RegExpShared* regexp_toShared(JSContext* cx, HandleObject proxy) const override;
     virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const override;
 
     // Allocate CrossCompartmentWrappers in the nursery.
     virtual bool canNurseryAllocate() const override { return true; }
 
     static const CrossCompartmentWrapper singleton;
     static const CrossCompartmentWrapper singletonWithPrototype;
@@ -263,17 +263,18 @@ class JS_FRIEND_API(OpaqueCrossCompartme
     virtual bool hasOwn(JSContext* cx, HandleObject wrapper, HandleId id,
                         bool* bp) const override;
     virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject wrapper,
                                               AutoIdVector& props) const override;
     virtual bool getBuiltinClass(JSContext* cx, HandleObject wrapper, ESClass* cls) const override;
     virtual bool isArray(JSContext* cx, HandleObject obj,
                          JS::IsArrayAnswer* answer) const override;
     virtual const char* className(JSContext* cx, HandleObject wrapper) const override;
-    virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const override;
+    virtual JSString* fun_toString(JSContext* cx, HandleObject proxy,
+                                   bool isToSource) const override;
 
     static const OpaqueCrossCompartmentWrapper singleton;
 };
 
 /*
  * Base class for security wrappers. A security wrapper is potentially hiding
  * all or part of some wrapped object thus SecurityWrapper defaults to denying
  * access to the wrappee. This is the opposite of Wrapper which tries to be
--- a/js/src/proxy/BaseProxyHandler.cpp
+++ b/js/src/proxy/BaseProxyHandler.cpp
@@ -311,17 +311,17 @@ BaseProxyHandler::construct(JSContext* c
 
 const char*
 BaseProxyHandler::className(JSContext* cx, HandleObject proxy) const
 {
     return proxy->isCallable() ? "Function" : "Object";
 }
 
 JSString*
-BaseProxyHandler::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const
+BaseProxyHandler::fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const
 {
     if (proxy->isCallable())
         return JS_NewStringCopyZ(cx, "function () {\n    [native code]\n}");
     RootedValue v(cx, ObjectValue(*proxy));
     ReportIsNotFunction(cx, v);
     return nullptr;
 }
 
--- a/js/src/proxy/CrossCompartmentWrapper.cpp
+++ b/js/src/proxy/CrossCompartmentWrapper.cpp
@@ -444,22 +444,22 @@ CrossCompartmentWrapper::hasInstance(JSC
 const char*
 CrossCompartmentWrapper::className(JSContext* cx, HandleObject wrapper) const
 {
     AutoCompartment call(cx, wrappedObject(wrapper));
     return Wrapper::className(cx, wrapper);
 }
 
 JSString*
-CrossCompartmentWrapper::fun_toString(JSContext* cx, HandleObject wrapper, unsigned indent) const
+CrossCompartmentWrapper::fun_toString(JSContext* cx, HandleObject wrapper, bool isToSource) const
 {
     RootedString str(cx);
     {
         AutoCompartment call(cx, wrappedObject(wrapper));
-        str = Wrapper::fun_toString(cx, wrapper, indent);
+        str = Wrapper::fun_toString(cx, wrapper, isToSource);
         if (!str)
             return nullptr;
     }
     if (!cx->compartment()->wrap(cx, &str))
         return nullptr;
     return str;
 }
 
--- a/js/src/proxy/DeadObjectProxy.cpp
+++ b/js/src/proxy/DeadObjectProxy.cpp
@@ -148,17 +148,17 @@ template <DeadProxyIsCallableIsConstruct
 const char*
 DeadObjectProxy<CC>::className(JSContext* cx, HandleObject wrapper) const
 {
     return "DeadObject";
 }
 
 template <DeadProxyIsCallableIsConstructorOption CC>
 JSString*
-DeadObjectProxy<CC>::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const
+DeadObjectProxy<CC>::fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const
 {
     ReportDead(cx);
     return nullptr;
 }
 
 template <DeadProxyIsCallableIsConstructorOption CC>
 RegExpShared*
 DeadObjectProxy<CC>::regexp_toShared(JSContext* cx, HandleObject proxy) const
--- a/js/src/proxy/DeadObjectProxy.h
+++ b/js/src/proxy/DeadObjectProxy.h
@@ -54,17 +54,18 @@ class DeadObjectProxy : public BaseProxy
     // BaseProxyHandler::enumerate will throw by calling ownKeys.
     virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
                             const CallArgs& args) const override;
     virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
                              bool* bp) const override;
     virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const override;
     virtual bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const override;
     virtual const char* className(JSContext* cx, HandleObject proxy) const override;
-    virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const override;
+    virtual JSString* fun_toString(JSContext* cx, HandleObject proxy,
+                                   bool isToSource) const override;
     virtual RegExpShared* regexp_toShared(JSContext* cx, HandleObject proxy) const override;
 
     virtual bool isCallable(JSObject* obj) const override {
         return CC == DeadProxyIsCallableIsConstructor || CC == DeadProxyIsCallableNotConstructor;
     }
     virtual bool isConstructor(JSObject* obj) const override {
         return CC == DeadProxyIsCallableIsConstructor || CC == DeadProxyNotCallableIsConstructor;
     }
--- a/js/src/proxy/OpaqueCrossCompartmentWrapper.cpp
+++ b/js/src/proxy/OpaqueCrossCompartmentWrapper.cpp
@@ -178,16 +178,16 @@ const char*
 OpaqueCrossCompartmentWrapper::className(JSContext* cx,
                                          HandleObject proxy) const
 {
     return "Opaque";
 }
 
 JSString*
 OpaqueCrossCompartmentWrapper::fun_toString(JSContext* cx, HandleObject proxy,
-                                            unsigned indent) const
+                                            bool isToSource) const
 {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
                               js_Function_str, js_toString_str, "object");
     return nullptr;
 }
 
 const OpaqueCrossCompartmentWrapper OpaqueCrossCompartmentWrapper::singleton;
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -557,27 +557,27 @@ Proxy::className(JSContext* cx, HandleOb
     // Do the safe thing if the policy rejects.
     if (!policy.allowed()) {
         return handler->BaseProxyHandler::className(cx, proxy);
     }
     return handler->className(cx, proxy);
 }
 
 JSString*
-Proxy::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent)
+Proxy::fun_toString(JSContext* cx, HandleObject proxy, bool isToSource)
 {
     if (!CheckRecursionLimit(cx))
         return nullptr;
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE,
                            BaseProxyHandler::GET, /* mayThrow = */ false);
     // Do the safe thing if the policy rejects.
     if (!policy.allowed())
-        return handler->BaseProxyHandler::fun_toString(cx, proxy, indent);
-    return handler->fun_toString(cx, proxy, indent);
+        return handler->BaseProxyHandler::fun_toString(cx, proxy, isToSource);
+    return handler->fun_toString(cx, proxy, isToSource);
 }
 
 RegExpShared*
 Proxy::regexp_toShared(JSContext* cx, HandleObject proxy)
 {
     if (!CheckRecursionLimit(cx))
         return nullptr;
     return proxy->as<ProxyObject>().handler()->regexp_toShared(cx, proxy);
--- a/js/src/proxy/Proxy.h
+++ b/js/src/proxy/Proxy.h
@@ -54,17 +54,17 @@ class Proxy
     static bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
                                              AutoIdVector& props);
     static bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
                            const CallArgs& args);
     static bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp);
     static bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls);
     static bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer);
     static const char* className(JSContext* cx, HandleObject proxy);
-    static JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent);
+    static JSString* fun_toString(JSContext* cx, HandleObject proxy, bool isToSource);
     static RegExpShared* regexp_toShared(JSContext* cx, HandleObject proxy);
     static bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp);
 
     static bool watch(JSContext* cx, HandleObject proxy, HandleId id, HandleObject callable);
     static bool unwatch(JSContext* cx, HandleObject proxy, HandleId id);
 
     static bool getElements(JSContext* cx, HandleObject obj, uint32_t begin, uint32_t end,
                             ElementAdder* adder);
--- a/js/src/proxy/ScriptedProxyHandler.cpp
+++ b/js/src/proxy/ScriptedProxyHandler.cpp
@@ -1255,17 +1255,17 @@ ScriptedProxyHandler::isArray(JSContext*
 const char*
 ScriptedProxyHandler::className(JSContext* cx, HandleObject proxy) const
 {
     // Right now the caller is not prepared to handle failures.
     return BaseProxyHandler::className(cx, proxy);
 }
 
 JSString*
-ScriptedProxyHandler::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const
+ScriptedProxyHandler::fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const
 {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
                               js_Function_str, js_toString_str, "object");
     return nullptr;
 }
 
 RegExpShared*
 ScriptedProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy) const
--- a/js/src/proxy/ScriptedProxyHandler.h
+++ b/js/src/proxy/ScriptedProxyHandler.h
@@ -63,17 +63,17 @@ class ScriptedProxyHandler : public Base
                             const CallArgs& args) const override;
     virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
                              bool* bp) const override;
     virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const override;
     virtual bool isArray(JSContext* cx, HandleObject proxy,
                          JS::IsArrayAnswer* answer) const override;
     virtual const char* className(JSContext* cx, HandleObject proxy) const override;
     virtual JSString* fun_toString(JSContext* cx, HandleObject proxy,
-                                   unsigned indent) const override;
+                                   bool isToSource) const override;
     virtual RegExpShared* regexp_toShared(JSContext* cx, HandleObject proxy) const override;
     virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy,
                                   MutableHandleValue vp) const override;
 
     virtual bool isCallable(JSObject* obj) const override;
     virtual bool isConstructor(JSObject* obj) const override;
 
     virtual bool isScripted() const override { return true; }
--- a/js/src/proxy/Wrapper.cpp
+++ b/js/src/proxy/Wrapper.cpp
@@ -259,21 +259,21 @@ const char*
 Wrapper::className(JSContext* cx, HandleObject proxy) const
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return GetObjectClassName(cx, target);
 }
 
 JSString*
-Wrapper::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const
+Wrapper::fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
-    return fun_toStringHelper(cx, target, indent);
+    return fun_toStringHelper(cx, target, isToSource);
 }
 
 RegExpShared*
 Wrapper::regexp_toShared(JSContext* cx, HandleObject proxy) const
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return RegExpToShared(cx, target);
 }
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -4779,17 +4779,17 @@ static bool
 DecompileFunction(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() < 1 || !args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
         args.rval().setUndefined();
         return true;
     }
     RootedFunction fun(cx, &args[0].toObject().as<JSFunction>());
-    JSString* result = JS_DecompileFunction(cx, fun, 0);
+    JSString* result = JS_DecompileFunction(cx, fun);
     if (!result)
         return false;
     args.rval().setString(result);
     return true;
 }
 
 static bool
 DecompileThisScript(JSContext* cx, unsigned argc, Value* vp)
@@ -4801,17 +4801,17 @@ DecompileThisScript(JSContext* cx, unsig
         args.rval().setString(cx->runtime()->emptyString);
         return true;
     }
 
     {
         JSAutoCompartment ac(cx, iter.script());
 
         RootedScript script(cx, iter.script());
-        JSString* result = JS_DecompileScript(cx, script, "test", 0);
+        JSString* result = JS_DecompileScript(cx, script);
         if (!result)
             return false;
 
         args.rval().setString(result);
     }
 
     return JS_WrapValue(cx, args.rval());
 }
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -882,18 +882,19 @@ JSRuntime::setUsedByHelperThread(Zone* z
 }
 
 void
 JSRuntime::clearUsedByHelperThread(Zone* zone)
 {
     MOZ_ASSERT(zone->group()->usedByHelperThread);
     zone->group()->usedByHelperThread = false;
     numHelperThreadZones--;
-    if (gc.fullGCForAtomsRequested() && !TlsContext.get())
-        gc.triggerFullGCForAtoms();
+    JSContext* cx = TlsContext.get();
+    if (gc.fullGCForAtomsRequested() && cx->canCollectAtoms())
+        gc.triggerFullGCForAtoms(cx);
 }
 
 bool
 js::CurrentThreadCanAccessRuntime(const JSRuntime* rt)
 {
     return rt->activeContext() == TlsContext.get();
 }
 
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -8891,28 +8891,28 @@ js::IsAsmJSModuleLoadedFromCache(JSConte
     args.rval().set(BooleanValue(loadedFromCache));
     return true;
 }
 
 /*****************************************************************************/
 // asm.js toString/toSource support
 
 JSString*
-js::AsmJSModuleToString(JSContext* cx, HandleFunction fun, bool addParenToLambda)
+js::AsmJSModuleToString(JSContext* cx, HandleFunction fun, bool isToSource)
 {
     MOZ_ASSERT(IsAsmJSModule(fun));
 
     const AsmJSMetadata& metadata = AsmJSModuleFunctionToModule(fun).metadata().asAsmJS();
     uint32_t begin = metadata.toStringStart;
     uint32_t end = metadata.srcEndAfterCurly();
     ScriptSource* source = metadata.scriptSource.get();
 
     StringBuffer out(cx);
 
-    if (addParenToLambda && fun->isLambda() && !out.append("("))
+    if (isToSource && fun->isLambda() && !out.append("("))
         return nullptr;
 
     bool haveSource = source->hasSourceData();
     if (!haveSource && !JSScript::loadSource(cx, source, &haveSource))
         return nullptr;
 
     if (!haveSource) {
         if (!out.append("function "))
@@ -8925,17 +8925,17 @@ js::AsmJSModuleToString(JSContext* cx, H
         Rooted<JSFlatString*> src(cx, source->substring(cx, begin, end));
         if (!src)
             return nullptr;
 
         if (!out.append(src))
             return nullptr;
     }
 
-    if (addParenToLambda && fun->isLambda() && !out.append(")"))
+    if (isToSource && fun->isLambda() && !out.append(")"))
         return nullptr;
 
     return out.finishString();
 }
 
 JSString*
 js::AsmJSFunctionToString(JSContext* cx, HandleFunction fun)
 {
--- a/js/src/wasm/AsmJS.h
+++ b/js/src/wasm/AsmJS.h
@@ -75,17 +75,17 @@ extern bool
 IsAsmJSFunction(JSContext* cx, unsigned argc, JS::Value* vp);
 
 // asm.js toString/toSource support:
 
 extern JSString*
 AsmJSFunctionToString(JSContext* cx, HandleFunction fun);
 
 extern JSString*
-AsmJSModuleToString(JSContext* cx, HandleFunction fun, bool addParenToLambda);
+AsmJSModuleToString(JSContext* cx, HandleFunction fun, bool isToSource);
 
 // asm.js heap:
 
 extern bool
 IsValidAsmJSHeapLength(uint32_t length);
 
 } // namespace js
 
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -27,16 +27,17 @@
 #include "nsStyleStructInlines.h"
 #include "nsSubDocumentFrame.h"
 #include "nsView.h"
 #include "RenderFrameParent.h"
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/webrender/WebRenderAPI.h"
 #include "ClientLayerManager.h"
 #include "FrameLayerBuilder.h"
 
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 
 namespace mozilla {
@@ -44,29 +45,29 @@ namespace layout {
 
 typedef FrameMetrics::ViewID ViewID;
 
 /**
  * Gets the layer-pixel offset of aContainerFrame's content rect top-left
  * from the nearest display item reference frame (which we assume will be inducing
  * a ContainerLayer).
  */
-static nsIntPoint
+static LayoutDeviceIntPoint
 GetContentRectLayerOffset(nsIFrame* aContainerFrame, nsDisplayListBuilder* aBuilder)
 {
   nscoord auPerDevPixel = aContainerFrame->PresContext()->AppUnitsPerDevPixel();
 
   // Offset to the content rect in case we have borders or padding
   // Note that aContainerFrame could be a reference frame itself, so
   // we need to be careful here to ensure that we call ToReferenceFrame
   // on aContainerFrame and not its parent.
   nsPoint frameOffset = aBuilder->ToReferenceFrame(aContainerFrame) +
     aContainerFrame->GetContentRectRelativeToSelf().TopLeft();
 
-  return frameOffset.ToNearestPixels(auPerDevPixel);
+  return LayoutDeviceIntPoint::FromAppUnitsToNearest(frameOffset, auPerDevPixel);
 }
 
 // Return true iff |aManager| is a "temporary layer manager".  They're
 // used for small software rendering tasks, like drawWindow.  That's
 // currently implemented by a BasicLayerManager without a backing
 // widget, and hence in non-retained mode.
 inline static bool
 IsTempLayerManager(LayerManager* aManager)
@@ -155,17 +156,16 @@ RenderFrameParent::Destroy()
   mFrameLoaderDestroyed = true;
   mLayerManager = nullptr;
 }
 
 already_AddRefed<Layer>
 RenderFrameParent::BuildLayer(nsDisplayListBuilder* aBuilder,
                               nsIFrame* aFrame,
                               LayerManager* aManager,
-                              const nsIntRect& aVisibleRect,
                               nsDisplayItem* aItem,
                               const ContainerLayerParameters& aContainerParameters)
 {
   MOZ_ASSERT(aFrame,
              "makes no sense to have a shadow tree without a frame");
   MOZ_ASSERT(!mContainer ||
              IsTempLayerManager(aManager) ||
              mContainer->Manager() == aManager,
@@ -195,17 +195,17 @@ RenderFrameParent::BuildLayer(nsDisplayL
     layer = aManager->CreateRefLayer();
   }
   if (!layer) {
     // Probably a temporary layer manager that doesn't know how to
     // use ref layers.
     return nullptr;
   }
   static_cast<RefLayer*>(layer.get())->SetReferentId(mLayersId);
-  nsIntPoint offset = GetContentRectLayerOffset(aFrame, aBuilder);
+  LayoutDeviceIntPoint offset = GetContentRectLayerOffset(aFrame, aBuilder);
   // We can only have an offset if we're a child of an inactive
   // container, but our display item is LAYER_ACTIVE_FORCE which
   // forces all layers above to be active.
   MOZ_ASSERT(aContainerParameters.mOffset == nsIntPoint());
   gfx::Matrix4x4 m = gfx::Matrix4x4::Translation(offset.x, offset.y, 0.0);
   // Remote content can't be repainted by us, so we multiply down
   // the resolution that our container expects onto our container.
   m.PreScale(aContainerParameters.mXScale, aContainerParameters.mYScale, 1.0);
@@ -369,17 +369,33 @@ nsDisplayRemote::nsDisplayRemote(nsDispl
   }
 }
 
 already_AddRefed<Layer>
 nsDisplayRemote::BuildLayer(nsDisplayListBuilder* aBuilder,
                             LayerManager* aManager,
                             const ContainerLayerParameters& aContainerParameters)
 {
-  int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
-  nsIntRect visibleRect = GetVisibleRect().ToNearestPixels(appUnitsPerDevPixel);
-  visibleRect += aContainerParameters.mOffset;
-  RefPtr<Layer> layer = mRemoteFrame->BuildLayer(aBuilder, mFrame, aManager, visibleRect, this, aContainerParameters);
+  RefPtr<Layer> layer = mRemoteFrame->BuildLayer(aBuilder, mFrame, aManager, this, aContainerParameters);
   if (layer && layer->AsContainerLayer()) {
     layer->AsContainerLayer()->SetEventRegionsOverride(mEventRegionsOverride);
   }
   return layer.forget();
 }
+
+bool
+nsDisplayRemote::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
+                                         const StackingContextHelper& aSc,
+                                         nsTArray<WebRenderParentCommand>& aParentCommands,
+                                         mozilla::layers::WebRenderLayerManager* aManager,
+                                         nsDisplayListBuilder* aDisplayListBuilder)
+{
+  MOZ_ASSERT(aManager->IsLayersFreeTransaction());
+
+  mozilla::LayoutDeviceRect visible = mozilla::LayoutDeviceRect::FromAppUnits(
+      GetVisibleRect(), mFrame->PresContext()->AppUnitsPerDevPixel());
+  visible += mozilla::layout::GetContentRectLayerOffset(mFrame, aDisplayListBuilder);
+
+  aBuilder.PushIFrame(aSc.ToRelativeLayoutRect(visible),
+      mozilla::wr::AsPipelineId(mRemoteFrame->GetLayersId()));
+
+  return true;
+}
--- a/layout/ipc/RenderFrameParent.h
+++ b/layout/ipc/RenderFrameParent.h
@@ -67,17 +67,16 @@ public:
   void BuildDisplayList(nsDisplayListBuilder* aBuilder,
                         nsSubDocumentFrame* aFrame,
                         const nsRect& aDirtyRect,
                         const nsDisplayListSet& aLists);
 
   already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                      nsIFrame* aFrame,
                                      LayerManager* aManager,
-                                     const nsIntRect& aVisibleRect,
                                      nsDisplayItem* aItem,
                                      const ContainerLayerParameters& aContainerParameters);
 
   void OwnerContentChanged(nsIContent* aContent);
 
   bool HitTest(const nsRect& aRect);
 
   void GetTextureFactoryIdentifier(TextureFactoryIdentifier* aTextureFactoryIdentifier);
@@ -158,16 +157,22 @@ public:
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override
   { return mozilla::LAYER_ACTIVE_FORCE; }
 
   virtual already_AddRefed<Layer>
   BuildLayer(nsDisplayListBuilder* aBuilder, LayerManager* aManager,
              const ContainerLayerParameters& aContainerParameters) override;
 
+  virtual bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
+                                       const StackingContextHelper& aSc,
+                                       nsTArray<WebRenderParentCommand>& aParentCommands,
+                                       mozilla::layers::WebRenderLayerManager* aManager,
+                                       nsDisplayListBuilder* aDisplayListBuilder) override;
+
   NS_DISPLAY_DECL_NAME("Remote", TYPE_REMOTE)
 
 private:
   RenderFrameParent* mRemoteFrame;
   mozilla::layers::EventRegionsOverride mEventRegionsOverride;
 };
 
 
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -2086,20 +2086,17 @@ already_AddRefed<LayerManager> nsDisplay
   }
 
   nsIFrame* frame = aBuilder->RootReferenceFrame();
   nsPresContext* presContext = frame->PresContext();
   nsIPresShell* presShell = presContext->PresShell();
   nsIDocument* document = presShell->GetDocument();
 
   if (gfxPrefs::WebRenderLayersFree() &&
-      layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR &&
-      // We don't yet support many display items used in chrome, so
-      // layers-free mode is only for content.
-      !presContext->IsChrome()) {
+      layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
     if (doBeginTransaction) {
       if (aCtx) {
         if (!layerManager->BeginTransactionWithTarget(aCtx)) {
           return nullptr;
         }
       } else {
         if (!layerManager->BeginTransaction()) {
           return nullptr;
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1979,22 +1979,19 @@ fuzzy(8,1900) == 1291528.html 1291528-re
 # should be same.  |fuzzy()| here allows the difference in border, but not
 # background color.
 fuzzy(255,1000) skip-if(!cocoaWidget) == 1294102-1.html 1294102-1-ref.html
 random-if(Android) fuzzy-if(skiaContent,15,50) == 1295466-1.xhtml 1295466-1-ref.xhtml #bug 982547
 fuzzy-if(Android,27,874) fuzzy-if(gtkWidget,14,29) == 1313772.xhtml 1313772-ref.xhtml # Bug 1128229
 fuzzy(2,320000) == 1315113-1.html 1315113-1-ref.html
 fuzzy(2,20000) == 1315113-2.html 1315113-2-ref.html
 == 1315632-1.html 1315632-1-ref.html
-fuzzy(2,40000) fuzzy-if(webrender,26,691) == 1316719-1a.html 1316719-1-ref.html
-fuzzy(2,40000) fuzzy-if(webrender,26,691) == 1316719-1b.html 1316719-1-ref.html
-fuzzy(2,40000) == 1316719-1c.html 1316719-1-ref.html
-pref(layers.advanced.background-color,1) skip-if(!webrender) fuzzy-if(webrender,27,700) == 1316719-1a.html 1316719-1-ref.html
-pref(layers.advanced.background-color,1) skip-if(!webrender) fuzzy-if(webrender,27,700) == 1316719-1b.html 1316719-1-ref.html
-pref(layers.advanced.background-color,1) skip-if(!webrender) fuzzy-if(webrender,27,700) == 1316719-1c.html 1316719-1-ref.html
+fuzzy(2,40000) fuzzy-if(webrender,1-2,349-349) == 1316719-1a.html 1316719-1-ref.html
+fuzzy(2,40000) fuzzy-if(webrender,1-2,349-349) == 1316719-1b.html 1316719-1-ref.html
+fuzzy(2,40000) fuzzy-if(webrender,1-1,323-323) == 1316719-1c.html 1316719-1-ref.html
 skip-if(Android) != 1318769-1.html 1318769-1-ref.html
 fails-if(styloVsGecko) == 1322512-1.html 1322512-1-ref.html
 == 1330051.svg 1330051-ref.svg
 == 1348481-1.html 1348481-ref.html
 == 1348481-2.html 1348481-ref.html
 == 1352464-1.html 1352464-1-ref.html
 == 1358375-1.html 1358375-ref.html
 == 1358375-2.html 1358375-ref.html
--- a/layout/reftests/font-inflation/reftest.list
+++ b/layout/reftests/font-inflation/reftest.list
@@ -32,17 +32,18 @@ test-pref(font.size.inflation.emPerLine,
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) == input-text-2-noheight.html input-text-2-noheight-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) == input-text-3-height.html input-text-3-height-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) == input-text-3-noheight.html input-text-3-noheight-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) == textarea-1.html textarea-1-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) == textarea-2.html textarea-2-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) == textarea-3.html textarea-3-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) == css-transform-1.html css-transform-1-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) == css-transform-2.html css-transform-2-ref.html
-fuzzy-if(asyncPan&&!layersGPUAccelerated,102,1764) test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) == container-with-clamping.html container-with-clamping-ref.html
+# skipped - bug 1380830
+fuzzy-if(asyncPan&&!layersGPUAccelerated,102,1764) skip test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) == container-with-clamping.html container-with-clamping-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) skip-if(styloVsGecko) load video-1.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) HTTP(..) == intrinsic-min-1.html intrinsic-min-1-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) HTTP(..) == intrinsic-max-1.html intrinsic-max-1-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) HTTP(..) == intrinsic-fit-1a.html intrinsic-fit-1a-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) HTTP(..) == intrinsic-fit-1b.html intrinsic-fit-1b-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) HTTP(..) == intrinsic-fit-1c.html intrinsic-fit-1c-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) HTTP(..) == intrinsic-fit-2a.html intrinsic-fit-1a-ref.html
 test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) HTTP(..) == intrinsic-fit-2b.html intrinsic-fit-1b-ref.html
--- a/mfbt/AllocPolicy.h
+++ b/mfbt/AllocPolicy.h
@@ -8,16 +8,17 @@
  * An allocation policy concept, usable for structures and algorithms to
  * control how memory is allocated and how failures are handled.
  */
 
 #ifndef mozilla_AllocPolicy_h
 #define mozilla_AllocPolicy_h
 
 #include "mozilla/Attributes.h"
+#include "mozilla/Assertions.h"
 #include "mozilla/TemplateLib.h"
 
 #include <stddef.h>
 #include <stdlib.h>
 
 namespace mozilla {
 
 /*
@@ -123,11 +124,72 @@ public:
   }
 
   MOZ_MUST_USE bool checkSimulatedOOM() const
   {
     return true;
   }
 };
 
+/*
+ * A policy which always fails to allocate memory, returning nullptr. Methods
+ * which expect an existing allocation assert.
+ *
+ * This type should be used in situations where you want to use a MFBT type with
+ * inline storage, and don't want to allow it to allocate on the heap.
+ */
+class NeverAllocPolicy
+{
+public:
+  template <typename T>
+  T* maybe_pod_malloc(size_t aNumElems)
+  {
+    return nullptr;
+  }
+
+  template <typename T>
+  T* maybe_pod_calloc(size_t aNumElems)
+  {
+    return nullptr;
+  }
+
+  template <typename T>
+  T* maybe_pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize)
+  {
+    MOZ_CRASH("NeverAllocPolicy::maybe_pod_realloc");
+  }
+
+  template <typename T>
+  T* pod_malloc(size_t aNumElems)
+  {
+    return nullptr;
+  }
+
+  template <typename T>
+  T* pod_calloc(size_t aNumElems)
+  {
+    return nullptr;
+  }
+
+  template <typename T>
+  T* pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize)
+  {
+    MOZ_CRASH("NeverAllocPolicy::pod_realloc");
+  }
+
+  void free_(void* aPtr)
+  {
+    MOZ_CRASH("NeverAllocPolicy::free_");
+  }
+
+  void reportAllocOverflow() const
+  {
+  }
+
+  MOZ_MUST_USE bool checkSimulatedOOM() const
+  {
+    return true;
+  }
+};
+
 } // namespace mozilla
 
 #endif /* mozilla_AllocPolicy_h */
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -1110,16 +1110,20 @@ public class BrowserApp extends GeckoApp
 
     @Override
     public void onAttachedToWindow() {
         final SafeIntent intent = new SafeIntent(getIntent());
 
         // We can't show the first run experience until Gecko has finished initialization (bug 1077583).
         checkFirstrun(this, intent);
 
+        if (Versions.preJB) {
+           conditionallyNotifyEOL();
+        }
+
         if (!IntentUtils.getIsInAutomationFromEnvironment(intent)) {
             DawnHelper.conditionallyNotifyDawn(this);
         }
     }
 
     @Override
     protected void processTabQueue() {
         if (TabQueueHelper.TAB_QUEUE_ENABLED && mInitialized) {
@@ -3447,16 +3451,20 @@ public class BrowserApp extends GeckoApp
             mDynamicToolbar.setPinned(true, PinReason.FULL_SCREEN);
         } else {
             mDynamicToolbar.setPinned(false, PinReason.FULL_SCREEN);
             mDynamicToolbar.setVisible(true, VisibilityTransition.IMMEDIATE);
         }
     }
 
     @Override
+    public void onContextMenu(GeckoView view, int screenX, int screenY,
+                              String uri, String elementSrc) {}
+
+    @Override
     public boolean onPrepareOptionsMenu(Menu aMenu) {
         if (aMenu == null)
             return false;
 
         // Hide the tab history panel when hardware menu button is pressed.
         TabHistoryFragment frag = (TabHistoryFragment) getSupportFragmentManager().findFragmentByTag(TAB_HISTORY_FRAGMENT_TAG);
         if (frag != null) {
             frag.dismiss();
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -562,9 +562,13 @@ public class CustomTabsActivity extends 
     @Override
     public void onTitleChange(GeckoView view, String title) {
         mCurrentTitle = title;
         updateActionBar();
     }
 
     @Override
     public void onFullScreen(GeckoView view, boolean fullScreen) {}
+
+    @Override
+    public void onContextMenu(GeckoView view, int screenX, int screenY,
+                              String uri, String elementSrc) {}
 }
--- a/mobile/android/chrome/geckoview/GeckoViewContent.js
+++ b/mobile/android/chrome/geckoview/GeckoViewContent.js
@@ -17,31 +17,33 @@ class GeckoViewContent extends GeckoView
   register() {
     debug("register");
 
     addEventListener("DOMTitleChanged", this, false);
     addEventListener("MozDOMFullscreen:Entered", this, false);
     addEventListener("MozDOMFullscreen:Exit", this, false);
     addEventListener("MozDOMFullscreen:Exited", this, false);
     addEventListener("MozDOMFullscreen:Request", this, false);
+    addEventListener("contextmenu", this, { capture: true, passive: false });
 
     this.messageManager.addMessageListener("GeckoView:DOMFullscreenEntered",
                                            this);
     this.messageManager.addMessageListener("GeckoView:DOMFullscreenExited",
                                            this);
   }
 
   unregister() {
     debug("unregister");
 
     removeEventListener("DOMTitleChanged", this);
     removeEventListener("MozDOMFullscreen:Entered", this);
     removeEventListener("MozDOMFullscreen:Exit", this);
     removeEventListener("MozDOMFullscreen:Exited", this);
     removeEventListener("MozDOMFullscreen:Request", this);
+    removeEventListener("contextmenu", this);
 
     this.messageManager.removeMessageListener("GeckoView:DOMFullscreenEntered",
                                               this);
     this.messageManager.removeMessageListener("GeckoView:DOMFullscreenExited",
                                               this);
   }
 
   receiveMessage(aMsg) {
@@ -61,23 +63,45 @@ class GeckoViewContent extends GeckoView
                  .getInterface(Ci.nsIDOMWindowUtils)
                  .exitFullscreen();
         }
         break;
     }
   }
 
   handleEvent(aEvent) {
-    if (aEvent.originalTarget.defaultView != content) {
-      return;
-    }
-
     debug("handleEvent " + aEvent.type);
 
     switch (aEvent.type) {
+      case "contextmenu":
+        function nearestParentHref(node) {
+          while (node && !node.href) {
+            node = node.parentNode;
+          }
+          return node && node.href;
+        }
+
+        let node = aEvent.target;
+        let hrefNode = nearestParentHref(node);
+        let isImageNode = node instanceof Ci.nsIDOMHTMLImageElement;
+        let isMediaNode = node instanceof Ci.nsIDOMHTMLMediaElement;
+        let msg = {
+          screenX: aEvent.screenX,
+          screenY: aEvent.screenY,
+          uri: hrefNode,
+          elementSrc: isImageNode || isMediaNode
+                      ? node.currentSrc || node.src
+                      : null
+        };
+
+        if (hrefNode || isImageNode || isMediaNode) {
+          sendAsyncMessage("GeckoView:ContextMenu", msg);
+          aEvent.preventDefault();
+        }
+        break;
       case "MozDOMFullscreen:Request":
         sendAsyncMessage("GeckoView:DOMFullscreenRequest");
         break;
       case "MozDOMFullscreen:Entered":
       case "MozDOMFullscreen:Exited":
         // Content may change fullscreen state by itself, and we should ensure
         // that the parent always exits fullscreen when content has left
         // full screen mode.
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoView.java
@@ -91,36 +91,42 @@ public class GeckoView extends LayerView
 
     private final EventDispatcher mEventDispatcher =
         new EventDispatcher(mNativeQueue);
 
     private final GeckoViewHandler<ContentListener> mContentHandler =
         new GeckoViewHandler<ContentListener>(
             "GeckoViewContent", this,
             new String[]{
+                "GeckoView:ContextMenu",
                 "GeckoView:DOMTitleChanged",
                 "GeckoView:FullScreenEnter",
                 "GeckoView:FullScreenExit"
             }
         ) {
             @Override
             public void handleMessage(final ContentListener listener,
                                       final String event,
                                       final GeckoBundle message,
                                       final EventCallback callback) {
 
-                if ("GeckoView:DOMTitleChanged".equals(event)) {
+                if ("GeckoView:ContextMenu".equals(event)) {
+                    listener.onContextMenu(GeckoView.this,
+                                           message.getInt("screenX"),
+                                           message.getInt("screenY"),
+                                           message.getString("uri"),
+                                           message.getString("elementSrc"));
+                } else if ("GeckoView:DOMTitleChanged".equals(event)) {
                     listener.onTitleChange(GeckoView.this,
                                            message.getString("title"));
                 } else if ("GeckoView:FullScreenEnter".equals(event)) {
                     listener.onFullScreen(GeckoView.this, true);
                 } else if ("GeckoView:FullScreenExit".equals(event)) {
                     listener.onFullScreen(GeckoView.this, false);
                 }
-
             }
         };
 
     private final GeckoViewHandler<NavigationListener> mNavigationHandler =
         new GeckoViewHandler<NavigationListener>(
             "GeckoViewNavigation", this,
             new String[]{ "GeckoView:LocationChange" }
         ) {
@@ -1283,16 +1289,33 @@ public class GeckoView extends LayerView
          * A page has entered or exited full screen mode. Typically, the implementation
          * would set the Activity containing the GeckoView to full screen when the page is
          * in full screen mode.
          *
          * @param view The GeckoView that initiated the callback.
          * @param fullScreen True if the page is in full screen mode.
          */
         void onFullScreen(GeckoView view, boolean fullScreen);
+
+
+        /**
+         * A user has initiated the context menu via long-press.
+         * This event is fired on links, (nested) images and (nested) media
+         * elements.
+         *
+         * @param view The GeckoView that initiated the callback.
+         * @param screenX The screen coordinates of the press.
+         * @param screenY The screen coordinates of the press.
+         * @param uri The URI of the pressed link, set for links and
+         *            image-links.
+         * @param elementSrc The source URI of the pressed element, set for
+         *                   (nested) images and media elements.
+         */
+        void onContextMenu(GeckoView view, int screenX, int screenY,
+                           String uri, String elementSrc);
     }
 
     public interface NavigationListener {
         /**
         * A view has started loading content from the network.
         * @param view The GeckoView that initiated the callback.
         * @param url The resource being loaded.
         */
--- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
+++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
@@ -128,16 +128,24 @@ public class GeckoViewActivity extends A
             getWindow().setFlags(fullScreen ? WindowManager.LayoutParams.FLAG_FULLSCREEN : 0,
                                  WindowManager.LayoutParams.FLAG_FULLSCREEN);
             if (fullScreen) {
                 getActionBar().hide();
             } else {
                 getActionBar().show();
             }
         }
+
+        @Override
+        public void onContextMenu(GeckoView view, int screenX, int screenY,
+                                  String uri, String elementSrc) {
+            Log.d(LOGTAG, "onContextMenu screenX=" + screenX +
+                          " screenY=" + screenY + " uri=" + uri +
+                          " elementSrc=" + elementSrc);
+        }
     }
 
     private class MyGeckoViewProgress implements GeckoView.ProgressListener {
         @Override
         public void onPageStart(GeckoView view, String url) {
             Log.i(LOGTAG, "Starting to load page at " + url);
             Log.i(LOGTAG, "zerdatime " + SystemClock.elapsedRealtime() +
                   " - page load start");
--- a/mobile/android/modules/geckoview/GeckoViewContent.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewContent.jsm
@@ -33,16 +33,17 @@ class GeckoViewContent extends GeckoView
                                  /* capture */ true, /* untrusted */ false);
     this.window.addEventListener("MozDOMFullScreen:Exited", this,
                                  /* capture */ true, /* untrusted */ false);
 
     this.eventDispatcher.registerListener(this, "GeckoViewContent:ExitFullScreen");
     this.messageManager.addMessageListener("GeckoView:DOMFullscreenExit", this);
     this.messageManager.addMessageListener("GeckoView:DOMFullscreenRequest", this);
     this.messageManager.addMessageListener("GeckoView:DOMTitleChanged", this);
+    this.messageManager.addMessageListener("GeckoView:ContextMenu", this);
   }
 
   // Bundle event handler.
   onEvent(aEvent, aData, aCallback) {
     debug("onEvent: " + aEvent);
     switch (aEvent) {
       case "GeckoViewContent:ExitFullScreen":
         this.messageManager.sendAsyncMessage("GeckoView:DOMFullscreenExited");
@@ -54,16 +55,17 @@ class GeckoViewContent extends GeckoView
     this.window.removeEventListener("MozDOMFullScreen:Entered", this,
                                     /* capture */ true);
     this.window.removeEventListener("MozDOMFullScreen:Exited", this,
                                     /* capture */ true);
     this.eventDispatcher.unregisterListener(this, "GeckoViewContent:ExitFullScreen");
     this.messageManager.removeMessageListener("GeckoView:DOMFullscreenExit", this);
     this.messageManager.removeMessageListener("GeckoView:DOMFullscreenRequest", this);
     this.messageManager.removeMessageListener("GeckoView:DOMTitleChanged", this);
+    this.messageManager.removeMessageListener("GeckoView:ContextMenu", this);
   }
 
   // DOM event handler
   handleEvent(aEvent) {
     debug("handleEvent: aEvent.type=" + aEvent.type);
 
     switch (aEvent.type) {
       case "MozDOMFullscreen:Entered":
@@ -78,16 +80,25 @@ class GeckoViewContent extends GeckoView
     }
   }
 
   // Message manager event handler.
   receiveMessage(aMsg) {
     debug("receiveMessage " + aMsg.name);
 
     switch (aMsg.name) {
+      case "GeckoView:ContextMenu":
+        this.eventDispatcher.sendRequest({
+          type: aMsg.name,
+          screenX: aMsg.data.screenX,
+          screenY: aMsg.data.screenY,
+          elementSrc: aMsg.data.elementSrc,
+          uri: aMsg.data.uri
+        });
+        break;
       case "GeckoView:DOMFullscreenExit":
         this.window.QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIDOMWindowUtils)
                    .remoteFrameFullscreenReverted();
         break;
       case "GeckoView:DOMFullscreenRequest":
         this.window.QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIDOMWindowUtils)
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1435,16 +1435,17 @@ pref("javascript.options.jit.full_debug_
 // fail.
 pref("javascript.options.discardSystemSource", false);
 // This preference limits the memory usage of javascript.
 // If you want to change these values for your device,
 // please find Bug 417052 comment 17 and Bug 456721
 // Comment 32 and Bug 613551.
 pref("javascript.options.mem.high_water_mark", 128);
 pref("javascript.options.mem.max", -1);
+pref("javascript.options.mem.nursery.max_kb", -1);
 pref("javascript.options.mem.gc_per_zone", true);
 pref("javascript.options.mem.gc_incremental", true);
 pref("javascript.options.mem.gc_incremental_slice_ms", 5);
 pref("javascript.options.mem.gc_compacting", true);
 pref("javascript.options.mem.log", false);
 pref("javascript.options.mem.notify", false);
 pref("javascript.options.gc_on_memory_pressure", true);
 pref("javascript.options.compact_on_user_inactive", true);
--- a/netwerk/protocol/res/ExtensionProtocolHandler.cpp
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.cpp
@@ -1,29 +1,29 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ExtensionProtocolHandler.h"
 
-#include "mozilla/AbstractThread.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/ExtensionPolicyService.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/ipc/IPCStreamUtils.h"
 #include "mozilla/ipc/URIParams.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/RefPtr.h"
 
 #include "FileDescriptor.h"
 #include "FileDescriptorFile.h"
 #include "LoadInfo.h"
+#include "nsContentUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIFile.h"
 #include "nsIFileChannel.h"
 #include "nsIFileStreams.h"
 #include "nsIFileURL.h"
 #include "nsIJARChannel.h"
 #include "nsIMIMEService.h"
 #include "nsIURL.h"
@@ -104,16 +104,18 @@ class ExtensionStreamGetter : public Ref
     // in an unpacked extension.
     ExtensionStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo)
       : mURI(aURI)
       , mLoadInfo(aLoadInfo)
       , mIsJarChannel(false)
     {
       MOZ_ASSERT(aURI);
       MOZ_ASSERT(aLoadInfo);
+
+      SetupEventTarget();
     }
 
     // To use when getting an FD for a packed extension JAR file
     // in order to load a resource.
     ExtensionStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo,
                           already_AddRefed<nsIJARChannel>&& aJarChannel,
                           nsIFile* aJarFile)
       : mURI(aURI)
@@ -121,20 +123,31 @@ class ExtensionStreamGetter : public Ref
       , mJarChannel(Move(aJarChannel))
       , mJarFile(aJarFile)
       , mIsJarChannel(true)
     {
       MOZ_ASSERT(aURI);
       MOZ_ASSERT(aLoadInfo);
       MOZ_ASSERT(mJarChannel);
       MOZ_ASSERT(aJarFile);
+
+      SetupEventTarget();
     }
 
     ~ExtensionStreamGetter() {}
 
+    void SetupEventTarget()
+    {
+      mMainThreadEventTarget =
+        nsContentUtils::GetEventTargetByLoadInfo(mLoadInfo, TaskCategory::Other);
+      if (!mMainThreadEventTarget) {
+        mMainThreadEventTarget = GetMainThreadSerialEventTarget();
+      }
+    }
+
     // Get an input stream or file descriptor from the parent asynchronously.
     Result<Ok, nsresult> GetAsync(nsIStreamListener* aListener,
                                   nsIChannel* aChannel);
 
     // Handle an input stream being returned from the parent
     void OnStream(nsIInputStream* aStream);
 
     // Handle file descriptor being returned from the parent
@@ -144,16 +157,17 @@ class ExtensionStreamGetter : public Ref
 
   private:
     nsCOMPtr<nsIURI> mURI;
     nsCOMPtr<nsILoadInfo> mLoadInfo;
     nsCOMPtr<nsIJARChannel> mJarChannel;
     nsCOMPtr<nsIFile> mJarFile;
     nsCOMPtr<nsIStreamListener> mListener;
     nsCOMPtr<nsIChannel> mChannel;
+    nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
     bool mIsJarChannel;
 };
 
 class ExtensionJARFileOpener : public nsISupports
 {
 public:
   ExtensionJARFileOpener(nsIFile* aFile,
                          NeckoParent::GetExtensionFDResolver& aResolve) :
@@ -220,47 +234,48 @@ NS_IMPL_ISUPPORTS(ExtensionJARFileOpener
 #define DEFAULT_THREAD_TIMEOUT_MS 30000
 
 // Request an FD or input stream from the parent.
 Result<Ok, nsresult>
 ExtensionStreamGetter::GetAsync(nsIStreamListener* aListener,
                                 nsIChannel* aChannel)
 {
   MOZ_ASSERT(IsNeckoChild());
+  MOZ_ASSERT(mMainThreadEventTarget);
 
   mListener = aListener;
   mChannel = aChannel;
 
   // Serialize the URI to send to parent
   mozilla::ipc::URIParams uri;
   SerializeURI(mURI, uri);
 
   // Serialize the LoadInfo to send to parent
   OptionalLoadInfoArgs loadInfo;
   NS_TRY(mozilla::ipc::LoadInfoToLoadInfoArgs(mLoadInfo, &loadInfo));
 
   RefPtr<ExtensionStreamGetter> self = this;
   if (mIsJarChannel) {
     // Request an FD for this moz-extension URI
     gNeckoChild->SendGetExtensionFD(uri, loadInfo)->Then(
-      AbstractThread::MainThread(),
+      mMainThreadEventTarget,
       __func__,
       [self] (const FileDescriptor& fd) {
         self->OnFD(fd);
       },
       [self] (const mozilla::ipc::PromiseRejectReason) {
         self->OnFD(FileDescriptor());
       }
     );
     return Ok();
   }
 
   // Request an input stream for this moz-extension URI
   gNeckoChild->SendGetExtensionStream(uri, loadInfo)->Then(
-    AbstractThread::MainThread(),
+    mMainThreadEventTarget,
     __func__,
     [self] (const OptionalIPCStream& stream) {
       nsCOMPtr<nsIInputStream> inputStream;
       if (stream.type() == OptionalIPCStream::OptionalIPCStream::TIPCStream) {
         inputStream = ipc::DeserializeIPCStream(stream);
       }
       self->OnStream(inputStream);
     },
@@ -272,33 +287,35 @@ ExtensionStreamGetter::GetAsync(nsIStrea
 }
 
 // Handle an input stream sent from the parent.
 void
 ExtensionStreamGetter::OnStream(nsIInputStream* aStream)
 {
   MOZ_ASSERT(IsNeckoChild());
   MOZ_ASSERT(mListener);
+  MOZ_ASSERT(mMainThreadEventTarget);
 
   // We must keep an owning reference to the listener
   // until we pass it on to AsyncRead.
   nsCOMPtr<nsIStreamListener> listener = mListener.forget();
 
   MOZ_ASSERT(mChannel);
 
   if (!aStream) {
     // The parent didn't send us back a stream.
     listener->OnStartRequest(mChannel, nullptr);
     listener->OnStopRequest(mChannel, nullptr, NS_ERROR_FILE_ACCESS_DENIED);
     mChannel->Cancel(NS_BINDING_ABORTED);
     return;
   }
 
   nsCOMPtr<nsIInputStreamPump> pump;
-  nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump), aStream);
+  nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump), aStream, -1, -1, 0,
+                                      0, false, mMainThreadEventTarget);
   if (NS_FAILED(rv)) {
     mChannel->Cancel(NS_BINDING_ABORTED);
     return;
   }
 
   rv = pump->AsyncRead(listener, nullptr);
   if (NS_FAILED(rv)) {
     mChannel->Cancel(NS_BINDING_ABORTED);
--- a/services/sync/modules/keys.js
+++ b/services/sync/modules/keys.js
@@ -1,17 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = [
   "BulkKeyBundle",
-  "SyncKeyBundle"
 ];
 
 var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/main.js");
 Cu.import("resource://services-sync/util.js");
@@ -162,54 +161,8 @@ BulkKeyBundle.prototype = {
       throw new Error("BulkKeyBundle.keyPairB64 value must be an array of 2 " +
                       "keys.");
     }
 
     this.encryptionKey  = Utils.safeAtoB(value[0]);
     this.hmacKey        = Utils.safeAtoB(value[1]);
   },
 };
-
-/**
- * Represents a key pair derived from a Sync Key via HKDF.
- *
- * Instances of this type should be considered immutable. You create an
- * instance by specifying the username and 26 character "friendly" Base32
- * encoded Sync Key. The Sync Key is derived at instance creation time.
- *
- * If the username or Sync Key is invalid, an Error will be thrown.
- */
-this.SyncKeyBundle = function SyncKeyBundle(username, syncKey) {
-  let log = Log.repository.getLogger("Sync.SyncKeyBundle");
-  log.info("SyncKeyBundle being created.");
-  KeyBundle.call(this);
-
-  this.generateFromKey(username, syncKey);
-}
-SyncKeyBundle.prototype = {
-  __proto__: KeyBundle.prototype,
-
-  /*
-   * If we've got a string, hash it into keys and store them.
-   */
-  generateFromKey: function generateFromKey(username, syncKey) {
-    if (!username || (typeof username != "string")) {
-      throw new Error("Sync Key cannot be generated from non-string username.");
-    }
-
-    if (!syncKey || (typeof syncKey != "string")) {
-      throw new Error("Sync Key cannot be generated from non-string key.");
-    }
-
-    if (!Utils.isPassphrase(syncKey)) {
-      throw new Error("Provided key is not a passphrase, cannot derive Sync " +
-                      "Key Bundle.");
-    }
-
-    // Expand the base32 Sync Key to an AES 256 and 256 bit HMAC key.
-    let prk = Utils.decodeKeyBase32(syncKey);
-    let info = HMAC_INPUT + username;
-    let okm = Utils.hkdfExpand(prk, info, 32 * 2);
-    this.encryptionKey = okm.slice(0, 32);
-    this.hmacKey = okm.slice(32, 64);
-  },
-};
-
--- a/services/sync/tests/unit/head_errorhandler_common.js
+++ b/services/sync/tests/unit/head_errorhandler_common.js
@@ -92,17 +92,18 @@ const EHTestsCommon = {
 
     return CatapultEngine;
   }()),
 
 
   generateCredentialsChangedFailure() {
     // Make sync fail due to changed credentials. We simply re-encrypt
     // the keys with a different Sync Key, without changing the local one.
-    let newSyncKeyBundle = new SyncKeyBundle("johndoe", "23456234562345623456234562");
+    let newSyncKeyBundle = new BulkKeyBundle("crypto");
+    newSyncKeyBundle.generateRandom();
     let keys = Service.collectionKeys.asWBO();
     keys.encrypt(newSyncKeyBundle);
     return keys.upload(Service.resource(Service.cryptoKeysURL));
   },
 
   async setUp(server) {
     await configureIdentity({ username: "johndoe" }, server);
     return EHTestsCommon.generateAndUploadKeys()
--- a/services/sync/tests/unit/test_keys.js
+++ b/services/sync/tests/unit/test_keys.js
@@ -112,64 +112,16 @@ add_test(function test_repeated_hmac() {
   let k = Utils.makeHMACKey("foo");
   let one = sha256HMAC(Utils.decodeKeyBase32(testKey), k);
   let two = sha256HMAC(Utils.decodeKeyBase32(testKey), k);
   do_check_eq(one, two);
 
   run_next_test();
 });
 
-add_test(function test_sync_key_bundle_derivation() {
-  _("Ensure derivation from known values works.");
-
-  // The known values in this test were originally verified against Firefox
-  // Home.
-  let bundle = new SyncKeyBundle("st3fan", "q7ynpwq7vsc9m34hankbyi3s3i");
-
-  // These should be compared to the results from Home, as they once were.
-  let e = "14b8c09fa84e92729ee695160af6e0385f8f6215a25d14906e1747bdaa2de426";
-  let h = "370e3566245d79fe602a3adb5137e42439cd2a571235197e0469d7d541b07875";
-
-  let realE = Utils.bytesAsHex(bundle.encryptionKey);
-  let realH = Utils.bytesAsHex(bundle.hmacKey);
-
-  _("Real E: " + realE);
-  _("Real H: " + realH);
-  do_check_eq(realH, h);
-  do_check_eq(realE, e);
-
-  run_next_test();
-});
-
-add_test(function test_keymanager() {
-  let testKey = "ababcdefabcdefabcdefabcdef";
-  let username = "john@example.com";
-
-  // Decode the key here to mirror what generateEntry will do,
-  // but pass it encoded into the KeyBundle call below.
-
-  let sha256inputE = "" + HMAC_INPUT + username + "\x01";
-  let key = Utils.makeHMACKey(Utils.decodeKeyBase32(testKey));
-  let encryptKey = sha256HMAC(sha256inputE, key);
-
-  let sha256inputH = encryptKey + HMAC_INPUT + username + "\x02";
-  let hmacKey = sha256HMAC(sha256inputH, key);
-
-  // Encryption key is stored in base64 for WeaveCrypto convenience.
-  do_check_eq(encryptKey, new SyncKeyBundle(username, testKey).encryptionKey);
-  do_check_eq(hmacKey, new SyncKeyBundle(username, testKey).hmacKey);
-
-  // Test with the same KeyBundle for both.
-  let obj = new SyncKeyBundle(username, testKey);
-  do_check_eq(hmacKey, obj.hmacKey);
-  do_check_eq(encryptKey, obj.encryptionKey);
-
-  run_next_test();
-});
-
 add_task(async function test_ensureLoggedIn() {
   let log = Log.repository.getLogger("Test");
   Log.repository.rootLogger.addAppender(new Log.DumpAppender());
 
   let identityConfig = makeIdentityConfig();
   let browseridManager = new BrowserIDManager();
   configureFxAccountIdentity(browseridManager, identityConfig);
   await browseridManager.ensureLoggedIn();
--- a/services/sync/tests/unit/test_service_detect_upgrade.js
+++ b/services/sync/tests/unit/test_service_detect_upgrade.js
@@ -215,17 +215,18 @@ add_task(async function v5_upgrade() {
     let m = new WBORecord("meta", "global");
     m.payload = {"syncID": "foooooooooooooooooooooooooo",
                  "storageVersion": STORAGE_VERSION + 1};
     await m.upload(Service.resource(Service.metaURL));
 
     _("New meta/global: " + JSON.stringify(meta_global));
 
     // Fill the keys with bad data.
-    let badKeys = new SyncKeyBundle("foobar", "aaaaaaaaaaaaaaaaaaaaaaaaaa");
+    let badKeys = new BulkKeyBundle("crypto");
+    badKeys.generateRandom();
     await update_server_keys(badKeys, "keys", "crypto/keys");  // v4
     await update_server_keys(badKeys, "bulk", "crypto/bulk");  // v5
 
     _("Generating new keys.");
     generateNewKeys(Service.collectionKeys);
 
     // Now sync and see what happens. It should be a version fail, not a crypto
     // fail.
deleted file mode 100644
--- a/testing/web-platform/meta/service-workers/service-worker/controller-on-reload.https.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[controller-on-reload.https.html]
-  type: testharness
-  [controller is set upon reload after registration]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/service-workers/service-worker/multi-globals/url-parsing.https.html.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-[url-parsing.https.html]
-  type: testharness
-  [register should use the relevant global of the object it was called on to resolve the script URL and the default scope URL]
-    expected: FAIL
-
-  [register should use the relevant global of the object it was called on to resolve the script URL and the given scope URL]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/service-workers/service-worker/multiple-register.https.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[multiple-register.https.html]
-  type: testharness
-  [Subsequent registrations from a different iframe resolve to the different registration object but they refer to the same registration and workers]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/service-workers/service-worker/registration-iframe.https.html.ini
+++ /dev/null
@@ -1,11 +0,0 @@
-[registration-iframe.https.html]
-  type: testharness
-  [register method should use the "relevant global object" to parse its scriptURL and scope - normal case]
-    expected: FAIL
-
-  [register method should use the "relevant global object" to parse its scriptURL and scope - error case]
-    expected: FAIL
-
-  [A scope url should start with the given script url]
-    expected: FAIL
-
--- a/toolkit/components/contextualidentity/ContextualIdentityService.jsm
+++ b/toolkit/components/contextualidentity/ContextualIdentityService.jsm
@@ -28,16 +28,40 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/AsyncShutdown.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
                                   "resource://gre/modules/DeferredTask.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 
+function _TabRemovalObserver(resolver, tabParentIds) {
+  this._resolver = resolver;
+  this._tabParentIds = tabParentIds;
+  Services.obs.addObserver(this, "ipc:browser-destroyed");
+}
+
+_TabRemovalObserver.prototype = {
+  _resolver: null,
+  _tabParentIds: null,
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+
+  observe(subject, topic, data) {
+    let tabParent = subject.QueryInterface(Ci.nsITabParent);
+    if (this._tabParentIds.has(tabParent.tabId)) {
+      this._tabParentIds.delete(tabParent.tabId);
+      if (this._tabParentIds.size == 0) {
+        Services.obs.removeObserver(this, "ipc:browser-destroyed");
+        this._resolver();
+      }
+    }
+  }
+};
+
 function _ContextualIdentityService(path) {
   this.init(path);
 }
 
 _ContextualIdentityService.prototype = {
   _defaultIdentities: [
     { userContextId: 1,
       public: true,
@@ -304,19 +328,43 @@ function _ContextualIdentityService(path
     let count = 0;
     this._forEachContainerTab(function() {
       ++count;
     }, userContextId);
     return count;
   },
 
   closeContainerTabs(userContextId = 0) {
-    this._forEachContainerTab(function(tab, tabbrowser) {
-      tabbrowser.removeTab(tab);
-    }, userContextId);
+    return new Promise(resolve => {
+      let tabParentIds = new Set();
+      this._forEachContainerTab((tab, tabbrowser) => {
+        let frameLoader = tab.linkedBrowser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+
+        // We don't have tabParent in non-e10s mode.
+        if (frameLoader.tabParent) {
+          tabParentIds.add(frameLoader.tabParent.tabId);
+        }
+
+        tabbrowser.removeTab(tab);
+      }, userContextId);
+
+      if (tabParentIds.size == 0) {
+        resolve();
+        return;
+      }
+
+      new _TabRemovalObserver(resolve, tabParentIds);
+    });
+  },
+
+  notifyAllContainersCleared() {
+    for (let identity of this._identities) {
+      Services.obs.notifyObservers(null, "clear-origin-attributes-data",
+                                   JSON.stringify({ userContextId: identity.userContextId }));
+    }
   },
 
   _forEachContainerTab(callback, userContextId = 0) {
     let windowList = Services.wm.getEnumerator("navigator:browser");
     while (windowList.hasMoreElements()) {
       let win = windowList.getNext();
 
       if (win.closed || !win.gBrowser) {
--- a/toolkit/components/telemetry/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/TelemetrySession.jsm
@@ -43,34 +43,28 @@ const REASON_SHUTDOWN = "shutdown";
 const ENVIRONMENT_CHANGE_LISTENER = "TelemetrySession::onEnvironmentChange";
 
 const MS_IN_ONE_HOUR  = 60 * 60 * 1000;
 const MIN_SUBSESSION_LENGTH_MS = Preferences.get("toolkit.telemetry.minSubsessionLength", 5 * 60) * 1000;
 
 const LOGGER_NAME = "Toolkit.Telemetry";
 const LOGGER_PREFIX = "TelemetrySession" + (Utils.isContentProcess ? "#content::" : "::");
 
-const PREF_BRANCH = "toolkit.telemetry.";
-const PREF_PREVIOUS_BUILDID = PREF_BRANCH + "previousBuildID";
-const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
-const PREF_UNIFIED = PREF_BRANCH + "unified";
-const PREF_SHUTDOWN_PINGSENDER = PREF_BRANCH + "shutdownPingSender.enabled";
-
 const MESSAGE_TELEMETRY_PAYLOAD = "Telemetry:Payload";
 const MESSAGE_TELEMETRY_THREAD_HANGS = "Telemetry:ChildThreadHangs";
 const MESSAGE_TELEMETRY_GET_CHILD_THREAD_HANGS = "Telemetry:GetChildThreadHangs";
 const MESSAGE_TELEMETRY_USS = "Telemetry:USS";
 const MESSAGE_TELEMETRY_GET_CHILD_USS = "Telemetry:GetChildUSS";
 
 const DATAREPORTING_DIRECTORY = "datareporting";
 const ABORTED_SESSION_FILE_NAME = "aborted-session-ping";
 
 // Whether the FHR/Telemetry unification features are enabled.
 // Changing this pref requires a restart.
-const IS_UNIFIED_TELEMETRY = Preferences.get(PREF_UNIFIED, false);
+const IS_UNIFIED_TELEMETRY = Preferences.get(TelemetryUtils.Preferences.Unified, false);
 
 // Maximum number of content payloads that we are willing to store.
 const MAX_NUM_CONTENT_PAYLOADS = 10;
 
 // Do not gather data more than once a minute (ms)
 const TELEMETRY_INTERVAL = 60 * 1000;
 // Delay before intializing telemetry (ms)
 const TELEMETRY_DELAY = Preferences.get("toolkit.telemetry.initDelay", 60) * 1000;
@@ -526,19 +520,16 @@ var TelemetryScheduler = {
 
     this._rescheduleTimeout();
   },
 };
 
 this.EXPORTED_SYMBOLS = ["TelemetrySession"];
 
 this.TelemetrySession = Object.freeze({
-  Constants: Object.freeze({
-    PREF_PREVIOUS_BUILDID,
-  }),
   /**
    * Send a ping to a test server. Used only for testing.
    */
   testPing() {
     return Impl.testPing();
   },
   /**
    * Returns the current telemetry payload.
@@ -1511,22 +1502,22 @@ var Impl = {
     annotateCrashReport(this._sessionId);
 
     // Initialize some probes that are kept in their own modules
     this._thirdPartyCookies = new ThirdPartyCookieProbe();
     this._thirdPartyCookies.init();
 
     // Record old value and update build ID preference if this is the first
     // run with a new build ID.
-    let previousBuildId = Preferences.get(PREF_PREVIOUS_BUILDID, null);
+    let previousBuildId = Preferences.get(TelemetryUtils.Preferences.PreviousBuildID, null);
     let thisBuildID = Services.appinfo.appBuildID;
     // If there is no previousBuildId preference, we send null to the server.
     if (previousBuildId != thisBuildID) {
       this._previousBuildId = previousBuildId;
-      Preferences.set(PREF_PREVIOUS_BUILDID, thisBuildID);
+      Preferences.set(TelemetryUtils.Preferences.PreviousBuildID, thisBuildID);
     }
 
     this.attachEarlyObservers();
 
     ppml.addMessageListener(MESSAGE_TELEMETRY_PAYLOAD, this);
     ppml.addMessageListener(MESSAGE_TELEMETRY_THREAD_HANGS, this);
     ppml.addMessageListener(MESSAGE_TELEMETRY_USS, this);
   },
@@ -1806,17 +1797,17 @@ var Impl = {
 
     if (IS_UNIFIED_TELEMETRY) {
       let shutdownPayload = this.getSessionPayload(REASON_SHUTDOWN, false);
 
       // Only send the shutdown ping using the pingsender from the second
       // browsing session on, to mitigate issues with "bot" profiles (see bug 1354482).
       // Note: sending the "shutdown" ping using the pingsender is currently disabled
       // due to a crash happening on OSX platforms. See bug 1357745 for context.
-      let sendWithPingsender = Preferences.get(PREF_SHUTDOWN_PINGSENDER, false) &&
+      let sendWithPingsender = Preferences.get(TelemetryUtils.Preferences.ShutdownPingSender, false) &&
                                !TelemetryReportingPolicy.isFirstRun();
 
       let options = {
         addClientId: true,
         addEnvironment: true,
         usePingSender: sendWithPingsender,
       };
       p.push(TelemetryController.submitExternalPing(getPingType(shutdownPayload), shutdownPayload, options)
--- a/toolkit/components/telemetry/TelemetryUtils.jsm
+++ b/toolkit/components/telemetry/TelemetryUtils.jsm
@@ -31,16 +31,17 @@ this.TelemetryUtils = {
     FirstRun: "toolkit.telemetry.reportingpolicy.firstRun",
     OverrideOfficialCheck: "toolkit.telemetry.send.overrideOfficialCheck",
     Server: "toolkit.telemetry.server",
     ShutdownPingSender: "toolkit.telemetry.shutdownPingSender.enabled",
     TelemetryEnabled: "toolkit.telemetry.enabled",
     Unified: "toolkit.telemetry.unified",
     NewProfilePingEnabled: "toolkit.telemetry.newProfilePing.enabled",
     NewProfilePingDelay: "toolkit.telemetry.newProfilePing.delay",
+    PreviousBuildID: "toolkit.telemetry.previousBuildID",
 
     // Log Preferences
     LogLevel: "toolkit.telemetry.log.level",
     LogDump: "toolkit.telemetry.log.dump",
 
     // Data reporting Preferences
     AcceptedPolicyDate: "datareporting.policy.dataSubmissionPolicyNotifiedTime",
     AcceptedPolicyVersion: "datareporting.policy.dataSubmissionPolicyAcceptedVersion",
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryControllerBuildID.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryControllerBuildID.js
@@ -29,17 +29,17 @@ updateAppInfo();
 
 // Check that when run with no previous build ID stored, we update the pref but do not
 // put anything into the metadata.
 add_task(async function test_firstRun() {
   await TelemetryController.testReset();
   let metadata = TelemetrySession.getMetadata();
   do_check_false("previousBuildID" in metadata);
   let appBuildID = getAppInfo().appBuildID;
-  let buildIDPref = Services.prefs.getCharPref(TelemetrySession.Constants.PREF_PREVIOUS_BUILDID);
+  let buildIDPref = Services.prefs.getCharPref(TelemetryUtils.Preferences.PreviousBuildID);
   do_check_eq(appBuildID, buildIDPref);
 });
 
 // Check that a subsequent run with the same build ID does not put prev build ID in
 // metadata. Assumes testFirstRun() has already been called to set the previousBuildID pref.
 add_task(async function test_secondRun() {
   await TelemetryController.testReset();
   let metadata = TelemetrySession.getMetadata();
@@ -52,17 +52,17 @@ add_task(async function test_secondRun()
 const NEW_BUILD_ID = "20130314";
 add_task(async function test_newBuild() {
   let info = getAppInfo();
   let oldBuildID = info.appBuildID;
   info.appBuildID = NEW_BUILD_ID;
   await TelemetryController.testReset();
   let metadata = TelemetrySession.getMetadata();
   do_check_eq(metadata.previousBuildId, oldBuildID);
-  let buildIDPref = Services.prefs.getCharPref(TelemetrySession.Constants.PREF_PREVIOUS_BUILDID);
+  let buildIDPref = Services.prefs.getCharPref(TelemetryUtils.Preferences.PreviousBuildID);
   do_check_eq(NEW_BUILD_ID, buildIDPref);
 });
 
 
 function run_test() {
   // Make sure we have a profile directory.
   do_get_profile();
 
--- a/tools/profiler/core/ProfileBuffer.cpp
+++ b/tools/profiler/core/ProfileBuffer.cpp
@@ -3,16 +3,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ProfileBuffer.h"
 
 #include "ProfilerMarker.h"
 
+using namespace mozilla;
+
 ProfileBuffer::ProfileBuffer(int aEntrySize)
   : mEntries(mozilla::MakeUnique<ProfileBufferEntry[]>(aEntrySize))
   , mWritePos(0)
   , mReadPos(0)
   , mEntrySize(aEntrySize)
   , mGeneration(0)
 {
 }
@@ -51,41 +53,68 @@ ProfileBuffer::AddThreadIdEntry(int aThr
     // This is the start of a sample, so make a note of its location in |aLS|.
     aLS->mGeneration = mGeneration;
     aLS->mPos = mWritePos;
   }
   AddEntry(ProfileBufferEntry::ThreadId(aThreadId));
 }
 
 void
-ProfileBuffer::AddDynamicStringEntry(const char* aStr)
-{
-  size_t strLen = strlen(aStr) + 1;   // +1 for the null terminator
-  for (size_t j = 0; j < strLen; ) {
-    // Store up to kNumChars characters in the entry.
-    char chars[ProfileBufferEntry::kNumChars];
-    size_t len = ProfileBufferEntry::kNumChars;
-    if (j + len >= strLen) {
-      len = strLen - j;
-    }
-    memcpy(chars, &aStr[j], len);
-    j += ProfileBufferEntry::kNumChars;
-
-    AddEntry(ProfileBufferEntry::DynamicStringFragment(chars));
-  }
-}
-
-void
 ProfileBuffer::AddStoredMarker(ProfilerMarker *aStoredMarker)
 {
   aStoredMarker->SetGeneration(mGeneration);
   mStoredMarkers.insert(aStoredMarker);
 }
 
 void
+ProfileBuffer::CollectNativeLeafAddr(void* aAddr)
+{
+  AddEntry(ProfileBufferEntry::NativeLeafAddr(aAddr));
+}
+
+void
+ProfileBuffer::CollectJitReturnAddr(void* aAddr)
+{
+  AddEntry(ProfileBufferEntry::JitReturnAddr(aAddr));
+}
+
+void
+ProfileBuffer::CollectCodeLocation(
+  const char* aLabel, const char* aStr, int aLineNumber,
+  const Maybe<js::ProfileEntry::Category>& aCategory)
+{
+  AddEntry(ProfileBufferEntry::Label(aLabel));
+
+  if (aStr) {
+    // Store the string using one or more DynamicStringFragment entries.
+    size_t strLen = strlen(aStr) + 1;   // +1 for the null terminator
+    for (size_t j = 0; j < strLen; ) {
+      // Store up to kNumChars characters in the entry.
+      char chars[ProfileBufferEntry::kNumChars];
+      size_t len = ProfileBufferEntry::kNumChars;
+      if (j + len >= strLen) {
+        len = strLen - j;
+      }
+      memcpy(chars, &aStr[j], len);
+      j += ProfileBufferEntry::kNumChars;
+
+      AddEntry(ProfileBufferEntry::DynamicStringFragment(chars));
+    }
+  }
+
+  if (aLineNumber != -1) {
+    AddEntry(ProfileBufferEntry::LineNumber(aLineNumber));
+  }
+
+  if (aCategory.isSome()) {
+    AddEntry(ProfileBufferEntry::Category(int(*aCategory)));
+  }
+}
+
+void
 ProfileBuffer::DeleteExpiredStoredMarkers()
 {
   // Delete markers of samples that have been overwritten due to circular
   // buffer wraparound.
   uint32_t generation = mGeneration;
   while (mStoredMarkers.peek() &&
          mStoredMarkers.peek()->HasExpired(generation)) {
     delete mStoredMarkers.popHead();
--- a/tools/profiler/core/ProfileBuffer.h
+++ b/tools/profiler/core/ProfileBuffer.h
@@ -8,17 +8,17 @@
 
 #include "platform.h"
 #include "ProfileBufferEntry.h"
 #include "ProfilerMarker.h"
 #include "ProfileJSONWriter.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/RefCounted.h"
 
-class ProfileBuffer final
+class ProfileBuffer final : public ProfilerStackCollector
 {
 public:
   explicit ProfileBuffer(int aEntrySize);
 
   ~ProfileBuffer();
 
   // LastSample is used to record the buffer location of the most recent
   // sample for each thread. It is used for periodic samples stored in the
@@ -37,19 +37,26 @@ public:
 
   // Add |aEntry| to the buffer, ignoring what kind of entry it is.
   void AddEntry(const ProfileBufferEntry& aEntry);
 
   // Add to the buffer a sample start (ThreadId) entry for aThreadId. Also,
   // record the resulting generation and index in |aLS| if it's non-null.
   void AddThreadIdEntry(int aThreadId, LastSample* aLS = nullptr);
 
-  // Add to the buffer a dynamic string. It'll be spread across one or more
-  // DynamicStringFragment entries.
-  void AddDynamicStringEntry(const char* aStr);
+  virtual mozilla::Maybe<uint32_t> Generation() override
+  {
+    return mozilla::Some(mGeneration);
+  }
+
+  virtual void CollectNativeLeafAddr(void* aAddr) override;
+  virtual void CollectJitReturnAddr(void* aAddr) override;
+  virtual void CollectCodeLocation(
+    const char* aLabel, const char* aStr, int aLineNumber,
+    const mozilla::Maybe<js::ProfileEntry::Category>& aCategory) override;
 
   // Maximum size of a frameKey string that we'll handle.
   static const size_t kMaxFrameKeyLength = 512;
 
   void StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThreadId,
                            double aSinceTime, JSContext* cx,
                            UniqueStacks& aUniqueStacks) const;
   void StreamMarkersToJSON(SpliceableJSONWriter& aWriter, int aThreadId,
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -17,17 +17,17 @@
 //   call (profiler_get_backtrace()). It involves writing a stack trace and
 //   little else into a temporary ProfileBuffer, and wrapping that up in a
 //   ProfilerBacktrace that can be subsequently used in a marker. The sampling
 //   is done on-thread, and so Registers::SyncPopulate() is used to get the
 //   register values.
 //
 // - A "backtrace" sample is the simplest kind. It is done in response to an
 //   API call (profiler_suspend_and_sample_thread()). It involves getting a
-//   stack trace and passing it to a callback function; it does not write to a
+//   stack trace via a ProfilerStackCollector; it does not write to a
 //   ProfileBuffer. The sampling is done from off-thread, and so uses
 //   SuspendAndSampleAndResumeThread() to get the register values.
 
 #include <algorithm>
 #include <ostream>
 #include <fstream>
 #include <sstream>
 #include <errno.h>
@@ -49,17 +49,19 @@
 #include "mozilla/StaticPtr.h"
 #include "ThreadInfo.h"
 #include "nsIHttpProtocolHandler.h"
 #include "nsIObserverService.h"
 #include "nsIXULAppInfo.h"
 #include "nsIXULRuntime.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsDirectoryServiceDefs.h"
+#include "nsJSPrincipals.h"
 #include "nsMemoryReporterManager.h"
+#include "nsScriptSecurityManager.h"
 #include "nsXULAppAPI.h"
 #include "nsProfilerStartParams.h"
 #include "ProfilerParent.h"
 #include "mozilla/Services.h"
 #include "nsThreadUtils.h"
 #include "ProfilerMarkerPayload.h"
 #include "shared-libraries.h"
 #include "prdtoa.h"
@@ -671,69 +673,82 @@ public:
   Address mLR;    // ARM link register.
 #if defined(GP_OS_linux) || defined(GP_OS_android)
   // This contains all the registers, which means it duplicates the four fields
   // above. This is ok.
   ucontext_t* mContext; // The context from the signal handler.
 #endif
 };
 
-static void
-AddPseudoEntry(PSLockRef aLock, NotNull<RacyThreadInfo*> aRacyInfo,
-               const js::ProfileEntry& entry, ProfileBuffer& aBuffer)
+static bool
+IsChromeJSScript(JSScript* aScript)
 {
   // WARNING: this function runs within the profiler's "critical section".
 
+  nsIScriptSecurityManager* const secman =
+    nsScriptSecurityManager::GetScriptSecurityManager();
+  NS_ENSURE_TRUE(secman, false);
+
+  JSPrincipals* const principals = JS_GetScriptPrincipals(aScript);
+  return secman->IsSystemPrincipal(nsJSPrincipals::get(principals));
+}
+
+static void
+AddPseudoEntry(uint32_t aFeatures, NotNull<RacyThreadInfo*> aRacyInfo,
+               const js::ProfileEntry& entry,
+               ProfilerStackCollector& aCollector)
+{
+  // WARNING: this function runs within the profiler's "critical section".
+  // WARNING: this function might be called while the profiler is inactive, and
+  //          cannot rely on ActivePS.
+
   MOZ_ASSERT(entry.kind() == js::ProfileEntry::Kind::CPP_NORMAL ||
              entry.kind() == js::ProfileEntry::Kind::JS_NORMAL);
 
-  aBuffer.AddEntry(ProfileBufferEntry::Label(entry.label()));
-
   const char* dynamicString = entry.dynamicString();
   int lineno = -1;
 
   // XXX: it's unclear why the computation of lineno should depend on
   // |dynamicString|. Perhaps it shouldn't?
 
   if (dynamicString) {
-    // Adjust the dynamic string as necessary.
-    if (ActivePS::FeaturePrivacy(aLock)) {
-      dynamicString = "(private)";
-    } else if (strlen(dynamicString) >= ProfileBuffer::kMaxFrameKeyLength) {
-      dynamicString = "(too long)";
-    }
-
-    // Store the string using one or more DynamicStringFragment entries.
-    aBuffer.AddDynamicStringEntry(dynamicString);
+    bool isChromeJSEntry = false;
     if (entry.isJs()) {
-      JSScript* script = entry.script();
-      if (script) {
+      // We call entry.script() repeatedly -- rather than storing the result in
+      // a local variable in order -- to avoid rooting hazards.
+      if (entry.script()) {
+        isChromeJSEntry = IsChromeJSScript(entry.script());
         if (!entry.pc()) {
           // The JIT only allows the top-most entry to have a nullptr pc.
           MOZ_ASSERT(&entry == &aRacyInfo->entries[aRacyInfo->stackSize() - 1]);
         } else {
-          lineno = JS_PCToLineNumber(script, entry.pc());
+          lineno = JS_PCToLineNumber(entry.script(), entry.pc());
         }
       }
     } else {
       lineno = entry.line();
     }
+
+    // Adjust the dynamic string as necessary.
+    if (ProfilerFeature::HasPrivacy(aFeatures) && !isChromeJSEntry) {
+      dynamicString = "(private)";
+    } else if (strlen(dynamicString) >= ProfileBuffer::kMaxFrameKeyLength) {
+      dynamicString = "(too long)";
+    }
+
   } else {
     // XXX: Bug 1010578. Don't assume a CPP entry and try to get the line for
     // js entries as well.
     if (entry.isCpp()) {
       lineno = entry.line();
     }
   }
 
-  if (lineno != -1) {
-    aBuffer.AddEntry(ProfileBufferEntry::LineNumber(lineno));
-  }
-
-  aBuffer.AddEntry(ProfileBufferEntry::Category(int(entry.category())));
+  aCollector.CollectCodeLocation(entry.label(), dynamicString, lineno,
+                                 Some(entry.category()));
 }
 
 // Setting MAX_NATIVE_FRAMES too high risks the unwinder wasting a lot of time
 // looping on corrupted stacks.
 //
 // The PseudoStack frame size is found in PseudoStack::MaxEntries.
 static const size_t MAX_NATIVE_FRAMES = 1024;
 static const size_t MAX_JS_FRAMES     = 1024;
@@ -761,40 +776,45 @@ struct AutoWalkJSStack
 
   ~AutoWalkJSStack() {
     if (walkAllowed) {
       WALKING_JS_STACK = false;
     }
   }
 };
 
+// Merges the pseudo-stack, native stack, and JS stack, outputting the details
+// to aCollector.
 static void
-MergeStacksIntoProfile(PSLockRef aLock, bool aIsSynchronous,
-                       const ThreadInfo& aThreadInfo, const Registers& aRegs,
-                       const NativeStack& aNativeStack, ProfileBuffer& aBuffer)
+MergeStacks(uint32_t aFeatures, bool aIsSynchronous,
+            const ThreadInfo& aThreadInfo, const Registers& aRegs,
+            const NativeStack& aNativeStack,
+            ProfilerStackCollector& aCollector)
 {
   // WARNING: this function runs within the profiler's "critical section".
+  // WARNING: this function might be called while the profiler is inactive, and
+  //          cannot rely on ActivePS.
 
   NotNull<RacyThreadInfo*> racyInfo = aThreadInfo.RacyInfo();
   js::ProfileEntry* pseudoEntries = racyInfo->entries;
   uint32_t pseudoCount = racyInfo->stackSize();
   JSContext* context = aThreadInfo.mContext;
 
   // Make a copy of the JS stack into a JSFrame array. This is necessary since,
   // like the native stack, the JS stack is iterated youngest-to-oldest and we
   // need to iterate oldest-to-youngest when adding entries to aInfo.
 
   // Synchronous sampling reports an invalid buffer generation to
   // ProfilingFrameIterator to avoid incorrectly resetting the generation of
   // sampled JIT entries inside the JS engine. See note below concerning 'J'
   // entries.
-  uint32_t startBufferGen;
-  startBufferGen = aIsSynchronous
-                 ? UINT32_MAX
-                 : aBuffer.mGeneration;
+  uint32_t startBufferGen = UINT32_MAX;
+  if (!aIsSynchronous && aCollector.Generation().isSome()) {
+    startBufferGen = *aCollector.Generation();
+  }
   uint32_t jsCount = 0;
   JS::ProfilingFrameIterator::Frame jsFrames[MAX_JS_FRAMES];
 
   // Only walk jit stack if profiling frame iterator is turned on.
   if (context && JS::IsProfilingEnabledForContext(context)) {
     AutoWalkJSStack autoWalkJSStack;
     const uint32_t maxFrames = ArrayLength(jsFrames);
 
@@ -896,17 +916,17 @@ MergeStacksIntoProfile(PSLockRef aLock, 
     // Check to see if pseudoStack frame is top-most.
     if (pseudoStackAddr > jsStackAddr && pseudoStackAddr > nativeStackAddr) {
       MOZ_ASSERT(pseudoIndex < pseudoCount);
       js::ProfileEntry& pseudoEntry = pseudoEntries[pseudoIndex];
 
       // Pseudo-frames with the CPP_MARKER_FOR_JS kind are just annotations and
       // should not be recorded in the profile.
       if (pseudoEntry.kind() != js::ProfileEntry::Kind::CPP_MARKER_FOR_JS) {
-        AddPseudoEntry(aLock, racyInfo, pseudoEntry, aBuffer);
+        AddPseudoEntry(aFeatures, racyInfo, pseudoEntry, aCollector);
       }
       pseudoIndex++;
       continue;
     }
 
     // Check to see if JS jit stack frame is top-most
     if (jsStackAddr > nativeStackAddr) {
       MOZ_ASSERT(jsIndex >= 0);
@@ -922,49 +942,48 @@ MergeStacksIntoProfile(PSLockRef aLock, 
       // amount of time, such as in nsRefreshDriver. Problematically, the
       // stored backtrace may be alive across a GC during which the profiler
       // itself is disabled. In that case, the JS engine is free to discard its
       // JIT code. This means that if we inserted such OptInfoAddr entries into
       // the buffer, nsRefreshDriver would now be holding on to a backtrace
       // with stale JIT code return addresses.
       if (aIsSynchronous ||
           jsFrame.kind == JS::ProfilingFrameIterator::Frame_Wasm) {
-        aBuffer.AddEntry(ProfileBufferEntry::Label(""));
-        aBuffer.AddDynamicStringEntry(jsFrame.label);
+        aCollector.CollectCodeLocation("", jsFrame.label, -1, Nothing());
       } else {
         MOZ_ASSERT(jsFrame.kind == JS::ProfilingFrameIterator::Frame_Ion ||
                    jsFrame.kind == JS::ProfilingFrameIterator::Frame_Baseline);
-        aBuffer.AddEntry(
-          ProfileBufferEntry::JitReturnAddr(jsFrames[jsIndex].returnAddress));
+        aCollector.CollectJitReturnAddr(jsFrames[jsIndex].returnAddress);
       }
 
       jsIndex--;
       continue;
     }
 
     // If we reach here, there must be a native stack entry and it must be the
     // greatest entry.
     if (nativeStackAddr) {
       MOZ_ASSERT(nativeIndex >= 0);
       void* addr = (void*)aNativeStack.mPCs[nativeIndex];
-      aBuffer.AddEntry(ProfileBufferEntry::NativeLeafAddr(addr));
+      aCollector.CollectNativeLeafAddr(addr);
     }
     if (nativeIndex >= 0) {
       nativeIndex--;
     }
   }
 
   // Update the JS context with the current profile sample buffer generation.
   //
   // Do not do this for synchronous samples, which use their own
   // ProfileBuffers instead of the global one in CorePS.
-  if (!aIsSynchronous && context) {
-    MOZ_ASSERT(aBuffer.mGeneration >= startBufferGen);
-    uint32_t lapCount = aBuffer.mGeneration - startBufferGen;
-    JS::UpdateJSContextProfilerSampleBufferGen(context, aBuffer.mGeneration,
+  if (!aIsSynchronous && context && aCollector.Generation().isSome()) {
+    MOZ_ASSERT(*aCollector.Generation() >= startBufferGen);
+    uint32_t lapCount = *aCollector.Generation() - startBufferGen;
+    JS::UpdateJSContextProfilerSampleBufferGen(context,
+                                               *aCollector.Generation(),
                                                lapCount);
   }
 }
 
 #if defined(GP_OS_windows)
 static uintptr_t GetThreadHandle(PlatformData* aData);
 #endif
 
@@ -979,16 +998,18 @@ StackWalkCallback(uint32_t aFrameNumber,
   nativeStack->mCount++;
 }
 
 static void
 DoNativeBacktrace(PSLockRef aLock, const ThreadInfo& aThreadInfo,
                   const Registers& aRegs, NativeStack& aNativeStack)
 {
   // WARNING: this function runs within the profiler's "critical section".
+  // WARNING: this function might be called while the profiler is inactive, and
+  //          cannot rely on ActivePS.
 
   // Start with the current function. We use 0 as the frame number here because
   // the FramePointerStackWalk() and MozStackWalk() calls below will use 1..N.
   // This is a bit weird but it doesn't matter because StackWalkCallback()
   // doesn't use the frame number argument.
   StackWalkCallback(/* frameNum */ 0, aRegs.mPC, aRegs.mSP, &aNativeStack);
 
   uint32_t maxFrames = uint32_t(MAX_NATIVE_FRAMES - aNativeStack.mCount);
@@ -1012,16 +1033,18 @@ DoNativeBacktrace(PSLockRef aLock, const
 #endif
 
 #ifdef USE_EHABI_STACKWALK
 static void
 DoNativeBacktrace(PSLockRef aLock, const ThreadInfo& aThreadInfo,
                   const Registers& aRegs, NativeStack& aNativeStack)
 {
   // WARNING: this function runs within the profiler's "critical section".
+  // WARNING: this function might be called while the profiler is inactive, and
+  //          cannot rely on ActivePS.
 
   const mcontext_t* mcontext = &aRegs.mContext->uc_mcontext;
   mcontext_t savedContext;
   NotNull<RacyThreadInfo*> racyInfo = aThreadInfo.RacyInfo();
 
   // The pseudostack contains an "EnterJIT" frame whenever we enter
   // JIT code with profiling enabled; the stack pointer value points
   // the saved registers.  We use this to unwind resume unwinding
@@ -1091,16 +1114,18 @@ ASAN_memcpy(void* aDst, const void* aSrc
 }
 #endif
 
 static void
 DoNativeBacktrace(PSLockRef aLock, const ThreadInfo& aThreadInfo,
                   const Registers& aRegs, NativeStack& aNativeStack)
 {
   // WARNING: this function runs within the profiler's "critical section".
+  // WARNING: this function might be called while the profiler is inactive, and
+  //          cannot rely on ActivePS.
 
   const mcontext_t* mc = &aRegs.mContext->uc_mcontext;
 
   lul::UnwindRegs startRegs;
   memset(&startRegs, 0, sizeof(startRegs));
 
 #if defined(GP_PLAT_amd64_linux)
   startRegs.xip = lul::TaggedUWord(mc->gregs[REG_RIP]);
@@ -1236,23 +1261,23 @@ DoSharedSample(PSLockRef aLock, bool aIs
   TimeDuration delta = aNow - CorePS::ProcessStartTime();
   aBuffer.AddEntry(ProfileBufferEntry::Time(delta.ToMilliseconds()));
 
   NativeStack nativeStack;
 #if defined(HAVE_NATIVE_UNWIND)
   if (ActivePS::FeatureStackWalk(aLock)) {
     DoNativeBacktrace(aLock, aThreadInfo, aRegs, nativeStack);
 
-    MergeStacksIntoProfile(aLock, aIsSynchronous, aThreadInfo, aRegs,
-                           nativeStack, aBuffer);
+    MergeStacks(ActivePS::Features(aLock), aIsSynchronous, aThreadInfo, aRegs,
+                nativeStack, aBuffer);
   } else
 #endif
   {
-    MergeStacksIntoProfile(aLock, aIsSynchronous, aThreadInfo, aRegs,
-                           nativeStack, aBuffer);
+    MergeStacks(ActivePS::Features(aLock), aIsSynchronous, aThreadInfo, aRegs,
+                nativeStack, aBuffer);
 
     if (ActivePS::FeatureLeaf(aLock)) {
       aBuffer.AddEntry(ProfileBufferEntry::NativeLeafAddr((void*)aRegs.mPC));
     }
   }
 }
 
 // Writes the components of a synchronous sample to the given ProfileBuffer.
@@ -3264,10 +3289,67 @@ profiler_suspend_and_sample_thread(
       // NOTE: Make sure to disable the sampler before it is destroyed, in case
       // the profiler is running at the same time.
       sampler.Disable(lock);
       break;
     }
   }
 }
 
+// NOTE: aCollector's methods will be called while the target thread is paused.
+// Doing things in those methods like allocating -- which may try to claim
+// locks -- is a surefire way to deadlock.
+void
+profiler_suspend_and_sample_thread(int aThreadId,
+                                   uint32_t aFeatures,
+                                   ProfilerStackCollector& aCollector,
+                                   bool aSampleNative /* = true */)
+{
+  // Lock the profiler mutex
+  PSAutoLock lock(gPSMutex);
+
+  const CorePS::ThreadVector& liveThreads = CorePS::LiveThreads(lock);
+  for (uint32_t i = 0; i < liveThreads.size(); i++) {
+    ThreadInfo* info = liveThreads.at(i);
+
+    if (info->ThreadId() == aThreadId) {
+      if (info->IsMainThread()) {
+        aCollector.SetIsMainThread();
+      }
+
+      // Allocate the space for the native stack
+      NativeStack nativeStack;
+
+      // Suspend, sample, and then resume the target thread.
+      Sampler sampler(lock);
+      sampler.SuspendAndSampleAndResumeThread(lock, *info,
+                                              [&](const Registers& aRegs) {
+        // The target thread is now suspended. Collect a native backtrace, and
+        // call the callback.
+        bool isSynchronous = false;
+#if defined(HAVE_NATIVE_UNWIND)
+        if (aSampleNative) {
+          DoNativeBacktrace(lock, *info, aRegs, nativeStack);
+
+          MergeStacks(aFeatures, isSynchronous, *info, aRegs, nativeStack,
+                      aCollector);
+        } else
+#endif
+        {
+          MergeStacks(aFeatures, isSynchronous, *info, aRegs, nativeStack,
+                      aCollector);
+
+          if (ProfilerFeature::HasLeaf(aFeatures)) {
+            aCollector.CollectNativeLeafAddr((void*)aRegs.mPC);
+          }
+        }
+      });
+
+      // NOTE: Make sure to disable the sampler before it is destroyed, in case
+      // the profiler is running at the same time.
+      sampler.Disable(lock);
+      break;
+    }
+  }
+}
+
 // END externally visible functions
 ////////////////////////////////////////////////////////////////////////
--- a/tools/profiler/gecko/ThreadResponsiveness.cpp
+++ b/tools/profiler/gecko/ThreadResponsiveness.cpp
@@ -4,19 +4,23 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ThreadResponsiveness.h"
 #include "platform.h"
 #include "nsComponentManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "nsITimer.h"
 #include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SystemGroup.h"
 
 using mozilla::Mutex;
 using mozilla::MutexAutoLock;
+using mozilla::SystemGroup;
+using mozilla::TaskCategory;
 using mozilla::TimeStamp;
 
 class CheckResponsivenessTask : public mozilla::Runnable,
                                 public nsITimerCallback {
 public:
   CheckResponsivenessTask()
     : mozilla::Runnable("CheckResponsivenessTask")
     , mLastTracerTime(TimeStamp::Now())
@@ -40,17 +44,19 @@ public:
   void DoFirstDispatchIfNeeded()
   {
     if (mHasEverBeenSuccessfullyDispatched) {
       return;
     }
 
     // Dispatching can fail during early startup, particularly when
     // MOZ_PROFILER_STARTUP is used.
-    nsresult rv = NS_DispatchToMainThread(this);
+    nsresult rv = SystemGroup::Dispatch("CheckResponsivenessTask",
+                                        TaskCategory::Other,
+                                        do_AddRef(this));
     if (NS_SUCCEEDED(rv)) {
       mHasEverBeenSuccessfullyDispatched = true;
     }
   }
 
   // Can only run on the main thread.
   NS_IMETHOD Run() override
   {
@@ -61,25 +67,28 @@ public:
     // This is raced on because we might pause the thread here
     // for profiling so if we tried to use a monitor to protect
     // mLastTracerTime we could deadlock. We're risking seeing
     // a partial write which will show up as an outlier in our
     // performance data.
     mLastTracerTime = TimeStamp::Now();
     if (!mTimer) {
       mTimer = do_CreateInstance("@mozilla.org/timer;1");
+      mTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::Other));
     }
     mTimer->InitWithCallback(this, 16, nsITimer::TYPE_ONE_SHOT);
 
     return NS_OK;
   }
 
   NS_IMETHOD Notify(nsITimer* aTimer) final
   {
-    NS_DispatchToMainThread(this);
+    SystemGroup::Dispatch("CheckResponsivenessTask",
+                          TaskCategory::Other,
+                          do_AddRef(this));
     return NS_OK;
   }
 
   void Terminate() {
     MutexAutoLock mon(mMutex);
     mStop = true;
   }
 
--- a/tools/profiler/moz.build
+++ b/tools/profiler/moz.build
@@ -76,16 +76,17 @@ if CONFIG['MOZ_GECKO_PROFILER']:
             'core/shared-libraries-macos.cc',
         ]
     elif CONFIG['OS_TARGET'] == 'WINNT':
         SOURCES += [
             'core/shared-libraries-win32.cc',
         ]
 
     LOCAL_INCLUDES += [
+        '/caps',
         '/docshell/base',
         '/ipc/chromium/src',
         '/mozglue/linker',
         '/toolkit/crashreporter/google-breakpad/src',
         '/tools/profiler/core/',
         '/tools/profiler/gecko/',
         '/xpcom/base',
     ]
--- a/tools/profiler/public/GeckoProfiler.h
+++ b/tools/profiler/public/GeckoProfiler.h
@@ -20,16 +20,17 @@
 #include <signal.h>
 #include <stdarg.h>
 #include <stdint.h>
 #include <stdlib.h>
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/GuardObjects.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/ThreadLocal.h"
 #include "mozilla/UniquePtr.h"
 #include "js/TypeDecls.h"
 #include "js/ProfilingStack.h"
 #include "nscore.h"
 
 // Make sure that we can use std::min here without the Windows headers messing
@@ -272,21 +273,62 @@ PROFILER_FUNC(int profiler_current_threa
 typedef void ProfilerStackCallback(void** aPCs, size_t aCount, bool aIsMainThread);
 
 // This method suspends the thread identified by aThreadId, optionally samples
 // it for its native stack, and then calls the callback.
 //
 // WARNING: The target thread is suspended during the callback. Do not try to
 // allocate or acquire any locks, or you could deadlock. The target thread will
 // have resumed by the time this function returns.
+//
+// XXX: this function is in the process of being replaced with the other profiler_suspend_and_sample_thread() function.
 PROFILER_FUNC_VOID(
   profiler_suspend_and_sample_thread(int aThreadId,
                                      const std::function<ProfilerStackCallback>& aCallback,
                                      bool aSampleNative = true))
 
+// An object of this class is passed to profiler_suspend_and_sample_thread().
+// For each stack frame, one of the Collect methods will be called.
+class ProfilerStackCollector
+{
+public:
+  // Some collectors need to worry about possibly overwriting previous
+  // generations of data. If that's not an issue, this can return Nothing,
+  // which is the default behaviour.
+  virtual mozilla::Maybe<uint32_t> Generation() { return mozilla::Nothing(); }
+
+  // This method will be called once if the thread being suspended is the main
+  // thread. Default behaviour is to do nothing.
+  virtual void SetIsMainThread() {}
+
+  // WARNING: The target thread is suspended when the Collect methods are
+  // called. Do not try to allocate or acquire any locks, or you could
+  // deadlock. The target thread will have resumed by the time this function
+  // returns.
+
+  virtual void CollectNativeLeafAddr(void* aAddr) = 0;
+
+  virtual void CollectJitReturnAddr(void* aAddr) = 0;
+
+  // aLabel is static and never null. aStr may be null. aLineNumber may be -1.
+  virtual void CollectCodeLocation(
+    const char* aLabel, const char* aStr, int aLineNumber,
+    const mozilla::Maybe<js::ProfileEntry::Category>& aCategory) = 0;
+};
+
+// This method suspends the thread identified by aThreadId, samples its
+// pseudo-stack, JS stack, and (optionally) native stack, passing the collected
+// frames into aCollector. aFeatures dictates which compiler features are used.
+// |Privacy| and |Leaf| are the only relevant ones.
+PROFILER_FUNC_VOID(
+  profiler_suspend_and_sample_thread(int aThreadId,
+                                     uint32_t aFeatures,
+                                     ProfilerStackCollector& aCollector,
+                                     bool aSampleNative = true))
+
 struct ProfilerBacktraceDestructor
 {
 #ifdef MOZ_GECKO_PROFILER
   void operator()(ProfilerBacktrace*);
 #else
   void operator()(ProfilerBacktrace*) {}
 #endif
 };
--- a/tools/profiler/tests/gtest/GeckoProfiler.cpp
+++ b/tools/profiler/tests/gtest/GeckoProfiler.cpp
@@ -732,8 +732,75 @@ TEST(GeckoProfiler, Bug1355807)
 
   // In bug 1355807 this caused an assertion failure in StopJSSampling().
   profiler_start(PROFILER_DEFAULT_ENTRIES, PROFILER_DEFAULT_INTERVAL,
                  features,
                  fewThreadsFilter, MOZ_ARRAY_LENGTH(fewThreadsFilter));
 
   profiler_stop();
 }
+
+class GTestStackCollector final : public ProfilerStackCollector
+{
+public:
+  GTestStackCollector()
+    : mSetIsMainThread(0)
+    , mFrames(0)
+  {}
+
+  virtual void SetIsMainThread() { mSetIsMainThread++; }
+
+  virtual void CollectNativeLeafAddr(void* aAddr) { mFrames++; }
+  virtual void CollectJitReturnAddr(void* aAddr) { mFrames++; }
+  virtual void CollectCodeLocation(
+    const char* aLabel, const char* aStr, int aLineNumber,
+    const mozilla::Maybe<js::ProfileEntry::Category>& aCategory) { mFrames++; }
+
+  int mSetIsMainThread;
+  int mFrames;
+};
+
+void DoSuspendAndSample(int aTid, nsIThread* aThread)
+{
+  aThread->Dispatch(
+    NS_NewRunnableFunction(
+      "GeckoProfiler_SuspendAndSample_Test::TestBody",
+      [&]() {
+        uint32_t features = ProfilerFeature::Leaf;
+        GTestStackCollector collector;
+        profiler_suspend_and_sample_thread(aTid, features, collector,
+                                           /* sampleNative = */ true);
+
+        ASSERT_TRUE(collector.mSetIsMainThread == 1);
+        ASSERT_TRUE(collector.mFrames > 5); // approximate; must be > 0
+      }),
+    NS_DISPATCH_SYNC);
+}
+
+TEST(GeckoProfiler, SuspendAndSample)
+{
+  nsCOMPtr<nsIThread> thread;
+  nsresult rv = NS_NewNamedThread("GeckoProfGTest", getter_AddRefs(thread));
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+  int tid = Thread::GetCurrentId();
+
+  ASSERT_TRUE(!profiler_is_active());
+
+  // Suspend and sample while the profiler is inactive.
+  DoSuspendAndSample(tid, thread);
+
+  uint32_t features = ProfilerFeature::JS | ProfilerFeature::Threads;
+  const char* filters[] = { "GeckoMain", "Compositor" };
+
+  profiler_start(PROFILER_DEFAULT_ENTRIES, PROFILER_DEFAULT_INTERVAL,
+                 features, filters, MOZ_ARRAY_LENGTH(filters));
+
+  ASSERT_TRUE(profiler_is_active());
+
+  // Suspend and sample while the profiler is active.
+  DoSuspendAndSample(tid, thread);
+
+  profiler_stop();
+
+  ASSERT_TRUE(!profiler_is_active());
+}
+
--- a/widget/headless/HeadlessWidget.cpp
+++ b/widget/headless/HeadlessWidget.cpp
@@ -19,17 +19,20 @@ nsIWidget::CreateHeadlessWidget()
 }
 
 namespace mozilla {
 namespace widget {
 
 already_AddRefed<gfxContext>
 CreateDefaultTarget(IntSize aSize)
 {
-  RefPtr<DrawTarget> target = Factory::CreateDrawTarget(gfxVars::ContentBackend(), aSize, SurfaceFormat::B8G8R8A8);
+  // Always use at least a 1x1 draw target to avoid gfx issues
+  // with 0x0 targets.
+  IntSize size = (aSize.width <= 0 || aSize.height <= 0) ? gfx::IntSize(1, 1) : aSize;
+  RefPtr<DrawTarget> target = Factory::CreateDrawTarget(gfxVars::ContentBackend(), size, SurfaceFormat::B8G8R8A8);
   RefPtr<gfxContext> ctx = gfxContext::CreatePreservingTransformOrNull(target);
   return ctx.forget();
 }
 
 NS_IMPL_ISUPPORTS_INHERITED0(HeadlessWidget, nsBaseWidget)
 
 nsresult
 HeadlessWidget::Create(nsIWidget* aParent,