Merge autoland to mozilla-central. a=merge
authorBrindusan Cristian <cbrindusan@mozilla.com>
Fri, 04 Oct 2019 19:06:58 +0300
changeset 496422 8d68e16d3d775740b85377b775eecb2173c6a03c
parent 496421 81ebffadd73ab463785bacfbcd38644fa576bacd (current diff)
parent 496319 d7ab956c296257c99630aa0d4e91d41434c55137 (diff)
child 496423 e966603209bba385cac292c937cd74d9426a3412
push id97227
push userncsoregi@mozilla.com
push dateFri, 04 Oct 2019 21:43:23 +0000
treeherderautoland@02bc638506ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone71.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 autoland to mozilla-central. a=merge
dom/base/test/chrome/frame_bug814638.xul
dom/base/test/chrome/host_bug814638.xul
dom/base/test/chrome/test_bug814638.xul
dom/media/test/test_mediarecorder_getencodeddata.html
dom/media/test/test_mediarecorder_unsupported_src.html
testing/web-platform/meta/css/CSS2/generated-content/content-counter-004.xht.ini
testing/web-platform/meta/mediacapture-record/MediaRecorder-creation.https.html.ini
testing/web-platform/meta/mediacapture-record/MediaRecorder-destroy-script-execution.html.ini
testing/web-platform/meta/mediacapture-record/MediaRecorder-error.html.ini
testing/web-platform/meta/mediacapture-record/MediaRecorder-events-and-exceptions.html.ini
testing/web-platform/meta/mediacapture-record/MediaRecorder-pause-resume.html.ini
testing/web-platform/meta/mediacapture-record/MediaRecorder-stop.html.ini
--- a/dom/media/MediaRecorder.cpp
+++ b/dom/media/MediaRecorder.cpp
@@ -38,16 +38,24 @@
 #include "nsIScriptError.h"
 #include "nsMimeTypes.h"
 #include "nsProxyRelease.h"
 #include "nsTArray.h"
 
 mozilla::LazyLogModule gMediaRecorderLog("MediaRecorder");
 #define LOG(type, msg) MOZ_LOG(gMediaRecorderLog, type, msg)
 
