Backed out changeset f594e6f00208 (bug 1940883) for causing crashes in bug 1941164.
[gecko.git] / dom / media / MediaStreamTrack.cpp
blob903c5fd14e58a72526ed960ff6a4fab17f37e6b1
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "MediaStreamTrack.h"
8 #include "DOMMediaStream.h"
9 #include "MediaSegment.h"
10 #include "MediaStreamError.h"
11 #include "MediaTrackGraphImpl.h"
12 #include "MediaTrackListener.h"
13 #include "mozilla/BasePrincipal.h"
14 #include "mozilla/dom/Promise.h"
15 #include "nsContentUtils.h"
16 #include "nsGlobalWindowInner.h"
17 #include "nsIUUIDGenerator.h"
18 #include "nsServiceManagerUtils.h"
19 #include "systemservices/MediaUtils.h"
21 #ifdef LOG
22 # undef LOG
23 #endif
25 static mozilla::LazyLogModule gMediaStreamTrackLog("MediaStreamTrack");
26 #define LOG(type, msg) MOZ_LOG(gMediaStreamTrackLog, type, msg)
28 using namespace mozilla::media;
30 namespace mozilla::dom {
32 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaStreamTrackSource)
33 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaStreamTrackSource)
34 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrackSource)
35 NS_INTERFACE_MAP_ENTRY(nsISupports)
36 NS_INTERFACE_MAP_END
38 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrackSource)
40 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaStreamTrackSource)
41 tmp->Destroy();
42 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
43 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
45 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaStreamTrackSource)
46 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
47 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
49 auto MediaStreamTrackSource::ApplyConstraints(
50 const dom::MediaTrackConstraints& aConstraints,
51 CallerType aCallerType) -> RefPtr<ApplyConstraintsPromise> {
52 return ApplyConstraintsPromise::CreateAndReject(
53 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::OverconstrainedError, ""),
54 __func__);
57 /**
58 * MTGListener monitors state changes of the media flowing through the
59 * MediaTrackGraph.
62 * For changes to PrincipalHandle the following applies:
64 * When the main thread principal for a MediaStreamTrack changes, its principal
65 * will be set to the combination of the previous principal and the new one.
67 * As a PrincipalHandle change later happens on the MediaTrackGraph thread, we
68 * will be notified. If the latest principal on main thread matches the
69 * PrincipalHandle we just saw on MTG thread, we will set the track's principal
70 * to the new one.
72 * We know at this point that the old principal has been flushed out and data
73 * under it cannot leak to consumers.
75 * In case of multiple changes to the main thread state, the track's principal
76 * will be a combination of its old principal and all the new ones until the
77 * latest main thread principal matches the PrincipalHandle on the MTG thread.
79 class MediaStreamTrack::MTGListener : public MediaTrackListener {
80 public:
81 explicit MTGListener(MediaStreamTrack* aTrack) : mTrack(aTrack) {}
83 void DoNotifyPrincipalHandleChanged(
84 const PrincipalHandle& aNewPrincipalHandle) {
85 MOZ_ASSERT(NS_IsMainThread());
87 if (!mTrack) {
88 return;
91 mTrack->NotifyPrincipalHandleChanged(aNewPrincipalHandle);
94 void NotifyPrincipalHandleChanged(
95 MediaTrackGraph* aGraph,
96 const PrincipalHandle& aNewPrincipalHandle) override {
97 aGraph->DispatchToMainThreadStableState(
98 NewRunnableMethod<StoreCopyPassByConstLRef<PrincipalHandle>>(
99 "dom::MediaStreamTrack::MTGListener::"
100 "DoNotifyPrincipalHandleChanged",
101 this, &MTGListener::DoNotifyPrincipalHandleChanged,
102 aNewPrincipalHandle));
105 void NotifyRemoved(MediaTrackGraph* aGraph) override {
106 // `mTrack` is a WeakPtr and must be destroyed on main thread.
107 // We dispatch ourselves to main thread here in case the MediaTrackGraph
108 // is holding the last reference to us.
109 aGraph->DispatchToMainThreadStableState(
110 NS_NewRunnableFunction("MediaStreamTrack::MTGListener::mTrackReleaser",
111 [self = RefPtr<MTGListener>(this)]() {}));
114 void DoNotifyEnded() {
115 MOZ_ASSERT(NS_IsMainThread());
117 if (!mTrack) {
118 return;
121 if (!mTrack->GetParentObject()) {
122 return;
125 AbstractThread::MainThread()->Dispatch(
126 NewRunnableMethod("MediaStreamTrack::OverrideEnded", mTrack.get(),
127 &MediaStreamTrack::OverrideEnded));
130 void NotifyEnded(MediaTrackGraph* aGraph) override {
131 aGraph->DispatchToMainThreadStableState(
132 NewRunnableMethod("MediaStreamTrack::MTGListener::DoNotifyEnded", this,
133 &MTGListener::DoNotifyEnded));
136 protected:
137 // Main thread only.
138 WeakPtr<MediaStreamTrack> mTrack;
141 class MediaStreamTrack::TrackSink : public MediaStreamTrackSource::Sink {
142 public:
143 explicit TrackSink(MediaStreamTrack* aTrack) : mTrack(aTrack) {}
146 * Keep the track source alive. This track and any clones are controlling the
147 * lifetime of the source by being registered as its sinks.
149 bool KeepsSourceAlive() const override { return true; }
151 bool Enabled() const override {
152 if (!mTrack) {
153 return false;
155 return mTrack->Enabled();
158 void PrincipalChanged() override {
159 if (mTrack) {
160 mTrack->PrincipalChanged();
164 void MutedChanged(bool aNewState) override {
165 if (mTrack) {
166 mTrack->MutedChanged(aNewState);
170 void OverrideEnded() override {
171 if (mTrack) {
172 mTrack->OverrideEnded();
176 private:
177 WeakPtr<MediaStreamTrack> mTrack;
180 MediaStreamTrack::MediaStreamTrack(nsPIDOMWindowInner* aWindow,
181 mozilla::MediaTrack* aInputTrack,
182 MediaStreamTrackSource* aSource,
183 MediaStreamTrackState aReadyState,
184 bool aMuted,
185 const MediaTrackConstraints& aConstraints)
186 : mWindow(aWindow),
187 mInputTrack(aInputTrack),
188 mSource(aSource),
189 mSink(MakeUnique<TrackSink>(this)),
190 mPrincipal(aSource->GetPrincipal()),
191 mReadyState(aReadyState),
192 mEnabled(true),
193 mMuted(aMuted),
194 mConstraints(aConstraints) {
195 if (!Ended()) {
196 GetSource().RegisterSink(mSink.get());
198 // Even if the input track is destroyed we need mTrack so that methods
199 // like AddListener still work. Keeping the number of paths to a minimum
200 // also helps prevent bugs elsewhere. We'll be ended through the
201 // MediaStreamTrackSource soon enough.
202 auto graph = mInputTrack->IsDestroyed()
203 ? MediaTrackGraph::GetInstanceIfExists(
204 mWindow, mInputTrack->mSampleRate,
205 MediaTrackGraph::DEFAULT_OUTPUT_DEVICE)
206 : mInputTrack->Graph();
207 MOZ_DIAGNOSTIC_ASSERT(graph,
208 "A destroyed input track is only expected when "
209 "cloning, but since we're live there must be another "
210 "live track that is keeping the graph alive");
212 mTrack = graph->CreateForwardedInputTrack(mInputTrack->mType);
213 mPort = mTrack->AllocateInputPort(mInputTrack);
214 mMTGListener = new MTGListener(this);
215 AddListener(mMTGListener);
218 nsresult rv;
219 nsCOMPtr<nsIUUIDGenerator> uuidgen =
220 do_GetService("@mozilla.org/uuid-generator;1", &rv);
222 nsID uuid;
223 memset(&uuid, 0, sizeof(uuid));
224 if (uuidgen) {
225 uuidgen->GenerateUUIDInPlace(&uuid);
228 char chars[NSID_LENGTH];
229 uuid.ToProvidedString(chars);
230 mID = NS_ConvertASCIItoUTF16(chars);
233 MediaStreamTrack::~MediaStreamTrack() { Destroy(); }
235 void MediaStreamTrack::Destroy() {
236 SetReadyState(MediaStreamTrackState::Ended);
237 // Remove all listeners -- avoid iterating over the list we're removing from
238 for (const auto& listener : mTrackListeners.Clone()) {
239 RemoveListener(listener);
241 // Do the same as above for direct listeners
242 for (const auto& listener : mDirectTrackListeners.Clone()) {
243 RemoveDirectListener(listener);
247 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrack)
249 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaStreamTrack,
250 DOMEventTargetHelper)
251 tmp->Destroy();
252 NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
253 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSource)
254 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
255 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPrincipal)
256 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
257 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
259 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaStreamTrack,
260 DOMEventTargetHelper)
261 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
262 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSource)
263 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
264 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPrincipal)
265 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
267 NS_IMPL_ADDREF_INHERITED(MediaStreamTrack, DOMEventTargetHelper)
268 NS_IMPL_RELEASE_INHERITED(MediaStreamTrack, DOMEventTargetHelper)
269 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrack)
270 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
272 JSObject* MediaStreamTrack::WrapObject(JSContext* aCx,
273 JS::Handle<JSObject*> aGivenProto) {
274 return MediaStreamTrack_Binding::Wrap(aCx, this, aGivenProto);
277 void MediaStreamTrack::GetId(nsAString& aID) const { aID = mID; }
279 void MediaStreamTrack::SetEnabled(bool aEnabled) {
280 LOG(LogLevel::Info,
281 ("MediaStreamTrack %p %s", this, aEnabled ? "Enabled" : "Disabled"));
283 if (mEnabled == aEnabled) {
284 return;
287 mEnabled = aEnabled;
289 if (Ended()) {
290 return;
293 mTrack->SetDisabledTrackMode(mEnabled ? DisabledTrackMode::ENABLED
294 : DisabledTrackMode::SILENCE_BLACK);
295 NotifyEnabledChanged();
298 void MediaStreamTrack::Stop() {
299 LOG(LogLevel::Info, ("MediaStreamTrack %p Stop()", this));
301 if (Ended()) {
302 LOG(LogLevel::Warning, ("MediaStreamTrack %p Already ended", this));
303 return;
306 SetReadyState(MediaStreamTrackState::Ended);
308 NotifyEnded();
311 void MediaStreamTrack::GetCapabilities(MediaTrackCapabilities& aResult,
312 CallerType aCallerType) {
313 GetSource().GetCapabilities(aResult);
316 void MediaStreamTrack::GetConstraints(dom::MediaTrackConstraints& aResult) {
317 aResult = mConstraints;
320 void MediaStreamTrack::GetSettings(dom::MediaTrackSettings& aResult,
321 CallerType aCallerType) {
322 GetSource().GetSettings(aResult);
324 // Spoof values when privacy.resistFingerprinting is true.
325 nsIGlobalObject* global = mWindow ? mWindow->AsGlobal() : nullptr;
326 if (!nsContentUtils::ShouldResistFingerprinting(
327 aCallerType, global, RFPTarget::StreamVideoFacingMode)) {
328 return;
330 if (aResult.mFacingMode.WasPassed()) {
331 aResult.mFacingMode.Value().AssignASCII(
332 GetEnumString(VideoFacingModeEnum::User));
336 already_AddRefed<Promise> MediaStreamTrack::ApplyConstraints(
337 const MediaTrackConstraints& aConstraints, CallerType aCallerType,
338 ErrorResult& aRv) {
339 if (MOZ_LOG_TEST(gMediaStreamTrackLog, LogLevel::Info)) {
340 nsString str;
341 aConstraints.ToJSON(str);
343 LOG(LogLevel::Info, ("MediaStreamTrack %p ApplyConstraints() with "
344 "constraints %s",
345 this, NS_ConvertUTF16toUTF8(str).get()));
348 nsIGlobalObject* go = mWindow ? mWindow->AsGlobal() : nullptr;
350 RefPtr<Promise> promise = Promise::Create(go, aRv);
351 if (aRv.Failed()) {
352 return nullptr;
355 // Forward constraints to the source.
357 // After GetSource().ApplyConstraints succeeds (after it's been to
358 // media-thread and back), and no sooner, do we set mConstraints to the newly
359 // applied values.
361 // Keep a reference to this, to make sure it's still here when we get back.
362 RefPtr<MediaStreamTrack> self(this);
363 GetSource()
364 .ApplyConstraints(aConstraints, aCallerType)
365 ->Then(
366 GetCurrentSerialEventTarget(), __func__,
367 [this, self, promise, aConstraints](bool aDummy) {
368 if (!mWindow || !mWindow->IsCurrentInnerWindow()) {
369 return; // Leave Promise pending after navigation by design.
371 mConstraints = aConstraints;
372 promise->MaybeResolve(false);
374 [this, self, promise](const RefPtr<MediaMgrError>& aError) {
375 if (!mWindow || !mWindow->IsCurrentInnerWindow()) {
376 return; // Leave Promise pending after navigation by design.
378 promise->MaybeReject(
379 MakeRefPtr<MediaStreamError>(mWindow, *aError));
381 return promise.forget();
384 ProcessedMediaTrack* MediaStreamTrack::GetTrack() const {
385 MOZ_DIAGNOSTIC_ASSERT(!Ended());
386 return mTrack;
389 MediaTrackGraph* MediaStreamTrack::Graph() const {
390 MOZ_DIAGNOSTIC_ASSERT(!Ended());
391 return mTrack->Graph();
394 MediaTrackGraphImpl* MediaStreamTrack::GraphImpl() const {
395 MOZ_DIAGNOSTIC_ASSERT(!Ended());
396 return mTrack->GraphImpl();
399 void MediaStreamTrack::SetPrincipal(nsIPrincipal* aPrincipal) {
400 if (aPrincipal == mPrincipal) {
401 return;
403 mPrincipal = aPrincipal;
405 LOG(LogLevel::Info,
406 ("MediaStreamTrack %p principal changed to %p. Now: "
407 "null=%d, codebase=%d, expanded=%d, system=%d",
408 this, mPrincipal.get(), mPrincipal->GetIsNullPrincipal(),
409 mPrincipal->GetIsContentPrincipal(),
410 mPrincipal->GetIsExpandedPrincipal(), mPrincipal->IsSystemPrincipal()));
411 for (PrincipalChangeObserver<MediaStreamTrack>* observer :
412 mPrincipalChangeObservers) {
413 observer->PrincipalChanged(this);
417 void MediaStreamTrack::PrincipalChanged() {
418 mPendingPrincipal = GetSource().GetPrincipal();
419 nsCOMPtr<nsIPrincipal> newPrincipal = mPrincipal;
420 LOG(LogLevel::Info, ("MediaStreamTrack %p Principal changed on main thread "
421 "to %p (pending). Combining with existing principal %p.",
422 this, mPendingPrincipal.get(), mPrincipal.get()));
423 if (nsContentUtils::CombineResourcePrincipals(&newPrincipal,
424 mPendingPrincipal)) {
425 SetPrincipal(newPrincipal);
429 void MediaStreamTrack::NotifyPrincipalHandleChanged(
430 const PrincipalHandle& aNewPrincipalHandle) {
431 PrincipalHandle handle(aNewPrincipalHandle);
432 LOG(LogLevel::Info, ("MediaStreamTrack %p principalHandle changed on "
433 "MediaTrackGraph thread to %p. Current principal: %p, "
434 "pending: %p",
435 this, GetPrincipalFromHandle(handle), mPrincipal.get(),
436 mPendingPrincipal.get()));
437 if (PrincipalHandleMatches(handle, mPendingPrincipal)) {
438 SetPrincipal(mPendingPrincipal);
439 mPendingPrincipal = nullptr;
443 void MediaStreamTrack::MutedChanged(bool aNewState) {
444 MOZ_ASSERT(NS_IsMainThread());
447 * 4.3.1 Life-cycle and Media flow - Media flow
448 * To set a track's muted state to newState, the User Agent MUST run the
449 * following steps:
450 * 1. Let track be the MediaStreamTrack in question.
451 * 2. Set track's muted attribute to newState.
452 * 3. If newState is true let eventName be mute, otherwise unmute.
453 * 4. Fire a simple event named eventName on track.
456 if (mMuted == aNewState) {
457 return;
460 LOG(LogLevel::Info,
461 ("MediaStreamTrack %p became %s", this, aNewState ? "muted" : "unmuted"));
463 mMuted = aNewState;
465 if (Ended()) {
466 return;
469 nsString eventName = aNewState ? u"mute"_ns : u"unmute"_ns;
470 DispatchTrustedEvent(eventName);
473 void MediaStreamTrack::NotifyEnded() {
474 MOZ_ASSERT(mReadyState == MediaStreamTrackState::Ended);
476 for (const auto& consumer : mConsumers.Clone()) {
477 if (consumer) {
478 consumer->NotifyEnded(this);
479 } else {
480 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
481 mConsumers.RemoveElement(consumer);
486 void MediaStreamTrack::NotifyEnabledChanged() {
487 GetSource().SinkEnabledStateChanged();
489 for (const auto& consumer : mConsumers.Clone()) {
490 if (consumer) {
491 consumer->NotifyEnabledChanged(this, Enabled());
492 } else {
493 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
494 mConsumers.RemoveElement(consumer);
499 bool MediaStreamTrack::AddPrincipalChangeObserver(
500 PrincipalChangeObserver<MediaStreamTrack>* aObserver) {
501 // XXX(Bug 1631371) Check if this should use a fallible operation as it
502 // pretended earlier.
503 mPrincipalChangeObservers.AppendElement(aObserver);
504 return true;
507 bool MediaStreamTrack::RemovePrincipalChangeObserver(
508 PrincipalChangeObserver<MediaStreamTrack>* aObserver) {
509 return mPrincipalChangeObservers.RemoveElement(aObserver);
512 void MediaStreamTrack::AddConsumer(MediaStreamTrackConsumer* aConsumer) {
513 MOZ_ASSERT(!mConsumers.Contains(aConsumer));
514 mConsumers.AppendElement(aConsumer);
516 // Remove destroyed consumers for cleanliness
517 while (mConsumers.RemoveElement(nullptr)) {
518 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
522 void MediaStreamTrack::RemoveConsumer(MediaStreamTrackConsumer* aConsumer) {
523 mConsumers.RemoveElement(aConsumer);
525 // Remove destroyed consumers for cleanliness
526 while (mConsumers.RemoveElement(nullptr)) {
527 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
531 already_AddRefed<MediaStreamTrack> MediaStreamTrack::Clone() {
532 RefPtr<MediaStreamTrack> newTrack = CloneInternal();
533 newTrack->SetEnabled(Enabled());
534 newTrack->SetMuted(Muted());
535 return newTrack.forget();
538 void MediaStreamTrack::SetReadyState(MediaStreamTrackState aState) {
539 MOZ_ASSERT(!(mReadyState == MediaStreamTrackState::Ended &&
540 aState == MediaStreamTrackState::Live),
541 "We don't support overriding the ready state from ended to live");
543 if (Ended()) {
544 return;
547 if (mReadyState == MediaStreamTrackState::Live &&
548 aState == MediaStreamTrackState::Ended) {
549 if (mSource) {
550 mSource->UnregisterSink(mSink.get());
552 if (mMTGListener) {
553 RemoveListener(mMTGListener);
555 if (mPort) {
556 mPort->Destroy();
558 if (mTrack) {
559 mTrack->Destroy();
561 mPort = nullptr;
562 mTrack = nullptr;
563 mMTGListener = nullptr;
566 mReadyState = aState;
569 void MediaStreamTrack::OverrideEnded() {
570 MOZ_ASSERT(NS_IsMainThread());
572 if (Ended()) {
573 return;
576 LOG(LogLevel::Info, ("MediaStreamTrack %p ended", this));
578 SetReadyState(MediaStreamTrackState::Ended);
580 NotifyEnded();
582 DispatchTrustedEvent(u"ended"_ns);
585 void MediaStreamTrack::AddListener(MediaTrackListener* aListener) {
586 LOG(LogLevel::Debug,
587 ("MediaStreamTrack %p adding listener %p", this, aListener));
588 mTrackListeners.AppendElement(aListener);
590 if (Ended()) {
591 return;
593 mTrack->AddListener(aListener);
596 void MediaStreamTrack::RemoveListener(MediaTrackListener* aListener) {
597 LOG(LogLevel::Debug,
598 ("MediaStreamTrack %p removing listener %p", this, aListener));
599 mTrackListeners.RemoveElement(aListener);
601 if (Ended()) {
602 return;
604 mTrack->RemoveListener(aListener);
607 void MediaStreamTrack::AddDirectListener(DirectMediaTrackListener* aListener) {
608 LOG(LogLevel::Debug, ("MediaStreamTrack %p (%s) adding direct listener %p to "
609 "track %p",
610 this, AsAudioStreamTrack() ? "audio" : "video",
611 aListener, mTrack.get()));
612 mDirectTrackListeners.AppendElement(aListener);
614 if (Ended()) {
615 return;
617 mTrack->AddDirectListener(aListener);
620 void MediaStreamTrack::RemoveDirectListener(
621 DirectMediaTrackListener* aListener) {
622 LOG(LogLevel::Debug,
623 ("MediaStreamTrack %p removing direct listener %p from track %p", this,
624 aListener, mTrack.get()));
625 mDirectTrackListeners.RemoveElement(aListener);
627 if (Ended()) {
628 return;
630 mTrack->RemoveDirectListener(aListener);
633 already_AddRefed<MediaInputPort> MediaStreamTrack::ForwardTrackContentsTo(
634 ProcessedMediaTrack* aTrack) {
635 MOZ_ASSERT(NS_IsMainThread());
636 MOZ_RELEASE_ASSERT(aTrack);
637 return aTrack->AllocateInputPort(mTrack);
640 } // namespace mozilla::dom