Merge mozilla-central to mozilla-inbound. a=merge on a CLOSED TREE
authorRazvan Maries <rmaries@mozilla.com>
Thu, 29 Nov 2018 23:46:52 +0200
changeset 505273 da158202751e9481710c64264d5f0e9161f6eb36
parent 505272 11c709d6c6d965459d14cbf20d52bc4fabccf391 (current diff)
parent 505202 6453222232be364fb8ce3fd29b6cbcd480e5f2e3 (diff)
child 505274 efac27a2bec4fa7e81fa3c8a6d023795aa6447dc
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound. a=merge on a CLOSED TREE
--- a/browser/base/content/test/plugins/browser_CTP_drag_drop.js
+++ b/browser/base/content/test/plugins/browser_CTP_drag_drop.js
@@ -1,19 +1,20 @@
 var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
 var gNewWindow = null;
 
 add_task(async function() {
-  registerCleanupFunction(function() {
+  registerCleanupFunction(async function() {
     clearAllPluginPermissions();
     setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
     setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
     Services.prefs.clearUserPref("plugins.click_to_play");
     Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
     gNewWindow.close();
+    await BrowserTestUtils.waitForEvent(gNewWindow, "unload", true);
     gNewWindow = null;
     window.focus();
   });
 });
 
 add_task(async function() {
   Services.prefs.setBoolPref("plugins.click_to_play", true);
   Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_theme_icons.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_theme_icons.js
@@ -1,20 +1,20 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 const LIGHT_THEME_COLORS = {
-  "accentcolor": "#FFF",
-  "textcolor": "#000",
+  "frame": "#FFF",
+  "tab_background_text": "#000",
 };
 
 const DARK_THEME_COLORS = {
-  "accentcolor": "#000",
-  "textcolor": "#FFF",
+  "frame": "#000",
+  "tab_background_text": "#FFF",
 };
 
 const TOOLBAR_MAPPING = {
   "navbar": "nav-bar",
   "tabstrip": "TabsToolbar",
 };
 
 async function testBrowserAction(extension, expectedIcon) {
@@ -137,20 +137,20 @@ add_task(async function browseraction_th
       "size": 32,
     }],
     withDefaultIcon: false,
   });
 });
 
 add_task(async function browseraction_theme_icons_different_toolbars() {
   let themeData = {
-    "accentcolor": "#000",
-    "textcolor": "#fff",
+    "frame": "#000",
+    "tab_background_text": "#fff",
     "toolbar": "#fff",
-    "toolbar_text": "#000",
+    "bookmark_text": "#000",
   };
   await testStaticTheme({
     themeData,
     expectedIcon: "dark",
     themeIcons: [{
       "light": "light.png",
       "dark": "dark.png",
       "size": 19,
--- a/dom/media/AudioCaptureStream.cpp
+++ b/dom/media/AudioCaptureStream.cpp
@@ -103,19 +103,16 @@ void AudioCaptureStream::ProcessInput(Gr
           }
         }
         toMix.Mix(mMixer, MONO, Graph()->GraphRate());
       }
     }
     // This calls MixerCallback below
     mMixer.FinishMixing();
   }
-
-  // Regardless of the status of the input tracks, we go foward.
-  mTracks.AdvanceKnownTracksTime(GraphTimeToStreamTimeWithBlocking((aTo)));
 }
 
 void AudioCaptureStream::MixerCallback(AudioDataValue* aMixedBuffer,
                                        AudioSampleFormat aFormat,
                                        uint32_t aChannels, uint32_t aFrames,
                                        uint32_t aSampleRate) {
   AutoTArray<nsTArray<AudioDataValue>, MONO> output;
   AutoTArray<const AudioDataValue*, MONO> bufferPtrs;
--- a/dom/media/CanvasCaptureMediaStream.cpp
+++ b/dom/media/CanvasCaptureMediaStream.cpp
@@ -110,18 +110,17 @@ OutputStreamDriver::OutputStreamDriver(S
     : FrameCaptureListener(),
       mSourceStream(aSourceStream),
       mTrackListener(
           new TrackListener(aTrackId, aPrincipalHandle, aSourceStream)) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mSourceStream);
   mSourceStream->AddTrack(aTrackId, new VideoSegment());
   mSourceStream->AddTrackListener(mTrackListener, aTrackId);
-  mSourceStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
-  mSourceStream->SetPullEnabled(true);
+  mSourceStream->SetPullingEnabled(aTrackId, true);
 
   // All CanvasCaptureMediaStreams shall at least get one frame.
   mFrameCaptureRequested = true;
 }
 
 OutputStreamDriver::~OutputStreamDriver() {
   MOZ_ASSERT(NS_IsMainThread());
   // MediaStreamGraph will keep the listener alive until it can end the track in
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -4189,44 +4189,50 @@ RefPtr<SourceListener::InitPromise> Sour
                 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError, log),
                 __func__);
             return;
           }
         }
 
         // Start() queued the tracks to be added synchronously to avoid races
         stream->FinishAddTracks();
-        stream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
         LOG(("started all sources"));
 
         aHolder.Resolve(true, __func__);
       });
 
   return init->Then(
       GetMainThreadSerialEventTarget(), __func__,
       [self = RefPtr<SourceListener>(this), this]() {
         if (mStopped) {
           // We were shut down during the async init
           return InitPromise::CreateAndResolve(true, __func__);
         }
 
-        mStream->SetPullEnabled(true);
-
         for (DeviceState* state :
              {mAudioDeviceState.get(), mVideoDeviceState.get()}) {
           if (!state) {
             continue;
           }
           MOZ_DIAGNOSTIC_ASSERT(!state->mTrackEnabled);
           MOZ_DIAGNOSTIC_ASSERT(!state->mDeviceEnabled);
           MOZ_DIAGNOSTIC_ASSERT(!state->mStopped);
 
           state->mDeviceEnabled = true;
           state->mTrackEnabled = true;
           state->mTrackEnabledTime = TimeStamp::Now();
+
+          if (state->mDevice->GetMediaSource() !=
+              MediaSourceEnum::AudioCapture) {
+            // For AudioCapture mStream is a dummy stream, so we don't try to
+            // enable pulling - there won't be a track to enable it for.
+            mStream->SetPullingEnabled(
+                state == mAudioDeviceState.get() ? kAudioTrack : kVideoTrack,
+                true);
+          }
         }
         return InitPromise::CreateAndResolve(true, __func__);
       },
       [self = RefPtr<SourceListener>(this),
        this](RefPtr<MediaMgrError>&& aResult) {
         if (mStopped) {
           return InitPromise::CreateAndReject(std::move(aResult), __func__);
         }
@@ -4291,21 +4297,22 @@ void SourceListener::Remove() {
   mWindowListener = nullptr;
 
   // If it's destroyed, don't call - listener will be removed and we'll be
   // notified!
   if (!mStream->IsDestroyed()) {
     // We disable pulling before removing so we don't risk having live tracks
     // without a listener attached - that wouldn't produce data and would be
     // illegal to the graph.
-    mStream->SetPullEnabled(false);
     if (mAudioDeviceState) {
+      mStream->SetPullingEnabled(kAudioTrack, false);
       mStream->RemoveTrackListener(mAudioDeviceState->mListener, kAudioTrack);
     }
     if (mVideoDeviceState) {
+      mStream->SetPullingEnabled(kVideoTrack, false);
       mStream->RemoveTrackListener(mVideoDeviceState->mListener, kVideoTrack);
     }
   }
 
   if (mAudioDeviceState) {
     mAudioDeviceState->mListener = nullptr;
   }
   if (mVideoDeviceState) {
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -191,17 +191,17 @@ void MediaStreamGraphImpl::UpdateCurrent
         }
       }
     }
 
     // The stream is fully finished when all of its track data has been played
     // out.
     if (stream->mFinished && !stream->mNotifiedFinished &&
         mProcessedTime >= stream->StreamTimeToGraphTime(
-                              stream->GetStreamTracks().GetAllTracksEnd())) {
+                              stream->GetStreamTracks().GetLatestTrackEnd())) {
       stream->mNotifiedFinished = true;
       SetStreamOrderDirty();
     }
   }
 }
 
 template <typename C, typename Chunk>
 void MediaStreamGraphImpl::ProcessChunkMetadataForInterval(MediaStream* aStream,
@@ -1195,17 +1195,17 @@ void MediaStreamGraphImpl::UpdateGraph(G
     if (SourceMediaStream* is = stream->AsSourceStream()) {
       ensureNextIteration |= is->PullNewData(aEndBlockingDecisions);
       is->ExtractPendingInput(mStateComputedTime);
     }
     if (stream->mFinished) {
       // The stream's not suspended, and since it's finished, underruns won't
       // stop it playing out. So there's no blocking other than what we impose
       // here.
-      GraphTime endTime = stream->GetStreamTracks().GetAllTracksEnd() +
+      GraphTime endTime = stream->GetStreamTracks().GetLatestTrackEnd() +
                           stream->mTracksStartTime;
       if (endTime <= mStateComputedTime) {
         LOG(LogLevel::Verbose,
             ("%p: MediaStream %p is blocked due to being finished", this,
              stream));
         stream->mStartBlocking = mStateComputedTime;
       } else {
         LOG(LogLevel::Verbose,
@@ -1216,22 +1216,31 @@ void MediaStreamGraphImpl::UpdateGraph(G
              MediaTimeToSeconds(endTime)));
         // Data can't be added to a finished stream, so underruns are
         // irrelevant.
         stream->mStartBlocking = std::min(endTime, aEndBlockingDecisions);
       }
     } else {
       stream->mStartBlocking = WillUnderrun(stream, aEndBlockingDecisions);
 
-      SourceMediaStream* s = stream->AsSourceStream();
-      if (s && s->mPullEnabled) {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+      if (SourceMediaStream* s = stream->AsSourceStream()) {
         for (StreamTracks::TrackIter i(s->mTracks); !i.IsEnded(); i.Next()) {
           if (i->IsEnded()) {
             continue;
           }
+          SourceMediaStream::TrackData* data;
+          {
+            MutexAutoLock lock(s->mMutex);
+            data = s->FindDataForTrack(i->GetID());
+          }
+          MOZ_ASSERT(data);
+          if (!data->mPullingEnabled) {
+            continue;
+          }
           if (i->GetEnd() <
               stream->GraphTimeToStreamTime(aEndBlockingDecisions)) {
             LOG(LogLevel::Error,
                 ("%p: SourceMediaStream %p track %u (%s) is live and pulled, "
                  "but wasn't fed "
                  "enough data. TrackListeners=%zu. Track-end=%f, "
                  "Iteration-end=%f",
                  this, stream, i->GetID(),
@@ -1241,16 +1250,17 @@ void MediaStreamGraphImpl::UpdateGraph(G
                  MediaTimeToSeconds(
                      stream->GraphTimeToStreamTime(aEndBlockingDecisions))));
             MOZ_DIAGNOSTIC_ASSERT(false,
                                   "A non-finished SourceMediaStream wasn't fed "
                                   "enough data by NotifyPull");
           }
         }
       }
+#endif /* MOZ_DIAGNOSTIC_ASSERT_ENABLED */
     }
   }
 
   for (MediaStream* stream : mSuspendedStreams) {
     stream->mStartBlocking = mStateComputedTime;
   }
 
   // If the loop is woken up so soon that IterationEnd() barely advances or
@@ -1302,17 +1312,17 @@ void MediaStreamGraphImpl::Process() {
           // Since an AudioNodeStream is present, go ahead and
           // produce audio block by block for all the rest of the streams.
           ProduceDataForStreamsBlockByBlock(i, n->SampleRate());
           doneAllProducing = true;
         } else {
           ps->ProcessInput(mProcessedTime, mStateComputedTime,
                            ProcessedMediaStream::ALLOW_FINISH);
           NS_ASSERTION(
-              stream->mTracks.GetEnd() >=
+              stream->mTracks.GetEarliestTrackEnd() >=
                   GraphTimeToStreamTimeWithBlocking(stream, mStateComputedTime),
               "Stream did not produce enough data");
         }
       }
     }
     // Only playback audio and video in real-time mode
     if (mRealtime) {
       CreateOrDestroyAudioStreams(stream);
@@ -1959,17 +1969,16 @@ void MediaStream::FinishOnGraphThread() 
             ("MediaStream %p will finish, but track %d has not ended.", this,
              track->GetID()));
         NS_ASSERTION(false, "Finished stream cannot contain live track");
       }
     }
   }
 #endif
   mFinished = true;
-  mTracks.AdvanceKnownTracksTime(STREAM_TIME_MAX);
 
   // Let the MSG knows that this stream can be destroyed if necessary to avoid
   // unnecessarily processing it in the future.
   GraphImpl()->SetStreamOrderDirty();
 }
 
 StreamTracks::Track* MediaStream::FindTrack(TrackID aID) const {
   return mTracks.FindTrack(aID);
@@ -2455,18 +2464,16 @@ void MediaStream::AddMainThreadListener(
 
   nsCOMPtr<nsIRunnable> runnable = new NotifyRunnable(this);
   GraphImpl()->Dispatch(runnable.forget());
 }
 
 SourceMediaStream::SourceMediaStream()
     : MediaStream(),
       mMutex("mozilla::media::SourceMediaStream"),
-      mUpdateKnownTracksTime(0),
-      mPullEnabled(false),
       mFinishPending(false) {}
 
 nsresult SourceMediaStream::OpenAudioInput(CubebUtils::AudioDeviceID aID,
                                            AudioDataListener* aListener) {
   MOZ_ASSERT(GraphImpl());
   mInputListener = aListener;
   return GraphImpl()->OpenAudioInput(aID, aListener);
 }
@@ -2493,49 +2500,63 @@ void SourceMediaStream::DestroyImpl() {
   }
 
   // Hold mMutex while mGraph is reset so that other threads holding mMutex
   // can null-check know that the graph will not destroyed.
   MutexAutoLock lock(mMutex);
   MediaStream::DestroyImpl();
 }
 
-void SourceMediaStream::SetPullEnabled(bool aEnabled) {
+void SourceMediaStream::SetPullingEnabled(TrackID aTrackID, bool aEnabled) {
   class Message : public ControlMessage {
    public:
-    Message(SourceMediaStream* aStream, bool aEnabled)
-        : ControlMessage(nullptr), mStream(aStream), mEnabled(aEnabled) {}
+    Message(SourceMediaStream* aStream, TrackID aTrackID, bool aEnabled)
+        : ControlMessage(nullptr),
+          mStream(aStream),
+          mTrackID(aTrackID),
+          mEnabled(aEnabled) {}
     void Run() override {
       MutexAutoLock lock(mStream->mMutex);
-      mStream->mPullEnabled = mEnabled;
+      TrackData* data = mStream->FindDataForTrack(mTrackID);
+      if (!data) {
+        // We can't enable pulling for a track that was never added. We ignore
+        // this if we're disabling pulling, since shutdown sequences are
+        // complex. If there's truly an issue we'll have issues enabling anyway.
+        MOZ_ASSERT_IF(mEnabled,
+                      mStream->mTracks.FindTrack(mTrackID) &&
+                          mStream->mTracks.FindTrack(mTrackID)->IsEnded());
+        return;
+      }
+      data->mPullingEnabled = mEnabled;
     }
     SourceMediaStream* mStream;
+    TrackID mTrackID;
     bool mEnabled;
   };
-  GraphImpl()->AppendMessage(MakeUnique<Message>(this, aEnabled));
+  GraphImpl()->AppendMessage(MakeUnique<Message>(this, aTrackID, aEnabled));
 }
 
 bool SourceMediaStream::PullNewData(GraphTime aDesiredUpToTime) {
   TRACE_AUDIO_CALLBACK_COMMENT("SourceMediaStream %p", this);
   MutexAutoLock lock(mMutex);
-  if (!mPullEnabled || mFinished) {
+  if (mFinished) {
     return false;
   }
   // Compute how much stream time we'll need assuming we don't block
   // the stream at all.
   StreamTime t = GraphTimeToStreamTime(aDesiredUpToTime);
-  StreamTime current = mTracks.GetEnd();
+  StreamTime current = mTracks.GetEarliestTrackEnd();
   LOG(LogLevel::Verbose,
       ("%p: Calling NotifyPull aStream=%p t=%f current end=%f", GraphImpl(),
        this, GraphImpl()->MediaTimeToSeconds(t),
        GraphImpl()->MediaTimeToSeconds(current)));
-  if (t <= current) {
-    return false;
-  }
   for (const TrackData& track : mUpdateTracks) {
+    if (!track.mPullingEnabled) {
+      continue;
+    }
     if (track.mCommands & TrackEventCommand::TRACK_EVENT_ENDED) {
       continue;
     }
     current = track.mEndOfFlushedData + track.mData->GetDuration();
     if (t <= current) {
       continue;
     }
     MutexAutoUnlock unlock(mMutex);
@@ -2597,21 +2618,18 @@ void SourceMediaStream::ExtractPendingIn
       data->mEndOfFlushedData += data->mData->GetDuration();
       dest->AppendFrom(data->mData);
     }
     if (data->mCommands & SourceMediaStream::TRACK_END) {
       mTracks.FindTrack(data->mID)->SetEnded();
       mUpdateTracks.RemoveElementAt(i);
     }
   }
-  if (!mFinished) {
-    mTracks.AdvanceKnownTracksTime(mUpdateKnownTracksTime);
-  }
-
-  if (mTracks.GetEnd() > 0) {
+
+  if (mTracks.GetEarliestTrackEnd() > 0) {
     mHasCurrentData = true;
   }
 
   if (finished) {
     FinishOnGraphThread();
   }
 }
 
@@ -2626,16 +2644,17 @@ void SourceMediaStream::AddTrackInternal
       ("%p: AddTrackInternal: %lu/%lu", GraphImpl(),
        (long)mPendingTracks.Length(), (long)mUpdateTracks.Length()));
   data->mID = aID;
   data->mInputRate = aRate;
   data->mResamplerChannelCount = 0;
   data->mEndOfFlushedData = 0;
   data->mCommands = TRACK_CREATE;
   data->mData = aSegment;
+  data->mPullingEnabled = false;
   ResampleAudioToGraphSampleRate(data, aSegment);
   if (!(aFlags & ADDTRACK_QUEUED) && GraphImpl()) {
     GraphImpl()->EnsureNextIteration();
   }
 }
 
 void SourceMediaStream::AddAudioTrack(TrackID aID, TrackRate aRate,
                                       AudioSegment* aSegment, uint32_t aFlags) {
@@ -2841,25 +2860,16 @@ void SourceMediaStream::EndTrack(TrackID
   if (track) {
     track->mCommands |= TrackEventCommand::TRACK_EVENT_ENDED;
   }
   if (auto graph = GraphImpl()) {
     graph->EnsureNextIteration();
   }
 }
 
-void SourceMediaStream::AdvanceKnownTracksTime(StreamTime aKnownTime) {
-  MutexAutoLock lock(mMutex);
-  MOZ_ASSERT(aKnownTime >= mUpdateKnownTracksTime);
-  mUpdateKnownTracksTime = aKnownTime;
-  if (auto graph = GraphImpl()) {
-    graph->EnsureNextIteration();
-  }
-}
-
 void SourceMediaStream::FinishPendingWithLockHeld() {
   mMutex.AssertCurrentThreadOwns();
   mFinishPending = true;
   if (auto graph = GraphImpl()) {
     graph->EnsureNextIteration();
   }
 }
 
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -415,17 +415,17 @@ class MediaStream : public mozilla::Link
 
   // These Impl methods perform the core functionality of the control methods
   // above, on the media graph thread.
   /**
    * Stop all stream activity and disconnect it from all inputs and outputs.
    * This must be idempotent.
    */
   virtual void DestroyImpl();
-  StreamTime GetTracksEnd() const { return mTracks.GetEnd(); }
+  StreamTime GetTracksEnd() const { return mTracks.GetEarliestTrackEnd(); }
 #ifdef DEBUG
   void DumpTrackInfo() const { return mTracks.DumpTrackInfo(); }
 #endif
   void SetAudioOutputVolumeImpl(void* aKey, float aVolume);
   void AddAudioOutputImpl(void* aKey);
   void RemoveAudioOutputImpl(void* aKey);
   void AddVideoOutputImpl(already_AddRefed<MediaStreamVideoSink> aSink,
                           TrackID aID);
@@ -653,23 +653,24 @@ class SourceMediaStream : public MediaSt
  public:
   explicit SourceMediaStream();
 
   SourceMediaStream* AsSourceStream() override { return this; }
 
   // Main thread only
 
   /**
-   * Enable or disable pulling. When pulling is enabled, NotifyPull
-   * gets called on MediaStream/TrackListeners for this stream during the
-   * MediaStreamGraph control loop. Pulling is initially disabled.
-   * Due to unavoidable race conditions, after a call to SetPullEnabled(false)
+   * Enable or disable pulling for a specific track.
+   * When pulling is enabled, NotifyPull gets called on the corresponding
+   * MediaStreamTrackListeners for this stream during the MediaStreamGraph
+   * control loop. Pulling is initially disabled for all tracks. Due to
+   * unavoidable race conditions, after a call to SetPullingEnabled(false)
    * it is still possible for a NotifyPull to occur.
    */
-  void SetPullEnabled(bool aEnabled);
+  void SetPullingEnabled(TrackID aTrackID, bool aEnabled);
 
   // Users of audio inputs go through the stream so it can track when the
   // last stream referencing an input goes away, so it can close the cubeb
   // input.  Also note: callable on any thread (though it bounces through
   // MainThread to set the command if needed).
   nsresult OpenAudioInput(CubebUtils::AudioDeviceID aID,
                           AudioDataListener* aListener);
   // Note: also implied when Destroy() happens
@@ -693,19 +694,18 @@ class SourceMediaStream : public MediaSt
    * Extract any state updates pending in the stream, and apply them.
    */
   void ExtractPendingInput(GraphTime aCurrentTime);
 
   enum {
     ADDTRACK_QUEUED = 0x01  // Queue track add until FinishAddTracks()
   };
   /**
-   * Add a new track to the stream starting at the stream's current time
-   * (which must be greater than or equal to the last time passed to
-   * AdvanceKnownTracksTime). Takes ownership of aSegment.
+   * Add a new track to the stream starting at the stream's current time.
+   * Takes ownership of aSegment.
    */
   void AddTrack(TrackID aID, MediaSegment* aSegment, uint32_t aFlags = 0) {
     AddTrackInternal(aID, GraphRate(), aSegment, aFlags);
   }
 
   /**
    * Like AddTrack, but resamples audio from aRate to the graph rate.
    */
@@ -735,22 +735,16 @@ class SourceMediaStream : public MediaSt
   StreamTime GetEndOfAppendedData(TrackID aID);
   /**
    * Indicate that a track has ended. Do not do any more API calls
    * affecting this track.
    * Ignored if the track does not exist.
    */
   void EndTrack(TrackID aID);
   /**
-   * Indicate that no tracks will be added starting before time aKnownTime.
-   * aKnownTime must be >= its value at the last call to AdvanceKnownTracksTime.
-   */
-  void AdvanceKnownTracksTime(StreamTime aKnownTime);
-  void AdvanceKnownTracksTimeWithLockHeld(StreamTime aKnownTime);
-  /**
    * Indicate that this stream should enter the "finished" state. All tracks
    * must have been ended via EndTrack. The finish time of the stream is
    * when all tracks have ended.
    */
   void FinishPendingWithLockHeld();
   void FinishPending() {
     MutexAutoLock lock(mMutex);
     FinishPendingWithLockHeld();
@@ -811,16 +805,18 @@ class SourceMediaStream : public MediaSt
     // End-time of data already flushed to the track (excluding mData)
     StreamTime mEndOfFlushedData;
     // Each time the track updates are flushed to the media graph thread,
     // the segment buffer is emptied.
     nsAutoPtr<MediaSegment> mData;
     // Each time the track updates are flushed to the media graph thread,
     // this is cleared.
     uint32_t mCommands;
+    // True if the producer of this track is having data pulled by the graph.
+    bool mPullingEnabled;
   };
 
   bool NeedsMixing();
 
   void ResampleAudioToGraphSampleRate(TrackData* aTrackData,
                                       MediaSegment* aSegment);
 
   void AddDirectTrackListenerImpl(
@@ -862,25 +858,23 @@ class SourceMediaStream : public MediaSt
   // XXX Should really be a CubebUtils::AudioDeviceID, but they aren't
   // copyable (opaque pointers)
   RefPtr<AudioDataListener> mInputListener;
 
   // This must be acquired *before* MediaStreamGraphImpl's lock, if they are
   // held together.
   Mutex mMutex;
   // protected by mMutex
-  StreamTime mUpdateKnownTracksTime;
   // This time stamp will be updated in adding and blocked SourceMediaStream,
   // |AddStreamGraphThread| and |AdvanceTimeVaryingValuesToCurrentTime| in
   // particularly.
   TimeStamp mStreamTracksStartTimeStamp;
   nsTArray<TrackData> mUpdateTracks;
   nsTArray<TrackData> mPendingTracks;
   nsTArray<TrackBound<DirectMediaStreamTrackListener>> mDirectTrackListeners;
-  bool mPullEnabled;
   bool mFinishPending;
 };
 
 /**
  * The blocking mode decides how a track should be blocked in a MediaInputPort.
  */
 enum class BlockingMode {
   /**
--- a/dom/media/StreamTracks.cpp
+++ b/dom/media/StreamTracks.cpp
@@ -10,46 +10,41 @@
 namespace mozilla {
 
 #ifdef DEBUG
 
 extern LazyLogModule gMediaStreamGraphLog;
 #define STREAM_LOG(type, msg) MOZ_LOG(gMediaStreamGraphLog, type, msg)
 
 void StreamTracks::DumpTrackInfo() const {
-  STREAM_LOG(LogLevel::Info,
-             ("DumpTracks: mTracksKnownTime %" PRId64, mTracksKnownTime));
+  STREAM_LOG(LogLevel::Info, ("Dumping StreamTracks"));
   for (uint32_t i = 0; i < mTracks.Length(); ++i) {
     Track* track = mTracks[i];
     if (track->IsEnded()) {
       STREAM_LOG(LogLevel::Info, ("Track[%d] %d: ended", i, track->GetID()));
     } else {
       STREAM_LOG(LogLevel::Info, ("Track[%d] %d: %" PRId64 "", i,
                                   track->GetID(), track->GetEnd()));
     }
   }
 }
 #endif
 
-StreamTime StreamTracks::GetEnd() const {
-  StreamTime t = mTracksKnownTime;
+StreamTime StreamTracks::GetEarliestTrackEnd() const {
+  StreamTime t = STREAM_TIME_MAX;
   for (uint32_t i = 0; i < mTracks.Length(); ++i) {
     Track* track = mTracks[i];
     if (!track->IsEnded()) {
       t = std::min(t, track->GetEnd());
     }
   }
   return t;
 }
 
-StreamTime StreamTracks::GetAllTracksEnd() const {
-  if (mTracksKnownTime < STREAM_TIME_MAX) {
-    // A track might be added.
-    return STREAM_TIME_MAX;
-  }
+StreamTime StreamTracks::GetLatestTrackEnd() const {
   StreamTime t = 0;
   for (uint32_t i = 0; i < mTracks.Length(); ++i) {
     Track* track = mTracks[i];
     if (!track->IsEnded()) {
       return STREAM_TIME_MAX;
     }
     t = std::max(t, track->GetEnd());
   }
--- a/dom/media/StreamTracks.h
+++ b/dom/media/StreamTracks.h
@@ -139,17 +139,16 @@ class StreamTracks {
     }
     bool LessThan(Track* aA, Track* aB) const {
       return aA->GetID() < aB->GetID();
     }
   };
 
   StreamTracks()
       : mGraphRate(0),
-        mTracksKnownTime(0),
         mForgottenTime(0),
         mTracksDirty(false)
 #ifdef DEBUG
         ,
         mGraphRateIsSet(false)
 #endif
   {
     MOZ_COUNT_CTOR(StreamTracks);
@@ -189,45 +188,31 @@ class StreamTracks {
    */
   Track& AddTrack(TrackID aID, StreamTime aStart, MediaSegment* aSegment) {
     NS_ASSERTION(!FindTrack(aID), "Track with this ID already exists");
 
     Track* track = new Track(aID, aStart, aSegment);
     mTracks.InsertElementSorted(track, CompareTracksByID());
     mTracksDirty = true;
 
-    if (mTracksKnownTime == STREAM_TIME_MAX) {
-      // There exists code like
-      // http://mxr.mozilla.org/mozilla-central/source/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp?rev=96b197deb91e&mark=1292-1297#1292
-      NS_WARNING(
-          "Adding track to StreamTracks that should have no more tracks");
-    } else {
-      // NS_ASSERTION(mTracksKnownTime <= aStart, "Start time too early");
-    }
     return *track;
   }
 
-  void AdvanceKnownTracksTime(StreamTime aKnownTime) {
-    NS_ASSERTION(aKnownTime >= mTracksKnownTime,
-                 "Can't move tracks-known time earlier");
-    mTracksKnownTime = aKnownTime;
-  }
-
   /**
    * The end time for the StreamTracks is the latest time for which we have
    * data for all tracks that haven't ended by that time.
    */
-  StreamTime GetEnd() const;
+  StreamTime GetEarliestTrackEnd() const;
 
   /**
-   * Returns the earliest time >= 0 at which all tracks have ended
-   * and all their data has been played out and no new tracks can be added,
-   * or STREAM_TIME_MAX if there is no such time.
+   * Returns the earliest time >= 0 at which all tracks have ended and all
+   * their data has been played out, or STREAM_TIME_MAX if there is no such
+   * time.
    */
-  StreamTime GetAllTracksEnd() const;
+  StreamTime GetLatestTrackEnd() const;
 
 #ifdef DEBUG
   void DumpTrackInfo() const;
 #endif
 
   Track* FindTrack(TrackID aID) const;
 
   class MOZ_STACK_CLASS TrackIter final {
@@ -294,19 +279,16 @@ class StreamTracks {
     }
 
     mTracksDirty = false;
     return true;
   }
 
  protected:
   TrackRate mGraphRate;  // StreamTime per second
-  // Any new tracks added will start at or after this time. In other words, the
-  // track list is complete and correct for all times less than this time.
-  StreamTime mTracksKnownTime;
   StreamTime mForgottenTime;
 
  private:
   // All known tracks for this StreamTracks
   nsTArray<nsAutoPtr<Track>> mTracks;
   bool mTracksDirty;
 
 #ifdef DEBUG
--- a/dom/media/TrackUnionStream.cpp
+++ b/dom/media/TrackUnionStream.cpp
@@ -138,18 +138,16 @@ void TrackUnionStream::ProcessInput(Grap
       mTrackMap.RemoveElementAt(i);
     }
   }
   if (allFinished && mAutofinish && (aFlags & ALLOW_FINISH)) {
     // All streams have finished and won't add any more tracks, and
     // all our tracks have actually finished and been removed from our map,
     // so we're finished now.
     FinishOnGraphThread();
-  } else {
-    mTracks.AdvanceKnownTracksTime(GraphTimeToStreamTimeWithBlocking(aTo));
   }
   if (allHaveCurrentData) {
     // We can make progress if we're not blocked
     mHasCurrentData = true;
   }
 }
 
 uint32_t TrackUnionStream::AddTrack(MediaInputPort* aPort,
--- a/dom/media/mediasink/DecodedStream.cpp
+++ b/dom/media/mediasink/DecodedStream.cpp
@@ -90,18 +90,17 @@ class DecodedStreamGraphListener {
 
   void NotifyOutput(const RefPtr<SourceMediaStream>& aStream, TrackID aTrackID,
                     StreamTime aCurrentTrackTime) {
     if (aTrackID != mAudioTrackID && mAudioTrackID != TRACK_NONE) {
       // Only audio playout drives the clock forward, if present.
       return;
     }
     if (aStream) {
-      int64_t t = aStream->StreamTimeToMicroseconds(aCurrentTrackTime);
-      mOnOutput.Notify(t);
+      mOnOutput.Notify(aStream->StreamTimeToMicroseconds(aCurrentTrackTime));
     }
   }
 
   TrackID AudioTrackID() const { return mAudioTrackID; }
 
   TrackID VideoTrackID() const { return mVideoTrackID; }
 
   void DoNotifyTrackEnded(TrackID aTrackID) {
@@ -227,19 +226,18 @@ DecodedStreamData::DecodedStreamData(
     AbstractThread* aMainThread)
     : mAudioFramesWritten(0),
       mStreamVideoWritten(0),
       mStreamAudioWritten(0),
       mNextVideoTime(aInit.mStartTime),
       mNextAudioTime(aInit.mStartTime),
       mHaveSentFinishAudio(false),
       mHaveSentFinishVideo(false),
-      mStream(aOutputStreamManager->mSourceStream)
+      mStream(aOutputStreamManager->mSourceStream),
       // DecodedStreamGraphListener will resolve these promises.
-      ,
       mListener(MakeRefPtr<DecodedStreamGraphListener>(
           mStream, aInit.mAudioTrackID, std::move(aAudioPromise),
           aInit.mVideoTrackID, std::move(aVideoPromise), aMainThread)),
       mEOSVideoCompensation(false),
       mOutputStreamManager(aOutputStreamManager),
       mAbstractMainThread(aMainThread) {
   MOZ_ASSERT(NS_IsMainThread());
   // Initialize tracks on main thread and in the MediaStreamGraph.
@@ -719,34 +717,27 @@ StreamTime DecodedStream::SentDuration()
 
   if (!mData) {
     return 0;
   }
 
   return std::max(mData->mStreamAudioWritten, mData->mStreamVideoWritten);
 }
 
-void DecodedStream::AdvanceTracks() {
-  AssertOwnerThread();
-
-  mData->mStream->AdvanceKnownTracksTime(mStreamTimeOffset + SentDuration());
-}
-
 void DecodedStream::SendData() {
   AssertOwnerThread();
   MOZ_ASSERT(mStartTime.isSome(), "Must be called after StartPlayback()");
 
   // Not yet created on the main thread. MDSM will try again later.
   if (!mData) {
     return;
   }
 
   SendAudio(mParams.mVolume, mSameOrigin, mPrincipalHandle);
   SendVideo(mSameOrigin, mPrincipalHandle);
-  AdvanceTracks();
 }
 
 TimeUnit DecodedStream::GetEndTime(TrackType aType) const {
   AssertOwnerThread();
   if (aType == TrackInfo::kAudioTrack && mInfo.HasAudio() && mData) {
     auto t = mStartTime.ref() +
              FramesToTimeUnit(mData->mAudioFramesWritten, mInfo.mAudio.mRate);
     if (t.IsValid()) {
--- a/dom/media/mediasink/DecodedStream.h
+++ b/dom/media/mediasink/DecodedStream.h
@@ -76,20 +76,18 @@ class DecodedStream : public media::Medi
   media::TimeUnit FromMicroseconds(int64_t aTime) {
     return media::TimeUnit::FromMicroseconds(aTime);
   }
   void DestroyData(UniquePtr<DecodedStreamData> aData);
   void SendAudio(double aVolume, bool aIsSameOrigin,
                  const PrincipalHandle& aPrincipalHandle);
   void SendVideo(bool aIsSameOrigin, const PrincipalHandle& aPrincipalHandle);
   StreamTime SentDuration();
-  void AdvanceTracks();
   void SendData();
   void NotifyOutput(int64_t aTime);
-  void NotifyTrackEnd(StreamTime aEndTime);
 
   void AssertOwnerThread() const {
     MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   }
 
   void PlayingChanged();
 
   void ConnectListener();
--- a/dom/media/webaudio/AudioNodeStream.cpp
+++ b/dom/media/webaudio/AudioNodeStream.cpp
@@ -557,19 +557,16 @@ void AudioNodeStream::ProduceOutputBefor
         DisabledTrackMode::ENABLED) {
       mLastChunks[0].SetNull(WEBAUDIO_BLOCK_SIZE);
     }
   }
 }
 
 void AudioNodeStream::AdvanceOutputSegment() {
   StreamTracks::Track* track = EnsureTrack(AUDIO_TRACK);
-  // No more tracks will be coming
-  mTracks.AdvanceKnownTracksTime(STREAM_TIME_MAX);
-
   AudioSegment* segment = track->Get<AudioSegment>();
 
   AudioChunk copyChunk = *mLastChunks[0].AsMutableChunk();
   AudioSegment tmpSegment;
   tmpSegment.AppendAndConsumeChunk(&copyChunk);
 
   for (TrackBound<MediaStreamTrackListener>& b : mTrackListeners) {
     // Notify MediaStreamTrackListeners.
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -53,16 +53,17 @@ ClientLayerManager::ClientLayerManager(n
   , mLastPaintTime(TimeDuration::Forever())
   , mTargetRotation(ROTATION_0)
   , mRepeatTransaction(false)
   , mIsRepeatTransaction(false)
   , mTransactionIncomplete(false)
   , mCompositorMightResample(false)
   , mNeedsComposite(false)
   , mQueuedAsyncPaints(false)
+  , mNotifyingWidgetListener(false)
   , mPaintSequenceNumber(0)
   , mForwarder(new ShadowLayerForwarder(this))
 {
   MOZ_COUNT_CTOR(ClientLayerManager);
   mMemoryPressureObserver = MemoryPressureObserver::Create(this);
 }
 
 
@@ -80,16 +81,19 @@ ClientLayerManager::~ClientLayerManager(
   mRoot = nullptr;
 
   MOZ_COUNT_DTOR(ClientLayerManager);
 }
 
 void
 ClientLayerManager::Destroy()
 {
+  MOZ_DIAGNOSTIC_ASSERT(!mNotifyingWidgetListener,
+    "Try to avoid destroying widgets and layer managers during DidCompositeWindow, if you can");
+
   // It's important to call ClearCachedResource before Destroy because the
   // former will early-return if the later has already run.
   ClearCachedResources();
   LayerManager::Destroy();
 
   if (mTransactionIdAllocator) {
     // Make sure to notify the refresh driver just in case it's waiting on a
     // pending transaction. Do this at the top of the event loop so we don't
@@ -490,21 +494,28 @@ ClientLayerManager::DidComposite(Transac
   // the end of the method invocation.
   RefPtr<ClientLayerManager> selfRef = this;
 
   // |aTransactionId| will be > 0 if the compositor is acknowledging a shadow
   // layers transaction.
   if (aTransactionId.IsValid()) {
     nsIWidgetListener *listener = mWidget->GetWidgetListener();
     if (listener) {
+      mNotifyingWidgetListener = true;
       listener->DidCompositeWindow(aTransactionId, aCompositeStart, aCompositeEnd);
+      mNotifyingWidgetListener = false;
     }
-    listener = mWidget->GetAttachedWidgetListener();
-    if (listener) {
-      listener->DidCompositeWindow(aTransactionId, aCompositeStart, aCompositeEnd);
+    // DidCompositeWindow might have called Destroy on us and nulled out mWidget,
+    // see bug 1510058.
+    // Re-check it here.
+    if (mWidget) {
+      listener = mWidget->GetAttachedWidgetListener();
+      if (listener) {
+        listener->DidCompositeWindow(aTransactionId, aCompositeStart, aCompositeEnd);
+      }
     }
     if (mTransactionIdAllocator) {
       mTransactionIdAllocator->NotifyTransactionCompleted(aTransactionId);
     }
   }
 
   // These observers fire whether or not we were in a transaction.
   for (size_t i = 0; i < mDidCompositeObservers.Length(); i++) {
--- a/gfx/layers/client/ClientLayerManager.h
+++ b/gfx/layers/client/ClientLayerManager.h
@@ -322,16 +322,17 @@ private:
   // Used to repeat the transaction right away (to avoid rebuilding
   // a display list) to support progressive drawing.
   bool mRepeatTransaction;
   bool mIsRepeatTransaction;
   bool mTransactionIncomplete;
   bool mCompositorMightResample;
   bool mNeedsComposite;
   bool mQueuedAsyncPaints;
+  bool mNotifyingWidgetListener;
 
   // An incrementing sequence number for paints.
   // Incremented in BeginTransaction(), but not for repeat transactions.
   uint32_t mPaintSequenceNumber;
 
   APZTestData mApzTestData;
 
   RefPtr<ShadowLayerForwarder> mForwarder;
--- a/gfx/layers/opengl/TextureHostOGL.cpp
+++ b/gfx/layers/opengl/TextureHostOGL.cpp
@@ -694,17 +694,17 @@ SurfaceTextureHost::CreateRenderTexture(
 void
 SurfaceTextureHost::PushResourceUpdates(wr::TransactionBuilder& aResources,
                                         ResourceUpdateOp aOp,
                                         const Range<wr::ImageKey>& aImageKeys,
                                         const wr::ExternalImageId& aExtID)
 {
   auto method = aOp == TextureHost::ADD_IMAGE ? &wr::TransactionBuilder::AddExternalImage
                                               : &wr::TransactionBuilder::UpdateExternalImage;
-  auto bufferType = wr::WrExternalImageBufferType::TextureRectHandle;
+  auto bufferType = wr::WrExternalImageBufferType::TextureExternalHandle;
 
   switch (GetFormat()) {
     case gfx::SurfaceFormat::R8G8B8X8:
     case gfx::SurfaceFormat::R8G8B8A8: {
       MOZ_ASSERT(aImageKeys.length() == 1);
 
       // XXX Add RGBA handling. Temporary hack to avoid crash
       // With BGRA format setting, rendering works without problem.
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -979,18 +979,21 @@ GeckoChildProcessHost::PerformAsyncLaunc
       }
       break;
     case GeckoProcessType_VR:
       if (mSandboxLevel > 0 && !PR_GetEnv("MOZ_DISABLE_VR_SANDBOX")) {
         // TODO: Implement sandbox for VR process, Bug 1430043.
       }
       break;
     case GeckoProcessType_RDD:
-      if (mSandboxLevel > 0 && !PR_GetEnv("MOZ_DISABLE_RDD_SANDBOX")) {
-        // TODO: Implement sandbox for RDD process, Bug 1498624.
+      if (!PR_GetEnv("MOZ_DISABLE_RDD_SANDBOX")) {
+        if (!mSandboxBroker.SetSecurityLevelForRDDProcess()) {
+          return false;
+        }
+        shouldSandboxCurrentProcess = true;
       }
       break;
     case GeckoProcessType_Default:
     default:
       MOZ_CRASH("Bad process type in GeckoChildProcessHost");
       break;
   };
 
--- a/js/moz.configure
+++ b/js/moz.configure
@@ -365,16 +365,21 @@ js_option('--enable-jitspew',
           help='{Enable|Disable} the Jit spew and IONFLAGS environment '
                'variable')
 
 set_define('JS_JITSPEW',
            depends_if('--enable-jitspew')(lambda _: True))
 set_config('JS_JITSPEW',
            depends_if('--enable-jitspew')(lambda _: True))
 
+# Also enable the structured spewer
+set_define('JS_STRUCTURED_SPEW',
+           depends_if('--enable-jitspew')(lambda _: True))
+set_config('JS_STRUCTURED_SPEW',
+           depends_if('--enable-jitspew')(lambda _: True))
 # When enabled, masm will generate assumeUnreachable calls that act as
 # assertions in the generated code. This option is worth disabling when you
 # have to track mutated values through the generated code, to avoid constantly
 # dumping registers on and off the stack.
 js_option('--enable-masm-verbose',
           default=depends(when=moz_debug)(lambda: True),
           help='{Enable|Disable} MacroAssembler verbosity of generated code.')
 set_define('JS_MASM_VERBOSE',
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -4,25 +4,27 @@
  * 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 "gc/Zone-inl.h"
 
 #include "gc/FreeOp.h"
 #include "gc/Policy.h"
 #include "gc/PublicIterators.h"
+#include "jit/BaselineIC.h"
 #include "jit/BaselineJIT.h"
 #include "jit/Ion.h"
 #include "jit/JitRealm.h"
 #include "vm/Debugger.h"
 #include "vm/Runtime.h"
 #include "wasm/WasmInstance.h"
 
 #include "gc/GC-inl.h"
 #include "gc/Marking-inl.h"
+#include "vm/JSScript-inl.h"
 #include "vm/Realm-inl.h"
 
 using namespace js;
 using namespace js::gc;
 
 Zone * const Zone::NotOnList = reinterpret_cast<Zone*>(1);
 
 JS::Zone::Zone(JSRuntime* rt)
@@ -255,21 +257,30 @@ Zone::discardJitCode(FreeOp* fop, bool d
         /*
          * Make it impossible to use the control flow graphs cached on the
          * BaselineScript. They get deleted.
          */
         if (script->hasBaselineScript()) {
             script->baselineScript()->setControlFlowGraph(nullptr);
         }
 
-        // Try to release the script's TypeScript. This should happen last
-        // because we can't do this when the script still has JIT code.
+        // Try to release the script's TypeScript. This should happen after
+        // releasing JIT code because we can't do this when the script still has
+        // JIT code.
         if (releaseTypes) {
             script->maybeReleaseTypes();
         }
+
+        // The optimizedStubSpace will be purged below so make sure ICScript
+        // doesn't point into it. We do this after (potentially) releasing types
+        // because TypeScript contains the ICScript* and there's no need to
+        // purge stubs if we just destroyed the Typescript.
+        if (discardBaselineCode && script->hasICScript()) {
+            script->icScript()->purgeOptimizedStubs(script->zone());
+        }
     }
 
     /*
      * When scripts contains pointers to nursery things, the store buffer
      * can contain entries that point into the optimized stub space. Since
      * this method can be called outside the context of a GC, this situation
      * could result in us trying to mark invalid store buffer entries.
      *
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -1058,16 +1058,17 @@ InitFromBailout(JSContext* cx, size_t fr
         op = JSOp(*pc);
         if (skippedLoopEntry && script->trackRecordReplayProgress()) {
             mozilla::recordreplay::AdvanceExecutionProgressCounter();
         }
     }
 
     const uint32_t pcOff = script->pcToOffset(pc);
     BaselineScript* baselineScript = script->baselineScript();
+    ICScript* icScript = script->icScript();
 
 #ifdef DEBUG
     uint32_t expectedDepth;
     bool reachablePC;
     if (!ReconstructStackDepth(cx, script, resumeAfter ? GetNextPc(pc) : pc, &expectedDepth, &reachablePC)) {
         return false;
     }
 
@@ -1115,34 +1116,34 @@ InitFromBailout(JSContext* cx, size_t fr
         // If the bailout was a resumeAfter, and the opcode is monitored,
         // then the bailed out state should be in a position to enter
         // into the ICTypeMonitor chain for the op.
         bool enterMonitorChain = false;
         if (resumeAfter && (CodeSpec[op].format & JOF_TYPESET)) {
             // Not every monitored op has a monitored fallback stub, e.g.
             // JSOP_NEWOBJECT, which always returns the same type for a
             // particular script/pc location.
-            ICEntry& icEntry = baselineScript->icEntryFromPCOffset(pcOff);
+            ICEntry& icEntry = icScript->icEntryFromPCOffset(pcOff);
             ICFallbackStub* fallbackStub = icEntry.firstStub()->getChainFallback();
             if (fallbackStub->isMonitoredFallback()) {
                 enterMonitorChain = true;
             }
         }
 
         uint32_t numUses = js::StackUses(pc);
 
         if (resumeAfter && !enterMonitorChain) {
             pc = GetNextPc(pc);
         }
 
         builder.setResumePC(pc);
         builder.setResumeFramePtr(prevFramePtr);
 
         if (enterMonitorChain) {
-            ICEntry& icEntry = baselineScript->icEntryFromPCOffset(pcOff);
+            ICEntry& icEntry = icScript->icEntryFromPCOffset(pcOff);
             ICFallbackStub* fallbackStub = icEntry.firstStub()->getChainFallback();
             MOZ_ASSERT(fallbackStub->isMonitoredFallback());
             JitSpew(JitSpew_BaselineBailouts, "      [TYPE-MONITOR CHAIN]");
 
             ICTypeMonitor_Fallback* typeMonitorFallback =
                 fallbackStub->toMonitoredFallbackStub()->getFallbackMonitorStub(cx, script);
             if (!typeMonitorFallback) {
                 return false;
@@ -1312,17 +1313,17 @@ InitFromBailout(JSContext* cx, size_t fr
                                                     FrameType::BaselineJS,
                                                     BaselineStubFrameLayout::Size());
     if (!builder.writeWord(baselineFrameDescr, "Descriptor")) {
         return false;
     }
 
     // Calculate and write out return address.
     // The icEntry in question MUST have an inlinable fallback stub.
-    ICEntry& icEntry = baselineScript->icEntryFromPCOffset(pcOff);
+    ICEntry& icEntry = icScript->icEntryFromPCOffset(pcOff);
     MOZ_ASSERT(IsInlinableFallback(icEntry.firstStub()->getChainFallback()));
 
     RetAddrEntry& retAddrEntry =
         baselineScript->retAddrEntryFromPCOffset(pcOff, RetAddrEntry::Kind::IC);
     if (!builder.writePtr(baselineScript->returnAddressForEntry(retAddrEntry), "ReturnAddr")) {
         return false;
     }
 
--- a/js/src/jit/BaselineCacheIRCompiler.cpp
+++ b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -2409,75 +2409,16 @@ ICCacheIR_Monitored::stubDataStart()
 }
 
 uint8_t*
 ICCacheIR_Updated::stubDataStart()
 {
     return reinterpret_cast<uint8_t*>(this) + stubInfo_->stubDataOffset();
 }
 
-/* static */ ICCacheIR_Regular*
-ICCacheIR_Regular::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                         ICCacheIR_Regular& other)
-{
-    const CacheIRStubInfo* stubInfo = other.stubInfo();
-    MOZ_ASSERT(stubInfo->makesGCCalls());
-
-    size_t bytesNeeded = stubInfo->stubDataOffset() + stubInfo->stubDataSize();
-    void* newStub = space->alloc(bytesNeeded);
-    if (!newStub) {
-        return nullptr;
-    }
-
-    ICCacheIR_Regular* res = new(newStub) ICCacheIR_Regular(other.jitCode(), stubInfo);
-    stubInfo->copyStubData(&other, res);
-    return res;
-}
-
-
-/* static */ ICCacheIR_Monitored*
-ICCacheIR_Monitored::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                           ICCacheIR_Monitored& other)
-{
-    const CacheIRStubInfo* stubInfo = other.stubInfo();
-    MOZ_ASSERT(stubInfo->makesGCCalls());
-
-    size_t bytesNeeded = stubInfo->stubDataOffset() + stubInfo->stubDataSize();
-    void* newStub = space->alloc(bytesNeeded);
-    if (!newStub) {
-        return nullptr;
-    }
-
-    ICCacheIR_Monitored* res = new(newStub) ICCacheIR_Monitored(other.jitCode(), firstMonitorStub,
-                                                                stubInfo);
-    stubInfo->copyStubData(&other, res);
-    return res;
-}
-
-/* static */ ICCacheIR_Updated*
-ICCacheIR_Updated::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                         ICCacheIR_Updated& other)
-{
-    const CacheIRStubInfo* stubInfo = other.stubInfo();
-    MOZ_ASSERT(stubInfo->makesGCCalls());
-
-    size_t bytesNeeded = stubInfo->stubDataOffset() + stubInfo->stubDataSize();
-    void* newStub = space->alloc(bytesNeeded);
-    if (!newStub) {
-        return nullptr;
-    }
-
-    ICCacheIR_Updated* res = new(newStub) ICCacheIR_Updated(other.jitCode(), stubInfo);
-    res->updateStubGroup() = other.updateStubGroup();
-    res->updateStubId() = other.updateStubId();
-
-    stubInfo->copyStubData(&other, res);
-    return res;
-}
-
 bool
 BaselineCacheIRCompiler::emitCallStringConcatResult()
 {
     AutoOutputRegister output(*this);
     Register lhs = allocator.useRegister(masm, reader.stringOperandId());
     Register rhs = allocator.useRegister(masm, reader.stringOperandId());
     AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
 
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -45,20 +45,18 @@ BaselineCompiler::BaselineCompiler(JSCon
   : cx(cx),
     script(script),
     pc(script->code()),
     ionCompileable_(jit::IsIonEnabled(cx) && CanIonCompileScript(cx, script)),
     compileDebugInstrumentation_(script->isDebuggee()),
     alloc_(alloc),
     analysis_(alloc, script),
     frame(script, masm),
-    stubSpace_(),
-    icEntries_(),
     pcMappingEntries_(),
-    icLoadLabels_(),
+    icEntryIndex_(0),
     pushedBeforeCall_(0),
 #ifdef DEBUG
     inCall_(false),
 #endif
     profilerPushToggleOffset_(),
     profilerEnterFrameToggleOffset_(),
     profilerExitFrameToggleOffset_(),
     traceLoggerToggleOffsets_(cx),
@@ -241,17 +239,16 @@ BaselineCompiler::compile()
     size_t resumeEntries =
         script->hasResumeOffsets() ? script->resumeOffsets().size() : 0;
     UniquePtr<BaselineScript> baselineScript(
         BaselineScript::New(script, bailoutPrologueOffset_.offset(),
                             debugOsrPrologueOffset_.offset(),
                             debugOsrEpilogueOffset_.offset(),
                             profilerEnterFrameToggleOffset_.offset(),
                             profilerExitFrameToggleOffset_.offset(),
-                            icEntries_.length(),
                             retAddrEntries_.length(),
                             pcMappingIndexEntries.length(),
                             pcEntries.length(),
                             bytecodeTypeMapEntries,
                             resumeEntries,
                             traceLoggerToggleOffsets_.length()),
         JS::DeletePolicy<BaselineScript>(cx->runtime()));
     if (!baselineScript) {
@@ -267,42 +264,26 @@ BaselineCompiler::compile()
             script->filename(), script->lineno(), script->column());
 
     MOZ_ASSERT(pcMappingIndexEntries.length() > 0);
     baselineScript->copyPCMappingIndexEntries(&pcMappingIndexEntries[0]);
 
     MOZ_ASSERT(pcEntries.length() > 0);
     baselineScript->copyPCMappingEntries(pcEntries);
 
-    // Copy ICEntries and RetAddrEntries.
-    if (icEntries_.length() > 0) {
-        baselineScript->copyICEntries(script, &icEntries_[0]);
-    }
+    // Copy RetAddrEntries.
     if (retAddrEntries_.length() > 0) {
         baselineScript->copyRetAddrEntries(script, &retAddrEntries_[0]);
     }
 
-    // Adopt fallback stubs from the compiler into the baseline script.
-    baselineScript->adoptFallbackStubs(&stubSpace_);
-
     // If profiler instrumentation is enabled, toggle instrumentation on.
     if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) {
         baselineScript->toggleProfilerInstrumentation(true);
     }
 
-    // Patch IC loads using IC entries.
-    for (size_t i = 0; i < icLoadLabels_.length(); i++) {
-        CodeOffset label = icLoadLabels_[i].label;
-        size_t icEntry = icLoadLabels_[i].icEntry;
-        ICEntry* entryAddr = &(baselineScript->icEntry(icEntry));
-        Assembler::PatchDataWithValueCheck(CodeLocationLabel(code, label),
-                                           ImmPtr(entryAddr),
-                                           ImmPtr((void*)-1));
-    }
-
     if (modifiesArguments_) {
         baselineScript->setModifiesArguments();
     }
     if (analysis_.usesEnvironmentChain()) {
         baselineScript->setUsesEnvironmentChain();
     }
 
 #ifdef JS_TRACE_LOGGING
@@ -580,44 +561,46 @@ BaselineCompiler::emitOutOfLinePostBarri
     masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, PostWriteBarrier));
 
     masm.popValue(R0);
     masm.ret();
     return true;
 }
 
 bool
-BaselineCompiler::emitIC(ICStub* stub, bool isForOp)
-{
-    MOZ_ASSERT_IF(isForOp, BytecodeOpHasIC(JSOp(*pc)));
-
-    if (!stub) {
-        return false;
-    }
-
-    CodeOffset patchOffset, callOffset;
-    EmitCallIC(masm, &patchOffset, &callOffset);
-
-    // ICs need both an ICEntry and a RetAddrEntry.
-
-    RetAddrEntry::Kind kind = isForOp ? RetAddrEntry::Kind::IC : RetAddrEntry::Kind::NonOpIC;
+BaselineCompiler::emitNextIC()
+{
+    // Emit a call to an IC stored in ICScript. Calls to this must match the
+    // ICEntry order in ICScript: first the non-op IC entries for |this| and
+    // formal arguments, then the for-op IC entries for JOF_IC ops.
+
+    uint32_t pcOffset = script->pcToOffset(pc);
+
+    // We don't use every ICEntry and we can skip unreachable ops, so we have
+    // to loop until we find an ICEntry for the current pc.
+    const ICEntry* entry;
+    do {
+        entry = &script->icScript()->icEntry(icEntryIndex_);
+        icEntryIndex_++;
+    } while (entry->pcOffset() < pcOffset);
+
+    MOZ_RELEASE_ASSERT(entry->pcOffset() == pcOffset);
+    MOZ_ASSERT_IF(entry->isForOp(), BytecodeOpHasIC(JSOp(*pc)));
+
+    CodeOffset callOffset;
+    EmitCallIC(masm, entry, &callOffset);
+
+    RetAddrEntry::Kind kind =
+        entry->isForOp() ? RetAddrEntry::Kind::IC : RetAddrEntry::Kind::NonOpIC;
+
     if (!retAddrEntries_.emplaceBack(script->pcToOffset(pc), kind, callOffset)) {
         ReportOutOfMemory(cx);
         return false;
     }
 
-    if (!icEntries_.emplaceBack(stub, script->pcToOffset(pc), isForOp)) {
-        ReportOutOfMemory(cx);
-        return false;
-    }
-
-    if (!addICLoadLabel(patchOffset)) {
-        return false;
-    }
-
     return true;
 }
 
 void
 BaselineCompiler::prepareVMCall()
 {
     pushedBeforeCall_ = masm.framePushed();
 #ifdef DEBUG
@@ -942,18 +925,17 @@ BaselineCompiler::emitWarmUpCounterIncre
     masm.branchPtr(Assembler::Equal,
                    Address(scriptReg, JSScript::offsetOfIonScript()),
                    ImmPtr(ION_COMPILING_SCRIPT), &skipCall);
 
     // Try to compile and/or finish a compilation.
     if (JSOp(*pc) == JSOP_LOOPENTRY) {
         // During the loop entry we can try to OSR into ion.
         // The ic has logic for this.
-        ICWarmUpCounter_Fallback::Compiler stubCompiler(cx);
-        if (!emitNonOpIC(stubCompiler.getStub(&stubSpace_))) {
+        if (!emitNextIC()) {
             return false;
         }
     } else {
         // To call stubs we need to have an opcode. This code handles the
         // prologue and there is no dedicatd opcode present. Therefore use an
         // annotated vm call.
         prepareVMCall();
 
@@ -977,27 +959,25 @@ BaselineCompiler::emitArgumentTypeChecks
 {
     if (!function()) {
         return true;
     }
 
     frame.pushThis();
     frame.popRegsAndSync(1);
 
-    ICTypeMonitor_Fallback::Compiler compiler(cx, uint32_t(0));
-    if (!emitNonOpIC(compiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     for (size_t i = 0; i < function()->nargs(); i++) {
         frame.pushArg(i);
         frame.popRegsAndSync(1);
 
-        ICTypeMonitor_Fallback::Compiler compiler(cx, i + 1);
-        if (!emitNonOpIC(compiler.getStub(&stubSpace_))) {
+        if (!emitNextIC()) {
             return false;
         }
     }
 
     return true;
 }
 
 bool
@@ -1430,18 +1410,17 @@ BaselineCompiler::emit_JSOP_GOTO()
 
 bool
 BaselineCompiler::emitToBoolean()
 {
     Label skipIC;
     masm.branchTestBoolean(Assembler::Equal, R0, &skipIC);
 
     // Call IC
-    ICToBool_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     masm.bind(&skipIC);
     return true;
 }
 
 bool
@@ -1526,18 +1505,17 @@ BaselineCompiler::emit_JSOP_POS()
     // Keep top stack value in R0.
     frame.popRegsAndSync(1);
 
     // Inline path for int32 and double.
     Label done;
     masm.branchTestNumber(Assembler::Equal, R0, &done);
 
     // Call IC.
-    ICToNumber_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     masm.bind(&done);
     frame.push(R0);
     return true;
 }
 
@@ -2154,35 +2132,33 @@ BaselineCompiler::emit_JSOP_POW()
 
 bool
 BaselineCompiler::emitBinaryArith()
 {
     // Keep top JSStack value in R0 and R2
     frame.popRegsAndSync(2);
 
     // Call IC
-    ICBinaryArith_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     // Mark R0 as pushed stack value.
     frame.push(R0);
     return true;
 }
 
 bool
 BaselineCompiler::emitUnaryArith()
 {
     // Keep top stack value in R0.
     frame.popRegsAndSync(1);
 
     // Call IC
-    ICUnaryArith_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     // Mark R0 as pushed stack value.
     frame.push(R0);
     return true;
 }
 
@@ -2238,18 +2214,17 @@ bool
 BaselineCompiler::emitCompare()
 {
     // CODEGEN
 
     // Keep top JSStack value in R0 and R1.
     frame.popRegsAndSync(2);
 
     // Call IC.
-    ICCompare_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     // Mark R0 as pushed stack value.
     frame.push(R0, JSVAL_TYPE_BOOLEAN);
     return true;
 }
 
@@ -2309,23 +2284,17 @@ BaselineCompiler::emit_JSOP_NEWARRAY()
     uint32_t length = GET_UINT32(pc);
     MOZ_ASSERT(length <= INT32_MAX,
                "the bytecode emitter must fail to compile code that would "
                "produce JSOP_NEWARRAY with a length exceeding int32_t range");
 
     // Pass length in R0.
     masm.move32(Imm32(AssertedCast<int32_t>(length)), R0.scratchReg());
 
-    ObjectGroup* group = ObjectGroup::allocationSiteGroup(cx, script, pc, JSProto_Array);
-    if (!group) {
-        return false;
-    }
-
-    ICNewArray_Fallback::Compiler stubCompiler(cx, group);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     frame.push(R0);
     return true;
 }
 
 typedef ArrayObject* (*NewArrayCopyOnWriteFn)(JSContext*, HandleArrayObject, gc::InitialHeap);
@@ -2367,47 +2336,44 @@ BaselineCompiler::emit_JSOP_INITELEM_ARR
     uint32_t index = GET_UINT32(pc);
     MOZ_ASSERT(index <= INT32_MAX,
                "the bytecode emitter must fail to compile code that would "
                "produce JSOP_INITELEM_ARRAY with a length exceeding "
                "int32_t range");
     masm.moveValue(Int32Value(AssertedCast<int32_t>(index)), R1);
 
     // Call IC.
-    ICSetElem_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     // Pop the rhs, so that the object is on the top of the stack.
     frame.pop();
     return true;
 }
 
 bool
 BaselineCompiler::emit_JSOP_NEWOBJECT()
 {
     frame.syncStack(0);
 
-    ICNewObject_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     frame.push(R0);
     return true;
 }
 
 bool
 BaselineCompiler::emit_JSOP_NEWINIT()
 {
     frame.syncStack(0);
 
-    ICNewObject_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     frame.push(R0);
     return true;
 }
 
 bool
@@ -2423,18 +2389,17 @@ BaselineCompiler::emit_JSOP_INITELEM()
     // Push the object to store the result of the IC.
     frame.push(R0);
     frame.syncStack(0);
 
     // Keep RHS on the stack.
     frame.pushScratchValue();
 
     // Call IC.
-    ICSetElem_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     // Pop the rhs, so that the object is on the top of the stack.
     frame.pop();
     return true;
 }
 
@@ -2474,18 +2439,17 @@ bool
 BaselineCompiler::emit_JSOP_INITPROP()
 {
     // Load lhs in R0, rhs in R1.
     frame.syncStack(0);
     masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R0);
     masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R1);
 
     // Call IC.
-    ICSetProp_Fallback::Compiler compiler(cx);
-    if (!emitOpIC(compiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     // Leave the object on the stack.
     frame.pop();
     return true;
 }
 
@@ -2503,18 +2467,17 @@ BaselineCompiler::emit_JSOP_INITHIDDENPR
 
 bool
 BaselineCompiler::emit_JSOP_GETELEM()
 {
     // Keep top two stack values in R0 and R1.
     frame.popRegsAndSync(2);
 
     // Call IC.
-    ICGetElem_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     // Mark R0 as pushed stack value.
     frame.push(R0);
     return true;
 }
 
@@ -2526,18 +2489,17 @@ BaselineCompiler::emit_JSOP_GETELEM_SUPE
     frame.pop();
 
     // Keep receiver and index in R0 and R1.
     frame.popRegsAndSync(2);
 
     // Keep obj on the stack.
     frame.pushScratchValue();
 
-    ICGetElem_Fallback::Compiler stubCompiler(cx, /* hasReceiver = */ true);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     frame.pop(); // This value is also popped in InitFromBailout.
     frame.push(R0);
     return true;
 }
 
@@ -2556,18 +2518,17 @@ BaselineCompiler::emit_JSOP_SETELEM()
 
     // Keep object and index in R0 and R1.
     frame.popRegsAndSync(2);
 
     // Keep RHS on the stack.
     frame.pushScratchValue();
 
     // Call IC.
-    ICSetElem_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     return true;
 }
 
 bool
 BaselineCompiler::emit_JSOP_STRICTSETELEM()
@@ -2648,32 +2609,30 @@ BaselineCompiler::emit_JSOP_STRICTDELELE
     return emit_JSOP_DELELEM();
 }
 
 bool
 BaselineCompiler::emit_JSOP_IN()
 {
     frame.popRegsAndSync(2);
 
-    ICIn_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     frame.push(R0);
     return true;
 }
 
 bool
 BaselineCompiler::emit_JSOP_HASOWN()
 {
     frame.popRegsAndSync(2);
 
-    ICHasOwn_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     frame.push(R0);
     return true;
 }
 
 bool
@@ -2699,18 +2658,17 @@ BaselineCompiler::emit_JSOP_GETGNAME()
         return true;
     }
 
     frame.syncStack(0);
 
     masm.movePtr(ImmGCPtr(&script->global().lexicalEnvironment()), R0.scratchReg());
 
     // Call IC.
-    ICGetName_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     // Mark R0 as pushed stack value.
     frame.push(R0);
     return true;
 }
 
@@ -2774,18 +2732,17 @@ BaselineCompiler::emit_JSOP_SETPROP()
     // Keep lhs in R0, rhs in R1.
     frame.popRegsAndSync(2);
 
     // Keep RHS on the stack.
     frame.push(R1);
     frame.syncStack(0);
 
     // Call IC.
-    ICSetProp_Fallback::Compiler compiler(cx);
-    if (!emitOpIC(compiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     return true;
 }
 
 bool
 BaselineCompiler::emit_JSOP_STRICTSETPROP()
@@ -2860,18 +2817,17 @@ BaselineCompiler::emit_JSOP_STRICTSETPRO
 
 bool
 BaselineCompiler::emit_JSOP_GETPROP()
 {
     // Keep object in R0.
     frame.popRegsAndSync(1);
 
     // Call IC.
-    ICGetProp_Fallback::Compiler compiler(cx);
-    if (!emitOpIC(compiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     // Mark R0 as pushed stack value.
     frame.push(R0);
     return true;
 }
 
@@ -2896,18 +2852,17 @@ BaselineCompiler::emit_JSOP_GETBOUNDNAME
 bool
 BaselineCompiler::emit_JSOP_GETPROP_SUPER()
 {
     // Receiver -> R1, Object -> R0
     frame.popRegsAndSync(1);
     masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R1);
     frame.pop();
 
-    ICGetProp_Fallback::Compiler compiler(cx, /* hasReceiver = */ true);
-    if (!emitOpIC(compiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     frame.push(R0);
     return true;
 }
 
 
@@ -2983,18 +2938,17 @@ BaselineCompiler::emit_JSOP_GETALIASEDVA
 {
     frame.syncStack(0);
 
     Address address = getEnvironmentCoordinateAddress(R0.scratchReg());
     masm.loadValue(address, R0);
 
     if (ionCompileable_) {
         // No need to monitor types if we know Ion can't compile this script.
-        ICTypeMonitor_Fallback::Compiler compiler(cx, nullptr);
-        if (!emitOpIC(compiler.getStub(&stubSpace_))) {
+        if (!emitNextIC()) {
             return false;
         }
     }
 
     frame.push(R0);
     return true;
 }
 
@@ -3010,18 +2964,17 @@ BaselineCompiler::emit_JSOP_SETALIASEDVA
         frame.syncStack(0);
         masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R1);
 
         // Load and box lhs into R0.
         getEnvironmentCoordinateObject(R2.scratchReg());
         masm.tagValue(JSVAL_TYPE_OBJECT, R2.scratchReg(), R0);
 
         // Call SETPROP IC.
-        ICSetProp_Fallback::Compiler compiler(cx);
-        if (!emitOpIC(compiler.getStub(&stubSpace_))) {
+        if (!emitNextIC()) {
             return false;
         }
 
         return true;
     }
 
     // Keep rvalue in R0.
     frame.popRegsAndSync(1);
@@ -3050,18 +3003,17 @@ BaselineCompiler::emit_JSOP_SETALIASEDVA
 bool
 BaselineCompiler::emit_JSOP_GETNAME()
 {
     frame.syncStack(0);
 
     masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg());
 
     // Call IC.
-    ICGetName_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     // Mark R0 as pushed stack value.
     frame.push(R0);
     return true;
 }
 
@@ -3072,18 +3024,17 @@ BaselineCompiler::emit_JSOP_BINDNAME()
 
     if (*pc == JSOP_BINDGNAME && !script->hasNonSyntacticScope()) {
         masm.movePtr(ImmGCPtr(&script->global().lexicalEnvironment()), R0.scratchReg());
     } else {
         masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg());
     }
 
     // Call IC.
-    ICBindName_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     // Mark R0 as pushed stack value.
     frame.push(R0);
     return true;
 }
 
@@ -3140,33 +3091,31 @@ BaselineCompiler::emit_JSOP_GETIMPORT()
     if (targetEnv->getSlot(shape->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) {
         if (!emitUninitializedLexicalCheck(R0)) {
             return false;
         }
     }
 
     if (ionCompileable_) {
         // No need to monitor types if we know Ion can't compile this script.
-        ICTypeMonitor_Fallback::Compiler compiler(cx, nullptr);
-        if (!emitOpIC(compiler.getStub(&stubSpace_))) {
+        if (!emitNextIC()) {
             return false;
         }
     }
 
     frame.push(R0);
     return true;
 }
 
 bool
 BaselineCompiler::emit_JSOP_GETINTRINSIC()
 {
     frame.syncStack(0);
 
-    ICGetIntrinsic_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     frame.push(R0);
     return true;
 }
 
 typedef bool (*DefVarFn)(JSContext*, HandlePropertyName, unsigned, HandleObject);
@@ -3370,18 +3319,17 @@ BaselineCompiler::emit_JSOP_INITELEM_INC
     // Keep the object and rhs on the stack.
     frame.syncStack(0);
 
     // Load object in R0, index in R1.
     masm.loadValue(frame.addressOfStackValue(frame.peek(-3)), R0);
     masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R1);
 
     // Call IC.
-    ICSetElem_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     // Pop the rhs
     frame.pop();
 
     // Increment index
     Address indexAddr = frame.addressOfStackValue(frame.peek(-1));
@@ -3668,19 +3616,17 @@ BaselineCompiler::emitCall()
 
     bool construct = JSOp(*pc) == JSOP_NEW || JSOp(*pc) == JSOP_SUPERCALL;
     uint32_t argc = GET_ARGC(pc);
 
     frame.syncStack(0);
     masm.move32(Imm32(argc), R0.scratchReg());
 
     // Call IC
-    ICCall_Fallback::Compiler stubCompiler(cx, /* isConstructing = */ construct,
-                                           /* isSpread = */ false);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     // Update FrameInfo.
     frame.popn(2 + argc + construct);
     frame.push(R0);
     return true;
 }
@@ -3690,19 +3636,17 @@ BaselineCompiler::emitSpreadCall()
 {
     MOZ_ASSERT(IsCallPC(pc));
 
     frame.syncStack(0);
     masm.move32(Imm32(1), R0.scratchReg());
 
     // Call IC
     bool construct = JSOp(*pc) == JSOP_SPREADNEW || JSOp(*pc) == JSOP_SPREADSUPERCALL;
-    ICCall_Fallback::Compiler stubCompiler(cx, /* isConstructing = */ construct,
-                                           /* isSpread = */ true);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     // Update FrameInfo.
     frame.popn(3 + construct);
     frame.push(R0);
     return true;
 }
@@ -3848,32 +3792,30 @@ BaselineCompiler::emit_JSOP_GIMPLICITTHI
     return emit_JSOP_IMPLICITTHIS();
 }
 
 bool
 BaselineCompiler::emit_JSOP_INSTANCEOF()
 {
     frame.popRegsAndSync(2);
 
-    ICInstanceOf_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     frame.push(R0);
     return true;
 }
 
 bool
 BaselineCompiler::emit_JSOP_TYPEOF()
 {
     frame.popRegsAndSync(1);
 
-    ICTypeOf_Fallback::Compiler stubCompiler(cx);
-    if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     frame.push(R0);
     return true;
 }
 
 bool
@@ -4531,33 +4473,31 @@ BaselineCompiler::emit_JSOP_TABLESWITCH(
     return true;
 }
 
 bool
 BaselineCompiler::emit_JSOP_ITER()
 {
     frame.popRegsAndSync(1);
 
-    ICGetIterator_Fallback::Compiler compiler(cx);
-    if (!emitOpIC(compiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     frame.push(R0);
     return true;
 }
 
 bool
 BaselineCompiler::emit_JSOP_MOREITER()
 {
     frame.syncStack(0);
     masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0);
 
-    ICIteratorMore_Fallback::Compiler compiler(cx);
-    if (!emitOpIC(compiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     frame.push(R0);
     return true;
 }
 
 bool
@@ -4588,18 +4528,21 @@ BaselineCompiler::emit_JSOP_ISNOITER()
 bool
 BaselineCompiler::emit_JSOP_ENDITER()
 {
     if (!emit_JSOP_JUMPTARGET()) {
         return false;
     }
     frame.popRegsAndSync(1);
 
-    ICIteratorClose_Fallback::Compiler compiler(cx);
-    return emitOpIC(compiler.getStub(&stubSpace_));
+    if (!emitNextIC()) {
+        return false;
+    }
+
+    return true;
 }
 
 bool
 BaselineCompiler::emit_JSOP_ISGENCLOSING()
 {
     return emitIsMagicValue();
 }
 
@@ -4825,26 +4768,17 @@ BaselineCompiler::emit_JSOP_RUNONCE()
     return callVM(RunOnceScriptPrologueInfo);
 }
 
 bool
 BaselineCompiler::emit_JSOP_REST()
 {
     frame.syncStack(0);
 
-    ArrayObject* templateObject =
-        ObjectGroup::newArrayObject(cx, nullptr, 0, TenuredObject,
-                                    ObjectGroup::NewArrayKind::UnknownIndex);
-    if (!templateObject) {
-        return false;
-    }
-
-    // Call IC.
-    ICRest_Fallback::Compiler compiler(cx, templateObject);
-    if (!emitOpIC(compiler.getStub(&stubSpace_))) {
+    if (!emitNextIC()) {
         return false;
     }
 
     // Mark R0 as pushed stack value.
     frame.push(R0);
     return true;
 }
 
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -257,43 +257,34 @@ class BaselineCompiler final
     StackMacroAssembler masm;
     bool ionCompileable_;
     bool compileDebugInstrumentation_;
 
     TempAllocator& alloc_;
     BytecodeAnalysis analysis_;
     FrameInfo frame;
 
-    FallbackICStubSpace stubSpace_;
-    js::Vector<ICEntry, 16, SystemAllocPolicy> icEntries_;
     js::Vector<RetAddrEntry, 16, SystemAllocPolicy> retAddrEntries_;
 
     // Stores the native code offset for a bytecode pc.
     struct PCMappingEntry
     {
         uint32_t pcOffset;
         uint32_t nativeOffset;
         PCMappingSlotInfo slotInfo;
 
         // If set, insert a PCMappingIndexEntry before encoding the
         // current entry.
         bool addIndexEntry;
     };
 
     js::Vector<PCMappingEntry, 16, SystemAllocPolicy> pcMappingEntries_;
 
-    // Labels for the 'movWithPatch' for loading IC entry pointers in
-    // the generated IC-calling code in the main jitcode.  These need
-    // to be patched with the actual icEntry offsets after the BaselineScript
-    // has been allocated.
-    struct ICLoadLabel {
-        size_t icEntry;
-        CodeOffset label;
-    };
-    js::Vector<ICLoadLabel, 16, SystemAllocPolicy> icLoadLabels_;
+    // Index of the current ICEntry in the script's ICScript.
+    uint32_t icEntryIndex_;
 
     uint32_t pushedBeforeCall_;
 #ifdef DEBUG
     bool inCall_;
 #endif
 
     CodeOffset profilerPushToggleOffset_;
     CodeOffset profilerEnterFrameToggleOffset_;
@@ -346,28 +337,16 @@ class BaselineCompiler final
     MOZ_MUST_USE bool appendRetAddrEntry(RetAddrEntry::Kind kind, uint32_t retOffset) {
         if (!retAddrEntries_.emplaceBack(script->pcToOffset(pc), kind, CodeOffset(retOffset))) {
             ReportOutOfMemory(cx);
             return false;
         }
         return true;
     }
 
-    bool addICLoadLabel(CodeOffset label) {
-        MOZ_ASSERT(!icEntries_.empty());
-        ICLoadLabel loadLabel;
-        loadLabel.label = label;
-        loadLabel.icEntry = icEntries_.length() - 1;
-        if (!icLoadLabels_.append(loadLabel)) {
-            ReportOutOfMemory(cx);
-            return false;
-        }
-        return true;
-    }
-
     JSFunction* function() const {
         // Not delazifying here is ok as the function is guaranteed to have
         // been delazified before compilation started.
         return script->functionNonDelazifying();
     }
 
     ModuleObject* module() const {
         return script->module();
@@ -415,24 +394,17 @@ class BaselineCompiler final
 
     MOZ_MUST_USE bool emitCheckThis(ValueOperand val, bool reinit=false);
     void emitLoadReturnValue(ValueOperand val);
 
     void emitInitializeLocals();
     MOZ_MUST_USE bool emitPrologue();
     MOZ_MUST_USE bool emitEpilogue();
     MOZ_MUST_USE bool emitOutOfLinePostBarrierSlot();
-    MOZ_MUST_USE bool emitIC(ICStub* stub, bool isForOp);
-    MOZ_MUST_USE bool emitOpIC(ICStub* stub) {
-        return emitIC(stub, true);
-    }
-    MOZ_MUST_USE bool emitNonOpIC(ICStub* stub) {
-        return emitIC(stub, false);
-    }
-
+    MOZ_MUST_USE bool emitNextIC();
     MOZ_MUST_USE bool emitStackCheck();
     MOZ_MUST_USE bool emitInterruptCheck();
     MOZ_MUST_USE bool emitWarmUpCounterIncrement(bool allowOsr=true);
     MOZ_MUST_USE bool emitArgumentTypeChecks();
     void emitIsDebuggeeCheck();
     MOZ_MUST_USE bool emitDebugPrologue();
     MOZ_MUST_USE bool emitDebugTrap();
     MOZ_MUST_USE bool emitTraceLoggerEnter();
--- a/js/src/jit/BaselineDebugModeOSR.cpp
+++ b/js/src/jit/BaselineDebugModeOSR.cpp
@@ -18,77 +18,65 @@
 
 using namespace js;
 using namespace js::jit;
 
 struct DebugModeOSREntry
 {
     JSScript* script;
     BaselineScript* oldBaselineScript;
-    ICStub* oldStub;
-    ICStub* newStub;
     BaselineDebugModeOSRInfo* recompInfo;
     uint32_t pcOffset;
     RetAddrEntry::Kind frameKind;
 
     explicit DebugModeOSREntry(JSScript* script)
       : script(script),
         oldBaselineScript(script->baselineScript()),
-        oldStub(nullptr),
-        newStub(nullptr),
         recompInfo(nullptr),
         pcOffset(uint32_t(-1)),
         frameKind(RetAddrEntry::Kind::Invalid)
     { }
 
     DebugModeOSREntry(JSScript* script, uint32_t pcOffset)
       : script(script),
         oldBaselineScript(script->baselineScript()),
-        oldStub(nullptr),
-        newStub(nullptr),
         recompInfo(nullptr),
         pcOffset(pcOffset),
         frameKind(RetAddrEntry::Kind::Invalid)
     { }
 
     DebugModeOSREntry(JSScript* script, const RetAddrEntry& retAddrEntry)
       : script(script),
         oldBaselineScript(script->baselineScript()),
-        oldStub(nullptr),
-        newStub(nullptr),
         recompInfo(nullptr),
         pcOffset(retAddrEntry.pcOffset()),
         frameKind(retAddrEntry.kind())
     {
 #ifdef DEBUG
         MOZ_ASSERT(pcOffset == retAddrEntry.pcOffset());
         MOZ_ASSERT(frameKind == retAddrEntry.kind());
 #endif
     }
 
     DebugModeOSREntry(JSScript* script, BaselineDebugModeOSRInfo* info)
       : script(script),
         oldBaselineScript(script->baselineScript()),
-        oldStub(nullptr),
-        newStub(nullptr),
         recompInfo(nullptr),
         pcOffset(script->pcToOffset(info->pc)),
         frameKind(info->frameKind)
     {
 #ifdef DEBUG
         MOZ_ASSERT(pcOffset == script->pcToOffset(info->pc));
         MOZ_ASSERT(frameKind == info->frameKind);
 #endif
     }
 
     DebugModeOSREntry(DebugModeOSREntry&& other)
       : script(other.script),
         oldBaselineScript(other.oldBaselineScript),
-        oldStub(other.oldStub),
-        newStub(other.newStub),
         recompInfo(other.recompInfo ? other.takeRecompInfo() : nullptr),
         pcOffset(other.pcOffset),
         frameKind(other.frameKind)
     { }
 
     ~DebugModeOSREntry() {
         // Note that this is nulled out when the recompInfo is taken by the
         // frame. The frame then has the responsibility of freeing the
@@ -127,22 +115,16 @@ struct DebugModeOSREntry
         jsbytecode* pc = script->offsetToPC(pcOffset);
 
         // XXX: Work around compiler error disallowing using bitfields
         // with the template magic of new_.
         RetAddrEntry::Kind kind = frameKind;
         recompInfo = cx->new_<BaselineDebugModeOSRInfo>(pc, kind);
         return !!recompInfo;
     }
-
-    ICFallbackStub* fallbackStub() const {
-        MOZ_ASSERT(script);
-        MOZ_ASSERT(oldStub);
-        return script->baselineScript()->icEntryFromPCOffset(pcOffset).fallbackStub();
-    }
 };
 
 typedef Vector<DebugModeOSREntry> DebugModeOSREntryVector;
 
 class UniqueScriptOSREntryIter
 {
     const DebugModeOSREntryVector& entries_;
     size_t index_;
@@ -179,26 +161,24 @@ class UniqueScriptOSREntryIter
         return *this;
     }
 };
 
 static bool
 CollectJitStackScripts(JSContext* cx, const Debugger::ExecutionObservableSet& obs,
                        const ActivationIterator& activation, DebugModeOSREntryVector& entries)
 {
-    ICStub* prevFrameStubPtr = nullptr;
     bool needsRecompileHandler = false;
     for (OnlyJSJitFrameIter iter(activation); !iter.done(); ++iter) {
         const JSJitFrameIter& frame = iter.frame();
         switch (frame.type()) {
           case FrameType::BaselineJS: {
             JSScript* script = frame.script();
 
             if (!obs.shouldRecompileOrInvalidate(script)) {
-                prevFrameStubPtr = nullptr;
                 break;
             }
 
             BaselineFrame* baselineFrame = frame.baselineFrame();
 
             if (BaselineDebugModeOSRInfo* info = baselineFrame->getDebugModeOSRInfo()) {
                 // If patching a previously patched yet unpopped frame, we can
                 // use the BaselineDebugModeOSRInfo on the frame directly to
@@ -230,24 +210,20 @@ CollectJitStackScripts(JSContext* cx, co
 
             if (entries.back().needsRecompileInfo()) {
                 if (!entries.back().allocateRecompileInfo(cx)) {
                     return false;
                 }
 
                 needsRecompileHandler |= true;
             }
-            entries.back().oldStub = prevFrameStubPtr;
-            prevFrameStubPtr = nullptr;
             break;
           }
 
           case FrameType::BaselineStub:
-            prevFrameStubPtr =
-                reinterpret_cast<BaselineStubFrameLayout*>(frame.fp())->maybeStubPtr();
             break;
 
           case FrameType::IonJS: {
             InlineFrameIterator inlineIter(cx, &frame);
             while (true) {
                 if (obs.shouldRecompileOrInvalidate(inlineIter.script())) {
                     if (!entries.append(DebugModeOSREntry(inlineIter.script()))) {
                         return false;
@@ -342,38 +318,30 @@ SpewPatchBaselineFrameFromExceptionHandl
 {
     JitSpew(JitSpew_BaselineDebugModeOSR,
             "Patch return %p -> %p on BaselineJS frame (%s:%u:%u) from exception handler at %s",
             oldReturnAddress, newReturnAddress, script->filename(), script->lineno(),
             script->column(), CodeName[(JSOp)*pc]);
 }
 
 static void
-SpewPatchStubFrame(ICStub* oldStub, ICStub* newStub)
-{
-    JitSpew(JitSpew_BaselineDebugModeOSR,
-            "Patch   stub %p -> %p on BaselineStub frame (%s)",
-            oldStub, newStub, newStub ? ICStub::KindString(newStub->kind()) : "exception handler");
-}
-
-static void
 PatchBaselineFramesForDebugMode(JSContext* cx,
                                 const Debugger::ExecutionObservableSet& obs,
                                 const ActivationIterator& activation,
                                 DebugModeOSREntryVector& entries, size_t* start)
 {
     //
     // Recompile Patching Overview
     //
     // When toggling debug mode with live baseline scripts on the stack, we
     // could have entered the VM via the following ways from the baseline
     // script.
     //
     // Off to On:
-    //  A. From a "can call" stub.
+    //  A. From a "can call" IC stub.
     //  B. From a VM call.
     //  H. From inside HandleExceptionBaseline
     //  I. From inside the interrupt handler via the prologue stack check.
     //  J. From the warmup counter in the prologue.
     //
     // On to Off:
     //  - All the ways above.
     //  C. From the debug trap handler.
@@ -419,23 +387,19 @@ PatchBaselineFramesForDebugMode(JSContex
             MOZ_ASSERT(pcOffset < script->length());
 
             BaselineScript* bl = script->baselineScript();
             RetAddrEntry::Kind kind = entry.frameKind;
 
             if (kind == RetAddrEntry::Kind::IC) {
                 // Case A above.
                 //
-                // Patching these cases needs to patch both the stub frame and
-                // the baseline frame. The stub frame is patched below. For
-                // the baseline frame here, we resume right after the IC
-                // returns.
-                //
-                // Since we're using the same IC stub code, we can resume
-                // directly to the IC resume address.
+                // For the baseline frame here, we resume right after the IC
+                // returns. Since we're using the same IC stubs and stub code,
+                // we don't have to patch the stub or stub frame.
                 RetAddrEntry& retAddrEntry = bl->retAddrEntryFromPCOffset(pcOffset, kind);
                 uint8_t* retAddr = bl->returnAddressForEntry(retAddrEntry);
                 SpewPatchBaselineFrame(prev->returnAddress(), retAddr, script, kind, pc);
                 DebugModeOSRVolatileJitFrameIter::forwardLiveIterators(
                     cx, prev->returnAddress(), retAddr);
                 prev->setReturnAddress(retAddr);
                 entryIndex++;
                 break;
@@ -585,61 +549,16 @@ PatchBaselineFramesForDebugMode(JSContex
             prev->setReturnAddress(reinterpret_cast<uint8_t*>(handlerAddr));
             frame.baselineFrame()->setDebugModeOSRInfo(recompInfo);
             frame.baselineFrame()->setOverridePc(recompInfo->pc);
 
             entryIndex++;
             break;
           }
 
-          case FrameType::BaselineStub: {
-            JSJitFrameIter prev(iter.frame());
-            ++prev;
-            BaselineFrame* prevFrame = prev.baselineFrame();
-            if (!obs.shouldRecompileOrInvalidate(prevFrame->script())) {
-                break;
-            }
-
-            DebugModeOSREntry& entry = entries[entryIndex];
-
-            // If the script wasn't recompiled, there's nothing to patch.
-            if (!entry.recompiled()) {
-                break;
-            }
-
-            BaselineStubFrameLayout* layout =
-                reinterpret_cast<BaselineStubFrameLayout*>(frame.fp());
-            MOZ_ASSERT(layout->maybeStubPtr() == entry.oldStub);
-
-            // Patch baseline stub frames for case A above.
-            //
-            // We need to patch the stub frame to point to an ICStub belonging
-            // to the recompiled baseline script. These stubs are allocated up
-            // front in CloneOldBaselineStub. They share the same JitCode as
-            // the old baseline script's stubs, so we don't need to patch the
-            // exit frame's return address.
-            //
-            // Subtlety here: the debug trap handler of case C above pushes a
-            // stub frame with a null stub pointer. This handler will exist
-            // across recompiling the script, so we don't patch anything for
-            // such stub frames. We will return to that handler, which takes
-            // care of cleaning up the stub frame.
-            //
-            // Note that for stub pointers that are already on the C stack
-            // (i.e. fallback calls), we need to check for recompilation using
-            // DebugModeOSRVolatileStub.
-            if (layout->maybeStubPtr()) {
-                MOZ_ASSERT(entry.newStub || prevFrame->isHandlingException());
-                SpewPatchStubFrame(entry.oldStub, entry.newStub);
-                layout->setStubPtr(entry.newStub);
-            }
-
-            break;
-          }
-
           case FrameType::IonJS: {
             // Nothing to patch.
             InlineFrameIterator inlineIter(cx, &frame);
             while (true) {
                 if (obs.shouldRecompileOrInvalidate(inlineIter.script())) {
                     entryIndex++;
                 }
                 if (!inlineIter.more()) {
@@ -707,123 +626,16 @@ RecompileBaselineScriptForDebugMode(JSCo
     }
 
     // Don't destroy the old baseline script yet, since if we fail any of the
     // recompiles we need to rollback all the old baseline scripts.
     MOZ_ASSERT(script->baselineScript()->hasDebugInstrumentation() == observing);
     return true;
 }
 
-#define PATCHABLE_ICSTUB_KIND_LIST(_)           \
-    _(CacheIR_Monitored)                        \
-    _(CacheIR_Regular)                          \
-    _(CacheIR_Updated)                          \
-    _(Call_Scripted)                            \
-    _(Call_AnyScripted)                         \
-    _(Call_Native)                              \
-    _(Call_ClassHook)                           \
-    _(Call_ScriptedApplyArray)                  \
-    _(Call_ScriptedApplyArguments)              \
-    _(Call_ScriptedFunCall)
-
-static bool
-CloneOldBaselineStub(JSContext* cx, DebugModeOSREntryVector& entries, size_t entryIndex)
-{
-    DebugModeOSREntry& entry = entries[entryIndex];
-    if (!entry.oldStub) {
-        return true;
-    }
-
-    ICStub* oldStub = entry.oldStub;
-    MOZ_ASSERT(oldStub->makesGCCalls());
-
-    // If this script was not recompiled (because it already had the correct
-    // debug instrumentation), don't clone to avoid attaching duplicate stubs.
-    if (!entry.recompiled()) {
-        entry.newStub = nullptr;
-        return true;
-    }
-
-    if (entry.frameKind == RetAddrEntry::Kind::Invalid) {
-        // The exception handler can modify the frame's override pc while
-        // unwinding scopes. This is fine, but if we have a stub frame, the code
-        // code below will get confused: the entry's pcOffset doesn't match the
-        // stub that's still on the stack. To prevent that, we just set the new
-        // stub to nullptr as we will never return to this stub frame anyway.
-        entry.newStub = nullptr;
-        return true;
-    }
-
-    // Get the new fallback stub from the recompiled baseline script.
-    ICFallbackStub* fallbackStub = entry.fallbackStub();
-
-    // Some stubs are monitored, get the first stub in the monitor chain from
-    // the new fallback stub if so. We do this before checking for fallback
-    // stubs below, to ensure monitored fallback stubs have a type monitor
-    // chain.
-    ICStub* firstMonitorStub;
-    if (fallbackStub->isMonitoredFallback()) {
-        ICMonitoredFallbackStub* monitored = fallbackStub->toMonitoredFallbackStub();
-        ICTypeMonitor_Fallback* fallback = monitored->getFallbackMonitorStub(cx, entry.script);
-        if (!fallback) {
-            return false;
-        }
-        firstMonitorStub = fallback->firstMonitorStub();
-    } else {
-        firstMonitorStub = nullptr;
-    }
-
-    // We don't need to clone fallback stubs, as they are guaranteed to
-    // exist. Furthermore, their JitCode is cached and should be the same even
-    // across the recompile.
-    if (oldStub->isFallback()) {
-        MOZ_ASSERT(oldStub->jitCode() == fallbackStub->jitCode());
-        entry.newStub = fallbackStub;
-        return true;
-    }
-
-    // Check if we have already cloned the stub on a younger frame. Ignore
-    // frames that entered the exception handler (entries[i].newStub is nullptr
-    // in that case, see above).
-    for (size_t i = 0; i < entryIndex; i++) {
-        if (oldStub == entries[i].oldStub && entries[i].frameKind != RetAddrEntry::Kind::Invalid) {
-            MOZ_ASSERT(entries[i].newStub);
-            entry.newStub = entries[i].newStub;
-            return true;
-        }
-    }
-
-    ICStubSpace* stubSpace = ICStubCompiler::StubSpaceForStub(oldStub->makesGCCalls(),
-                                                              entry.script);
-
-    // Clone the existing stub into the recompiled IC.
-    //
-    // Note that since JitCode is a GC thing, cloning an ICStub with the same
-    // JitCode ensures it won't be collected.
-    switch (oldStub->kind()) {
-#define CASE_KIND(kindName)                                                  \
-      case ICStub::kindName:                                                 \
-        entry.newStub = IC##kindName::Clone(cx, stubSpace, firstMonitorStub, \
-                                            *oldStub->to##kindName());       \
-        break;
-        PATCHABLE_ICSTUB_KIND_LIST(CASE_KIND)
-#undef CASE_KIND
-
-      default:
-        MOZ_CRASH("Bad stub kind");
-    }
-
-    if (!entry.newStub) {
-        return false;
-    }
-
-    fallbackStub->addNewStub(entry.newStub);
-    return true;
-}
-
 static bool
 InvalidateScriptsInZone(JSContext* cx, Zone* zone, const Vector<DebugModeOSREntry>& entries)
 {
     RecompileInfoVector invalid;
     for (UniqueScriptOSREntryIter iter(entries); !iter.done(); ++iter) {
         JSScript* script = iter.entry().script;
         if (script->zone() != zone) {
             continue;
@@ -913,19 +725,17 @@ jit::RecompileOnStackBaselineScriptsForD
     }
 
     // Try to recompile all the scripts. If we encounter an error, we need to
     // roll back as if none of the compilations happened, so that we don't
     // crash.
     for (size_t i = 0; i < entries.length(); i++) {
         JSScript* script = entries[i].script;
         AutoRealm ar(cx, script);
-        if (!RecompileBaselineScriptForDebugMode(cx, script, observing) ||
-            !CloneOldBaselineStub(cx, entries, i))
-        {
+        if (!RecompileBaselineScriptForDebugMode(cx, script, observing)) {
             UndoRecompileBaselineScriptsForDebugMode(cx, entries);
             return false;
         }
     }
 
     // If all recompiles succeeded, destroy the old baseline scripts and patch
     // the live frames.
     //
--- a/js/src/jit/BaselineDebugModeOSR.h
+++ b/js/src/jit/BaselineDebugModeOSR.h
@@ -17,65 +17,16 @@
 namespace js {
 namespace jit {
 
 // Note that this file and the corresponding .cpp implement debug mode
 // on-stack recompilation. This is to be distinguished from ordinary
 // Baseline->Ion OSR, which is used to jump into compiled loops.
 
 //
-// A volatile location due to recompilation of an on-stack baseline script
-// (e.g., for debug mode toggling).
-//
-// It is usually used in fallback stubs which may trigger on-stack
-// recompilation by calling out into the VM. Example use:
-//
-//     DebugModeOSRVolatileStub<FallbackStubT*> stub(frame, stub_)
-//
-//     // Call out to the VM
-//     // Other effectful operations like TypeScript::Monitor
-//
-//     if (stub.invalid()) {
-//         return true;
-//     }
-//
-//     // First use of stub after VM call.
-//
-template <typename T>
-class DebugModeOSRVolatileStub
-{
-    T stub_;
-    BaselineFrame* frame_;
-    uint32_t pcOffset_;
-
-  public:
-    DebugModeOSRVolatileStub(BaselineFrame* frame, ICFallbackStub* stub)
-      : stub_(static_cast<T>(stub)),
-        frame_(frame),
-        pcOffset_(stub->icEntry()->pcOffset())
-    { }
-
-    bool invalid() const {
-        MOZ_ASSERT(!frame_->isHandlingException());
-        ICEntry& entry = frame_->script()->baselineScript()->icEntryFromPCOffset(pcOffset_);
-        return stub_ != entry.fallbackStub();
-    }
-
-    operator const T&() const { MOZ_ASSERT(!invalid()); return stub_; }
-    T operator->() const { MOZ_ASSERT(!invalid()); return stub_; }
-    T* address() { MOZ_ASSERT(!invalid()); return &stub_; }
-    const T* address() const { MOZ_ASSERT(!invalid()); return &stub_; }
-    T& get() { MOZ_ASSERT(!invalid()); return stub_; }
-    const T& get() const { MOZ_ASSERT(!invalid()); return stub_; }
-
-    bool operator!=(const T& other) const { MOZ_ASSERT(!invalid()); return stub_ != other; }
-    bool operator==(const T& other) const { MOZ_ASSERT(!invalid()); return stub_ == other; }
-};
-
-//
 // A frame iterator that updates internal JSJitFrameIter in case of
 // recompilation of an on-stack baseline script.
 //
 
 class DebugModeOSRVolatileJitFrameIter : public JitFrameIter
 {
     DebugModeOSRVolatileJitFrameIter** stack;
     DebugModeOSRVolatileJitFrameIter* prev;
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -118,16 +118,345 @@ ICEntry::fallbackStub() const
 void
 ICEntry::trace(JSTracer* trc)
 {
     for (ICStub* stub = firstStub(); stub; stub = stub->next()) {
         stub->trace(trc);
     }
 }
 
+/* static */ UniquePtr<ICScript>
+ICScript::create(JSContext* cx, JSScript* script)
+{
+    FallbackICStubSpace stubSpace;
+    js::Vector<ICEntry, 16, SystemAllocPolicy> icEntries;
+
+    auto addIC = [cx, &icEntries, script](jsbytecode* pc, ICStub* stub) {
+        if (!stub) {
+            MOZ_ASSERT(cx->isExceptionPending());
+            return false;
+        }
+        uint32_t offset = pc ? script->pcToOffset(pc) : ICEntry::NonOpPCOffset;
+        if (!icEntries.emplaceBack(stub, offset)) {
+            ReportOutOfMemory(cx);
+            return false;
+        }
+        return true;
+    };
+
+    // Add ICEntries and fallback stubs for this/argument type checks.
+    // Note: we pass a nullptr pc to indicate this is a non-op IC.
+    // See ICEntry::NonOpPCOffset.
+    if (JSFunction* fun = script->functionNonDelazifying()) {
+        ICTypeMonitor_Fallback::Compiler compiler(cx, uint32_t(0));
+        if (!addIC(nullptr, compiler.getStub(&stubSpace))) {
+            return nullptr;
+        }
+
+        for (size_t i = 0; i < fun->nargs(); i++) {
+            ICTypeMonitor_Fallback::Compiler compiler(cx, i + 1);
+            if (!addIC(nullptr, compiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+        }
+    }
+
+    jsbytecode const* pcEnd = script->codeEnd();
+
+    // Add ICEntries and fallback stubs for JOF_IC bytecode ops.
+    for (jsbytecode* pc = script->code(); pc < pcEnd; pc = GetNextPc(pc)) {
+        JSOp op = JSOp(*pc);
+        if (!BytecodeOpHasIC(op)) {
+            continue;
+        }
+
+        switch (op) {
+          case JSOP_NOT:
+          case JSOP_AND:
+          case JSOP_OR:
+          case JSOP_IFEQ:
+          case JSOP_IFNE: {
+            ICToBool_Fallback::Compiler stubCompiler(cx);
+            if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_BITNOT:
+          case JSOP_NEG: {
+            ICUnaryArith_Fallback::Compiler stubCompiler(cx);
+            if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_BITOR:
+          case JSOP_BITXOR:
+          case JSOP_BITAND:
+          case JSOP_LSH:
+          case JSOP_RSH:
+          case JSOP_URSH:
+          case JSOP_ADD:
+          case JSOP_SUB:
+          case JSOP_MUL:
+          case JSOP_DIV:
+          case JSOP_MOD:
+          case JSOP_POW: {
+            ICBinaryArith_Fallback::Compiler stubCompiler(cx);
+            if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_EQ:
+          case JSOP_NE:
+          case JSOP_LT:
+          case JSOP_LE:
+          case JSOP_GT:
+          case JSOP_GE:
+          case JSOP_STRICTEQ:
+          case JSOP_STRICTNE: {
+            ICCompare_Fallback::Compiler stubCompiler(cx);
+            if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_POS: {
+            ICToNumber_Fallback::Compiler stubCompiler(cx);
+            if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_LOOPENTRY: {
+            ICWarmUpCounter_Fallback::Compiler stubCompiler(cx);
+            if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_NEWARRAY: {
+            ObjectGroup* group = ObjectGroup::allocationSiteGroup(cx, script, pc, JSProto_Array);
+            if (!group) {
+                return nullptr;
+            }
+            ICNewArray_Fallback::Compiler stubCompiler(cx, group);
+            if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_NEWOBJECT:
+          case JSOP_NEWINIT: {
+            ICNewObject_Fallback::Compiler stubCompiler(cx);
+            if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_INITELEM:
+          case JSOP_INITHIDDENELEM:
+          case JSOP_INITELEM_ARRAY:
+          case JSOP_INITELEM_INC:
+          case JSOP_SETELEM:
+          case JSOP_STRICTSETELEM: {
+            ICSetElem_Fallback::Compiler stubCompiler(cx);
+            if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_INITPROP:
+          case JSOP_INITLOCKEDPROP:
+          case JSOP_INITHIDDENPROP:
+          case JSOP_SETALIASEDVAR:
+          case JSOP_INITGLEXICAL:
+          case JSOP_INITALIASEDLEXICAL:
+          case JSOP_SETPROP:
+          case JSOP_STRICTSETPROP:
+          case JSOP_SETNAME:
+          case JSOP_STRICTSETNAME:
+          case JSOP_SETGNAME:
+          case JSOP_STRICTSETGNAME: {
+            ICSetProp_Fallback::Compiler compiler(cx);
+            if (!addIC(pc, compiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_GETPROP:
+          case JSOP_CALLPROP:
+          case JSOP_LENGTH:
+          case JSOP_GETPROP_SUPER:
+          case JSOP_GETBOUNDNAME: {
+            bool hasReceiver = (op == JSOP_GETPROP_SUPER);
+            ICGetProp_Fallback::Compiler compiler(cx, hasReceiver);
+            if (!addIC(pc, compiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_GETELEM:
+          case JSOP_CALLELEM:
+          case JSOP_GETELEM_SUPER: {
+            bool hasReceiver = (op == JSOP_GETELEM_SUPER);
+            ICGetElem_Fallback::Compiler stubCompiler(cx, hasReceiver);
+            if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_IN: {
+            ICIn_Fallback::Compiler stubCompiler(cx);
+            if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_HASOWN: {
+            ICHasOwn_Fallback::Compiler stubCompiler(cx);
+            if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_GETNAME:
+          case JSOP_GETGNAME: {
+            ICGetName_Fallback::Compiler stubCompiler(cx);
+            if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_BINDNAME:
+          case JSOP_BINDGNAME: {
+            ICBindName_Fallback::Compiler stubCompiler(cx);
+            if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_GETALIASEDVAR:
+          case JSOP_GETIMPORT: {
+            ICTypeMonitor_Fallback::Compiler compiler(cx, nullptr);
+            if (!addIC(pc, compiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_GETINTRINSIC: {
+            ICGetIntrinsic_Fallback::Compiler stubCompiler(cx);
+            if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_CALL:
+          case JSOP_CALL_IGNORES_RV:
+          case JSOP_CALLITER:
+          case JSOP_SUPERCALL:
+          case JSOP_FUNCALL:
+          case JSOP_FUNAPPLY:
+          case JSOP_NEW:
+          case JSOP_EVAL:
+          case JSOP_STRICTEVAL: {
+            bool construct = JSOp(*pc) == JSOP_NEW || JSOp(*pc) == JSOP_SUPERCALL;
+            ICCall_Fallback::Compiler stubCompiler(cx, /* isConstructing = */ construct,
+                                                   /* isSpread = */ false);
+            if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_SPREADCALL:
+          case JSOP_SPREADSUPERCALL:
+          case JSOP_SPREADNEW:
+          case JSOP_SPREADEVAL:
+          case JSOP_STRICTSPREADEVAL: {
+            bool construct = JSOp(*pc) == JSOP_SPREADNEW || JSOp(*pc) == JSOP_SPREADSUPERCALL;
+            ICCall_Fallback::Compiler stubCompiler(cx, /* isConstructing = */ construct,
+                                                   /* isSpread = */ true);
+            if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_INSTANCEOF: {
+            ICInstanceOf_Fallback::Compiler stubCompiler(cx);
+            if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_TYPEOF:
+          case JSOP_TYPEOFEXPR: {
+            ICTypeOf_Fallback::Compiler stubCompiler(cx);
+            if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_ITER: {
+            ICGetIterator_Fallback::Compiler compiler(cx);
+            if (!addIC(pc, compiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_MOREITER: {
+            ICIteratorMore_Fallback::Compiler compiler(cx);
+            if (!addIC(pc, compiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_ENDITER: {
+            ICIteratorClose_Fallback::Compiler compiler(cx);
+            if (!addIC(pc, compiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          case JSOP_REST: {
+            ArrayObject* templateObject =
+                ObjectGroup::newArrayObject(cx, nullptr, 0, TenuredObject,
+                                            ObjectGroup::NewArrayKind::UnknownIndex);
+            if (!templateObject) {
+                return nullptr;
+            }
+            ICRest_Fallback::Compiler compiler(cx, templateObject);
+            if (!addIC(pc, compiler.getStub(&stubSpace))) {
+                return nullptr;
+            }
+            break;
+          }
+          default:
+            MOZ_CRASH("JOF_IC op not handled");
+        }
+    }
+
+    UniquePtr<ICScript> icScript(
+        script->zone()->pod_malloc_with_extra<ICScript, ICEntry>(icEntries.length()));
+    if (!icScript) {
+        ReportOutOfMemory(cx);
+        return nullptr;
+    }
+    new (icScript.get()) ICScript(icEntries.length());
+
+    // Adopt fallback stubs into the ICScript.
+    icScript->fallbackStubSpace_.adoptFrom(&stubSpace);
+
+    if (icEntries.length() > 0) {
+        icScript->initICEntries(script, &icEntries[0]);
+    }
+
+    return icScript;
+}
+
 ICStubConstIterator&
 ICStubConstIterator::operator++()
 {
     MOZ_ASSERT(currentStub_ != nullptr);
     currentStub_ = currentStub_->next();
     return *this;
 }
 
@@ -712,17 +1041,17 @@ ICMonitoredStub::ICMonitoredStub(Kind ki
 }
 
 bool
 ICMonitoredFallbackStub::initMonitoringChain(JSContext* cx, JSScript* script)
 {
     MOZ_ASSERT(fallbackMonitorStub_ == nullptr);
 
     ICTypeMonitor_Fallback::Compiler compiler(cx, this);
-    ICStubSpace* space = script->baselineScript()->fallbackStubSpace();
+    ICStubSpace* space = script->icScript()->fallbackStubSpace();
     ICTypeMonitor_Fallback* stub = compiler.getStub(space);
     if (!stub) {
         return false;
     }
     fallbackMonitorStub_ = stub;
     return true;
 }
 
@@ -747,16 +1076,25 @@ ICUpdatedStub::initUpdatingChain(JSConte
     if (!stub) {
         return false;
     }
 
     firstUpdateStub_ = stub;
     return true;
 }
 
+/* static */ ICStubSpace*
+ICStubCompiler::StubSpaceForStub(bool makesGCCalls, JSScript* script)
+{
+    if (makesGCCalls) {
+        return script->icScript()->fallbackStubSpace();
+    }
+    return script->zone()->jitZone()->optimizedStubSpace();
+}
+
 JitCode*
 ICStubCompiler::getStubCode()
 {
     JitRealm* realm = cx->realm()->jitRealm();
 
     // Check for existing cached stubcode.
     uint32_t stubKey = getKey();
     JitCode* stubCode = realm->getStubCode(stubKey);
@@ -886,19 +1224,18 @@ ICStubCompiler::pushStubPayload(MacroAss
 
 void
 ICStubCompiler::PushStubPayload(MacroAssembler& masm, Register scratch)
 {
     pushStubPayload(masm, scratch);
     masm.adjustFrame(sizeof(intptr_t));
 }
 
-//
 void
-BaselineScript::noteAccessedGetter(uint32_t pcOffset)
+ICScript::noteAccessedGetter(uint32_t pcOffset)
 {
     ICEntry& entry = icEntryFromPCOffset(pcOffset);
     ICFallbackStub* stub = entry.fallbackStub();
 
     if (stub->isGetProp_Fallback()) {
         stub->toGetProp_Fallback()->noteAccessedGetter();
     }
 }
@@ -1693,21 +2030,19 @@ StripPreliminaryObjectStubs(JSContext* c
     }
 }
 
 //
 // GetElem_Fallback
 //
 
 static bool
-DoGetElemFallback(JSContext* cx, BaselineFrame* frame, ICGetElem_Fallback* stub_, HandleValue lhs,
+DoGetElemFallback(JSContext* cx, BaselineFrame* frame, ICGetElem_Fallback* stub, HandleValue lhs,
                   HandleValue rhs, MutableHandleValue res)
 {
-    // This fallback stub may trigger debug mode toggling.
-    DebugModeOSRVolatileStub<ICGetElem_Fallback*> stub(frame, stub_);
     stub->incrementEnteredCount();
 
     RootedScript script(cx, frame->script());
     jsbytecode* pc = stub->icEntry()->pc(frame->script());
     StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
 
     JSOp op = JSOp(*pc);
     FallbackICSpew(cx, stub, "GetElem(%s)", CodeName[op]);
@@ -1760,21 +2095,16 @@ DoGetElemFallback(JSContext* cx, Baselin
 
     if (!isOptimizedArgs) {
         if (!GetElementOperation(cx, op, lhsCopy, rhs, res)) {
             return false;
         }
         TypeScript::Monitor(cx, script, pc, types, res);
     }
 
-    // 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 (attached) {
         return true;
     }
@@ -1785,22 +2115,20 @@ DoGetElemFallback(JSContext* cx, Baselin
     if (rhs.isNumber() && rhs.toNumber() < 0) {
         stub->noteNegativeIndex();
     }
 
     return true;
 }
 
 static bool
-DoGetElemSuperFallback(JSContext* cx, BaselineFrame* frame, ICGetElem_Fallback* stub_,
+DoGetElemSuperFallback(JSContext* cx, BaselineFrame* frame, ICGetElem_Fallback* stub,
                        HandleValue lhs, HandleValue rhs, HandleValue receiver,
                        MutableHandleValue res)
 {
-    // This fallback stub may trigger debug mode toggling.
-    DebugModeOSRVolatileStub<ICGetElem_Fallback*> stub(frame, stub_);
     stub->incrementEnteredCount();
 
     RootedScript script(cx, frame->script());
     jsbytecode* pc = stub->icEntry()->pc(frame->script());
     StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
 
     JSOp op = JSOp(*pc);
     FallbackICSpew(cx, stub, "GetElemSuper(%s)", CodeName[op]);
@@ -1838,21 +2166,16 @@ DoGetElemSuperFallback(JSContext* cx, Ba
 
     // |lhs| is [[HomeObject]].[[Prototype]] which must be Object
     RootedObject lhsObj(cx, &lhs.toObject());
     if (!GetObjectElementOperation(cx, op, lhsObj, receiver, rhs, res)) {
         return false;
     }
     TypeScript::Monitor(cx, script, pc, types, res);
 
-    // 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 (attached) {
         return true;
     }
@@ -1926,21 +2249,19 @@ SetUpdateStubData(ICCacheIR_Updated* stu
 {
     if (info->isSet()) {
         stub->updateStubGroup() = info->group();
         stub->updateStubId() = info->id();
     }
 }
 
 static bool
-DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub_, Value* stack,
+DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub, Value* stack,
                   HandleValue objv, HandleValue index, HandleValue rhs)
 {
-    // This fallback stub may trigger debug mode toggling.
-    DebugModeOSRVolatileStub<ICSetElem_Fallback*> stub(frame, stub_);
     stub->incrementEnteredCount();
 
     RootedScript script(cx, frame->script());
     RootedScript outerScript(cx, script);
     jsbytecode* pc = stub->icEntry()->pc(script);
     JSOp op = JSOp(*pc);
     FallbackICSpew(cx, stub, "SetElem(%s)", CodeName[JSOp(*pc)]);
 
@@ -2029,21 +2350,16 @@ DoSetElemFallback(JSContext* cx, Baselin
     if (op == JSOP_INITHIDDENELEM) {
         return true;
     }
 
     // Overwrite the object on the stack (pushed for the decompiler) with the rhs.
     MOZ_ASSERT(stack[2] == objv);
     stack[2] = rhs;
 
-    // Check if debug mode toggling made the stub invalid.
-    if (stub.invalid()) {
-        return true;
-    }
-
     if (attached) {
         return true;
     }
 
     // The SetObjectElement call might have entered this IC recursively, so try
     // to transition.
     if (stub->state().maybeTransition()) {
         stub->discardStubs(cx);
@@ -2117,17 +2433,17 @@ ICSetElem_Fallback::Compiler::generateSt
 
     masm.push(ICStubReg);
     pushStubPayload(masm, R0.scratchReg());
 
     return tailCallVM(DoSetElemFallbackInfo, masm);
 }
 
 void
-BaselineScript::noteHasDenseAdd(uint32_t pcOffset)
+ICScript::noteHasDenseAdd(uint32_t pcOffset)
 {
     ICEntry& entry = icEntryFromPCOffset(pcOffset);
     ICFallbackStub* stub = entry.fallbackStub();
 
     if (stub->isSetElem_Fallback()) {
         stub->toSetElem_Fallback()->noteHasDenseAdd();
     }
 }
@@ -2217,21 +2533,19 @@ StoreToTypedArray(JSContext* cx, MacroAs
                   const ValueOperand& value, const BaseIndex& dest, Register scratch,
                   Label* failure);
 
 //
 // In_Fallback
 //
 
 static bool
-DoInFallback(JSContext* cx, BaselineFrame* frame, ICIn_Fallback* stub_,
+DoInFallback(JSContext* cx, BaselineFrame* frame, ICIn_Fallback* stub,
              HandleValue key, HandleValue objValue, MutableHandleValue res)
 {
-    // This fallback stub may trigger debug mode toggling.
-    DebugModeOSRVolatileStub<ICIn_Fallback*> stub(frame, stub_);
     stub->incrementEnteredCount();
 
     FallbackICSpew(cx, stub, "In");
 
     if (!objValue.isObject()) {
         ReportInNotObjectError(cx, key, -2, objValue, -1);
         return false;
     }
@@ -2271,21 +2585,19 @@ ICIn_Fallback::Compiler::generateStubCod
     return tailCallVM(DoInFallbackInfo, masm);
 }
 
 //
 // HasOwn_Fallback
 //
 
 static bool
-DoHasOwnFallback(JSContext* cx, BaselineFrame* frame, ICHasOwn_Fallback* stub_,
+DoHasOwnFallback(JSContext* cx, BaselineFrame* frame, ICHasOwn_Fallback* stub,
                  HandleValue keyValue, HandleValue objValue, MutableHandleValue res)
 {
-    // This fallback stub may trigger debug mode toggling.
-    DebugModeOSRVolatileStub<ICIn_Fallback*> stub(frame, stub_);
     stub->incrementEnteredCount();
 
     FallbackICSpew(cx, stub, "HasOwn");
 
     TryAttachStub<HasPropIRGenerator>("HasOwn", cx, frame, stub,
         BaselineCacheIRStubKind::Regular, CacheKind::HasOwn,
         keyValue, objValue);
 
@@ -2322,21 +2634,19 @@ ICHasOwn_Fallback::Compiler::generateStu
 }
 
 
 //
 // GetName_Fallback
 //
 
 static bool
-DoGetNameFallback(JSContext* cx, BaselineFrame* frame, ICGetName_Fallback* stub_,
+DoGetNameFallback(JSContext* cx, BaselineFrame* frame, ICGetName_Fallback* stub,
                   HandleObject envChain, MutableHandleValue res)
 {
-    // This fallback stub may trigger debug mode toggling.
-    DebugModeOSRVolatileStub<ICGetName_Fallback*> stub(frame, stub_);
     stub->incrementEnteredCount();
 
     RootedScript script(cx, frame->script());
     jsbytecode* pc = stub->icEntry()->pc(script);
     mozilla::DebugOnly<JSOp> op = JSOp(*pc);
     FallbackICSpew(cx, stub, "GetName(%s)", CodeName[JSOp(*pc)]);
 
     MOZ_ASSERT(op == JSOP_GETNAME || op == JSOP_GETGNAME);
@@ -2355,21 +2665,16 @@ DoGetNameFallback(JSContext* cx, Baselin
         if (!GetEnvironmentName<GetNameMode::Normal>(cx, envChain, name, res)) {
             return false;
         }
     }
 
     StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
     TypeScript::Monitor(cx, script, pc, types, res);
 
-    // 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;
     }
 
     return true;
 }
 
@@ -2440,21 +2745,19 @@ ICBindName_Fallback::Compiler::generateS
     return tailCallVM(DoBindNameFallbackInfo, masm);
 }
 
 //
 // GetIntrinsic_Fallback
 //
 
 static bool
-DoGetIntrinsicFallback(JSContext* cx, BaselineFrame* frame, ICGetIntrinsic_Fallback* stub_,
+DoGetIntrinsicFallback(JSContext* cx, BaselineFrame* frame, ICGetIntrinsic_Fallback* stub,
                        MutableHandleValue res)
 {
-    // This fallback stub may trigger debug mode toggling.
-    DebugModeOSRVolatileStub<ICGetIntrinsic_Fallback*> stub(frame, stub_);
     stub->incrementEnteredCount();
 
     RootedScript script(cx, frame->script());
     jsbytecode* pc = stub->icEntry()->pc(script);
     mozilla::DebugOnly<JSOp> op = JSOp(*pc);
     FallbackICSpew(cx, stub, "GetIntrinsic(%s)", CodeName[JSOp(*pc)]);
 
     MOZ_ASSERT(op == JSOP_GETINTRINSIC);
@@ -2464,21 +2767,16 @@ DoGetIntrinsicFallback(JSContext* cx, Ba
     }
 
     // An intrinsic operation will always produce the same result, so only
     // needs to be monitored once. Attach a stub to load the resulting constant
     // directly.
 
     TypeScript::Monitor(cx, script, pc, res);
 
-    // Check if debug mode toggling made the stub invalid.
-    if (stub.invalid()) {
-        return true;
-    }
-
     TryAttachStub<GetIntrinsicIRGenerator>("GetIntrinsic", cx, frame, stub, BaselineCacheIRStubKind::Regular, res);
 
     return true;
 }
 
 typedef bool (*DoGetIntrinsicFallbackFn)(JSContext*, BaselineFrame*, ICGetIntrinsic_Fallback*,
                                          MutableHandleValue);
 static const VMFunction DoGetIntrinsicFallbackInfo =
@@ -2528,25 +2826,23 @@ ComputeGetPropResult(JSContext* cx, Base
             }
         }
     }
 
     return true;
 }
 
 static bool
-DoGetPropFallback(JSContext* cx, BaselineFrame* frame, ICGetProp_Fallback* stub_,
+DoGetPropFallback(JSContext* cx, BaselineFrame* frame, ICGetProp_Fallback* stub,
                   MutableHandleValue val, MutableHandleValue res)
 {
-    // This fallback stub may trigger debug mode toggling.
-    DebugModeOSRVolatileStub<ICGetProp_Fallback*> stub(frame, stub_);
     stub->incrementEnteredCount();
 
     RootedScript script(cx, frame->script());
-    jsbytecode* pc = stub_->icEntry()->pc(script);
+    jsbytecode* pc = stub->icEntry()->pc(script);
     JSOp op = JSOp(*pc);
     FallbackICSpew(cx, stub, "GetProp(%s)", CodeName[op]);
 
     MOZ_ASSERT(op == JSOP_GETPROP ||
                op == JSOP_CALLPROP ||
                op == JSOP_LENGTH ||
                op == JSOP_GETBOUNDNAME);
 
@@ -2588,38 +2884,31 @@ DoGetPropFallback(JSContext* cx, Baselin
 
     if (!ComputeGetPropResult(cx, frame, op, name, val, res)) {
         return false;
     }
 
     StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
     TypeScript::Monitor(cx, script, pc, types, res);
 
-    // 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;
     }
     return true;
 }
 
 static bool
-DoGetPropSuperFallback(JSContext* cx, BaselineFrame* frame, ICGetProp_Fallback* stub_,
+DoGetPropSuperFallback(JSContext* cx, BaselineFrame* frame, ICGetProp_Fallback* stub,
                        HandleValue receiver, MutableHandleValue val, MutableHandleValue res)
 {
-    // This fallback stub may trigger debug mode toggling.
-    DebugModeOSRVolatileStub<ICGetProp_Fallback*> stub(frame, stub_);
     stub->incrementEnteredCount();
 
     RootedScript script(cx, frame->script());
-    jsbytecode* pc = stub_->icEntry()->pc(script);
+    jsbytecode* pc = stub->icEntry()->pc(script);
     FallbackICSpew(cx, stub, "GetPropSuper(%s)", CodeName[JSOp(*pc)]);
 
     MOZ_ASSERT(JSOp(*pc) == JSOP_GETPROP_SUPER);
 
     RootedPropertyName name(cx, script->getName(pc));
 
     // There are some reasons we can fail to attach a stub that are temporary.
     // We want to avoid calling noteUnoptimizableAccess() if the reason we
@@ -2659,21 +2948,16 @@ DoGetPropSuperFallback(JSContext* cx, Ba
     RootedObject valObj(cx, &val.toObject());
     if (!GetProperty(cx, valObj, receiver, name, res)) {
         return false;
     }
 
     StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
     TypeScript::Monitor(cx, script, pc, types, res);
 
-    // 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;
     }
 
     return true;
 }
 
@@ -2749,21 +3033,19 @@ ICGetProp_Fallback::Compiler::postGenera
     cx->realm()->jitRealm()->initBailoutReturnAddr(address, getKey(), kind);
 }
 
 //
 // SetProp_Fallback
 //
 
 static bool
-DoSetPropFallback(JSContext* cx, BaselineFrame* frame, ICSetProp_Fallback* stub_, Value* stack,
+DoSetPropFallback(JSContext* cx, BaselineFrame* frame, ICSetProp_Fallback* stub, Value* stack,
                   HandleValue lhs, HandleValue rhs)
 {
-    // This fallback stub may trigger debug mode toggling.
-    DebugModeOSRVolatileStub<ICSetProp_Fallback*> stub(frame, stub_);
     stub->incrementEnteredCount();
 
     RootedScript script(cx, frame->script());
     jsbytecode* pc = stub->icEntry()->pc(script);
     JSOp op = JSOp(*pc);
     FallbackICSpew(cx, stub, "SetProp(%s)", CodeName[op]);
 
     MOZ_ASSERT(op == JSOP_SETPROP ||
@@ -2873,21 +3155,16 @@ DoSetPropFallback(JSContext* cx, Baselin
             return false;
         }
     }
 
     // Overwrite the LHS on the stack (pushed for the decompiler) with the RHS.
     MOZ_ASSERT(stack[1] == lhs);
     stack[1] = rhs;
 
-    // Check if debug mode toggling made the stub invalid.
-    if (stub.invalid()) {
-        return true;
-    }
-
     if (attached) {
         return true;
     }
 
     // The SetProperty call might have entered this IC recursively, so try
     // to transition.
     if (stub->state().maybeTransition()) {
         stub->discardStubs(cx);
@@ -3565,21 +3842,19 @@ TryAttachConstStringSplit(JSContext* cx,
     }
 
     stub->addNewStub(newStub);
     *attached = true;
     return true;
 }
 
 static bool
-DoCallFallback(JSContext* cx, BaselineFrame* frame, ICCall_Fallback* stub_, uint32_t argc,
+DoCallFallback(JSContext* cx, BaselineFrame* frame, ICCall_Fallback* stub, uint32_t argc,
                Value* vp, MutableHandleValue res)
 {
-    // This fallback stub may trigger debug mode toggling.
-    DebugModeOSRVolatileStub<ICCall_Fallback*> stub(frame, stub_);
     stub->incrementEnteredCount();
 
     RootedScript script(cx, frame->script());
     jsbytecode* pc = stub->icEntry()->pc(script);
     JSOp op = JSOp(*pc);
     FallbackICSpew(cx, stub, "Call(%s)", CodeName[op]);
 
     MOZ_ASSERT(argc == GET_ARGC(pc));
@@ -3670,21 +3945,16 @@ DoCallFallback(JSContext* cx, BaselineFr
         }
 
         res.set(callArgs.rval());
     }
 
     StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
     TypeScript::Monitor(cx, script, pc, types, res);
 
-    // 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;
     }
 
     // Try to transition again in case we called this IC recursively.
     if (stub->state().maybeTransition()) {
         stub->discardStubs(cx);
@@ -3704,21 +3974,19 @@ DoCallFallback(JSContext* cx, BaselineFr
         if (canAttachStub) {
             stub->state().trackNotAttached();
         }
     }
     return true;
 }
 
 static bool
-DoSpreadCallFallback(JSContext* cx, BaselineFrame* frame, ICCall_Fallback* stub_, Value* vp,
+DoSpreadCallFallback(JSContext* cx, BaselineFrame* frame, ICCall_Fallback* stub, Value* vp,
                      MutableHandleValue res)
 {
-    // This fallback stub may trigger debug mode toggling.
-    DebugModeOSRVolatileStub<ICCall_Fallback*> stub(frame, stub_);
     stub->incrementEnteredCount();
 
     RootedScript script(cx, frame->script());
     jsbytecode* pc = stub->icEntry()->pc(script);
     JSOp op = JSOp(*pc);
     bool constructing = (op == JSOP_SPREADNEW || op == JSOP_SPREADSUPERCALL);
     FallbackICSpew(cx, stub, "SpreadCall(%s)", CodeName[op]);
 
@@ -3738,21 +4006,16 @@ DoSpreadCallFallback(JSContext* cx, Base
     {
         return false;
     }
 
     if (!SpreadCallOperation(cx, script, pc, thisv, callee, arr, newTarget, res)) {
         return false;
     }
 
-    // Check if debug mode toggling made the stub invalid.
-    if (stub.invalid()) {
-        return true;
-    }
-
     // Add a type monitor stub for the resulting value.
     StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
     if (!stub->addMonitorStubForValue(cx, frame, types, res)) {
         return false;
     }
 
     return true;
 }
@@ -5136,34 +5399,27 @@ ICGetIterator_Fallback::Compiler::genera
     return tailCallVM(DoGetIteratorFallbackInfo, masm);
 }
 
 //
 // IteratorMore_Fallback
 //
 
 static bool
-DoIteratorMoreFallback(JSContext* cx, BaselineFrame* frame, ICIteratorMore_Fallback* stub_,
+DoIteratorMoreFallback(JSContext* cx, BaselineFrame* frame, ICIteratorMore_Fallback* stub,
                        HandleObject iterObj, MutableHandleValue res)
 {
-    // This fallback stub may trigger debug mode toggling.
-    DebugModeOSRVolatileStub<ICIteratorMore_Fallback*> stub(frame, stub_);
     stub->incrementEnteredCount();
 
     FallbackICSpew(cx, stub, "IteratorMore");
 
     if (!IteratorMore(cx, iterObj, res)) {
         return false;
     }
 
-    // Check if debug mode toggling made the stub invalid.
-    if (stub.invalid()) {
-        return true;
-    }
-
     if (!res.isMagic(JS_NO_ITER_VALUE) && !res.isString()) {
         stub->setHasNonStringResult();
     }
 
     if (iterObj->is<PropertyIteratorObject>() &&
         !stub->hasStub(ICStub::IteratorMore_Native))
     {
         ICIteratorMore_Native::Compiler compiler(cx);
@@ -5270,21 +5526,19 @@ ICIteratorClose_Fallback::Compiler::gene
     return tailCallVM(DoIteratorCloseFallbackInfo, masm);
 }
 
 //
 // InstanceOf_Fallback
 //
 
 static bool
-DoInstanceOfFallback(JSContext* cx, BaselineFrame* frame, ICInstanceOf_Fallback* stub_,
+DoInstanceOfFallback(JSContext* cx, BaselineFrame* frame, ICInstanceOf_Fallback* stub,
                      HandleValue lhs, HandleValue rhs, MutableHandleValue res)
 {
-    // This fallback stub may trigger debug mode toggling.
-    DebugModeOSRVolatileStub<ICInstanceOf_Fallback*> stub(frame, stub_);
     stub->incrementEnteredCount();
 
     FallbackICSpew(cx, stub, "InstanceOf");
 
     if (!rhs.isObject()) {
         ReportValueError(cx, JSMSG_BAD_INSTANCEOF_RHS, -1, rhs, nullptr);
         return false;
     }
@@ -5292,21 +5546,16 @@ DoInstanceOfFallback(JSContext* cx, Base
     RootedObject obj(cx, &rhs.toObject());
     bool cond = false;
     if (!HasInstance(cx, obj, lhs, &cond)) {
         return false;
     }
 
     res.setBoolean(cond);
 
-    // Check if debug mode toggling made the stub invalid.
-    if (stub.invalid()) {
-        return true;
-    }
-
     if (!obj->is<JSFunction>()) {
         // ensure we've recorded at least one failure, so we can detect there was a non-optimizable case
         if (!stub->state().hasFailures()) {
             stub->state().trackNotAttached();
         }
         return true;
     }
 
@@ -5401,31 +5650,16 @@ ICCall_Scripted::ICCall_Scripted(JitCode
                                  JSFunction* callee, JSObject* templateObject,
                                  uint32_t pcOffset)
   : ICMonitoredStub(ICStub::Call_Scripted, stubCode, firstMonitorStub),
     callee_(callee),
     templateObject_(templateObject),
     pcOffset_(pcOffset)
 { }
 
-/* static */ ICCall_Scripted*
-ICCall_Scripted::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                       ICCall_Scripted& other)
-{
-    return New<ICCall_Scripted>(cx, space, other.jitCode(), firstMonitorStub, other.callee_,
-                                other.templateObject_, other.pcOffset_);
-}
-
-/* static */ ICCall_AnyScripted*
-ICCall_AnyScripted::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                          ICCall_AnyScripted& other)
-{
-    return New<ICCall_AnyScripted>(cx, space, other.jitCode(), firstMonitorStub, other.pcOffset_);
-}
-
 ICCall_Native::ICCall_Native(JitCode* stubCode, ICStub* firstMonitorStub,
                              JSFunction* callee, JSObject* templateObject,
                              uint32_t pcOffset)
   : ICMonitoredStub(ICStub::Call_Native, stubCode, firstMonitorStub),
     callee_(callee),
     templateObject_(templateObject),
     pcOffset_(pcOffset)
 {
@@ -5433,24 +5667,16 @@ ICCall_Native::ICCall_Native(JitCode* st
     // The simulator requires VM calls to be redirected to a special swi
     // instruction to handle them. To make this work, we store the redirected
     // pointer in the stub.
     native_ = Simulator::RedirectNativeFunction(JS_FUNC_TO_DATA_PTR(void*, callee->native()),
                                                 Args_General3);
 #endif
 }
 
-/* static */ ICCall_Native*
-ICCall_Native::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                     ICCall_Native& other)
-{
-    return New<ICCall_Native>(cx, space, other.jitCode(), firstMonitorStub, other.callee_,
-                              other.templateObject_, other.pcOffset_);
-}
-
 ICCall_ClassHook::ICCall_ClassHook(JitCode* stubCode, ICStub* firstMonitorStub,
                                    const Class* clasp, Native native,
                                    JSObject* templateObject, uint32_t pcOffset)
   : ICMonitoredStub(ICStub::Call_ClassHook, stubCode, firstMonitorStub),
     clasp_(clasp),
     native_(JS_FUNC_TO_DATA_PTR(void*, native)),
     templateObject_(templateObject),
     pcOffset_(pcOffset)
@@ -5458,55 +5684,16 @@ ICCall_ClassHook::ICCall_ClassHook(JitCo
 #ifdef JS_SIMULATOR
     // The simulator requires VM calls to be redirected to a special swi
     // instruction to handle them. To make this work, we store the redirected
     // pointer in the stub.
     native_ = Simulator::RedirectNativeFunction(native_, Args_General3);
 #endif
 }
 
-/* static */ ICCall_ClassHook*
-ICCall_ClassHook::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                        ICCall_ClassHook& other)
-{
-    ICCall_ClassHook* res = New<ICCall_ClassHook>(cx, space, other.jitCode(), firstMonitorStub,
-                                                  other.clasp(), nullptr, other.templateObject_,
-                                                  other.pcOffset_);
-    if (res) {
-        res->native_ = other.native();
-    }
-    return res;
-}
-
-/* static */ ICCall_ScriptedApplyArray*
-ICCall_ScriptedApplyArray::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                                 ICCall_ScriptedApplyArray& other)
-{
-    return New<ICCall_ScriptedApplyArray>(cx, space, other.jitCode(), firstMonitorStub,
-                                          other.pcOffset_);
-}
-
-/* static */ ICCall_ScriptedApplyArguments*
-ICCall_ScriptedApplyArguments::Clone(JSContext* cx,
-                                     ICStubSpace* space,
-                                     ICStub* firstMonitorStub,
-                                     ICCall_ScriptedApplyArguments& other)
-{
-    return New<ICCall_ScriptedApplyArguments>(cx, space, other.jitCode(), firstMonitorStub,
-                                              other.pcOffset_);
-}
-
-/* static */ ICCall_ScriptedFunCall*
-ICCall_ScriptedFunCall::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                              ICCall_ScriptedFunCall& other)
-{
-    return New<ICCall_ScriptedFunCall>(cx, space, other.jitCode(), firstMonitorStub,
-                                       other.pcOffset_);
-}
-
 //
 // Rest_Fallback
 //
 
 static bool
 DoRestFallback(JSContext* cx, BaselineFrame* frame, ICRest_Fallback* stub,
                MutableHandleValue res)
 {
@@ -5543,18 +5730,16 @@ ICRest_Fallback::Compiler::generateStubC
 //
 // UnaryArith_Fallback
 //
 
 static bool
 DoUnaryArithFallback(JSContext* cx, BaselineFrame* frame, ICUnaryArith_Fallback* stub,
                      HandleValue val, MutableHandleValue res)
 {
-    // This fallback stub may trigger debug mode toggling.
-    DebugModeOSRVolatileStub<ICUnaryArith_Fallback*> debug_stub(frame, stub);
     stub->incrementEnteredCount();
 
     RootedScript script(cx, frame->script());
     jsbytecode* pc = stub->icEntry()->pc(script);
     JSOp op = JSOp(*pc);
     FallbackICSpew(cx, stub, "UnaryArith(%s)", CodeName[op]);
 
     switch (op) {
@@ -5572,21 +5757,16 @@ DoUnaryArithFallback(JSContext* cx, Base
             return false;
         }
         break;
       }
       default:
         MOZ_CRASH("Unexpected op");
     }
 
-    // Check if debug mode toggling made the stub invalid.
-    if (debug_stub.invalid()) {
-        return true;
-    }
-
     if (res.isDouble()) {
         stub->setSawDoubleResult();
     }
 
     TryAttachStub<UnaryArithIRGenerator>("UniaryArith", cx, frame, stub, BaselineCacheIRStubKind::Regular, op, val, res);
     return true;
 }
 
@@ -5615,21 +5795,19 @@ ICUnaryArith_Fallback::Compiler::generat
     return tailCallVM(DoUnaryArithFallbackInfo, masm);
 }
 
 //
 // BinaryArith_Fallback
 //
 
 static bool
-DoBinaryArithFallback(JSContext* cx, BaselineFrame* frame, ICBinaryArith_Fallback* stub_,
+DoBinaryArithFallback(JSContext* cx, BaselineFrame* frame, ICBinaryArith_Fallback* stub,
                       HandleValue lhs, HandleValue rhs, MutableHandleValue ret)
 {
-    // This fallback stub may trigger debug mode toggling.
-    DebugModeOSRVolatileStub<ICBinaryArith_Fallback*> stub(frame, stub_);
     stub->incrementEnteredCount();
 
     RootedScript script(cx, frame->script());
     jsbytecode* pc = stub->icEntry()->pc(script);
     JSOp op = JSOp(*pc);
     FallbackICSpew(cx, stub, "CacheIRBinaryArith(%s,%d,%d)", CodeName[op],
             int(lhs.isDouble() ? JSVAL_TYPE_DOUBLE : lhs.extractNonDoubleType()),
             int(rhs.isDouble() ? JSVAL_TYPE_DOUBLE : rhs.extractNonDoubleType()));
@@ -5707,21 +5885,16 @@ DoBinaryArithFallback(JSContext* cx, Bas
             return false;
         }
         break;
       }
       default:
         MOZ_CRASH("Unhandled baseline arith op");
     }
 
-    // Check if debug mode toggling made the stub invalid.
-    if (stub.invalid()) {
-        return true;
-    }
-
     if (ret.isDouble()) {
         stub->setSawDoubleResult();
     }
 
     TryAttachStub<BinaryArithIRGenerator>("BinaryArith", cx, frame, stub, BaselineCacheIRStubKind::Regular, op, lhs, rhs, ret);
     return true;
 }
 
@@ -5751,21 +5924,19 @@ ICBinaryArith_Fallback::Compiler::genera
 
     return tailCallVM(DoBinaryArithFallbackInfo, masm);
 }
 
 //
 // Compare_Fallback
 //
 static bool
-DoCompareFallback(JSContext* cx, BaselineFrame* frame, ICCompare_Fallback* stub_, HandleValue lhs,
+DoCompareFallback(JSContext* cx, BaselineFrame* frame, ICCompare_Fallback* stub, HandleValue lhs,
                   HandleValue rhs, MutableHandleValue ret)
 {
-    // This fallback stub may trigger debug mode toggling.
-    DebugModeOSRVolatileStub<ICCompare_Fallback*> stub(frame, stub_);
     stub->incrementEnteredCount();
 
     RootedScript script(cx, frame->script());
     jsbytecode* pc = stub->icEntry()->pc(script);
     JSOp op = JSOp(*pc);
 
     FallbackICSpew(cx, stub, "Compare(%s)", CodeName[op]);
 
@@ -5819,21 +5990,16 @@ DoCompareFallback(JSContext* cx, Baselin
         break;
       default:
         MOZ_ASSERT_UNREACHABLE("Unhandled baseline compare op");
         return false;
     }
 
     ret.setBoolean(out);
 
-    // Check if debug mode toggling made the stub invalid.
-    if (stub.invalid()) {
-        return true;
-    }
-
     TryAttachStub<CompareIRGenerator>("Compare", cx, frame, stub, BaselineCacheIRStubKind::Regular, op, lhs, rhs);
     return true;
 }
 
 typedef bool (*DoCompareFallbackFn)(JSContext*, BaselineFrame*, ICCompare_Fallback*,
                                     HandleValue, HandleValue, MutableHandleValue);
 static const VMFunction DoCompareFallbackInfo =
     FunctionInfo<DoCompareFallbackFn>(DoCompareFallback, "DoCompareFallback", TailCall,
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -217,63 +217,161 @@ void TypeFallbackICSpew(JSContext* cx, I
 // An entry in the BaselineScript IC descriptor table. There's one ICEntry per
 // IC.
 class ICEntry
 {
     // A pointer to the first IC stub for this instruction.
     ICStub* firstStub_;
 
     // The PC of this IC's bytecode op within the JSScript.
-    uint32_t pcOffset_ : 31;
-    uint32_t isForOp_ : 1;
+    uint32_t pcOffset_;
 
   public:
-    ICEntry(ICStub* firstStub, uint32_t pcOffset, bool isForOp)
-      : firstStub_(firstStub), pcOffset_(pcOffset), isForOp_(uint32_t(isForOp))
-    {
-        // The offset must fit in at least 31 bits, since we shave off 1 for
-        // the isForOp_ flag.
-        MOZ_ASSERT(pcOffset_ == pcOffset);
-        JS_STATIC_ASSERT(BaselineMaxScriptLength <= (1u << 31) - 1);
-        MOZ_ASSERT(pcOffset <= BaselineMaxScriptLength);
-    }
+    // Non-op ICs are Baseline ICs used for function argument/this type
+    // monitoring in the script's prologue. All other ICs are "for op" ICs.
+    // Note: the last bytecode op in a script is always a return so UINT32_MAX
+    // is never a valid bytecode offset.
+    static constexpr uint32_t NonOpPCOffset = UINT32_MAX;
+
+    ICEntry(ICStub* firstStub, uint32_t pcOffset)
+      : firstStub_(firstStub), pcOffset_(pcOffset)
+    {}
 
     ICStub* firstStub() const {
         MOZ_ASSERT(firstStub_);
         return firstStub_;
     }
 
     ICFallbackStub* fallbackStub() const;
 
     void setFirstStub(ICStub* stub) {
         firstStub_ = stub;
     }
 
     uint32_t pcOffset() const {
-        return pcOffset_;
+        return pcOffset_ == NonOpPCOffset ? 0 : pcOffset_;
     }
     jsbytecode* pc(JSScript* script) const {
-        return script->offsetToPC(pcOffset_);
+        return script->offsetToPC(pcOffset());
     }
 
     static inline size_t offsetOfFirstStub() {
         return offsetof(ICEntry, firstStub_);
     }
 
     inline ICStub** addressOfFirstStub() {
         return &firstStub_;
     }
 
     bool isForOp() const {
-        return !!isForOp_;
+        return pcOffset_ != NonOpPCOffset;
     }
 
     void trace(JSTracer* trc);
 };
 
+// [SMDOC] ICScript
+//
+// ICScript contains IC data used by Baseline (Ion has its own IC chains, stored
+// in IonScript).
+//
+// For each IC we store an ICEntry, which points to the first ICStub in the
+// chain. Note that multiple stubs in the same zone can share Baseline IC code.
+// This works because the stub data is stored in the ICStub instead of baked in
+// in the stub code.
+//
+// Storing this separate from BaselineScript simplifies debug mode OSR because
+// the ICScript can be reused when we replace the BaselineScript. It also makes
+// it easier to experiment with interpreter ICs in the future because the
+// interpreter and Baseline JIT will be able to use exactly the same IC data.
+//
+// ICScript contains the following:
+//
+// * Fallback stub space: this stores all fallback stubs and the "can GC" stubs.
+//   These stubs are never purged before destroying the ICScript. (Other stubs
+//   are stored in the optimized stub space stored in JitZone and can be
+//   discarded more eagerly. See ICScript::purgeOptimizedStubs.)
+//
+// * List of IC entries, in the following order:
+//
+//   - Type monitor IC for |this|.
+//   - Type monitor IC for each formal argument.
+//   - IC for each JOF_IC bytecode op.
+//
+// ICScript is stored in TypeScript and allocated/destroyed at the same time.
+class ICScript
+{
+    // Allocated space for fallback stubs.
+    FallbackICStubSpace fallbackStubSpace_ = {};
+
+    uint32_t numICEntries_;
+
+    explicit ICScript(uint32_t numICEntries)
+      : numICEntries_(numICEntries)
+    {}
+
+    ICEntry* icEntryList() {
+        return (ICEntry*)(reinterpret_cast<uint8_t*>(this) + sizeof(ICScript));
+    }
+
+    void initICEntries(JSScript* script, const ICEntry* entries);
+
+  public:
+    static MOZ_MUST_USE js::UniquePtr<ICScript> create(JSContext* cx, JSScript* script);
+
+    ~ICScript() {
+        // The contents of the fallback stub space are removed and freed
+        // separately after the next minor GC. See prepareForDestruction.
+        MOZ_ASSERT(fallbackStubSpace_.isEmpty());
+    }
+    void prepareForDestruction(Zone* zone) {
+        // When the script contains pointers to nursery things, the store buffer can
+        // contain entries that point into the fallback stub space. Since we can
+        // destroy scripts outside the context of a GC, this situation could result
+        // in us trying to mark invalid store buffer entries.
+        //
+        // Defer freeing any allocated blocks until after the next minor GC.
+        fallbackStubSpace_.freeAllAfterMinorGC(zone);
+    }
+
+    FallbackICStubSpace* fallbackStubSpace() {
+        return &fallbackStubSpace_;
+    }
+
+    void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t* data,
+                                size_t* fallbackStubs) const {
+        *data += mallocSizeOf(this);
+
+        // |data| already includes the ICStubSpace itself, so use
+        // sizeOfExcludingThis.
+        *fallbackStubs += fallbackStubSpace_.sizeOfExcludingThis(mallocSizeOf);
+    }
+
+    size_t numICEntries() const {
+        return numICEntries_;
+    }
+
+    ICEntry& icEntry(size_t index) {
+        MOZ_ASSERT(index < numICEntries());
+        return icEntryList()[index];
+    }
+
+    void noteAccessedGetter(uint32_t pcOffset);
+    void noteHasDenseAdd(uint32_t pcOffset);
+
+    void trace(JSTracer* trc);
+    void purgeOptimizedStubs(Zone* zone);
+
+    ICEntry* maybeICEntryFromPCOffset(uint32_t pcOffset);
+    ICEntry* maybeICEntryFromPCOffset(uint32_t pcOffset, ICEntry* prevLookedUpEntry);
+
+    ICEntry& icEntryFromPCOffset(uint32_t pcOffset);
+    ICEntry& icEntryFromPCOffset(uint32_t pcOffset, ICEntry* prevLookedUpEntry);
+};
+
 class ICMonitoredStub;
 class ICMonitoredFallbackStub;
 class ICUpdatedStub;
 
 // Constant iterator that traverses arbitrary chains of ICStubs.
 // No requirements are made of the ICStub used to construct this
 // iterator, aside from that the stub be part of a nullptr-terminated
 // chain.
@@ -777,19 +875,16 @@ class ICCacheIR_Trait
 class ICCacheIR_Regular : public ICStub, public ICCacheIR_Trait<ICCacheIR_Regular>
 {
   public:
     ICCacheIR_Regular(JitCode* stubCode, const CacheIRStubInfo* stubInfo)
       : ICStub(ICStub::CacheIR_Regular, stubCode),
         ICCacheIR_Trait(stubInfo)
     {}
 
-    static ICCacheIR_Regular* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                                    ICCacheIR_Regular& other);
-
     void notePreliminaryObject() {
         extra_ = 1;
     }
     bool hasPreliminaryObject() const {
         return extra_;
     }
 
     uint8_t* stubDataStart();
@@ -830,19 +925,16 @@ class ICCacheIR_Monitored : public ICMon
 
   public:
     ICCacheIR_Monitored(JitCode* stubCode, ICStub* firstMonitorStub,
                         const CacheIRStubInfo* stubInfo)
       : ICMonitoredStub(ICStub::CacheIR_Monitored, stubCode, firstMonitorStub),
         ICCacheIR_Trait(stubInfo)
     {}
 
-    static ICCacheIR_Monitored* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                                      ICCacheIR_Monitored& other);
-
     void notePreliminaryObject() {
         extra_ = 1;
     }
     bool hasPreliminaryObject() const {
         return extra_;
     }
 
     uint8_t* stubDataStart();
@@ -925,19 +1017,16 @@ class ICCacheIR_Updated : public ICUpdat
   public:
     ICCacheIR_Updated(JitCode* stubCode, const CacheIRStubInfo* stubInfo)
       : ICUpdatedStub(ICStub::CacheIR_Updated, stubCode),
         ICCacheIR_Trait(stubInfo),
         updateStubGroup_(nullptr),
         updateStubId_(JSID_EMPTY)
     {}
 
-    static ICCacheIR_Updated* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                                    ICCacheIR_Updated& other);
-
     GCPtrObjectGroup& updateStubGroup() {
         return updateStubGroup_;
     }
     GCPtrId& updateStubId() {
         return updateStubId_;
     }
 
     void notePreliminaryObject() {
@@ -1046,22 +1135,18 @@ class ICStubCompiler
     template <typename T, typename... Args>
     T* newStub(Args&&... args) {
         return ICStub::New<T>(cx, std::forward<Args>(args)...);
     }
 
   public:
     virtual ICStub* getStub(ICStubSpace* space) = 0;
 
-    static ICStubSpace* StubSpaceForStub(bool makesGCCalls, JSScript* outerScript) {
-        if (makesGCCalls) {
-            return outerScript->baselineScript()->fallbackStubSpace();
-        }
-        return outerScript->zone()->jitZone()->optimizedStubSpace();
-    }
+    static ICStubSpace* StubSpaceForStub(bool makesGCCalls, JSScript* script);
+
     ICStubSpace* getStubSpace(JSScript* outerScript) {
         return StubSpaceForStub(ICStub::NonCacheIRStubMakesGCCalls(kind), outerScript);
     }
 };
 
 // WarmUpCounter_Fallback
 
 // A WarmUpCounter IC chain has only the fallback stub.
@@ -2109,19 +2194,16 @@ class ICCall_Scripted : public ICMonitor
     GCPtrObject templateObject_;
     uint32_t pcOffset_;
 
     ICCall_Scripted(JitCode* stubCode, ICStub* firstMonitorStub,
                     JSFunction* callee, JSObject* templateObject,
                     uint32_t pcOffset);
 
   public:
-    static ICCall_Scripted* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                                  ICCall_Scripted& other);
-
     GCPtrFunction& callee() {
         return callee_;
     }
     GCPtrObject& templateObject() {
         return templateObject_;
     }
 
     static size_t offsetOfCallee() {
@@ -2140,19 +2222,16 @@ class ICCall_AnyScripted : public ICMoni
     uint32_t pcOffset_;
 
     ICCall_AnyScripted(JitCode* stubCode, ICStub* firstMonitorStub, uint32_t pcOffset)
       : ICMonitoredStub(ICStub::Call_AnyScripted, stubCode, firstMonitorStub),
         pcOffset_(pcOffset)
     { }
 
   public:
-    static ICCall_AnyScripted* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                                     ICCall_AnyScripted& other);
-
     static size_t offsetOfPCOffset() {
         return offsetof(ICCall_AnyScripted, pcOffset_);
     }
 };
 
 // Compiler for Call_Scripted and Call_AnyScripted stubs.
 class ICCallScriptedCompiler : public ICCallStubCompiler {
   protected:
@@ -2221,19 +2300,16 @@ class ICCall_Native : public ICMonitored
     void* native_;
 #endif
 
     ICCall_Native(JitCode* stubCode, ICStub* firstMonitorStub,
                   JSFunction* callee, JSObject* templateObject,
                   uint32_t pcOffset);
 
   public:
-    static ICCall_Native* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                                ICCall_Native& other);
-
     GCPtrFunction& callee() {
         return callee_;
     }
     GCPtrObject& templateObject() {
         return templateObject_;
     }
 
     static size_t offsetOfCallee() {
@@ -2303,19 +2379,16 @@ class ICCall_ClassHook : public ICMonito
     GCPtrObject templateObject_;
     uint32_t pcOffset_;
 
     ICCall_ClassHook(JitCode* stubCode, ICStub* firstMonitorStub,
                      const Class* clasp, Native native, JSObject* templateObject,
                      uint32_t pcOffset);
 
   public:
-    static ICCall_ClassHook* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
-                                   ICCall_ClassHook& other);
-
     const Class* clasp() {
         return clasp_;
     }
     void* native() {
         return native_;
     }
     GCPtrObject& templateObject() {
         return templateObject_;
@@ -2381,21 +2454,16 @@ class ICCall_ScriptedApplyArray : public
     uint32_t pcOffset_;
 
     ICCall_ScriptedApplyArray(JitCode* stubCode, ICStub* firstMonitorStub, uint32_t pcOffset)
       : ICMonitoredStub(ICStub::Call_ScriptedApplyArray, stubCode, firstMonitorStub),
         pcOffset_(pcOffset)
     {}
 
   public:
-    static ICCall_ScriptedApplyArray* Clone(JSContext* cx,
-                                            ICStubSpace* space,
-                                            ICStub* firstMonitorStub,
-                                            ICCall_ScriptedApplyArray& other);
-
     static size_t offsetOfPCOffset() {
         return offsetof(ICCall_ScriptedApplyArray, pcOffset_);
     }
 
     // Compiler for this stub kind.
     class Compiler : public ICCallStubCompiler {
       protected:
         ICStub* firstMonitorStub_;
@@ -2424,21 +2492,16 @@ class ICCall_ScriptedApplyArguments : pu
     uint32_t pcOffset_;
 
     ICCall_ScriptedApplyArguments(JitCode* stubCode, ICStub* firstMonitorStub, uint32_t pcOffset)
       : ICMonitoredStub(ICStub::Call_ScriptedApplyArguments, stubCode, firstMonitorStub),
         pcOffset_(pcOffset)
     {}
 
   public:
-    static ICCall_ScriptedApplyArguments* Clone(JSContext* cx,
-                                                ICStubSpace* space,
-                                                ICStub* firstMonitorStub,
-                                                ICCall_ScriptedApplyArguments& other);
-
     static size_t offsetOfPCOffset() {
         return offsetof(ICCall_ScriptedApplyArguments, pcOffset_);
     }
 
     // Compiler for this stub kind.
     class Compiler : public ICCallStubCompiler {
       protected:
         ICStub* firstMonitorStub_;
@@ -2468,19 +2531,16 @@ class ICCall_ScriptedFunCall : public IC
     uint32_t pcOffset_;
 
     ICCall_ScriptedFunCall(JitCode* stubCode, ICStub* firstMonitorStub, uint32_t pcOffset)
       : ICMonitoredStub(ICStub::Call_ScriptedFunCall, stubCode, firstMonitorStub),
         pcOffset_(pcOffset)
     {}
 
   public:
-    static ICCall_ScriptedFunCall* Clone(JSContext* cx, ICStubSpace* space,
-                                         ICStub* firstMonitorStub, ICCall_ScriptedFunCall& other);
-
     static size_t offsetOfPCOffset() {
         return offsetof(ICCall_ScriptedFunCall, pcOffset_);
     }
 
     // Compiler for this stub kind.
     class Compiler : public ICCallStubCompiler {
       protected:
         ICStub* firstMonitorStub_;
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -231,29 +231,59 @@ GetCacheIRReceiverForUnboxedProperty(ICC
     if (!reader.matchOp(CacheOp::StoreUnboxedProperty)) {
         return false;
     }
 
     *receiver = ReceiverGuard(group, nullptr);
     return true;
 }
 
+ICScript*
+BaselineInspector::icScript() const
+{
+    return script->icScript();
+}
+
+ICEntry&
+BaselineInspector::icEntryFromPC(jsbytecode* pc)
+{
+    ICEntry* entry = maybeICEntryFromPC(pc);
+    MOZ_ASSERT(entry);
+    return *entry;
+}
+
+ICEntry*
+BaselineInspector::maybeICEntryFromPC(jsbytecode* pc)
+{
+    MOZ_ASSERT(hasICScript());
+    MOZ_ASSERT(isValidPC(pc));
+    ICEntry* ent =
+        icScript()->maybeICEntryFromPCOffset(script->pcToOffset(pc), prevLookedUpEntry);
+    if (!ent) {
+        return nullptr;
+    }
+
+    MOZ_ASSERT(ent->isForOp());
+    prevLookedUpEntry = ent;
+    return ent;
+}
+
 bool
 BaselineInspector::maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receivers,
                                           ObjectGroupVector& convertUnboxedGroups)
 {
     // Return a list of the receivers seen by the baseline IC for the current
     // op. Empty lists indicate no receivers are known, or there was an
     // uncacheable access. convertUnboxedGroups is used for unboxed object
     // groups which have been seen, but have had instances converted to native
     // objects and should be eagerly converted by Ion.
     MOZ_ASSERT(receivers.empty());
     MOZ_ASSERT(convertUnboxedGroups.empty());
 
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return true;
     }
 
     MOZ_ASSERT(isValidPC(pc));
     const ICEntry& entry = icEntryFromPC(pc);
 
     ICStub* stub = entry.firstStub();
     while (stub->next()) {
@@ -294,17 +324,17 @@ BaselineInspector::maybeInfoForPropertyO
     }
 
     return true;
 }
 
 ICStub*
 BaselineInspector::monomorphicStub(jsbytecode* pc)
 {
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return nullptr;
     }
 
     // IonBuilder::analyzeNewLoopTypes may call this (via expectedResultType
     // below) on code that's unreachable, according to BytecodeAnalysis. Use
     // maybeICEntryFromPC to handle this.
     const ICEntry* entry = maybeICEntryFromPC(pc);
     if (!entry) {
@@ -319,17 +349,17 @@ BaselineInspector::monomorphicStub(jsbyt
     }
 
     return stub;
 }
 
 bool
 BaselineInspector::dimorphicStub(jsbytecode* pc, ICStub** pfirst, ICStub** psecond)
 {
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return false;
     }
 
     const ICEntry& entry = icEntryFromPC(pc);
 
     ICStub* stub = entry.firstStub();
     ICStub* next = stub->next();
     ICStub* after = next ? next->next() : nullptr;
@@ -678,17 +708,17 @@ TryToSpecializeBinaryArithOp(ICStub** st
     MOZ_ASSERT(sawInt32);
     *result = MIRType::Int32;
     return true;
 }
 
 MIRType
 BaselineInspector::expectedBinaryArithSpecialization(jsbytecode* pc)
 {
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return MIRType::None;
     }
 
     MIRType result;
     ICStub* stubs[2];
 
     const ICEntry& entry = icEntryFromPC(pc);
     ICFallbackStub* stub = entry.fallbackStub();
@@ -711,33 +741,33 @@ BaselineInspector::expectedBinaryArithSp
     }
 
     return MIRType::None;
 }
 
 bool
 BaselineInspector::hasSeenNegativeIndexGetElement(jsbytecode* pc)
 {
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return false;
     }
 
     const ICEntry& entry = icEntryFromPC(pc);
     ICStub* stub = entry.fallbackStub();
 
     if (stub->isGetElem_Fallback()) {
         return stub->toGetElem_Fallback()->hasNegativeIndex();
     }
     return false;
 }
 
 bool
 BaselineInspector::hasSeenAccessedGetter(jsbytecode* pc)
 {
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return false;
     }
 
     const ICEntry& entry = icEntryFromPC(pc);
     ICStub* stub = entry.fallbackStub();
 
     if (stub->isGetProp_Fallback()) {
         return stub->toGetProp_Fallback()->hasAccessedGetter();
@@ -745,30 +775,30 @@ BaselineInspector::hasSeenAccessedGetter
     return false;
 }
 
 bool
 BaselineInspector::hasSeenNonStringIterMore(jsbytecode* pc)
 {
     MOZ_ASSERT(JSOp(*pc) == JSOP_MOREITER);
 
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return false;
     }
 
     const ICEntry& entry = icEntryFromPC(pc);
     ICStub* stub = entry.fallbackStub();
 
     return stub->toIteratorMore_Fallback()->hasNonStringResult();
 }
 
 bool
 BaselineInspector::hasSeenDoubleResult(jsbytecode* pc)
 {
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return false;
     }
 
     const ICEntry& entry = icEntryFromPC(pc);
     ICStub* stub = entry.fallbackStub();
 
     MOZ_ASSERT(stub->isUnaryArith_Fallback() || stub->isBinaryArith_Fallback());
 
@@ -777,17 +807,17 @@ BaselineInspector::hasSeenDoubleResult(j
     }
 
     return stub->toBinaryArith_Fallback()->sawDoubleResult();
 }
 
 JSObject*
 BaselineInspector::getTemplateObject(jsbytecode* pc)
 {
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return nullptr;
     }
 
     const ICEntry& entry = icEntryFromPC(pc);
     for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
         switch (stub->kind()) {
           case ICStub::NewArray_Fallback:
             return stub->toNewArray_Fallback()->templateObject();
@@ -806,17 +836,17 @@ BaselineInspector::getTemplateObject(jsb
     }
 
     return nullptr;
 }
 
 ObjectGroup*
 BaselineInspector::getTemplateObjectGroup(jsbytecode* pc)
 {
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return nullptr;
     }
 
     const ICEntry& entry = icEntryFromPC(pc);
     for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
         switch (stub->kind()) {
           case ICStub::NewArray_Fallback:
             return stub->toNewArray_Fallback()->templateGroup();
@@ -828,17 +858,17 @@ BaselineInspector::getTemplateObjectGrou
     return nullptr;
 }
 
 JSFunction*
 BaselineInspector::getSingleCallee(jsbytecode* pc)
 {
     MOZ_ASSERT(*pc == JSOP_NEW);
 
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return nullptr;
     }
 
     const ICEntry& entry = icEntryFromPC(pc);
     ICStub* stub = entry.firstStub();
 
     if (entry.fallbackStub()->state().hasFailures()) {
         return nullptr;
@@ -849,17 +879,17 @@ BaselineInspector::getSingleCallee(jsbyt
     }
 
     return stub->toCall_Scripted()->callee();
 }
 
 JSObject*
 BaselineInspector::getTemplateObjectForNative(jsbytecode* pc, Native native)
 {
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return nullptr;
     }
 
     const ICEntry& entry = icEntryFromPC(pc);
     for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
         if (stub->isCall_Native() && stub->toCall_Native()->callee()->native() == native) {
             return stub->toCall_Native()->templateObject();
         }
@@ -867,17 +897,17 @@ BaselineInspector::getTemplateObjectForN
 
     return nullptr;
 }
 
 bool
 BaselineInspector::isOptimizableConstStringSplit(jsbytecode* pc, JSString** strOut,
                                                  JSString** sepOut, ArrayObject** objOut)
 {
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return false;
     }
 
     const ICEntry& entry = icEntryFromPC(pc);
 
     // If ConstStringSplit stub is attached, must have only one stub attached.
     if (entry.fallbackStub()->numOptimizedStubs() != 1) {
         return false;
@@ -892,54 +922,54 @@ BaselineInspector::isOptimizableConstStr
     *sepOut = stub->toCall_ConstStringSplit()->expectedSep();
     *objOut = stub->toCall_ConstStringSplit()->templateObject();
     return true;
 }
 
 JSObject*
 BaselineInspector::getTemplateObjectForClassHook(jsbytecode* pc, const Class* clasp)
 {
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return nullptr;
     }
 
     const ICEntry& entry = icEntryFromPC(pc);
     for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
         if (stub->isCall_ClassHook() && stub->toCall_ClassHook()->clasp() == clasp) {
             return stub->toCall_ClassHook()->templateObject();
         }
     }
 
     return nullptr;
 }
 
 LexicalEnvironmentObject*
 BaselineInspector::templateNamedLambdaObject()
 {
-    if (!hasBaselineScript()) {
+    if (!script->hasBaselineScript()) {
         return nullptr;
     }
 
-    JSObject* res = baselineScript()->templateEnvironment();
+    JSObject* res = script->baselineScript()->templateEnvironment();
     if (script->bodyScope()->hasEnvironment()) {
         res = res->enclosingEnvironment();
     }
     MOZ_ASSERT(res);
 
     return &res->as<LexicalEnvironmentObject>();
 }
 
 CallObject*
 BaselineInspector::templateCallObject()
 {
-    if (!hasBaselineScript()) {
+    if (!script->hasBaselineScript()) {
         return nullptr;
     }
 
-    JSObject* res = baselineScript()->templateEnvironment();
+    JSObject* res = script->baselineScript()->templateEnvironment();
     MOZ_ASSERT(res);
 
     return &res->as<CallObject>();
 }
 
 static bool
 MatchCacheIRReceiverGuard(CacheIRReader& reader, ICStub* stub, const CacheIRStubInfo* stubInfo,
                           ObjOperandId objId, ReceiverGuard* receiver)
@@ -1217,17 +1247,17 @@ AddCacheIRGetPropFunction(ICCacheIR_Moni
 bool
 BaselineInspector::commonGetPropFunction(jsbytecode* pc, bool innerized,
                                          JSObject** holder, Shape** holderShape,
                                          JSFunction** commonGetter, Shape** globalShape,
                                          bool* isOwnProperty,
                                          ReceiverVector& receivers,
                                          ObjectGroupVector& convertUnboxedGroups)
 {
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return false;
     }
 
     MOZ_ASSERT(receivers.empty());
     MOZ_ASSERT(convertUnboxedGroups.empty());
 
     *globalShape = nullptr;
     *commonGetter = nullptr;
@@ -1286,17 +1316,17 @@ GetMegamorphicGetterSetterFunction(ICStu
     JSObject* obj = isGetter ? propShape->getterObject() : propShape->setterObject();
     return &obj->as<JSFunction>();
 }
 
 bool
 BaselineInspector::megamorphicGetterSetterFunction(jsbytecode* pc, bool isGetter,
                                                    JSFunction** getterOrSetter)
 {
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return false;
     }
 
     *getterOrSetter = nullptr;
     const ICEntry& entry = icEntryFromPC(pc);
 
     for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
         if (stub->isCacheIR_Monitored()) {
@@ -1443,17 +1473,17 @@ AddCacheIRSetPropFunction(ICCacheIR_Upda
 }
 
 bool
 BaselineInspector::commonSetPropFunction(jsbytecode* pc, JSObject** holder, Shape** holderShape,
                                          JSFunction** commonSetter, bool* isOwnProperty,
                                          ReceiverVector& receivers,
                                          ObjectGroupVector& convertUnboxedGroups)
 {
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return false;
     }
 
     MOZ_ASSERT(receivers.empty());
     MOZ_ASSERT(convertUnboxedGroups.empty());
 
     *commonSetter = nullptr;
     const ICEntry& entry = icEntryFromPC(pc);
@@ -1543,17 +1573,17 @@ BaselineInspector::maybeInfoForProtoRead
 {
     // This is like maybeInfoForPropertyOp, but for when the property exists on
     // the prototype.
 
     MOZ_ASSERT(receivers.empty());
     MOZ_ASSERT(convertUnboxedGroups.empty());
     MOZ_ASSERT(!*holder);
 
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return true;
     }
 
     MOZ_ASSERT(isValidPC(pc));
     const ICEntry& entry = icEntryFromPC(pc);
 
     ICStub* stub = entry.firstStub();
     while (stub->next()) {
@@ -1608,17 +1638,17 @@ GetCacheIRExpectedInputType(ICCacheIR_Mo
 
     MOZ_ASSERT_UNREACHABLE("Unexpected instruction");
     return MIRType::Value;
 }
 
 MIRType
 BaselineInspector::expectedPropertyAccessInputType(jsbytecode* pc)
 {
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return MIRType::Value;
     }
 
     const ICEntry& entry = icEntryFromPC(pc);
     MIRType type = MIRType::None;
 
     for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
         MIRType stubType = MIRType::None;
@@ -1648,17 +1678,17 @@ BaselineInspector::expectedPropertyAcces
     return (type == MIRType::None) ? MIRType::Value : type;
 }
 
 bool
 BaselineInspector::instanceOfData(jsbytecode* pc, Shape** shape, uint32_t* slot,
                                   JSObject** prototypeObject)
 {
     MOZ_ASSERT(*pc == JSOP_INSTANCEOF);
-    if (!hasBaselineScript()) {
+    if (!hasICScript()) {
         return false;
     }
 
     const ICEntry& entry = icEntryFromPC(pc);
     ICStub* firstStub = entry.firstStub();
 
     // Ensure singleton instanceof stub
     if (!firstStub->next() ||
--- a/js/src/jit/BaselineInspector.h
+++ b/js/src/jit/BaselineInspector.h
@@ -47,58 +47,36 @@ class BaselineInspector
 
   public:
     explicit BaselineInspector(JSScript* script)
       : script(script), prevLookedUpEntry(nullptr)
     {
         MOZ_ASSERT(script);
     }
 
-    bool hasBaselineScript() const {
-        return script->hasBaselineScript();
+    bool hasICScript() const {
+        return script->hasICScript();
     }
 
-    BaselineScript* baselineScript() const {
-        return script->baselineScript();
-    }
+    ICScript* icScript() const;
 
   private:
 #ifdef DEBUG
     bool isValidPC(jsbytecode* pc) {
         return script->containsPC(pc);
     }
 #endif
 
-    ICEntry& icEntryFromPC(jsbytecode* pc) {
-        MOZ_ASSERT(hasBaselineScript());
-        MOZ_ASSERT(isValidPC(pc));
-        ICEntry& ent =
-            baselineScript()->icEntryFromPCOffset(script->pcToOffset(pc), prevLookedUpEntry);
-        MOZ_ASSERT(ent.isForOp());
-        prevLookedUpEntry = &ent;
-        return ent;
-    }
-
-    ICEntry* maybeICEntryFromPC(jsbytecode* pc) {
-        MOZ_ASSERT(hasBaselineScript());
-        MOZ_ASSERT(isValidPC(pc));
-        ICEntry* ent =
-            baselineScript()->maybeICEntryFromPCOffset(script->pcToOffset(pc), prevLookedUpEntry);
-        if (!ent) {
-            return nullptr;
-        }
-        MOZ_ASSERT(ent->isForOp());
-        prevLookedUpEntry = ent;
-        return ent;
-    }
+    ICEntry& icEntryFromPC(jsbytecode* pc);
+    ICEntry* maybeICEntryFromPC(jsbytecode* pc);
 
     template <typename ICInspectorType>
     ICInspectorType makeICInspector(jsbytecode* pc, ICStub::Kind expectedFallbackKind) {
         ICEntry* ent = nullptr;
-        if (hasBaselineScript()) {
+        if (hasICScript()) {
             ent = &icEntryFromPC(pc);
             MOZ_ASSERT(ent->fallbackStub()->kind() == expectedFallbackKind);
         }
         return ICInspectorType(this, pc, ent);
     }
 
     ICStub* monomorphicStub(jsbytecode* pc);
     MOZ_MUST_USE bool dimorphicStub(jsbytecode* pc, ICStub** pfirst, ICStub** psecond);
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -12,16 +12,17 @@
 
 #include "gc/FreeOp.h"
 #include "jit/BaselineCompiler.h"
 #include "jit/BaselineIC.h"
 #include "jit/CompileInfo.h"
 #include "jit/IonControlFlow.h"
 #include "jit/JitCommon.h"
 #include "jit/JitSpewer.h"
+#include "util/StructuredSpewer.h"
 #include "vm/Debugger.h"
 #include "vm/Interpreter.h"
 #include "vm/TraceLogging.h"
 #include "wasm/WasmInstance.h"
 
 #include "gc/PrivateIterators-inl.h"
 #include "jit/JitFrames-inl.h"
 #include "jit/MacroAssembler-inl.h"
@@ -347,42 +348,38 @@ jit::CanEnterBaselineMethod(JSContext* c
 
 BaselineScript*
 BaselineScript::New(JSScript* jsscript,
                     uint32_t bailoutPrologueOffset,
                     uint32_t debugOsrPrologueOffset,
                     uint32_t debugOsrEpilogueOffset,
                     uint32_t profilerEnterToggleOffset,
                     uint32_t profilerExitToggleOffset,
-                    size_t icEntries,
                     size_t retAddrEntries,
                     size_t pcMappingIndexEntries, size_t pcMappingSize,
                     size_t bytecodeTypeMapEntries,
                     size_t resumeEntries,
                     size_t traceLoggerToggleOffsetEntries)
 {
     static const unsigned DataAlignment = sizeof(uintptr_t);
 
-    size_t icEntriesSize = icEntries * sizeof(ICEntry);
     size_t retAddrEntriesSize = retAddrEntries * sizeof(RetAddrEntry);
     size_t pcMappingIndexEntriesSize = pcMappingIndexEntries * sizeof(PCMappingIndexEntry);
     size_t bytecodeTypeMapSize = bytecodeTypeMapEntries * sizeof(uint32_t);
     size_t resumeEntriesSize = resumeEntries * sizeof(uintptr_t);
     size_t tlEntriesSize = traceLoggerToggleOffsetEntries * sizeof(uint32_t);
 
-    size_t paddedICEntriesSize = AlignBytes(icEntriesSize, DataAlignment);
     size_t paddedRetAddrEntriesSize = AlignBytes(retAddrEntriesSize, DataAlignment);
     size_t paddedPCMappingIndexEntriesSize = AlignBytes(pcMappingIndexEntriesSize, DataAlignment);
     size_t paddedPCMappingSize = AlignBytes(pcMappingSize, DataAlignment);
     size_t paddedBytecodeTypesMapSize = AlignBytes(bytecodeTypeMapSize, DataAlignment);
     size_t paddedResumeEntriesSize = AlignBytes(resumeEntriesSize, DataAlignment);
     size_t paddedTLEntriesSize = AlignBytes(tlEntriesSize, DataAlignment);
 
-    size_t allocBytes = paddedICEntriesSize +
-                        paddedRetAddrEntriesSize +
+    size_t allocBytes = paddedRetAddrEntriesSize +
                         paddedPCMappingIndexEntriesSize +
                         paddedPCMappingSize +
                         paddedBytecodeTypesMapSize +
                         paddedResumeEntriesSize +
                         paddedTLEntriesSize;
 
     BaselineScript* script = jsscript->zone()->pod_malloc_with_extra<BaselineScript, uint8_t>(allocBytes);
     if (!script) {
@@ -392,20 +389,16 @@ BaselineScript::New(JSScript* jsscript,
                                 debugOsrPrologueOffset,
                                 debugOsrEpilogueOffset,
                                 profilerEnterToggleOffset,
                                 profilerExitToggleOffset);
 
     size_t offsetCursor = sizeof(BaselineScript);
     MOZ_ASSERT(offsetCursor == AlignBytes(sizeof(BaselineScript), DataAlignment));
 
-    script->icEntriesOffset_ = offsetCursor;
-    script->icEntries_ = icEntries;
-    offsetCursor += paddedICEntriesSize;
-
     script->retAddrEntriesOffset_ = offsetCursor;
     script->retAddrEntries_ = retAddrEntries;
     offsetCursor += paddedRetAddrEntriesSize;
 
     script->pcMappingIndexOffset_ = offsetCursor;
     script->pcMappingIndexEntries_ = pcMappingIndexEntries;
     offsetCursor += paddedPCMappingIndexEntriesSize;
 
@@ -427,17 +420,21 @@ BaselineScript::New(JSScript* jsscript,
     return script;
 }
 
 void
 BaselineScript::trace(JSTracer* trc)
 {
     TraceEdge(trc, &method_, "baseline-method");
     TraceNullableEdge(trc, &templateEnv_, "baseline-template-environment");
+}
 
+void
+ICScript::trace(JSTracer* trc)
+{
     // Mark all IC stub codes hanging off the IC stub entries.
     for (size_t i = 0; i < numICEntries(); i++) {
         ICEntry& ent = icEntry(i);
         ent.trace(trc);
     }
 }
 
 /* static */
@@ -458,26 +455,16 @@ BaselineScript::Trace(JSTracer* trc, Bas
 void
 BaselineScript::Destroy(FreeOp* fop, BaselineScript* script)
 {
 
     MOZ_ASSERT(!script->hasPendingIonBuilder());
 
     script->unlinkDependentWasmImports(fop);
 
-    /*
-     * When the script contains pointers to nursery things, the store buffer can
-     * contain entries that point into the fallback stub space. Since we can
-     * destroy scripts outside the context of a GC, this situation could result
-     * in us trying to mark invalid store buffer entries.
-     *
-     * Defer freeing any allocated blocks until after the next minor GC.
-     */
-    script->fallbackStubSpace_.freeAllAfterMinorGC(script->method()->zone());
-
     fop->delete_(script);
 }
 
 void
 JS::DeletePolicy<js::jit::BaselineScript>::operator()(const js::jit::BaselineScript* script)
 {
     BaselineScript::Destroy(rt_->defaultFreeOp(), const_cast<BaselineScript*>(script));
 }
@@ -529,23 +516,16 @@ BaselineScript::removeDependentWasmImpor
     for (DependentWasmImport& dep : *dependentWasmImports_) {
         if (dep.instance == &instance && dep.importIndex == idx) {
             dependentWasmImports_->erase(&dep);
             break;
         }
     }
 }
 
-ICEntry&
-BaselineScript::icEntry(size_t index)
-{
-    MOZ_ASSERT(index < numICEntries());
-    return icEntryList()[index];
-}
-
 RetAddrEntry&
 BaselineScript::retAddrEntry(size_t index)
 {
     MOZ_ASSERT(index < numRetAddrEntries());
     return retAddrEntryList()[index];
 }
 
 PCMappingIndexEntry&
@@ -567,25 +547,25 @@ BaselineScript::pcMappingReader(size_t i
 
     return CompactBufferReader(dataStart, dataEnd);
 }
 
 struct ICEntries
 {
     using EntryT = ICEntry;
 
-    BaselineScript* const baseline_;
+    ICScript* const icScript_;
 
-    explicit ICEntries(BaselineScript* baseline) : baseline_(baseline) {}
+    explicit ICEntries(ICScript* icScript) : icScript_(icScript) {}
 
     size_t numEntries() const {
-        return baseline_->numICEntries();
+        return icScript_->numICEntries();
     }
     ICEntry& operator[](size_t index) const {
-        return baseline_->icEntry(index);
+        return icScript_->icEntry(index);
     }
 };
 
 struct RetAddrEntries
 {
     using EntryT = RetAddrEntry;
 
     BaselineScript* const baseline_;
@@ -622,21 +602,21 @@ BaselineScript::retAddrEntryFromReturnOf
                        &loc);
 
     MOZ_ASSERT(found);
     MOZ_ASSERT(loc < numRetAddrEntries());
     MOZ_ASSERT(retAddrEntry(loc).returnOffset().offset() == returnOffset.offset());
     return retAddrEntry(loc);
 }
 
-template <typename Entries>
+template <typename Entries, typename ScriptT>
 static inline bool
-ComputeBinarySearchMid(BaselineScript* baseline, uint32_t pcOffset, size_t* loc)
+ComputeBinarySearchMid(ScriptT* script, uint32_t pcOffset, size_t* loc)
 {
-    Entries entries(baseline);
+    Entries entries(script);
     return BinarySearchIf(entries, 0, entries.numEntries(),
                           [pcOffset](typename Entries::EntryT& entry) {
                               uint32_t entryOffset = entry.pcOffset();
                               if (pcOffset < entryOffset) {
                                   return -1;
                               }
                               if (entryOffset < pcOffset) {
                                   return 1;
@@ -648,17 +628,17 @@ ComputeBinarySearchMid(BaselineScript* b
 
 uint8_t*
 BaselineScript::returnAddressForEntry(const RetAddrEntry& ent)
 {
     return method()->raw() + ent.returnOffset().offset();
 }
 
 ICEntry*
-BaselineScript::maybeICEntryFromPCOffset(uint32_t pcOffset)
+ICScript::maybeICEntryFromPCOffset(uint32_t pcOffset)
 {
     // Multiple IC entries can have the same PC offset, but this method only looks for
     // those which have isForOp() set.
     size_t mid;
     if (!ComputeBinarySearchMid<ICEntries>(this, pcOffset, &mid)) {
         return nullptr;
     }
 
@@ -682,25 +662,25 @@ BaselineScript::maybeICEntryFromPCOffset
         if (icEntry(i).isForOp()) {
             return &icEntry(i);
         }
     }
     return nullptr;
 }
 
 ICEntry&
-BaselineScript::icEntryFromPCOffset(uint32_t pcOffset)
+ICScript::icEntryFromPCOffset(uint32_t pcOffset)
 {
     ICEntry* entry = maybeICEntryFromPCOffset(pcOffset);
     MOZ_RELEASE_ASSERT(entry);
     return *entry;
 }
 
 ICEntry*
-BaselineScript::maybeICEntryFromPCOffset(uint32_t pcOffset, ICEntry* prevLookedUpEntry)
+ICScript::maybeICEntryFromPCOffset(uint32_t pcOffset, ICEntry* prevLookedUpEntry)
 {
     // Do a linear forward search from the last queried PC offset, or fallback to a
     // binary search if the last offset is too far away.
     if (prevLookedUpEntry && pcOffset >= prevLookedUpEntry->pcOffset() &&
         (pcOffset - prevLookedUpEntry->pcOffset()) <= 10)
     {
         ICEntry* firstEntry = &icEntry(0);
         ICEntry* lastEntry = &icEntry(numICEntries() - 1);
@@ -713,17 +693,17 @@ BaselineScript::maybeICEntryFromPCOffset
         }
         return nullptr;
     }
 
     return maybeICEntryFromPCOffset(pcOffset);
 }
 
 ICEntry&
-BaselineScript::icEntryFromPCOffset(uint32_t pcOffset, ICEntry* prevLookedUpEntry)
+ICScript::icEntryFromPCOffset(uint32_t pcOffset, ICEntry* prevLookedUpEntry)
 {
     ICEntry* entry = maybeICEntryFromPCOffset(pcOffset, prevLookedUpEntry);
     MOZ_RELEASE_ASSERT(entry);
     return *entry;
 }
 
 RetAddrEntry&
 BaselineScript::retAddrEntryFromPCOffset(uint32_t pcOffset, RetAddrEntry::Kind kind)
@@ -797,23 +777,23 @@ BaselineScript::computeResumeNativeOffse
     };
 
     mozilla::Span<const uint32_t> pcOffsets = script->resumeOffsets();
     uint8_t** nativeOffsets = resumeEntryList();
     std::transform(pcOffsets.begin(), pcOffsets.end(), nativeOffsets, computeNative);
 }
 
 void
-BaselineScript::copyICEntries(JSScript* script, const ICEntry* entries)
+ICScript::initICEntries(JSScript* script, const ICEntry* entries)
 {
     // Fix up the return offset in the IC entries and copy them in.
     // Also write out the IC entry ptrs in any fallback stubs that were added.
     for (uint32_t i = 0; i < numICEntries(); i++) {
         ICEntry& realEntry = icEntry(i);
-        realEntry = entries[i];
+        new (&realEntry) ICEntry(entries[i]);
 
         // If the attached stub is a fallback stub, then fix it up with
         // a pointer to the (now available) realEntry.
         if (realEntry.firstStub()->isFallback()) {
             realEntry.firstStub()->toFallbackStub()->fixupICEntry(&realEntry);
         }
 
         if (realEntry.firstStub()->isTypeMonitor_Fallback()) {
@@ -827,22 +807,16 @@ void
 BaselineScript::copyRetAddrEntries(JSScript* script, const RetAddrEntry* entries)
 {
     for (uint32_t i = 0; i < numRetAddrEntries(); i++) {
         retAddrEntry(i) = entries[i];
     }
 }
 
 void
-BaselineScript::adoptFallbackStubs(FallbackICStubSpace* stubSpace)
-{
-    fallbackStubSpace_.adoptFrom(stubSpace);
-}
-
-void
 BaselineScript::copyPCMappingEntries(const CompactBufferWriter& entries)
 {
     MOZ_ASSERT(entries.length() > 0);
     MOZ_ASSERT(entries.length() == pcMappingSize_);
 
     memcpy(pcMappingData(), entries.buffer(), entries.length());
 }
 
@@ -1101,17 +1075,17 @@ BaselineScript::toggleProfilerInstrument
     } else {
         Assembler::ToggleToJmp(enterToggleLocation);
         Assembler::ToggleToJmp(exitToggleLocation);
         flags_ &= ~uint32_t(PROFILER_INSTRUMENTATION_ON);
     }
 }
 
 void
-BaselineScript::purgeOptimizedStubs(Zone* zone)
+ICScript::purgeOptimizedStubs(Zone* zone)
 {
     JitSpew(JitSpew_BaselineIC, "Purging optimized stubs");
 
     for (size_t i = 0; i < numICEntries(); i++) {
         ICEntry& entry = icEntry(i);
         ICStub* lastStub = entry.firstStub();
         while (lastStub->next()) {
             lastStub = lastStub->next();
@@ -1157,17 +1131,17 @@ BaselineScript::purgeOptimizedStubs(Zone
         while (stub->next()) {
             MOZ_ASSERT(stub->allocatedInFallbackSpace());
             stub = stub->next();
         }
     }
 #endif
 }
 
-#ifdef JS_JITSPEW
+#ifdef JS_STRUCTURED_SPEW
 static bool
 GetStubEnteredCount(ICStub* stub, uint32_t* count)
 {
     switch (stub->kind()) {
       case ICStub::CacheIR_Regular:
         *count = stub->toCacheIR_Regular()->enteredCount();
         return true;
       case ICStub::CacheIR_Updated:
@@ -1176,74 +1150,89 @@ GetStubEnteredCount(ICStub* stub, uint32
       case ICStub::CacheIR_Monitored:
         *count = stub->toCacheIR_Monitored()->enteredCount();
         return true;
       default:
         return false;
     }
 }
 
+bool
+HasEnteredCounters(ICEntry& entry)
+{
+    ICStub* stub = entry.firstStub();
+    while (stub && !stub->isFallback()) {
+        uint32_t count;
+        if (GetStubEnteredCount(stub, &count)) {
+            return true;
+        }
+        stub = stub->next();
+    }
+    return false;
+}
+
 void
 jit::JitSpewBaselineICStats(JSScript* script, const char* dumpReason)
 {
-    MOZ_ASSERT(script->hasBaselineScript());
-    BaselineScript* blScript = script->baselineScript();
-
-    if (!JitSpewEnabled(JitSpew_BaselineIC_Statistics)) {
+    MOZ_ASSERT(script->hasICScript());
+    JSContext* cx = TlsContext.get();
+    AutoStructuredSpewer spew(cx, SpewChannel::BaselineICStats, script);
+    if (!spew) {
         return;
     }
 
-    Fprinter& out = JitSpewPrinter();
-
-    out.printf("[BaselineICStats] Dumping IC info for %s script %s:%d:%d\n",
-                dumpReason, script->filename(), script->lineno(),
-                script->column());
-
-    for (size_t i = 0; i < blScript->numICEntries(); i++) {
-        ICEntry& entry = blScript->icEntry(i);
+    ICScript* icScript = script->icScript();
+    spew->property("reason", dumpReason);
+    spew->beginListProperty("entries");
+    for (size_t i = 0; i < icScript->numICEntries(); i++) {
+        ICEntry& entry = icScript->icEntry(i);
+        if (!HasEnteredCounters(entry)) {
+            continue;
+        }
 
         uint32_t pcOffset = entry.pcOffset();
         jsbytecode* pc = entry.pc(script);
 
         unsigned column;
         unsigned int line = PCToLineNumber(script, pc, &column);
-        out.printf("[BaselineICStats]     %s - pc=%u line=%u col=%u\n",
-                   CodeName[*pc], pcOffset, line, column);
 
+        spew->beginObject();
+        spew->property("op", CodeName[*pc]);
+        spew->property("pc", pcOffset);
+        spew->property("line", line);
+        spew->property("column", column);
+
+        spew->beginListProperty("counts");
         ICStub* stub = entry.firstStub();
-        out.printf("[BaselineICStats]          ");
-        while (stub) {
+        while (stub && !stub->isFallback()) {
             uint32_t count;
             if (GetStubEnteredCount(stub, &count)) {
-                out.printf("%u -> ", count);
-            } else if (stub->isFallback()) {
-                out.printf("(fb) %u", stub->toFallbackStub()->enteredCount());
+                spew->value(count);
             } else {
-                out.printf(" ?? -> ");
+                spew->value("?");
             }
             stub = stub->next();
         }
-        out.printf("\n");
+        spew->endList();
+        spew->property("fallback_count", entry.fallbackStub()->enteredCount());
+        spew->endObject();
     }
+    spew->endList();
 }
 #endif
 
 
 void
 jit::FinishDiscardBaselineScript(FreeOp* fop, JSScript* script)
 {
     if (!script->hasBaselineScript()) {
         return;
     }
 
     if (script->baselineScript()->active()) {
-        // Script is live on the stack. Keep the BaselineScript, but destroy
-        // stubs allocated in the optimized stub space.
-        script->baselineScript()->purgeOptimizedStubs(script->zone());
-
         // Reset |active| flag so that we don't need a separate script
         // iteration to unmark them.
         script->baselineScript()->resetActive();
 
         // The baseline caches have been wiped out, so the script will need to
         // warm back up before it can be inlined during Ion compilation.
         script->baselineScript()->clearIonCompiledOrInlined();
         return;
@@ -1253,18 +1242,23 @@ jit::FinishDiscardBaselineScript(FreeOp*
     script->setBaselineScript(fop->runtime(), nullptr);
     BaselineScript::Destroy(fop, baseline);
 }
 
 void
 jit::AddSizeOfBaselineData(JSScript* script, mozilla::MallocSizeOf mallocSizeOf, size_t* data,
                            size_t* fallbackStubs)
 {
+    if (script->hasICScript()) {
+        // ICScript is stored in TypeScript but we report its size here and not
+        // in TypeScript::sizeOfIncludingThis.
+        script->icScript()->addSizeOfIncludingThis(mallocSizeOf, data, fallbackStubs);
+    }
     if (script->hasBaselineScript()) {
-        script->baselineScript()->addSizeOfIncludingThis(mallocSizeOf, data, fallbackStubs);
+        script->baselineScript()->addSizeOfIncludingThis(mallocSizeOf, data);
     }
 }
 
 void
 jit::ToggleBaselineProfiling(JSRuntime* runtime, bool enable)
 {
     JitRuntime* jrt = runtime->jitRuntime();
     if (!jrt) {
--- a/js/src/jit/BaselineJIT.h
+++ b/js/src/jit/BaselineJIT.h
@@ -240,19 +240,16 @@ struct BaselineScript final
     // Code pointer containing the actual method.
     HeapPtr<JitCode*> method_ = nullptr;
 
     // For functions with a call object, template objects to use for the call
     // object and decl env object (linked via the call object's enclosing
     // scope).
     HeapPtr<EnvironmentObject*> templateEnv_ = nullptr;
 
-    // Allocated space for fallback stubs.
-    FallbackICStubSpace fallbackStubSpace_ = {};
-
     // If non-null, the list of wasm::Modules that contain an optimized call
     // directly to this script.
     Vector<DependentWasmImport>* dependentWasmImports_ = nullptr;
 
     // Early Ion bailouts will enter at this address. This is after frame
     // construction and before environment chain is initialized.
     uint32_t bailoutPrologueOffset_;
 
@@ -308,19 +305,16 @@ struct BaselineScript final
     };
 
   private:
     uint32_t flags_ = 0;
 
   private:
     void trace(JSTracer* trc);
 
-    uint32_t icEntriesOffset_ = 0;
-    uint32_t icEntries_ = 0;
-
     uint32_t retAddrEntriesOffset_ = 0;
     uint32_t retAddrEntries_ = 0;
 
     uint32_t pcMappingIndexOffset_ = 0;
     uint32_t pcMappingIndexEntries_ = 0;
 
     uint32_t pcMappingOffset_ = 0;
     uint32_t pcMappingSize_ = 0;
@@ -365,51 +359,37 @@ struct BaselineScript final
       : bailoutPrologueOffset_(bailoutPrologueOffset),
         debugOsrPrologueOffset_(debugOsrPrologueOffset),
         debugOsrEpilogueOffset_(debugOsrEpilogueOffset),
         profilerEnterToggleOffset_(profilerEnterToggleOffset),
         profilerExitToggleOffset_(profilerExitToggleOffset)
     { }
 
   public:
-    ~BaselineScript() {
-        // The contents of the fallback stub space are removed and freed
-        // separately after the next minor GC. See BaselineScript::Destroy.
-        MOZ_ASSERT(fallbackStubSpace_.isEmpty());
-    }
-
     static BaselineScript* New(JSScript* jsscript,
                                uint32_t bailoutPrologueOffset,
                                uint32_t debugOsrPrologueOffset,
                                uint32_t debugOsrEpilogueOffset,
                                uint32_t profilerEnterToggleOffset,
                                uint32_t profilerExitToggleOffset,
-                               size_t icEntries,
                                size_t retAddrEntries,
                                size_t pcMappingIndexEntries, size_t pcMappingSize,
                                size_t bytecodeTypeMapEntries,
                                size_t resumeEntries,
                                size_t traceLoggerToggleOffsetEntries);
 
     static void Trace(JSTracer* trc, BaselineScript* script);
     static void Destroy(FreeOp* fop, BaselineScript* script);
 
-    void purgeOptimizedStubs(Zone* zone);
-
     static inline size_t offsetOfMethod() {
         return offsetof(BaselineScript, method_);
     }
 
-    void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t* data,
-                                size_t* fallbackStubs) const {
+    void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t* data) const {
         *data += mallocSizeOf(this);
-
-        // |data| already includes the ICStubSpace itself, so use
-        // sizeOfExcludingThis.
-        *fallbackStubs += fallbackStubSpace_.sizeOfExcludingThis(mallocSizeOf);
     }
 
     bool active() const {
         return flags_ & ACTIVE;
     }
     void setActive() {
         flags_ |= ACTIVE;
     }
@@ -453,34 +433,28 @@ struct BaselineScript final
     }
     uint8_t* debugOsrPrologueEntryAddr() const {
         return method_->raw() + debugOsrPrologueOffset_;
     }
     uint8_t* debugOsrEpilogueEntryAddr() const {
         return method_->raw() + debugOsrEpilogueOffset_;
     }
 
-    ICEntry* icEntryList() {
-        return (ICEntry*)(reinterpret_cast<uint8_t*>(this) + icEntriesOffset_);
-    }
     RetAddrEntry* retAddrEntryList() {
         return (RetAddrEntry*)(reinterpret_cast<uint8_t*>(this) + retAddrEntriesOffset_);
     }
     uint8_t** resumeEntryList() {
         return (uint8_t**)(reinterpret_cast<uint8_t*>(this) + resumeEntriesOffset_);
     }
     PCMappingIndexEntry* pcMappingIndexEntryList() {
         return (PCMappingIndexEntry*)(reinterpret_cast<uint8_t*>(this) + pcMappingIndexOffset_);
     }
     uint8_t* pcMappingData() {
         return reinterpret_cast<uint8_t*>(this) + pcMappingOffset_;
     }
-    FallbackICStubSpace* fallbackStubSpace() {
-        return &fallbackStubSpace_;
-    }
 
     JitCode* method() const {
         return method_;
     }
     void setMethod(JitCode* code) {
         MOZ_ASSERT(!method_);
         method_ = code;
     }
@@ -492,45 +466,30 @@ struct BaselineScript final
         MOZ_ASSERT(!templateEnv_);
         templateEnv_ = templateEnv;
     }
 
     bool containsCodeAddress(uint8_t* addr) const {
         return method()->raw() <= addr && addr <= method()->raw() + method()->instructionsSize();
     }
 
-    ICEntry* maybeICEntryFromPCOffset(uint32_t pcOffset);
-    ICEntry* maybeICEntryFromPCOffset(uint32_t pcOffset,
-                                      ICEntry* prevLookedUpEntry);
-
-    ICEntry& icEntry(size_t index);
-    ICEntry& icEntryFromPCOffset(uint32_t pcOffset);
-    ICEntry& icEntryFromPCOffset(uint32_t pcOffset, ICEntry* prevLookedUpEntry);
-
     uint8_t* returnAddressForEntry(const RetAddrEntry& ent);
 
     RetAddrEntry& retAddrEntry(size_t index);
     RetAddrEntry& retAddrEntryFromPCOffset(uint32_t pcOffset, RetAddrEntry::Kind kind);
     RetAddrEntry& prologueRetAddrEntry(RetAddrEntry::Kind kind);
     RetAddrEntry& retAddrEntryFromReturnOffset(CodeOffset returnOffset);
     RetAddrEntry& retAddrEntryFromReturnAddress(uint8_t* returnAddr);
 
-    size_t numICEntries() const {
-        return icEntries_;
-    }
-
     size_t numRetAddrEntries() const {
         return retAddrEntries_;
     }
 
-    void copyICEntries(JSScript* script, const ICEntry* entries);
     void copyRetAddrEntries(JSScript* script, const RetAddrEntry* entries);
 
-    void adoptFallbackStubs(FallbackICStubSpace* stubSpace);
-
     // Copy resumeOffsets list from |script| and convert the pcOffsets
     // to native addresses in the Baseline code.
     void computeResumeNativeOffsets(JSScript* script);
 
     PCMappingIndexEntry& pcMappingIndexEntry(size_t index);
     CompactBufferReader pcMappingReader(size_t indexEntry);
 
     size_t numPCMappingIndexEntries() const {
@@ -581,19 +540,16 @@ struct BaselineScript final
 
     uint32_t* traceLoggerToggleOffsets() {
         MOZ_ASSERT(traceLoggerToggleOffsetsOffset_);
         return reinterpret_cast<uint32_t*>(reinterpret_cast<uint8_t*>(this) +
                                            traceLoggerToggleOffsetsOffset_);
     }
 #endif
 
-    void noteAccessedGetter(uint32_t pcOffset);
-    void noteHasDenseAdd(uint32_t pcOffset);
-
     static size_t offsetOfFlags() {
         return offsetof(BaselineScript, flags_);
     }
     static size_t offsetOfResumeEntriesOffset() {
         return offsetof(BaselineScript, resumeEntriesOffset_);
     }
 
     static void writeBarrierPre(Zone* zone, BaselineScript* script);
@@ -761,17 +717,17 @@ BailoutIonToBaseline(JSContext* cx, JitA
 // Mark baseline scripts on the stack as active, so that they are not discarded
 // during GC.
 void
 MarkActiveBaselineScripts(Zone* zone);
 
 MethodStatus
 BaselineCompile(JSContext* cx, JSScript* script, bool forceDebugInstrumentation = false);
 
-#ifdef JS_JITSPEW
+#ifdef JS_STRUCTURED_SPEW
 void
 JitSpewBaselineICStats(JSScript* script, const char* dumpReason);
 #endif
 
 static const unsigned BASELINE_MAX_ARGS_LENGTH = 20000;
 
 } // namespace jit
 } // namespace js
--- a/js/src/jit/CacheIRCompiler.cpp
+++ b/js/src/jit/CacheIRCompiler.cpp
@@ -938,65 +938,16 @@ CacheIRStubInfo::stubDataSize() const
         StubField::Type type = fieldType(field++);
         if (type == StubField::Type::Limit) {
             return size;
         }
         size += StubField::sizeInBytes(type);
     }
 }
 
-void
-CacheIRStubInfo::copyStubData(ICStub* src, ICStub* dest) const
-{
-    uint8_t* srcBytes = reinterpret_cast<uint8_t*>(src);
-    uint8_t* destBytes = reinterpret_cast<uint8_t*>(dest);
-
-    size_t field = 0;
-    size_t offset = 0;
-    while (true) {
-        StubField::Type type = fieldType(field);
-        switch (type) {
-          case StubField::Type::RawWord:
-            *reinterpret_cast<uintptr_t*>(destBytes + offset) =
-                *reinterpret_cast<uintptr_t*>(srcBytes + offset);
-            break;
-          case StubField::Type::RawInt64:
-          case StubField::Type::DOMExpandoGeneration:
-            *reinterpret_cast<uint64_t*>(destBytes + offset) =
-                *reinterpret_cast<uint64_t*>(srcBytes + offset);
-            break;
-          case StubField::Type::Shape:
-            getStubField<ICStub, Shape*>(dest, offset).init(getStubField<ICStub, Shape*>(src, offset));
-            break;
-          case StubField::Type::JSObject:
-            getStubField<ICStub, JSObject*>(dest, offset).init(getStubField<ICStub, JSObject*>(src, offset));
-            break;
-          case StubField::Type::ObjectGroup:
-            getStubField<ICStub, ObjectGroup*>(dest, offset).init(getStubField<ICStub, ObjectGroup*>(src, offset));
-            break;
-          case StubField::Type::Symbol:
-            getStubField<ICStub, JS::Symbol*>(dest, offset).init(getStubField<ICStub, JS::Symbol*>(src, offset));
-            break;
-          case StubField::Type::String:
-            getStubField<ICStub, JSString*>(dest, offset).init(getStubField<ICStub, JSString*>(src, offset));
-            break;
-          case StubField::Type::Id:
-            getStubField<ICStub, jsid>(dest, offset).init(getStubField<ICStub, jsid>(src, offset));
-            break;
-          case StubField::Type::Value:
-            getStubField<ICStub, Value>(dest, offset).init(getStubField<ICStub, Value>(src, offset));
-            break;
-          case StubField::Type::Limit:
-            return; // Done.
-        }
-        field++;
-        offset += StubField::sizeInBytes(type);
-    }
-}
-
 template <typename T>
 static GCPtr<T>*
 AsGCPtr(uintptr_t* ptr)
 {
     return reinterpret_cast<GCPtr<T>*>(ptr);
 }
 
 uintptr_t
--- a/js/src/jit/CacheIRCompiler.h
+++ b/js/src/jit/CacheIRCompiler.h
@@ -978,18 +978,16 @@ class CacheIRStubInfo
     js::GCPtr<T>& getStubField(Stub* stub, uint32_t field) const;
 
     template <class T>
     js::GCPtr<T>& getStubField(ICStub* stub, uint32_t field) const {
         return getStubField<ICStub, T>(stub, field);
     }
 
     uintptr_t getStubRawWord(ICStub* stub, uint32_t field) const;
-
-    void copyStubData(ICStub* src, ICStub* dest) const;
 };
 
 template <typename T>
 void TraceCacheIRStub(JSTracer* trc, T* stub, const CacheIRStubInfo* stubInfo);
 
 void
 LoadTypedThingData(MacroAssembler& masm, TypedThingLayout layout, Register obj, Register result);
 
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -3297,16 +3297,20 @@ jit::TraceJitScripts(JSTracer* trc, JSSc
 {
     if (script->hasIonScript()) {
         jit::IonScript::Trace(trc, script->ionScript());
     }
 
     if (script->hasBaselineScript()) {
         jit::BaselineScript::Trace(trc, script->baselineScript());
     }
+
+    if (script->hasICScript()) {
+        script->icScript()->trace(trc);
+    }
 }
 
 bool
 jit::JitSupportsFloatingPoint()
 {
     return js::jit::MacroAssembler::SupportsFloatingPoint();
 }
 
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -777,17 +777,17 @@ IonBuilder::init()
     return Ok();
 }
 
 AbortReasonOr<Ok>
 IonBuilder::build()
 {
     // Spew IC info for inlined script, but only when actually compiling,
     // not when analyzing it.
-#ifdef JS_JITSPEW
+#ifdef JS_STRUCTURED_SPEW
     if (!info().isAnalysis()) {
         JitSpewBaselineICStats(script(), "To-Be-Compiled");
     }
 #endif
 
     MOZ_TRY(init());
 
     if (script()->hasBaselineScript()) {
@@ -978,17 +978,17 @@ IonBuilder::processIterators()
 AbortReasonOr<Ok>
 IonBuilder::buildInline(IonBuilder* callerBuilder, MResumePoint* callerResumePoint,
                         CallInfo& callInfo)
 {
     inlineCallInfo_ = &callInfo;
 
     // Spew IC info for inlined script, but only when actually compiling,
     // not when analyzing it.
-#ifdef JS_JITSPEW
+#ifdef JS_STRUCTURED_SPEW
     if (!info().isAnalysis()) {
         JitSpewBaselineICStats(script(), "To-Be-Inlined");
     }
 #endif
 
     MOZ_TRY(init());
 
     JitSpew(JitSpew_IonScripts, "Inlining script %s:%u:%u (%p)",
--- a/js/src/jit/JitSpewer.cpp
+++ b/js/src/jit/JitSpewer.cpp
@@ -17,16 +17,17 @@
 #else
 #include <unistd.h>
 #endif
 #include "jit/Ion.h"
 #include "jit/MIR.h"
 #include "jit/MIRGenerator.h"
 #include "jit/MIRGraph.h"
 #include "threading/LockGuard.h"
+#include "util/Text.h"
 #include "vm/HelperThreads.h"
 #include "vm/MutexIDs.h"
 
 #include "vm/Realm-inl.h"
 
 #ifndef JIT_SPEW_DIR
 # if defined(_WIN32)
 #  define JIT_SPEW_DIR "."
@@ -367,31 +368,16 @@ AutoSpewEndFunction::~AutoSpewEndFunctio
 
 Fprinter&
 jit::JitSpewPrinter()
 {
     static Fprinter out;
     return out;
 }
 
-
-static bool
-ContainsFlag(const char* str, const char* flag)
-{
-    size_t flaglen = strlen(flag);
-    const char* index = strstr(str, flag);
-    while (index) {
-        if ((index == str || index[-1] == ',') && (index[flaglen] == 0 || index[flaglen] == ',')) {
-            return true;
-        }
-        index = strstr(index + flaglen, flag);
-    }
-    return false;
-}
-
 void
 jit::CheckLogging()
 {
     if (LoggingChecked) {
         return;
     }
     LoggingChecked = true;
     const char* env = getenv("IONFLAGS");
--- a/js/src/jit/arm/SharedICHelpers-arm.h
+++ b/js/src/jit/arm/SharedICHelpers-arm.h
@@ -26,26 +26,23 @@ EmitRestoreTailCallReg(MacroAssembler& m
 
 inline void
 EmitRepushTailCallReg(MacroAssembler& masm)
 {
     // No-op on ARM because link register is always holding the return address.
 }
 
 inline void
-EmitCallIC(MacroAssembler& masm, CodeOffset* patchOffset, CodeOffset* callOffset)
+EmitCallIC(MacroAssembler& masm, const ICEntry* entry, CodeOffset* callOffset)
 {
-    // Move ICEntry offset into ICStubReg
-    CodeOffset offset = masm.movWithPatch(ImmWord(-1), ICStubReg);
-    *patchOffset = offset;
+    // Load stub pointer into ICStubReg.
+    masm.loadPtr(AbsoluteAddress(entry).offset(ICEntry::offsetOfFirstStub()),
+                 ICStubReg);
 
-    // Load stub pointer into ICStubReg
-    masm.loadPtr(Address(ICStubReg, ICEntry::offsetOfFirstStub()), ICStubReg);
-
-    // Load stubcode pointer from BaselineStubEntry.
+    // Load stubcode pointer from the ICStub.
     // R2 won't be active when we call ICs, so we can use r0.
     MOZ_ASSERT(R2 == ValueOperand(r1, r0));
     masm.loadPtr(Address(ICStubReg, ICStub::offsetOfStubCode()), r0);
 
     // Call the stubcode via a direct branch-and-link.
     masm.ma_blx(r0);
     *callOffset = CodeOffset(masm.currentOffset());
 }
--- a/js/src/jit/arm64/SharedICHelpers-arm64.h
+++ b/js/src/jit/arm64/SharedICHelpers-arm64.h
@@ -26,26 +26,23 @@ EmitRestoreTailCallReg(MacroAssembler& m
 
 inline void
 EmitRepushTailCallReg(MacroAssembler& masm)
 {
     // No-op on ARM because link register is always holding the return address.
 }
 
 inline void
-EmitCallIC(MacroAssembler& masm, CodeOffset* patchOffset, CodeOffset* callOffset)
+EmitCallIC(MacroAssembler& masm, const ICEntry* entry, CodeOffset* callOffset)
 {
-    // Move ICEntry offset into ICStubReg
-    CodeOffset offset = masm.movWithPatch(ImmWord(-1), ICStubReg);
-    *patchOffset = offset;
+    // Load stub pointer into ICStubReg.
+    masm.loadPtr(AbsoluteAddress(entry).offset(ICEntry::offsetOfFirstStub()),
+                 ICStubReg);
 
-    // Load stub pointer into ICStubReg
-    masm.loadPtr(Address(ICStubReg, ICEntry::offsetOfFirstStub()), ICStubReg);
-
-    // Load stubcode pointer from BaselineStubEntry.
+    // Load stubcode pointer from the ICStub.
     // R2 won't be active when we call ICs, so we can use r0.
     MOZ_ASSERT(R2 == ValueOperand(r0));
     masm.loadPtr(Address(ICStubReg, ICStub::offsetOfStubCode()), r0);
 
     // Call the stubcode via a direct branch-and-link.
     masm.Blr(x0);
     *callOffset = CodeOffset(masm.currentOffset());
 }
--- a/js/src/jit/mips-shared/SharedICHelpers-mips-shared.h
+++ b/js/src/jit/mips-shared/SharedICHelpers-mips-shared.h
@@ -38,26 +38,23 @@ EmitRestoreTailCallReg(MacroAssembler& m
 
 inline void
 EmitRepushTailCallReg(MacroAssembler& masm)
 {
     // No-op on MIPS because ra register is always holding the return address.
 }
 
 inline void
-EmitCallIC(MacroAssembler& masm, CodeOffset* patchOffset, CodeOffset* callOffset)
+EmitCallIC(MacroAssembler& masm, const ICEntry* entry, CodeOffset* callOffset)
 {
-    // Move ICEntry offset into ICStubReg.
-    CodeOffset offset = masm.movWithPatch(ImmWord(-1), ICStubReg);
-    *patchOffset = offset;
+    // Load stub pointer into ICStubReg.
+    masm.loadPtr(AbsoluteAddress(entry).offset(ICEntry::offsetOfFirstStub()),
+                 ICStubReg);
 
-    // Load stub pointer into ICStubReg.
-    masm.loadPtr(Address(ICStubReg, ICEntry::offsetOfFirstStub()), ICStubReg);
-
-    // Load stubcode pointer from BaselineStubEntry.
+    // Load stubcode pointer from the ICStub.
     // R2 won't be active when we call ICs, so we can use it as scratch.
     masm.loadPtr(Address(ICStubReg, ICStub::offsetOfStubCode()), R2.scratchReg());
 
     // Call the stubcode via a direct jump-and-link
     masm.call(R2.scratchReg());
     *callOffset = CodeOffset(masm.currentOffset());
 }
 
--- a/js/src/jit/none/SharedICHelpers-none.h
+++ b/js/src/jit/none/SharedICHelpers-none.h
@@ -9,17 +9,17 @@
 
 namespace js {
 namespace jit {
 
 static const size_t ICStackValueOffset = 0;
 
 inline void EmitRestoreTailCallReg(MacroAssembler&) { MOZ_CRASH(); }
 inline void EmitRepushTailCallReg(MacroAssembler&) { MOZ_CRASH(); }
-inline void EmitCallIC(MacroAssembler&, CodeOffset*, CodeOffset*) { MOZ_CRASH(); }
+inline void EmitCallIC(MacroAssembler&, const ICEntry*, CodeOffset*) { MOZ_CRASH(); }
 inline void EmitEnterTypeMonitorIC(MacroAssembler&, size_t v = 0) { MOZ_CRASH(); }
 inline void EmitReturnFromIC(MacroAssembler&) { MOZ_CRASH(); }
 inline void EmitBaselineLeaveStubFrame(MacroAssembler&, bool v = false) { MOZ_CRASH(); }
 inline void EmitStubGuardFailure(MacroAssembler&) { MOZ_CRASH(); }
 
 template <typename T> inline void EmitPreBarrier(MacroAssembler&, T, MIRType) { MOZ_CRASH(); }
 
 } // namespace jit
--- a/js/src/jit/x64/SharedICHelpers-x64.h
+++ b/js/src/jit/x64/SharedICHelpers-x64.h
@@ -26,24 +26,20 @@ EmitRestoreTailCallReg(MacroAssembler& m
 
 inline void
 EmitRepushTailCallReg(MacroAssembler& masm)
 {
     masm.Push(ICTailCallReg);
 }
 
 inline void
-EmitCallIC(MacroAssembler& masm, CodeOffset* patchOffset, CodeOffset* callOffset)
+EmitCallIC(MacroAssembler& masm, const ICEntry* entry, CodeOffset* callOffset)
 {
-    // Move ICEntry offset into ICStubReg
-    CodeOffset offset = masm.movWithPatch(ImmWord(-1), ICStubReg);
-    *patchOffset = offset;
-
-    // Load stub pointer into ICStubReg
-    masm.loadPtr(Address(ICStubReg, (int32_t) ICEntry::offsetOfFirstStub()),
+    // Load stub pointer into ICStubReg.
+    masm.loadPtr(AbsoluteAddress(entry).offset(ICEntry::offsetOfFirstStub()),
                  ICStubReg);
 
     // Call the stubcode.
     masm.call(Address(ICStubReg, ICStub::offsetOfStubCode()));
     *callOffset = CodeOffset(masm.currentOffset());
 }
 
 inline void
--- a/js/src/jit/x86/SharedICHelpers-x86.h
+++ b/js/src/jit/x86/SharedICHelpers-x86.h
@@ -26,28 +26,23 @@ EmitRestoreTailCallReg(MacroAssembler& m
 
 inline void
 EmitRepushTailCallReg(MacroAssembler& masm)
 {
     masm.Push(ICTailCallReg);
 }
 
 inline void
-EmitCallIC(MacroAssembler& masm, CodeOffset* patchOffset, CodeOffset* callOffset)
+EmitCallIC(MacroAssembler& masm, const ICEntry* entry, CodeOffset* callOffset)
 {
-    // Move ICEntry offset into ICStubReg
-    CodeOffset offset = masm.movWithPatch(ImmWord(-1), ICStubReg);
-    *patchOffset = offset;
-
-    // Load stub pointer into ICStubReg
-    masm.loadPtr(Address(ICStubReg, (int32_t) ICEntry::offsetOfFirstStub()),
+    // Load stub pointer into ICStubReg.
+    masm.loadPtr(AbsoluteAddress(entry).offset(ICEntry::offsetOfFirstStub()),
                  ICStubReg);
 
-    // Load stubcode pointer from BaselineStubEntry into ICTailCallReg
-    // ICTailCallReg will always be unused in the contexts where ICs are called.
+    // Call the stubcode.
     masm.call(Address(ICStubReg, ICStub::offsetOfStubCode()));
     *callOffset = CodeOffset(masm.currentOffset());
 }
 
 inline void
 EmitEnterTypeMonitorIC(MacroAssembler& masm,
                        size_t monitorStubOffset = ICMonitoredStub::offsetOfFirstMonitorStub())
 {
--- a/js/src/js-config.mozbuild
+++ b/js/src/js-config.mozbuild
@@ -12,19 +12,21 @@ if CONFIG['NIGHTLY_BUILD']:
     DEFINES['ENABLE_WASM_GENERALIZED_TABLES'] = True
     DEFINES['WASM_PRIVATE_REFTYPES'] = True
 
 # Some huge-mapping optimization instead of bounds checks on supported
 # platforms.
 if CONFIG['JS_CODEGEN_X64'] or CONFIG['JS_CODEGEN_ARM64']:
     DEFINES['WASM_HUGE_MEMORY'] = True
 
-# Enables CACHEIR_LOGS to diagnose IC coverage.
+# Enables CACHEIR_LOGS to diagnose IC coverage, and
+# Structured spewer for diagnostics
 if CONFIG['MOZ_DEBUG'] or CONFIG['NIGHTLY_BUILD']:
     DEFINES['JS_CACHEIR_SPEW'] = True
+    DEFINES['JS_STRUCTURED_SPEW'] = True
 
 # CTypes
 if CONFIG['JS_HAS_CTYPES']:
     DEFINES['JS_HAS_CTYPES'] = True
     if not CONFIG['MOZ_SYSTEM_FFI']:
         DEFINES['FFI_BUILDING'] = True
 
 # Forward MOZ_LINKER config
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -235,16 +235,17 @@ UNIFIED_SOURCES += [
     'proxy/Wrapper.cpp',
     'threading/Mutex.cpp',
     'threading/ProtectedData.cpp',
     'util/AllocPolicy.cpp',
     'util/CompleteFile.cpp',
     'util/NativeStack.cpp',
     'util/Printf.cpp',
     'util/StringBuffer.cpp',
+    'util/StructuredSpewer.cpp',
     'util/Text.cpp',
     'util/Unicode.cpp',
     'vm/ArgumentsObject.cpp',
     'vm/ArrayBufferObject.cpp',
     'vm/ArrayBufferViewObject.cpp',
     'vm/AsyncFunction.cpp',
     'vm/AsyncIteration.cpp',
     'vm/BytecodeUtil.cpp',
new file mode 100644
--- /dev/null
+++ b/js/src/util/StructuredSpewer.cpp
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/. */
+
+#ifdef JS_STRUCTURED_SPEW
+
+#include "util/StructuredSpewer.h"
+
+#include "mozilla/Sprintf.h"
+
+#include "util/Text.h"
+#include "vm/JSContext.h"
+#include "vm/JSScript.h"
+
+using namespace js;
+
+const StructuredSpewer::NameArray StructuredSpewer::names_ =
+{
+#define STRUCTURED_CHANNEL(name) #name,
+    STRUCTURED_CHANNEL_LIST(STRUCTURED_CHANNEL)
+#undef STRUCTURED_CHANNEL
+};
+
+
+// Choose a sensible default spew directory.
+//
+// The preference here is to use the current working directory,
+// except on Android.
+#ifndef DEFAULT_SPEW_DIRECTORY
+# if defined(_WIN32)
+#  define DEFAULT_SPEW_DIRECTORY "."
+# elif defined(__ANDROID__)
+#  define DEFAULT_SPEW_DIRECTORY "/data/local/tmp"
+# else
+#  define DEFAULT_SPEW_DIRECTORY "."
+# endif
+#endif
+
+void
+StructuredSpewer::ensureInitializationAttempted()
+{
+    if (!outputInitializationAttempted_) {
+        // We cannot call getenv during record replay, so disable
+        // the spewer.
+        if (!mozilla::recordreplay::IsRecordingOrReplaying()) {
+            char filename[2048] = {0};
+            // For ease of use with Treeherder
+            if (getenv("SPEW_UPLOAD") && getenv("MOZ_UPLOAD_DIR")) {
+                SprintfLiteral(filename, "%s/spew_output", getenv("MOZ_UPLOAD_DIR"));
+            } else if (getenv("SPEW_FILE")) {
+                SprintfLiteral(filename, "%s", getenv("SPEW_FILE"));
+            } else {
+                SprintfLiteral(filename, "%s/spew_output", DEFAULT_SPEW_DIRECTORY);
+            }
+            tryToInitializeOutput(filename);
+        }
+        // We can't use the intialization state of the Fprinter, as it is not
+        // marked as initialized in a case where we cannot open the output, so
+        // we track the attempt separately.
+        outputInitializationAttempted_ = true;
+    }
+}
+
+
+void
+StructuredSpewer::tryToInitializeOutput(const char* path)
+{
+    static mozilla::Atomic<uint32_t,
+                           mozilla::ReleaseAcquire,
+                           mozilla::recordreplay::Behavior::DontPreserve> threadCounter;
+
+    char suffix_path[2048] = {0};
+    SprintfLiteral(suffix_path, "%s.%d.%d", path, getpid(), threadCounter++);
+
+    if (!output_.init(suffix_path)) {
+        // Returning here before we've emplaced the JSONPrinter
+        // means this is effectively disabled, but fail earlier
+        // we also disable all the bits
+        selectedChannels_.disableAll();
+        return;
+    }
+
+    // These logs are structured as a JSON array.
+    output_.put("[");
+    json_.emplace(output_);
+    return;
+}
+
+
+// Treat pattern like a glob, and return true if pattern exists
+// in the script's name or filename or line number.
+//
+// This is the most basic matching I can imagine
+static bool
+MatchJSScript(JSScript* script, const char* pattern)
+{
+    if (!pattern) {
+        return false;
+    }
+
+    char signature[2048] = {0};
+    SprintfLiteral(signature, "%s:%d:%d", script->filename(),
+                                          script->lineno(),
+                                          script->column());
+
+    // Trivial containment match.
+    char* result = strstr(signature, pattern);
+
+    return result != nullptr;
+}
+
+/* static */ bool
+StructuredSpewer::enabled(JSScript* script)
+{
+    // We cannot call getenv under record/replay.
+    if (mozilla::recordreplay::IsRecordingOrReplaying()) {
+        return false;
+    }
+    static const char* pattern = getenv("SPEW_FILTER");
+    if (!pattern || MatchJSScript(script, pattern)) {
+        return true;
+    }
+    return false;
+}
+
+bool
+StructuredSpewer::enabled(JSContext* cx, const JSScript* script, SpewChannel channel) const
+{
+    return script->spewEnabled() && cx->spewer().filter().enabled(channel);
+}
+
+// Attempt to setup a common header for objects based on script/channel.
+//
+// Returns true if the spewer is prepared for more input
+void
+StructuredSpewer::startObject(JSContext* cx, const JSScript* script, SpewChannel channel)
+{
+    MOZ_ASSERT(json_.isSome());
+
+    JSONPrinter& json = json_.ref();
+
+    json.beginObject();
+    json.property("channel", getName(channel));
+    json.beginObjectProperty("location");
+    {
+        json.property("filename", script->filename());
+        json.property("line", script->lineno());
+        json.property("column", script->column());
+    }
+    json.endObject();
+}
+
+/* static */ void
+StructuredSpewer::spew(JSContext* cx, SpewChannel channel, const char* fmt, ...)
+{
+    // Because we don't have a script here, use the singleton's
+    // filter to determine if the channel is active.
+    if (!cx->spewer().filter().enabled(channel)) {
+        return;
+    }
+
+    cx->spewer().ensureInitializationAttempted();
+
+    va_list ap;
+    va_start(ap, fmt);
+
+    MOZ_ASSERT(cx->spewer().json_.isSome());
+
+    JSONPrinter& json = cx->spewer().json_.ref();
+
+    json.beginObject();
+    json.property("channel", getName(channel));
+    json.formatProperty("message", fmt, ap);
+    json.endObject();
+
+    va_end(ap);
+}
+
+// Currently uses the exact spew flag representation as text.
+void
+StructuredSpewer::parseSpewFlags(const char* flags) {
+    // If '*' or 'all' are in the list, enable all spew.
+    bool star = ContainsFlag(flags, "*") || ContainsFlag(flags, "all");
+#define CHECK_CHANNEL(name) \
+    if (ContainsFlag(flags, #name) || star) { \
+        selectedChannels_.enableChannel(SpewChannel:: name); \
+    }
+
+    STRUCTURED_CHANNEL_LIST(CHECK_CHANNEL)
+
+#undef CHECK_CHANNEL
+
+}
+
+AutoStructuredSpewer::AutoStructuredSpewer(JSContext* cx,
+                                           SpewChannel channel,
+                                           JSScript* script)
+  : printer_(mozilla::Nothing())
+{
+    if (!cx->spewer().enabled(cx, script, channel)) {
+        return;
+    }
+
+    cx->spewer().ensureInitializationAttempted();
+
+    cx->spewer().startObject(cx, script, channel);
+    printer_.emplace(&cx->spewer().json_.ref());
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/js/src/util/StructuredSpewer.h
@@ -0,0 +1,255 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef jit_StructuredSpewer_h
+#define jit_StructuredSpewer_h
+
+#ifdef JS_STRUCTURED_SPEW
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EnumeratedArray.h"
+#include "mozilla/EnumSet.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Sprintf.h"
+
+#include "vm/JSONPrinter.h"
+#include "vm/Printer.h"
+
+#ifdef XP_WIN
+#include <process.h>
+#define getpid _getpid
+#else
+#include <unistd.h>
+#endif
+
+// [SMDOC] JSON Structured Spewer
+//
+// This spewer design has two goals:
+//
+//   1. Provide a spew mechanism that has first-class support for slicing and
+//      dicing output. This means that filtering by script and channel should be
+//      the dominant output mechanism.
+//   2. Provide a simple powerful mechanism for getting information out of the
+//      compiler and into tools. I'm inspired by tools like CacheIR analyzer,
+//      IR Hydra, and the upcoming tracelogger integration into perf.html.
+//
+// The spewer has four main control knobs, all currently set as
+// environment variables. All but the first are optional.
+//
+//   SPEW: Activates the spewer. The value provided is interpreted as a comma
+//         separated list that selects channels by name. Currently there's no
+//         mapping between internal and external names, so the channel names
+//         are exactly those described in STRUCTURED_CHANNEL_LIST below.
+//
+//   SPEW_FILE: Selects the file to write to. An absolute path.
+//
+//   SPEW_FILTER: A string which is matched against 'signature' constructed or a
+//        JSScript, currently connsisting of filename:line:col.
+//
+//        Matching in this version is merely finding the string in
+//        in question in the 'signature'
+//
+//   SPEW_UPLOAD: If this variable is set as well as MOZ_UPLOAD_DIR, output goes
+//        to $MOZ_UPLOAD_DIR/spew_output* to ease usage with Treeherder.
+//
+// Other notes:
+// - Thread safety is provided by opening a new spewer file for every thread.
+// - Each file is prefixed with the PID to handle multiple processes.
+// - Files are opened lazily, just before the first write to them.
+
+class JSScript;
+
+namespace js {
+
+#define STRUCTURED_CHANNEL_LIST(_) \
+    _(BaselineICStats)
+
+// Structured spew channels
+enum class SpewChannel {
+#define STRUCTURED_CHANNEL(name) name,
+    STRUCTURED_CHANNEL_LIST(STRUCTURED_CHANNEL)
+#undef STRUCTURED_CHANNEL
+    Count
+};
+
+// A filter is used to select what channels are enabled
+//
+// To save memory, JSScripts do not have their own filters, but instead have
+// a single bit which tracks if that script has opted into spewing.
+class StructuredSpewFilter
+{
+    // Packed set of bits indicating what spew channels
+    // are enabled.
+    mozilla::EnumSet<SpewChannel> bits_;
+
+  public:
+    // Default construct to all bits disabled.
+    StructuredSpewFilter()
+      : bits_()
+    {}
+
+    // Return true iff spew is enabled for this channel for
+    // the script this was created for.
+    bool enabled(SpewChannel x) const {
+        return bits_.contains(x);
+    }
+
+    void enableChannel(SpewChannel x) {
+        bits_ += x;
+    }
+
+    void disableAll() {
+        bits_.clear();
+    }
+
+};
+
+class StructuredSpewer
+{
+  public:
+    StructuredSpewer()
+      : outputInitializationAttempted_(false),
+        json_(mozilla::Nothing()),
+        selectedChannels_()
+    {
+        // If we are recording or replaying, we cannot use getenv
+        if (mozilla::recordreplay::IsRecordingOrReplaying()) {
+            return;
+        }
+        if (getenv("SPEW")) {
+            parseSpewFlags(getenv("SPEW"));
+        }
+    }
+
+    ~StructuredSpewer() {
+        if (json_.isSome()) {
+            json_->endList();
+            output_.flush();
+            output_.finish();
+        }
+    }
+
+    // Check if the spewer is enabled for a particular script, used to power
+    // script level filtering.
+    static bool enabled(JSScript* script);
+
+    // A generic printf like spewer that logs the formatted string.
+    static void spew(JSContext* cx, SpewChannel channel, const char* fmt, ...) MOZ_FORMAT_PRINTF(3,4);
+
+  private:
+    // In order to support lazy initialization, and simultaneously support a
+    // failure to open a log file being non-fatal (as lazily reporting failure
+    // would be hard, we have an akward set of states to represent.
+    //
+    // We need to handle:
+    // - Output file not initialized, and not yet attempted
+    // - Output file not intialized, attempted, and failed.
+    // - Output file initialized, JSON writer ready for input.
+    //
+    // Because Fprinter doesn't record whether or not its initialization was
+    // attempted, we keep track of that here.
+    //
+    // The contract we require is that ensureInitializationAttempted() be called
+    // just before any attempte to write. This will ensure the file open is
+    // attemped in the right place.
+    bool outputInitializationAttempted_;
+    Fprinter output_;
+    mozilla::Maybe<JSONPrinter> json_;
+
+    // Globally selected channels.
+    StructuredSpewFilter selectedChannels_;
+
+    using NameArray = mozilla::EnumeratedArray<SpewChannel,
+                                               SpewChannel::Count,
+                                               const char*>;
+    // Channel Names
+    static NameArray const names_;
+
+    // Return the global filter.
+    StructuredSpewFilter& filter() {
+        return selectedChannels_;
+    }
+
+    // Get channel name
+    static const char* getName(SpewChannel channel) {
+        return names_[channel];
+    }
+
+    // Call just before writes to the output are expected.
+    //
+    // Avoids opening files that will remain empty.
+    void ensureInitializationAttempted();
+
+    void tryToInitializeOutput(const char* path);
+
+    // Using flags, choose the enabled channels for this spewer.
+    void parseSpewFlags(const char* flags);
+
+    // Returns true iff the channels is enabled for the given script.
+    bool enabled(JSContext* cx, const JSScript* script, SpewChannel channel) const;
+
+    // Start a record
+    void startObject(JSContext* cx, const JSScript* script, SpewChannel channel);
+
+    friend class AutoStructuredSpewer;
+};
+
+// An RAII class for accessing the structured spewer.
+//
+// This class prefixes the spew with channel and location information.
+//
+// Before writing with this Spewer, it must be checked: ie.
+//
+//     {
+//       AutoSpew x(...);
+//       if (x) {
+//          x->property("lalala", y);
+//       }
+//     }
+//
+// As the selected channel may not be enabled.
+//
+// Note: If the lifespan of two AutoSpewers overlap, then the output
+//  may not be well defined JSON. These spewers should be given as
+//  short a lifespan as possible.
+//
+//  As well, this class cannot be copied or assigned to ensure the
+//  correct number of destructors fire.
+class MOZ_RAII AutoStructuredSpewer {
+    mozilla::Maybe<JSONPrinter*> printer_;
+    AutoStructuredSpewer(const AutoStructuredSpewer&) = delete;
+    void operator=(AutoStructuredSpewer&) = delete;
+  public:
+
+    explicit AutoStructuredSpewer(JSContext* cx, SpewChannel channel, JSScript* script);
+
+    ~AutoStructuredSpewer() {
+        if (printer_.isSome()) {
+            printer_.ref()->endObject();
+        }
+    }
+
+    explicit operator bool() const {
+        return printer_.isSome();
+    }
+
+    JSONPrinter* operator->() {
+        MOZ_ASSERT(printer_.isSome());
+        return printer_.ref();
+    }
+
+    JSONPrinter& operator*() {
+        MOZ_ASSERT(printer_.isSome());
+        return *printer_.ref();
+    }
+
+};
+
+} // namespace js
+
+#endif
+#endif /* jit_StructuredSpewer_h */
--- a/js/src/util/Text.cpp
+++ b/js/src/util/Text.cpp
@@ -283,16 +283,30 @@ js::PutEscapedStringImpl(char* buffer, s
     }
   stop:
     if (buffer) {
         buffer[n] = '\0';
     }
     return n;
 }
 
+bool
+js::ContainsFlag(const char* str, const char* flag)
+{
+    size_t flaglen = strlen(flag);
+    const char* index = strstr(str, flag);
+    while (index) {
+        if ((index == str || index[-1] == ',') && (index[flaglen] == 0 || index[flaglen] == ',')) {
+            return true;
+        }
+        index = strstr(index + flaglen, flag);
+    }
+    return false;
+}
+
 template size_t
 js::PutEscapedStringImpl(char* buffer, size_t bufferSize, GenericPrinter* out, const Latin1Char* chars,
                          size_t length, uint32_t quote);
 
 template size_t
 js::PutEscapedStringImpl(char* buffer, size_t bufferSize, GenericPrinter* out, const char* chars,
                          size_t length, uint32_t quote);
 
--- a/js/src/util/Text.h
+++ b/js/src/util/Text.h
@@ -221,11 +221,15 @@ FileEscapedString(FILE* fp, const char* 
     bool res = EscapedStringPrinter(out, chars, length, quote);
     out.finish();
     return res;
 }
 
 JSString*
 EncodeURI(JSContext* cx, const char* chars, size_t length);
 
+// Return true if input string contains a given flag in a comma separated list.
+bool
+ContainsFlag(const char* str, const char* flag);
+
 } // namespace js
 
 #endif // util_Text_h
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1858,18 +1858,18 @@ SetObjectElementOperation(JSContext* cx,
     // anyway.
     TypeScript::MonitorAssign(cx, obj, id);
 
     if (obj->isNative() && JSID_IS_INT(id)) {
         uint32_t length = obj->as<NativeObject>().getDenseInitializedLength();
         int32_t i = JSID_TO_INT(id);
         if ((uint32_t)i >= length) {
             // Annotate script if provided with information (e.g. baseline)
-            if (script && script->hasBaselineScript() && IsSetElemPC(pc)) {
-                script->baselineScript()->noteHasDenseAdd(script->pcToOffset(pc));
+            if (script && script->hasICScript() && IsSetElemPC(pc)) {
+                script->icScript()->noteHasDenseAdd(script->pcToOffset(pc));
             }
         }
     }
 
     // Set the HadElementsAccess flag on the object if needed. This flag is
     // used to do more eager dictionary-mode conversion for objects that are
     // used as hashmaps. Set this flag only for objects with many properties,
     // to avoid unnecessary Shape changes.
--- a/js/src/vm/JSContext.cpp
+++ b/js/src/vm/JSContext.cpp
@@ -1370,16 +1370,19 @@ JSContext::JSContext(JSRuntime* runtime,
     enqueuePromiseJobCallback(nullptr),
     enqueuePromiseJobCallbackData(nullptr),
     jobQueue(nullptr),
     drainingJobQueue(false),
     stopDrainingJobQueue(false),
     canSkipEnqueuingJobs(false),
     promiseRejectionTrackerCallback(nullptr),
     promiseRejectionTrackerCallbackData(nullptr)
+#ifdef JS_STRUCTURED_SPEW
+    , structuredSpewer_()
+#endif
 {
     MOZ_ASSERT(static_cast<JS::RootingContext*>(this) ==
                JS::RootingContext::get(this));
 
     MOZ_ASSERT(!TlsContext.get());
     TlsContext.set(this);
 
     for (size_t i = 0; i < mozilla::ArrayLength(nativeStackQuota); i++) {
--- a/js/src/vm/JSContext.h
+++ b/js/src/vm/JSContext.h
@@ -13,16 +13,17 @@
 
 #include "ds/TraceableFifo.h"
 #include "js/CharacterEncoding.h"
 #include "js/GCVector.h"
 #include "js/Result.h"
 #include "js/Utility.h"
 #include "js/Vector.h"
 #include "threading/ProtectedData.h"
+#include "util/StructuredSpewer.h"
 #include "vm/ErrorReporting.h"
 #include "vm/MallocProvider.h"
 #include "vm/Runtime.h"
 
 struct DtoaState;
 
 namespace js {
 
@@ -954,16 +955,25 @@ struct JSContext : public JS::RootingCon
     }
 
   public:
     // Assert the arguments are in this context's realm (for scripts),
     // compartment (for objects) or zone (for strings, symbols).
     template <class... Args> inline void check(const Args&... args);
     template <class... Args> inline void releaseCheck(const Args&... args);
     template <class... Args> MOZ_ALWAYS_INLINE void debugOnlyCheck(const Args&... args);
+
+#ifdef JS_STRUCTURED_SPEW
+  private:
+    // Spewer for this thread
+    js::ThreadData<js::StructuredSpewer> structuredSpewer_;
+  public:
+    js::StructuredSpewer& spewer() { return structuredSpewer_.ref(); }
+#endif
+
 }; /* struct JSContext */
 
 inline JS::Result<>
 JSContext::boolToResult(bool ok)
 {
     if (MOZ_LIKELY(ok)) {
         MOZ_ASSERT(!isExceptionPending());
         MOZ_ASSERT(!isPropagatingForcedReturn());
--- a/js/src/vm/JSONPrinter.cpp
+++ b/js/src/vm/JSONPrinter.cpp
@@ -115,16 +115,24 @@ JSONPrinter::formatProperty(const char* 
     beginStringProperty(name);
     out_.vprintf(format, ap);
     endStringProperty();
 
     va_end(ap);
 }
 
 void
+JSONPrinter::formatProperty(const char* name, const char* format, va_list ap)
+{
+    beginStringProperty(name);
+    out_.vprintf(format, ap);
+    endStringProperty();
+}
+
+void
 JSONPrinter::value(const char* format, ...)
 {
     va_list ap;
     va_start(ap, format);
 
     if (!first_) {
         out_.putChar(',');
     }
--- a/js/src/vm/JSONPrinter.h
+++ b/js/src/vm/JSONPrinter.h
@@ -57,16 +57,17 @@ class JSONPrinter
 #if defined(XP_DARWIN) || defined(__OpenBSD__)
     // On OSX and OpenBSD, size_t is long unsigned, uint32_t is unsigned, and
     // uint64_t is long long unsigned. Everywhere else, size_t matches either
     // uint32_t or uint64_t.
     void property(const char* name, size_t value);
 #endif
 
     void formatProperty(const char* name, const char* format, ...) MOZ_FORMAT_PRINTF(3, 4);
+    void formatProperty(const char* name, const char* format, va_list ap);
 
     // JSON requires decimals to be separated by periods, but the LC_NUMERIC
     // setting may cause printf to use commas in some locales.
     enum TimePrecision { SECONDS, MILLISECONDS, MICROSECONDS };
     void property(const char* name, const mozilla::TimeDuration& dur, TimePrecision precision);
 
     void floatProperty(const char* name, double value, size_t precision);
 
--- a/js/src/vm/JSScript-inl.h
+++ b/js/src/vm/JSScript-inl.h
@@ -224,9 +224,16 @@ JSScript::trackRecordReplayProgress() co
     // scripts execute may depend on performed Ion optimizations (for example,
     // self hosted TypedObject logic), so they are ignored.
     return MOZ_UNLIKELY(mozilla::recordreplay::IsRecordingOrReplaying())
         && !runtimeFromAnyThread()->parentRuntime
         && !selfHosted()
         && mozilla::recordreplay::ShouldUpdateProgressCounter(filename());
 }
 
+inline js::jit::ICScript*
+JSScript::icScript() const
+{
+    MOZ_ASSERT(hasICScript());
+    return types_->icScript();
+}
+
 #endif /* vm_JSScript_inl_h */
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -3512,16 +3512,21 @@ JSScript::fullyInitFromEmitter(JSContext
     // JSFunction::hasUncompletedScript relies on the fact that the existence
     // of the pointer to JSScript means the pointed JSScript is complete.
     if (bce->sc->isFunctionBox()) {
         initFromFunctionBox(script, bce->sc->asFunctionBox());
     } else if (bce->sc->isModuleContext()) {
         initFromModuleContext(script);
     }
 
+#ifdef JS_STRUCTURED_SPEW
+    // We want this to happen after line number initialization to allow filtering to work.
+    script->setSpewEnabled(StructuredSpewer::enabled(script));
+#endif
+
 #ifdef DEBUG
     script->assertValidJumpTargets();
 #endif
 
     return true;
 }
 
 #ifdef DEBUG
@@ -3624,17 +3629,17 @@ JSScript::finalize(FreeOp* fop)
     if (fop->runtime()->lcovOutput().isEnabled() && hasScriptName()) {
         realm()->lcovOutput.collectCodeCoverageInfo(realm(), this, getScriptName());
         destroyScriptName();
     }
 
     fop->runtime()->geckoProfiler().onScriptFinalized(this);
 
     if (types_) {
-        types_->destroy();
+        types_->destroy(zone());
     }
 
     jit::DestroyJitScripts(fop, this);
 
     destroyScriptCounts();
     destroyDebugScript(fop);
 
 #ifdef MOZ_VTUNE
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -28,16 +28,17 @@
 #include "frontend/NameAnalysisTypes.h"
 #include "gc/Barrier.h"
 #include "gc/Rooting.h"
 #include "jit/IonCode.h"
 #include "js/CompileOptions.h"
 #include "js/UbiNode.h"
 #include "js/UniquePtr.h"
 #include "js/Utility.h"
+#include "util/StructuredSpewer.h"
 #include "vm/BytecodeIterator.h"
 #include "vm/BytecodeLocation.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/JSAtom.h"
 #include "vm/NativeObject.h"
 #include "vm/Scope.h"
 #include "vm/Shape.h"
 #include "vm/SharedImmutableStringsCache.h"
@@ -47,16 +48,17 @@ namespace JS {
 struct ScriptSourceInfo;
 template<typename UnitT> class SourceText;
 } // namespace JS
 
 namespace js {
 
 namespace jit {
     struct BaselineScript;
+    class ICScript;
     struct IonScriptCounts;
 } // namespace jit
 
 # define ION_DISABLED_SCRIPT ((js::jit::IonScript*)0x1)
 # define ION_COMPILING_SCRIPT ((js::jit::IonScript*)0x2)
 # define ION_PENDING_SCRIPT ((js::jit::IonScript*)0x3)
 
 # define BASELINE_DISABLED_SCRIPT ((js::jit::BaselineScript*)0x1)
@@ -1771,16 +1773,19 @@ class JSScript : public js::gc::TenuredC
         FailedLexicalCheck = 1 << 16,
 
         // See comments below.
         NeedsArgsAnalysis = 1 << 17,
         NeedsArgsObj = 1 << 18,
 
         // Set if the debugger's onNewScript hook has not yet been called.
         HideScriptFromDebugger = 1 << 19,
+
+        // Set if the script has opted into spew
+        SpewEnabled = 1 << 20,
     };
   private:
     // Note: don't make this a bitfield! It makes it hard to read these flags
     // from JIT code.
     uint32_t mutableFlags_ = 0;
 
     // 16-bit fields.
 
@@ -2183,16 +2188,23 @@ class JSScript : public js::gc::TenuredC
 
     bool hideScriptFromDebugger() const {
         return hasFlag(MutableFlags::HideScriptFromDebugger);
     }
     void clearHideScriptFromDebugger() {
         clearFlag(MutableFlags::HideScriptFromDebugger);
     }
 
+    bool spewEnabled() const {
+        return hasFlag(MutableFlags::SpewEnabled);
+    }
+    void setSpewEnabled(bool enabled) {
+        setFlag(MutableFlags::SpewEnabled, enabled);
+    }
+
     bool needsHomeObject() const {
         return hasFlag(ImmutableFlags::NeedsHomeObject);
     }
 
     bool isDerivedClassConstructor() const {
         return hasFlag(ImmutableFlags::IsDerivedClassConstructor);
     }
 
@@ -2294,16 +2306,24 @@ class JSScript : public js::gc::TenuredC
         return baseline != BASELINE_DISABLED_SCRIPT;
     }
     js::jit::BaselineScript* baselineScript() const {
         MOZ_ASSERT(hasBaselineScript());
         return baseline;
     }
     inline void setBaselineScript(JSRuntime* rt, js::jit::BaselineScript* baselineScript);
 
+    inline js::jit::ICScript* icScript() const;
+
+    bool hasICScript() const {
+        // ICScript is stored in TypeScript so we have an ICScript iff we have a
+        // TypeScript.
+        return !!types_;
+    }
+
     void updateJitCodeRaw(JSRuntime* rt);
 
     static size_t offsetOfBaselineScript() {
         return offsetof(JSScript, baseline);
     }
     static size_t offsetOfIonScript() {
         return offsetof(JSScript, ion);
     }
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -7,26 +7,28 @@
 #include "vm/NativeObject-inl.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Casting.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/DebugOnly.h"
 
 #include "gc/Marking.h"
+#include "jit/BaselineIC.h"
 #include "js/CharacterEncoding.h"
 #include "js/Value.h"
 #include "vm/Debugger.h"
 #include "vm/TypedArrayObject.h"
 #include "vm/UnboxedObject.h"
 
 #include "gc/Nursery-inl.h"
 #include "vm/ArrayObject-inl.h"
 #include "vm/EnvironmentObject-inl.h"
 #include "vm/JSObject-inl.h"
+#include "vm/JSScript-inl.h"
 #include "vm/Shape-inl.h"
 #include "vm/TypeInference-inl.h"
 #include "vm/UnboxedObject-inl.h"
 
 using namespace js;
 
 using JS::AutoCheckCannotGC;
 using mozilla::ArrayLength;
@@ -2334,22 +2336,22 @@ GetExistingProperty(JSContext* cx,
 
     if (shape->hasDefaultGetter()) {
         return true;
     }
 
     {
         jsbytecode* pc;
         JSScript* script = cx->currentScript(&pc);
-        if (script && script->hasBaselineScript()) {
+        if (script && script->hasICScript()) {
             switch (JSOp(*pc)) {
               case JSOP_GETPROP:
               case JSOP_CALLPROP:
               case JSOP_LENGTH:
-                script->baselineScript()->noteAccessedGetter(script->pcToOffset(pc));
+                script->icScript()->noteAccessedGetter(script->pcToOffset(pc));
                 break;
               default:
                 break;
             }
         }
     }
 
     if (!allowGC) {
--- a/js/src/vm/TraceLogging.cpp
+++ b/js/src/vm/TraceLogging.cpp
@@ -818,30 +818,16 @@ TraceLoggerThreadState::~TraceLoggerThre
         js_delete(r.front().value());
     }
 
 #ifdef DEBUG
     initialized = false;
 #endif
 }
 
-static bool
-ContainsFlag(const char* str, const char* flag)
-{
-    size_t flaglen = strlen(flag);
-    const char* index = strstr(str, flag);
-    while (index) {
-        if ((index == str || index[-1] == ',') && (index[flaglen] == 0 || index[flaglen] == ',')) {
-            return true;
-        }
-        index = strstr(index + flaglen, flag);
-    }
-    return false;
-}
-
 bool
 TraceLoggerThreadState::init()
 {
     const char* env = getenv("TLLOG");
     if (env) {
         if (strstr(env, "help")) {
             fflush(nullptr);
             printf(
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -15,16 +15,17 @@
 #include "mozilla/Sprintf.h"
 
 #include <new>
 
 #include "jsapi.h"
 #include "builtin/String.h"
 
 #include "gc/HashUtil.h"
+#include "jit/BaselineIC.h"
 #include "jit/BaselineJIT.h"
 #include "jit/CompileInfo.h"
 #include "jit/Ion.h"
 #include "jit/IonAnalysis.h"
 #include "jit/JitRealm.h"
 #include "jit/OptimizationTracking.h"
 #include "js/MemoryMetrics.h"
 #include "js/UniquePtr.h"
@@ -166,17 +167,17 @@ TypeSet::TypeString(const TypeSet::Type 
 TypeSet::ObjectGroupString(const ObjectGroup* group)
 {
     return TypeString(TypeSet::ObjectType(group));
 }
 
 #ifdef DEBUG
 
 bool
-js::InferSpewActive(SpewChannel channel)
+js::InferSpewActive(TypeSpewChannel channel)
 {
     static bool active[SPEW_COUNT];
     static bool checked = false;
     if (!checked) {
         checked = true;
         PodArrayZero(active);
         if (mozilla::recordreplay::IsRecordingOrReplaying()) {
             return false;
@@ -3742,24 +3743,37 @@ js::TypeMonitorResult(JSContext* cx, JSS
 bool
 JSScript::makeTypes(JSContext* cx)
 {
     MOZ_ASSERT(!types_);
     cx->check(this);
 
     AutoEnterAnalysis enter(cx);
 
+    UniquePtr<jit::ICScript> icScript(jit::ICScript::create(cx, this));
+    if (!icScript) {
+        return false;
+    }
+
+    // We need to call prepareForDestruction on ICScript before we |delete| it.
+    auto prepareForDestruction = mozilla::MakeScopeExit([&] {
+        icScript->prepareForDestruction(cx->zone());
+    });
+
     unsigned count = TypeScript::NumTypeSets(this);
 
     size_t size = TypeScript::SizeIncludingTypeArray(count);
     auto typeScript = reinterpret_cast<TypeScript*>(cx->pod_calloc<uint8_t>(size));
     if (!typeScript) {
         return false;
     }
 
+    prepareForDestruction.release();
+    typeScript->icScript_ = std::move(icScript);
+
 #ifdef JS_CRASH_DIAGNOSTICS
     {
         StackTypeSet* typeArray = typeScript->typeArray();
         for (unsigned i = 0; i < count; i++) {
             typeArray[i].initMagic();
         }
     }
 #endif
@@ -4904,27 +4918,29 @@ void
 JSScript::maybeReleaseTypes()
 {
     if (!types_ || zone()->types.keepTypeScripts || hasBaselineScript()) {
         return;
     }
 
     MOZ_ASSERT(!hasIonScript());
 
-    types_->destroy();
+    types_->destroy(zone());
     types_ = nullptr;
 
     // Freeze constraints on stack type sets need to be regenerated the
     // next time the script is analyzed.
     clearFlag(MutableFlags::HasFreezeConstraints);
 }
 
 void
-TypeScript::destroy()
+TypeScript::destroy(Zone* zone)
 {
+    icScript_->prepareForDestruction(zone);
+
     js_delete(this);
 }
 
 void
 Zone::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                              size_t* typePool,
                              size_t* regexpZone,
                              size_t* jitZone,
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -32,16 +32,17 @@ namespace js {
 
 class TypeConstraint;
 class TypeZone;
 class CompilerConstraintList;
 class HeapTypeSetKey;
 
 namespace jit {
 
+class ICScript;
 struct IonScript;
 class TempAllocator;
 
 } // namespace jit
 
 // If there is an OOM while sweeping types, the type information is deoptimized
 // so that it stays correct (i.e. overapproximates the possible types in the
 // zone), but constraints might not have been triggered on the deoptimization
@@ -229,30 +230,39 @@ class TypeScript
     friend class ::JSScript;
 
     // The freeze constraints added to stack type sets will only directly
     // invalidate the script containing those stack type sets. This Vector
     // contains compilations that inlined this script, so we can invalidate
     // them as well.
     RecompileInfoVector inlinedCompilations_;
 
+    // ICScript and TypeScript have the same lifetimes, so we store a pointer to
+    // ICScript here to not increase sizeof(JSScript).
+    js::UniquePtr<js::jit::ICScript> icScript_;
+
     // Variable-size array
     StackTypeSet typeArray_[1];
 
   public:
     RecompileInfoVector& inlinedCompilations() {
         return inlinedCompilations_;
     }
     MOZ_MUST_USE bool addInlinedCompilation(RecompileInfo info) {
         if (!inlinedCompilations_.empty() && inlinedCompilations_.back() == info) {
             return true;
         }
         return inlinedCompilations_.append(info);
     }
 
+    jit::ICScript* icScript() const {
+        MOZ_ASSERT(icScript_);
+        return icScript_.get();
+    }
+
     /* Array of type sets for variables and JOF_TYPESET ops. */
     StackTypeSet* typeArray() const {
         // Ensure typeArray_ is the last data member of TypeScript.
         JS_STATIC_ASSERT(sizeof(TypeScript) ==
                          sizeof(typeArray_) + offsetof(TypeScript, typeArray_));
         return const_cast<StackTypeSet*>(typeArray_);
     }
 
@@ -309,19 +319,20 @@ class TypeScript
      */
     static bool FreezeTypeSets(CompilerConstraintList* constraints, JSScript* script,
                                TemporaryTypeSet** pThisTypes,
                                TemporaryTypeSet** pArgTypes,
                                TemporaryTypeSet** pBytecodeTypes);
 
     static void Purge(JSContext* cx, HandleScript script);
 
-    void destroy();
+    void destroy(Zone* zone);
 
     size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+        // Note: icScript_ size is reported in jit::AddSizeOfBaselineData.
         return mallocSizeOf(this);
     }
 
 #ifdef DEBUG
     void printTypes(JSContext* cx, HandleScript script) const;
 #endif
 };
 
@@ -430,25 +441,25 @@ class TypeZone
     mozilla::Maybe<IonCompilationId> currentCompilationId() const {
         return currentCompilationId_.ref();
     }
     mozilla::Maybe<IonCompilationId>& currentCompilationIdRef() {
         return currentCompilationId_.ref();
     }
 };
 
-enum SpewChannel {
+enum TypeSpewChannel {
     ISpewOps,      /* ops: New constraints and types. */
     ISpewResult,   /* result: Final type sets. */
     SPEW_COUNT
 };
 
 #ifdef DEBUG
 
-bool InferSpewActive(SpewChannel channel);
+bool InferSpewActive(TypeSpewChannel channel);
 const char * InferSpewColorReset();
 const char * InferSpewColor(TypeConstraint* constraint);
 const char * InferSpewColor(TypeSet* types);
 
 #define InferSpew(channel, ...) if (InferSpewActive(channel)) { InferSpewImpl(__VA_ARGS__); } else {}
 void InferSpewImpl(const char* fmt, ...) MOZ_FORMAT_PRINTF(1, 2);
 
 /* Check that the type property for id in group contains value. */
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -12,16 +12,17 @@
 #include "jit/BaselineIC.h"
 #include "jit/ExecutableAllocator.h"
 #include "jit/JitCommon.h"
 #include "jit/Linker.h"
 
 #include "gc/Nursery-inl.h"
 #include "jit/MacroAssembler-inl.h"
 #include "vm/JSObject-inl.h"
+#include "vm/JSScript-inl.h"
 #include "vm/Shape-inl.h"
 #include "vm/TypeInference-inl.h"
 
 using mozilla::ArrayLength;
 using mozilla::PodCopy;
 
 using namespace js;
 
@@ -644,18 +645,18 @@ UnboxedLayout::makeNativeGroup(JSContext
 
         PlainObject* templateObject = &script->getObject(pc)->as<PlainObject>();
         replacementGroup->addDefiniteProperties(cx, templateObject->lastProperty());
 
         ObjectGroupRealm& realm = ObjectGroupRealm::get(group);
         realm.replaceAllocationSiteGroup(script, pc, JSProto_Object, replacementGroup);
 
         // Clear any baseline information at this opcode which might use the old group.
-        if (script->hasBaselineScript()) {
-            jit::ICEntry& entry = script->baselineScript()->icEntryFromPCOffset(script->pcToOffset(pc));
+        if (script->hasICScript()) {
+            jit::ICEntry& entry = script->icScript()->icEntryFromPCOffset(script->pcToOffset(pc));
             jit::ICFallbackStub* fallback = entry.fallbackStub();
             for (jit::ICStubIterator iter = fallback->beginChain(); !iter.atEnd(); iter++) {
                 iter.unlink(cx);
             }
             if (fallback->isNewObject_Fallback()) {
                 fallback->toNewObject_Fallback()->setTemplateObject(nullptr);
             } else if (fallback->isNewArray_Fallback()) {
                 fallback->toNewArray_Fallback()->setTemplateGroup(replacementGroup);
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/position-sticky-in-transformed-scrollframe-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html reftest-async-scroll>
+<div style="transform: translateY(10px); margin-top: 90px">
+<div style="overflow:scroll; height: 400px; width: 400px; background-color: yellow"
+     reftest-displayport-x="0"
+     reftest-displayport-y="0"
+     reftest-displayport-w="400"
+     reftest-displayport-h="800"
+     reftest-async-scroll-x="0"
+     reftest-async-scroll-y="120">
+<div style="height: 800px">
+<div style="position:sticky; top: 0px; height: 20px; background-color: green">sticky</div>
+</div>
+</div>
+</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/position-sticky-in-transformed-scrollframe-2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html reftest-async-scroll>
+<div style="transform: translateY(90px); margin-top: 10px">
+<div style="overflow:scroll; height: 400px; width: 400px; background-color: yellow"
+     reftest-displayport-x="0"
+     reftest-displayport-y="0"
+     reftest-displayport-w="400"
+     reftest-displayport-h="800"
+     reftest-async-scroll-x="0"
+     reftest-async-scroll-y="120">
+<div style="height: 800px">
+<div style="position:sticky; top: 0px; height: 20px; background-color: green">sticky</div>
+</div>
+</div>
+</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/position-sticky-in-transformed-scrollframe-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<div style="margin-top: 100px">
+<div id="scroller" style="overflow:scroll; height: 400px; width: 400px; background-color: yellow">
+<div style="height: 800px">
+<div style="position: relative; top: 120px; height: 20px; background-color: green">sticky</div>
+</div>
+</div>
+</div>
+<script>
+    document.getElementById('scroller').scrollTop = 120;
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/position-sticky-transformed-in-scrollframe-1-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<div id="scroller" style="overflow:scroll; height: 400px; width: 400px">
+<div style="transform: translateY(10px); margin-top: 90px; background-color: yellow; height: 400px">
+<div style="position: relative; top: 30px; height: 20px; background-color: green">sticky</div>
+</div>
+<div style="height: 400px">spacer</div>
+</div>
+<script>
+    document.getElementById('scroller').scrollTop = 120;
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/position-sticky-transformed-in-scrollframe-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html reftest-async-scroll>
+<div style="overflow:scroll; height: 400px; width: 400px"
+     reftest-displayport-x="0"
+     reftest-displayport-y="0"
+     reftest-displayport-w="400"
+     reftest-displayport-h="890"
+     reftest-async-scroll-x="0"
+     reftest-async-scroll-y="120">
+<div style="transform: translateY(10px); margin-top: 90px; background-color: yellow; height: 400px">
+<div style="position:sticky; top: 0px; height: 20px; background-color: green">sticky</div>
+</div>
+<div style="height: 400px">spacer</div>
+</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/position-sticky-transformed-in-scrollframe-2-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<div id="scroller" style="overflow:scroll; height: 400px; width: 400px">
+<div style="margin-top: 120px; background-color: yellow; height: 380px">
+<div style="height: 90px"></div>
+<div style="height: 20px; background-color: green">sticky</div>
+</div>
+<div style="height: 310px"></div>
+</div>
+<script>
+    document.getElementById('scroller').scrollTop = 120;
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/position-sticky-transformed-in-scrollframe-2.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html reftest-async-scroll>
+<div style="overflow:scroll; height: 400px; width: 400px"
+     reftest-displayport-x="0"
+     reftest-displayport-y="0"
+     reftest-displayport-w="400"
+     reftest-displayport-h="890"
+     reftest-async-scroll-x="0"
+     reftest-async-scroll-y="120">
+<div style="transform: translateY(90px); margin-top: 10px; background-color: yellow; height: 400px">
+<div style="position:sticky; top: 0px; height: 20px; background-color: green">sticky</div>
+</div>
+<div style="height: 400px">spacer</div>
+</div>
--- a/layout/reftests/async-scrolling/reftest.list
+++ b/layout/reftests/async-scrolling/reftest.list
@@ -65,16 +65,20 @@ fuzzy-if(Android,0-6,0-8) skip-if(!async
 fuzzy-if(Android,0-6,0-8) skip-if(!asyncPan) == fixed-pos-scrolled-clip-3.html fixed-pos-scrolled-clip-3-ref.html
 fuzzy-if(Android,0-6,0-8) skip-if(!asyncPan) == fixed-pos-scrolled-clip-4.html fixed-pos-scrolled-clip-4-ref.html
 skip-if(!asyncPan) == fixed-pos-scrolled-clip-5.html fixed-pos-scrolled-clip-5-ref.html
 skip-if(!asyncPan) == position-sticky-bug1434250.html position-sticky-bug1434250-ref.html
 fuzzy-if(Android,0-6,0-4) skip-if(!asyncPan) == position-sticky-scrolled-clip-1.html position-sticky-scrolled-clip-1-ref.html
 fuzzy-if(Android,0-6,0-4) skip == position-sticky-scrolled-clip-2.html position-sticky-scrolled-clip-2-ref.html # bug ?????? - incorrectly applying clip to sticky contents
 fuzzy-if(Android,0-2,0-4) skip-if(!asyncPan) == curtain-effect-1.html curtain-effect-1-ref.html
 fuzzy-if(Android,0-1,0-4) skip-if(!asyncPan) == transformed-1.html transformed-1-ref.html
+fuzzy-if(Android,2-2,4-4) skip-if(!asyncPan) == position-sticky-transformed-in-scrollframe-1.html position-sticky-transformed-in-scrollframe-1-ref.html
+fuzzy-if(Android,3-3,4-4) skip-if(!asyncPan) == position-sticky-transformed-in-scrollframe-2.html position-sticky-transformed-in-scrollframe-2-ref.html
+fuzzy-if(Android,3-3,4-4) skip-if(!asyncPan) == position-sticky-in-transformed-scrollframe-1.html position-sticky-in-transformed-scrollframe-ref.html
+fuzzy-if(Android,3-3,4-4) skip-if(!asyncPan) == position-sticky-in-transformed-scrollframe-2.html position-sticky-in-transformed-scrollframe-ref.html
 
 # for the following tests, we want to disable the low-precision buffer
 # as it will expand the displayport beyond what the test specifies in
 # its reftest-displayport attributes, and interfere with where we expect
 # checkerboarding to occur
 default-preferences pref(layers.low-precision-buffer,false)
 skip-if(!asyncPan) == checkerboard-1.html checkerboard-1-ref.html
 skip-if(!asyncPan) == checkerboard-2.html checkerboard-2-ref.html
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -233,22 +233,16 @@ var validGradientAndElementValues = [
   "radial-gradient(at calc(-25%) top, red, blue)",
   "radial-gradient(at left calc(-25%), red, blue)",
   "radial-gradient(at calc(-25px) top, red, blue)",
   "radial-gradient(at left calc(-25px), red, blue)",
   "radial-gradient(at calc(100px + -25%) top, red, blue)",
   "radial-gradient(at left calc(100px + -25%), red, blue)",
   "radial-gradient(at calc(100px + -25px) top, red, blue)",
   "radial-gradient(at left calc(100px + -25px), red, blue)",
-
-  "-webkit-linear-gradient(top, red, blue)",
-  "-moz-linear-gradient(top, red, blue)",
-  "-moz-linear-gradient(center 0%, red, blue)",
-  "-moz-linear-gradient(50% top, red, blue)",
-  "-moz-linear-gradient(50% 0%, red, blue)",
 ];
 var invalidGradientAndElementValues = [
   "-moz-element(#a:1)",
   "-moz-element(a#a)",
   "-moz-element(#a a)",
   "-moz-element(#a+a)",
   "-moz-element(#a())",
   /* no quirks mode colors */
@@ -642,16 +636,17 @@ if (IsCSSPropertyPrefEnabled("layout.css
 
     // Contain/cover keywords (valid only for -moz/-webkit prefixed):
     "-webkit-radial-gradient(cover, red, blue)",
     "-webkit-radial-gradient(cover circle, red, blue)",
     "-webkit-radial-gradient(contain, red, blue)",
     "-webkit-radial-gradient(contain ellipse, red, blue)",
 
     // Initial side/corner/point (valid only for -moz/-webkit prefixed):
+    "-webkit-linear-gradient(top, red, blue)",
     "-webkit-linear-gradient(left, red, blue)",
     "-webkit-linear-gradient(bottom, red, blue)",
     "-webkit-linear-gradient(right top, red, blue)",
     "-webkit-linear-gradient(top right, red, blue)",
     "-webkit-radial-gradient(right, red, blue)",
     "-webkit-radial-gradient(left bottom, red, blue)",
     "-webkit-radial-gradient(bottom left, red, blue)",
     "-webkit-radial-gradient(center, red, blue)",
@@ -845,16 +840,21 @@ if (IsCSSPropertyPrefEnabled("layout.css
     "-moz-linear-gradient(red, yellow, blue)",
     "-moz-linear-gradient(red 1px, yellow 20%, blue 24em, green)",
     "-moz-linear-gradient(red, yellow, green, blue 50%)",
     "-moz-linear-gradient(red -50%, yellow -25%, green, blue)",
     "-moz-linear-gradient(red -99px, yellow, green, blue 120%)",
     "-moz-linear-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))",
     "-moz-linear-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)",
 
+    "-moz-linear-gradient(top, red, blue)",
+    "-moz-linear-gradient(center 0%, red, blue)",
+    "-moz-linear-gradient(50% top, red, blue)",
+    "-moz-linear-gradient(50% 0%, red, blue)",
+
     "-moz-linear-gradient(to top, red, blue)",
     "-moz-linear-gradient(to bottom, red, blue)",
     "-moz-linear-gradient(to left, red, blue)",
     "-moz-linear-gradient(to right, red, blue)",
     "-moz-linear-gradient(to top left, red, blue)",
     "-moz-linear-gradient(to top right, red, blue)",
     "-moz-linear-gradient(to bottom left, red, blue)",
     "-moz-linear-gradient(to bottom right, red, blue)",
--- a/media/mtransport/nr_socket_prsock.cpp
+++ b/media/mtransport/nr_socket_prsock.cpp
@@ -111,16 +111,17 @@ nrappkit copyright:
 #include "nsTArray.h"
 #include "mozilla/dom/TCPSocketBinding.h"
 #include "mozilla/SystemGroup.h"
 #include "nsITCPSocketCallback.h"
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
 #include "nsISocketFilter.h"
 #include "nsDebug.h"
+#include "nsNetUtil.h"
 
 #ifdef XP_WIN
 #include "mozilla/WindowsVersion.h"
 #endif
 
 #if defined(MOZILLA_INTERNAL_API)
 // csi_platform.h deep in nrappkit defines LOG_INFO and LOG_WARNING
 #ifdef LOG_INFO
@@ -2155,16 +2156,20 @@ static nr_socket_vtbl nr_socket_local_vt
 /* static */
 int
 NrSocketBase::CreateSocket(nr_transport_addr *addr,
                            RefPtr<NrSocketBase> *sock,
                            const std::shared_ptr<NrSocketProxyConfig>& config)
 {
   int r, _status;
 
+  if (IsForbiddenAddress(addr)) {
+    ABORT(R_REJECTED);
+  }
+
   // create IPC bridge for content process
   if (XRE_IsParentProcess()) {
     *sock = new NrSocket();
   } else {
     switch (addr->protocol) {
       case IPPROTO_UDP:
         *sock = new NrUdpSocketIpc();
         break;
@@ -2191,16 +2196,36 @@ NrSocketBase::CreateSocket(nr_transport_
   _status = 0;
 abort:
   if (_status) {
     *sock = nullptr;
   }
   return _status;
 }
 
+// static
+bool NrSocketBase::IsForbiddenAddress(nr_transport_addr *addr) {
+  int r, port;
+
+  r = nr_transport_addr_get_port(addr, &port);
+  if (r) {
+    return true;
+  }
+
+  // allow auto assigned ports
+  if (port != 0) {
+    // Don't need to check an override scheme
+    nsresult rv = NS_CheckPortSafety(port, nullptr);
+    if(NS_FAILED(rv)) {
+      return true;
+    }
+  }
+
+  return false;
+}
 
 static int nr_socket_local_destroy(void **objp) {
   if(!objp || !*objp)
     return 0;
 
   NrSocketBase *sock = static_cast<NrSocketBase *>(*objp);
   *objp = nullptr;
 
--- a/media/mtransport/nr_socket_prsock.h
+++ b/media/mtransport/nr_socket_prsock.h
@@ -98,16 +98,17 @@ public:
   }
   virtual ~NrSocketBase() {}
 
   // Factory method; will create either an NrSocket, NrUdpSocketIpc, or
   // NrTcpSocketIpc as appropriate.
   static int CreateSocket(nr_transport_addr *addr,
                           RefPtr<NrSocketBase> *sock,
                           const std::shared_ptr<NrSocketProxyConfig>& config);
+  static bool IsForbiddenAddress(nr_transport_addr *addr);
 
   // the nr_socket APIs
   virtual int create(nr_transport_addr *addr) = 0;
   virtual int sendto(const void *msg, size_t len,
                      int flags, nr_transport_addr *to) = 0;
   virtual int recvfrom(void * buf, size_t maxlen,
                        size_t *len, int flags,
                        nr_transport_addr *from) = 0;
--- a/media/mtransport/test/test_nr_socket_unittest.cpp
+++ b/media/mtransport/test/test_nr_socket_unittest.cpp
@@ -457,16 +457,57 @@ class TestNrSocketTest : public Mtranspo
   std::vector<RefPtr<TestNrSocket>> private_addrs_;
   std::vector<RefPtr<TestNat>> nats_;
 };
 
 } // namespace mozilla
 
 using mozilla::TestNrSocketTest;
 using mozilla::TestNat;
+using mozilla::NrSocketBase;
+
+TEST_F(TestNrSocketTest, UnsafePortRejectedUDP) {
+  nr_transport_addr address;
+  ASSERT_FALSE(nr_str_port_to_transport_addr("127.0.0.1",
+                                             // ssh
+                                             22,
+                                             IPPROTO_UDP,
+                                             &address));
+  ASSERT_TRUE(NrSocketBase::IsForbiddenAddress(&address));
+}
+
+TEST_F(TestNrSocketTest, UnsafePortRejectedTCP) {
+  nr_transport_addr address;
+  ASSERT_FALSE(nr_str_port_to_transport_addr("127.0.0.1",
+                                             // ssh
+                                             22,
+                                             IPPROTO_TCP,
+                                             &address));
+  ASSERT_TRUE(NrSocketBase::IsForbiddenAddress(&address));
+}
+
+TEST_F(TestNrSocketTest, SafePortAcceptedUDP) {
+  nr_transport_addr address;
+  ASSERT_FALSE(nr_str_port_to_transport_addr("127.0.0.1",
+                                             // stuns
+                                             5349,
+                                             IPPROTO_UDP,
+                                             &address));
+  ASSERT_FALSE(NrSocketBase::IsForbiddenAddress(&address));
+}
+
+TEST_F(TestNrSocketTest, SafePortAcceptedTCP) {
+  nr_transport_addr address;
+  ASSERT_FALSE(nr_str_port_to_transport_addr("127.0.0.1",
+                                             // turns
+                                             5349,
+                                             IPPROTO_TCP,
+                                             &address));
+  ASSERT_FALSE(NrSocketBase::IsForbiddenAddress(&address));
+}
 
 TEST_F(TestNrSocketTest, PublicConnectivity) {
   CreatePublicAddrs(2);
 
   ASSERT_TRUE(CheckConnectivity(public_addrs_[0], public_addrs_[1]));
   ASSERT_TRUE(CheckConnectivity(public_addrs_[1], public_addrs_[0]));
   ASSERT_TRUE(CheckConnectivity(public_addrs_[0], public_addrs_[0]));
   ASSERT_TRUE(CheckConnectivity(public_addrs_[1], public_addrs_[1]));
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
@@ -1668,34 +1668,33 @@ public:
     }
     MOZ_LOG(gMediaPipelineLog, LogLevel::Debug,
             ("GenericReceiveListener added %s track %d (%p) to stream %p",
              mTrack->AsAudioStreamTrack() ? "audio" : "video",
              mTrackId,
              mTrack.get(),
              mSource.get()));
 
-    mSource->AdvanceKnownTracksTime(STREAM_TIME_MAX);
     mSource->AddTrackListener(this, mTrackId);
   }
 
   void AddSelf()
   {
     if (!mListening) {
       mListening = true;
-      mSource->SetPullEnabled(true);
+      mSource->SetPullingEnabled(mTrackId, true);
       mMaybeTrackNeedsUnmute = true;
     }
   }
 
   void RemoveSelf()
   {
     if (mListening) {
       mListening = false;
-      mSource->SetPullEnabled(false);
+      mSource->SetPullingEnabled(mTrackId, false);
     }
   }
 
   void OnRtpReceived()
   {
     if (mMaybeTrackNeedsUnmute) {
       mMaybeTrackNeedsUnmute = false;
       NS_DispatchToMainThread(
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
@@ -149,17 +149,19 @@ public final class GeckoLoader {
         f = Environment.getDownloadCacheDirectory();
         putenv("EXTERNAL_STORAGE=" + f.getPath());
 
         // setup the app-specific cache path
         f = context.getCacheDir();
         putenv("CACHE_DIRECTORY=" + f.getPath());
 
         f = context.getExternalFilesDir(null);
-        putenv("PUBLIC_STORAGE=" + f.getPath());
+        if (f != null) {
+            putenv("PUBLIC_STORAGE=" + f.getPath());
+        }
 
         if (Build.VERSION.SDK_INT >= 17) {
             android.os.UserManager um = (android.os.UserManager)context.getSystemService(Context.USER_SERVICE);
             if (um != null) {
                 putenv("MOZ_ANDROID_USER_SERIAL_NUMBER=" + um.getSerialNumberForUser(android.os.Process.myUserHandle()));
             } else {
                 Log.d(LOGTAG, "Unable to obtain user manager service on a device with SDK version " + Build.VERSION.SDK_INT);
             }
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -709,22 +709,28 @@ VARCACHE_PREF(
 // this pref has no effect if the master 'layout.css.prefixes.webkit' pref is
 // set to false.)
 VARCACHE_PREF(
   "layout.css.prefixes.device-pixel-ratio-webkit",
    layout_css_prefixes_device_pixel_ratio_webkit,
   bool, true
 )
 
-// Is -moz-prefixed gradient functions enabled?
+// Are -moz-prefixed gradient functions enabled?
+#ifdef EARLY_BETA_OR_EARLIER
+# define PREF_VALUE false
+#else
+# define PREF_VALUE true
+#endif
 VARCACHE_PREF(
   "layout.css.prefixes.gradients",
    layout_css_prefixes_gradients,
-  bool, true
+  bool, PREF_VALUE
 )
+#undef PREF_VALUE
 
 // Whether the offset-* logical property aliases are enabled.
 VARCACHE_PREF(
   "layout.css.offset-logical-properties.enabled",
    layout_css_offset_logical_properties_enabled,
   bool, false
 )
 
--- a/security/manager/ssl/OSReauthenticator.cpp
+++ b/security/manager/ssl/OSReauthenticator.cpp
@@ -211,67 +211,16 @@ ReauthenticateUserWindows(const nsACStri
         reauthenticated = true;
         break;
     }
   }
   return NS_OK;
 }
 #endif // XP_WIN
 
-#ifdef XP_MACOSX
-#include <CoreFoundation/CoreFoundation.h>
-#include <Security/Security.h>
-
-static nsresult
-ReauthenticateUserMacOS(const nsACString& aPrompt,
-              /* out */ bool& aReauthenticated)
-{
-  // The idea here is that we ask to be authorized to unlock the user's session.
-  // This should cause a prompt to come up for the user asking them for their
-  // password. If they correctly enter it, we'll return a successful result. If
-  // they cancel the prompt or otherwise fail to provide their password, we
-  // return a failing result.
-  AuthorizationItem authorizationItems[] = {
-    { "system.login.screensaver", 0, NULL, 0 },
-  };
-  AuthorizationRights authorizationRights = {
-    ArrayLength(authorizationItems),
-    authorizationItems,
-  };
-  const nsCString& promptFlat = PromiseFlatCString(aPrompt);
-  // All "kAuthorization..." constants come from the MacOS SDK.
-  AuthorizationItem environmentItems[] =  {
-    { kAuthorizationEnvironmentPrompt,
-      promptFlat.Length(),
-      (void*)promptFlat.get(),
-      0
-    },
-  };
-  AuthorizationEnvironment environment = {
-    ArrayLength(environmentItems),
-    environmentItems,
-  };
-  AuthorizationFlags flags = kAuthorizationFlagDefaults |
-                             kAuthorizationFlagInteractionAllowed |
-                             kAuthorizationFlagExtendRights |
-                             kAuthorizationFlagPreAuthorize |
-                             kAuthorizationFlagDestroyRights;
-  AuthorizationRef authorizationRef = nullptr;
-  OSStatus result = AuthorizationCreate(&authorizationRights,
-                                        &environment,
-                                        flags,
-                                        &authorizationRef);
-  aReauthenticated = result == errAuthorizationSuccess;
-  if (authorizationRef) {
-    AuthorizationFree(authorizationRef, kAuthorizationFlagDestroyRights);
-  }
-  return NS_OK;
-}
-#endif // XP_MACOSX
-
 static nsresult
 ReauthenticateUser(const nsACString& prompt, /* out */ bool& reauthenticated)
 {
   reauthenticated = false;
 #if defined(XP_WIN)
   return ReauthenticateUserWindows(prompt, reauthenticated);
 #elif defined(XP_MACOSX)
   return ReauthenticateUserMacOS(prompt, reauthenticated);
--- a/security/manager/ssl/OSReauthenticator.h
+++ b/security/manager/ssl/OSReauthenticator.h
@@ -18,9 +18,15 @@ class OSReauthenticator : public nsIOSRe
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOSREAUTHENTICATOR
 
 private:
   virtual ~OSReauthenticator() = default;
 };
 
+#ifdef XP_MACOSX
+nsresult
+ReauthenticateUserMacOS(const nsACString& aPrompt,
+              /* out */ bool& aReauthenticated);
+#endif // XP_MACOSX
+
 #endif // OSReauthenticator_h
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/OSReauthenticatorDarwin.mm
@@ -0,0 +1,55 @@
+/* -*- 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 "OSReauthenticator.h"
+
+#include "nsCocoaUtils.h"
+
+using namespace mozilla;
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <LocalAuthentication/LocalAuthentication.h>
+
+nsresult
+ReauthenticateUserMacOS(const nsACString& aPrompt,
+              /* out */ bool& aReauthenticated)
+{
+  // The idea here is that we ask to be authorized to unlock the user's session.
+  // This should cause a prompt to come up for the user asking them for their
+  // password. If they correctly enter it, we'll set aReauthenticated to true.
+
+  LAContext* context = [[LAContext alloc] init];
+  NSString* prompt = nsCocoaUtils::ToNSString(NS_ConvertUTF8toUTF16(aPrompt));
+
+  dispatch_semaphore_t sema = dispatch_semaphore_create(0);
+
+  __block BOOL biometricSuccess; // mark variable r/w across the block
+
+  // Note: This is an async callback in an already-async Promise chain.
+  [context evaluatePolicy:LAPolicyDeviceOwnerAuthentication
+           localizedReason:prompt
+           reply:^(BOOL success, NSError *error) {
+    dispatch_async(dispatch_get_main_queue(), ^{
+      // error is not particularly useful in this context, and we have no
+      // mechanism to really return it. We could use it to set the nsresult,
+      // but this is a best-effort mechanism and there's no particular case for
+      // propagating up XPCOM.
+      biometricSuccess = success;
+      dispatch_semaphore_signal(sema);
+    });
+  }];
+
+  // What we want to do here is convert this into a blocking call, since
+  // our calling methods expect us to block and set aReauthenticated on return.
+  dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
+  dispatch_release(sema);
+  sema = NULL;
+
+  aReauthenticated = biometricSuccess;
+
+  [context release];
+  return NS_OK;
+}
--- a/security/manager/ssl/moz.build
+++ b/security/manager/ssl/moz.build
@@ -146,19 +146,21 @@ if CONFIG['MOZ_LIB_SECRET']:
         'LibSecret.cpp',
     ]
     CFLAGS += CONFIG['MOZ_LIB_SECRET_CFLAGS']
     CXXFLAGS += CONFIG['MOZ_LIB_SECRET_CFLAGS']
 
 if CONFIG['OS_ARCH'] == 'Darwin':
     UNIFIED_SOURCES += [
         'KeychainSecret.cpp',
+        'OSReauthenticatorDarwin.mm',
     ]
     OS_LIBS += [
-        '-framework Security'
+        '-framework LocalAuthentication',
+        '-framework Security',
     ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     OS_LIBS += [
         'credui'
     ]
     UNIFIED_SOURCES += [
         'CredentialManagerSecret.cpp',
--- a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
+++ b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
@@ -670,16 +670,97 @@ SandboxBroker::SetSecurityLevelForGPUPro
 #define SANDBOX_ENSURE_SUCCESS(result, message) \
   do { \
     MOZ_ASSERT(sandbox::SBOX_ALL_OK == result, message); \
     if (sandbox::SBOX_ALL_OK != result) \
       return false; \
   } while (0)
 
 bool
+SandboxBroker::SetSecurityLevelForRDDProcess()
+{
+  if (!mPolicy) {
+    return false;
+  }
+
+  auto result = SetJobLevel(mPolicy, sandbox::JOB_LOCKDOWN,
+                            0 /* ui_exceptions */);
+  SANDBOX_ENSURE_SUCCESS(result,
+                         "SetJobLevel should never fail with these arguments, what happened?");
+
+  result = mPolicy->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS,
+                                  sandbox::USER_LOCKDOWN);
+  SANDBOX_ENSURE_SUCCESS(result,
+                         "SetTokenLevel should never fail with these arguments, what happened?");
+
+  result = mPolicy->SetAlternateDesktop(true);
+  if (NS_WARN_IF(result != sandbox::SBOX_ALL_OK)) {
+    LOG_W("SetAlternateDesktop failed, result: %i, last error: %x",
+          result, ::GetLastError());
+  }
+
+  result = mPolicy->SetIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW);
+  SANDBOX_ENSURE_SUCCESS(result,
+                         "SetIntegrityLevel should never fail with these arguments, what happened?");
+
+  result =
+    mPolicy->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_UNTRUSTED);
+  SANDBOX_ENSURE_SUCCESS(result,
+                         "SetDelayedIntegrityLevel should never fail with these arguments, what happened?");
+
+  sandbox::MitigationFlags mitigations =
+    sandbox::MITIGATION_BOTTOM_UP_ASLR |
+    sandbox::MITIGATION_HEAP_TERMINATE |
+    sandbox::MITIGATION_SEHOP |
+    sandbox::MITIGATION_EXTENSION_POINT_DISABLE |
+    sandbox::MITIGATION_DEP_NO_ATL_THUNK |
+    sandbox::MITIGATION_DEP |
+    sandbox::MITIGATION_DYNAMIC_CODE_DISABLE |
+    sandbox::MITIGATION_IMAGE_LOAD_PREFER_SYS32;
+
+  result = mPolicy->SetProcessMitigations(mitigations);
+  SANDBOX_ENSURE_SUCCESS(result,
+                         "Invalid flags for SetProcessMitigations.");
+
+  mitigations =
+    sandbox::MITIGATION_STRICT_HANDLE_CHECKS |
+    sandbox::MITIGATION_DLL_SEARCH_ORDER;
+
+  result = mPolicy->SetDelayedProcessMitigations(mitigations);
+  SANDBOX_ENSURE_SUCCESS(result,
+                         "Invalid flags for SetDelayedProcessMitigations.");
+
+  // Add the policy for the client side of a pipe. It is just a file
+  // in the \pipe\ namespace. We restrict it to pipes that start with
+  // "chrome." so the sandboxed process cannot connect to system services.
+  result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES,
+                            sandbox::TargetPolicy::FILES_ALLOW_ANY,
+                            L"\\??\\pipe\\chrome.*");
+  SANDBOX_ENSURE_SUCCESS(result,
+                         "With these static arguments AddRule should never fail, what happened?");
+
+  // Add the policy for the client side of the crash server pipe.
+  result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES,
+                            sandbox::TargetPolicy::FILES_ALLOW_ANY,
+                            L"\\??\\pipe\\gecko-crash-server-pipe.*");
+  SANDBOX_ENSURE_SUCCESS(result,
+                         "With these static arguments AddRule should never fail, what happened?");
+
+  // The process needs to be able to duplicate shared memory handles,
+  // which are Section handles, to the content processes.
+  result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_HANDLES,
+                            sandbox::TargetPolicy::HANDLES_DUP_ANY,
+                            L"Section");
+  SANDBOX_ENSURE_SUCCESS(result,
+                         "With these static arguments AddRule should never fail, what happened?");
+
+  return true;
+}
+
+bool
 SandboxBroker::SetSecurityLevelForPluginProcess(int32_t aSandboxLevel)
 {
   if (!mPolicy) {
     return false;
   }
 
   sandbox::JobLevel jobLevel;
   sandbox::TokenLevel accessTokenLevel;
--- a/security/sandbox/win/src/sandboxbroker/sandboxBroker.h
+++ b/security/sandbox/win/src/sandboxbroker/sandboxBroker.h
@@ -44,16 +44,17 @@ public:
 
   // Security levels for different types of processes
 #if defined(MOZ_CONTENT_SANDBOX)
   void SetSecurityLevelForContentProcess(int32_t aSandboxLevel,
                                          bool aIsFileProcess);
 #endif
 
   void SetSecurityLevelForGPUProcess(int32_t aSandboxLevel);
+  bool SetSecurityLevelForRDDProcess();
 
   bool SetSecurityLevelForPluginProcess(int32_t aSandboxLevel);
   enum SandboxLevel {
     LockDown,
     Restricted
   };
   bool SetSecurityLevelForGMPlugin(SandboxLevel aLevel);
 
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/wptrunner.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/wptrunner.py
@@ -279,16 +279,18 @@ def run_tests(config, test_paths, produc
                         unexpected_count += manager_group.unexpected_count()
 
                 test_total += test_count
                 unexpected_total += unexpected_count
                 logger.info("Got %i unexpected results" % unexpected_count)
                 logger.suite_end()
                 if repeat_until_unexpected and unexpected_total > 0:
                     break
+                if len(test_loader.test_ids) == skipped_tests:
+                    break;
 
     if test_total == 0:
         if skipped_tests > 0:
             logger.warning("All requested tests were skipped")
         else:
             logger.error("No tests ran")
             return False
 
--- a/toolkit/components/extensions/parent/ext-proxy.js
+++ b/toolkit/components/extensions/parent/ext-proxy.js
@@ -27,16 +27,23 @@ const proxySvc = Ci.nsIProtocolProxyServ
 const PROXY_TYPES_MAP = new Map([
   ["none", proxySvc.PROXYCONFIG_DIRECT],
   ["autoDetect", proxySvc.PROXYCONFIG_WPAD],
   ["system", proxySvc.PROXYCONFIG_SYSTEM],
   ["manual", proxySvc.PROXYCONFIG_MANUAL],
   ["autoConfig", proxySvc.PROXYCONFIG_PAC],
 ]);
 
+const DEFAULT_PORTS = new Map([
+  ["http", 80],
+  ["ssl", 443],
+  ["ftp", 21],
+  ["socks", 1080],
+]);
+
 ExtensionPreferencesManager.addSetting("proxy.settings", {
   prefNames: [
     "network.proxy.type",
     "network.proxy.http",
     "network.proxy.http_port",
     "network.proxy.share_proxy_settings",
     "network.proxy.ftp",
     "network.proxy.ftp_port",
@@ -61,18 +68,18 @@ ExtensionPreferencesManager.addSetting("
       "network.proxy.socks_version": value.socksVersion,
       "network.proxy.no_proxies_on": value.passthrough,
     };
 
     for (let prop of ["http", "ftp", "ssl", "socks"]) {
       if (value[prop]) {
         let url = new URL(`http://${value[prop]}`);
         prefs[`network.proxy.${prop}`] = url.hostname;
-        let port = parseInt(url.port, 10);
-        prefs[`network.proxy.${prop}_port`] = isNaN(port) ? 0 : port;
+        let port = parseInt(url.port, 10) || DEFAULT_PORTS.get(prop);
+        prefs[`network.proxy.${prop}_port`] = port;
       } else {
         prefs[`network.proxy.${prop}`] = undefined;
         prefs[`network.proxy.${prop}_port`] = undefined;
       }
     }
 
     return prefs;
   },
--- a/toolkit/components/extensions/schemas/theme.json
+++ b/toolkit/components/extensions/schemas/theme.json
@@ -83,17 +83,18 @@
               "additional_backgrounds": {
                 "type": "array",
                 "items": { "$ref": "ImageDataOrExtensionURL" },
                 "maxItems": 15,
                 "optional": true
               },
               "headerURL": {
                 "$ref": "ImageDataOrExtensionURL",
-                "optional": true
+                "optional": true,
+                "deprecated": "Please use <em>theme.images.theme_frame</em>, this alias will be removed in Firefox 69."
               },
               "theme_frame": {
                 "$ref": "ImageDataOrExtensionURL",
                 "optional": true
               }
             },
             "additionalProperties": { "$ref": "ImageDataOrExtensionURL" }
           },
@@ -102,29 +103,31 @@
             "optional": true,
             "properties": {
               "tab_selected": {
                 "$ref": "ThemeColor",
                 "optional": true
               },
               "accentcolor": {
                 "$ref": "ThemeColor",
-                "optional": true
+                "optional": true,
+                "deprecated": "Please use <em>theme.colors.frame</em>, this alias will be removed in Firefox 69."
               },
               "frame": {
                 "$ref": "ThemeColor",
                 "optional": true
               },
               "frame_inactive": {
                 "$ref": "ThemeColor",
                 "optional": true
               },
               "textcolor": {
                 "$ref": "ThemeColor",
-                "optional": true
+                "optional": true,
+                "deprecated": "Please use <em>theme.colors.tab_background_text</em>, this alias will be removed in Firefox 69."
               },
               "tab_background_text": {
                 "$ref": "ThemeColor",
                 "optional": true
               },
               "tab_background_separator": {
                 "$ref": "ThemeColor",
                 "optional": true
@@ -142,17 +145,18 @@
                 "optional": true
               },
               "toolbar": {
                 "$ref": "ThemeColor",
                 "optional": true
               },
               "toolbar_text": {
                 "$ref": "ThemeColor",
-                "optional": true
+                "optional": true,
+                "deprecated": "Please use <em>theme.colors.bookmark_text</em>, this alias will be removed in Firefox 69."
               },
               "bookmark_text": {
                 "$ref": "ThemeColor",
                 "optional": true
               },
               "toolbar_field": {
                 "$ref": "ThemeColor",
                 "optional": true
--- a/toolkit/components/extensions/test/browser/browser_ext_management_themes.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_management_themes.js
@@ -12,17 +12,17 @@ add_task(async function test_management_
 
   let theme = ExtensionTestUtils.loadExtension({
     manifest: {
       "name": "Simple theme test",
       "version": "1.0",
       "description": "test theme",
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
       },
     },
     files: {
       "image1.png": BACKGROUND,
     },
     useAddonManager: "temporary",
   });
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_additional_backgrounds_alignment.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_additional_backgrounds_alignment.js
@@ -1,27 +1,27 @@
 "use strict";
 
 /* globals InspectorUtils */
 
-// Case 1 - When there is a headerURL image and additional_backgrounds_alignment is not specified.
+// Case 1 - When there is a theme_frame image and additional_backgrounds_alignment is not specified.
 // So background-position should default to "right top"
 add_task(async function test_default_additional_backgrounds_alignment() {
   const RIGHT_TOP = "100% 0%";
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
           "additional_backgrounds": ["image1.png", "image1.png"],
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
         },
       },
     },
     files: {
       "image1.png": BACKGROUND,
     },
 
   });
@@ -29,49 +29,49 @@ add_task(async function test_default_add
   await extension.startup();
 
   let docEl = document.documentElement;
   let rootCS = window.getComputedStyle(docEl);
 
   Assert.equal(
     rootCS.getPropertyValue("background-position"),
     RIGHT_TOP,
-    "root only contains headerURL alignment property"
+    "root only contains theme_frame alignment property"
   );
 
 
   let toolbox = document.querySelector("#navigator-toolbox");
   let toolboxCS = window.getComputedStyle(toolbox);
 
   Assert.equal(
     toolboxCS.getPropertyValue("background-position"),
     RIGHT_TOP,
     toolbox.id + " only contains default additional backgrounds alignment property"
   );
 
   await extension.unload();
 });
 
 
-// Case 2 - When there is a headerURL image and additional_backgrounds_alignment is specified.
+// Case 2 - When there is a theme_frame image and additional_backgrounds_alignment is specified.
 add_task(async function test_additional_backgrounds_alignment() {
   const LEFT_BOTTOM = "0% 100%";
   const CENTER_CENTER = "50% 50%";
   const RIGHT_TOP = "100% 0%";
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
           "additional_backgrounds": ["image1.png", "image1.png", "image1.png"],
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
         },
         "properties": {
           additional_backgrounds_alignment: ["left bottom", "center center", "right top"],
         },
       },
     },
     files: {
       "image1.png": BACKGROUND,
@@ -82,17 +82,17 @@ add_task(async function test_additional_
   await extension.startup();
 
   let docEl = document.documentElement;
   let rootCS = window.getComputedStyle(docEl);
 
   Assert.equal(
     rootCS.getPropertyValue("background-position"),
     RIGHT_TOP,
-    "root only contains headerURL alignment property"
+    "root only contains theme_frame alignment property"
   );
 
 
   let toolbox = document.querySelector("#navigator-toolbox");
   let toolboxCS = window.getComputedStyle(toolbox);
 
   Assert.equal(
     toolboxCS.getPropertyValue("background-position"),
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_alpha_accentcolor.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_alpha_accentcolor.js
@@ -1,20 +1,20 @@
 "use strict";
 
-add_task(async function test_alpha_accentcolor() {
+add_task(async function test_alpha_frame_color() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
         "colors": {
-          "accentcolor": "rgba(230, 128, 0, 0.1)",
-          "textcolor": TEXT_COLOR,
+          "frame": "rgba(230, 128, 0, 0.1)",
+          "tab_background_text": TEXT_COLOR,
         },
       },
     },
     files: {
       "image1.png": BACKGROUND,
     },
   });
 
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_arrowpanels.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_arrowpanels.js
@@ -19,21 +19,21 @@ add_task(async function test_popup_styli
   const POPUP_BACKGROUND_COLOR = "#FF0000";
   const POPUP_TEXT_COLOR = "#008000";
   const POPUP_BORDER_COLOR = "#0000FF";
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
           "popup": POPUP_BACKGROUND_COLOR,
           "popup_text": POPUP_TEXT_COLOR,
           "popup_border": POPUP_BORDER_COLOR,
         },
       },
     },
     files: {
       "image1.png": BACKGROUND,
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_autocomplete_popup.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_autocomplete_popup.js
@@ -75,21 +75,21 @@ add_task(async function setup() {
 });
 
 add_task(async function test_popup_url() {
   // Load extension with brighttext not set
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
           "popup": POPUP_COLOR,
           "popup_border": POPUP_BORDER_COLOR,
           "popup_text": POPUP_TEXT_COLOR_DARK,
           "popup_highlight": POPUP_SELECTED_COLOR,
           "popup_highlight_text": POPUP_SELECTED_TEXT_COLOR,
         },
       },
     },
@@ -174,21 +174,21 @@ add_task(async function test_popup_url()
                false,
                "lwt-popup-darktext attribute should be removed");
 
   // Load a manifest with popup_text being bright. Test for bright text properties.
   extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
           "popup": POPUP_COLOR,
           "popup_text": POPUP_TEXT_COLOR_BRIGHT,
           "popup_highlight": POPUP_SELECTED_COLOR,
           "popup_highlight_text": POPUP_SELECTED_TEXT_COLOR,
         },
       },
     },
     files: {
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_getCurrent.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_getCurrent.js
@@ -14,31 +14,31 @@ add_task(async function test_get_current
       const ACCENT_COLOR_1 = "#a14040";
       const TEXT_COLOR_1 = "#fac96e";
 
       const ACCENT_COLOR_2 = "#03fe03";
       const TEXT_COLOR_2 = "#0ef325";
 
       const theme1 = {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR_1,
-          "textcolor": TEXT_COLOR_1,
+          "frame": ACCENT_COLOR_1,
+          "tab_background_text": TEXT_COLOR_1,
         },
       };
 
       const theme2 = {
         "images": {
-          "headerURL": "image2.png",
+          "theme_frame": "image2.png",
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR_2,
-          "textcolor": TEXT_COLOR_2,
+          "frame": ACCENT_COLOR_2,
+          "tab_background_text": TEXT_COLOR_2,
         },
       };
 
       function ensureWindowFocused(winId) {
         browser.test.log("Waiting for focused window to be " + winId);
         return new Promise(async (resolve) => {
           let listener = windowId => {
             if (windowId === winId) {
@@ -56,36 +56,36 @@ add_task(async function test_get_current
             browser.windows.onFocusChanged.removeListener(listener);
             resolve();
           }
         });
       }
 
       function testTheme1(returnedTheme) {
         browser.test.assertTrue(
-          returnedTheme.images.headerURL.includes("image1.png"),
-          "Theme 1 header URL should be applied");
+          returnedTheme.images.theme_frame.includes("image1.png"),
+          "Theme 1 theme_frame image should be applied");
         browser.test.assertEq(
-          ACCENT_COLOR_1, returnedTheme.colors.accentcolor,
-          "Theme 1 accent color should be applied");
+          ACCENT_COLOR_1, returnedTheme.colors.frame,
+          "Theme 1 frame color should be applied");
         browser.test.assertEq(
-          TEXT_COLOR_1, returnedTheme.colors.textcolor,
-          "Theme 1 text color should be applied");
+          TEXT_COLOR_1, returnedTheme.colors.tab_background_text,
+          "Theme 1 tab_background_text color should be applied");
       }
 
       function testTheme2(returnedTheme) {
         browser.test.assertTrue(
-          returnedTheme.images.headerURL.includes("image2.png"),
-          "Theme 2 header URL should be applied");
+          returnedTheme.images.theme_frame.includes("image2.png"),
+          "Theme 2 theme_frame image should be applied");
         browser.test.assertEq(
-          ACCENT_COLOR_2, returnedTheme.colors.accentcolor,
-          "Theme 2 accent color should be applied");
+          ACCENT_COLOR_2, returnedTheme.colors.frame,
+          "Theme 2 frame color should be applied");
         browser.test.assertEq(
-          TEXT_COLOR_2, returnedTheme.colors.textcolor,
-          "Theme 2 text color should be applied");
+          TEXT_COLOR_2, returnedTheme.colors.tab_background_text,
+          "Theme 2 tab_background_text color should be applied");
       }
 
       function testEmptyTheme(returnedTheme) {
         browser.test.assertEq(0, Object.keys(returnedTheme).length, JSON.stringify(returnedTheme, null, 2));
       }
 
       browser.test.log("Testing getCurrent() with initial unthemed window");
       const firstWin = await browser.windows.getCurrent();
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_onUpdated.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_onUpdated.js
@@ -14,56 +14,56 @@ add_task(async function test_on_updated(
       const ACCENT_COLOR_1 = "#a14040";
       const TEXT_COLOR_1 = "#fac96e";
 
       const ACCENT_COLOR_2 = "#03fe03";
       const TEXT_COLOR_2 = "#0ef325";
 
       const theme1 = {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR_1,
-          "textcolor": TEXT_COLOR_1,
+          "frame": ACCENT_COLOR_1,
+          "tab_background_text": TEXT_COLOR_1,
         },
       };
 
       const theme2 = {
         "images": {
-          "headerURL": "image2.png",
+          "theme_frame": "image2.png",
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR_2,
-          "textcolor": TEXT_COLOR_2,
+          "frame": ACCENT_COLOR_2,
+          "tab_background_text": TEXT_COLOR_2,
         },
       };
 
       function testTheme1(returnedTheme) {
         browser.test.assertTrue(
-          returnedTheme.images.headerURL.includes("image1.png"),
-          "Theme 1 header URL should be applied");
+          returnedTheme.images.theme_frame.includes("image1.png"),
+          "Theme 1 theme_frame image should be applied");
         browser.test.assertEq(
-          ACCENT_COLOR_1, returnedTheme.colors.accentcolor,
-          "Theme 1 accent color should be applied");
+          ACCENT_COLOR_1, returnedTheme.colors.frame,
+          "Theme 1 frame color should be applied");
         browser.test.assertEq(
-          TEXT_COLOR_1, returnedTheme.colors.textcolor,
-          "Theme 1 text color should be applied");
+          TEXT_COLOR_1, returnedTheme.colors.tab_background_text,
+          "Theme 1 tab_background_text color should be applied");
       }
 
       function testTheme2(returnedTheme) {
         browser.test.assertTrue(
-          returnedTheme.images.headerURL.includes("image2.png"),
-          "Theme 2 header URL should be applied");
+          returnedTheme.images.theme_frame.includes("image2.png"),
+          "Theme 2 theme_frame image should be applied");
         browser.test.assertEq(
-          ACCENT_COLOR_2, returnedTheme.colors.accentcolor,
-          "Theme 2 accent color should be applied");
+          ACCENT_COLOR_2, returnedTheme.colors.frame,
+          "Theme 2 frame color should be applied");
         browser.test.assertEq(
-          TEXT_COLOR_2, returnedTheme.colors.textcolor,
-          "Theme 2 text color should be applied");
+          TEXT_COLOR_2, returnedTheme.colors.tab_background_text,
+          "Theme 2 tab_background_text color should be applied");
       }
 
       const firstWin = await browser.windows.getCurrent();
       const secondWin = await browser.windows.create();
 
       const onceThemeUpdated = () => new Promise(resolve => {
         const listener = updateInfo => {
           browser.theme.onUpdated.removeListener(listener);
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_updates.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_updates.js
@@ -60,35 +60,37 @@ add_task(async function test_dynamic_the
     },
   });
 
   let defaultStyle = window.getComputedStyle(window.document.documentElement);
   await extension.startup();
 
   extension.sendMessage("update-theme", {
     "images": {
-      "headerURL": "image1.png",
+      "theme_frame": "image1.png",
     },
     "colors": {
-      "accentcolor": ACCENT_COLOR_1,
-      "textcolor": TEXT_COLOR_1,
+      "frame": ACCENT_COLOR_1,
+      "tab_background_text": TEXT_COLOR_1,
     },
   });
 
   await extension.awaitMessage("theme-updated");
 
   validateTheme("image1.png", ACCENT_COLOR_1, TEXT_COLOR_1, true);
 
+  // Check with the LWT aliases (to update on Firefox 69, because the
+  // LWT aliases are going to be removed).
   extension.sendMessage("update-theme", {
     "images": {
-      "headerURL": "image2.png",
+      "theme_frame": "image2.png",
     },
     "colors": {
-      "accentcolor": ACCENT_COLOR_2,
-      "textcolor": TEXT_COLOR_2,
+      "frame": ACCENT_COLOR_2,
+      "tab_background_text": TEXT_COLOR_2,
     },
   });
 
   await extension.awaitMessage("theme-updated");
 
   validateTheme("image2.png", ACCENT_COLOR_2, TEXT_COLOR_2, true);
 
   extension.sendMessage("reset-theme");
@@ -124,35 +126,35 @@ add_task(async function test_dynamic_the
     },
   });
 
   let defaultStyle = window.getComputedStyle(window.document.documentElement);
   await extension.startup();
 
   extension.sendMessage("update-theme", {
     "images": {
-      "headerURL": BACKGROUND_1,
+      "theme_frame": BACKGROUND_1,
     },
     "colors": {
-      "accentcolor": ACCENT_COLOR_1,
-      "textcolor": TEXT_COLOR_1,
+      "frame": ACCENT_COLOR_1,
+      "tab_background_text": TEXT_COLOR_1,
     },
   });
 
   await extension.awaitMessage("theme-updated");
 
   validateTheme(BACKGROUND_1, ACCENT_COLOR_1, TEXT_COLOR_1, true);
 
   extension.sendMessage("update-theme", {
     "images": {
-      "headerURL": BACKGROUND_2,
+      "theme_frame": BACKGROUND_2,
     },
     "colors": {
-      "accentcolor": ACCENT_COLOR_2,
-      "textcolor": TEXT_COLOR_2,
+      "frame": ACCENT_COLOR_2,
+      "tab_background_text": TEXT_COLOR_2,
     },
   });
 
   await extension.awaitMessage("theme-updated");
 
   validateTheme(BACKGROUND_2, ACCENT_COLOR_2, TEXT_COLOR_2, true);
 
   extension.sendMessage("reset-theme");
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_findbar.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_findbar.js
@@ -5,23 +5,23 @@
 
 add_task(async function test_support_toolbar_properties_on_findbar() {
   const TOOLBAR_COLOR = "#ff00ff";
   const TOOLBAR_TEXT_COLOR = "#9400ff";
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
           "toolbar": TOOLBAR_COLOR,
-          "toolbar_text": TOOLBAR_TEXT_COLOR,
+          "bookmark_text": TOOLBAR_TEXT_COLOR,
         },
       },
     },
     files: {
       "image1.png": BACKGROUND,
     },
   });
 
@@ -49,21 +49,21 @@ add_task(async function test_support_too
 add_task(async function test_support_toolbar_field_properties_on_findbar() {
   const TOOLBAR_FIELD_COLOR = "#ff00ff";
   const TOOLBAR_FIELD_TEXT_COLOR = "#9400ff";
   const TOOLBAR_FIELD_BORDER_COLOR = "#ffffff";
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
           "toolbar_field": TOOLBAR_FIELD_COLOR,
           "toolbar_field_text": TOOLBAR_FIELD_TEXT_COLOR,
           "toolbar_field_border": TOOLBAR_FIELD_BORDER_COLOR,
         },
       },
     },
     files: {
       "image1.png": BACKGROUND,
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_getCurrent_differentExt.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_getCurrent_differentExt.js
@@ -3,21 +3,21 @@
 // This test checks whether browser.theme.getCurrent() works correctly when theme
 // does not originate from extension querying the theme.
 
 add_task(async function test_getcurrent() {
   const theme = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
         },
       },
     },
     files: {
       "image1.png": BACKGROUND,
     },
   });
 
@@ -32,22 +32,22 @@ add_task(async function test_getcurrent(
   });
 
   await extension.startup();
 
   info("Testing getCurrent after static theme startup");
   let updatedPromise = extension.awaitMessage("theme-updated");
   await theme.startup();
   let receivedTheme = await updatedPromise;
-  Assert.ok(receivedTheme.images.headerURL.includes("image1.png"),
-            "getCurrent returns correct headerURL");
-  Assert.equal(receivedTheme.colors.accentcolor, ACCENT_COLOR,
-               "getCurrent returns correct accentcolor");
-  Assert.equal(receivedTheme.colors.textcolor, TEXT_COLOR,
-               "getCurrent returns correct textcolor");
+  Assert.ok(receivedTheme.images.theme_frame.includes("image1.png"),
+            "getCurrent returns correct theme_frame image");
+  Assert.equal(receivedTheme.colors.frame, ACCENT_COLOR,
+               "getCurrent returns correct frame color");
+  Assert.equal(receivedTheme.colors.tab_background_text, TEXT_COLOR,
+               "getCurrent returns correct tab_background_text color");
 
   info("Testing getCurrent after static theme unload");
   updatedPromise = extension.awaitMessage("theme-updated");
   await theme.unload();
   receivedTheme = await updatedPromise;
   Assert.equal(Object.keys(receivedTheme), 0,
                "getCurrent returns empty theme");
 
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_multiple_backgrounds.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_multiple_backgrounds.js
@@ -1,21 +1,21 @@
 "use strict";
 
 add_task(async function test_support_backgrounds_position() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "face1.png",
+          "theme_frame": "face1.png",
           "additional_backgrounds": ["face2.png", "face2.png", "face2.png"],
         },
         "colors": {
-          "accentcolor": `rgb(${FRAME_COLOR.join(",")})`,
-          "textcolor": `rgb(${TAB_BACKGROUND_TEXT_COLOR.join(",")})`,
+          "frame": `rgb(${FRAME_COLOR.join(",")})`,
+          "tab_background_text": `rgb(${TAB_BACKGROUND_TEXT_COLOR.join(",")})`,
         },
         "properties": {
           "additional_backgrounds_alignment": ["left top", "center top", "right bottom"],
         },
       },
     },
     files: {
       "face1.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA),
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors.js
@@ -82,26 +82,26 @@ add_task(async function test_support_ntp
     SpecialPowers.clearUserPref("browser.newtab.preload");
   });
   gBrowser.removePreloadedBrowser();
   for (let url of ["about:newtab", "about:home", "about:welcome"]) {
     info("Opening url: " + url);
     await BrowserTestUtils.withNewTab({gBrowser, url}, async browser => {
       await test_ntp_theme({
         colors: {
-          accentcolor: ACCENT_COLOR,
-          textcolor: TEXT_COLOR,
+          frame: ACCENT_COLOR,
+          tab_background_text: TEXT_COLOR,
           ntp_background: "#add8e6",
           ntp_text: "#00008b",
         },
       }, false, url);
 
       await test_ntp_theme({
         colors: {
-          accentcolor: ACCENT_COLOR,
-          textcolor: TEXT_COLOR,
+          frame: ACCENT_COLOR,
+          tab_background_text: TEXT_COLOR,
           ntp_background: "#00008b",
           ntp_text: "#add8e6",
         },
       }, true, url);
     });
   }
 });
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js
@@ -98,27 +98,27 @@ add_task(async function test_per_window_
       async function checkWindow(theme, isBrightText, winId) {
         let windowChecked = promiseWindowChecked();
         browser.test.sendMessage("check-window", {theme, isBrightText, winId});
         await windowChecked;
       }
 
       const darkTextTheme = {
         colors: {
-          accentcolor: "#add8e6",
-          textcolor: "#000",
+          frame: "#add8e6",
+          tab_background_text: "#000",
           ntp_background: "#add8e6",
           ntp_text: "#000",
         },
       };
 
       const brightTextTheme = {
         colors: {
-          accentcolor: "#00008b",
-          textcolor: "#add8e6",
+          frame: "#00008b",
+          tab_background_text: "#add8e6",
           ntp_background: "#00008b",
           ntp_text: "#add8e6",
         },
       };
 
       let {id: winId} = await browser.windows.getCurrent();
       let {id: secondWinId} = await createWindow();
 
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_persistence.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_persistence.js
@@ -3,21 +3,21 @@
 // This test checks whether applied WebExtension themes are persisted and applied
 // on newly opened windows.
 
 add_task(async function test_multiple_windows() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
         },
       },
     },
     files: {
       "image1.png": BACKGROUND,
     },
   });
 
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_sanitization.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_sanitization.js
@@ -3,19 +3,19 @@
 // This test checks color sanitization in various situations
 
 add_task(async function test_sanitization_invalid() {
   // This test checks that invalid values are sanitized
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
-          "toolbar_text": "ntimsfavoriteblue",
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
+          "bookmark_text": "ntimsfavoriteblue",
         },
       },
     },
   });
 
   await extension.startup();
 
   let navbar = document.querySelector("#nav-bar");
@@ -29,19 +29,19 @@ add_task(async function test_sanitizatio
 });
 
 add_task(async function test_sanitization_css_variables() {
   // This test checks that CSS variables are sanitized
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
-          "toolbar_text": "var(--arrowpanel-dimmed)",
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
+          "bookmark_text": "var(--arrowpanel-dimmed)",
         },
       },
     },
   });
 
   await extension.startup();
 
   let navbar = document.querySelector("#nav-bar");
@@ -55,18 +55,18 @@ add_task(async function test_sanitizatio
 });
 
 add_task(async function test_sanitization_transparent() {
   // This test checks whether transparent values are applied properly
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
           "toolbar_top_separator": "transparent",
         },
       },
     },
   });
 
   await extension.startup();
 
@@ -74,24 +74,24 @@ add_task(async function test_sanitizatio
   Assert.ok(
     window.getComputedStyle(navbar).boxShadow.includes("rgba(0, 0, 0, 0)"),
     "Top separator should be transparent"
   );
 
   await extension.unload();
 });
 
-add_task(async function test_sanitization_transparent_accentcolor() {
-  // This test checks whether transparent accentcolor falls back to white.
+add_task(async function test_sanitization_transparent_frame_color() {
+  // This test checks whether transparent frame color falls back to white.
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "colors": {
-          "accentcolor": "transparent",
-          "textcolor": TEXT_COLOR,
+          "frame": "transparent",
+          "tab_background_text": TEXT_COLOR,
         },
       },
     },
   });
 
   await extension.startup();
 
   let docEl = document.documentElement;
@@ -99,24 +99,24 @@ add_task(async function test_sanitizatio
     window.getComputedStyle(docEl).backgroundColor,
     "rgb(255, 255, 255)",
     "Accent color should be white",
   );
 
   await extension.unload();
 });
 
-add_task(async function test_sanitization_transparent_textcolor() {
+add_task(async function test_sanitization_transparent_tab_background_text_color() {
   // This test checks whether transparent textcolor falls back to black.
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": "transparent",
+          "frame": ACCENT_COLOR,
+          "tab_background_text": "transparent",
         },
       },
     },
   });
 
   await extension.startup();
 
   let docEl = document.documentElement;
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_separators.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_separators.js
@@ -8,21 +8,21 @@ add_task(async function test_support_sep
   const SEPARATOR_VERTICAL_COLOR = "#f0000f";
   const SEPARATOR_FIELD_COLOR = "#9400ff";
   const SEPARATOR_BOTTOM_COLOR = "#3366cc";
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
           "toolbar_top_separator": SEPARATOR_TOP_COLOR,
           "toolbar_vertical_separator": SEPARATOR_VERTICAL_COLOR,
           "toolbar_field_separator": SEPARATOR_FIELD_COLOR,
           "toolbar_bottom_separator": SEPARATOR_BOTTOM_COLOR,
         },
       },
     },
     files: {
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_static_onUpdated.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_static_onUpdated.js
@@ -3,21 +3,21 @@
 // This test checks whether browser.theme.onUpdated works
 // when a static theme is applied
 
 add_task(async function test_on_updated() {
   const theme = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
         },
       },
     },
     files: {
       "image1.png": BACKGROUND,
     },
   });
 
@@ -31,22 +31,22 @@ add_task(async function test_on_updated(
 
   await extension.startup();
 
   info("Testing update event on static theme startup");
   let updatedPromise = extension.awaitMessage("theme-updated");
   await theme.startup();
   const {theme: receivedTheme, windowId} = await updatedPromise;
   Assert.ok(!windowId, "No window id in static theme update event");
-  Assert.ok(receivedTheme.images.headerURL.includes("image1.png"),
-            "Theme header URL should be applied");
-  Assert.equal(receivedTheme.colors.accentcolor, ACCENT_COLOR,
-               "Theme accent color should be applied");
-  Assert.equal(receivedTheme.colors.textcolor, TEXT_COLOR,
-               "Theme text color should be applied");
+  Assert.ok(receivedTheme.images.theme_frame.includes("image1.png"),
+            "Theme theme_frame image should be applied");
+  Assert.equal(receivedTheme.colors.frame, ACCENT_COLOR,
+               "Theme frame color should be applied");
+  Assert.equal(receivedTheme.colors.tab_background_text, TEXT_COLOR,
+               "Theme tab_background_text color should be applied");
 
   info("Testing update event on static theme unload");
   updatedPromise = extension.awaitMessage("theme-updated");
   await theme.unload();
   const updateInfo = await updatedPromise;
   Assert.ok(!windowId, "No window id in static theme update event on unload");
   Assert.equal(Object.keys(updateInfo.theme), 0,
                "unloading theme sends empty theme in update event");
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_line.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_line.js
@@ -4,18 +4,18 @@
 // the color of the tab line are applied properly.
 
 add_task(async function test_support_tab_line() {
   const TAB_LINE_COLOR = "#9400ff";
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
           "tab_line": TAB_LINE_COLOR,
         },
       },
     },
   });
 
   await extension.startup();
 
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_loading.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_loading.js
@@ -3,22 +3,22 @@
 add_task(async function test_support_tab_loading_filling() {
   const TAB_LOADING_COLOR = "#FF0000";
   const TAB_TEXT_COLOR = "#9400ff";
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
         "colors": {
-          "accentcolor": "#000",
+          "frame": "#000",
           "toolbar": "#124455",
-          "textcolor": TAB_TEXT_COLOR,
+          "tab_background_text": TAB_TEXT_COLOR,
           "tab_loading":  TAB_LOADING_COLOR,
         },
       },
     },
     files: {
       "image1.png": BACKGROUND,
     },
   });
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_selected.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_selected.js
@@ -4,18 +4,18 @@
 // the background color of selected tab are applied correctly.
 
 add_task(async function test_tab_background_color_property() {
   const TAB_BACKGROUND_COLOR = "#9400ff";
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
           "tab_selected": TAB_BACKGROUND_COLOR,
         },
       },
     },
   });
 
   await extension.startup();
 
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_separators.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_separators.js
@@ -2,18 +2,18 @@
 
 add_task(async function test_support_tab_separators() {
   const TAB_SEPARATOR_COLOR = "#FF0000";
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "colors": {
-          "accentcolor": "#000",
-          "textcolor": "#9400ff",
+          "frame": "#000",
+          "tab_background_text": "#9400ff",
           "tab_background_separator":  TAB_SEPARATOR_COLOR,
         },
       },
     },
   });
   await extension.startup();
 
   info("Checking background tab separator color");
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_text.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_text.js
@@ -4,21 +4,21 @@
 // the text color of the selected tab are applied properly.
 
 add_task(async function test_support_tab_text_property_css_color() {
   const TAB_TEXT_COLOR = "#9400ff";
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
           "tab_text": TAB_TEXT_COLOR,
         },
       },
     },
     files: {
       "image1.png": BACKGROUND,
     },
   });
@@ -35,21 +35,21 @@ add_task(async function test_support_tab
 });
 
 add_task(async function test_support_tab_text_chrome_array() {
   const TAB_TEXT_COLOR = [148, 0, 255];
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
         "colors": {
-          "accentcolor": FRAME_COLOR,
-          "textcolor": TAB_BACKGROUND_TEXT_COLOR,
+          "frame": FRAME_COLOR,
+          "tab_background_text": TAB_BACKGROUND_TEXT_COLOR,
           "tab_text": TAB_TEXT_COLOR,
         },
       },
     },
     files: {
       "image1.png": BACKGROUND,
     },
   });
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_theme_transition.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_theme_transition.js
@@ -7,19 +7,19 @@ add_task(async function test_theme_trans
   const TOOLBAR = "#f27489";
   const TEXT_COLOR = "#000000";
   const TRANSITION_PROPERTY = "background-color";
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "colors": {
-          "textcolor": TEXT_COLOR,
+          "tab_background_text": TEXT_COLOR,
           "toolbar": TOOLBAR,
-          "toolbar_text": TEXT_COLOR,
+          "bookmark_text": TEXT_COLOR,
         },
       },
     },
   });
 
   await extension.startup();
 
   // check transition effect for toolbars
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields.js
@@ -18,21 +18,21 @@ add_task(async function test_support_too
 
   const TOOLBAR_FIELD_BACKGROUND = "#ff00ff";
   const TOOLBAR_FIELD_COLOR = "#00ff00";
   const TOOLBAR_FIELD_BORDER = "#aaaaff";
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
           "toolbar_field": TOOLBAR_FIELD_BACKGROUND,
           "toolbar_field_text": TOOLBAR_FIELD_COLOR,
           "toolbar_field_border": TOOLBAR_FIELD_BORDER,
         },
       },
     },
     files: {
       "image1.png": BACKGROUND,
@@ -83,18 +83,18 @@ add_task(async function test_support_too
   });
   let toolbox = document.querySelector("#navigator-toolbox");
   let urlbar = toolbox.querySelector("#urlbar");
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
           "toolbar_field": "#fff",
           "toolbar_field_text": "#000",
         },
       },
     },
   });
 
   await extension.startup();
@@ -105,18 +105,18 @@ add_task(async function test_support_too
             "Brighttext attribute should not be set");
 
   await extension.unload();
 
   extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
           "toolbar_field": "#000",
           "toolbar_field_text": "#fff",
         },
       },
     },
   });
 
   await extension.startup();
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields_focus.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields_focus.js
@@ -6,18 +6,18 @@ add_task(async function test_toolbar_fie
   const TOOLBAR_FOCUS_BACKGROUND = "#FF0000";
   const TOOLBAR_FOCUS_TEXT = "#9400FF";
   const TOOLBAR_FOCUS_BORDER = "#FFFFFF";
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "colors": {
-          "accentcolor": "#FF0000",
-          "textcolor": "#ffffff",
+          "frame": "#FF0000",
+          "tab_background_color": "#ffffff",
           "toolbar_field": TOOLBAR_FIELD_BACKGROUND,
           "toolbar_field_text": TOOLBAR_FIELD_COLOR,
           "toolbar_field_focus": TOOLBAR_FOCUS_BACKGROUND,
           "toolbar_field_text_focus": TOOLBAR_FOCUS_TEXT,
           "toolbar_field_border_focus": TOOLBAR_FOCUS_BORDER,
         },
       },
     },
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_colors.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_colors.js
@@ -8,21 +8,21 @@
 add_task(async function test_button_background_properties() {
   const BUTTON_BACKGROUND_ACTIVE = "#FFFFFF";
   const BUTTON_BACKGROUND_HOVER = "#59CBE8";
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
           "button_background_active": BUTTON_BACKGROUND_ACTIVE,
           "button_background_hover": BUTTON_BACKGROUND_HOVER,
         },
       },
     },
     files: {
       "image1.png": BACKGROUND,
     },
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_icons.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_icons.js
@@ -6,21 +6,21 @@
 add_task(async function test_icons_properties() {
   const ICONS_COLOR = "#001b47";
   const ICONS_ATTENTION_COLOR = "#44ba77";
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
           "icons": ICONS_COLOR,
           "icons_attention": ICONS_ATTENTION_COLOR,
         },
       },
     },
     files: {
       "image1.png": BACKGROUND,
     },
@@ -55,21 +55,21 @@ add_task(async function test_icons_prope
   await extension.unload();
 });
 
 add_task(async function test_no_icons_properties() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": "image1.png",
+          "theme_frame": "image1.png",
         },
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
         },
       },
     },
     files: {
       "image1.png": BACKGROUND,
     },
   });
 
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_toolbars.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbars.js
@@ -5,19 +5,21 @@
 
 add_task(async function test_support_toolbar_property() {
   const TOOLBAR_COLOR = "#ff00ff";
   const TOOLBAR_TEXT_COLOR = "#9400ff";
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "colors": {
-          "accentcolor": ACCENT_COLOR,
-          "textcolor": TEXT_COLOR,
+          "frame": ACCENT_COLOR,
+          "tab_background_text": TEXT_COLOR,
           "toolbar": TOOLBAR_COLOR,
+          // NOTE: this property is going to be removed on Firefox 69
+          // (and bookmark_text is going to replace it).
           "toolbar_text": TOOLBAR_TEXT_COLOR,
         },
       },
     },
   });
 
   let toolbox = document.querySelector("#navigator-toolbox");
   let toolbars = [...toolbox.querySelectorAll("toolbar:not(#TabsToolbar)")].filter(toolbar => {
--- a/toolkit/components/extensions/test/xpcshell/test_ext_proxy_config.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_proxy_config.js
@@ -172,22 +172,22 @@ add_task(async function test_browser_set
     {
       proxyType: "manual",
       http: "http://www.mozilla.org",
       autoConfigUrl: "",
     },
     {
       "network.proxy.type": proxySvc.PROXYCONFIG_MANUAL,
       "network.proxy.http": "www.mozilla.org",
-      "network.proxy.http_port": 0,
+      "network.proxy.http_port": 80,
       "network.proxy.autoconfig_url": "",
     },
     {
       proxyType: "manual",
-      http: "www.mozilla.org",
+      http: "www.mozilla.org:80",
       autoConfigUrl: "",
     }
   );
 
   // When using proxyAll, we expect all proxies to be set to
   // be the same as http.
   await testProxy(
     {
@@ -240,16 +240,89 @@ add_task(async function test_browser_set
       "network.proxy.ssl_port": 8082,
       "network.proxy.socks": "mozilla.org",
       "network.proxy.socks_port": 8083,
       "network.proxy.socks_version": 4,
       "network.proxy.no_proxies_on": ".mozilla.org",
     }
   );
 
+  await testProxy(
+    {
+      proxyType: "manual",
+      http: "http://www.mozilla.org",
+      ftp: "ftp://www.mozilla.org",
+      ssl: "https://www.mozilla.org",
+      socks: "mozilla.org",
+      socksVersion: 4,
+      passthrough: ".mozilla.org",
+    },
+    {
+      "network.proxy.type": proxySvc.PROXYCONFIG_MANUAL,
+      "network.proxy.http": "www.mozilla.org",
+      "network.proxy.http_port": 80,
+      "network.proxy.share_proxy_settings": false,
+      "network.proxy.ftp": "www.mozilla.org",
+      "network.proxy.ftp_port": 21,
+      "network.proxy.ssl": "www.mozilla.org",
+      "network.proxy.ssl_port": 443,
+      "network.proxy.socks": "mozilla.org",
+      "network.proxy.socks_port": 1080,
+      "network.proxy.socks_version": 4,
+      "network.proxy.no_proxies_on": ".mozilla.org",
+    },
+    {
+      proxyType: "manual",
+      http: "www.mozilla.org:80",
+      httpProxyAll: false,
+      ftp: "www.mozilla.org:21",
+      ssl: "www.mozilla.org:443",
+      socks: "mozilla.org:1080",
+      socksVersion: 4,
+      passthrough: ".mozilla.org",
+    }
+  );
+
+
+  await testProxy(
+    {
+      proxyType: "manual",
+      http: "http://www.mozilla.org:80",
+      ftp: "ftp://www.mozilla.org:21",
+      ssl: "https://www.mozilla.org:443",
+      socks: "mozilla.org:1080",
+      socksVersion: 4,
+      passthrough: ".mozilla.org",
+    },
+    {
+      "network.proxy.type": proxySvc.PROXYCONFIG_MANUAL,
+      "network.proxy.http": "www.mozilla.org",
+      "network.proxy.http_port": 80,
+      "network.proxy.share_proxy_settings": false,
+      "network.proxy.ftp": "www.mozilla.org",
+      "network.proxy.ftp_port": 21,
+      "network.proxy.ssl": "www.mozilla.org",
+      "network.proxy.ssl_port": 443,
+      "network.proxy.socks": "mozilla.org",
+      "network.proxy.socks_port": 1080,
+      "network.proxy.socks_version": 4,
+      "network.proxy.no_proxies_on": ".mozilla.org",
+    },
+    {
+      proxyType: "manual",
+      http: "www.mozilla.org:80",
+      httpProxyAll: false,
+      ftp: "www.mozilla.org:21",
+      ssl: "www.mozilla.org:443",
+      socks: "mozilla.org:1080",
+      socksVersion: 4,
+      passthrough: ".mozilla.org",
+    }
+  );
+
   // Test resetting values.
   await testProxy(
     {
       proxyType: "none",
       http: "",
       ftp: "",
       ssl: "",
       socks: "",
--- a/toolkit/components/telemetry/core/TelemetryHistogram.cpp
+++ b/toolkit/components/telemetry/core/TelemetryHistogram.cpp
@@ -2395,36 +2395,38 @@ void TelemetryHistogram::InitializeGloba
 
   if (XRE_IsParentProcess()) {
     gHistogramStorage =
       new Histogram*[HistogramCount * size_t(ProcessID::Count)] {};
     gKeyedHistogramStorage =
       new KeyedHistogram*[HistogramCount * size_t(ProcessID::Count)] {};
   }
 
-    // Some Telemetry histograms depend on the value of C++ constants and hardcode
-    // their values in Histograms.json.
-    // We add static asserts here for those values to match so that future changes
-    // don't go unnoticed.
-    static_assert((JS::gcreason::NUM_TELEMETRY_REASONS + 1) ==
-                        gHistogramInfos[mozilla::Telemetry::GC_MINOR_REASON].bucketCount &&
-                  (JS::gcreason::NUM_TELEMETRY_REASONS + 1) ==
-                        gHistogramInfos[mozilla::Telemetry::GC_MINOR_REASON_LONG].bucketCount &&
-                  (JS::gcreason::NUM_TELEMETRY_REASONS + 1) ==
-                        gHistogramInfos[mozilla::Telemetry::GC_REASON_2].bucketCount,
-                  "NUM_TELEMETRY_REASONS is assumed to be a fixed value in Histograms.json."
-                  " If this was an intentional change, update the n_values for the "
-                  "following in Histograms.json: GC_MINOR_REASON, GC_MINOR_REASON_LONG, "
-                  "GC_REASON_2");
-
-    static_assert((mozilla::StartupTimeline::MAX_EVENT_ID + 1) ==
-                        gHistogramInfos[mozilla::Telemetry::STARTUP_MEASUREMENT_ERRORS].bucketCount,
-                  "MAX_EVENT_ID is assumed to be a fixed value in Histograms.json.  If this"
-                  " was an intentional change, update the n_values for the following in "
-                  "Histograms.json: STARTUP_MEASUREMENT_ERRORS");
+  // Some Telemetry histograms depend on the value of C++ constants and hardcode
+  // their values in Histograms.json.
+  // We add static asserts here for those values to match so that future changes
+  // don't go unnoticed.
+  // clang-format off
+  static_assert((JS::gcreason::NUM_TELEMETRY_REASONS + 1) ==
+      gHistogramInfos[mozilla::Telemetry::GC_MINOR_REASON].bucketCount &&
+      (JS::gcreason::NUM_TELEMETRY_REASONS + 1) ==
+      gHistogramInfos[mozilla::Telemetry::GC_MINOR_REASON_LONG].bucketCount &&
+      (JS::gcreason::NUM_TELEMETRY_REASONS + 1) ==
+      gHistogramInfos[mozilla::Telemetry::GC_REASON_2].bucketCount,
+      "NUM_TELEMETRY_REASONS is assumed to be a fixed value in Histograms.json."
+      " If this was an intentional change, update the n_values for the "
+      "following in Histograms.json: GC_MINOR_REASON, GC_MINOR_REASON_LONG, "
+      "GC_REASON_2");
+
+  static_assert((mozilla::StartupTimeline::MAX_EVENT_ID + 1) ==
+      gHistogramInfos[mozilla::Telemetry::STARTUP_MEASUREMENT_ERRORS].bucketCount,
+      "MAX_EVENT_ID is assumed to be a fixed value in Histograms.json.  If this"
+      " was an intentional change, update the n_values for the following in "
+      "Histograms.json: STARTUP_MEASUREMENT_ERRORS");
+  // clang-format on
 
   gInitDone = true;
 }
 
 void TelemetryHistogram::DeInitializeGlobalState()
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   gCanRecordBase = false;
--- a/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/testcase.py
+++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/testcase.py
@@ -1,25 +1,32 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import os
+import re
 import simplejson as json
 import time
 import zlib
 
 from firefox_puppeteer import PuppeteerMixin
 from marionette_driver.addons import Addons
 from marionette_driver.errors import MarionetteException
 from marionette_driver.wait import Wait
 from marionette_harness import MarionetteTestCase
 from marionette_harness.runner import httpd
 
 
+CANARY_CLIENT_ID = "c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0"
+UUID_PATTERN = re.compile(
+    r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
+)
+
+
 class TelemetryTestCase(PuppeteerMixin, MarionetteTestCase):
 
     def __init__(self, *args, **kwargs):
         super(TelemetryTestCase, self).__init__(*args, **kwargs)
         self.pings = []
 
     def setUp(self, *args, **kwargs):
         super(TelemetryTestCase, self).setUp(*args, **kwargs)
@@ -76,16 +83,31 @@ class TelemetryTestCase(PuppeteerMixin, 
         }
 
         # Firefox will be forced to restart with the prefs enforced.
         self.marionette.enforce_gecko_prefs(telemetry_prefs)
 
         # Wait 5 seconds to ensure that telemetry has reinitialized
         time.sleep(5)
 
+    def assertIsValidUUID(self, value):
+        """Check if the given UUID is valid."""
+        self.assertIsNotNone(value)
+        self.assertNotEqual(value, "")
+
+        # Check for client ID that is used when Telemetry upload is disabled
+        self.assertNotEqual(
+            value, CANARY_CLIENT_ID, msg="UUID is CANARY CLIENT ID"
+        )
+
+        self.assertIsNotNone(
+            re.match(UUID_PATTERN, value),
+            msg="UUID does not match regular expression",
+        )
+
     def wait_for_pings(self, action_func, ping_filter, count):
         """Call the given action and wait for pings to come in and return
         the `count` number of pings, that match the given filter.
         """
         # Keep track of the current number of pings
         current_num_pings = len(self.pings)
 
         # New list to store new pings that satisfy the filter
--- a/toolkit/components/telemetry/tests/marionette/tests/client/test_search_counts_across_sessions.py
+++ b/toolkit/components/telemetry/tests/marionette/tests/client/test_search_counts_across_sessions.py
@@ -5,18 +5,16 @@
 import textwrap
 
 from telemetry_harness.testcase import TelemetryTestCase
 from telemetry_harness.ping_filters import (
     MAIN_ENVIRONMENT_CHANGE_PING,
     MAIN_SHUTDOWN_PING,
 )
 
-CANARY_CLIENT_ID = "c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0"
-
 
 class TestSearchCounts(TelemetryTestCase):
     """Test for SEARCH_COUNTS across sessions."""
 
     def get_current_search_engine(self):
         """Retrieve the identifier of the current search engine."""
 
         script = """\
@@ -81,21 +79,17 @@ class TestSearchCounts(TelemetryTestCase
         #     - previousSubsessionId should not be set
         #     - subSessionCounter should be 1
         #     - profileSubSessionCounter should be 1
         #     - reason should be "shutdown"
         # - Other ping contents:
         #     - SEARCH_COUNTS values should match performed search action
 
         client_id = ping1["clientId"]
-        self.assertIsNotNone(client_id)
-        self.assertNotEqual(client_id, "")
-
-        # Check for client ID that used when Telemetry upload is disabled
-        self.assertNotEqual(client_id, CANARY_CLIENT_ID)
+        self.assertIsValidUUID(client_id)
 
         ping1_info = ping1["payload"]["info"]
         self.assertEqual(ping1_info["reason"], "shutdown")
 
         s1_session_id = ping1_info["sessionId"]
         self.assertNotEqual(s1_session_id, "")
 
         s1_s1_subsession_id = ping1_info["subsessionId"]