+#define MIN_VIDEO_BITRATE_BPS 10e3        // 10kbps
+#define DEFAULT_VIDEO_BITRATE_BPS 2500e3  // 2.5Mbps
+#define MAX_VIDEO_BITRATE_BPS 100e6       // 100Mbps
+
+#define MIN_AUDIO_BITRATE_BPS 500        // 500bps
+#define DEFAULT_AUDIO_BITRATE_BPS 128e3  // 128kbps
+#define MAX_AUDIO_BITRATE_BPS 512e3      // 512kbps
+
 namespace mozilla {
 
 namespace dom {
 
 using namespace mozilla::media;
 
 /* static */ StaticRefPtr<nsIAsyncShutdownBlocker>
     gMediaRecorderShutdownBlocker;
@@ -136,40 +144,399 @@ class MediaRecorderReporter final : publ
   nsTArray<RefPtr<MediaRecorder>> mRecorders;
 };
 NS_IMPL_ISUPPORTS(MediaRecorderReporter, nsIMemoryReporter);
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaRecorder)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaRecorder,
                                                   DOMEventTargetHelper)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStream)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStream)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioNode)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityDomException)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnknownDomException)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaRecorder,
                                                 DOMEventTargetHelper)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStream)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mStream)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioNode)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityDomException)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mUnknownDomException)
   tmp->UnRegisterActivityObserver();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaRecorder)
   NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(MediaRecorder, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(MediaRecorder, DOMEventTargetHelper)
 
+namespace {
+bool PrincipalSubsumes(MediaRecorder* aRecorder, nsIPrincipal* aPrincipal) {
+  if (!aRecorder->GetOwner()) {
+    return false;
+  }
+  nsCOMPtr<Document> doc = aRecorder->GetOwner()->GetExtantDoc();
+  if (!doc) {
+    return false;
+  }
+  if (!aPrincipal) {
+    return false;
+  }
+  bool subsumes;
+  if (NS_FAILED(doc->NodePrincipal()->Subsumes(aPrincipal, &subsumes))) {
+    return false;
+  }
+  return subsumes;
+}
+
+bool MediaStreamTracksPrincipalSubsumes(
+    MediaRecorder* aRecorder,
+    const nsTArray<RefPtr<MediaStreamTrack>>& aTracks) {
+  nsCOMPtr<nsIPrincipal> principal = nullptr;
+  for (const auto& track : aTracks) {
+    nsContentUtils::CombineResourcePrincipals(&principal,
+                                              track->GetPrincipal());
+  }
+  return PrincipalSubsumes(aRecorder, principal);
+}
+
+bool AudioNodePrincipalSubsumes(MediaRecorder* aRecorder,
+                                AudioNode* aAudioNode) {
+  MOZ_ASSERT(aAudioNode);
+  Document* doc =
+      aAudioNode->GetOwner() ? aAudioNode->GetOwner()->GetExtantDoc() : nullptr;
+  nsCOMPtr<nsIPrincipal> principal = doc ? doc->NodePrincipal() : nullptr;
+  return PrincipalSubsumes(aRecorder, principal);
+}
+
+enum class TypeSupport {
+  Supported,
+  MediaTypeInvalid,
+  NoVideoWithAudioType,
+  ContainersDisabled,
+  CodecsDisabled,
+  ContainerUnsupported,
+  CodecUnsupported,
+  CodecDuplicated,
+};
+
+nsCString TypeSupportToCString(TypeSupport aSupport,
+                               const nsAString& aMimeType) {
+  nsAutoCString mime = NS_ConvertUTF16toUTF8(aMimeType);
+  switch (aSupport) {
+    case TypeSupport::Supported:
+      return nsPrintfCString("%s is supported", mime.get());
+    case TypeSupport::MediaTypeInvalid:
+      return nsPrintfCString("%s is not a valid media type", mime.get());
+    case TypeSupport::NoVideoWithAudioType:
+      return nsPrintfCString(
+          "Video cannot be recorded with %s as it is an audio type",
+          mime.get());
+    case TypeSupport::ContainersDisabled:
+      return NS_LITERAL_CSTRING("All containers are disabled");
+    case TypeSupport::CodecsDisabled:
+      return NS_LITERAL_CSTRING("All codecs are disabled");
+    case TypeSupport::ContainerUnsupported:
+      return nsPrintfCString("%s indicates an unsupported container",
+                             mime.get());
+    case TypeSupport::CodecUnsupported:
+      return nsPrintfCString("%s indicates an unsupported codec", mime.get());
+    case TypeSupport::CodecDuplicated:
+      return nsPrintfCString("%s contains the same codec multiple times",
+                             mime.get());
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unknown TypeSupport");
+      return NS_LITERAL_CSTRING("Unknown error");
+  }
+}
+
+TypeSupport CanRecordAudioTrackWith(const Maybe<MediaContainerType>& aMimeType,
+                                    const nsAString& aMimeTypeString) {
+  if (aMimeTypeString.IsEmpty()) {
+    // For the empty string we just need to check whether we have support for an
+    // audio container and an audio codec.
+    if (!MediaEncoder::IsWebMEncoderEnabled() &&
+        !MediaDecoder::IsOggEnabled()) {
+      // No container support for audio.
+      return TypeSupport::ContainersDisabled;
+    }
+
+    if (!MediaDecoder::IsOpusEnabled()) {
+      // No codec support for audio.
+      return TypeSupport::CodecsDisabled;
+    }
+
+    return TypeSupport::Supported;
+  }
+
+  if (!aMimeType) {
+    // A mime type string was set, but it couldn't be parsed to a valid
+    // MediaContainerType.
+    return TypeSupport::MediaTypeInvalid;
+  }
+
+  if (aMimeType->Type() != MEDIAMIMETYPE(VIDEO_WEBM) &&
+      aMimeType->Type() != MEDIAMIMETYPE(AUDIO_WEBM) &&
+      aMimeType->Type() != MEDIAMIMETYPE(AUDIO_OGG)) {
+    // Any currently supported container can record audio.
+    return TypeSupport::ContainerUnsupported;
+  }
+
+  if (aMimeType->Type() == MEDIAMIMETYPE(VIDEO_WEBM) &&
+      !MediaEncoder::IsWebMEncoderEnabled()) {
+    return TypeSupport::ContainerUnsupported;
+  }
+
+  if (aMimeType->Type() == MEDIAMIMETYPE(AUDIO_WEBM) &&
+      !MediaEncoder::IsWebMEncoderEnabled()) {
+    return TypeSupport::ContainerUnsupported;
+  }
+
+  if (aMimeType->Type() == MEDIAMIMETYPE(AUDIO_OGG) &&
+      !MediaDecoder::IsOggEnabled()) {
+    return TypeSupport::ContainerUnsupported;
+  }
+
+  if (!MediaDecoder::IsOpusEnabled()) {
+    return TypeSupport::CodecUnsupported;
+  }
+
+  if (!aMimeType->ExtendedType().HaveCodecs()) {
+    // No codecs constrained, we can pick opus.
+    return TypeSupport::Supported;
+  }
+
+  size_t opus = 0;
+  size_t unknown = 0;
+  for (const auto& codec : aMimeType->ExtendedType().Codecs().Range()) {
+    // Ignore video codecs.
+    if (codec.EqualsLiteral("vp8")) {
+      continue;
+    }
+    if (codec.EqualsLiteral("vp8.0")) {
+      continue;
+    }
+    if (codec.EqualsLiteral("opus")) {
+      // All containers support opus
+      opus++;
+      continue;
+    }
+    unknown++;
+  }
+
+  if (unknown > 0) {
+    // Unsupported codec.
+    return TypeSupport::CodecUnsupported;
+  }
+
+  if (opus == 0) {
+    // Codecs specified but not opus. Unsupported for audio.
+    return TypeSupport::CodecUnsupported;
+  }
+
+  if (opus > 1) {
+    // Opus specified more than once. Bad form.
+    return TypeSupport::CodecDuplicated;
+  }
+
+  return TypeSupport::Supported;
+}
+
+TypeSupport CanRecordVideoTrackWith(const Maybe<MediaContainerType>& aMimeType,
+                                    const nsAString& aMimeTypeString) {
+  if (aMimeTypeString.IsEmpty()) {
+    // For the empty string we just need to check whether we have support for a
+    // video container and a video codec. The VP8 encoder is always available.
+    if (!MediaEncoder::IsWebMEncoderEnabled()) {
+      // No container support for video.
+      return TypeSupport::ContainersDisabled;
+    }
+
+    return TypeSupport::Supported;
+  }
+
+  if (!aMimeType) {
+    // A mime type string was set, but it couldn't be parsed to a valid
+    // MediaContainerType.
+    return TypeSupport::MediaTypeInvalid;
+  }
+
+  if (!aMimeType->Type().HasVideoMajorType()) {
+    return TypeSupport::NoVideoWithAudioType;
+  }
+
+  if (aMimeType->Type() != MEDIAMIMETYPE(VIDEO_WEBM)) {
+    return TypeSupport::ContainerUnsupported;
+  }
+
+  if (!MediaEncoder::IsWebMEncoderEnabled()) {
+    return TypeSupport::ContainerUnsupported;
+  }
+
+  if (!aMimeType->ExtendedType().HaveCodecs()) {
+    // No codecs constrained, we can pick vp8.
+    return TypeSupport::Supported;
+  }
+
+  size_t vp8 = 0;
+  size_t unknown = 0;
+  for (const auto& codec : aMimeType->ExtendedType().Codecs().Range()) {
+    if (codec.EqualsLiteral("opus")) {
+      // Ignore audio codecs.
+      continue;
+    }
+    if (codec.EqualsLiteral("vp8")) {
+      vp8++;
+      continue;
+    }
+    if (codec.EqualsLiteral("vp8.0")) {
+      vp8++;
+      continue;
+    }
+    unknown++;
+  }
+
+  if (unknown > 0) {
+    // Unsupported codec.
+    return TypeSupport::CodecUnsupported;
+  }
+
+  if (vp8 == 0) {
+    // Codecs specified but not vp8. Unsupported for video.
+    return TypeSupport::CodecUnsupported;
+  }
+
+  if (vp8 > 1) {
+    // Vp8 specified more than once. Bad form.
+    return TypeSupport::CodecDuplicated;
+  }
+
+  return TypeSupport::Supported;
+}
+
+TypeSupport CanRecordWith(MediaStreamTrack* aTrack,
+                          const Maybe<MediaContainerType>& aMimeType,
+                          const nsAString& aMimeTypeString) {
+  if (aTrack->AsAudioStreamTrack()) {
+    return CanRecordAudioTrackWith(aMimeType, aMimeTypeString);
+  }
+
+  if (aTrack->AsVideoStreamTrack()) {
+    return CanRecordVideoTrackWith(aMimeType, aMimeTypeString);
+  }
+
+  MOZ_CRASH("Unexpected track type");
+}
+
+TypeSupport IsTypeSupportedImpl(const nsAString& aMIMEType) {
+  if (aMIMEType.IsEmpty()) {
+    // Lie and return true even if no container/codec support is enabled,
+    // because the spec mandates it.
+    return TypeSupport::Supported;
+  }
+  Maybe<MediaContainerType> mime = MakeMediaContainerType(aMIMEType);
+  TypeSupport rv = CanRecordAudioTrackWith(mime, aMIMEType);
+  if (rv == TypeSupport::Supported) {
+    return rv;
+  }
+  return CanRecordVideoTrackWith(mime, aMIMEType);
+}
+
+nsString SelectMimeType(uint8_t aNumVideoTracks, uint8_t aNumAudioTracks,
+                        const nsString& aConstrainedMimeType) {
+  const bool hasVideo = aNumVideoTracks > 0;
+  const bool hasAudio = aNumAudioTracks > 0;
+  MOZ_ASSERT(hasVideo || hasAudio);
+
+  Maybe<MediaContainerType> constrainedType =
+      MakeMediaContainerType(aConstrainedMimeType);
+
+  nsCString majorType;
+  {
+    // Select major type and container.
+    if (constrainedType) {
+      MOZ_ASSERT_IF(hasVideo, constrainedType->Type().HasVideoMajorType());
+      MOZ_ASSERT(!constrainedType->Type().HasApplicationMajorType());
+      majorType = constrainedType->Type().AsString();
+    } else if (hasVideo) {
+      majorType = NS_LITERAL_CSTRING(VIDEO_WEBM);
+    } else {
+      majorType = NS_LITERAL_CSTRING(AUDIO_OGG);
+    }
+  }
+
+  nsString codecs;
+  {
+    if (constrainedType && constrainedType->ExtendedType().HaveCodecs()) {
+      codecs = constrainedType->ExtendedType().Codecs().AsString();
+    } else {
+      if (hasVideo && hasAudio) {
+        codecs = NS_LITERAL_STRING("\"vp8, opus\"");
+      } else if (hasVideo) {
+        codecs = NS_LITERAL_STRING("vp8");
+      } else {
+        codecs = NS_LITERAL_STRING("opus");
+      }
+    }
+  }
+
+  nsString result = NS_ConvertUTF8toUTF16(nsPrintfCString(
+      "%s; codecs=%s", majorType.get(), NS_ConvertUTF16toUTF8(codecs).get()));
+
+  MOZ_ASSERT_IF(hasAudio,
+                CanRecordAudioTrackWith(MakeMediaContainerType(result),
+                                        result) == TypeSupport::Supported);
+  MOZ_ASSERT_IF(hasVideo,
+                CanRecordVideoTrackWith(MakeMediaContainerType(result),
+                                        result) == TypeSupport::Supported);
+  return result;
+}
+
+void SelectBitrates(uint32_t aBitsPerSecond, uint8_t aNumVideoTracks,
+                    uint32_t* aOutVideoBps, uint8_t aNumAudioTracks,
+                    uint32_t* aOutAudioBps) {
+  uint32_t vbps = 0;
+  uint32_t abps = 0;
+
+  const uint32_t minVideoBps = MIN_VIDEO_BITRATE_BPS * aNumVideoTracks;
+  const uint32_t maxVideoBps = MAX_VIDEO_BITRATE_BPS * aNumVideoTracks;
+
+  const uint32_t minAudioBps = MIN_AUDIO_BITRATE_BPS * aNumAudioTracks;
+  const uint32_t maxAudioBps = MAX_AUDIO_BITRATE_BPS * aNumAudioTracks;
+
+  if (aNumVideoTracks == 0) {
+    MOZ_DIAGNOSTIC_ASSERT(aNumAudioTracks > 0);
+    abps = std::min(maxAudioBps, std::max(minAudioBps, aBitsPerSecond));
+  } else if (aNumAudioTracks == 0) {
+    vbps = std::min(maxVideoBps, std::max(minVideoBps, aBitsPerSecond));
+  } else {
+    // Scale the bits so that video gets 20 times the bits of audio.
+    // Since we must account for varying number of tracks of each type we weight
+    // them by type; video = weight 20, audio = weight 1.
+    const uint32_t videoWeight = aNumVideoTracks * 20;
+    const uint32_t audioWeight = aNumAudioTracks;
+    const uint32_t totalWeights = audioWeight + videoWeight;
+    const uint32_t videoBitrate =
+        uint64_t(aBitsPerSecond) * videoWeight / totalWeights;
+    const uint32_t audioBitrate =
+        uint64_t(aBitsPerSecond) * audioWeight / totalWeights;
+    vbps = std::min(maxVideoBps, std::max(minVideoBps, videoBitrate));
+    abps = std::min(maxAudioBps, std::max(minAudioBps, audioBitrate));
+  }
+
+  *aOutVideoBps = vbps;
+  *aOutAudioBps = abps;
+}
+}  // namespace
+
 /**
  * Session is an object to represent a single recording event.
  * In original design, all recording context is stored in MediaRecorder, which
  * causes a problem if someone calls MediaRecorder::Stop and
  * MediaRecorder::Start quickly. To prevent blocking main thread, media encoding
  * is executed in a second thread, named as Read Thread. For the same reason, we
  * do not wait Read Thread shutdown in MediaRecorder::Stop. If someone call
  * MediaRecorder::Start before Read Thread shutdown, the same recording context
@@ -216,59 +583,33 @@ class MediaRecorder::Session : public Pr
         : Runnable("StoreEncodedBufferRunnable"),
           mSession(aSession),
           mBuffer(std::move(aBuffer)) {}
 
     NS_IMETHOD
     Run() override {
       MOZ_ASSERT(NS_IsMainThread());
       mSession->MaybeCreateMutableBlobStorage();
-      for (uint32_t i = 0; i < mBuffer.Length(); i++) {
-        if (mBuffer[i].IsEmpty()) {
+      for (const auto& part : mBuffer) {
+        if (part.IsEmpty()) {
           continue;
         }
 
-        nsresult rv = mSession->mMutableBlobStorage->Append(
-            mBuffer[i].Elements(), mBuffer[i].Length());
+        nsresult rv = mSession->mMutableBlobStorage->Append(part.Elements(),
+                                                            part.Length());
         if (NS_WARN_IF(NS_FAILED(rv))) {
           mSession->DoSessionEndTask(rv);
           break;
         }
       }
 
       return NS_OK;
     }
   };
 
-  // Fire a named event, run in main thread task.
-  class DispatchEventRunnable : public Runnable {
-   public:
-    explicit DispatchEventRunnable(Session* aSession,
-                                   const nsAString& aEventName)
-        : Runnable("dom::MediaRecorder::Session::DispatchEventRunnable"),
-          mSession(aSession),
-          mEventName(aEventName) {}
-
-    NS_IMETHOD Run() override {
-      LOG(LogLevel::Debug,
-          ("Session.DispatchEventRunnable s=(%p) e=(%s)", mSession.get(),
-           NS_ConvertUTF16toUTF8(mEventName).get()));
-      MOZ_ASSERT(NS_IsMainThread());
-
-      NS_ENSURE_TRUE(mSession->mRecorder, NS_OK);
-      mSession->mRecorder->DispatchSimpleEvent(mEventName);
-
-      return NS_OK;
-    }
-
-   private:
-    RefPtr<Session> mSession;
-    nsString mEventName;
-  };
-
   class EncoderListener : public MediaEncoderListener {
    public:
     EncoderListener(TaskQueue* aEncoderThread, Session* aSession)
         : mEncoderThread(aEncoderThread), mSession(aSession) {}
 
     void Forget() {
       MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
       mSession = nullptr;
@@ -303,106 +644,111 @@ class MediaRecorder::Session : public Pr
     }
 
    protected:
     RefPtr<TaskQueue> mEncoderThread;
     RefPtr<Session> mSession;
   };
 
  public:
-  Session(MediaRecorder* aRecorder, uint32_t aTimeSlice)
+  Session(MediaRecorder* aRecorder,
+          nsTArray<RefPtr<MediaStreamTrack>> aMediaStreamTracks,
+          TimeDuration aTimeslice, uint32_t aVideoBitsPerSecond,
+          uint32_t aAudioBitsPerSecond)
       : mRecorder(aRecorder),
-        mMediaStreamReady(false),
+        mMediaStreamTracks(std::move(aMediaStreamTracks)),
         mMainThread(mRecorder->GetOwner()->EventTargetFor(TaskCategory::Other)),
-        mTimeSlice(aTimeSlice),
+        mMimeType(mRecorder->mMimeType),
+        mTimeslice(aTimeslice),
+        mVideoBitsPerSecond(aVideoBitsPerSecond),
+        mAudioBitsPerSecond(aAudioBitsPerSecond),
         mStartTime(TimeStamp::Now()),
         mRunningState(RunningState::Idling) {
     MOZ_ASSERT(NS_IsMainThread());
 
-    aRecorder->GetMimeType(mMimeType);
     mMaxMemory = Preferences::GetUint("media.recorder.max_memory",
                                       MAX_ALLOW_MEMORY_BUFFER);
-    mLastBlobTimeStamp = mStartTime;
     Telemetry::ScalarAdd(Telemetry::ScalarID::MEDIARECORDER_RECORDING_COUNT, 1);
   }
 
   void PrincipalChanged(MediaStreamTrack* aTrack) override {
     NS_ASSERTION(mMediaStreamTracks.Contains(aTrack),
                  "Principal changed for unrecorded track");
-    if (!MediaStreamTracksPrincipalSubsumes()) {
+    if (!MediaStreamTracksPrincipalSubsumes(mRecorder, mMediaStreamTracks)) {
       DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
     }
   }
 
   void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override {
     LOG(LogLevel::Warning,
         ("Session.NotifyTrackAdded %p Raising error due to track set change",
          this));
-    if (mMediaStreamReady) {
-      DoSessionEndTask(NS_ERROR_ABORT);
-    }
-
-    NS_DispatchToMainThread(
-        NewRunnableMethod("MediaRecorder::Session::MediaStreamReady", this,
-                          &Session::MediaStreamReady));
-    return;
+    DoSessionEndTask(NS_ERROR_ABORT);
   }
 
   void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override {
-    if (!mMediaStreamReady) {
-      // We haven't chosen the track set to record yet.
-      return;
-    }
-
     if (aTrack->Ended()) {
       // TrackEncoder will pickup tracks that end itself.
       return;
     }
-
-    MOZ_ASSERT(mEncoder);
-    if (mEncoder) {
-      mEncoder->RemoveMediaStreamTrack(aTrack);
-    }
-
     LOG(LogLevel::Warning,
         ("Session.NotifyTrackRemoved %p Raising error due to track set change",
          this));
     DoSessionEndTask(NS_ERROR_ABORT);
   }
 
   void Start() {
     LOG(LogLevel::Debug, ("Session.Start %p", this));
     MOZ_ASSERT(NS_IsMainThread());
 
-    DOMMediaStream* domStream = mRecorder->Stream();
-    if (domStream) {
-      // The callback reports back when tracks are available and can be
-      // attached to MediaEncoder. This allows `recorder.start()` before any
-      // tracks are available. We have supported this historically and have
-      // mochitests assuming this behavior.
-      mMediaStream = domStream;
+    if (mRecorder->mStream) {
+      // The TrackListener reports back when tracks are added or removed from
+      // the MediaStream.
+      mMediaStream = mRecorder->mStream;
       mMediaStream->RegisterTrackListener(this);
-      nsTArray<RefPtr<MediaStreamTrack>> tracks(2);
-      mMediaStream->GetTracks(tracks);
-      for (const auto& track : tracks) {
-        // Notify of existing tracks, as the stream doesn't do this by itself.
-        NotifyTrackAdded(track);
+
+      uint8_t trackTypes = 0;
+      int32_t audioTracks = 0;
+      int32_t videoTracks = 0;
+      for (const auto& track : mMediaStreamTracks) {
+        if (track->AsAudioStreamTrack()) {
+          ++audioTracks;
+          trackTypes |= ContainerWriter::CREATE_AUDIO_TRACK;
+        } else if (track->AsVideoStreamTrack()) {
+          ++videoTracks;
+          trackTypes |= ContainerWriter::CREATE_VIDEO_TRACK;
+        } else {
+          MOZ_CRASH("Unexpected track type");
+        }
       }
+
+      if (audioTracks > 1 || videoTracks > 1) {
+        // When MediaRecorder supports multiple tracks, we should set up a
+        // single MediaInputPort from the input stream, and let main thread
+        // check track principals async later.
+        nsPIDOMWindowInner* window = mRecorder->GetOwner();
+        Document* document = window ? window->GetExtantDoc() : nullptr;
+        nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+                                        NS_LITERAL_CSTRING("Media"), document,
+                                        nsContentUtils::eDOM_PROPERTIES,
+                                        "MediaRecorderMultiTracksNotSupported");
+        DoSessionEndTask(NS_ERROR_ABORT);
+        return;
+      }
+
+      for (const auto& t : mMediaStreamTracks) {
+        t->AddPrincipalChangeObserver(this);
+      }
+
+      LOG(LogLevel::Debug, ("Session.Start track types = (%d)", trackTypes));
+      InitEncoder(trackTypes, mMediaStreamTracks[0]->Graph()->GraphRate());
       return;
     }
 
     if (mRecorder->mAudioNode) {
-      // Check that we may access the audio node's content.
-      if (!AudioNodePrincipalSubsumes()) {
-        LOG(LogLevel::Warning,
-            ("Session.Start AudioNode principal check failed"));
-        DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
-        return;
-      }
-
       TrackRate trackRate =
           mRecorder->mAudioNode->Context()->Graph()->GraphRate();
 
       // Web Audio node has only audio.
       InitEncoder(ContainerWriter::CREATE_AUDIO_TRACK, trackRate);
       return;
     }
 
@@ -419,60 +765,59 @@ class MediaRecorder::Session : public Pr
 
     // Remove main thread state added in Start().
     if (mMediaStream) {
       mMediaStream->UnregisterTrackListener(this);
       mMediaStream = nullptr;
     }
 
     {
-      auto tracks(std::move(mMediaStreamTracks));
-      for (RefPtr<MediaStreamTrack>& track : tracks) {
+      for (const auto& track : mMediaStreamTracks) {
         track->RemovePrincipalChangeObserver(this);
       }
     }
 
     if (mRunningState.isOk() &&
         mRunningState.inspect() == RunningState::Idling) {
       LOG(LogLevel::Debug, ("Session.Stop Explicit end task %p", this));
       // End the Session directly if there is no encoder.
       DoSessionEndTask(NS_OK);
     } else if (mRunningState.isOk() &&
                (mRunningState.inspect() == RunningState::Starting ||
                 mRunningState.inspect() == RunningState::Running)) {
       mRunningState = RunningState::Stopping;
     }
   }
 
-  nsresult Pause() {
+  void Pause() {
     LOG(LogLevel::Debug, ("Session.Pause"));
     MOZ_ASSERT(NS_IsMainThread());
-
-    if (!mEncoder) {
-      return NS_ERROR_FAILURE;
+    MOZ_ASSERT_IF(mRunningState.isOk(),
+                  mRunningState.unwrap() != RunningState::Idling);
+    if (mRunningState.isErr() ||
+        mRunningState.unwrap() == RunningState::Stopping ||
+        mRunningState.unwrap() == RunningState::Stopped) {
+      return;
     }
-
+    MOZ_ASSERT(mEncoder);
     mEncoder->Suspend();
-    NS_DispatchToMainThread(
-        new DispatchEventRunnable(this, NS_LITERAL_STRING("pause")));
-    return NS_OK;
   }
 
-  nsresult Resume() {
+  void Resume() {
     LOG(LogLevel::Debug, ("Session.Resume"));
     MOZ_ASSERT(NS_IsMainThread());
-
-    if (!mEncoder) {
-      return NS_ERROR_FAILURE;
+    MOZ_ASSERT_IF(mRunningState.isOk(),
+                  mRunningState.unwrap() != RunningState::Idling);
+    if (mRunningState.isErr() ||
+        mRunningState.unwrap() == RunningState::Stopping ||
+        mRunningState.unwrap() == RunningState::Stopped) {
+      return;
     }
-
+    MOZ_ASSERT(mEncoder);
     mEncoder->Resume();
-    NS_DispatchToMainThread(
-        new DispatchEventRunnable(this, NS_LITERAL_STRING("resume")));
-    return NS_OK;
   }
 
   void RequestData() {
     LOG(LogLevel::Debug, ("Session.RequestData"));
     MOZ_ASSERT(NS_IsMainThread());
 
     GatherBlob()->Then(
         mMainThread, __func__,
@@ -613,165 +958,49 @@ class MediaRecorder::Session : public Pr
 
     // Append pulled data into cache buffer.
     NS_DispatchToMainThread(
         new StoreEncodedBufferRunnable(this, std::move(encodedBuf)));
 
     // Whether push encoded data back to onDataAvailable automatically or we
     // need a flush.
     bool pushBlob = aForceFlush;
-    if (!pushBlob && mTimeSlice > 0 &&
-        (TimeStamp::Now() - mLastBlobTimeStamp).ToMilliseconds() > mTimeSlice) {
+    if (!pushBlob && !mLastBlobTimeStamp.IsNull() &&
+        (TimeStamp::Now() - mLastBlobTimeStamp) > mTimeslice) {
       pushBlob = true;
     }
     if (pushBlob) {
-      mLastBlobTimeStamp = TimeStamp::Now();
+      if (!mLastBlobTimeStamp.IsNull()) {
+        // Only update the timestamp if the encoder has been initialized.
+        mLastBlobTimeStamp = TimeStamp::Now();
+      }
       InvokeAsync(mMainThread, this, __func__, &Session::GatherBlob)
           ->Then(mMainThread, __func__,
                  [this, self = RefPtr<Session>(this)](
                      const BlobPromise::ResolveOrRejectValue& aResult) {
+                   // Assert that we've seen the start event
+                   MOZ_ASSERT_IF(
+                       mRunningState.isOk(),
+                       mRunningState.inspect() != RunningState::Starting);
                    if (aResult.IsReject()) {
                      LOG(LogLevel::Warning,
                          ("GatherBlob failed for pushing blob"));
                      DoSessionEndTask(aResult.RejectValue());
                      return;
                    }
 
                    nsresult rv = mRecorder->CreateAndDispatchBlobEvent(
                        aResult.ResolveValue());
                    if (NS_FAILED(rv)) {
                      DoSessionEndTask(NS_OK);
                    }
                  });
     }
   }
 
-  void MediaStreamReady() {
-    if (!mMediaStream) {
-      // Already shut down. This can happen because MediaStreamReady is async.
-      return;
-    }
-
-    if (mMediaStreamReady) {
-      return;
-    }
-
-    if (!mRunningState.isOk() ||
-        mRunningState.inspect() != RunningState::Idling) {
-      return;
-    }
-
-    nsTArray<RefPtr<mozilla::dom::MediaStreamTrack>> tracks;
-    mMediaStream->GetTracks(tracks);
-    uint8_t trackTypes = 0;
-    int32_t audioTracks = 0;
-    int32_t videoTracks = 0;
-    for (auto& track : tracks) {
-      if (track->Ended()) {
-        continue;
-      }
-
-      ConnectMediaStreamTrack(*track);
-
-      if (track->AsAudioStreamTrack()) {
-        ++audioTracks;
-        trackTypes |= ContainerWriter::CREATE_AUDIO_TRACK;
-      } else if (track->AsVideoStreamTrack()) {
-        ++videoTracks;
-        trackTypes |= ContainerWriter::CREATE_VIDEO_TRACK;
-      } else {
-        MOZ_CRASH("Unexpected track type");
-      }
-    }
-
-    if (trackTypes == 0) {
-      MOZ_ASSERT(audioTracks == 0);
-      MOZ_ASSERT(videoTracks == 0);
-      return;
-    }
-
-    mMediaStreamReady = true;
-
-    if (audioTracks > 1 || videoTracks > 1) {
-      // When MediaRecorder supports multiple tracks, we should set up a single
-      // MediaInputPort from the input stream, and let main thread check
-      // track principals async later.
-      nsPIDOMWindowInner* window = mRecorder->GetOwner();
-      Document* document = window ? window->GetExtantDoc() : nullptr;
-      nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
-                                      NS_LITERAL_CSTRING("Media"), document,
-                                      nsContentUtils::eDOM_PROPERTIES,
-                                      "MediaRecorderMultiTracksNotSupported");
-      DoSessionEndTask(NS_ERROR_ABORT);
-      return;
-    }
-
-    // Check that we may access the tracks' content.
-    if (!MediaStreamTracksPrincipalSubsumes()) {
-      LOG(LogLevel::Warning, ("Session.MediaTracksReady MediaStreamTracks "
-                              "principal check failed"));
-      DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
-      return;
-    }
-
-    LOG(LogLevel::Debug,
-        ("Session.MediaTracksReady track type = (%d)", trackTypes));
-    InitEncoder(trackTypes, mMediaStreamTracks[0]->Graph()->GraphRate());
-  }
-
-  void ConnectMediaStreamTrack(MediaStreamTrack& aTrack) {
-    for (auto& track : mMediaStreamTracks) {
-      if (track->AsAudioStreamTrack() && aTrack.AsAudioStreamTrack()) {
-        // We only allow one audio track. See bug 1276928.
-        return;
-      }
-      if (track->AsVideoStreamTrack() && aTrack.AsVideoStreamTrack()) {
-        // We only allow one video track. See bug 1276928.
-        return;
-      }
-    }
-    mMediaStreamTracks.AppendElement(&aTrack);
-    aTrack.AddPrincipalChangeObserver(this);
-  }
-
-  bool PrincipalSubsumes(nsIPrincipal* aPrincipal) {
-    if (!mRecorder->GetOwner()) return false;
-    nsCOMPtr<Document> doc = mRecorder->GetOwner()->GetExtantDoc();
-    if (!doc) {
-      return false;
-    }
-    if (!aPrincipal) {
-      return false;
-    }
-    bool subsumes;
-    if (NS_FAILED(doc->NodePrincipal()->Subsumes(aPrincipal, &subsumes))) {
-      return false;
-    }
-    return subsumes;
-  }
-
-  bool MediaStreamTracksPrincipalSubsumes() {
-    MOZ_ASSERT(mRecorder->mDOMStream);
-    nsCOMPtr<nsIPrincipal> principal = nullptr;
-    for (RefPtr<MediaStreamTrack>& track : mMediaStreamTracks) {
-      nsContentUtils::CombineResourcePrincipals(&principal,
-                                                track->GetPrincipal());
-    }
-    return PrincipalSubsumes(principal);
-  }
-
-  bool AudioNodePrincipalSubsumes() {
-    MOZ_ASSERT(mRecorder->mAudioNode);
-    Document* doc = mRecorder->mAudioNode->GetOwner()
-                        ? mRecorder->mAudioNode->GetOwner()->GetExtantDoc()
-                        : nullptr;
-    nsCOMPtr<nsIPrincipal> principal = doc ? doc->NodePrincipal() : nullptr;
-    return PrincipalSubsumes(principal);
-  }
-
   void InitEncoder(uint8_t aTrackTypes, TrackRate aTrackRate) {
     LOG(LogLevel::Debug, ("Session.InitEncoder %p", this));
     MOZ_ASSERT(NS_IsMainThread());
 
     if (!mRunningState.isOk() ||
         mRunningState.inspect() != RunningState::Idling) {
       MOZ_ASSERT_UNREACHABLE("Double-init");
       return;
@@ -830,50 +1059,19 @@ class MediaRecorder::Session : public Pr
       nsresult rv = barrier->AddBlocker(
           gMediaRecorderShutdownBlocker, NS_LITERAL_STRING(__FILE__), __LINE__,
           NS_LITERAL_STRING("MediaRecorder::Session: shutdown"));
       MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
     }
 
     gSessions.PutEntry(this);
 
-    uint32_t audioBitrate = mRecorder->GetAudioBitrate();
-    uint32_t videoBitrate = mRecorder->GetVideoBitrate();
-    uint32_t bitrate = mRecorder->GetBitrate();
-    if (bitrate > 0) {
-      // There's a total cap set. We have to make sure the type-specific limits
-      // are within range.
-      if ((aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK) &&
-          (aTrackTypes & ContainerWriter::CREATE_VIDEO_TRACK) &&
-          audioBitrate + videoBitrate > bitrate) {
-        LOG(LogLevel::Info, ("Session.InitEncoder Bitrates higher than total "
-                             "cap. Recalculating."));
-        double factor =
-            bitrate / static_cast<double>(audioBitrate + videoBitrate);
-        audioBitrate = static_cast<uint32_t>(audioBitrate * factor);
-        videoBitrate = static_cast<uint32_t>(videoBitrate * factor);
-      } else if ((aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK) &&
-                 !(aTrackTypes & ContainerWriter::CREATE_VIDEO_TRACK)) {
-        audioBitrate = std::min(audioBitrate, bitrate);
-        videoBitrate = 0;
-      } else if (!(aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK) &&
-                 (aTrackTypes & ContainerWriter::CREATE_VIDEO_TRACK)) {
-        audioBitrate = 0;
-        videoBitrate = std::min(videoBitrate, bitrate);
-      }
-      MOZ_ASSERT(audioBitrate + videoBitrate <= bitrate);
-    }
-
-    // Allocate encoder and bind with union stream.
-    // At this stage, the API doesn't allow UA to choose the output mimeType
-    // format.
-
-    mEncoder =
-        MediaEncoder::CreateEncoder(mEncoderThread, mMimeType, audioBitrate,
-                                    videoBitrate, aTrackTypes, aTrackRate);
+    mEncoder = MediaEncoder::CreateEncoder(
+        mEncoderThread, mMimeType, mAudioBitsPerSecond, mVideoBitsPerSecond,
+        aTrackTypes, aTrackRate);
 
     if (!mEncoder) {
       LOG(LogLevel::Error, ("Session.InitEncoder !mEncoder %p", this));
       DoSessionEndTask(NS_ERROR_ABORT);
       return;
     }
 
     mEncoderListener = MakeAndAddRef<EncoderListener>(mEncoderThread, this);
@@ -884,23 +1082,26 @@ class MediaRecorder::Session : public Pr
     MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
     Unused << rv;
 
     if (mRecorder->mAudioNode) {
       mEncoder->ConnectAudioNode(mRecorder->mAudioNode,
                                  mRecorder->mAudioNodeOutput);
     }
 
-    for (auto& track : mMediaStreamTracks) {
+    for (const auto& track : mMediaStreamTracks) {
       mEncoder->ConnectMediaStreamTrack(track);
     }
 
-    // If user defines timeslice interval for video blobs we have to set
-    // appropriate video keyframe interval defined in milliseconds.
-    mEncoder->SetVideoKeyFrameInterval(mTimeSlice);
+    // If a timeslice is defined we set an appropriate video keyframe interval.
+    // This allows users to get blobs regularly when the timeslice interval is
+    // shorter than the default key frame interval, as we'd normally wait for a
+    // key frame before sending data to the blob.
+    mEncoder->SetVideoKeyFrameInterval(
+        std::max(TimeDuration::FromSeconds(1), mTimeslice).ToMilliseconds());
 
     // Set mRunningState to Running so that DoSessionEndTask will
     // take the responsibility to end the session.
     mRunningState = RunningState::Starting;
   }
 
   // This is the task that will stop recording per spec:
   // - Stop gathering data (this is inherently async)
@@ -937,17 +1138,17 @@ class MediaRecorder::Session : public Pr
 
     GatherBlob()
         ->Then(mMainThread, __func__,
                [this, self = RefPtr<Session>(this), rv, needsStartEvent](
                    const BlobPromise::ResolveOrRejectValue& aResult) {
                  if (mRecorder->mSessions.LastElement() == this) {
                    // Set state to inactive, but only if the recorder is not
                    // controlled by another session already.
-                   mRecorder->ForceInactive();
+                   mRecorder->Inactivate();
                  }
 
                  if (needsStartEvent) {
                    mRecorder->DispatchSimpleEvent(NS_LITERAL_STRING("start"));
                  }
 
                  // If there was an error, Fire the appropriate one
                  if (NS_FAILED(rv)) {
@@ -971,20 +1172,16 @@ class MediaRecorder::Session : public Pr
                    // Failed to dispatch blob event. That's unexpected. It's
                    // probably all right to fire an error event if we haven't
                    // already.
                    if (NS_SUCCEEDED(rv)) {
                      mRecorder->NotifyError(NS_ERROR_FAILURE);
                    }
                  }
 
-                 // Dispatch stop event and clear MIME type.
-                 mMimeType = NS_LITERAL_STRING("");
-                 mRecorder->SetMimeType(mMimeType);
-
                  // Fire an event named stop
                  mRecorder->DispatchSimpleEvent(NS_LITERAL_STRING("stop"));
 
                  // And finally, Shutdown and destroy the Session
                  return Shutdown();
                })
         ->Then(mMainThread, __func__, [this, self = RefPtr<Session>(this)] {
           gSessions.RemoveEntry(this);
@@ -996,38 +1193,40 @@ class MediaRecorder::Session : public Pr
             gMediaRecorderShutdownBlocker = nullptr;
           }
         });
   }
 
   void MediaEncoderInitialized() {
     MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
 
+    // Start issuing timeslice-based blobs.
+    MOZ_ASSERT(mLastBlobTimeStamp.IsNull());
+    mLastBlobTimeStamp = TimeStamp::Now();
+
+    Extract(false);
+
     NS_DispatchToMainThread(NewRunnableFrom([self = RefPtr<Session>(this), this,
                                              mime = mEncoder->MimeType()]() {
       if (mRunningState.isErr()) {
         return NS_OK;
       }
-      mMimeType = mime;
-      mRecorder->SetMimeType(mime);
       RunningState state = mRunningState.inspect();
       if (state == RunningState::Starting || state == RunningState::Stopping) {
         if (state == RunningState::Starting) {
           // We set it to Running in the runnable since we can only assign
           // mRunningState on main thread. We set it before running the start
           // event runnable since that dispatches synchronously (and may cause
           // js calls to methods depending on mRunningState).
           mRunningState = RunningState::Running;
         }
         mRecorder->DispatchSimpleEvent(NS_LITERAL_STRING("start"));
       }
       return NS_OK;
     }));
-
-    Extract(false);
   }
 
   void MediaEncoderDataAvailable() {
     MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
 
     Extract(false);
   }
 
@@ -1139,19 +1338,16 @@ class MediaRecorder::Session : public Pr
   };
 
   // Our associated MediaRecorder.
   const RefPtr<MediaRecorder> mRecorder;
 
   // Stream currently recorded.
   RefPtr<DOMMediaStream> mMediaStream;
 
-  // True after we have decided on the track set to use for the recording.
-  bool mMediaStreamReady;
-
   // Tracks currently recorded. This should be a subset of mMediaStream's track
   // set.
   nsTArray<RefPtr<MediaStreamTrack>> mMediaStreamTracks;
 
   // Main thread used for MozPromise operations.
   const RefPtr<nsISerialEventTarget> mMainThread;
   // Runnable thread for reading data from MediaEncoder.
   RefPtr<TaskQueue> mEncoderThread;
@@ -1163,62 +1359,43 @@ class MediaRecorder::Session : public Pr
   RefPtr<ShutdownPromise> mShutdownPromise;
   // A buffer to cache encoded media data.
   RefPtr<MutableBlobStorage> mMutableBlobStorage;
   // Max memory to use for the MutableBlobStorage.
   uint64_t mMaxMemory;
   // If set, is a promise for the latest GatherBlob() operation. Allows
   // GatherBlob() operations to be serialized in order to avoid races.
   RefPtr<BlobPromise> mBlobPromise;
-  // Current session mimeType
-  nsString mMimeType;
+  // Session mimeType
+  const nsString mMimeType;
   // Timestamp of the last fired dataavailable event.
   TimeStamp mLastBlobTimeStamp;
   // The interval of passing encoded data from MutableBlobStorage to
   // onDataAvailable handler.
-  const uint32_t mTimeSlice;
+  const TimeDuration mTimeslice;
+  // The video bitrate the recorder was configured with.
+  const uint32_t mVideoBitsPerSecond;
+  // The audio bitrate the recorder was configured with.
+  const uint32_t mAudioBitsPerSecond;
   // The time this session started, for telemetry.
   const TimeStamp mStartTime;
   // The session's current main thread state. The error type gets set when
   // ending a recording with an error. An NS_OK error is invalid.
   // Main thread only.
   Result<RunningState, nsresult> mRunningState;
 };
 
 MediaRecorder::~MediaRecorder() {
   LOG(LogLevel::Debug, ("~MediaRecorder (%p)", this));
   UnRegisterActivityObserver();
 }
 
-MediaRecorder::MediaRecorder(DOMMediaStream& aSourceMediaTrack,
-                             nsPIDOMWindowInner* aOwnerWindow)
-    : DOMEventTargetHelper(aOwnerWindow),
-      mAudioNodeOutput(0),
-      mState(RecordingState::Inactive),
-      mAudioBitsPerSecond(0),
-      mVideoBitsPerSecond(0),
-      mBitsPerSecond(0) {
+MediaRecorder::MediaRecorder(nsPIDOMWindowInner* aOwnerWindow)
+    : DOMEventTargetHelper(aOwnerWindow) {
   MOZ_ASSERT(aOwnerWindow);
-  mDOMStream = &aSourceMediaTrack;
-
-  RegisterActivityObserver();
-}
-
-MediaRecorder::MediaRecorder(AudioNode& aSrcAudioNode, uint32_t aSrcOutput,
-                             nsPIDOMWindowInner* aOwnerWindow)
-    : DOMEventTargetHelper(aOwnerWindow),
-      mAudioNodeOutput(aSrcOutput),
-      mState(RecordingState::Inactive),
-      mAudioBitsPerSecond(0),
-      mVideoBitsPerSecond(0),
-      mBitsPerSecond(0) {
-  MOZ_ASSERT(aOwnerWindow);
-
-  mAudioNode = &aSrcAudioNode;
-
   RegisterActivityObserver();
 }
 
 void MediaRecorder::RegisterActivityObserver() {
   if (nsPIDOMWindowInner* window = GetOwner()) {
     mDocument = window->GetExtantDoc();
     if (mDocument) {
       mDocument->RegisterActivityObserver(
@@ -1229,297 +1406,533 @@ void MediaRecorder::RegisterActivityObse
 
 void MediaRecorder::UnRegisterActivityObserver() {
   if (mDocument) {
     mDocument->UnregisterActivityObserver(
         NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
   }
 }
 
-void MediaRecorder::SetMimeType(const nsString& aMimeType) {
-  mMimeType = aMimeType;
-}
-
 void MediaRecorder::GetMimeType(nsString& aMimeType) { aMimeType = mMimeType; }
 
-void MediaRecorder::Start(const Optional<uint32_t>& aTimeSlice,
+void MediaRecorder::Start(const Optional<uint32_t>& aTimeslice,
                           ErrorResult& aResult) {
   LOG(LogLevel::Debug, ("MediaRecorder.Start %p", this));
 
   InitializeDomExceptions();
 
+  // When a MediaRecorder object’s start() method is invoked, the UA MUST run
+  // the following steps:
+
+  // 1. Let recorder be the MediaRecorder object on which the method was
+  //    invoked.
+
+  // 2. Let timeslice be the method’s first argument, if provided, or undefined.
+  TimeDuration timeslice =
+      aTimeslice.WasPassed()
+          ? TimeDuration::FromMilliseconds(aTimeslice.Value())
+          : TimeDuration::Forever();
+
+  // 3. Let stream be the value of recorder’s stream attribute.
+
+  // 4. Let tracks be the set of live tracks in stream’s track set.
+  nsTArray<RefPtr<MediaStreamTrack>> tracks;
+  if (mStream) {
+    mStream->GetTracks(tracks);
+  }
+  for (const auto& t : nsTArray<RefPtr<MediaStreamTrack>>(tracks)) {
+    if (t->Ended()) {
+      tracks.RemoveElement(t);
+    }
+  }
+
+  // 5. If the value of recorder’s state attribute is not inactive, throw an
+  //    InvalidStateError DOMException and abort these steps.
   if (mState != RecordingState::Inactive) {
-    aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    aResult.ThrowDOMException(
+        NS_ERROR_DOM_INVALID_STATE_ERR,
+        NS_LITERAL_CSTRING("The MediaRecorder has already been started"));
     return;
   }
 
-  nsTArray<RefPtr<MediaStreamTrack>> tracks;
-  if (mDOMStream) {
-    mDOMStream->GetTracks(tracks);
+  // 6. If the isolation properties of stream disallow access from recorder,
+  //    throw a SecurityError DOMException and abort these steps.
+  if (mStream) {
+    RefPtr<nsIPrincipal> streamPrincipal = mStream->GetPrincipal();
+    if (!PrincipalSubsumes(this, streamPrincipal)) {
+      aResult.ThrowDOMException(
+          NS_ERROR_DOM_SECURITY_ERR,
+          NS_LITERAL_CSTRING("The MediaStream's isolation properties disallow "
+                             "access from MediaRecorder"));
+      return;
+    }
+  }
+  if (mAudioNode && !AudioNodePrincipalSubsumes(this, mAudioNode)) {
+    LOG(LogLevel::Warning,
+        ("MediaRecorder %p Start AudioNode principal check failed", this));
+    aResult.ThrowDOMException(
+        NS_ERROR_DOM_SECURITY_ERR,
+        NS_LITERAL_CSTRING("The AudioNode's isolation properties disallow "
+                           "access from MediaRecorder"));
+    return;
+  }
+
+  // 7. If stream is inactive, throw a NotSupportedError DOMException and abort
+  //    these steps.
+  if (mStream && !mStream->Active()) {
+    aResult.ThrowDOMException(
+        NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+        NS_LITERAL_CSTRING("The MediaStream is inactive"));
+    return;
   }
-  if (!tracks.IsEmpty()) {
-    // If there are tracks already available that we're not allowed
-    // to record, we should throw a security error.
-    RefPtr<nsIPrincipal> streamPrincipal = mDOMStream->GetPrincipal();
-    bool subsumes = false;
-    nsPIDOMWindowInner* window;
-    Document* doc;
-    if (!(window = GetOwner()) || !(doc = window->GetExtantDoc()) ||
-        NS_FAILED(doc->NodePrincipal()->Subsumes(streamPrincipal, &subsumes)) ||
-        !subsumes) {
-      aResult.Throw(NS_ERROR_DOM_SECURITY_ERR);
+
+  // 8. If the [[ConstrainedMimeType]] slot specifies a media type, container,
+  //    or codec, then run the following sub steps:
+  //   1. Constrain the configuration of recorder to the media type, container,
+  //      and codec specified in the [[ConstrainedMimeType]] slot.
+  //   2. For each track in tracks, if the User Agent cannot record the track
+  //      using the current configuration, then throw a NotSupportedError
+  //      DOMException and abort all steps.
+  Maybe<MediaContainerType> mime;
+  if (mConstrainedMimeType.Length() > 0) {
+    mime = MakeMediaContainerType(mConstrainedMimeType);
+    MOZ_DIAGNOSTIC_ASSERT(
+        mime,
+        "Invalid media MIME type should have been caught by IsTypeSupported");
+  }
+  for (const auto& track : tracks) {
+    TypeSupport support = CanRecordWith(track, mime, mConstrainedMimeType);
+    if (support != TypeSupport::Supported) {
+      nsString id;
+      track->GetId(id);
+      aResult.ThrowDOMException(
+          NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+          nsPrintfCString(
+              "%s track cannot be recorded: %s",
+              track->AsAudioStreamTrack() ? "An audio" : "A video",
+              TypeSupportToCString(support, mConstrainedMimeType).get()));
+      return;
+    }
+  }
+  if (mAudioNode) {
+    TypeSupport support = CanRecordAudioTrackWith(mime, mConstrainedMimeType);
+    if (support != TypeSupport::Supported) {
+      aResult.ThrowDOMException(
+          NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+          nsPrintfCString(
+              "An AudioNode cannot be recorded: %s",
+              TypeSupportToCString(support, mConstrainedMimeType).get()));
       return;
     }
   }
 
-  uint32_t timeSlice = aTimeSlice.WasPassed() ? aTimeSlice.Value() : 0;
+  // 9. Let extendedMimeType be the value of recorder’s [[ConstrainedMimeType]]
+  //    slot.
+  nsString extendedMimeType = mConstrainedMimeType;
+
+  // 10. Modify extendedMimeType by adding media type, subtype and codecs
+  //     parameter reflecting the configuration used by the MediaRecorder to
+  //     record all tracks in tracks, if not already present. This MAY include
+  //     the profiles parameter [RFC6381] or further codec-specific parameters.
+  uint8_t numVideoTracks = 0;
+  uint8_t numAudioTracks = 0;
+  for (const auto& t : tracks) {
+    if (t->AsVideoStreamTrack() && numVideoTracks < UINT8_MAX) {
+      ++numVideoTracks;
+    } else if (t->AsAudioStreamTrack() && numAudioTracks < UINT8_MAX) {
+      ++numAudioTracks;
+    }
+  }
+  if (mAudioNode) {
+    MOZ_DIAGNOSTIC_ASSERT(!mStream);
+    ++numAudioTracks;
+  }
+  extendedMimeType =
+      SelectMimeType(numVideoTracks, numAudioTracks, extendedMimeType);
+
+  // 11. Set recorder’s mimeType attribute to extendedMimeType.
+  mMimeType = std::move(extendedMimeType);
+
+  // 12. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
+  //     recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
+  //     values the User Agent deems reasonable for the respective media types,
+  //     for recording all tracks in tracks, such that the sum of
+  //     videoBitsPerSecond and audioBitsPerSecond is close to the value of
+  //     recorder’s
+  //     [[ConstrainedBitsPerSecond]] slot.
+  if (mConstrainedBitsPerSecond) {
+    SelectBitrates(*mConstrainedBitsPerSecond, numVideoTracks,
+                   &mVideoBitsPerSecond, numAudioTracks, &mAudioBitsPerSecond);
+  }
+
+  // 13. Let videoBitrate be the value of recorder’s videoBitsPerSecond
+  //     attribute, and constrain the configuration of recorder to target an
+  //     aggregate bitrate of videoBitrate bits per second for all video tracks
+  //     recorder will be recording. videoBitrate is a hint for the encoder and
+  //     the value might be surpassed, not achieved, or only be achieved over a
+  //     long period of time.
+  const uint32_t videoBitrate = mVideoBitsPerSecond;
+
+  // 14. Let audioBitrate be the value of recorder’s audioBitsPerSecond
+  //     attribute, and constrain the configuration of recorder to target an
+  //     aggregate bitrate of audioBitrate bits per second for all audio tracks
+  //     recorder will be recording. audioBitrate is a hint for the encoder and
+  //     the value might be surpassed, not achieved, or only be achieved over a
+  //     long period of time.
+  const uint32_t audioBitrate = mAudioBitsPerSecond;
+
+  // 15. Set recorder’s state to recording
+  mState = RecordingState::Recording;
+
   MediaRecorderReporter::AddMediaRecorder(this);
-  mState = RecordingState::Recording;
   // Start a session.
   mSessions.AppendElement();
-  mSessions.LastElement() = new Session(this, timeSlice);
+  mSessions.LastElement() = new Session(this, std::move(tracks), timeslice,
+                                        videoBitrate, audioBitrate);
   mSessions.LastElement()->Start();
 }
 
 void MediaRecorder::Stop(ErrorResult& aResult) {
   LOG(LogLevel::Debug, ("MediaRecorder.Stop %p", this));
   MediaRecorderReporter::RemoveMediaRecorder(this);
+
+  // When a MediaRecorder object’s stop() method is invoked, the UA MUST run the
+  // following steps:
+
+  // 1. Let recorder be the MediaRecorder object on which the method was
+  //    invoked.
+
+  // 2. If recorder’s state attribute is inactive, abort these steps.
   if (mState == RecordingState::Inactive) {
     return;
   }
-  mState = RecordingState::Inactive;
+
+  // 3. Inactivate the recorder with recorder.
+  Inactivate();
+
+  // 4. Queue a task, using the DOM manipulation task source, that runs the
+  //    following steps:
+  //   1. Stop gathering data.
+  //   2. Let blob be the Blob of collected data so far, then fire a blob event
+  //      named dataavailable at recorder with blob.
+  //   3. Fire an event named stop at recorder.
   MOZ_ASSERT(mSessions.Length() > 0);
   mSessions.LastElement()->Stop();
+
+  // 5. return undefined.
 }
 
 void MediaRecorder::Pause(ErrorResult& aResult) {
   LOG(LogLevel::Debug, ("MediaRecorder.Pause %p", this));
+
+  // When a MediaRecorder object’s pause() method is invoked, the UA MUST run
+  // the following steps:
+
+  // 1. If state is inactive, throw an InvalidStateError DOMException and abort
+  //    these steps.
   if (mState == RecordingState::Inactive) {
-    aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    aResult.ThrowDOMException(
+        NS_ERROR_DOM_INVALID_STATE_ERR,
+        NS_LITERAL_CSTRING("The MediaRecorder is inactive"));
     return;
   }
 
+  // 2. If state is paused, abort these steps.
   if (mState == RecordingState::Paused) {
     return;
   }
 
-  MOZ_ASSERT(mSessions.Length() > 0);
-  nsresult rv = mSessions.LastElement()->Pause();
-  if (NS_FAILED(rv)) {
-    NotifyError(rv);
-    return;
-  }
+  // 3. Set state to paused, and queue a task, using the DOM manipulation task
+  //    source, that runs the following steps:
+  mState = RecordingState::Paused;
+  MOZ_ASSERT(!mSessions.IsEmpty());
+  NS_DispatchToMainThread(NS_NewRunnableFunction(
+      "MediaRecorder::Pause", [session = mSessions.LastElement(),
+                               recorder = RefPtr<MediaRecorder>(this)] {
+        // 1. Stop gathering data into blob (but keep it available so that
+        //    recording can be resumed in the future).
+        session->Pause();
 
-  mState = RecordingState::Paused;
+        // 2. Let target be the MediaRecorder context object. Fire an event
+        //    named pause at target.
+        recorder->DispatchSimpleEvent(NS_LITERAL_STRING("pause"));
+      }));
+
+  // 4. return undefined.
 }
 
 void MediaRecorder::Resume(ErrorResult& aResult) {
   LOG(LogLevel::Debug, ("MediaRecorder.Resume %p", this));
+
+  // When a MediaRecorder object’s resume() method is invoked, the UA MUST run
+  // the following steps:
+
+  // 1. If state is inactive, throw an InvalidStateError DOMException and abort
+  //    these steps.
   if (mState == RecordingState::Inactive) {
-    aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    aResult.ThrowDOMException(
+        NS_ERROR_DOM_INVALID_STATE_ERR,
+        NS_LITERAL_CSTRING("The MediaRecorder is inactive"));
     return;
   }
 
+  // 2. If state is recording, abort these steps.
   if (mState == RecordingState::Recording) {
     return;
   }
 
-  MOZ_ASSERT(mSessions.Length() > 0);
-  nsresult rv = mSessions.LastElement()->Resume();
-  if (NS_FAILED(rv)) {
-    NotifyError(rv);
-    return;
-  }
+  // 3. Set state to recording, and queue a task, using the DOM manipulation
+  //    task source, that runs the following steps:
+  mState = RecordingState::Recording;
+  MOZ_ASSERT(!mSessions.IsEmpty());
+  NS_DispatchToMainThread(NS_NewRunnableFunction(
+      "MediaRecorder::Resume", [session = mSessions.LastElement(),
+                                recorder = RefPtr<MediaRecorder>(this)] {
+        // 1. Resume (or continue) gathering data into the current blob.
+        session->Resume();
 
-  mState = RecordingState::Recording;
+        // 2. Let target be the MediaRecorder context object. Fire an event
+        //    named resume at target.
+        recorder->DispatchSimpleEvent(NS_LITERAL_STRING("resume"));
+      }));
+
+  // 4. return undefined.
 }
 
 void MediaRecorder::RequestData(ErrorResult& aResult) {
+  LOG(LogLevel::Debug, ("MediaRecorder.RequestData %p", this));
+
+  // When a MediaRecorder object’s requestData() method is invoked, the UA MUST
+  // run the following steps:
+
+  // 1. If state is inactive throw an InvalidStateError DOMException and
+  //    terminate these steps. Otherwise the UA MUST queue a task, using the DOM
+  //    manipulation task source, that runs the following steps:
+  //   1. Let blob be the Blob of collected data so far and let target be the
+  //      MediaRecorder context object, then fire a blob event named
+  //      dataavailable at target with blob. (Note that blob will be empty if no
+  //      data has been gathered yet.)
+  //   2. Create a new Blob and gather subsequent data into it.
   if (mState == RecordingState::Inactive) {
-    aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    aResult.ThrowDOMException(
+        NS_ERROR_DOM_INVALID_STATE_ERR,
+        NS_LITERAL_CSTRING("The MediaRecorder is inactive"));
     return;
   }
   MOZ_ASSERT(mSessions.Length() > 0);
   mSessions.LastElement()->RequestData();
+
+  // 2. return undefined.
 }
 
 JSObject* MediaRecorder::WrapObject(JSContext* aCx,
                                     JS::Handle<JSObject*> aGivenProto) {
   return MediaRecorder_Binding::Wrap(aCx, this, aGivenProto);
 }
 
 /* static */
 already_AddRefed<MediaRecorder> MediaRecorder::Constructor(
     const GlobalObject& aGlobal, DOMMediaStream& aStream,
-    const MediaRecorderOptions& aInitDict, ErrorResult& aRv) {
+    const MediaRecorderOptions& aOptions, ErrorResult& aRv) {
   nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
       do_QueryInterface(aGlobal.GetAsSupports());
   if (!ownerWindow) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  if (!IsTypeSupported(aInitDict.mMimeType)) {
-    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+  // When the MediaRecorder() constructor is invoked, the User Agent MUST run
+  // the following steps:
+
+  // 1. Let stream be the constructor’s first argument.
+
+  // 2. Let options be the constructor’s second argument.
+
+  // 3. If invoking is type supported with options’ mimeType member as its
+  //    argument returns false, throw a NotSupportedError DOMException and abort
+  //    these steps.
+  TypeSupport support = IsTypeSupportedImpl(aOptions.mMimeType);
+  if (support != TypeSupport::Supported) {
+    // This catches also the empty string mimeType when support for any encoders
+    // has been disabled.
+    aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+                          TypeSupportToCString(support, aOptions.mMimeType));
     return nullptr;
   }
 
-  RefPtr<MediaRecorder> object = new MediaRecorder(aStream, ownerWindow);
-  object->SetOptions(aInitDict);
-  return object.forget();
+  // 4. Let recorder be a newly constructed MediaRecorder object.
+  RefPtr<MediaRecorder> recorder = new MediaRecorder(ownerWindow);
+
+  // 5. Let recorder have a [[ConstrainedMimeType]] internal slot, initialized
+  //    to the value of options' mimeType member.
+  recorder->mConstrainedMimeType = aOptions.mMimeType;
+
+  // 6. Let recorder have a [[ConstrainedBitsPerSecond]] internal slot,
+  //    initialized to the value of options’ bitsPerSecond member, if it is
+  //    present, otherwise undefined.
+  recorder->mConstrainedBitsPerSecond =
+      aOptions.mBitsPerSecond.WasPassed()
+          ? Some(aOptions.mBitsPerSecond.Value())
+          : Nothing();
+
+  // 7. Initialize recorder’s stream attribute to stream.
+  recorder->mStream = &aStream;
+
+  // 8. Initialize recorder’s mimeType attribute to the value of recorder’s
+  //    [[ConstrainedMimeType]] slot.
+  recorder->mMimeType = recorder->mConstrainedMimeType;
+
+  // 9. Initialize recorder’s state attribute to inactive.
+  recorder->mState = RecordingState::Inactive;
+
+  // 10. Initialize recorder’s videoBitsPerSecond attribute to the value of
+  //     options’ videoBitsPerSecond member, if it is present. Otherwise, choose
+  //     a target value the User Agent deems reasonable for video.
+  recorder->mVideoBitsPerSecond = aOptions.mVideoBitsPerSecond.WasPassed()
+                                      ? aOptions.mVideoBitsPerSecond.Value()
+                                      : DEFAULT_VIDEO_BITRATE_BPS;
+
+  // 11. Initialize recorder’s audioBitsPerSecond attribute to the value of
+  //     options’ audioBitsPerSecond member, if it is present. Otherwise, choose
+  //     a target value the User Agent deems reasonable for audio.
+  recorder->mAudioBitsPerSecond = aOptions.mAudioBitsPerSecond.WasPassed()
+                                      ? aOptions.mAudioBitsPerSecond.Value()
+                                      : DEFAULT_AUDIO_BITRATE_BPS;
+
+  // 12. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
+  //     recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
+  //     values the User Agent deems reasonable for the respective media types,
+  //     such that the sum of videoBitsPerSecond and audioBitsPerSecond is close
+  //     to the value of recorder’s [[ConstrainedBitsPerSecond]] slot.
+  if (recorder->mConstrainedBitsPerSecond) {
+    SelectBitrates(*recorder->mConstrainedBitsPerSecond, 1,
+                   &recorder->mVideoBitsPerSecond, 1,
+                   &recorder->mAudioBitsPerSecond);
+  }
+
+  // 13. Return recorder.
+  return recorder.forget();
 }
 
 /* static */
 already_AddRefed<MediaRecorder> MediaRecorder::Constructor(
-    const GlobalObject& aGlobal, AudioNode& aSrcAudioNode, uint32_t aSrcOutput,
-    const MediaRecorderOptions& aInitDict, ErrorResult& aRv) {
+    const GlobalObject& aGlobal, AudioNode& aAudioNode,
+    uint32_t aAudioNodeOutput, const MediaRecorderOptions& aOptions,
+    ErrorResult& aRv) {
   // Allow recording from audio node only when pref is on.
   if (!Preferences::GetBool("media.recorder.audio_node.enabled", false)) {
     // Pretending that this constructor is not defined.
     NS_NAMED_LITERAL_STRING(argStr, "Argument 1 of MediaRecorder.constructor");
     NS_NAMED_LITERAL_STRING(typeStr, "MediaStream");
     aRv.ThrowTypeError<MSG_DOES_NOT_IMPLEMENT_INTERFACE>(argStr, typeStr);
     return nullptr;
   }
 
   nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
       do_QueryInterface(aGlobal.GetAsSupports());
   if (!ownerWindow) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  // aSrcOutput doesn't matter to destination node because it has no output.
-  if (aSrcAudioNode.NumberOfOutputs() > 0 &&
-      aSrcOutput >= aSrcAudioNode.NumberOfOutputs()) {
-    aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+  // aAudioNodeOutput doesn't matter to destination node because it has no
+  // output.
+  if (aAudioNode.NumberOfOutputs() > 0 &&
+      aAudioNodeOutput >= aAudioNode.NumberOfOutputs()) {
+    aRv.ThrowDOMException(NS_ERROR_DOM_INDEX_SIZE_ERR,
+                          NS_LITERAL_CSTRING("Invalid AudioNode output index"));
     return nullptr;
   }
 
-  if (!IsTypeSupported(aInitDict.mMimeType)) {
-    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+  // When the MediaRecorder() constructor is invoked, the User Agent MUST run
+  // the following steps:
+
+  // 1. Let stream be the constructor’s first argument. (we'll let audioNode be
+  //    the first arg, and audioNodeOutput the second)
+
+  // 2. Let options be the constructor’s second argument. (we'll let options be
+  //    the third arg)
+
+  // 3. If invoking is type supported with options’ mimeType member as its
+  //    argument returns false, throw a NotSupportedError DOMException and abort
+  //    these steps.
+  TypeSupport support = IsTypeSupportedImpl(aOptions.mMimeType);
+  if (support != TypeSupport::Supported) {
+    // This catches also the empty string mimeType when support for any encoders
+    // has been disabled.
+    aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+                          TypeSupportToCString(support, aOptions.mMimeType));
     return nullptr;
   }
 
-  RefPtr<MediaRecorder> object =
-      new MediaRecorder(aSrcAudioNode, aSrcOutput, ownerWindow);
-  object->SetOptions(aInitDict);
-  return object.forget();
-}
+  // 4. Let recorder be a newly constructed MediaRecorder object.
+  RefPtr<MediaRecorder> recorder = new MediaRecorder(ownerWindow);
+
+  // 5. Let recorder have a [[ConstrainedMimeType]] internal slot, initialized
+  //    to the value of options' mimeType member.
+  recorder->mConstrainedMimeType = aOptions.mMimeType;
+
+  // 6. Let recorder have a [[ConstrainedBitsPerSecond]] internal slot,
+  //    initialized to the value of options’ bitsPerSecond member, if it is
+  //    present, otherwise undefined.
+  recorder->mConstrainedBitsPerSecond =
+      aOptions.mBitsPerSecond.WasPassed()
+          ? Some(aOptions.mBitsPerSecond.Value())
+          : Nothing();
+
+  // 7. Initialize recorder’s stream attribute to stream. (make that the
+  //    audioNode and audioNodeOutput equivalents)
+  recorder->mAudioNode = &aAudioNode;
+  recorder->mAudioNodeOutput = aAudioNodeOutput;
+
+  // 8. Initialize recorder’s mimeType attribute to the value of recorder’s
+  //    [[ConstrainedMimeType]] slot.
+  recorder->mMimeType = recorder->mConstrainedMimeType;
+
+  // 9. Initialize recorder’s state attribute to inactive.
+  recorder->mState = RecordingState::Inactive;
 
-void MediaRecorder::SetOptions(const MediaRecorderOptions& aInitDict) {
-  SetMimeType(aInitDict.mMimeType);
-  mAudioBitsPerSecond = aInitDict.mAudioBitsPerSecond.WasPassed()
-                            ? aInitDict.mAudioBitsPerSecond.Value()
-                            : 0;
-  mVideoBitsPerSecond = aInitDict.mVideoBitsPerSecond.WasPassed()
-                            ? aInitDict.mVideoBitsPerSecond.Value()
-                            : 0;
-  mBitsPerSecond = aInitDict.mBitsPerSecond.WasPassed()
-                       ? aInitDict.mBitsPerSecond.Value()
-                       : 0;
-  // We're not handling dynamic changes yet. Eventually we'll handle
-  // setting audio, video and/or total -- and anything that isn't set,
-  // we'll derive. Calculated versions require querying bitrates after
-  // the encoder is Init()ed. This happens only after data is
-  // available and thus requires dynamic changes.
-  //
-  // Until dynamic changes are supported, I prefer to be safe and err
-  // slightly high
-  if (aInitDict.mBitsPerSecond.WasPassed() &&
-      !aInitDict.mVideoBitsPerSecond.WasPassed()) {
-    mVideoBitsPerSecond = mBitsPerSecond;
-  }
-}
+  // 10. Initialize recorder’s videoBitsPerSecond attribute to the value of
+  //     options’ videoBitsPerSecond member, if it is present. Otherwise, choose
+  //     a target value the User Agent deems reasonable for video.
+  recorder->mVideoBitsPerSecond = aOptions.mVideoBitsPerSecond.WasPassed()
+                                      ? aOptions.mVideoBitsPerSecond.Value()
+                                      : DEFAULT_VIDEO_BITRATE_BPS;
+
+  // 11. Initialize recorder’s audioBitsPerSecond attribute to the value of
+  //     options’ audioBitsPerSecond member, if it is present. Otherwise, choose
+  //     a target value the User Agent deems reasonable for audio.
+  recorder->mAudioBitsPerSecond = aOptions.mAudioBitsPerSecond.WasPassed()
+                                      ? aOptions.mAudioBitsPerSecond.Value()
+                                      : DEFAULT_AUDIO_BITRATE_BPS;
 
-static char const* const gWebMVideoEncoderCodecs[4] = {
-    "opus",
-    "vp8",
-    "vp8.0",
-    // no VP9 yet
-    nullptr,
-};
-static char const* const gWebMAudioEncoderCodecs[4] = {
-    "opus",
-    nullptr,
-};
-static char const* const gOggAudioEncoderCodecs[2] = {
-    "opus",
-    // we could support vorbis here too, but don't
-    nullptr,
-};
+  // 12. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
+  //     recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
+  //     values the User Agent deems reasonable for the respective media types,
+  //     such that the sum of videoBitsPerSecond and audioBitsPerSecond is close
+  //     to the value of recorder’s [[ConstrainedBitsPerSecond]] slot.
+  if (recorder->mConstrainedBitsPerSecond) {
+    SelectBitrates(*recorder->mConstrainedBitsPerSecond, 1,
+                   &recorder->mVideoBitsPerSecond, 1,
+                   &recorder->mAudioBitsPerSecond);
+  }
 
-template <class String>
-static bool CodecListContains(char const* const* aCodecs,
-                              const String& aCodec) {
-  for (int32_t i = 0; aCodecs[i]; ++i) {
-    if (aCodec.EqualsASCII(aCodecs[i])) return true;
-  }
-  return false;
+  // 13. Return recorder.
+  return recorder.forget();
 }
 
 /* static */
 bool MediaRecorder::IsTypeSupported(GlobalObject& aGlobal,
                                     const nsAString& aMIMEType) {
-  return IsTypeSupported(aMIMEType);
+  return MediaRecorder::IsTypeSupported(aMIMEType);
 }
 
 /* static */
 bool MediaRecorder::IsTypeSupported(const nsAString& aMIMEType) {
-  char const* const* codeclist = nullptr;
-
-  if (aMIMEType.IsEmpty()) {
-    return true;
-  }
-
-  nsContentTypeParser parser(aMIMEType);
-  nsAutoString mimeType;
-  nsresult rv = parser.GetType(mimeType);
-  if (NS_FAILED(rv)) {
-    return false;
-  }
-
-  // effectively a 'switch (mimeType) {'
-  if (mimeType.EqualsLiteral(AUDIO_OGG)) {
-    if (MediaDecoder::IsOggEnabled() && MediaDecoder::IsOpusEnabled()) {
-      codeclist = gOggAudioEncoderCodecs;
-    }
-  }
-#ifdef MOZ_WEBM_ENCODER
-  else if ((mimeType.EqualsLiteral(VIDEO_WEBM) ||
-            mimeType.EqualsLiteral(AUDIO_WEBM)) &&
-           MediaEncoder::IsWebMEncoderEnabled()) {
-    if (mimeType.EqualsLiteral(AUDIO_WEBM)) {
-      codeclist = gWebMAudioEncoderCodecs;
-    } else {
-      codeclist = gWebMVideoEncoderCodecs;
-    }
-  }
-#endif
-
-  // codecs don't matter if we don't support the container
-  if (!codeclist) {
-    return false;
-  }
-  // now filter on codecs, and if needed rescind support
-  nsAutoString codecstring;
-  rv = parser.GetParameter("codecs", codecstring);
-
-  nsTArray<nsString> codecs;
-  if (!ParseCodecsString(codecstring, codecs)) {
-    return false;
-  }
-  for (const nsString& codec : codecs) {
-    if (!CodecListContains(codeclist, codec)) {
-      // Totally unsupported codec
-      return false;
-    }
-  }
-
-  return true;
+  return IsTypeSupportedImpl(aMIMEType) == TypeSupport::Supported;
 }
 
 nsresult MediaRecorder::CreateAndDispatchBlobEvent(Blob* aBlob) {
   MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
 
   BlobEventInit init;
   init.mBubbles = false;
   init.mCancelable = false;
@@ -1611,19 +2024,36 @@ void MediaRecorder::NotifyOwnerDocumentA
   if (!doc->IsActive() || !doc->IsVisible()) {
     // Stop the session.
     ErrorResult result;
     Stop(result);
     result.SuppressException();
   }
 }
 
-void MediaRecorder::ForceInactive() {
-  LOG(LogLevel::Debug, ("MediaRecorder.ForceInactive %p", this));
+void MediaRecorder::Inactivate() {
+  LOG(LogLevel::Debug, ("MediaRecorder.Inactivate %p", this));
+  // The Inactivate the recorder algorithm given a recorder, is as follows:
+
+  // 1. Set recorder’s mimeType attribute to the value of the
+  //    [[ConstrainedMimeType]] slot.
+  mMimeType = mConstrainedMimeType;
+
+  // 2. Set recorder’s state attribute to inactive.
   mState = RecordingState::Inactive;
+
+  // 3. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
+  //    recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
+  //    values the User Agent deems reasonable for the respective media types,
+  //    such that the sum of videoBitsPerSecond and audioBitsPerSecond is close
+  //    to the value of recorder’s [[ConstrainedBitsPerSecond]] slot.
+  if (mConstrainedBitsPerSecond) {
+    SelectBitrates(*mConstrainedBitsPerSecond, 1, &mVideoBitsPerSecond, 1,
+                   &mAudioBitsPerSecond);
+  }
 }
 
 void MediaRecorder::InitializeDomExceptions() {
   mSecurityDomException = DOMException::Create(NS_ERROR_DOM_SECURITY_ERR);
   mUnknownDomException = DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR);
 }
 
 RefPtr<MediaRecorder::SizeOfPromise> MediaRecorder::SizeOfExcludingThis(
--- a/dom/media/MediaRecorder.h
+++ b/dom/media/MediaRecorder.h
@@ -44,20 +44,17 @@ class DOMException;
  * or RequestData function called by UA.
  */
 
 class MediaRecorder final : public DOMEventTargetHelper,
                             public nsIDocumentActivity {
  public:
   class Session;
 
-  MediaRecorder(DOMMediaStream& aSourceMediaTrack,
-                nsPIDOMWindowInner* aOwnerWindow);
-  MediaRecorder(AudioNode& aSrcAudioNode, uint32_t aSrcOutput,
-                nsPIDOMWindowInner* aOwnerWindow);
+  explicit MediaRecorder(nsPIDOMWindowInner* aOwnerWindow);
 
   static nsTArray<RefPtr<Session>> GetSessions();
 
   // nsWrapperCache
   JSObject* WrapObject(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
   NS_DECL_ISUPPORTS_INHERITED
@@ -74,110 +71,103 @@ class MediaRecorder final : public DOMEv
   void Stop(ErrorResult& aResult);
   // Pause a recording.
   void Pause(ErrorResult& aResult);
   // Resume a paused recording.
   void Resume(ErrorResult& aResult);
   // Extract encoded data Blob from MutableBlobStorage.
   void RequestData(ErrorResult& aResult);
   // Return the The DOMMediaStream passed from UA.
-  DOMMediaStream* Stream() const { return mDOMStream; }
+  DOMMediaStream* Stream() const { return mStream; }
   // Return the current encoding MIME type selected by the MediaEncoder.
   void GetMimeType(nsString& aMimeType);
   // The current state of the MediaRecorder object.
   RecordingState State() const { return mState; }
 
-  static bool IsTypeSupported(GlobalObject& aGlobal, const nsAString& aType);
-  static bool IsTypeSupported(const nsAString& aType);
+  static bool IsTypeSupported(GlobalObject& aGlobal,
+                              const nsAString& aMIMEType);
+  static bool IsTypeSupported(const nsAString& aMIMEType);
 
   // Construct a recorder with a DOM media stream object as its source.
   static already_AddRefed<MediaRecorder> Constructor(
       const GlobalObject& aGlobal, DOMMediaStream& aStream,
-      const MediaRecorderOptions& aInitDict, ErrorResult& aRv);
+      const MediaRecorderOptions& aOptions, ErrorResult& aRv);
   // Construct a recorder with a Web Audio destination node as its source.
   static already_AddRefed<MediaRecorder> Constructor(
-      const GlobalObject& aGlobal, AudioNode& aSrcAudioNode,
-      uint32_t aSrcOutput, const MediaRecorderOptions& aInitDict,
+      const GlobalObject& aGlobal, AudioNode& aAudioNode,
+      uint32_t aAudioNodeOutput, const MediaRecorderOptions& aOptions,
       ErrorResult& aRv);
 
   /*
    * Measure the size of the buffer, and heap memory in bytes occupied by
    * mAudioEncoder and mVideoEncoder.
    */
   typedef MozPromise<size_t, size_t, true> SizeOfPromise;
   RefPtr<SizeOfPromise> SizeOfExcludingThis(
       mozilla::MallocSizeOf aMallocSizeOf);
   // EventHandler
   IMPL_EVENT_HANDLER(start)
   IMPL_EVENT_HANDLER(stop)
   IMPL_EVENT_HANDLER(dataavailable)
   IMPL_EVENT_HANDLER(pause)
   IMPL_EVENT_HANDLER(resume)
   IMPL_EVENT_HANDLER(error)
-  IMPL_EVENT_HANDLER(warning)
 
   NS_DECL_NSIDOCUMENTACTIVITY
 
-  uint32_t GetAudioBitrate() { return mAudioBitsPerSecond; }
-  uint32_t GetVideoBitrate() { return mVideoBitsPerSecond; }
-  uint32_t GetBitrate() { return mBitsPerSecond; }
+  uint32_t AudioBitsPerSecond() const { return mAudioBitsPerSecond; }
+  uint32_t VideoBitsPerSecond() const { return mVideoBitsPerSecond; }
 
  protected:
   virtual ~MediaRecorder();
 
   MediaRecorder& operator=(const MediaRecorder& x) = delete;
   // Create dataavailable event with Blob data and it runs in main thread
   nsresult CreateAndDispatchBlobEvent(Blob* aBlob);
   // Creating a simple event to notify UA simple event.
   void DispatchSimpleEvent(const nsAString& aStr);
   // Creating a error event with message.
   void NotifyError(nsresult aRv);
-  // Set encoded MIME type.
-  void SetMimeType(const nsString& aMimeType);
-  void SetOptions(const MediaRecorderOptions& aInitDict);
 
   MediaRecorder(const MediaRecorder& x) = delete;  // prevent bad usage
   // Remove session pointer.
   void RemoveSession(Session* aSession);
   // Create DOMExceptions capturing the JS stack for async errors. These are
   // created ahead of time rather than on demand when firing an error as the JS
   // stack of the operation that started the async behavior will not be
   // available at the time the error event is fired. Note, depending on when
   // this is called there may not be a JS stack to capture.
   void InitializeDomExceptions();
-  // Set the recorder state to inactive. This is needed to handle error states
-  // in the recorder where state must transition to inactive before full
-  // stoppage can be reached.
-  void ForceInactive();
+  // Runs the "Inactivate the recorder" algorithm.
+  void Inactivate();
   // Stop the recorder and its internal session. This should be used by
   // sessions that are in the process of being destroyed.
   void StopForSessionDestruction();
   // DOM wrapper for source media stream. Will be null when input is audio node.
-  RefPtr<DOMMediaStream> mDOMStream;
+  RefPtr<DOMMediaStream> mStream;
   // Source audio node. Will be null when input is a media stream.
   RefPtr<AudioNode> mAudioNode;
   // Source audio node's output index. Will be zero when input is a media
   // stream.
-  const uint32_t mAudioNodeOutput;
+  uint32_t mAudioNodeOutput = 0;
 
   // The current state of the MediaRecorder object.
-  RecordingState mState;
+  RecordingState mState = RecordingState::Inactive;
   // Hold the sessions reference and clean it when the DestroyRunnable for a
   // session is running.
   nsTArray<RefPtr<Session>> mSessions;
 
   RefPtr<Document> mDocument;
 
-  // It specifies the container format as well as the audio and video capture
-  // formats.
   nsString mMimeType;
+  nsString mConstrainedMimeType;
 
-  uint32_t mAudioBitsPerSecond;
-  uint32_t mVideoBitsPerSecond;
-  uint32_t mBitsPerSecond;
+  uint32_t mAudioBitsPerSecond = 0;
+  uint32_t mVideoBitsPerSecond = 0;
+  Maybe<uint32_t> mConstrainedBitsPerSecond;
 
   // DOMExceptions that are created early and possibly thrown in NotifyError.
   // Creating them early allows us to capture the JS stack for which cannot be
   // done at the time the error event is fired.
   RefPtr<DOMException> mSecurityDomException;
   RefPtr<DOMException> mUnknownDomException;
 
  private:
--- a/dom/media/encoder/MediaEncoder.cpp
+++ b/dom/media/encoder/MediaEncoder.cpp
@@ -547,51 +547,43 @@ void MediaEncoder::ConnectMediaStreamTra
   EnsureGraphTrackFrom(aTrack->GetTrack());
 
   if (AudioStreamTrack* audio = aTrack->AsAudioStreamTrack()) {
     if (!mAudioEncoder) {
       // No audio encoder for this audio track. It could be disabled.
       LOG(LogLevel::Warning, ("Cannot connect to audio track - no encoder"));
       return;
     }
-    if (mAudioTrack) {
-      MOZ_ASSERT(false, "Only one audio track supported.");
-      return;
-    }
-    if (!mAudioListener) {
-      MOZ_ASSERT(false, "No audio listener for this audio track");
-      return;
-    }
+
+    MOZ_ASSERT(!mAudioTrack, "Only one audio track supported.");
+    MOZ_ASSERT(mAudioListener, "No audio listener for this audio track");
 
     LOG(LogLevel::Info, ("Connected to audio track %p", aTrack));
+
     mAudioTrack = audio;
     // With full duplex we don't risk having audio come in late to the MTG
     // so we won't need a direct listener.
     const bool enableDirectListener =
         !Preferences::GetBool("media.navigator.audio.full_duplex", false);
     if (enableDirectListener) {
       audio->AddDirectListener(mAudioListener);
     }
     audio->AddListener(mAudioListener);
   } else if (VideoStreamTrack* video = aTrack->AsVideoStreamTrack()) {
     if (!mVideoEncoder) {
       // No video encoder for this video track. It could be disabled.
       LOG(LogLevel::Warning, ("Cannot connect to video track - no encoder"));
       return;
     }
-    if (mVideoTrack) {
-      MOZ_ASSERT(false, "Only one video track supported.");
-      return;
-    }
-    if (!mVideoListener) {
-      MOZ_ASSERT(false, "No video listener for this audio track");
-      return;
-    }
+
+    MOZ_ASSERT(!mVideoTrack, "Only one video track supported.");
+    MOZ_ASSERT(mVideoListener, "No video listener for this video track");
 
     LOG(LogLevel::Info, ("Connected to video track %p", aTrack));
+
     mVideoTrack = video;
     video->AddDirectListener(mVideoListener);
     video->AddListener(mVideoListener);
   } else {
     MOZ_ASSERT(false, "Unknown track type");
   }
 }
 
@@ -633,123 +625,76 @@ already_AddRefed<MediaEncoder> MediaEnco
     TrackRate aTrackRate) {
   AUTO_PROFILER_LABEL("MediaEncoder::CreateEncoder", OTHER);
 
   UniquePtr<ContainerWriter> writer;
   RefPtr<AudioTrackEncoder> audioEncoder;
   RefPtr<VideoTrackEncoder> videoEncoder;
   auto driftCompensator =
       MakeRefPtr<DriftCompensator>(aEncoderThread, aTrackRate);
-  nsString mimeType;
 
-  if (!aTrackTypes) {
-    MOZ_ASSERT(false);
-    LOG(LogLevel::Error, ("No TrackTypes"));
+  Maybe<MediaContainerType> mimeType = MakeMediaContainerType(aMIMEType);
+  if (!mimeType) {
     return nullptr;
   }
-#ifdef MOZ_WEBM_ENCODER
-  else if (MediaEncoder::IsWebMEncoderEnabled() &&
-           aMIMEType.EqualsLiteral(VIDEO_WEBM)) {
-    if (aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK &&
-        MediaDecoder::IsOpusEnabled()) {
+
+  for (const auto& codec : mimeType->ExtendedType().Codecs().Range()) {
+    if (codec.EqualsLiteral("opus")) {
+      MOZ_ASSERT(!audioEncoder);
       audioEncoder = MakeAndAddRef<OpusTrackEncoder>(aTrackRate);
-    }
-    if (aTrackTypes & ContainerWriter::CREATE_VIDEO_TRACK) {
-      if (Preferences::GetBool("media.recorder.video.frame_drops", true)) {
-        videoEncoder = MakeAndAddRef<VP8TrackEncoder>(
-            driftCompensator, aTrackRate, FrameDroppingMode::ALLOW);
-      } else {
-        videoEncoder = MakeAndAddRef<VP8TrackEncoder>(
-            driftCompensator, aTrackRate, FrameDroppingMode::DISALLOW);
-      }
-    }
-    writer = MakeUnique<WebMWriter>();
-    mimeType = NS_LITERAL_STRING(VIDEO_WEBM);
-  } else if (MediaEncoder::IsWebMEncoderEnabled() &&
-             aMIMEType.EqualsLiteral(AUDIO_WEBM) &&
-             aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK) {
-    if (aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK &&
-        MediaDecoder::IsOpusEnabled()) {
-      audioEncoder = MakeAndAddRef<OpusTrackEncoder>(aTrackRate);
-    }
-    if (aTrackTypes & ContainerWriter::CREATE_VIDEO_TRACK) {
+    } else if (codec.EqualsLiteral("vp8") || codec.EqualsLiteral("vp8.0")) {
+      MOZ_ASSERT(!videoEncoder);
       if (Preferences::GetBool("media.recorder.video.frame_drops", true)) {
         videoEncoder = MakeAndAddRef<VP8TrackEncoder>(
             driftCompensator, aTrackRate, FrameDroppingMode::ALLOW);
       } else {
         videoEncoder = MakeAndAddRef<VP8TrackEncoder>(
             driftCompensator, aTrackRate, FrameDroppingMode::DISALLOW);
       }
-      mimeType = NS_LITERAL_STRING(VIDEO_WEBM);
     } else {
-      mimeType = NS_LITERAL_STRING(AUDIO_WEBM);
-    }
-    writer = MakeUnique<WebMWriter>();
-  }
-#endif  // MOZ_WEBM_ENCODER
-  else if (MediaDecoder::IsOggEnabled() && MediaDecoder::IsOpusEnabled() &&
-           aMIMEType.EqualsLiteral(AUDIO_OGG) &&
-           aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK) {
-    writer = MakeUnique<OggWriter>();
-    audioEncoder = MakeAndAddRef<OpusTrackEncoder>(aTrackRate);
-    mimeType = NS_LITERAL_STRING(AUDIO_OGG);
-  }
-#ifdef MOZ_WEBM_ENCODER
-  else if (MediaEncoder::IsWebMEncoderEnabled() &&
-           (aTrackTypes & ContainerWriter::CREATE_VIDEO_TRACK ||
-            !MediaDecoder::IsOggEnabled())) {
-    if (aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK &&
-        MediaDecoder::IsOpusEnabled()) {
-      audioEncoder = MakeAndAddRef<OpusTrackEncoder>(aTrackRate);
+      MOZ_CRASH("Unknown codec");
     }
-    if (aTrackTypes & ContainerWriter::CREATE_VIDEO_TRACK) {
-      if (Preferences::GetBool("media.recorder.video.frame_drops", true)) {
-        videoEncoder = MakeAndAddRef<VP8TrackEncoder>(
-            driftCompensator, aTrackRate, FrameDroppingMode::ALLOW);
-      } else {
-        videoEncoder = MakeAndAddRef<VP8TrackEncoder>(
-            driftCompensator, aTrackRate, FrameDroppingMode::DISALLOW);
-      }
-    }
-    writer = MakeUnique<WebMWriter>();
-    mimeType = NS_LITERAL_STRING(VIDEO_WEBM);
   }
+
+  if (mimeType->Type() == MEDIAMIMETYPE(VIDEO_WEBM) ||
+      mimeType->Type() == MEDIAMIMETYPE(AUDIO_WEBM)) {
+#ifdef MOZ_WEBM_ENCODER
+    MOZ_ASSERT_IF(mimeType->Type() == MEDIAMIMETYPE(AUDIO_WEBM), !videoEncoder);
+    writer = MakeUnique<WebMWriter>();
+#else
+    MOZ_CRASH("Webm cannot be selected if not supported");
 #endif  // MOZ_WEBM_ENCODER
-  else if (MediaDecoder::IsOggEnabled() && MediaDecoder::IsOpusEnabled() &&
-           aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK) {
+  } else if (mimeType->Type() == MEDIAMIMETYPE(AUDIO_OGG)) {
+    MOZ_ASSERT(audioEncoder);
+    MOZ_ASSERT(!videoEncoder);
     writer = MakeUnique<OggWriter>();
-    audioEncoder = MakeAndAddRef<OpusTrackEncoder>(aTrackRate);
-    mimeType = NS_LITERAL_STRING(AUDIO_OGG);
-  } else {
-    LOG(LogLevel::Error,
-        ("Can not find any encoder to record this media stream"));
-    return nullptr;
   }
+  NS_ENSURE_TRUE(writer, nullptr);
 
   LOG(LogLevel::Info,
       ("Create encoder result:a[%p](%u bps) v[%p](%u bps) w[%p] mimeType = "
        "%s.",
        audioEncoder.get(), aAudioBitrate, videoEncoder.get(), aVideoBitrate,
-       writer.get(), NS_ConvertUTF16toUTF8(mimeType).get()));
+       writer.get(), NS_ConvertUTF16toUTF8(aMIMEType).get()));
 
   if (audioEncoder) {
     audioEncoder->SetWorkerThread(aEncoderThread);
     if (aAudioBitrate != 0) {
       audioEncoder->SetBitrate(aAudioBitrate);
     }
   }
   if (videoEncoder) {
     videoEncoder->SetWorkerThread(aEncoderThread);
     if (aVideoBitrate != 0) {
       videoEncoder->SetBitrate(aVideoBitrate);
     }
   }
   return MakeAndAddRef<MediaEncoder>(
       aEncoderThread, std::move(driftCompensator), std::move(writer),
-      audioEncoder, videoEncoder, aTrackRate, mimeType);
+      audioEncoder, videoEncoder, aTrackRate, aMIMEType);
 }
 
 nsresult MediaEncoder::GetEncodedData(
     nsTArray<nsTArray<uint8_t>>* aOutputBufs) {
   AUTO_PROFILER_LABEL("MediaEncoder::GetEncodedData", OTHER);
 
   MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
   MOZ_ASSERT(mInitialized);
@@ -939,21 +884,23 @@ void MediaEncoder::Stop() {
     RemoveMediaStreamTrack(mAudioTrack);
   }
 
   if (mVideoTrack) {
     RemoveMediaStreamTrack(mVideoTrack);
   }
 }
 
+bool MediaEncoder::IsWebMEncoderEnabled() {
 #ifdef MOZ_WEBM_ENCODER
-bool MediaEncoder::IsWebMEncoderEnabled() {
   return StaticPrefs::media_encoder_webm_enabled();
+#else
+  return false;
+#endif
 }
-#endif
 
 const nsString& MediaEncoder::MimeType() const {
   MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
   return mMIMEType;
 }
 
 void MediaEncoder::NotifyInitialized() {
   MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
@@ -1016,23 +963,23 @@ size_t MediaEncoder::SizeOfExcludingThis
     size += mAudioEncoder->SizeOfExcludingThis(aMallocSizeOf);
   }
   if (mVideoEncoder) {
     size += mVideoEncoder->SizeOfExcludingThis(aMallocSizeOf);
   }
   return size;
 }
 
-void MediaEncoder::SetVideoKeyFrameInterval(int32_t aVideoKeyFrameInterval) {
+void MediaEncoder::SetVideoKeyFrameInterval(uint32_t aVideoKeyFrameInterval) {
   if (!mVideoEncoder) {
     return;
   }
 
   MOZ_ASSERT(mEncoderThread);
-  nsresult rv = mEncoderThread->Dispatch(NewRunnableMethod<int32_t>(
+  nsresult rv = mEncoderThread->Dispatch(NewRunnableMethod<uint32_t>(
       "mozilla::VideoTrackEncoder::SetKeyFrameInterval", mVideoEncoder,
       &VideoTrackEncoder::SetKeyFrameInterval, aVideoKeyFrameInterval));
   MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
   Unused << rv;
 }
 
 }  // namespace mozilla
 
--- a/dom/media/encoder/MediaEncoder.h
+++ b/dom/media/encoder/MediaEncoder.h
@@ -176,19 +176,17 @@ class MediaEncoder {
   /**
    * Cancels the encoding and shuts down the encoder using Shutdown().
    * Listeners are not notified of the shutdown.
    */
   void Cancel();
 
   bool HasError();
 
-#ifdef MOZ_WEBM_ENCODER
   static bool IsWebMEncoderEnabled();
-#endif
 
   const nsString& MimeType() const;
 
   /**
    * Notifies listeners that this MediaEncoder has been initialized.
    */
   void NotifyInitialized();
 
@@ -215,17 +213,17 @@ class MediaEncoder {
    * Measure the size of the buffer, and heap memory in bytes occupied by
    * mAudioEncoder and mVideoEncoder.
    */
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
   /**
    * Set desired video keyframe interval defined in milliseconds.
    */
-  void SetVideoKeyFrameInterval(int32_t aVideoKeyFrameInterval);
+  void SetVideoKeyFrameInterval(uint32_t aVideoKeyFrameInterval);
 
  protected:
   ~MediaEncoder();
 
  private:
   /**
    * Sets mGraphTrack if not already set, using a new stream from aTrack's
    * graph.
--- a/dom/media/encoder/TrackEncoder.cpp
+++ b/dom/media/encoder/TrackEncoder.cpp
@@ -24,17 +24,17 @@ static const int DEFAULT_SAMPLING_RATE =
 static const int DEFAULT_FRAME_WIDTH = 640;
 static const int DEFAULT_FRAME_HEIGHT = 480;
 // 1 second threshold if the audio encoder cannot be initialized.
 static const int AUDIO_INIT_FAILED_DURATION = 1;
 // 30 second threshold if the video encoder cannot be initialized.
 static const int VIDEO_INIT_FAILED_DURATION = 30;
 // A maximal key frame interval allowed to set.
 // Longer values will be shorten to this value.
-static const int DEFAULT_KEYFRAME_INTERVAL_MS = 1000;
+static const unsigned int DEFAULT_KEYFRAME_INTERVAL_MS = 1000;
 
 TrackEncoder::TrackEncoder(TrackRate aTrackRate)
     : mEncodingComplete(false),
       mEosSetInEncoder(false),
       mInitialized(false),
       mEndOfStream(false),
       mCanceled(false),
       mInitCounter(0),
@@ -748,17 +748,17 @@ void VideoTrackEncoder::AdvanceCurrentTi
 
 size_t VideoTrackEncoder::SizeOfExcludingThis(
     mozilla::MallocSizeOf aMallocSizeOf) {
   MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
   return mIncomingBuffer.SizeOfExcludingThis(aMallocSizeOf) +
          mOutgoingBuffer.SizeOfExcludingThis(aMallocSizeOf);
 }
 
-void VideoTrackEncoder::SetKeyFrameInterval(int32_t aKeyFrameInterval) {
+void VideoTrackEncoder::SetKeyFrameInterval(uint32_t aKeyFrameInterval) {
   MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
   if (aKeyFrameInterval == 0) {
     mKeyFrameInterval = DEFAULT_KEYFRAME_INTERVAL_MS;
     return;
   }
   mKeyFrameInterval = std::min(aKeyFrameInterval, DEFAULT_KEYFRAME_INTERVAL_MS);
 }
 
--- a/dom/media/encoder/TrackEncoder.h
+++ b/dom/media/encoder/TrackEncoder.h
@@ -415,17 +415,17 @@ class VideoTrackEncoder : public TrackEn
    * Dispatched from MediaTrackGraph when it has run an iteration so we can
    * hand more data to the encoder.
    */
   void AdvanceCurrentTime(const TimeStamp& aTime);
 
   /**
    * Set desired keyframe interval defined in milliseconds.
    */
-  void SetKeyFrameInterval(int32_t aKeyFrameInterval);
+  void SetKeyFrameInterval(uint32_t aKeyFrameInterval);
 
  protected:
   /**
    * Initialize the video encoder. In order to collect the value of width and
    * height of source frames, this initialization is delayed until we have
    * received the first valid video frame from MediaTrackGraph.
    * Listeners will be notified after it has been successfully initialized.
    */
@@ -513,17 +513,17 @@ class VideoTrackEncoder : public TrackEn
    * ALLOW to drop frames under load.
    * DISALLOW to encode all frames, mainly for testing.
    */
   FrameDroppingMode mFrameDroppingMode;
 
   /**
    * The desired keyframe interval defined in milliseconds.
    */
-  int32_t mKeyFrameInterval;
+  uint32_t mKeyFrameInterval;
 
   /**
    * True if the video MediaTrackTrack this VideoTrackEncoder is attached to is
    * currently enabled. While false, we encode all frames as black.
    */
   bool mEnabled;
 };
 
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -906,17 +906,18 @@ tags=mtg
 skip-if = toolkit == 'android' # bug 1297432, android(bug 1232305)
 tags=mtg
 [test_mediarecorder_creation.html]
 tags=mtg capturestream
 [test_mediarecorder_creation_fail.html]
 tags=mtg
 [test_mediarecorder_fires_start_event_once_when_erroring.html]
 tags=mtg
-[test_mediarecorder_getencodeddata.html]
+[test_mediarecorder_onerror_pause.html]
+scheme=https
 tags=mtg
 [test_mediarecorder_pause_resume_video.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 [test_mediarecorder_playback_can_repeat.html]
 tags=mtg
 [test_mediarecorder_principals.html]
 skip-if = toolkit == 'android' || (os == 'win' && os_version == '10.0' && webrender) # android(bug 1232305), Bug 1453375
 tags=mtg
@@ -960,19 +961,16 @@ tags=mtg
 [test_mediarecorder_record_timeslice.html]
 tags=mtg capturestream
 [test_mediarecorder_reload_crash.html]
 tags=mtg capturestream
 [test_mediarecorder_state_transition.html]
 tags=mtg capturestream
 [test_mediarecorder_state_event_order.html]
 tags=mtg capturestream
-[test_mediarecorder_unsupported_src.html]
-scheme=https
-tags=mtg
 [test_mediarecorder_webm_support.html]
 tags=mtg
 [test_mediarecorder_record_getdata_afterstart.html]
 tags=mtg capturestream
 [test_mediatrack_consuming_mediaresource.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 [test_mediatrack_consuming_mediastream.html]
 scheme=https
--- a/dom/media/test/test_mediarecorder_creation_fail.html
+++ b/dom/media/test/test_mediarecorder_creation_fail.html
@@ -4,71 +4,47 @@
   <title>Test MediaRecorder Record with media.ogg.enabled = false</title>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <script type="text/javascript" src="manifest.js"></script>
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
-
-function startTest() {
-  // the expect sequence should be
-  // 1. onerror
-  // 2. ondataavailable
-  // 3. onstop
-  var callbackStep = 0;
-  var stream = new AudioContext().createMediaStreamDestination().stream;
-  var mediaRecorder = new MediaRecorder(stream);
-
-  mediaRecorder.onerror = function (e) {
-    is(callbackStep, 0, 'should fired onstop callback');
-    is(e.error.name, 'UnknownError', 'error name should be UnknownError');
-    ok(e.error.stack.includes('test_mediarecorder_creation_fail.html'),
-      'Events fired from onerror should include an error with a stack trace indicating ' +
-      'an error in this test');
-    is(mediaRecorder.mimeType, '', 'mimetype should be empty');
-    is(mediaRecorder.state, 'inactive', 'state is inactive');
-    info('onerror callback fired');
-    callbackStep = 1;
-  };
-
-  mediaRecorder.onwarning = function () {
-    ok(false, 'Unexpected onwarning callback fired');
-  };
-
-  mediaRecorder.onstop = function () {
-    info('onstop callback fired');
-    is(mediaRecorder.state, 'inactive', 'state should be inactive');
-    is(callbackStep, 2, 'should fired onstop callback');
-    SimpleTest.finish();
-  };
-
-  // This handler fires every 250ms to generate a blob.
-  mediaRecorder.ondataavailable = function (evt) {
-    info('ondataavailable callback fired');
-    is(callbackStep, 1, 'should fired ondataavailable callback');
-    is(evt.data.size, 0, 'data size should be zero');
-    ok(evt instanceof BlobEvent,
-       'Events fired from ondataavailable should be BlobEvent');
-    is(evt.data.type, '', 'encoder start fail, blob mimeType should be empty');
-    callbackStep = 2;
-  };
-
-  // Start recording
-  mediaRecorder.start(250);
-  is(mediaRecorder.state, 'recording', 'Media recorder should be recording');
-  is(mediaRecorder.stream, stream,
-     'Media recorder stream = element stream at the start of recording');
+function testThrows(stream, options) {
+  try {
+    new MediaRecorder(stream, options);
+    return false;
+  } catch(e) {
+    return e.name;
+  }
 }
-
-SimpleTest.waitForExplicitFinish();
-SpecialPowers.pushPrefEnv({
-    "set": [
+(async () => {
+  SimpleTest.waitForExplicitFinish();
+  await SpecialPowers.pushPrefEnv({set: [
       ["media.ogg.enabled", false],
       ["media.encoder.webm.enabled", false],
-    ],
-  }, startTest);
-
+    ]});
+  const stream = new AudioContext().createMediaStreamDestination().stream;
+  is(testThrows(stream, {mimeType: "audio/ogg"}), "NotSupportedError",
+    "Creating an ogg recorder without ogg support throws");
+  is(testThrows(stream, {mimeType: "audio/webm"}), "NotSupportedError",
+    "Creating a webm recorder without webm support throws");
+  is(testThrows(stream, {mimeType: "video/webm"}), "NotSupportedError",
+    "Creating a webm recorder without webm support throws");
+  is(testThrows(stream, {mimeType: "apa/bepa"}), "NotSupportedError",
+    "Creating a recorder for a bogus mime type throws");
+  is(testThrows(stream, {}), false,
+    "Creating a default recorder never throws, even without container support");
+  const recorder = new MediaRecorder(stream);
+  try {
+    recorder.start();
+    ok(false, "Starting a recorder without container support should throw");
+  } catch(e) {
+    is(e.name, "NotSupportedError",
+      "Starting a recorder without container support throws");
+  }
+  SimpleTest.finish();
+})();
 </script>
 </pre>
 </body>
 </html>
deleted file mode 100644
--- a/dom/media/test/test_mediarecorder_getencodeddata.html
+++ /dev/null
@@ -1,83 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Bug 957452 Test GetEncodedData problem on asan build</title>
-  <script src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-SimpleTest.waitForExplicitFinish();
-SimpleTest.requestFlakyTimeout("untriaged");
-SpecialPowers.pushPrefEnv({
-    "set": [
-      ["media.ogg.enabled", false],
-      ["media.encoder.webm.enabled", false],
-    ],
-  },
-  function () {
-    var ac = new window.AudioContext();
-    var dest = ac.createMediaStreamDestination();
-    var stream = dest.stream;
-    var onErrorFired = false;
-    var expectedMimeType = '';
-    var ondataavailableFired = false;
-    setTimeout(function() {
-      var mediaRecorder = new MediaRecorder(stream);
-      mediaRecorder.onstop = function(e) {
-        is(e.target.state, 'inactive',
-           'Media recorder is inactive after being stopped');
-        ok(onErrorFired, 'onStop after onError');
-        ok(ondataavailableFired, 'ondataavailableFired');
-
-        //Apparently, as soon as the document is unloading, mediaRecorder.ondataavailable
-        //fires again, so set it to null to avoid failures
-        mediaRecorder.ondataavailable = null;
-        SimpleTest.finish();
-      };
-      mediaRecorder.ondataavailable = function(evt) {
-        ondataavailableFired = true;
-        ok(evt instanceof BlobEvent,
-           'Events fired from ondataavailable should be BlobEvent');
-        is(evt.type, 'dataavailable',
-           'Event type should dataavailable');
-        is(evt.data.size, 0,
-           'Blob data size received is equal to zero');
-        is(evt.data.type, expectedMimeType,
-           'Blob data received should have type = ' + expectedMimeType);
-        is(evt.target.mimeType, expectedMimeType,
-           'Mime type in ondataavailable = ' + expectedMimeType);
-      };
-      mediaRecorder.onerror = function(evt) {
-        is(evt.target.state, 'inactive',
-           'Media recorder is inactive after firing error');
-        ok(evt instanceof MediaRecorderErrorEvent,
-          'Events fired from onerror should be MediaRecorderErrorEvent');
-        is(evt.type, 'error',
-           'Event type is error');
-        ok(evt.error instanceof DOMException,
-          'Events fired from onerror should have a DOMException in their error member');
-        is(evt.error.name, 'UnknownError', 'Error name should be UnknownError.');
-        is(evt.error.message, 'The operation failed for an unknown transient reason');
-        ok(evt.error.stack.includes('test_mediarecorder_getencodeddata.html'),
-          'Events fired from onerror should include an error with a stack trace indicating ' +
-          'an error in this test');
-        try {
-          mediaRecorder.requestData();
-          ok(false, 'requestdata should fire an exception if called on an inactive recorder');
-        } catch (e) {
-          ok(e instanceof DOMException, 'requestdata should fire an exception ' +
-            'if called on an inactive recorder');
-          is(e.name, 'InvalidStateError', 'Exception name should be InvalidStateError');
-        }
-        onErrorFired = true;
-      };
-      mediaRecorder.start(0);
-    }, 100);
-  }
-);
-</script>
-</pre>
-</body>
-</html>
rename from dom/media/test/test_mediarecorder_unsupported_src.html
rename to dom/media/test/test_mediarecorder_onerror_pause.html
--- a/dom/media/test/test_mediarecorder_unsupported_src.html
+++ b/dom/media/test/test_mediarecorder_onerror_pause.html
@@ -6,99 +6,100 @@
   <script type="text/javascript" src="manifest.js"></script>
   <script type="text/javascript" src="gUM_support.js"></script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=957439">Mozilla Bug 957439</a>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
+function unexpected(e) {
+  ok(false, `Got unexpected ${e.type} event`);
+}
+
 
 async function startTest() {
   // also do general checks on mimetype support for audio-only
-  ok(MediaRecorder.isTypeSupported("audio/ogg"), 'Should support audio/ogg');
-  ok(MediaRecorder.isTypeSupported('audio/ogg; codecs="opus"'), 'Should support audio/ogg+opus');
-  ok(!MediaRecorder.isTypeSupported('audio/ogg; codecs="foobar"'), 'Should not support audio/ogg + unknown_codec');
-  ok(!MediaRecorder.isTypeSupported("video/webm"), 'Should not support video/webm');
-  ok(!MediaRecorder.isTypeSupported("video/mp4"), 'Should not support video/mp4');
+  ok(MediaRecorder.isTypeSupported("audio/ogg"),
+    'Should support audio/ogg');
+  ok(MediaRecorder.isTypeSupported('audio/ogg; codecs=opus'),
+    'Should support audio/ogg+opus');
+  ok(!MediaRecorder.isTypeSupported('audio/ogg; codecs=foobar'),
+    'Should not support audio/ogg + unknown_codec');
+  ok(MediaRecorder.isTypeSupported("video/webm"),
+    'Should support video/webm');
+  ok(!MediaRecorder.isTypeSupported("video/mp4"),
+    'Should not support video/mp4');
 
   try {
     await setupGetUserMediaTestPrefs();
-    let stream = await navigator.mediaDevices.getUserMedia({audio: false, video: true});
+    const expectedMimeType = 'video/webm; codecs="vp8, opus"';
+    const stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
+    const [audioTrack] = stream.getAudioTracks();
 
-    // Expected callback sequence should be:
-    // 1. onerror (from start)
-    // 2. ondataavailable
-    // 3. onstop
-    let callbackStep = 0;
-    let mediaRecorder = new MediaRecorder(stream);
-
+    // Expected event sequence should be:
+    // 1. start
+    // 2. error (from removed track)
+    // 3. dataavailable
+    // 4. stop
+    const mediaRecorder = new MediaRecorder(stream);
     is(mediaRecorder.stream, stream, 'Stream should be provided on creation');
 
-    mediaRecorder.onerror = function (e) {
-      callbackStep++;
-      info('onerror callback fired');
-      if (callbackStep == 1) {
-        try {
-          mediaRecorder.pause();
-          ok(false, 'pause should fire an exception if called on an inactive recorder');
-        } catch(e) {
-          ok(e instanceof DOMException, 'pause should fire an exception ' +
-            'if called on an inactive recorder');
-          is(e.name, 'InvalidStateError', 'Exception name should be InvalidStateError');
-        }
-      }
-      ok(callbackStep == 1, 'onerror callback should handle be the 1st event fired');
-      is(e.error.name, 'UnknownError', 'Error name should be UnknownError.');
-      ok(e.error.stack.includes('test_mediarecorder_unsupported_src.html'),
-        'Events fired from onerror should include an error with a stack trace indicating ' +
-        'an error in this test');
-      is(mediaRecorder.mimeType, '', 'mimetype should be empty');
-      is(mediaRecorder.state, 'inactive', 'state is inactive');
-    };
+    mediaRecorder.onstart = unexpected;
+    mediaRecorder.onerror = unexpected;
+    mediaRecorder.ondataavailable = unexpected;
+    mediaRecorder.onstop = unexpected;
+
+    mediaRecorder.start();
+    is(mediaRecorder.state, 'recording', 'state should be recording');
+    is(mediaRecorder.mimeType, expectedMimeType, 'mimetype should be set');
+
+    await new Promise(r => mediaRecorder.onstart = r);
+    mediaRecorder.onstart = unexpected;
+    ok(true, 'start event fired');
 
-    mediaRecorder.onwarning = function () {
-      ok(false, 'Unexpected onwarning callback fired.');
-    };
+    // Trigger an error
+    stream.removeTrack(audioTrack);
 
-    mediaRecorder.ondataavailable = function (evt) {
-      callbackStep++;
-      info('ondataavailable callback fired');
-      is(callbackStep, 2, 'ondataavailable callback should handle the 2nd event fired');
-      is(evt.data.size, 0, 'data size should be zero');
-      ok(evt instanceof BlobEvent,
-          'Events fired from ondataavailable should be BlobEvent');
-      is(evt.data.type, '', 'encoder start fail, blob miemType should be empty');
-    };
-
-    mediaRecorder.onstop = function() {
-      callbackStep++;
-      info('onstop callback fired');
-      is(mediaRecorder.state, 'inactive', 'state should be inactive');
-      is(callbackStep, 3, 'onstop callback should handle the 3rd event fired');
-      SimpleTest.finish();
-    };
+    const err = await new Promise(r => mediaRecorder.onerror = r);
+    mediaRecorder.onerror = unexpected;
+    ok(true, 'error event fired');
+    is(err.error.name, 'UnknownError', 'Error name should be UnknownError.');
+    ok(err.error.stack.includes('test_mediarecorder_onerror_pause.html'),
+      'Events fired from onerror should include an error with a stack trace indicating ' +
+      'an error in this test');
+    is(mediaRecorder.mimeType, '', 'mimetype should be unset');
+    is(mediaRecorder.state, 'inactive', 'state is inactive');
 
     try {
-      mediaRecorder.start();
+      mediaRecorder.pause();
+      ok(false, 'pause should fire an exception if called on an inactive recorder');
     } catch(e) {
-      ok(false, 'Should not get exception in start call.');
+      ok(e instanceof DOMException, 'pause should fire an exception ' +
+        'if called on an inactive recorder');
+      is(e.name, 'InvalidStateError', 'Exception name should be InvalidStateError');
     }
+
+    const evt = await new Promise(r => mediaRecorder.ondataavailable = r);
+    mediaRecorder.ondataavailable = unexpected;
+    ok(true, 'dataavailable event fired');
+    isnot(evt.data.size, 0, 'data size should not be zero');
+    ok(evt instanceof BlobEvent,
+        'Events fired from ondataavailable should be BlobEvent');
+    is(evt.data.type, expectedMimeType, 'blob mimeType is set');
+
+    await new Promise(r => mediaRecorder.onstop = r);
+    mediaRecorder.onstop = unexpected;
+    ok(true, 'onstop event fired');
+    is(mediaRecorder.state, 'inactive', 'state should be inactive');
   } catch (err) {
-    ok(false, 'Unexpected error fired with: ' + err);
+    ok(false, `Unexpected error fired with: ${err}`);
+  } finally {
     SimpleTest.finish();
   }
 }
 
 SimpleTest.waitForExplicitFinish();
-
-// In order to generate an "unsupported stream", pref off video encoding to
-// make the platform support audio encoding only.
-SpecialPowers.pushPrefEnv(
-  {
-    "set": [
-      ["media.encoder.webm.enabled", false],
-    ]
-  }, startTest);
+window.onload = startTest;
 
 </script>
 </head>
 </html>
--- a/dom/media/test/test_mediarecorder_record_4ch_audiocontext.html
+++ b/dom/media/test/test_mediarecorder_record_4ch_audiocontext.html
@@ -21,17 +21,17 @@ function startTest() {
 
   var source = context.createBufferSource();
   source.buffer = buffer;
   source.loop = true;
   var dest = context.createMediaStreamDestination();
   var stopTriggered = false;
   var onstopTriggered = false;
   dest.channelCount = 4;
-  var expectedMimeType = 'audio/ogg';
+  var expectedMimeType = 'audio/ogg; codecs=opus';
   var totalBlobSize = 0;
   source.channelCountMode = 'explicit';
   source.connect(dest);
   var elem = document.createElement('audio');
   elem.srcObject = dest.stream;
   mMediaStream = dest.stream;
   source.start(0);
   elem.play();
@@ -46,25 +46,23 @@ function startTest() {
 
   mMediaRecorder.onstop = function() {
     ok(true, 'onstop fired successfully');
     is(mMediaRecorder.state, 'inactive', 'check recording status is inactive');
     onstopTriggered = true;
     SimpleTest.finish();
   };
   mMediaRecorder.ondataavailable = function (e) {
-    if (mMediaRecorder.state == 'recording') {
-      ok(e.data.size > 0, 'check blob has data');
-    }
+    ok(e.data.size > 0, 'check blob has data');
     totalBlobSize += e.data.size;
-    ok(totalBlobSize > 0, 'check the totalBlobSize');
     is(e.data.type, expectedMimeType, 'blob should have expected mimetype');
-    is(mMediaRecorder.mimeType, expectedMimeType, 'recorder should have expected mimetype');
     if (!stopTriggered) {
+      is(mMediaRecorder.mimeType, expectedMimeType, 'recorder should have expected mimetype');
       mMediaRecorder.stop();
+      is(mMediaRecorder.mimeType, '', 'recorder should have reset its mimetype');
       stopTriggered = true;
     } else if (onstopTriggered) {
       ok(false, 'ondataavailable should come before onstop event');
     }
   };
   try {
     mMediaRecorder.start(1000);
     is('recording', mMediaRecorder.state, "check record state recording");
--- a/dom/media/test/test_mediarecorder_record_audiocontext.html
+++ b/dom/media/test/test_mediarecorder_record_audiocontext.html
@@ -40,17 +40,18 @@ function startTest() {
 
   mMediaRecorder.onstop = function() {
     ok(true, 'onstop fired successfully');
     is(mMediaRecorder.state, 'inactive', 'check recording status is inactive');
     SimpleTest.finish();
   };
   mMediaRecorder.ondataavailable = function (e) {
     if (mMediaRecorder.state == 'recording') {
-      is('audio/ogg', mMediaRecorder.mimeType, "check the record mimetype return " + mMediaRecorder.mimeType);
+      is(mMediaRecorder.mimeType, 'audio/ogg; codecs=opus', 'Expected MediaRecorder mimetype');
+      is(e.data.type, 'audio/ogg; codecs=opus', 'Expected Blob mimetype');
       ok(e.data.size > 0, 'check blob has data');
       mMediaRecorder.stop();
     }
   };
   try {
     mMediaRecorder.start(1000);
     is('recording', mMediaRecorder.state, "check record state recording");
   } catch (e) {
--- a/dom/media/test/test_mediarecorder_record_audionode.html
+++ b/dom/media/test/test_mediarecorder_record_audionode.html
@@ -47,36 +47,35 @@ function setUpSource(contextType, nodeTy
   }
 
   return node;
 }
 
 async function testRecord(source, mimeType) {
   const isOffline = source.context instanceof OfflineAudioContext;
   const recorder = new MediaRecorder(source, 0, {mimeType});
+  const extendedMimeType = `${mimeType || "audio/ogg"}; codecs=opus`;
   is(recorder.mimeType, mimeType, "Mime type is set");
 
   recorder.onwarning = () => ok(false, "should not fire onwarning");
   recorder.onerror = () => ok(false, "should not fire onerror");
   if (!isOffline) {
     recorder.onstop = () => ok(false, "should not fire stop yet");
   }
 
   recorder.start(1000);
   is("recording", recorder.state, "state should become recording after calling start()");
+  is(recorder.mimeType, extendedMimeType, "Mime type is fully defined");
 
   const chunks = [];
   let {data} = await new Promise(r => recorder.ondataavailable = r);
   if (!isOffline) {
     is(recorder.state, "recording", "Expected to still be recording");
   }
-  is(data.type, recorder.mimeType, "Blob has recorder mimetype");
-  if (mimeType != "") {
-    is(data.type, mimeType, "Blob has given mimetype");
-  }
+  is(data.type, extendedMimeType, "Blob has fully defined mimetype");
   isnot(data.size, 0, "should get data and its length should be > 0");
   chunks.push(data);
 
   if (isOffline) {
     await new Promise(r => recorder.onstop = r);
     is(recorder.state, "inactive", "Offline context should end by itself");
   } else {
     is(recorder.state, "recording", "Expected to still be recording");
@@ -87,27 +86,27 @@ async function testRecord(source, mimeTy
     chunks.push(data);
 
     await new Promise(r => recorder.onstop = r);
     is(recorder.state, "inactive", "state should remain inactive after stop event");
   }
   return new Blob(chunks, {type: chunks[0].type});
 }
 
-addLoadEvent(async function() {
+addLoadEvent(async () => {
   const src = setUpSource();
   let didThrow = false;
   try {
     new MediaRecorder(src);
   } catch (e) {
     didThrow = true;
   }
   ok(didThrow, "MediaRecorder(AudioNode) should be hidden behind a pref");
 
-  await SpecialPowers.pushPrefEnv({"set": [
+  await SpecialPowers.pushPrefEnv({set: [
       ["media.recorder.audio_node.enabled", true],
     ]});
 
   // Test with various context and source node types.
   for (const mimeType of [
     "audio/ogg",
     "audio/webm",
     "video/webm",
--- a/dom/media/test/test_mediarecorder_record_getdata_afterstart.html
+++ b/dom/media/test/test_mediarecorder_record_getdata_afterstart.html
@@ -21,29 +21,30 @@ function startTest(test, token) {
 
   element.token = token;
   manager.started(token);
   element.src = test.name;
   element.test = test;
   element.stream = element.mozCaptureStream();
 
   mMediaRecorder = new MediaRecorder(element.stream);
+  is(mMediaRecorder.mimeType, '', 'Expected MediaRecorder mimetype');
   mMediaRecorder.onwarning = function() {
     ok(false, 'onwarning unexpectedly fired');
   };
 
   mMediaRecorder.onerror = function() {
     ok(false, 'onerror unexpectedly fired');
   };
 
   mMediaRecorder.onstart = function() {
     info('onstart fired successfully');
     hasonstart = true;
-    // On audio only case, we produce audio/ogg as mimeType.
-    is('audio/ogg', mMediaRecorder.mimeType, "MediaRecorder mimetype as expected");
+    is(mMediaRecorder.mimeType, 'audio/ogg; codecs=opus',
+      "MediaRecorder mimetype as expected");
     mMediaRecorder.requestData();
   };
 
   mMediaRecorder.onstop = function() {
     info('onstop fired successfully');
     ok(hasondataavailable, "should have ondataavailable before onstop");
     is(mMediaRecorder.state, 'inactive', 'check recording status is inactive');
     SimpleTest.finish();
--- a/dom/media/test/test_mediarecorder_record_gum_video_timeslice.html
+++ b/dom/media/test/test_mediarecorder_record_gum_video_timeslice.html
@@ -13,16 +13,17 @@
 <script class="testbody" type="text/javascript">
 
 async function startTest() {
   try {
     await setupGetUserMediaTestPrefs();
     let stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
     let dataAvailableCount = 0;
     let onDataAvailableFirst = false;
+    const expectedMimeType = 'video/webm; codecs="vp8, opus"';
 
     mediaRecorder = new MediaRecorder(stream);
     is(mediaRecorder.stream, stream,
        'Media recorder stream = element stream at the start of recording');
     mediaRecorder.onwarning = function() {
       ok(false, 'onwarning unexpectedly fired');
     };
 
@@ -39,19 +40,17 @@ async function startTest() {
       dataAvailableCount++;
 
       ok(evt instanceof BlobEvent,
          'Events fired from ondataavailable should be BlobEvent');
       is(evt.type, 'dataavailable',
          'Event type should dataavailable');
       ok(evt.data.size >= 0,
          'Blob data size ' + evt.data.size + ' received is greater than or equal to zero');
-      is(mediaRecorder.mimeType, evt.data.type,
-         'Mime type in MediaRecorder and ondataavailable : '
-         + mediaRecorder.mimeType + ' == ' + evt.data.type);
+      is(evt.data.type, expectedMimeType, 'Expected blob mime type');
 
       // We'll stop recording upon the 1st blob being received
       if (dataAvailableCount === 1) {
         mediaRecorder.onstop = function (evt) {
           info('onstop fired');
 
           if (!onDataAvailableFirst) {
             ok(false, 'onstop unexpectedly fired before ondataavailable');
@@ -71,16 +70,17 @@ async function startTest() {
         // Ensure we've received at least two ondataavailable events before
         // onstop
         onDataAvailableFirst = true;
       }
     };
 
     mediaRecorder.start(250);
     is(mediaRecorder.state, 'recording', 'Media recorder should be recording');
+    is(mediaRecorder.mimeType, expectedMimeType, 'Expected mime type');
   } catch (err) {
     ok(false, 'Unexpected error fired with: ' + err);
     SimpleTest.finish();
   }
 }
 
 SimpleTest.waitForExplicitFinish();
 startTest();
--- a/dom/media/test/test_mediarecorder_record_immediate_stop.html
+++ b/dom/media/test/test_mediarecorder_record_immediate_stop.html
@@ -14,26 +14,27 @@ var manager = new MediaTestManager;
 /**
  * Stops the media recorder immediately after starting the recorder. This test
  * verifies whether the media recorder can handle this scenario nicely. The
  * return blob size should be greater than zero, but its duration would be zero
  * length when play.
  */
 function startTest(test, token) {
   var element = document.createElement('audio');
-  var expectedMimeType = test.type.substring(0, test.type.indexOf(';'));
+  var expectedMimeType = test.type;
 
   element.token = token;
   manager.started(token);
 
   element.src = test.name;
   element.test = test;
   element.stream = element.mozCaptureStreamUntilEnded();
 
-  var mediaRecorder = new MediaRecorder(element.stream);
+  var mediaRecorder =
+    new MediaRecorder(element.stream, {mimeType: expectedMimeType});
   var onStopFired = false;
   var onDataAvailableFired = false;
 
   mediaRecorder.onerror = function () {
     ok(false, 'Unexpected onerror callback fired');
   };
 
   mediaRecorder.onwarning = function () {
@@ -68,29 +69,21 @@ function startTest(test, token) {
       ok(evt instanceof BlobEvent,
          'Events fired from ondataavailable should be BlobEvent');
       is(evt.type, 'dataavailable',
          'Event type should dataavailable');
 
       // The initialization of encoder can be cancelled.
       // On some platforms, the stop method may run after media stream track
       // available, so the blob can contain the header data.
-      if (evt.data.size > 0) {
-        is(evt.data.type, expectedMimeType,
-           'Blob data received and should have mime type');
-        is(mediaRecorder.mimeType, expectedMimeType,
-           'Media Recorder mime type in ondataavailable = ' + expectedMimeType);
-      } else if (evt.data.size === 0) {
-        is(mediaRecorder.mimeType, '',
-           'Blob data mime type is empty');
-        is(mediaRecorder.mimeType, '',
-           'Media Recorder mime type in ondataavailable is empty');
-      } else {
-        ok(false, 'Blob size can not be negative');
-      }
+      is(evt.data.type, expectedMimeType,
+         'Blob data received and should have mime type');
+      is(mediaRecorder.mimeType, expectedMimeType,
+         'Media Recorder mime type in ondataavailable = ' + expectedMimeType);
+      ok(evt.data.size >= 0, 'Blob size can not be negative');
 
       // onstop should not have fired before ondataavailable
       if (onStopFired) {
         ok(false, 'ondataavailable unexpectedly fired later than onstop');
         manager.finished(token);
       }
     }
   };
--- a/dom/media/test/test_mediarecorder_record_no_timeslice.html
+++ b/dom/media/test/test_mediarecorder_record_no_timeslice.html
@@ -12,26 +12,27 @@
 var manager = new MediaTestManager;
 
 /**
  * Starts a test on every media recorder file included to check that a
  * stream derived from the file can be recorded with no time slice provided.
  */
 function startTest(test, token) {
   var element = document.createElement('audio');
-  var expectedMimeType = test.type.substring(0, test.type.indexOf(';'));
+  var expectedMimeType = test.type;
 
   element.token = token;
   manager.started(token);
 
   element.src = test.name;
   element.test = test;
   element.stream = element.mozCaptureStreamUntilEnded();
 
-  var mediaRecorder = new MediaRecorder(element.stream);
+  var mediaRecorder =
+    new MediaRecorder(element.stream, {mimeType: expectedMimeType});
   var onStopFired = false;
   var onDataAvailableFired = false;
 
   mediaRecorder.onerror = function () {
     ok(false, 'Unexpected onerror callback fired');
   };
 
   mediaRecorder.onwarning = function () {
--- a/dom/media/test/test_mediarecorder_record_startstopstart.html
+++ b/dom/media/test/test_mediarecorder_record_startstopstart.html
@@ -12,19 +12,17 @@
 <script class="testbody" type="text/javascript">
 
 function startTest() {
   var ac = new window.AudioContext();
   var dest = ac.createMediaStreamDestination();
   var recorder = new MediaRecorder(dest.stream);
   var stopCount = 0;
   var dataavailable = 0;
-  // mobile device may produce another format, but not landed.
-  // In audio only case, we should produce opus type.
-  var expectedMimeType = 'audio/ogg';
+  var expectedMimeType = 'audio/ogg; codecs=opus';
   recorder.onstop = function (e) {
     info('onstop fired');
     is(recorder.stream, dest.stream,
        'Media recorder stream = element stream post recording');
     stopCount++;
     if (stopCount == 2) {
       if (dataavailable >= 2) {
         SimpleTest.finish();
--- a/dom/media/test/test_mediarecorder_record_timeslice.html
+++ b/dom/media/test/test_mediarecorder_record_timeslice.html
@@ -12,31 +12,32 @@
 var manager = new MediaTestManager;
 
 /**
  * Starts a test on every media recorder file included to check that a stream
  * derived from the file can be recorded with a timeslice provided
  */
 function startTest(test, token) {
   var element = document.createElement('audio');
-  var expectedMimeType = test.type.substring(0, test.type.indexOf(';'));
+  var expectedMimeType = test.type;
 
   element.token = token;
   manager.started(token);
 
   element.src = test.name;
   element.test = test;
   element.preload = "auto";
 
   // Set up MediaRecorder once loadedmetadata fires and tracks are available.
   element.onloadedmetadata = function() {
     element.onloadedmetadata = null;
 
     const stream = element.mozCaptureStream();
-    const mediaRecorder = new MediaRecorder(stream);
+    const mediaRecorder =
+      new MediaRecorder(stream, {mimeType: expectedMimeType});
 
     mediaRecorder.onerror = function () {
       ok(false, 'Unexpected onerror callback fired');
     };
 
     mediaRecorder.onwarning = function () {
       ok(false, 'Unexpected onwarning callback fired');
     };
--- a/dom/media/test/test_mediarecorder_state_transition.html
+++ b/dom/media/test/test_mediarecorder_state_transition.html
@@ -232,26 +232,23 @@ function runStateTransitionTests(testStr
         if (operationTest.timeSlice && operation === 'start') {
           operationsString += ' with timeslice ' + operationTest.timeSlice;
           mediaRecorder[operation](operationTest.timeSlice);
         } else {
           mediaRecorder[operation]();
         }
       }
 
+      ok(operationTest.isValid, `${operationsString} should succeed`);
+    } catch (err) {
       if (operationTest.isValid) {
-        ok(true, 'Successful transitions for ' + operationsString);
+        ok(false, `${operationsString} failed unexpectedly with ${err.name}`);
       } else {
-        ok(false, 'Failed transitions for ' + operationsString);
-      }
-    } catch (err) {
-      if (!operationTest.isValid && err.name === 'InvalidStateError') {
-        ok(true, 'InvalidStateError fired for ' + operationsString);
-      } else {
-        ok(false, 'No InvalidStateError for ' + operationsString);
+        is(err.name, "InvalidStateError",
+          `${operationsString} expected to fail with InvalidStateError`);
       }
     }
   }
 }
 
 /**
  * Starts a test on every media recorder file included to check that various
  * state transition flows that can happen in the media recorder object throw
--- a/dom/media/test/test_mediarecorder_webm_support.html
+++ b/dom/media/test/test_mediarecorder_webm_support.html
@@ -3,16 +3,54 @@
 <head>
   <title>Media Recording - test WebM MIME support</title>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
-ok(MediaRecorder.isTypeSupported('video/webm'), 'Should support video/webm');
-ok(!MediaRecorder.isTypeSupported('video/webm; codecs="vp8, vorbis"'), 'Should not support video/webm + vp8/vorbis');
-ok(!MediaRecorder.isTypeSupported('video/webm; codecs="vp9, vorbis"'), 'Should not support video/webm + vp9/vorbis');
-ok(MediaRecorder.isTypeSupported('video/webm; codecs="vp8, opus"'), 'Should support video/webm + vp8/opus');
-ok(!MediaRecorder.isTypeSupported('video/webm; codecs="vp9, opus"'), 'Should not support video/webm + vp9/opus');
+ok(MediaRecorder.isTypeSupported('audio/webm'),
+   'Should support audio/webm');
+ok(MediaRecorder.isTypeSupported('AUDIO/WEBM'),
+   'Should support audio/webm, upper case');
+ok(MediaRecorder.isTypeSupported('AuDiO/wEbM'),
+   'Should support audio/webm, mixed case');
+
+ok(MediaRecorder.isTypeSupported('audio/webm;codecs=opus'),
+   'Should support audio/webm;codecs=opus');
+ok(MediaRecorder.isTypeSupported('AUDIO/WEBM;CODECS=opus'),
+   'Should support audio/webm;codecs=opus, upper case');
+ok(MediaRecorder.isTypeSupported('AuDiO/wEbM;cOdEcS=opus'),
+   'Should support audio/webm;codecs=opus, mixed case');
+
+ok(MediaRecorder.isTypeSupported('video/webm'),
+   'Should support video/webm');
+ok(MediaRecorder.isTypeSupported('VIDEO/WEBM'),
+   'Should support video/webm, upper case');
+ok(MediaRecorder.isTypeSupported('vIdEo/WeBm'),
+   'Should support video/webm, mixed case');
+
+ok(MediaRecorder.isTypeSupported('video/webm; codecs="vp8"'),
+   'Should support video/webm; codecs="vp8"');
+ok(MediaRecorder.isTypeSupported('VIDEO/WEBM; CODECS="vp8"'),
+   'Should support video/webm; codecs="vp8", upper case');
+ok(MediaRecorder.isTypeSupported('vIdEo/WeBm; CoDeCs="vp8"'),
+   'Should support video/webm; codecs="vp8", mixed case');
+
+ok(MediaRecorder.isTypeSupported('video/webm; codecs="vp8.0"'),
+   'Should support video/webm; codecs="vp8.0"');
+ok(MediaRecorder.isTypeSupported('VIDEO/WEBM; CODECS="vp8.0"'),
+   'Should support video/webm; codecs="vp8.0", upper case');
+ok(MediaRecorder.isTypeSupported('vIdEo/WeBm; CoDeCs="vp8.0"'),
+   'Should support video/webm; codecs="vp8.0", mixed case');
+
+ok(!MediaRecorder.isTypeSupported('video/webm; codecs="vp8, vorbis"'),
+   'Should not support video/webm + vp8/vorbis');
+ok(!MediaRecorder.isTypeSupported('video/webm; codecs="vp9, vorbis"'),
+   'Should not support video/webm + vp9/vorbis');
+ok(MediaRecorder.isTypeSupported('video/webm; codecs="vp8, opus"'),
+   'Should support video/webm + vp8/opus');
+ok(!MediaRecorder.isTypeSupported('video/webm; codecs="vp9, opus"'),
+   'Should not support video/webm + vp9/opus');
 </script>
 </head>
 </html>
--- a/dom/webidl/MediaRecorder.webidl
+++ b/dom/webidl/MediaRecorder.webidl
@@ -14,53 +14,41 @@ enum RecordingState { "inactive", "recor
 
 [Exposed=Window]
 interface MediaRecorder : EventTarget {
   [Throws]
   constructor(MediaStream stream, optional MediaRecorderOptions options = {});
   [Throws]
   constructor(AudioNode node, optional unsigned long output = 0,
               optional MediaRecorderOptions options = {});
-
   readonly attribute MediaStream stream;
-
   readonly attribute DOMString mimeType;
-
   readonly attribute RecordingState state;
-
   attribute EventHandler onstart;
-
   attribute EventHandler onstop;
-
   attribute EventHandler ondataavailable;
-
   attribute EventHandler onpause;
+  attribute EventHandler onresume;
+  attribute EventHandler onerror;
+  readonly attribute unsigned long videoBitsPerSecond;
+  readonly attribute unsigned long audioBitsPerSecond;
 
-  attribute EventHandler onresume;
-
-  attribute EventHandler onerror;
-
-  attribute EventHandler onwarning;
 
   [Throws]
   void start(optional unsigned long timeslice);
-
   [Throws]
   void stop();
-
   [Throws]
   void pause();
-
   [Throws]
   void resume();
-
   [Throws]
   void requestData();
 
   static boolean isTypeSupported(DOMString type);
 };
 
 dictionary MediaRecorderOptions {
-  DOMString mimeType = ""; // Default encoding mimeType.
+  DOMString mimeType = "";
   unsigned long audioBitsPerSecond;
   unsigned long videoBitsPerSecond;
   unsigned long bitsPerSecond;
 };
--- a/gfx/skia/skia/src/opts/SkOpts_hsw.cpp
+++ b/gfx/skia/skia/src/opts/SkOpts_hsw.cpp
@@ -2,17 +2,17 @@
  * Copyright 2018 Google Inc.
  *
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
 
 // As described in https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85525, MinGW will produce
 // unaligned instructions for this code, resulting in a crash.
-#if defined(__AVX2__) && !defined(__MINGW32__)
+#if defined(__AVX2__)
 
 #include <immintrin.h>
 #include <stdint.h>
 
 namespace hsw {
 
     void convolve_vertically(const int16_t* filter, int filterLen,
                              uint8_t* const* srcRows, int width,
--- a/intl/unicharutil/util/nsUnicharUtils.cpp
+++ b/intl/unicharutil/util/nsUnicharUtils.cpp
@@ -431,13 +431,13 @@ uint32_t HashUTF8AsUTF16(const char* aUT
       hash = AddToHash(hash, H_SURROGATE(ucs4), L_SURROGATE(ucs4));
     }
   }
 
   return hash;
 }
 
 bool IsSegmentBreakSkipChar(uint32_t u) {
-  return unicode::IsEastAsianWidthFWH(u) &&
+  return unicode::IsEastAsianWidthFHWexcludingEmoji(u) &&
          unicode::GetScriptCode(u) != unicode::Script::HANGUL;
 }
 
 }  // namespace mozilla
--- a/intl/unicharutil/util/nsUnicodeProperties.h
+++ b/intl/unicharutil/util/nsUnicodeProperties.h
@@ -119,22 +119,23 @@ inline uint32_t GetTitlecaseForLower(
 }
 
 inline uint32_t GetTitlecaseForAll(
     uint32_t aCh)  // maps both UC and LC to titlecase
 {
   return u_totitle(aCh);
 }
 
-inline bool IsEastAsianWidthFWH(uint32_t aCh) {
+inline bool IsEastAsianWidthFHWexcludingEmoji(uint32_t aCh) {
   switch (u_getIntPropertyValue(aCh, UCHAR_EAST_ASIAN_WIDTH)) {
     case U_EA_FULLWIDTH:
-    case U_EA_WIDE:
     case U_EA_HALFWIDTH:
       return true;
+    case U_EA_WIDE:
+      return u_hasBinaryProperty(aCh, UCHAR_EMOJI) ? false : true;
     case U_EA_AMBIGUOUS:
     case U_EA_NARROW:
     case U_EA_NEUTRAL:
       return false;
   }
   return false;
 }
 
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -625,23 +625,17 @@ static bool GCParameter(JSContext* cx, u
 
   uint32_t value = floor(d);
   if (param == JSGC_MARK_STACK_LIMIT && JS::IsIncrementalGCInProgress(cx)) {
     JS_ReportErrorASCII(
         cx, "attempt to set markStackLimit while a GC is in progress");
     return false;
   }
 
-  bool ok;
-  {
-    JSRuntime* rt = cx->runtime();
-    AutoLockGC lock(rt);
-    ok = rt->gc.setParameter(param, value, lock);
-  }
-
+  bool ok = cx->runtime()->gc.setParameter(param, value);
   if (!ok) {
     JS_ReportErrorASCII(cx, "Parameter value out of range");
     return false;
   }
 
   args.rval().setUndefined();
   return true;
 }
--- a/js/src/gc/Allocator.cpp
+++ b/js/src/gc/Allocator.cpp
@@ -594,50 +594,50 @@ Arena* GCRuntime::allocateArena(Chunk* c
                                 const AutoLockGC& lock) {
   MOZ_ASSERT(chunk->hasAvailableArenas());
 
   // Fail the allocation if we are over our heap size limits.
   if ((checkThresholds != ShouldCheckThresholds::DontCheckThresholds) &&
       (heapSize.bytes() >= tunables.gcMaxBytes()))
     return nullptr;
 
-  Arena* arena = chunk->allocateArena(rt, zone, thingKind, lock);
+  Arena* arena = chunk->allocateArena(this, zone, thingKind, lock);
   zone->gcHeapSize.addGCArena();
 
   // Trigger an incremental slice if needed.
   if (checkThresholds != ShouldCheckThresholds::DontCheckThresholds) {
     maybeAllocTriggerZoneGC(zone, ArenaSize);
   }
 
   return arena;
 }
 
-Arena* Chunk::allocateArena(JSRuntime* rt, Zone* zone, AllocKind thingKind,
+Arena* Chunk::allocateArena(GCRuntime* gc, Zone* zone, AllocKind thingKind,
                             const AutoLockGC& lock) {
-  Arena* arena = info.numArenasFreeCommitted > 0 ? fetchNextFreeArena(rt)
+  Arena* arena = info.numArenasFreeCommitted > 0 ? fetchNextFreeArena(gc)
                                                  : fetchNextDecommittedArena();
   arena->init(zone, thingKind, lock);
-  updateChunkListAfterAlloc(rt, lock);
+  updateChunkListAfterAlloc(gc, lock);
   return arena;
 }
 
 inline void GCRuntime::updateOnFreeArenaAlloc(const ChunkInfo& info) {
   MOZ_ASSERT(info.numArenasFreeCommitted <= numArenasFreeCommitted);
   --numArenasFreeCommitted;
 }
 
-Arena* Chunk::fetchNextFreeArena(JSRuntime* rt) {
+Arena* Chunk::fetchNextFreeArena(GCRuntime* gc) {
   MOZ_ASSERT(info.numArenasFreeCommitted > 0);
   MOZ_ASSERT(info.numArenasFreeCommitted <= info.numArenasFree);
 
   Arena* arena = info.freeArenasHead;
   info.freeArenasHead = arena->next;
   --info.numArenasFreeCommitted;
   --info.numArenasFree;
-  rt->gc.updateOnFreeArenaAlloc(info);
+  gc->updateOnFreeArenaAlloc(info);
 
   return arena;
 }
 
 Arena* Chunk::fetchNextDecommittedArena() {
   MOZ_ASSERT(info.numArenasFreeCommitted == 0);
   MOZ_ASSERT(info.numArenasFree > 0);
 
@@ -674,17 +674,17 @@ uint32_t Chunk::findDecommittedArenaOffs
   MOZ_CRASH("No decommitted arenas found.");
 }
 
 // ///////////  System -> Chunk Allocator  /////////////////////////////////////
 
 Chunk* GCRuntime::getOrAllocChunk(AutoLockGCBgAlloc& lock) {
   Chunk* chunk = emptyChunks(lock).pop();
   if (!chunk) {
-    chunk = Chunk::allocate(rt);
+    chunk = Chunk::allocate(this);
     if (!chunk) {
       return nullptr;
     }
     MOZ_ASSERT(chunk->info.numArenasFreeCommitted == 0);
   }
 
   if (wantBackgroundAllocation(lock)) {
     lock.tryToStartBackgroundAllocation();
@@ -704,17 +704,17 @@ Chunk* GCRuntime::pickChunk(AutoLockGCBg
     return availableChunks(lock).head();
   }
 
   Chunk* chunk = getOrAllocChunk(lock);
   if (!chunk) {
     return nullptr;
   }
 
-  chunk->init(rt);
+  chunk->init(this);
   MOZ_ASSERT(chunk->info.numArenasFreeCommitted == 0);
   MOZ_ASSERT(chunk->unused());
   MOZ_ASSERT(!fullChunks(lock).contains(chunk));
   MOZ_ASSERT(!availableChunks(lock).contains(chunk));
 
   chunkAllocationSinceLastGC = true;
 
   availableChunks(lock).push(chunk);
@@ -726,42 +726,43 @@ BackgroundAllocTask::BackgroundAllocTask
     : GCParallelTaskHelper(rt),
       chunkPool_(pool),
       enabled_(CanUseExtraThreads() && GetCPUCount() >= 2) {}
 
 void BackgroundAllocTask::run() {
   TraceLoggerThread* logger = TraceLoggerForCurrentThread();
   AutoTraceLog logAllocation(logger, TraceLogger_GCAllocation);
 
-  AutoLockGC lock(runtime());
+  GCRuntime* gc = &runtime()->gc;
+  AutoLockGC lock(gc);
   while (!cancel_ && runtime()->gc.wantBackgroundAllocation(lock)) {
     Chunk* chunk;
     {
       AutoUnlockGC unlock(lock);
-      chunk = Chunk::allocate(runtime());
+      chunk = Chunk::allocate(gc);
       if (!chunk) {
         break;
       }
-      chunk->init(runtime());
+      chunk->init(gc);
     }
     chunkPool_.ref().push(chunk);
   }
 }
 
 /* static */
-Chunk* Chunk::allocate(JSRuntime* rt) {
+Chunk* Chunk::allocate(GCRuntime* gc) {
   Chunk* chunk = static_cast<Chunk*>(MapAlignedPages(ChunkSize, ChunkSize));
   if (!chunk) {
     return nullptr;
   }
-  rt->gc.stats().count(gcstats::COUNT_NEW_CHUNK);
+  gc->stats().count(gcstats::COUNT_NEW_CHUNK);
   return chunk;
 }
 
-void Chunk::init(JSRuntime* rt) {
+void Chunk::init(GCRuntime* gc) {
   /* The chunk may still have some regions marked as no-access. */
   MOZ_MAKE_MEM_UNDEFINED(this, ChunkSize);
 
   /*
    * Poison the chunk. Note that decommitAllArenas() below will mark the
    * arenas as inaccessible (for memory sanitizers).
    */
   Poison(this, JS_FRESH_TENURED_PATTERN, ChunkSize,
@@ -776,17 +777,17 @@ void Chunk::init(JSRuntime* rt) {
   /*
    * Decommit the arenas. We do this after poisoning so that if the OS does
    * not have to recycle the pages, we still get the benefit of poisoning.
    */
   decommitAllArenas();
 
   /* Initialize the chunk info. */
   info.init();
-  new (&trailer) ChunkTrailer(rt);
+  new (&trailer) ChunkTrailer(gc->rt);
 
   /* The rest of info fields are initialized in pickChunk. */
 }
 
 void Chunk::decommitAllArenas() {
   decommittedArenas.clear(true);
   MarkPagesUnusedSoft(&arenas[0], ArenasPerChunk * ArenaSize);
 
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -755,58 +755,58 @@ inline void GCRuntime::prepareToFreeChun
    * frees chunk.
    */
   info.numArenasFreeCommitted = 0;
 #endif
 }
 
 inline void GCRuntime::updateOnArenaFree() { ++numArenasFreeCommitted; }
 
-void Chunk::addArenaToFreeList(JSRuntime* rt, Arena* arena) {
+void Chunk::addArenaToFreeList(GCRuntime* gc, Arena* arena) {
   MOZ_ASSERT(!arena->allocated());
   arena->next = info.freeArenasHead;
   info.freeArenasHead = arena;
   ++info.numArenasFreeCommitted;
   ++info.numArenasFree;
-  rt->gc.updateOnArenaFree();
+  gc->updateOnArenaFree();
 }
 
 void Chunk::addArenaToDecommittedList(const Arena* arena) {
   ++info.numArenasFree;
   decommittedArenas.set(Chunk::arenaIndex(arena->address()));
 }
 
 void Chunk::recycleArena(Arena* arena, SortedArenaList& dest,
                          size_t thingsPerArena) {
   arena->setAsFullyUnused();
   dest.insertAt(arena, thingsPerArena);
 }
 
-void Chunk::releaseArena(JSRuntime* rt, Arena* arena, const AutoLockGC& lock) {
-  addArenaToFreeList(rt, arena);
-  updateChunkListAfterFree(rt, lock);
-}
-
-bool Chunk::decommitOneFreeArena(JSRuntime* rt, AutoLockGC& lock) {
+void Chunk::releaseArena(GCRuntime* gc, Arena* arena, const AutoLockGC& lock) {
+  addArenaToFreeList(gc, arena);
+  updateChunkListAfterFree(gc, lock);
+}
+
+bool Chunk::decommitOneFreeArena(GCRuntime* gc, AutoLockGC& lock) {
   MOZ_ASSERT(info.numArenasFreeCommitted > 0);
-  Arena* arena = fetchNextFreeArena(rt);
-  updateChunkListAfterAlloc(rt, lock);
+  Arena* arena = fetchNextFreeArena(gc);
+  updateChunkListAfterAlloc(gc, lock);
 
   bool ok;
   {
     AutoUnlockGC unlock(lock);
     ok = MarkPagesUnusedSoft(arena, ArenaSize);
   }
 
   if (ok) {
     addArenaToDecommittedList(arena);
   } else {
-    addArenaToFreeList(rt, arena);
-  }
-  updateChunkListAfterFree(rt, lock);
+    addArenaToFreeList(gc, arena);
+  }
+  updateChunkListAfterFree(gc, lock);
 
   return ok;
 }
 
 void Chunk::decommitFreeArenasWithoutUnlocking(const AutoLockGC& lock) {
   for (size_t i = 0; i < ArenasPerChunk; ++i) {
     if (decommittedArenas.get(i) || arenas[i].allocated()) {
       continue;
@@ -814,45 +814,45 @@ void Chunk::decommitFreeArenasWithoutUnl
 
     if (MarkPagesUnusedSoft(&arenas[i], ArenaSize)) {
       info.numArenasFreeCommitted--;
       decommittedArenas.set(i);
     }
   }
 }
 
-void Chunk::updateChunkListAfterAlloc(JSRuntime* rt, const AutoLockGC& lock) {
+void Chunk::updateChunkListAfterAlloc(GCRuntime* gc, const AutoLockGC& lock) {
   if (MOZ_UNLIKELY(!hasAvailableArenas())) {
-    rt->gc.availableChunks(lock).remove(this);
-    rt->gc.fullChunks(lock).push(this);
-  }
-}
-
-void Chunk::updateChunkListAfterFree(JSRuntime* rt, const AutoLockGC& lock) {
+    gc->availableChunks(lock).remove(this);
+    gc->fullChunks(lock).push(this);
+  }
+}
+
+void Chunk::updateChunkListAfterFree(GCRuntime* gc, const AutoLockGC& lock) {
   if (info.numArenasFree == 1) {
-    rt->gc.fullChunks(lock).remove(this);
-    rt->gc.availableChunks(lock).push(this);
+    gc->fullChunks(lock).remove(this);
+    gc->availableChunks(lock).push(this);
   } else if (!unused()) {
-    MOZ_ASSERT(rt->gc.availableChunks(lock).contains(this));
+    MOZ_ASSERT(gc->availableChunks(lock).contains(this));
   } else {
     MOZ_ASSERT(unused());
-    rt->gc.availableChunks(lock).remove(this);
+    gc->availableChunks(lock).remove(this);
     decommitAllArenas();
     MOZ_ASSERT(info.numArenasFreeCommitted == 0);
-    rt->gc.recycleChunk(this, lock);
+    gc->recycleChunk(this, lock);
   }
 }
 
 void GCRuntime::releaseArena(Arena* arena, const AutoLockGC& lock) {
   MOZ_ASSERT(arena->allocated());
   MOZ_ASSERT(!arena->onDelayedMarkingList());
 
   arena->zone->gcHeapSize.removeGCArena();
   arena->release(lock);
-  arena->chunk()->releaseArena(rt, arena, lock);
+  arena->chunk()->releaseArena(this, arena, lock);
 }
 
 GCRuntime::GCRuntime(JSRuntime* rt)
     : rt(rt),
       systemZone(nullptr),
       atomsZone(nullptr),
       stats_(rt),
       marker(rt),
@@ -914,17 +914,17 @@ GCRuntime::GCRuntime(JSRuntime* rt)
       gcCallbackDepth(0),
       alwaysPreserveCode(false),
       lowMemoryState(false),
       lock(mutexid::GCLock),
       allocTask(rt, emptyChunks_.ref()),
       sweepTask(rt),
       freeTask(rt),
       decommitTask(rt),
-      nursery_(rt),
+      nursery_(this),
       storeBuffer_(rt, nursery()) {
   setGCMode(JSGC_MODE_GLOBAL);
 }
 
 #ifdef JS_GC_ZEAL
 
 void GCRuntime::getZealBits(uint32_t* zealBits, uint32_t* frequency,
                             uint32_t* scheduled) {
@@ -1201,17 +1201,17 @@ void js::gc::DumpArenaInfo() {
 }
 
 #endif  // JS_GC_ZEAL
 
 bool GCRuntime::init(uint32_t maxbytes) {
   MOZ_ASSERT(SystemPageSize());
 
   {
-    AutoLockGCBgAlloc lock(rt);
+    AutoLockGCBgAlloc lock(this);
 
     MOZ_ALWAYS_TRUE(tunables.setParameter(JSGC_MAX_BYTES, maxbytes, lock));
 
     const char* size = getenv("JSGC_MARK_STACK_LIMIT");
     if (size) {
       setMarkStackLimit(atoi(size), lock);
     }
 
@@ -1295,20 +1295,25 @@ void GCRuntime::finish() {
   FreeChunkPool(emptyChunks_.ref());
 
   gcTracer.finishTrace();
 
   nursery().printTotalProfileTimes();
   stats().printTotalProfileTimes();
 }
 
+bool GCRuntime::setParameter(JSGCParamKey key, uint32_t value) {
+  MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
+  waitBackgroundSweepEnd();
+  AutoLockGC lock(this);
+  return setParameter(key, value, lock);
+}
+
 bool GCRuntime::setParameter(JSGCParamKey key, uint32_t value,
                              AutoLockGC& lock) {
-  MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
-
   switch (key) {
     case JSGC_SLICE_TIME_BUDGET_MS:
       defaultTimeBudgetMS_ = value ? value : SliceBudget::UnlimitedTimeBudget;
       break;
     case JSGC_MARK_STACK_LIMIT:
       if (value == 0) {
         return false;
       }
@@ -1331,19 +1336,24 @@ bool GCRuntime::setParameter(JSGCParamKe
       for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         zone->updateGCThresholds(*this, GC_NORMAL, lock);
       }
   }
 
   return true;
 }
 
-void GCRuntime::resetParameter(JSGCParamKey key, AutoLockGC& lock) {
+void GCRuntime::resetParameter(JSGCParamKey key) {
   MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
-
+  waitBackgroundSweepEnd();
+  AutoLockGC lock(this);
+  resetParameter(key, lock);
+}
+
+void GCRuntime::resetParameter(JSGCParamKey key, AutoLockGC& lock) {
   switch (key) {
     case JSGC_SLICE_TIME_BUDGET_MS:
       defaultTimeBudgetMS_ = TuningDefaults::DefaultTimeBudgetMS;
       break;
     case JSGC_MARK_STACK_LIMIT:
       setMarkStackLimit(MarkStack::DefaultCapacity, lock);
       break;
     case JSGC_MODE:
@@ -1355,16 +1365,22 @@ void GCRuntime::resetParameter(JSGCParam
     default:
       tunables.resetParameter(key, lock);
       for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         zone->updateGCThresholds(*this, GC_NORMAL, lock);
       }
   }
 }
 
+uint32_t GCRuntime::getParameter(JSGCParamKey key) {
+  MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
+  AutoLockGC lock(this);
+  return getParameter(key, lock);
+}
+
 uint32_t GCRuntime::getParameter(JSGCParamKey key, const AutoLockGC& lock) {
   switch (key) {
     case JSGC_MAX_BYTES:
       return uint32_t(tunables.gcMaxBytes());
     case JSGC_MIN_NURSERY_BYTES:
       MOZ_ASSERT(tunables.gcMinNurseryBytes() < UINT32_MAX);
       return uint32_t(tunables.gcMinNurseryBytes());
     case JSGC_MAX_NURSERY_BYTES:
@@ -2502,17 +2518,17 @@ void GCRuntime::updateRuntimePointersToR
   }
 
   // Call callbacks to get the rest of the system to fixup other untraced
   // pointers.
   callWeakPointerZonesCallbacks();
 }
 
 void GCRuntime::clearRelocatedArenas(Arena* arenaList, JS::GCReason reason) {
-  AutoLockGC lock(rt);
+  AutoLockGC lock(this);
   clearRelocatedArenasWithoutUnlocking(arenaList, reason, lock);
 }
 
 void GCRuntime::clearRelocatedArenasWithoutUnlocking(Arena* arenaList,
                                                      JS::GCReason reason,
                                                      const AutoLockGC& lock) {
   // Clear the relocated arenas, now containing only forwarding pointers
   while (arenaList) {
@@ -2557,30 +2573,30 @@ void GCRuntime::protectAndHoldArenas(Are
 void GCRuntime::unprotectHeldRelocatedArenas() {
   for (Arena* arena = relocatedArenasToRelease; arena; arena = arena->next) {
     UnprotectPages(arena, ArenaSize);
     MOZ_ASSERT(!arena->allocated());
   }
 }
 
 void GCRuntime::releaseRelocatedArenas(Arena* arenaList) {
-  AutoLockGC lock(rt);
+  AutoLockGC lock(this);
   releaseRelocatedArenasWithoutUnlocking(arenaList, lock);
 }
 
 void GCRuntime::releaseRelocatedArenasWithoutUnlocking(Arena* arenaList,
                                                        const AutoLockGC& lock) {
   // Release relocated arenas previously cleared with clearRelocatedArenas().
   while (arenaList) {
     Arena* arena = arenaList;
     arenaList = arenaList->next;
 
     // We already updated the memory accounting so just call
     // Chunk::releaseArena.
-    arena->chunk()->releaseArena(rt, arena, lock);
+    arena->chunk()->releaseArena(this, arena, lock);
   }
 }
 
 // In debug mode we don't always release relocated arenas straight away.
 // Sometimes protect them instead and hold onto them until the next GC sweep
 // phase to catch any pointers to them that didn't get forwarded.
 
 void GCRuntime::releaseHeldRelocatedArenas() {
@@ -3132,17 +3148,17 @@ void GCRuntime::startDecommit() {
   // doing a shrinking GC we always decommit to release as much memory as
   // possible.
   if (schedulingState.inHighFrequencyGCMode() && !cleanUpEverything) {
     return;
   }
 
   BackgroundDecommitTask::ChunkVector toDecommit;
   {
-    AutoLockGC lock(rt);
+    AutoLockGC lock(this);
 
     // Verify that all entries in the empty chunks pool are already decommitted.
     for (ChunkPool::Iter chunk(emptyChunks(lock)); !chunk.done();
          chunk.next()) {
       MOZ_ASSERT(!chunk->info.numArenasFreeCommitted);
     }
 
     // Since we release the GC lock while doing the decommit syscall below,
@@ -3177,17 +3193,17 @@ void js::gc::BackgroundDecommitTask::set
 
 void js::gc::BackgroundDecommitTask::run() {
   AutoLockGC lock(runtime());
 
   for (Chunk* chunk : toDecommit.ref()) {
     // The arena list is not doubly-linked, so we have to work in the free
     // list order and not in the natural order.
     while (chunk->info.numArenasFreeCommitted) {
-      bool ok = chunk->decommitOneFreeArena(runtime(), lock);
+      bool ok = chunk->decommitOneFreeArena(&runtime()->gc, lock);
 
       // If we are low enough on memory that we can't update the page
       // tables, or if we need to return for any other reason, break out
       // of the loop.
       if (cancel_ || !ok) {
         break;
       }
     }
@@ -3223,42 +3239,34 @@ void GCRuntime::sweepBackgroundThings(Zo
         Arena* arenas = zone->arenas.arenaListsToSweep(kind);
         MOZ_RELEASE_ASSERT(uintptr_t(arenas) != uintptr_t(-1));
         if (arenas) {
           ArenaLists::backgroundFinalize(&fop, arenas, &emptyArenas);
         }
       }
     }
 
-    AutoLockGC lock(rt);
-
     // Release any arenas that are now empty.
     //
     // Periodically drop and reaquire the GC lock every so often to avoid
     // blocking the main thread from allocating chunks.
     //
     // Also use this opportunity to periodically recalculate the GC thresholds
     // as we free more memory.
     static const size_t LockReleasePeriod = 32;
-    size_t releaseCount = 0;
-    Arena* next;
-    for (Arena* arena = emptyArenas; arena; arena = next) {
-      next = arena->next;
-
-      releaseArena(arena, lock);
-      releaseCount++;
-      if (releaseCount % LockReleasePeriod == 0) {
-        lock.unlock();
-        lock.lock();
-        zone->updateGCThresholds(*this, invocationKind, lock);
+
+    while (emptyArenas) {
+      AutoLockGC lock(this);
+      for (size_t i = 0; i < LockReleasePeriod && emptyArenas; i++) {
+        Arena* arena = emptyArenas;
+        emptyArenas = emptyArenas->next;
+        releaseArena(arena, lock);
       }
-    }
-
-    // Do a final update now we've finished.
-    zone->updateGCThresholds(*this, invocationKind, lock);
+      zone->updateGCThresholds(*this, invocationKind, lock);
+    }
   }
 }
 
 void GCRuntime::assertBackgroundSweepingFinished() {
 #ifdef DEBUG
   {
     AutoLockHelperThreadState lock;
     MOZ_ASSERT(backgroundSweepZones.ref().isEmpty());
@@ -4238,17 +4246,17 @@ void js::gc::MarkingValidator::nonIncrem
 
   gc->waitBackgroundSweepEnd();
 
   /* Wait for off-thread parsing which can allocate. */
   HelperThreadState().waitForAllThreads();
 
   /* Save existing mark bits. */
   {
-    AutoLockGC lock(runtime);
+    AutoLockGC lock(gc);
     for (auto chunk = gc->allNonEmptyChunks(lock); !chunk.done();
          chunk.next()) {
       ChunkBitmap* bitmap = &chunk->bitmap;
       auto entry = MakeUnique<ChunkBitmap>();
       if (!entry) {
         return;
       }
 
@@ -4315,17 +4323,17 @@ void js::gc::MarkingValidator::nonIncrem
 
       for (GCZonesIter zone(runtime); !zone.done(); zone.next()) {
         WeakMapBase::unmarkZone(zone);
       }
 
       MOZ_ASSERT(gcmarker->isDrained());
       gcmarker->reset();
 
-      AutoLockGC lock(runtime);
+      AutoLockGC lock(gc);
       for (auto chunk = gc->allNonEmptyChunks(lock); !chunk.done();
            chunk.next()) {
         chunk->bitmap.clear();
       }
     }
   }
 
   {
@@ -4358,17 +4366,17 @@ void js::gc::MarkingValidator::nonIncrem
     for (GCZonesIter zone(runtime); !zone.done(); zone.next()) {
       zone->changeGCState(Zone::MarkBlackAndGray, Zone::MarkBlackOnly);
     }
     MOZ_ASSERT(gc->marker.isDrained());
   }
 
   /* Take a copy of the non-incremental mark state and restore the original. */
   {
-    AutoLockGC lock(runtime);
+    AutoLockGC lock(gc);
     for (auto chunk = gc->allNonEmptyChunks(lock); !chunk.done();
          chunk.next()) {
       ChunkBitmap* bitmap = &chunk->bitmap;
       ChunkBitmap* entry = map.lookup(chunk)->value().get();
       Swap(*entry, *bitmap);
     }
   }
 
@@ -5458,17 +5466,17 @@ IncrementalProgress GCRuntime::endSweepi
     callFinalizeCallbacks(&fop, JSFINALIZE_GROUP_END);
   }
 
   /* Free LIFO blocks on a background thread if possible. */
   startBackgroundFree();
 
   /* Update the GC state for zones we have swept. */
   for (SweepGroupZonesIter zone(rt); !zone.done(); zone.next()) {
-    AutoLockGC lock(rt);
+    AutoLockGC lock(this);
     zone->changeGCState(Zone::Sweep, Zone::Finished);
     zone->updateGCThresholds(*this, invocationKind, lock);
     zone->arenas.unmarkPreMarkedFreeCells();
   }
 
   /*
    * Start background thread to sweep zones if required, sweeping the atoms
    * zone last if present.
@@ -7480,17 +7488,17 @@ void GCRuntime::onOutOfMallocMemory() {
 
   // Make sure we release anything queued for release.
   decommitTask.join();
   nursery().joinDecommitTask();
 
   // Wait for background free of nursery huge slots to finish.
   sweepTask.join();
 
-  AutoLockGC lock(rt);
+  AutoLockGC lock(this);
   onOutOfMallocMemory(lock);
 }
 
 void GCRuntime::onOutOfMallocMemory(const AutoLockGC& lock) {
   // Release any relocated arenas we may be holding on to, without releasing
   // the GC lock.
   releaseHeldRelocatedArenasWithoutUnlocking(lock);
 
--- a/js/src/gc/GCLock.h
+++ b/js/src/gc/GCLock.h
@@ -22,82 +22,87 @@ class AutoUnlockGC;
  *
  * Usually functions will pass const references of this class.  However
  * non-const references can be used to either temporarily release the lock by
  * use of AutoUnlockGC or to start background allocation when the lock is
  * released.
  */
 class MOZ_RAII AutoLockGC {
  public:
-  explicit AutoLockGC(JSRuntime* rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
-      : runtime_(rt) {
+  explicit AutoLockGC(gc::GCRuntime* gc MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+      : gc(gc) {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     lock();
   }
+  explicit AutoLockGC(JSRuntime* rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+      : AutoLockGC(&rt->gc) {
+    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+  }
 
   ~AutoLockGC() { lockGuard_.reset(); }
 
+ protected:
   void lock() {
     MOZ_ASSERT(lockGuard_.isNothing());
-    lockGuard_.emplace(runtime_->gc.lock);
+    lockGuard_.emplace(gc->lock);
   }
 
   void unlock() {
     MOZ_ASSERT(lockGuard_.isSome());
     lockGuard_.reset();
   }
 
- protected:
   js::LockGuard<js::Mutex>& guard() { return lockGuard_.ref(); }
 
-  JSRuntime* runtime() const { return runtime_; }
+  gc::GCRuntime* const gc;
 
  private:
-  JSRuntime* runtime_;
   mozilla::Maybe<js::LockGuard<js::Mutex>> lockGuard_;
   MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 
   AutoLockGC(const AutoLockGC&) = delete;
   AutoLockGC& operator=(const AutoLockGC&) = delete;
+
+  friend class AutoUnlockGC;  // For lock/unlock.
 };
 
 /*
  * Same as AutoLockGC except it can optionally start a background chunk
  * allocation task when the lock is released.
  */
 class MOZ_RAII AutoLockGCBgAlloc : public AutoLockGC {
  public:
-  explicit AutoLockGCBgAlloc(JSRuntime* rt)
-      : AutoLockGC(rt), startBgAlloc(false) {}
+  explicit AutoLockGCBgAlloc(gc::GCRuntime* gc) : AutoLockGC(gc) {}
+  explicit AutoLockGCBgAlloc(JSRuntime* rt) : AutoLockGCBgAlloc(&rt->gc) {}
 
   ~AutoLockGCBgAlloc() {
     unlock();
 
     /*
      * We have to do this after releasing the lock because it may acquire
      * the helper lock which could cause lock inversion if we still held
      * the GC lock.
      */
     if (startBgAlloc) {
-      runtime()->gc.startBackgroundAllocTaskIfIdle();  // Ignore failure.
+      gc->startBackgroundAllocTaskIfIdle();  // Ignore failure.
     }
   }
 
   /*
    * This can be used to start a background allocation task (if one isn't
    * already running) that allocates chunks and makes them available in the
    * free chunks list.  This happens after the lock is released in order to
    * avoid lock inversion.
    */
   void tryToStartBackgroundAllocation() { startBgAlloc = true; }
 
  private:
   // true if we should start a background chunk allocation task after the
   // lock is released.
-  bool startBgAlloc;
+  bool startBgAlloc = false;
 };
 
 class MOZ_RAII AutoUnlockGC {
  public:
   explicit AutoUnlockGC(AutoLockGC& lock MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : lock(lock) {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     lock.unlock();
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -249,19 +249,22 @@ class GCRuntime {
   inline bool upcomingZealousGC();
   inline bool needZealousGC();
   inline bool hasIncrementalTwoSliceZealMode();
 
   MOZ_MUST_USE bool addRoot(Value* vp, const char* name);
   void removeRoot(Value* vp);
   void setMarkStackLimit(size_t limit, AutoLockGC& lock);
 
+  MOZ_MUST_USE bool setParameter(JSGCParamKey key, uint32_t value);
   MOZ_MUST_USE bool setParameter(JSGCParamKey key, uint32_t value,
                                  AutoLockGC& lock);
+  void resetParameter(JSGCParamKey key);
   void resetParameter(JSGCParamKey key, AutoLockGC& lock);
+  uint32_t getParameter(JSGCParamKey key);
   uint32_t getParameter(JSGCParamKey key, const AutoLockGC& lock);
 
   MOZ_MUST_USE bool triggerGC(JS::GCReason reason);
   // Check whether to trigger a zone GC after allocating GC cells. During an
   // incremental GC, optionally count |nbytes| towards the threshold for
   // performing the next slice.
   void maybeAllocTriggerZoneGC(Zone* zone, size_t nbytes = 0);
   // Check whether to trigger a zone GC after malloc memory.
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -23,16 +23,17 @@ class AutoLockGC;
 class AutoLockGCBgAlloc;
 class NurseryDecommitTask;
 
 namespace gc {
 
 class Arena;
 class ArenaCellSet;
 class ArenaList;
+class GCRuntime;
 class SortedArenaList;
 class StoreBuffer;
 class TenuredCell;
 struct Chunk;
 
 /*
  * This flag allows an allocation site to request a specific heap based upon the
  * estimated lifetime or lifetime requirements of objects allocated from that
@@ -776,46 +777,46 @@ struct Chunk {
   }
 
   bool unused() const { return info.numArenasFree == ArenasPerChunk; }
 
   bool hasAvailableArenas() const { return info.numArenasFree != 0; }
 
   bool isNurseryChunk() const { return trailer.storeBuffer; }
 
-  Arena* allocateArena(JSRuntime* rt, JS::Zone* zone, AllocKind kind,
+  Arena* allocateArena(GCRuntime* gc, JS::Zone* zone, AllocKind kind,
                        const AutoLockGC& lock);
 
-  void releaseArena(JSRuntime* rt, Arena* arena, const AutoLockGC& lock);
+  void releaseArena(GCRuntime* gc, Arena* arena, const AutoLockGC& lock);
   void recycleArena(Arena* arena, SortedArenaList& dest, size_t thingsPerArena);
 
-  MOZ_MUST_USE bool decommitOneFreeArena(JSRuntime* rt, AutoLockGC& lock);
+  MOZ_MUST_USE bool decommitOneFreeArena(GCRuntime* gc, AutoLockGC& lock);
   void decommitAllArenas();
 
   // This will decommit each unused not-already decommitted arena. It performs a
   // system call for each arena but is only used during OOM.
   void decommitFreeArenasWithoutUnlocking(const AutoLockGC& lock);
 
-  static Chunk* allocate(JSRuntime* rt);
-  void init(JSRuntime* rt);
+  static Chunk* allocate(GCRuntime* gc);
+  void init(GCRuntime* gc);
 
  private:
   /* Search for a decommitted arena to allocate. */
   unsigned findDecommittedArenaOffset();
   Arena* fetchNextDecommittedArena();
 
-  void addArenaToFreeList(JSRuntime* rt, Arena* arena);
+  void addArenaToFreeList(GCRuntime* gc, Arena* arena);
   void addArenaToDecommittedList(const Arena* arena);
 
-  void updateChunkListAfterAlloc(JSRuntime* rt, const AutoLockGC& lock);
-  void updateChunkListAfterFree(JSRuntime* rt, const AutoLockGC& lock);
+  void updateChunkListAfterAlloc(GCRuntime* gc, const AutoLockGC& lock);
+  void updateChunkListAfterFree(GCRuntime* gc, const AutoLockGC& lock);
 
  public:
   /* Unlink and return the freeArenasHead. */
-  Arena* fetchNextFreeArena(JSRuntime* rt);
+  Arena* fetchNextFreeArena(GCRuntime* gc);
 };
 
 static_assert(
     sizeof(Chunk) == ChunkSize,
     "Ensure the hardcoded chunk size definition actually matches the struct.");
 static_assert(js::gc::ChunkMarkBitmapOffset == offsetof(Chunk, bitmap),
               "The hardcoded API bitmap offset must match the actual offset.");
 static_assert(js::gc::ChunkRuntimeOffset ==
--- a/js/src/gc/Nursery-inl.h
+++ b/js/src/gc/Nursery-inl.h
@@ -13,16 +13,18 @@
 #include "gc/Heap.h"
 #include "gc/RelocationOverlay.h"
 #include "gc/Zone.h"
 #include "js/TracingAPI.h"
 #include "vm/JSContext.h"
 #include "vm/Runtime.h"
 #include "vm/SharedMem.h"
 
+inline JSRuntime* js::Nursery::runtime() const { return gc->rt; }
+
 template <typename T>
 bool js::Nursery::isInside(const SharedMem<T>& p) const {
   return isInside(p.unwrap(/*safe - used for value in comparison above*/));
 }
 
 MOZ_ALWAYS_INLINE /* static */ bool js::Nursery::getForwardedPointer(
     js::gc::Cell** ref) {
   js::gc::Cell* cell = (*ref);
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -65,17 +65,17 @@ struct NurseryChunk {
 
   // The end of the range is always ChunkSize - ArenaSize.
   void markPagesUnusedHard(size_t from);
   // The start of the range is always the beginning of the chunk.
   MOZ_MUST_USE bool markPagesInUseHard(size_t to);
 
   uintptr_t start() const { return uintptr_t(&data); }
   uintptr_t end() const { return uintptr_t(&trailer); }
-  gc::Chunk* toChunk(JSRuntime* rt);
+  gc::Chunk* toChunk(GCRuntime* gc);
 };
 static_assert(sizeof(js::NurseryChunk) == gc::ChunkSize,
               "Nursery chunk size must match gc::Chunk size.");
 
 }  // namespace js
 
 inline void js::NurseryChunk::poisonAndInit(JSRuntime* rt, size_t size) {
   poisonRange(0, size, JS_FRESH_NURSERY_PATTERN, MemCheckKind::MakeUndefined);
@@ -113,26 +113,26 @@ inline bool js::NurseryChunk::markPagesI
   return MarkPagesInUseHard(reinterpret_cast<void*>(start()), to);
 }
 
 // static
 inline js::NurseryChunk* js::NurseryChunk::fromChunk(Chunk* chunk) {
   return reinterpret_cast<NurseryChunk*>(chunk);
 }
 
-inline Chunk* js::NurseryChunk::toChunk(JSRuntime* rt) {
+inline Chunk* js::NurseryChunk::toChunk(GCRuntime* gc) {
   auto chunk = reinterpret_cast<Chunk*>(this);
-  chunk->init(rt);
+  chunk->init(gc);
   return chunk;
 }
 
 void js::NurseryDecommitTask::queueChunk(
     NurseryChunk* nchunk, const AutoLockHelperThreadState& lock) {
   // Using the chunk pointers to build the queue is infallible.
-  Chunk* chunk = nchunk->toChunk(runtime());
+  Chunk* chunk = nchunk->toChunk(&runtime()->gc);
   chunk->info.prev = nullptr;
   chunk->info.next = queue;
   queue = chunk;
 }
 
 void js::NurseryDecommitTask::queueRange(
     size_t newCapacity, NurseryChunk& newChunk,
     const AutoLockHelperThreadState& lock) {
@@ -185,18 +185,19 @@ void js::NurseryDecommitTask::run() {
 
     setFinishing(lock);
   }
 }
 
 void js::NurseryDecommitTask::decommitChunk(Chunk* chunk) {
   chunk->decommitAllArenas();
   {
-    AutoLockGC lock(runtime());
-    runtime()->gc.recycleChunk(chunk, lock);
+    GCRuntime* gc = &runtime()->gc;
+    AutoLockGC lock(gc);
+    gc->recycleChunk(chunk, lock);
   }
 }
 
 void js::NurseryDecommitTask::decommitRange(AutoLockHelperThreadState& lock) {
   // Clear this field here before releasing the lock. While the lock is
   // released the main thread may make new decommit requests or update the range
   // of the current requested chunk, but it won't attempt to use any
   // might-be-decommitted-soon memory.
@@ -204,32 +205,32 @@ void js::NurseryDecommitTask::decommitRa
   size_t thisPartialCapacity = partialCapacity;
   partialChunk = nullptr;
   {
     AutoUnlockHelperThreadState unlock(lock);
     thisPartialChunk->markPagesUnusedHard(thisPartialCapacity);
   }
 }
 
-js::Nursery::Nursery(JSRuntime* rt)
-    : runtime_(rt),
+js::Nursery::Nursery(GCRuntime* gc)
+    : gc(gc),
       position_(0),
       currentStartChunk_(0),
       currentStartPosition_(0),
       currentEnd_(0),
       currentStringEnd_(0),
       currentChunk_(0),
       capacity_(0),
       timeInChunkAlloc_(0),
       profileThreshold_(0),
       enableProfiling_(false),
       canAllocateStrings_(true),
       reportTenurings_(0),
       minorGCTriggerReason_(JS::GCReason::NO_REASON),
-      decommitTask(rt)
+      decommitTask(gc->rt)
 #ifdef JS_GC_ZEAL
       ,
       lastCanary_(nullptr)
 #endif
 {
   const char* env = getenv("MOZ_NURSERY_STRINGS");
   if (env && *env) {
     canAllocateStrings_ = (*env == '1');
@@ -273,52 +274,52 @@ bool js::Nursery::init(AutoLockGCBgAlloc
               "JS_GC_REPORT_TENURING=N\n"
               "\tAfter a minor GC, report any ObjectGroups with at least N "
               "instances tenured.\n");
       exit(0);
     }
     reportTenurings_ = atoi(env);
   }
 
-  if (!runtime()->gc.storeBuffer().enable()) {
+  if (!gc->storeBuffer().enable()) {
     return false;
   }
 
   MOZ_ASSERT(isEnabled());
   return true;
 }
 
 js::Nursery::~Nursery() { disable(); }
 
 void js::Nursery::enable() {
   MOZ_ASSERT(isEmpty());
-  MOZ_ASSERT(!runtime()->gc.isVerifyPreBarriersEnabled());
+  MOZ_ASSERT(!gc->isVerifyPreBarriersEnabled());
   if (isEnabled() || mozilla::recordreplay::IsRecordingOrReplaying()) {
     return;
   }
 
   {
-    AutoLockGCBgAlloc lock(runtime());
+    AutoLockGCBgAlloc lock(gc);
     capacity_ = roundSize(tunables().gcMinNurseryBytes());
     if (!allocateNextChunk(0, lock)) {
       capacity_ = 0;
       return;
     }
   }
 
   setCurrentChunk(0);
   setStartPosition();
   poisonAndInitCurrentChunk();
 #ifdef JS_GC_ZEAL
-  if (runtime()->hasZealMode(ZealMode::GenerationalGC)) {
+  if (gc->hasZealMode(ZealMode::GenerationalGC)) {
     enterZealMode();
   }
 #endif
 
-  MOZ_ALWAYS_TRUE(runtime()->gc.storeBuffer().enable());
+  MOZ_ALWAYS_TRUE(gc->storeBuffer().enable());
 }
 
 void js::Nursery::disable() {
   MOZ_ASSERT(isEmpty());
   if (!isEnabled()) {
     return;
   }
 
@@ -328,17 +329,17 @@ void js::Nursery::disable() {
   freeChunksFrom(0);
   capacity_ = 0;
 
   // We must reset currentEnd_ so that there is no space for anything in the
   // nursery. JIT'd code uses this even if the nursery is disabled.
   currentEnd_ = 0;
   currentStringEnd_ = 0;
   position_ = 0;
-  runtime()->gc.storeBuffer().disable();
+  gc->storeBuffer().disable();
 
   decommitTask.join();
 }
 
 void js::Nursery::enableStrings() {
   MOZ_ASSERT(isEmpty());
   canAllocateStrings_ = true;
   currentStringEnd_ = currentEnd_;
@@ -350,17 +351,17 @@ void js::Nursery::disableStrings() {
   currentStringEnd_ = 0;
 }
 
 bool js::Nursery::isEmpty() const {
   if (!isEnabled()) {
     return true;
   }
 
-  if (!runtime()->hasZealMode(ZealMode::GenerationalGC)) {
+  if (!gc->hasZealMode(ZealMode::GenerationalGC)) {
     MOZ_ASSERT(currentStartChunk_ == 0);
     MOZ_ASSERT(currentStartPosition_ == chunk(0).start());
   }
   return position() == currentStartPosition_;
 }
 
 #ifdef JS_GC_ZEAL
 void js::Nursery::enterZealMode() {
@@ -464,32 +465,32 @@ void* js::Nursery::allocate(size_t size)
   MOZ_ASSERT_IF(currentChunk_ == currentStartChunk_,
                 position() >= currentStartPosition_);
   MOZ_ASSERT(position() % CellAlignBytes == 0);
   MOZ_ASSERT(size % CellAlignBytes == 0);
 
 #ifdef JS_GC_ZEAL
   static const size_t CanarySize =
       (sizeof(Nursery::Canary) + CellAlignBytes - 1) & ~CellAlignMask;
-  if (runtime()->gc.hasZealMode(ZealMode::CheckNursery)) {
+  if (gc->hasZealMode(ZealMode::CheckNursery)) {
     size += CanarySize;
   }
 #endif
 
   if (currentEnd() < position() + size) {
     unsigned chunkno = currentChunk_ + 1;
     MOZ_ASSERT(chunkno <= maxChunkCount());
     MOZ_ASSERT(chunkno <= allocatedChunkCount());
     if (chunkno == maxChunkCount()) {
       return nullptr;
     }
     if (MOZ_UNLIKELY(chunkno == allocatedChunkCount())) {
       mozilla::TimeStamp start = ReallyNow();
       {
-        AutoLockGCBgAlloc lock(runtime());
+        AutoLockGCBgAlloc lock(gc);
         if (!allocateNextChunk(chunkno, lock)) {
           return nullptr;
         }
       }
       timeInChunkAlloc_ += ReallyNow() - start;
       MOZ_ASSERT(chunkno < allocatedChunkCount());
     }
     setCurrentChunk(chunkno);
@@ -502,17 +503,17 @@ void* js::Nursery::allocate(size_t size)
   // just as much to count it, as to check the profiler's state and decide not
   // to count it.
   stats().noteNurseryAlloc();
 
   DebugOnlyPoison(thing, JS_ALLOCATED_NURSERY_PATTERN, size,
                   MemCheckKind::MakeUndefined);
 
 #ifdef JS_GC_ZEAL
-  if (runtime()->gc.hasZealMode(ZealMode::CheckNursery)) {
+  if (gc->hasZealMode(ZealMode::CheckNursery)) {
     auto canary = reinterpret_cast<Canary*>(position() - CanarySize);
     canary->magicValue = CanaryMagicValue;
     canary->next = nullptr;
     if (lastCanary_) {
       MOZ_ASSERT(!lastCanary_->next);
       lastCanary_->next = canary;
     }
     lastCanary_ = canary;
@@ -803,17 +804,17 @@ void js::Nursery::printProfileDurations(
     fprintf(stderr, " %6" PRIi64, static_cast<int64_t>(time.ToMicroseconds()));
   }
   fprintf(stderr, "\n");
 }
 
 void js::Nursery::printTotalProfileTimes() {
   if (enableProfiling_) {
     fprintf(stderr, "MinorGC TOTALS: %7" PRIu64 " collections:             ",
-            runtime()->gc.minorGCCount());
+            gc->minorGCCount());
     printProfileDurations(totalDurations_);
   }
 }
 
 void js::Nursery::maybeClearProfileDurations() {
   for (auto& duration : profileDurations_) {
     duration = mozilla::TimeDuration();
   }
@@ -882,25 +883,25 @@ void js::Nursery::collect(JS::GCReason r
 
   mozilla::recordreplay::AutoDisallowThreadEvents disallow;
 
   if (!isEnabled() || isEmpty()) {
     // Our barriers are not always exact, and there may be entries in the
     // storebuffer even when the nursery is disabled or empty. It's not safe
     // to keep these entries as they may refer to tenured cells which may be
     // freed after this point.
-    rt->gc.storeBuffer().clear();
+    gc->storeBuffer().clear();
   }
 
   if (!isEnabled()) {
     return;
   }
 
 #ifdef JS_GC_ZEAL
-  if (rt->gc.hasZealMode(ZealMode::CheckNursery)) {
+  if (gc->hasZealMode(ZealMode::CheckNursery)) {
     for (auto canary = lastCanary_; canary; canary = canary->next) {
       MOZ_ASSERT(canary->magicValue == CanaryMagicValue);
     }
   }
   lastCanary_ = nullptr;
 #endif
 
   stats().beginNurseryCollection(reason);
@@ -943,22 +944,22 @@ void js::Nursery::collect(JS::GCReason r
     poisonAndInitCurrentChunk(previousGC.nurseryUsedBytes);
   }
 
   const float promotionRate = doPretenuring(rt, reason, tenureCounts);
 
   // We ignore gcMaxBytes when allocating for minor collection. However, if we
   // overflowed, we disable the nursery. The next time we allocate, we'll fail
   // because bytes >= gcMaxBytes.
-  if (rt->gc.heapSize.bytes() >= tunables().gcMaxBytes()) {
+  if (gc->heapSize.bytes() >= tunables().gcMaxBytes()) {
     disable();
   }
 
   endProfile(ProfileKey::Total);
-  rt->gc.incMinorGcNumber();
+  gc->incMinorGcNumber();
 
   TimeDuration totalTime = profileDurations_[ProfileKey::Total];
   rt->addTelemetry(JS_TELEMETRY_GC_MINOR_US, totalTime.ToMicroseconds());
   rt->addTelemetry(JS_TELEMETRY_GC_MINOR_REASON, uint32_t(reason));
   if (totalTime.ToMilliseconds() > 1.0) {
     rt->addTelemetry(JS_TELEMETRY_GC_MINOR_REASON_LONG, uint32_t(reason));
   }
   rt->addTelemetry(JS_TELEMETRY_GC_NURSERY_BYTES, committed());
@@ -998,17 +999,17 @@ void js::Nursery::doCollection(JS::GCRea
 
   const size_t initialNurseryCapacity = capacity();
   const size_t initialNurseryUsedBytes = usedSpace();
 
   // Move objects pointed to by roots from the nursery to the major heap.
   TenuringTracer mover(rt, this);
 
   // Mark the store buffer. This must happen first.
-  StoreBuffer& sb = runtime()->gc.storeBuffer();
+  StoreBuffer& sb = gc->storeBuffer();
 
   // The MIR graph only contains nursery pointers if cancelIonCompilations()
   // is set on the store buffer, in which case we cancel all compilations
   // of such graphs.
   startProfile(ProfileKey::CancelIonCompilations);
   if (sb.cancelIonCompilations()) {
     js::CancelOffThreadIonCompilesUsingNurseryPointers(rt);
   }
@@ -1030,28 +1031,28 @@ void js::Nursery::doCollection(JS::GCRea
   sb.traceWholeCells(mover);
   endProfile(ProfileKey::TraceWholeCells);
 
   startProfile(ProfileKey::TraceGenericEntries);
   sb.traceGenericEntries(&mover);
   endProfile(ProfileKey::TraceGenericEntries);
 
   startProfile(ProfileKey::MarkRuntime);
-  rt->gc.traceRuntimeForMinorGC(&mover, session);
+  gc->traceRuntimeForMinorGC(&mover, session);
   endProfile(ProfileKey::MarkRuntime);
 
   startProfile(ProfileKey::MarkDebugger);
   {
     gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_ROOTS);
     DebugAPI::traceAllForMovingGC(&mover);
   }
   endProfile(ProfileKey::MarkDebugger);
 
   startProfile(ProfileKey::SweepCaches);
-  rt->gc.purgeRuntimeForMinorGC();
+  gc->purgeRuntimeForMinorGC();
   endProfile(ProfileKey::SweepCaches);
 
   // Most of the work is done here. This loop iterates over objects that have
   // been moved to the major heap. If these objects have any outgoing pointers
   // to the nursery, then those nursery objects get moved as well, until no
   // objects are left to move. That is, we iterate to a fixed point.
   startProfile(ProfileKey::CollectToFP);
   collectToFixedPoint(mover, tenureCounts);
@@ -1065,36 +1066,36 @@ void js::Nursery::doCollection(JS::GCRea
 
   // Update any slot or element pointers whose destination has been tenured.
   startProfile(ProfileKey::UpdateJitActivations);
   js::jit::UpdateJitActivationsForMinorGC(rt);
   forwardedBuffers.clearAndCompact();
   endProfile(ProfileKey::UpdateJitActivations);
 
   startProfile(ProfileKey::ObjectsTenuredCallback);
-  rt->gc.callObjectsTenuredCallback();
+  gc->callObjectsTenuredCallback();
   endProfile(ProfileKey::ObjectsTenuredCallback);
 
   // Sweep.
   startProfile(ProfileKey::FreeMallocedBuffers);
-  rt->gc.queueBuffersForFreeAfterMinorGC(mallocedBuffers);
+  gc->queueBuffersForFreeAfterMinorGC(mallocedBuffers);
   endProfile(ProfileKey::FreeMallocedBuffers);
 
   startProfile(ProfileKey::ClearNursery);
   clear();
   endProfile(ProfileKey::ClearNursery);
 
   startProfile(ProfileKey::ClearStoreBuffer);
-  runtime()->gc.storeBuffer().clear();
+  gc->storeBuffer().clear();
   endProfile(ProfileKey::ClearStoreBuffer);
 
   // Make sure hashtables have been updated after the collection.
   startProfile(ProfileKey::CheckHashTables);
 #ifdef JS_GC_ZEAL
-  if (rt->hasZealMode(ZealMode::CheckHashTablesOnMinorGC)) {
+  if (gc->hasZealMode(ZealMode::CheckHashTablesOnMinorGC)) {
     CheckHashTablesAfterMovingGC(rt);
   }
 #endif
   endProfile(ProfileKey::CheckHashTables);
 
   previousGC.reason = reason;
   previousGC.nurseryCapacity = initialNurseryCapacity;
   previousGC.nurseryCommitted = spaceToEnd(allocatedChunkCount());
@@ -1218,17 +1219,17 @@ void js::Nursery::sweep(JSTracer* trc) {
 
   sweepDictionaryModeObjects();
   sweepMapAndSetObjects();
 }
 
 void js::Nursery::clear() {
   // Poison the nursery contents so touching a freed object will crash.
   unsigned firstClearChunk;
-  if (runtime()->hasZealMode(ZealMode::GenerationalGC)) {
+  if (gc->hasZealMode(ZealMode::GenerationalGC)) {
     // Poison all the chunks used in this cycle. The new start chunk is
     // reposioned in Nursery::collect() but there's no point optimising that in
     // this case.
     firstClearChunk = currentStartChunk_;
   } else {
     // In normal mode we start at the second chunk, the first one will be used
     // in the next cycle and poisoned in Nusery::collect();
     MOZ_ASSERT(currentStartChunk_ == 0);
@@ -1242,18 +1243,18 @@ void js::Nursery::clear() {
   if (currentChunk_ >= firstClearChunk) {
     chunk(currentChunk_)
         .poisonAfterEvict(position() - chunk(currentChunk_).start());
   }
 
   // Reset the start chunk & position if we're not in this zeal mode, or we're
   // in it and close to the end of the nursery.
   MOZ_ASSERT(maxChunkCount() > 0);
-  if (!runtime()->hasZealMode(ZealMode::GenerationalGC) ||
-      (runtime()->hasZealMode(ZealMode::GenerationalGC) &&
+  if (!gc->hasZealMode(ZealMode::GenerationalGC) ||
+      (gc->hasZealMode(ZealMode::GenerationalGC) &&
        currentChunk_ + 1 == maxChunkCount())) {
     setCurrentChunk(0);
   }
 
   // Set current start position for isEmpty checks.
   setStartPosition();
 }
 
@@ -1294,17 +1295,17 @@ MOZ_ALWAYS_INLINE void js::Nursery::setC
   MOZ_ASSERT(chunkno < allocatedChunkCount());
 
   currentChunk_ = chunkno;
   position_ = chunk(chunkno).start();
   setCurrentEnd();
 }
 
 void js::Nursery::poisonAndInitCurrentChunk(size_t extent) {
-  if (runtime()->hasZealMode(ZealMode::GenerationalGC) || !isSubChunkMode()) {
+  if (gc->hasZealMode(ZealMode::GenerationalGC) || !isSubChunkMode()) {
     chunk(currentChunk_).poisonAndInit(runtime());
   } else {
     extent = Min(capacity_, extent);
     MOZ_ASSERT(extent <= NurseryChunkUsableSize);
     chunk(currentChunk_).poisonAndInit(runtime(), extent);
   }
 }
 
@@ -1328,17 +1329,17 @@ bool js::Nursery::allocateNextChunk(cons
   MOZ_ASSERT(chunkno == allocatedChunkCount());
   MOZ_ASSERT(chunkno < JS_HOWMANY(capacity(), ChunkSize));
 
   if (!chunks_.resize(newCount)) {
     return false;
   }
 
   Chunk* newChunk;
-  newChunk = runtime()->gc.getOrAllocChunk(lock);
+  newChunk = gc->getOrAllocChunk(lock);
   if (!newChunk) {
     chunks_.shrinkTo(priorCount);
     return false;
   }
 
   chunks_[chunkno] = NurseryChunk::fromChunk(newChunk);
   return true;
 }
@@ -1398,24 +1399,24 @@ void js::Nursery::maybeResizeNursery(JS:
              promotionRate < ShrinkThreshold && newCapacity < capacity()) {
     shrinkAllocableSpace(newCapacity);
   }
 }
 
 bool js::Nursery::maybeResizeExact(JS::GCReason reason) {
   // Shrink the nursery to its minimum size if we ran out of memory or
   // received a memory pressure event.
-  if (gc::IsOOMReason(reason) || runtime()->gc.systemHasLowMemory()) {
+  if (gc::IsOOMReason(reason) || gc->systemHasLowMemory()) {
     minimizeAllocableSpace();
     return true;
   }
 
 #ifdef JS_GC_ZEAL
   // This zeal mode disabled nursery resizing.
-  if (runtime()->hasZealMode(ZealMode::GenerationalGC)) {
+  if (gc->hasZealMode(ZealMode::GenerationalGC)) {
     return true;
   }
 #endif
 
   MOZ_ASSERT(tunables().gcMaxNurseryBytes() >= ArenaSize);
   const size_t newMaxNurseryBytes = roundSize(tunables().gcMaxNurseryBytes());
   MOZ_ASSERT(newMaxNurseryBytes >= ArenaSize);
 
@@ -1508,17 +1509,17 @@ void js::Nursery::freeChunksFrom(const u
     decommitTask.startOrRunIfIdle(lock);
   }
 
   chunks_.shrinkTo(firstFreeChunk);
 }
 
 void js::Nursery::shrinkAllocableSpace(size_t newCapacity) {
 #ifdef JS_GC_ZEAL
-  if (runtime()->hasZealMode(ZealMode::GenerationalGC)) {
+  if (gc->hasZealMode(ZealMode::GenerationalGC)) {
     return;
   }
 #endif
 
   // Don't shrink the nursery to zero (use Nursery::disable() instead)
   // This can't happen due to the rounding-down performed above because of the
   // clamping in maybeResizeNursery().
   MOZ_ASSERT(newCapacity != 0);
@@ -1564,23 +1565,21 @@ uintptr_t js::Nursery::currentEnd() cons
   // failed.
   MOZ_ASSERT_IF(isSubChunkMode(), currentChunk_ == 0);
   MOZ_ASSERT_IF(isSubChunkMode(), currentEnd_ <= chunk(currentChunk_).end());
   MOZ_ASSERT_IF(!isSubChunkMode(), currentEnd_ == chunk(currentChunk_).end());
   MOZ_ASSERT(currentEnd_ != chunk(currentChunk_).start());
   return currentEnd_;
 }
 
-gcstats::Statistics& js::Nursery::stats() const {
-  return runtime()->gc.stats();
-}
+gcstats::Statistics& js::Nursery::stats() const { return gc->stats(); }
 
 MOZ_ALWAYS_INLINE const js::gc::GCSchedulingTunables& js::Nursery::tunables()
     const {
-  return runtime()->gc.tunables;
+  return gc->tunables;
 }
 
 bool js::Nursery::isSubChunkMode() const {
   return capacity() <= NurseryChunkUsableSize;
 }
 
 void js::Nursery::sweepDictionaryModeObjects() {
   for (auto obj : dictionaryModeObjects_) {
@@ -1589,32 +1588,32 @@ void js::Nursery::sweepDictionaryModeObj
     } else {
       Forwarded(obj)->updateDictionaryListPointerAfterMinorGC(obj);
     }
   }
   dictionaryModeObjects_.clear();
 }
 
 void js::Nursery::sweepMapAndSetObjects() {
-  auto fop = runtime_->defaultFreeOp();
+  auto fop = runtime()->defaultFreeOp();
 
   for (auto mapobj : mapsWithNurseryMemory_) {
     MapObject::sweepAfterMinorGC(fop, mapobj);
   }
   mapsWithNurseryMemory_.clearAndFree();
 
   for (auto setobj : setsWithNurseryMemory_) {
     SetObject::sweepAfterMinorGC(fop, setobj);
   }
   setsWithNurseryMemory_.clearAndFree();
 }
 
 JS_PUBLIC_API void JS::EnableNurseryStrings(JSContext* cx) {
   AutoEmptyNursery empty(cx);
-  ReleaseAllJITCode(cx->runtime()->defaultFreeOp());
+  ReleaseAllJITCode(cx->defaultFreeOp());
   cx->runtime()->gc.nursery().enableStrings();
 }
 
 JS_PUBLIC_API void JS::DisableNurseryStrings(JSContext* cx) {
   AutoEmptyNursery empty(cx);
-  ReleaseAllJITCode(cx->runtime()->defaultFreeOp());
+  ReleaseAllJITCode(cx->defaultFreeOp());
   cx->runtime()->gc.nursery().disableStrings();
 }
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -175,17 +175,17 @@ class Nursery {
 
   struct StringLayout {
     JS::Zone* zone;
     CellAlignedByte cell;
   };
 
   using BufferSet = HashSet<void*, PointerHasher<void*>, SystemAllocPolicy>;
 
-  explicit Nursery(JSRuntime* rt);
+  explicit Nursery(gc::GCRuntime* gc);
   ~Nursery();
 
   MOZ_MUST_USE bool init(AutoLockGCBgAlloc& lock);
 
   // Number of allocated (ready to use) chunks.
   unsigned allocatedChunkCount() const { return chunks_.length(); }
 
   // Total number of chunks and the capacity of the nursery. Chunks will be
@@ -399,17 +399,17 @@ class Nursery {
 
   // The amount of space in the mapped nursery available to allocations.
   static const size_t NurseryChunkUsableSize =
       gc::ChunkSize - gc::ChunkTrailerSize;
 
   void joinDecommitTask() { decommitTask.join(); }
 
  private:
-  JSRuntime* runtime_;
+  gc::GCRuntime* const gc;
 
   // Vector of allocated chunks to allocate from.
   Vector<NurseryChunk*, 0, SystemAllocPolicy> chunks_;
 
   // Pointer to the first unallocated byte in the nursery.
   uintptr_t position_;
 
   // These fields refer to the beginning of the nursery. They're normally 0
@@ -552,17 +552,17 @@ class Nursery {
                                       AutoLockGCBgAlloc& lock);
 
   MOZ_ALWAYS_INLINE uintptr_t currentEnd() const;
 
   uintptr_t position() const { return position_; }
 
   MOZ_ALWAYS_INLINE bool isSubChunkMode() const;
 
-  JSRuntime* runtime() const { return runtime_; }
+  JSRuntime* runtime() const;
   gcstats::Statistics& stats() const;
 
   const js::gc::GCSchedulingTunables& tunables() const;
 
   // Common internal allocator function.
   void* allocate(size_t size);
 
   void doCollection(JS::GCReason reason, gc::TenureCountCache& tenureCounts);
--- a/js/src/gc/Verifier.cpp
+++ b/js/src/gc/Verifier.cpp
@@ -195,17 +195,17 @@ void gc::GCRuntime::startVerifyPreBarrie
   VerifyPreTracer* trc = js_new<VerifyPreTracer>(rt);
   if (!trc) {
     return;
   }
 
   AutoPrepareForTracing prep(cx);
 
   {
-    AutoLockGC lock(cx->runtime());
+    AutoLockGC lock(this);
     for (auto chunk = allNonEmptyChunks(lock); !chunk.done(); chunk.next()) {
       chunk->bitmap.clear();
     }
   }
 
   gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::TRACE_HEAP);
 
   const size_t size = 64 * 1024 * 1024;
--- a/js/src/jit/BaselineCodeGen.cpp
+++ b/js/src/jit/BaselineCodeGen.cpp
@@ -1299,58 +1299,123 @@ bool BaselineCompilerCodeGen::emitWarmUp
     }
 
     if (!LoopEntryCanIonOsr(pc)) {
       // OSR into Ion not possible at this loop entry.
       return true;
     }
   }
 
-  Label skipCall;
+  Label done;
 
   const OptimizationInfo* info =
       IonOptimizations.get(IonOptimizations.firstLevel());
   uint32_t warmUpThreshold = info->compilerWarmUpThreshold(script, pc);
-  masm.branch32(Assembler::LessThan, countReg, Imm32(warmUpThreshold),
-                &skipCall);
+  masm.branch32(Assembler::LessThan, countReg, Imm32(warmUpThreshold), &done);
 
   // Do nothing if Ion is already compiling this script off-thread or if Ion has
   // been disabled for this script.
   masm.movePtr(ImmPtr(script->jitScript()), scriptReg);
   masm.loadPtr(Address(scriptReg, JitScript::offsetOfIonScript()), scriptReg);
   masm.branchPtr(Assembler::Equal, scriptReg, ImmPtr(IonCompilingScriptPtr),
-                 &skipCall);
+                 &done);
   masm.branchPtr(Assembler::Equal, scriptReg, ImmPtr(IonDisabledScriptPtr),
-                 &skipCall);
+                 &done);
 
   // 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 for this expects
-    // the frame size in R0.scratchReg().
+    // Try to OSR into Ion.
     computeFrameSize(R0.scratchReg());
-    if (!emitNextIC()) {
+
+    prepareVMCall();
+
+    pushBytecodePCArg();
+    pushArg(R0.scratchReg());
+    masm.PushBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
+
+    using Fn = bool (*)(JSContext*, BaselineFrame*, uint32_t, jsbytecode*,
+                        IonOsrTempData**);
+    if (!callVM<Fn, IonCompileScriptForBaselineOSR>()) {
       return false;
     }
+
+    // The return register holds the IonOsrTempData*. Perform OSR if it's not
+    // nullptr.
+    static_assert(ReturnReg != OsrFrameReg,
+                  "Code below depends on osrDataReg != OsrFrameReg");
+    Register osrDataReg = ReturnReg;
+    masm.branchTestPtr(Assembler::Zero, osrDataReg, osrDataReg, &done);
+
+    // Success! Switch from Baseline JIT code to Ion JIT code.
+
+    // At this point, stack looks like:
+    //
+    //  +-> [...Calling-Frame...]
+    //  |   [...Actual-Args/ThisV/ArgCount/Callee...]
+    //  |   [Descriptor]
+    //  |   [Return-Addr]
+    //  +---[Saved-FramePtr]
+    //      [...Baseline-Frame...]
+
+    // Restore the stack pointer so that the return address is on top of
+    // the stack.
+    masm.addToStackPtr(Imm32(frame.frameSize()));
+
+#ifdef DEBUG
+    // Get a scratch register that's not osrDataReg or OsrFrameReg.
+    AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
+    regs.take(BaselineFrameReg);
+    regs.take(osrDataReg);
+    regs.take(OsrFrameReg);
+
+    Register scratchReg = regs.takeAny();
+
+    // If profiler instrumentation is on, ensure that lastProfilingFrame is
+    // the frame currently being OSR-ed
+    {
+      Label checkOk;
+      AbsoluteAddress addressOfEnabled(
+          cx->runtime()->geckoProfiler().addressOfEnabled());
+      masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &checkOk);
+      masm.loadPtr(AbsoluteAddress((void*)&cx->jitActivation), scratchReg);
+      masm.loadPtr(
+          Address(scratchReg, JitActivation::offsetOfLastProfilingFrame()),
+          scratchReg);
+
+      // It may be the case that we entered the baseline frame with
+      // profiling turned off on, then in a call within a loop (i.e. a
+      // callee frame), turn on profiling, then return to this frame,
+      // and then OSR with profiling turned on.  In this case, allow for
+      // lastProfilingFrame to be null.
+      masm.branchPtr(Assembler::Equal, scratchReg, ImmWord(0), &checkOk);
+
+      masm.branchStackPtr(Assembler::Equal, scratchReg, &checkOk);
+      masm.assumeUnreachable("Baseline OSR lastProfilingFrame mismatch.");
+      masm.bind(&checkOk);
+    }
+#endif
+
+    // Jump into Ion.
+    masm.loadPtr(Address(osrDataReg, IonOsrTempData::offsetOfBaselineFrame()),
+                 OsrFrameReg);
+    masm.jump(Address(osrDataReg, IonOsrTempData::offsetOfJitCode()));
   } 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();
 
     masm.PushBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
 
     const RetAddrEntry::Kind kind = RetAddrEntry::Kind::WarmupCounter;
 
     using Fn = bool (*)(JSContext*, BaselineFrame*);
     if (!callVM<Fn, IonCompileScriptForBaselineAtEntry>(kind)) {
       return false;
     }
   }
-  masm.bind(&skipCall);
-
+
+  masm.bind(&done);
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emitWarmUpCounterIncrement() {
   Register scriptReg = R2.scratchReg();
   Register countReg = R0.scratchReg();
 
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -303,24 +303,16 @@ bool JitScript::initICEntriesAndBytecode
       case JSOP_STRICTEQ:
       case JSOP_STRICTNE: {
         ICStub* stub = alloc.newStub<ICCompare_Fallback>(Kind::Compare);
         if (!addIC(loc, stub)) {
           return false;
         }
         break;
       }
-      case JSOP_LOOPENTRY: {
-        ICStub* stub =
-            alloc.newStub<ICWarmUpCounter_Fallback>(Kind::WarmUpCounter);
-        if (!addIC(loc, stub)) {
-          return false;
-        }
-        break;
-      }
       case JSOP_NEWARRAY: {
         ObjectGroup* group = ObjectGroup::allocationSiteGroup(
             cx, script, loc.toRawBytecode(), JSProto_Array);
         if (!group) {
           return false;
         }
         ICStub* stub =
             alloc.newStub<ICNewArray_Fallback>(Kind::NewArray, group);
@@ -568,17 +560,16 @@ void ICStubIterator::unlink(JSContext* c
 
 /* static */
 bool ICStub::NonCacheIRStubMakesGCCalls(Kind kind) {
   MOZ_ASSERT(IsValidKind(kind));
   MOZ_ASSERT(!IsCacheIRKind(kind));
 
   switch (kind) {
     case Call_Fallback:
-    case WarmUpCounter_Fallback:
     // These three fallback stubs don't actually make non-tail calls,
     // but the fallback code for the bailout path needs to pop the stub frame
     // pushed during the bailout.
     case GetProp_Fallback:
     case SetProp_Fallback:
     case GetElem_Fallback:
       return true;
     default:
@@ -733,234 +724,16 @@ static void TryAttachStub(const char* na
         break;
     }
     if (!attached) {
       stub->state().trackNotAttached();
     }
   }
 }
 
-//
-// WarmUpCounter_Fallback
-//
-
-/* clang-format off */
-// The following data is kept in a temporary heap-allocated buffer, stored in
-// JitRuntime (high memory addresses at top, low at bottom):
-//
-//     +----->+=================================+  --      <---- High Address
-//     |      |                                 |   |
-//     |      |     ...BaselineFrame...         |   |-- Copy of BaselineFrame + stack values
-//     |      |                                 |   |
-//     |      +---------------------------------+   |
-//     |      |                                 |   |
-//     |      |     ...Locals/Stack...          |   |
-//     |      |                                 |   |
-//     |      +=================================+  --
-//     |      |     Padding(Maybe Empty)        |
-//     |      +=================================+  --
-//     +------|-- baselineFrame                 |   |-- IonOsrTempData
-//            |   jitcode                       |   |
-//            +=================================+  --      <---- Low Address
-//
-// A pointer to the IonOsrTempData is returned.
-/* clang-format on */
-
-struct IonOsrTempData {
-  void* jitcode;
-  uint8_t* baselineFrame;
-};
-
-static IonOsrTempData* PrepareOsrTempData(JSContext* cx, BaselineFrame* frame,
-                                          uint32_t frameSize, void* jitcode) {
-  uint32_t numValueSlots = frame->numValueSlots(frameSize);
-
-  // Calculate the amount of space to allocate:
-  //      BaselineFrame space:
-  //          (sizeof(Value) * numValueSlots)
-  //        + sizeof(BaselineFrame)
-  //
-  //      IonOsrTempData space:
-  //          sizeof(IonOsrTempData)
-
-  size_t frameSpace = sizeof(BaselineFrame) + sizeof(Value) * numValueSlots;
-  size_t ionOsrTempDataSpace = sizeof(IonOsrTempData);
-
-  size_t totalSpace = AlignBytes(frameSpace, sizeof(Value)) +
-                      AlignBytes(ionOsrTempDataSpace, sizeof(Value));
-
-  IonOsrTempData* info = (IonOsrTempData*)cx->allocateOsrTempData(totalSpace);
-  if (!info) {
-    ReportOutOfMemory(cx);
-    return nullptr;
-  }
-
-  memset(info, 0, totalSpace);
-
-  info->jitcode = jitcode;
-
-  // Copy the BaselineFrame + local/stack Values to the buffer. Arguments and
-  // |this| are not copied but left on the stack: the Baseline and Ion frame
-  // share the same frame prefix and Ion won't clobber these values. Note
-  // that info->baselineFrame will point to the *end* of the frame data, like
-  // the frame pointer register in baseline frames.
-  uint8_t* frameStart =
-      (uint8_t*)info + AlignBytes(ionOsrTempDataSpace, sizeof(Value));
-  info->baselineFrame = frameStart + frameSpace;
-
-  memcpy(frameStart, (uint8_t*)frame - numValueSlots * sizeof(Value),
-         frameSpace);
-
-  JitSpew(JitSpew_BaselineOSR, "Allocated IonOsrTempData at %p", (void*)info);
-  JitSpew(JitSpew_BaselineOSR, "Jitcode is %p", info->jitcode);
-
-  // All done.
-  return info;
-}
-
-bool DoWarmUpCounterFallbackOSR(JSContext* cx, BaselineFrame* frame,
-                                uint32_t frameSize,
-                                ICWarmUpCounter_Fallback* stub,
-                                IonOsrTempData** infoPtr) {
-  MOZ_ASSERT(infoPtr);
-  *infoPtr = nullptr;
-
-  MOZ_ASSERT(frame->debugFrameSize() == frameSize);
-
-  RootedScript script(cx, frame->script());
-  jsbytecode* pc = stub->icEntry()->pc(script);
-  MOZ_ASSERT(JSOp(*pc) == JSOP_LOOPENTRY);
-
-  FallbackICSpew(cx, stub, "WarmUpCounter(%d)", int(script->pcToOffset(pc)));
-
-  if (!IonCompileScriptForBaseline(cx, frame, frameSize, pc)) {
-    return false;
-  }
-
-  if (!script->hasIonScript() || script->ionScript()->osrPc() != pc ||
-      script->ionScript()->bailoutExpected() || frame->isDebuggee()) {
-    return true;
-  }
-
-  IonScript* ion = script->ionScript();
-  MOZ_ASSERT(cx->runtime()->geckoProfiler().enabled() ==
-             ion->hasProfilingInstrumentation());
-  MOZ_ASSERT(ion->osrPc() == pc);
-
-  JitSpew(JitSpew_BaselineOSR, "  OSR possible!");
-  void* jitcode = ion->method()->raw() + ion->osrEntryOffset();
-
-  // Prepare the temporary heap copy of the fake InterpreterFrame and actual
-  // args list.
-  JitSpew(JitSpew_BaselineOSR, "Got jitcode.  Preparing for OSR into ion.");
-  IonOsrTempData* info = PrepareOsrTempData(cx, frame, frameSize, jitcode);
-  if (!info) {
-    return false;
-  }
-  *infoPtr = info;
-
-  return true;
-}
-
-bool FallbackICCodeCompiler::emit_WarmUpCounter() {
-  // Note: frame size is stored in R0.scratchReg().
-
-  // Push a stub frame so that we can perform a non-tail call.
-  enterStubFrame(masm, R1.scratchReg());
-
-  Label noCompiledCode;
-  // Call DoWarmUpCounterFallbackOSR to compile/check-for Ion-compiled function
-  {
-    // Push IonOsrTempData pointer storage
-    masm.subFromStackPtr(Imm32(sizeof(void*)));
-    masm.push(masm.getStackPointer());
-
-    // Push stub pointer, frame size, frame pointer.
-    masm.push(ICStubReg);
-    masm.push(R0.scratchReg());
-    pushStubPayload(masm, R0.scratchReg());
-
-    using Fn = bool (*)(JSContext*, BaselineFrame*, uint32_t,
-                        ICWarmUpCounter_Fallback*, IonOsrTempData**);
-    if (!callVM<Fn, DoWarmUpCounterFallbackOSR>(masm)) {
-      return false;
-    }
-
-    // Pop IonOsrTempData pointer.
-    masm.pop(R0.scratchReg());
-
-    leaveStubFrame(masm);
-
-    // If no JitCode was found, then skip just exit the IC.
-    masm.branchPtr(Assembler::Equal, R0.scratchReg(), ImmPtr(nullptr),
-                   &noCompiledCode);
-  }
-
-  // Get a scratch register.
-  AllocatableGeneralRegisterSet regs(availableGeneralRegs(0));
-  Register osrDataReg = R0.scratchReg();
-  regs.take(osrDataReg);
-  regs.takeUnchecked(OsrFrameReg);
-
-  Register scratchReg = regs.takeAny();
-
-  // At this point, stack looks like:
-  //  +-> [...Calling-Frame...]
-  //  |   [...Actual-Args/ThisV/ArgCount/Callee...]
-  //  |   [Descriptor]
-  //  |   [Return-Addr]
-  //  +---[Saved-FramePtr]            <-- BaselineFrameReg points here.
-  //      [...Baseline-Frame...]
-
-  // Restore the stack pointer to point to the saved frame pointer.
-  masm.moveToStackPtr(BaselineFrameReg);
-
-  // Discard saved frame pointer, so that the return address is on top of
-  // the stack.
-  masm.pop(scratchReg);
-
-#ifdef DEBUG
-  // If profiler instrumentation is on, ensure that lastProfilingFrame is
-  // the frame currently being OSR-ed
-  {
-    Label checkOk;
-    AbsoluteAddress addressOfEnabled(
-        cx->runtime()->geckoProfiler().addressOfEnabled());
-    masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &checkOk);
-    masm.loadPtr(AbsoluteAddress((void*)&cx->jitActivation), scratchReg);
-    masm.loadPtr(
-        Address(scratchReg, JitActivation::offsetOfLastProfilingFrame()),
-        scratchReg);
-
-    // It may be the case that we entered the baseline frame with
-    // profiling turned off on, then in a call within a loop (i.e. a
-    // callee frame), turn on profiling, then return to this frame,
-    // and then OSR with profiling turned on.  In this case, allow for
-    // lastProfilingFrame to be null.
-    masm.branchPtr(Assembler::Equal, scratchReg, ImmWord(0), &checkOk);
-
-    masm.branchStackPtr(Assembler::Equal, scratchReg, &checkOk);
-    masm.assumeUnreachable("Baseline OSR lastProfilingFrame mismatch.");
-    masm.bind(&checkOk);
-  }
-#endif
-
-  // Jump into Ion.
-  masm.loadPtr(Address(osrDataReg, offsetof(IonOsrTempData, jitcode)),
-               scratchReg);
-  masm.loadPtr(Address(osrDataReg, offsetof(IonOsrTempData, baselineFrame)),
-               OsrFrameReg);
-  masm.jump(scratchReg);
-
-  // No jitcode available, do nothing.
-  masm.bind(&noCompiledCode);
-  EmitReturnFromIC(masm);
-  return true;
-}
-
 void ICFallbackStub::unlinkStub(Zone* zone, ICStub* prev, ICStub* stub) {
   MOZ_ASSERT(stub->next());
 
   // If stub is the last optimized stub, update lastStubPtrAddr.
   if (stub->next() == this) {
     MOZ_ASSERT(lastStubPtrAddr_ == stub->addressOfNext());
     if (prev) {
       lastStubPtrAddr_ = prev->addressOfNext();
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -1020,26 +1020,16 @@ class ICStubCompiler : public ICStubComp
   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.
-class ICWarmUpCounter_Fallback : public ICFallbackStub {
-  friend class ICStubSpace;
-
-  explicit ICWarmUpCounter_Fallback(TrampolinePtr stubCode)
-      : ICFallbackStub(ICStub::WarmUpCounter_Fallback, stubCode) {}
-};
-
 // Monitored fallback stubs - as the name implies.
 class ICMonitoredFallbackStub : public ICFallbackStub {
  protected:
   // Pointer to the fallback monitor stub. Created lazily by
   // getFallbackMonitorStub if needed.
   ICTypeMonitor_Fallback* fallbackMonitorStub_;
 
   ICMonitoredFallbackStub(Kind kind, TrampolinePtr stubCode)
@@ -1856,21 +1846,16 @@ extern MOZ_MUST_USE bool TypeMonitorResu
                                            BaselineFrame* frame,
                                            HandleScript script, jsbytecode* pc,
                                            HandleValue val);
 
 extern bool DoTypeUpdateFallback(JSContext* cx, BaselineFrame* frame,
                                  ICCacheIR_Updated* stub, HandleValue objval,
                                  HandleValue value);
 
-extern bool DoWarmUpCounterFallbackOSR(JSContext* cx, BaselineFrame* frame,
-                                       uint32_t frameSize,
-                                       ICWarmUpCounter_Fallback* stub,
-                                       IonOsrTempData** infoPtr);
-
 extern bool DoCallFallback(JSContext* cx, BaselineFrame* frame,
                            ICCall_Fallback* stub, uint32_t argc, Value* vp,
                            MutableHandleValue res);
 
 extern bool DoSpreadCallFallback(JSContext* cx, BaselineFrame* frame,
                                  ICCall_Fallback* stub, Value* vp,
                                  MutableHandleValue res);
 
--- a/js/src/jit/BaselineICList.h
+++ b/js/src/jit/BaselineICList.h
@@ -8,18 +8,16 @@
 #define jit_BaselineICList_h
 
 namespace js {
 namespace jit {
 
 // List of Baseline IC stub kinds. The stub kind determines the structure of the
 // ICStub data.
 #define IC_BASELINE_STUB_KIND_LIST(_) \
-  _(WarmUpCounter_Fallback)           \
-                                      \
   _(TypeMonitor_Fallback)             \
   _(TypeMonitor_SingleObject)         \
   _(TypeMonitor_ObjectGroup)          \
   _(TypeMonitor_PrimitiveSet)         \
   _(TypeMonitor_AnyValue)             \
                                       \
   _(TypeUpdate_Fallback)              \
   _(TypeUpdate_SingleObject)          \
@@ -69,17 +67,16 @@ namespace jit {
   _(CacheIR_Updated)
 
 // List of fallback trampolines. Each of these fallback trampolines exists as
 // part of the JitRuntime. Note that some fallback stubs in previous list may
 // have multiple trampolines in this list. For example, Call_Fallback has
 // constructing/spread variants here with different calling conventions needing
 // different trampolines.
 #define IC_BASELINE_FALLBACK_CODE_KIND_LIST(_) \
-  _(WarmUpCounter)                             \
   _(TypeMonitor)                               \
   _(TypeUpdate)                                \
   _(NewArray)                                  \
   _(NewObject)                                 \
   _(ToBool)                                    \
   _(UnaryArith)                                \
   _(Call)                                      \
   _(CallConstructing)                          \
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -121,17 +121,17 @@ static JitExecStatus EnterBaseline(JSCon
   // class constructors, which are forced to do it themselves.
   if (!data.result.isMagic() && data.constructing &&
       data.result.isPrimitive()) {
     MOZ_ASSERT(data.maxArgv[0].isObject());
     data.result = data.maxArgv[0];
   }
 
   // Release temporary buffer used for OSR into Ion.
-  cx->freeOsrTempData();
+  cx->runtime()->jitRuntime()->freeIonOsrTempData();
 
   MOZ_ASSERT_IF(data.result.isMagic(), data.result.isMagic(JS_ION_ERROR));
   return data.result.isMagic() ? JitExec_Error : JitExec_Ok;
 }
 
 JitExecStatus jit::EnterBaselineInterpreterAtBranch(JSContext* cx,
                                                     InterpreterFrame* fp,
                                                     jsbytecode* pc) {
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -369,25 +369,25 @@ void JitRuntime::ionLazyLinkListRemove(J
 void JitRuntime::ionLazyLinkListAdd(JSRuntime* rt, jit::IonBuilder* builder) {
   MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt),
              "Should only be mutated by the main thread.");
   MOZ_ASSERT(rt == builder->script()->runtimeFromMainThread());
   ionLazyLinkList(rt).insertFront(builder);
   ionLazyLinkListSize_++;
 }
 
-uint8_t* JSContext::allocateOsrTempData(size_t size) {
-  osrTempData_ = (uint8_t*)js_realloc(osrTempData_, size);
-  return osrTempData_;
+uint8_t* JitRuntime::allocateIonOsrTempData(size_t size) {
+  // Free the old buffer (if needed) before allocating a new one. Note that we
+  // could use realloc here but it's likely not worth the complexity.
+  freeIonOsrTempData();
+  ionOsrTempData_.ref().reset(static_cast<uint8_t*>(js_malloc(size)));
+  return ionOsrTempData_.ref().get();
 }
 
-void JSContext::freeOsrTempData() {
-  js_free(osrTempData_);
-  osrTempData_ = nullptr;
-}
+void JitRuntime::freeIonOsrTempData() { ionOsrTempData_.ref().reset(); }
 
 JitRealm::JitRealm() : stubCodes_(nullptr), stringsCanBeInNursery(false) {}
 
 JitRealm::~JitRealm() { js_delete(stubCodes_); }
 
 bool JitRealm::initialize(JSContext* cx, bool zoneHasNurseryStrings) {
   stubCodes_ = cx->new_<ICStubCodeMap>(cx->zone());
   if (!stubCodes_) {
@@ -2342,18 +2342,18 @@ static MethodStatus BaselineCanEnterAtBr
   // Or when we didn't force a recompile.
   if (script->hasIonScript() && pc != script->ionScript()->osrPc()) {
     return Method_Skipped;
   }
 
   return Method_Compiled;
 }
 
-bool jit::IonCompileScriptForBaseline(JSContext* cx, BaselineFrame* frame,
-                                      uint32_t frameSize, jsbytecode* pc) {
+static bool IonCompileScriptForBaseline(JSContext* cx, BaselineFrame* frame,
+                                        uint32_t frameSize, jsbytecode* pc) {
   MOZ_ASSERT(IsIonEnabled());
   MOZ_ASSERT(frame->debugFrameSize() == frameSize);
 
   RootedScript script(cx, frame->script());
   bool isLoopEntry = JSOp(*pc) == JSOP_LOOPENTRY;
 
   MOZ_ASSERT(!isLoopEntry || LoopEntryCanIonOsr(pc));
 
@@ -2429,16 +2429,124 @@ bool jit::IonCompileScriptForBaseline(JS
 bool jit::IonCompileScriptForBaselineAtEntry(JSContext* cx,
                                              BaselineFrame* frame) {
   JSScript* script = frame->script();
   uint32_t frameSize =
       BaselineFrame::frameSizeForNumValueSlots(script->nfixed());
   return IonCompileScriptForBaseline(cx, frame, frameSize, script->code());
 }
 
+/* clang-format off */
+// The following data is kept in a temporary heap-allocated buffer, stored in
+// JitRuntime (high memory addresses at top, low at bottom):
+//
+//     +----->+=================================+  --      <---- High Address
+//     |      |                                 |   |
+//     |      |     ...BaselineFrame...         |   |-- Copy of BaselineFrame + stack values
+//     |      |                                 |   |
+//     |      +---------------------------------+   |
+//     |      |                                 |   |
+//     |      |     ...Locals/Stack...          |   |
+//     |      |                                 |   |
+//     |      +=================================+  --
+//     |      |     Padding(Maybe Empty)        |
+//     |      +=================================+  --
+//     +------|-- baselineFrame                 |   |-- IonOsrTempData
+//            |   jitcode                       |   |
+//            +=================================+  --      <---- Low Address
+//
+// A pointer to the IonOsrTempData is returned.
+/* clang-format on */
+
+static IonOsrTempData* PrepareOsrTempData(JSContext* cx, BaselineFrame* frame,
+                                          uint32_t frameSize, void* jitcode) {
+  uint32_t numValueSlots = frame->numValueSlots(frameSize);
+
+  // Calculate the amount of space to allocate:
+  //      BaselineFrame space:
+  //          (sizeof(Value) * numValueSlots)
+  //        + sizeof(BaselineFrame)
+  //
+  //      IonOsrTempData space:
+  //          sizeof(IonOsrTempData)
+
+  size_t frameSpace = sizeof(BaselineFrame) + sizeof(Value) * numValueSlots;
+  size_t ionOsrTempDataSpace = sizeof(IonOsrTempData);
+
+  size_t totalSpace = AlignBytes(frameSpace, sizeof(Value)) +
+                      AlignBytes(ionOsrTempDataSpace, sizeof(Value));
+
+  JitRuntime* jrt = cx->runtime()->jitRuntime();
+  uint8_t* buf = jrt->allocateIonOsrTempData(totalSpace);
+  if (!buf) {
+    ReportOutOfMemory(cx);
+    return nullptr;
+  }
+
+  IonOsrTempData* info = new (buf) IonOsrTempData();
+  info->jitcode = jitcode;
+
+  // Copy the BaselineFrame + local/stack Values to the buffer. Arguments and
+  // |this| are not copied but left on the stack: the Baseline and Ion frame
+  // share the same frame prefix and Ion won't clobber these values. Note
+  // that info->baselineFrame will point to the *end* of the frame data, like
+  // the frame pointer register in baseline frames.
+  uint8_t* frameStart =
+      (uint8_t*)info + AlignBytes(ionOsrTempDataSpace, sizeof(Value));
+  info->baselineFrame = frameStart + frameSpace;
+
+  memcpy(frameStart, (uint8_t*)frame - numValueSlots * sizeof(Value),
+         frameSpace);
+
+  JitSpew(JitSpew_BaselineOSR, "Allocated IonOsrTempData at %p", info);
+  JitSpew(JitSpew_BaselineOSR, "Jitcode is %p", info->jitcode);
+
+  // All done.
+  return info;
+}
+
+bool jit::IonCompileScriptForBaselineOSR(JSContext* cx, BaselineFrame* frame,
+                                         uint32_t frameSize, jsbytecode* pc,
+                                         IonOsrTempData** infoPtr) {
+  MOZ_ASSERT(infoPtr);
+  *infoPtr = nullptr;
+
+  MOZ_ASSERT(frame->debugFrameSize() == frameSize);
+  MOZ_ASSERT(JSOp(*pc) == JSOP_LOOPENTRY);
+
+  if (!IonCompileScriptForBaseline(cx, frame, frameSize, pc)) {
+    return false;
+  }
+
+  RootedScript script(cx, frame->script());
+  if (!script->hasIonScript() || script->ionScript()->osrPc() != pc ||
+      script->ionScript()->bailoutExpected() || frame->isDebuggee()) {
+    return true;
+  }
+
+  IonScript* ion = script->ionScript();
+  MOZ_ASSERT(cx->runtime()->geckoProfiler().enabled() ==
+             ion->hasProfilingInstrumentation());
+  MOZ_ASSERT(ion->osrPc() == pc);
+
+  JitSpew(JitSpew_BaselineOSR, "  OSR possible!");
+  void* jitcode = ion->method()->raw() + ion->osrEntryOffset();
+
+  // Prepare the temporary heap copy of the fake InterpreterFrame and actual
+  // args list.
+  JitSpew(JitSpew_BaselineOSR, "Got jitcode.  Preparing for OSR into ion.");
+  IonOsrTempData* info = PrepareOsrTempData(cx, frame, frameSize, jitcode);
+  if (!info) {
+    return false;
+  }
+
+  *infoPtr = info;
+  return true;
+}
+
 MethodStatus jit::Recompile(JSContext* cx, HandleScript script, bool force) {
   MOZ_ASSERT(script->hasIonScript());
   if (script->ionScript()->isRecompiling()) {
     return Method_Compiled;
   }
 
   MOZ_ASSERT(!script->baselineScript()->hasPendingIonBuilder());
 
--- a/js/src/jit/Ion.h
+++ b/js/src/jit/Ion.h
@@ -135,24 +135,37 @@ MOZ_MUST_USE bool InitializeJit();
 JitContext* GetJitContext();
 JitContext* MaybeGetJitContext();
 
 void SetJitContext(JitContext* ctx);
 
 bool CanIonCompileScript(JSContext* cx, JSScript* script);
 bool CanIonInlineScript(JSScript* script);
 
-MOZ_MUST_USE bool IonCompileScriptForBaseline(JSContext* cx,
-                                              BaselineFrame* frame,
-                                              uint32_t frameSize,
-                                              jsbytecode* pc);
-
 MOZ_MUST_USE bool IonCompileScriptForBaselineAtEntry(JSContext* cx,
                                                      BaselineFrame* frame);
 
+struct IonOsrTempData {
+  void* jitcode;
+  uint8_t* baselineFrame;
+
+  static constexpr size_t offsetOfJitCode() {
+    return offsetof(IonOsrTempData, jitcode);
+  }
+  static constexpr size_t offsetOfBaselineFrame() {
+    return offsetof(IonOsrTempData, baselineFrame);
+  }
+};
+
+MOZ_MUST_USE bool IonCompileScriptForBaselineOSR(JSContext* cx,
+                                                 BaselineFrame* frame,
+                                                 uint32_t frameSize,
+                                                 jsbytecode* pc,
+                                                 IonOsrTempData** infoPtr);
+
 MethodStatus CanEnterIon(JSContext* cx, RunState& state);
 
 MethodStatus Recompile(JSContext* cx, HandleScript script, bool force);
 
 enum JitExecStatus {
   // The method call had to be aborted due to a stack limit check. This
   // error indicates that Ion never attempted to clean up frames.
   JitExec_Aborted,
--- a/js/src/jit/Jit.cpp
+++ b/js/src/jit/Jit.cpp
@@ -109,17 +109,17 @@ static EnterJitStatus JS_HAZ_JSNATIVE_CA
     CALL_GENERATED_CODE(enter, code, maxArgc, maxArgv, /* osrFrame = */ nullptr,
                         calleeToken, envChain, /* osrNumStackValues = */ 0,
                         result.address());
   }
 
   MOZ_ASSERT(!cx->hasIonReturnOverride());
 
   // Release temporary buffer used for OSR into Ion.
-  cx->freeOsrTempData();
+  cx->runtime()->jitRuntime()->freeIonOsrTempData();
 
   if (result.isMagic()) {
     MOZ_ASSERT(result.isMagic(JS_ION_ERROR));
     return EnterJitStatus::Error;
   }
 
   // Jit callers wrap primitive constructor return, except for derived
   // class constructors, which are forced to do it themselves.
--- a/js/src/jit/JitRealm.h
+++ b/js/src/jit/JitRealm.h
@@ -132,16 +132,21 @@ class JitRuntime {
  private:
   friend class JitRealm;
 
   // Executable allocator for all code except wasm code.
   MainThreadData<ExecutableAllocator> execAlloc_;
 
   MainThreadData<uint64_t> nextCompilationId_;
 
+  // Buffer for OSR from baseline to Ion. To avoid holding on to this for too
+  // long it's also freed in EnterBaseline and EnterJit (after returning from
+  // JIT code).
+  MainThreadData<js::UniquePtr<uint8_t>> ionOsrTempData_;
+
   // Shared exception-handler tail.
   WriteOnceData<uint32_t> exceptionTailOffset_;
 
   // Shared post-bailout-handler tail.
   WriteOnceData<uint32_t> bailoutTailOffset_;
 
   // Shared profiler exit frame tail.
   WriteOnceData<uint32_t> profilerExitFrameTailOffset_;
@@ -306,16 +311,19 @@ class JitRuntime {
   const BaselineICFallbackCode& baselineICFallbackCode() const {
     return baselineICFallbackCode_.ref();
   }
 
   IonCompilationId nextCompilationId() {
     return IonCompilationId(nextCompilationId_++);
   }
 
+  uint8_t* allocateIonOsrTempData(size_t size);
+  void freeIonOsrTempData();
+
   TrampolinePtr getVMWrapper(const VMFunction& f) const;
 
   TrampolinePtr getVMWrapper(VMFunctionId funId) const {
     MOZ_ASSERT(trampolineCode_);
     return trampolineCode(functionWrapperOffsets_[size_t(funId)]);
   }
   TrampolinePtr getVMWrapper(TailCallVMFunctionId funId) const {
     MOZ_ASSERT(trampolineCode_);
--- a/js/src/jit/VMFunctionList-inl.h
+++ b/js/src/jit/VMFunctionList-inl.h
@@ -98,17 +98,16 @@ namespace jit {
   _(DirectEvalStringFromIon, js::DirectEvalStringFromIon)                      \
   _(DivValues, js::DivValues)                                                  \
   _(DoCallFallback, js::jit::DoCallFallback)                                   \
   _(DoConcatStringObject, js::jit::DoConcatStringObject)                       \
   _(DoSpreadCallFallback, js::jit::DoSpreadCallFallback)                       \
   _(DoToNumber, js::jit::DoToNumber)                                           \
   _(DoToNumeric, js::jit::DoToNumeric)                                         \
   _(DoTypeUpdateFallback, js::jit::DoTypeUpdateFallback)                       \
-  _(DoWarmUpCounterFallbackOSR, js::jit::DoWarmUpCounterFallbackOSR)           \
   _(EnterWith, js::jit::EnterWith)                                             \
   _(FinalSuspend, js::jit::FinalSuspend)                                       \
   _(FinishBoundFunctionInit, JSFunction::finishBoundFunctionInit)              \
   _(FreshenLexicalEnv, js::jit::FreshenLexicalEnv)                             \
   _(FunWithProtoOperation, js::FunWithProtoOperation)                          \
   _(GeneratorThrowOrReturn, js::jit::GeneratorThrowOrReturn)                   \
   _(GetAndClearException, js::GetAndClearException)                            \
   _(GetElementOperation, js::GetElementOperation)                              \
@@ -142,16 +141,17 @@ namespace jit {
   _(InterruptCheck, js::jit::InterruptCheck)                                   \
   _(InvokeFunction, js::jit::InvokeFunction)                                   \
   _(InvokeFunctionShuffleNewTarget, js::jit::InvokeFunctionShuffleNewTarget)   \
   _(IonBinaryArithICUpdate, js::jit::IonBinaryArithIC::update)                 \
   _(IonBindNameICUpdate, js::jit::IonBindNameIC::update)                       \
   _(IonCompareICUpdate, js::jit::IonCompareIC::update)                         \
   _(IonCompileScriptForBaselineAtEntry,                                        \
     js::jit::IonCompileScriptForBaselineAtEntry)                               \
+  _(IonCompileScriptForBaselineOSR, js::jit::IonCompileScriptForBaselineOSR)   \
   _(IonForcedInvalidation, js::jit::IonForcedInvalidation)                     \
   _(IonForcedRecompile, js::jit::IonForcedRecompile)                           \
   _(IonGetIteratorICUpdate, js::jit::IonGetIteratorIC::update)                 \
   _(IonGetNameICUpdate, js::jit::IonGetNameIC::update)                         \
   _(IonGetPropSuperICUpdate, js::jit::IonGetPropSuperIC::update)               \
   _(IonGetPropertyICUpdate, js::jit::IonGetPropertyIC::update)                 \
   _(IonHasOwnICUpdate, js::jit::IonHasOwnIC::update)                           \
   _(IonInICUpdate, js::jit::IonInIC::update)                                   \
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -30,16 +30,18 @@ class TypedArrayObject;
 namespace gc {
 
 struct Cell;
 
 }
 
 namespace jit {
 
+struct IonOsrTempData;
+
 enum DataType : uint8_t {
   Type_Void,
   Type_Bool,
   Type_Int32,
   Type_Double,
   Type_Pointer,
   Type_Object,
   Type_Value,
@@ -692,16 +694,20 @@ template <>
 struct OutParamToDataType<uint32_t*> {
   static const DataType result = Type_Int32;
 };
 template <>
 struct OutParamToDataType<uint8_t**> {
   static const DataType result = Type_Pointer;
 };
 template <>
+struct OutParamToDataType<IonOsrTempData**> {
+  static const DataType result = Type_Pointer;
+};
+template <>
 struct OutParamToDataType<bool*> {
   static const DataType result = Type_Bool;
 };
 template <>
 struct OutParamToDataType<double*> {
   static const DataType result = Type_Double;
 };
 template <>
--- a/js/src/jsapi-tests/testGCChunkPool.cpp
+++ b/js/src/jsapi-tests/testGCChunkPool.cpp
@@ -14,17 +14,17 @@
 #include "jsapi-tests/tests.h"
 
 BEGIN_TEST(testGCChunkPool) {
   const int N = 10;
   js::gc::ChunkPool pool;
 
   // Create.
   for (int i = 0; i < N; ++i) {
-    js::gc::Chunk* chunk = js::gc::Chunk::allocate(cx->runtime());
+    js::gc::Chunk* chunk = js::gc::Chunk::allocate(&cx->runtime()->gc);
     CHECK(chunk);
     pool.push(chunk);
   }
   MOZ_ASSERT(pool.verify());
 
   // Iterate.
   uint32_t i = 0;
   for (js::gc::ChunkPool::Iter iter(pool); !iter.done(); iter.next(), ++i) {
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1332,30 +1332,25 @@ JS_PUBLIC_API void JS_UpdateWeakPointerA
 JS_PUBLIC_API void JS_UpdateWeakPointerAfterGCUnbarriered(JSObject** objp) {
   if (IsAboutToBeFinalizedUnbarriered(objp)) {
     *objp = nullptr;
   }
 }
 
 JS_PUBLIC_API void JS_SetGCParameter(JSContext* cx, JSGCParamKey key,
                                      uint32_t value) {
-  cx->runtime()->gc.waitBackgroundSweepEnd();
-  AutoLockGC lock(cx->runtime());
-  MOZ_ALWAYS_TRUE(cx->runtime()->gc.setParameter(key, value, lock));
+  MOZ_ALWAYS_TRUE(cx->runtime()->gc.setParameter(key, value));
 }
 
 JS_PUBLIC_API void JS_ResetGCParameter(JSContext* cx, JSGCParamKey key) {
-  cx->runtime()->gc.waitBackgroundSweepEnd();
-  AutoLockGC lock(cx->runtime());
-  cx->runtime()->gc.resetParameter(key, lock);
+  cx->runtime()->gc.resetParameter(key);
 }
 
 JS_PUBLIC_API uint32_t JS_GetGCParameter(JSContext* cx, JSGCParamKey key) {
-  AutoLockGC lock(cx->runtime());
-  return cx->runtime()->gc.getParameter(key, lock);
+  return cx->runtime()->gc.getParameter(key);
 }
 
 JS_PUBLIC_API void JS_SetGCParametersBasedOnAvailableMemory(JSContext* cx,
                                                             uint32_t availMem) {
   struct JSGCConfig {
     JSGCParamKey key;
     uint32_t value;
   };
--- a/js/src/vm/JSContext.cpp
+++ b/js/src/vm/JSContext.cpp
@@ -1279,17 +1279,16 @@ JSContext::JSContext(JSRuntime* runtime,
       cycleDetectorVector_(this, this),
       data(nullptr),
       asyncStackForNewActivations_(this),
       asyncCauseForNewActivations(this, nullptr),
       asyncCallIsExplicit(this, false),
       interruptCallbacks_(this),
       interruptCallbackDisabled(this, false),
       interruptBits_(0),
-      osrTempData_(this, nullptr),
       ionReturnOverride_(this, MagicValue(JS_ARG_POISON)),
       jitStackLimit(UINTPTR_MAX),
       jitStackLimitNoInterrupt(this, UINTPTR_MAX),
       jobQueue(this, nullptr),
       internalJobQueue(this),
       canSkipEnqueuingJobs(this, false),
       promiseRejectionTrackerCallback(this, nullptr),
       promiseRejectionTrackerCallbackData(this, nullptr),
@@ -1309,17 +1308,16 @@ JSContext::~JSContext() {
   /* Free the stuff hanging off of cx. */
   MOZ_ASSERT(!resolvingList);
 
   if (dtoaState) {
     DestroyDtoaState(dtoaState);
   }
 
   fx.destroyInstance();
-  freeOsrTempData();
 
 #ifdef JS_SIMULATOR
   js::jit::Simulator::Destroy(simulator_);
 #endif
 
 #ifdef JS_TRACE_LOGGING
   if (traceLogger) {
     DestroyTraceLogger(traceLogger);
--- a/js/src/vm/JSContext.h
+++ b/js/src/vm/JSContext.h
@@ -883,23 +883,16 @@ struct JSContext : public JS::RootingCon
   void* addressOfZone() { return &zone_; }
 
   const void* addressOfRealm() const { return &realm_; }
 
   // Futex state, used by Atomics.wait() and Atomics.wake() on the Atomics
   // object.
   js::FutexThread fx;
 
-  // Buffer for OSR from baseline to Ion. To avoid holding on to this for
-  // too long, it's also freed in EnterBaseline (after returning from JIT code).
-  js::ContextData<uint8_t*> osrTempData_;
-
-  uint8_t* allocateOsrTempData(size_t size);
-  void freeOsrTempData();
-
   // In certain cases, we want to optimize certain opcodes to typed
   // instructions, to avoid carrying an extra register to feed into an unbox.
   // Unfortunately, that's not always possible. For example, a GetPropertyCacheT
   // could return a typed double, but if it takes its out-of-line path, it could
   // return an object, and trigger invalidation. The invalidation bailout will
   // consider the return value to be a double, and create a garbage Value.
   //
   // To allow the GetPropertyCacheT optimization, we allow the ability for
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -2451,17 +2451,17 @@
      * able to OSR at this point, which is true unless there is non-loop state
      * on the stack. See JSOP_JUMPTARGET for the icIndex argument.
      *
      *   Category: Statements
      *   Type: Jumps
      *   Operands: uint32_t icIndex, uint8_t BITFIELD
      *   Stack: =>
      */ \
-    MACRO(JSOP_LOOPENTRY, 227, "loopentry", NULL, 6, 0, 0, JOF_LOOPENTRY|JOF_IC) \
+    MACRO(JSOP_LOOPENTRY, 227, "loopentry", NULL, 6, 0, 0, JOF_LOOPENTRY) \
     /*
      * Converts the value on the top of the stack to a String.
      *
      *   Category: Other
      *   Operands:
      *   Stack: val => ToString(val)
      */ \
     MACRO(JSOP_TOSTRING, 228, "tostring", NULL, 1, 1, 1, JOF_BYTE) \
--- a/layout/reftests/text/segment-break-transformation-1-ref.html
+++ b/layout/reftests/text/segment-break-transformation-1-ref.html
@@ -24,11 +24,16 @@ b { font-weight:normal; background-color
 <p><b><span>Hello Kitty</span></b>
 <p><b><span>HelloKitty</span></b>
 <p><b><span>HelloKitty</span></b>
 <p><b><span>HelloKitty</span></b>
 <!-- test surrogates handling -->
 <p><b><span>&#x20000;&#x20001;&#x20002;&#x20003;</span></b>
 <p><b><span>&#x20000;&#x6e2c;&#x20002;&#x20003;</span></b>
 <p><b><span>&#x20000;&#x20001;&#x6e2c;&#x20003;</span></b>
+<!-- test emoji handling -->
+<p><b><span>&#x65b7;&#x1f600; &#x6e2c;&#x8a66;</span></b>
+<p><b><span>&#x65b7;&#x884c; &#x1f601;&#x8a66;</span></b>
+<p><b><span>&#x65b7;&#x1f600; &#x1f601;&#x8a66;</span></b>
+<p><b><span>Hello&#x1f600; &#x1f601;Kitty</span></b>
 </div>
 </body>
 </html>
--- a/layout/reftests/text/segment-break-transformation-1.html
+++ b/layout/reftests/text/segment-break-transformation-1.html
@@ -40,11 +40,20 @@ Kitty</span></b>
 &#x200B;Kitty</span></b>
 <!-- test surrogates handling -->
 <p><b><span>&#x20000;&#x20001;
 &#x20002;&#x20003;</span></b>
 <p><b><span>&#x20000;&#x6e2c;
 &#x20002;&#x20003;</span></b>
 <p><b><span>&#x20000;&#x20001;
 &#x6e2c;&#x20003;</span></b>
+<!-- test emoji handling -->
+<p><b><span>&#x65b7;&#x1f600;
+&#x6e2c;&#x8a66;</span></b>
+<p><b><span>&#x65b7;&#x884c;
+&#x1f601;&#x8a66;</span></b>
+<p><b><span>&#x65b7;&#x1f600;
+&#x1f601;&#x8a66;</span></b>
+<p><b><span>Hello&#x1f600;
+&#x1f601;Kitty</span></b>
 </div>
 </body>
 </html>
deleted file mode 100644
--- a/testing/web-platform/meta/css/CSS2/generated-content/content-counter-004.xht.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[content-counter-004.xht]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/mediacapture-record/MediaRecorder-creation.https.html.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[MediaRecorder-creation.https.html]
-  [Video+Audio MediaRecorder]
-    expected: FAIL
-
-  [Video-only MediaRecorder]
-    expected: FAIL
-
-  [Audio-only MediaRecorder]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/mediacapture-record/MediaRecorder-destroy-script-execution.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[MediaRecorder-destroy-script-execution.html]
-  expected: TIMEOUT
-  [MediaRecorder will not fire the stop event when all tracks are ended and the script execution context is going away]
-    expected: NOTRUN
-
deleted file mode 100644
--- a/testing/web-platform/meta/mediacapture-record/MediaRecorder-error.html.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[MediaRecorder-error.html]
-  [MediaRecorder will stop recording when any of track is removed and error event will be fired]
-    expected: FAIL
-
-  [MediaRecorder will stop recording when any of track is added and error event will be fired]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/mediacapture-record/MediaRecorder-events-and-exceptions.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[MediaRecorder-events-and-exceptions.html]
-  [MediaRecorder events and exceptions]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/mediacapture-record/MediaRecorder-pause-resume.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[MediaRecorder-pause-resume.html]
-  [MediaRecorder handles pause() and resume() calls appropriately in state and events]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/mediacapture-record/MediaRecorder-stop.html.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-[MediaRecorder-stop.html]
-  expected: TIMEOUT
-  [MediaRecorder will stop recording and fire a stop event when stop() is called]
-    expected: NOTRUN
-
-  [MediaRecorder will stop recording and fire a stop event when all tracks are ended]
-    expected: TIMEOUT
-
--- a/testing/web-platform/meta/mediacapture-record/idlharness.window.js.ini
+++ b/testing/web-platform/meta/mediacapture-record/idlharness.window.js.ini
@@ -1,31 +1,10 @@
 [idlharness.window.html]
-  [MediaRecorder interface: attribute videoBitsPerSecond]
-    expected: FAIL
-
-  [MediaRecorder interface: attribute audioBitsPerSecond]
-    expected: FAIL
-
-  [MediaRecorder interface: [object MediaRecorder\] must inherit property "videoBitsPerSecond" with the proper type]
-    expected: FAIL
-
-  [MediaRecorder interface: [object MediaRecorder\] must inherit property "audioBitsPerSecond" with the proper type]
-    expected: FAIL
-
   [BlobEvent interface object length]
     expected: FAIL
 
   [BlobEvent interface: attribute timecode]
     expected: FAIL
 
   [BlobEvent interface: [object BlobEvent\] must inherit property "timecode" with the proper type]
     expected: FAIL
 
-  [MediaRecorderErrorEvent must be primary interface of undefined]
-    expected: FAIL
-
-  [Stringification of undefined]
-    expected: FAIL
-
-  [MediaRecorderErrorEvent interface: undefined must inherit property "error" with the proper type]
-    expected: FAIL
-
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-record/MediaRecorder-bitrate.https.html
@@ -0,0 +1,226 @@
+<!doctype html>
+<html>
+<head>
+<title>MediaRecorder {audio|video}bitsPerSecond attributes</title>
+<link rel="help" href="https://w3c.github.io/mediacapture-record/MediaRecorder.html">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<script>
+
+/*
+ * The bitrate handling is difficult to test, given that the spec uses text such
+ * as: "values the User Agent deems reasonable" and "such that the sum of
+ * videoBitsPerSecond and audioBitsPerSecond is close to the value of recorder’s
+ * [[ConstrainedBitsPerSecond]] slot". For cases like that this test tries to
+ * use values that are reasonable for the tested track types. Should a UA vendor
+ * see a need to update this to fit their definition of reasonable, they should
+ * feel free to do so, doing their best to avoid regressing existing compliant
+ * implementations.
+ */
+
+async function getStream(t, constraints) {
+  const stream = await navigator.mediaDevices.getUserMedia(constraints);
+  const tracks = stream.getTracks();
+  t.add_cleanup(() => tracks.forEach(tr => tr.stop()));
+  return stream;
+}
+
+function getAudioStream(t) {
+  return getStream(t, {audio: true});
+}
+
+function getVideoStream(t) {
+  return getStream(t, {video: true});
+}
+
+function getAudioVideoStream(t) {
+  return getStream(t, {audio: true, video: true});
+}
+
+const AUDIO_BITRATE = 1e5;      // 100kbps
+const VIDEO_BITRATE = 1e6;      // 1Mbps
+const LOW_TOTAL_BITRATE = 5e5;  // 500kbps
+const HIGH_TOTAL_BITRATE = 2e6; // 2Mbps
+const BITRATE_EPSILON = 1e5;    // 100kbps
+
+promise_test(async t => {
+  const rec = new MediaRecorder(await getAudioVideoStream(t));
+  assert_not_equals(rec.audioBitsPerSecond, 0);
+  assert_not_equals(rec.videoBitsPerSecond, 0);
+}, "Passing no bitrate config results in defaults");
+
+promise_test(async t => {
+  const rec = new MediaRecorder(await getAudioVideoStream(t), {
+    bitsPerSecond: 0,
+  });
+  assert_not_equals(rec.audioBitsPerSecond, 0);
+  assert_not_equals(rec.videoBitsPerSecond, 0);
+  assert_approx_equals(rec.audioBitsPerSecond + rec.videoBitsPerSecond, 0,
+    BITRATE_EPSILON);
+}, "Passing bitsPerSecond:0 results in targets close to 0");
+
+promise_test(async t => {
+  const rec = new MediaRecorder(await getAudioVideoStream(t), {
+    audioBitsPerSecond: 0,
+  });
+  assert_equals(rec.audioBitsPerSecond, 0);
+  assert_not_equals(rec.videoBitsPerSecond, 0);
+}, "Passing only audioBitsPerSecond:0 results in 0 for audio, default for video");
+
+promise_test(async t => {
+  const rec = new MediaRecorder(await getAudioVideoStream(t), {
+    videoBitsPerSecond: 0,
+  });
+  assert_not_equals(rec.audioBitsPerSecond, 0);
+  assert_equals(rec.videoBitsPerSecond, 0);
+}, "Passing only videoBitsPerSecond:0 results in 0 for video, default for audio");
+
+promise_test(async t => {
+  const rec = new MediaRecorder(await getAudioVideoStream(t), {
+    bitsPerSecond: 0,
+    audioBitsPerSecond: AUDIO_BITRATE,
+    videoBitsPerSecond: VIDEO_BITRATE,
+  });
+  assert_not_equals(rec.audioBitsPerSecond, 0);
+  assert_not_equals(rec.videoBitsPerSecond, 0);
+  assert_approx_equals(rec.audioBitsPerSecond + rec.videoBitsPerSecond, 0,
+    BITRATE_EPSILON);
+}, "Passing bitsPerSecond:0 overrides audio/video-specific values");
+
+promise_test(async t => {
+  const rec = new MediaRecorder(await getAudioVideoStream(t), {
+    bitsPerSecond: HIGH_TOTAL_BITRATE,
+    audioBitsPerSecond: 0,
+    videoBitsPerSecond: 0,
+  });
+  assert_not_equals(rec.audioBitsPerSecond, 0);
+  assert_not_equals(rec.videoBitsPerSecond, 0);
+  assert_approx_equals(rec.audioBitsPerSecond + rec.videoBitsPerSecond,
+    HIGH_TOTAL_BITRATE, BITRATE_EPSILON);
+}, "Passing bitsPerSecond overrides audio/video zero values");
+
+promise_test(async t => {
+  const rec = new MediaRecorder(await getAudioVideoStream(t), {
+    bitsPerSecond: HIGH_TOTAL_BITRATE,
+  });
+  assert_not_equals(rec.audioBitsPerSecond, 0);
+  assert_not_equals(rec.videoBitsPerSecond, 0);
+  assert_approx_equals(rec.audioBitsPerSecond + rec.videoBitsPerSecond,
+    HIGH_TOTAL_BITRATE, BITRATE_EPSILON);
+}, "Passing bitsPerSecond sets audio/video bitrate values");
+
+promise_test(async t => {
+  const rec = new MediaRecorder(await getAudioVideoStream(t), {
+    audioBitsPerSecond: AUDIO_BITRATE,
+  });
+  assert_equals(rec.audioBitsPerSecond, AUDIO_BITRATE);
+  assert_not_equals(rec.videoBitsPerSecond, 0);
+}, "Passing only audioBitsPerSecond results in default for video");
+
+promise_test(async t => {
+  const rec = new MediaRecorder(await getAudioVideoStream(t), {
+    videoBitsPerSecond: VIDEO_BITRATE,
+  });
+  assert_not_equals(rec.audioBitsPerSecond, 0);
+  assert_equals(rec.videoBitsPerSecond, VIDEO_BITRATE);
+}, "Passing only videoBitsPerSecond results in default for audio");
+
+promise_test(async t => {
+  const rec = new MediaRecorder(await getAudioStream(t), {
+    videoBitsPerSecond: VIDEO_BITRATE,
+  });
+  assert_not_equals(rec.audioBitsPerSecond, 0);
+  assert_equals(rec.videoBitsPerSecond, VIDEO_BITRATE);
+}, "Passing videoBitsPerSecond for audio-only stream still results in something for video");
+
+promise_test(async t => {
+  const rec = new MediaRecorder(await getVideoStream(t), {
+    audioBitsPerSecond: AUDIO_BITRATE,
+  });
+  assert_equals(rec.audioBitsPerSecond, AUDIO_BITRATE);
+  assert_not_equals(rec.videoBitsPerSecond, 0);
+}, "Passing audioBitsPerSecond for video-only stream still results in something for audio");
+
+promise_test(async t => {
+  const rec = new MediaRecorder(await getAudioStream(t), {
+    bitsPerSecond: HIGH_TOTAL_BITRATE,
+  });
+  assert_not_equals(rec.audioBitsPerSecond, 0);
+  assert_not_equals(rec.videoBitsPerSecond, 0);
+}, "Passing bitsPerSecond for audio-only stream still results in something for video");
+
+promise_test(async t => {
+  const rec = new MediaRecorder(await getVideoStream(t), {
+    bitsPerSecond: HIGH_TOTAL_BITRATE,
+  });
+  assert_not_equals(rec.audioBitsPerSecond, 0);
+  assert_not_equals(rec.videoBitsPerSecond, 0);
+}, "Passing bitsPerSecond for video-only stream still results in something for audio");
+
+promise_test(async t => {
+  const rec = new MediaRecorder(await getAudioVideoStream(t));
+  t.add_cleanup(() => rec.stop());
+  const abps = rec.audioBitsPerSecond;
+  const vbps = rec.videoBitsPerSecond;
+  rec.start();
+  assert_equals(rec.audioBitsPerSecond, abps);
+  assert_equals(rec.videoBitsPerSecond, vbps);
+}, "Selected default track bitrates are not changed by start()");
+
+promise_test(async t => {
+  const rec = new MediaRecorder(await getAudioVideoStream(t), {
+    audioBitsPerSecond: AUDIO_BITRATE,
+    videoBitsPerSecond: VIDEO_BITRATE,
+  });
+  t.add_cleanup(() => rec.stop());
+  const abps = rec.audioBitsPerSecond;
+  const vbps = rec.videoBitsPerSecond;
+  rec.start();
+  assert_equals(rec.audioBitsPerSecond, abps);
+  assert_equals(rec.videoBitsPerSecond, vbps);
+}, "Passed-in track bitrates are not changed by start()");
+
+promise_test(async t => {
+  const rec = new MediaRecorder(await getAudioVideoStream(t), {
+    bitsPerSecond: HIGH_TOTAL_BITRATE,
+  });
+  t.add_cleanup(() => rec.stop());
+  const abps = rec.audioBitsPerSecond;
+  const vbps = rec.videoBitsPerSecond;
+  rec.start();
+  assert_equals(rec.audioBitsPerSecond, abps);
+  assert_equals(rec.videoBitsPerSecond, vbps);
+}, "Passing bitsPerSecond for audio/video stream does not change track bitrates in start()");
+
+promise_test(async t => {
+  const rec = new MediaRecorder(await getAudioStream(t), {
+    bitsPerSecond: LOW_TOTAL_BITRATE,
+  });
+  t.add_cleanup(() => rec.stop());
+  const abps = rec.audioBitsPerSecond;
+  const vbps = rec.videoBitsPerSecond;
+  rec.start();
+  assert_approx_equals(rec.audioBitsPerSecond, LOW_TOTAL_BITRATE,
+    BITRATE_EPSILON);
+  assert_equals(rec.videoBitsPerSecond, 0);
+  assert_not_equals(rec.audioBitsPerSecond, abps);
+  assert_not_equals(rec.videoBitsPerSecond, vbps);
+}, "Passing bitsPerSecond for audio stream sets video track bitrate to 0 in start()");
+
+promise_test(async t => {
+  const rec = new MediaRecorder(await getVideoStream(t), {
+    bitsPerSecond: HIGH_TOTAL_BITRATE,
+  });
+  t.add_cleanup(() => rec.stop());
+  const abps = rec.audioBitsPerSecond;
+  const vbps = rec.videoBitsPerSecond;
+  rec.start();
+  assert_equals(rec.audioBitsPerSecond, 0);
+  assert_approx_equals(rec.videoBitsPerSecond, HIGH_TOTAL_BITRATE,
+    BITRATE_EPSILON);
+  assert_not_equals(rec.audioBitsPerSecond, abps);
+  assert_not_equals(rec.videoBitsPerSecond, vbps);
+}, "Passing bitsPerSecond for video stream sets audio track bitrate to 0 in start()");
+</script>
+</html>
--- a/testing/web-platform/tests/mediacapture-record/MediaRecorder-creation.https.html
+++ b/testing/web-platform/tests/mediacapture-record/MediaRecorder-creation.https.html
@@ -15,18 +15,18 @@
   function makeAsyncTest(constraints, verifyStream, message) {
     async_test(function(test) {
 
       const gotStream = test.step_func(function(stream) {
         verifyStream(stream);
 
         var recorder = new MediaRecorder(stream);
         assert_equals(recorder.state, "inactive");
-        assert_equals(recorder.videoBitsPerSecond, 0);
-        assert_equals(recorder.audioBitsPerSecond, 0);
+        assert_not_equals(recorder.videoBitsPerSecond, 0);
+        assert_not_equals(recorder.audioBitsPerSecond, 0);
         test.done();
       });
 
       const onError = test.unreached_func('Error creating MediaStream.');
       navigator.mediaDevices.getUserMedia(constraints).then(gotStream, onError);
     }, message);
   }
 
--- a/testing/web-platform/tests/mediacapture-record/MediaRecorder-events-and-exceptions.html
+++ b/testing/web-platform/tests/mediacapture-record/MediaRecorder-events-and-exceptions.html
@@ -31,33 +31,31 @@
 
   async_test(test => {
 
     recorderOnUnexpectedEvent = test.step_func(() => {
       assert_unreached('Unexpected event.');
     });
 
     recorderOnDataAvailable = test.step_func(event => {
-      // TODO(mcasas): ondataavailable might never be pinged with an empty Blob
-      // data on recorder.stop(), see http://crbug.com/54428
       assert_equals(recorder.state, "inactive");
-      assert_equals(event.data.size, 0, 'We should have gotten an empty Blob');
+      assert_not_equals(event.data.size, 0, 'We should get a Blob with data');
     });
 
     recorderOnStop = test.step_func(function() {
       assert_equals(recorder.state, "inactive");
-      assert_throws("InvalidStateError", function() { recorder.stop() },
-                    "recorder cannot be stop()ped in |inactive| state");
+      recorder.onstop = recorderOnUnexpectedEvent;
+      recorder.stop();
+      assert_equals(recorder.state, "inactive", "stop() is idempotent");
       assert_throws("InvalidStateError", function() { recorder.pause() },
                     "recorder cannot be pause()ed in |inactive| state");
       assert_throws("InvalidStateError", function() { recorder.resume() },
                     "recorder cannot be resume()d in |inactive| state");
       assert_throws("InvalidStateError", function() { recorder.requestData() },
                     "cannot requestData() if recorder is in |inactive| state");
-      recorder.onstop = recorderOnUnexpectedEvent;
       test.done();
     });
 
     recorderOnResume = test.step_func(function() {
       assert_equals(recorder.state, "recording");
       recorder.onresume = recorderOnUnexpectedEvent;
       recorder.onstop = recorderOnStop;
       recorder.stop();
@@ -79,32 +77,40 @@
 
     let stream = createVideoStream();
     assert_equals(stream.getAudioTracks().length, 0);
     assert_equals(stream.getVideoTracks().length, 1);
     assert_equals(stream.getVideoTracks()[0].readyState, 'live');
 
     assert_throws("NotSupportedError",
                   function() {
-                    recorder =
-                        new MediaRecorder(stream, {mimeType : "video/invalid"});
+                    recorder = new MediaRecorder(
+                      new MediaStream(), {mimeType : "video/invalid"});
                   },
                   "recorder should throw() with unsupported mimeType");
-    let recorder = new MediaRecorder(stream);
+    let recorder = new MediaRecorder(new MediaStream());
     assert_equals(recorder.state, "inactive");
 
-    assert_throws("InvalidStateError", function(){recorder.stop()},
-                  "recorder cannot be stop()ped in |inactive| state");
+    recorder.stop();
+    assert_equals(recorder.state, "inactive", "stop() is idempotent");
     assert_throws("InvalidStateError", function(){recorder.pause()},
                   "recorder cannot be pause()ed in |inactive| state");
     assert_throws("InvalidStateError", function(){recorder.resume()},
                   "recorder cannot be resume()d in |inactive| state");
     assert_throws("InvalidStateError", function(){recorder.requestData()},
                   "cannot requestData() if recorder is in |inactive| state");
 
+    assert_throws("NotSupportedError",
+                  function() {
+                    recorder.start();
+                  },
+                  "recorder should throw() when starting with inactive stream");
+
+    recorder.stream.addTrack(stream.getTracks()[0]);
+
     drawSomethingOnCanvas();
 
     recorder.onstop = recorderOnUnexpectedEvent;
     recorder.onpause = recorderOnUnexpectedEvent;
     recorder.onresume = recorderOnUnexpectedEvent;
     recorder.onerror = recorderOnUnexpectedEvent;
     recorder.ondataavailable = recorderOnDataAvailable;
     recorder.onstart = recorderOnStart;
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-record/MediaRecorder-mimetype.html
@@ -0,0 +1,147 @@
+<!doctype html>
+<html>
+<head>
+  <title>MediaRecorder MIMEType</title>
+  <link rel="help" href="https://w3c.github.io/mediacapture-record/MediaRecorder.html#dom-mediarecorder-mimetype">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<canvas id="canvas" width="200" height="200">
+</canvas>
+<script>
+function createAudioStream(t) {
+  const ac = new AudioContext();
+  const {stream} = ac.createMediaStreamDestination();
+  const tracks = stream.getTracks();
+  t.add_cleanup(() => tracks.forEach(tr => tr.stop()));
+  return stream;
+}
+
+function createVideoStream(t) {
+  const canvas = document.getElementById("canvas");
+  canvas.getContext('2d');
+  const stream = canvas.captureStream();
+  const tracks = stream.getTracks();
+  t.add_cleanup(() => tracks.forEach(tr => tr.stop()));
+  return stream;
+}
+
+function createAudioVideoStream(t) {
+  return new MediaStream([
+      ...createAudioStream(t).getTracks(),
+      ...createVideoStream(t).getTracks(),
+    ]);
+}
+
+test(t => {
+  const recorder = new MediaRecorder(createAudioStream(t));
+  assert_equals(recorder.mimeType, "",
+    "MediaRecorder has no default MIMEtype");
+}, "MediaRecorder sets no default MIMEType in the constructor for audio");
+
+test(t => {
+  const recorder = new MediaRecorder(createVideoStream(t));
+  assert_equals(recorder.mimeType, "",
+    "MediaRecorder has no default MIMEtype");
+}, "MediaRecorder sets no default MIMEType in the constructor for video");
+
+test(t => {
+  const stream = createAudioVideoStream(t);
+  const recorder = new MediaRecorder(stream);
+  assert_equals(recorder.mimeType, "",
+    "MediaRecorder has no default MIMEtype");
+}, "MediaRecorder sets no default MIMEType in the constructor for audio/video");
+
+test(t => {
+  assert_throws("NotSupportedError",
+    () => new MediaRecorder(new MediaStream(), {mimeType: "audio/banana"}));
+}, "MediaRecorder invalid audio MIMEType throws");
+
+test(t => {
+  assert_false(MediaRecorder.isTypeSupported("audio/banana"));
+}, "MediaRecorder invalid audio MIMEType is unsupported");
+
+test(t => {
+  assert_throws("NotSupportedError",
+    () => new MediaRecorder(new MediaStream(), {mimeType: "video/pineapple"}));
+}, "MediaRecorder invalid video MIMEType throws");
+
+test(t => {
+  assert_false(MediaRecorder.isTypeSupported("video/pineapple"));
+}, "MediaRecorder invalid video MIMEType is unsupported");
+
+// New MIME types could be added to this list as needed.
+for (const mimeType of [
+  'audio/mp4',
+  'video/mp4',
+  'audio/ogg',
+  'audio/ogg; codecs="vorbis"',
+  'audio/ogg; codecs="opus"',
+  'audio/webm',
+  'audio/webm; codecs="vorbis"',
+  'audio/webm; codecs="opus"',
+  'video/webm',
+  'video/webm; codecs="vp8"',
+  'video/webm; codecs="vp8, vorbis"',
+  'video/webm; codecs="vp8, opus"',
+  'video/webm; codecs="vp9"',
+  'video/webm; codecs="vp9, vorbis"',
+  'video/webm; codecs="vp9, opus"',
+  'video/webm; codecs="av1"',
+  'video/webm; codecs="av1, opus"',
+]) {
+  if (MediaRecorder.isTypeSupported(mimeType)) {
+    test(t => {
+      const recorder = new MediaRecorder(new MediaStream(), {mimeType});
+      assert_equals(recorder.mimeType, mimeType, "Supported MIMEType is set");
+    }, `Supported MIMEType ${mimeType} is set immediately after constructing`);
+  } else {
+    test(t => {
+      assert_throws("NotSupportedError",
+        () => new MediaRecorder(new MediaStream(), {mimeType}));
+    }, `Unsupported MIMEType ${mimeType} throws`);
+  }
+}
+
+test(t => {
+  const recorder = new MediaRecorder(createAudioStream(t));
+  recorder.start();
+  assert_not_equals(recorder.mimeType, "",
+    "MediaRecorder has a MIMEtype after start() for audio");
+  assert_regexp_match(recorder.mimeType, /^audio\//,
+    "MIMEtype has an expected media type");
+  assert_regexp_match(recorder.mimeType, /^[a-z]+\/[a-z]+/,
+    "MIMEtype has a container subtype");
+  assert_regexp_match(recorder.mimeType, /^[a-z]+\/[a-z]+; codecs=[^,]+$/,
+    "MIMEtype has one codec");
+}, "MediaRecorder sets a MIMEType after start() for audio");
+
+test(t => {
+  const recorder = new MediaRecorder(createVideoStream(t));
+  recorder.start();
+  assert_not_equals(recorder.mimeType, "",
+    "MediaRecorder has a MIMEtype after start() for video");
+  assert_regexp_match(recorder.mimeType, /^video\//,
+    "MIMEtype has an expected media type");
+  assert_regexp_match(recorder.mimeType, /^[a-z]+\/[a-z]+/,
+    "MIMEtype has a container subtype");
+  assert_regexp_match(recorder.mimeType, /^[a-z]+\/[a-z]+; codecs=[^,]+$/,
+    "MIMEtype has one codec");
+}, "MediaRecorder sets a MIMEType after start() for video");
+
+test(t => {
+  const recorder = new MediaRecorder(createAudioVideoStream(t));
+  recorder.start();
+  assert_not_equals(recorder.mimeType, "",
+    "MediaRecorder has a MIMEtype after start() for audio/video");
+  assert_regexp_match(recorder.mimeType, /^video\//,
+    "MIMEtype has an expected media type");
+  assert_regexp_match(recorder.mimeType, /^[a-z]+\/[a-z]+/,
+    "MIMEtype has a container subtype");
+  assert_regexp_match(recorder.mimeType, /^[a-z]+\/[a-z]+; codecs=[^,]+,[^,]+$/,
+    "MIMEtype has two codecs");
+}, "MediaRecorder sets a MIMEType after start() for audio/video");
+</script>
+</body>
+</html>
--- a/testing/web-platform/tests/mediacapture-record/MediaRecorder-pause-resume.html
+++ b/testing/web-platform/tests/mediacapture-record/MediaRecorder-pause-resume.html
@@ -6,18 +6,20 @@
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
 </head>
 <body>
 <canvas id="canvas" width="200" height="200">
 </canvas>
 <script>
     function createVideoStream() {
-        let canvas = document.getElementById("canvas");
-        canvas.getContext('2d');
+        const canvas = document.getElementById("canvas");
+        const ctx = canvas.getContext('2d');
+        ctx.fillStyle = 'green';
+        ctx.fillRect(0, 0, canvas.width, canvas.height);
         return canvas.captureStream();
     }
 
     function recordEvents(target, events) {
         let arr = [];
         for (let ev of events) {
             target.addEventListener(ev, _ => arr.push(ev));
         }
@@ -27,16 +29,17 @@
     promise_test(async () => {
         let video = createVideoStream();
         let recorder = new MediaRecorder(video);
         let events = recordEvents(recorder,
             ["start", "stop", "dataavailable", "pause", "resume", "error"]);
 
         recorder.start();
         assert_equals(recorder.state, "recording", "MediaRecorder has been started successfully");
+        await new Promise(r => recorder.onstart = r);
 
         recorder.pause();
         assert_equals(recorder.state, "paused", "MediaRecorder should be paused immediately following pause()");
 
         // A second call to pause should be idempotent
         recorder.pause();
         assert_equals(recorder.state, "paused", "MediaRecorder should be paused immediately following pause()");
 
--- a/testing/web-platform/tests/mediacapture-record/idlharness.window.js
+++ b/testing/web-platform/tests/mediacapture-record/idlharness.window.js
@@ -2,17 +2,17 @@
 // META: script=/resources/idlharness.js
 
 'use strict';
 
 // https://w3c.github.io/mediacapture-record/
 
 idl_test(
   ['mediastream-recording'],
-  ['mediacapture-streams', 'FileAPI', 'html', 'dom'],
+  ['mediacapture-streams', 'FileAPI', 'html', 'dom', 'WebIDL'],
   idl_array => {
     // Ignored errors will be surfaced in idlharness.js's test_object below.
     let recorder, blob, error;
     try {
       const canvas = document.createElement('canvas');
       document.body.appendChild(canvas);
       const context = canvas.getContext("2d");
       context.fillStyle = "red";
@@ -26,13 +26,15 @@ idl_test(
       blob = new BlobEvent("type", {
         data: new Blob(),
         timecode: performance.now(),
       });
     } catch(e) {}
     idl_array.add_objects({ BlobEvent: [blob] });
 
     try {
-      error = new MediaRecorderErrorEvent("type", {});
+      error = new MediaRecorderErrorEvent("type", {
+        error: new DOMException,
+      });
     } catch(e) {}
     idl_array.add_objects({ MediaRecorderErrorEvent: [error] });
   }
 );