Backed out changeset ddccd40117a0 (bug 1853271) for causing bug 1854769. CLOSED TREE
[gecko.git] / dom / media / MediaStreamTrack.cpp
blob58b44cb30837784076b284cff87e0e07cadcde1c
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, CallerType aCallerType)
51 -> 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 =
126 nsGlobalWindowInner::Cast(mTrack->GetParentObject())
127 ->AbstractMainThreadFor(TaskCategory::Other);
128 mainThread->Dispatch(NewRunnableMethod("MediaStreamTrack::OverrideEnded",
129 mTrack.get(),
130 &MediaStreamTrack::OverrideEnded));
133 void NotifyEnded(MediaTrackGraph* aGraph) override {
134 aGraph->DispatchToMainThreadStableState(
135 NewRunnableMethod("MediaStreamTrack::MTGListener::DoNotifyEnded", this,
136 &MTGListener::DoNotifyEnded));
139 protected:
140 // Main thread only.
141 WeakPtr<MediaStreamTrack> mTrack;
144 class MediaStreamTrack::TrackSink : public MediaStreamTrackSource::Sink {
145 public:
146 explicit TrackSink(MediaStreamTrack* aTrack) : mTrack(aTrack) {}
149 * Keep the track source alive. This track and any clones are controlling the
150 * lifetime of the source by being registered as its sinks.
152 bool KeepsSourceAlive() const override { return true; }
154 bool Enabled() const override {
155 if (!mTrack) {
156 return false;
158 return mTrack->Enabled();
161 void PrincipalChanged() override {
162 if (mTrack) {
163 mTrack->PrincipalChanged();
167 void MutedChanged(bool aNewState) override {
168 if (mTrack) {
169 mTrack->MutedChanged(aNewState);
173 void OverrideEnded() override {
174 if (mTrack) {
175 mTrack->OverrideEnded();
179 private:
180 WeakPtr<MediaStreamTrack> mTrack;
183 MediaStreamTrack::MediaStreamTrack(nsPIDOMWindowInner* aWindow,
184 mozilla::MediaTrack* aInputTrack,
185 MediaStreamTrackSource* aSource,
186 MediaStreamTrackState aReadyState,
187 bool aMuted,
188 const MediaTrackConstraints& aConstraints)
189 : mWindow(aWindow),
190 mInputTrack(aInputTrack),
191 mSource(aSource),
192 mSink(MakeUnique<TrackSink>(this)),
193 mPrincipal(aSource->GetPrincipal()),
194 mReadyState(aReadyState),
195 mEnabled(true),
196 mMuted(aMuted),
197 mConstraints(aConstraints) {
198 if (!Ended()) {
199 GetSource().RegisterSink(mSink.get());
201 // Even if the input track is destroyed we need mTrack so that methods
202 // like AddListener still work. Keeping the number of paths to a minimum
203 // also helps prevent bugs elsewhere. We'll be ended through the
204 // MediaStreamTrackSource soon enough.
205 auto graph = mInputTrack->IsDestroyed()
206 ? MediaTrackGraph::GetInstanceIfExists(
207 mWindow, mInputTrack->mSampleRate,
208 MediaTrackGraph::DEFAULT_OUTPUT_DEVICE)
209 : mInputTrack->Graph();
210 MOZ_DIAGNOSTIC_ASSERT(graph,
211 "A destroyed input track is only expected when "
212 "cloning, but since we're live there must be another "
213 "live track that is keeping the graph alive");
215 mTrack = graph->CreateForwardedInputTrack(mInputTrack->mType);
216 mPort = mTrack->AllocateInputPort(mInputTrack);
217 mMTGListener = new MTGListener(this);
218 AddListener(mMTGListener);
221 nsresult rv;
222 nsCOMPtr<nsIUUIDGenerator> uuidgen =
223 do_GetService("@mozilla.org/uuid-generator;1", &rv);
225 nsID uuid;
226 memset(&uuid, 0, sizeof(uuid));
227 if (uuidgen) {
228 uuidgen->GenerateUUIDInPlace(&uuid);
231 char chars[NSID_LENGTH];
232 uuid.ToProvidedString(chars);
233 mID = NS_ConvertASCIItoUTF16(chars);
236 MediaStreamTrack::~MediaStreamTrack() { Destroy(); }
238 void MediaStreamTrack::Destroy() {
239 SetReadyState(MediaStreamTrackState::Ended);
240 // Remove all listeners -- avoid iterating over the list we're removing from
241 for (const auto& listener : mTrackListeners.Clone()) {
242 RemoveListener(listener);
244 // Do the same as above for direct listeners
245 for (const auto& listener : mDirectTrackListeners.Clone()) {
246 RemoveDirectListener(listener);
250 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrack)
252 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaStreamTrack,
253 DOMEventTargetHelper)
254 tmp->Destroy();
255 NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
256 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSource)
257 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
258 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPrincipal)
259 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
260 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
262 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaStreamTrack,
263 DOMEventTargetHelper)
264 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
265 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSource)
266 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
267 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPrincipal)
268 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
270 NS_IMPL_ADDREF_INHERITED(MediaStreamTrack, DOMEventTargetHelper)
271 NS_IMPL_RELEASE_INHERITED(MediaStreamTrack, DOMEventTargetHelper)
272 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrack)
273 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
275 JSObject* MediaStreamTrack::WrapObject(JSContext* aCx,
276 JS::Handle<JSObject*> aGivenProto) {
277 return MediaStreamTrack_Binding::Wrap(aCx, this, aGivenProto);
280 void MediaStreamTrack::GetId(nsAString& aID) const { aID = mID; }
282 void MediaStreamTrack::SetEnabled(bool aEnabled) {
283 LOG(LogLevel::Info,
284 ("MediaStreamTrack %p %s", this, aEnabled ? "Enabled" : "Disabled"));
286 if (mEnabled == aEnabled) {
287 return;
290 mEnabled = aEnabled;
292 if (Ended()) {
293 return;
296 mTrack->SetDisabledTrackMode(mEnabled ? DisabledTrackMode::ENABLED
297 : DisabledTrackMode::SILENCE_BLACK);
298 NotifyEnabledChanged();
301 void MediaStreamTrack::Stop() {
302 LOG(LogLevel::Info, ("MediaStreamTrack %p Stop()", this));
304 if (Ended()) {
305 LOG(LogLevel::Warning, ("MediaStreamTrack %p Already ended", this));
306 return;
309 SetReadyState(MediaStreamTrackState::Ended);
311 NotifyEnded();
314 void MediaStreamTrack::GetConstraints(dom::MediaTrackConstraints& aResult) {
315 aResult = mConstraints;
318 void MediaStreamTrack::GetSettings(dom::MediaTrackSettings& aResult,
319 CallerType aCallerType) {
320 GetSource().GetSettings(aResult);
322 // Spoof values when privacy.resistFingerprinting is true.
323 nsIGlobalObject* global = mWindow ? mWindow->AsGlobal() : nullptr;
324 if (!nsContentUtils::ShouldResistFingerprinting(
325 aCallerType, global, RFPTarget::StreamVideoFacingMode)) {
326 return;
328 if (aResult.mFacingMode.WasPassed()) {
329 aResult.mFacingMode.Value().AssignASCII(
330 VideoFacingModeEnumValues::GetString(VideoFacingModeEnum::User));
334 already_AddRefed<Promise> MediaStreamTrack::ApplyConstraints(
335 const MediaTrackConstraints& aConstraints, CallerType aCallerType,
336 ErrorResult& aRv) {
337 if (MOZ_LOG_TEST(gMediaStreamTrackLog, LogLevel::Info)) {
338 nsString str;
339 aConstraints.ToJSON(str);
341 LOG(LogLevel::Info, ("MediaStreamTrack %p ApplyConstraints() with "
342 "constraints %s",
343 this, NS_ConvertUTF16toUTF8(str).get()));
346 nsIGlobalObject* go = mWindow ? mWindow->AsGlobal() : nullptr;
348 RefPtr<Promise> promise = Promise::Create(go, aRv);
349 if (aRv.Failed()) {
350 return nullptr;
353 // Forward constraints to the source.
355 // After GetSource().ApplyConstraints succeeds (after it's been to
356 // media-thread and back), and no sooner, do we set mConstraints to the newly
357 // applied values.
359 // Keep a reference to this, to make sure it's still here when we get back.
360 RefPtr<MediaStreamTrack> self(this);
361 GetSource()
362 .ApplyConstraints(aConstraints, aCallerType)
363 ->Then(
364 GetCurrentSerialEventTarget(), __func__,
365 [this, self, promise, aConstraints](bool aDummy) {
366 if (!mWindow || !mWindow->IsCurrentInnerWindow()) {
367 return; // Leave Promise pending after navigation by design.
369 mConstraints = aConstraints;
370 promise->MaybeResolve(false);
372 [this, self, promise](const RefPtr<MediaMgrError>& aError) {
373 if (!mWindow || !mWindow->IsCurrentInnerWindow()) {
374 return; // Leave Promise pending after navigation by design.
376 promise->MaybeReject(
377 MakeRefPtr<MediaStreamError>(mWindow, *aError));
379 return promise.forget();
382 ProcessedMediaTrack* MediaStreamTrack::GetTrack() const {
383 MOZ_DIAGNOSTIC_ASSERT(!Ended());
384 return mTrack;
387 MediaTrackGraph* MediaStreamTrack::Graph() const {
388 MOZ_DIAGNOSTIC_ASSERT(!Ended());
389 return mTrack->Graph();
392 MediaTrackGraphImpl* MediaStreamTrack::GraphImpl() const {
393 MOZ_DIAGNOSTIC_ASSERT(!Ended());
394 return mTrack->GraphImpl();
397 void MediaStreamTrack::SetPrincipal(nsIPrincipal* aPrincipal) {
398 if (aPrincipal == mPrincipal) {
399 return;
401 mPrincipal = aPrincipal;
403 LOG(LogLevel::Info,
404 ("MediaStreamTrack %p principal changed to %p. Now: "
405 "null=%d, codebase=%d, expanded=%d, system=%d",
406 this, mPrincipal.get(), mPrincipal->GetIsNullPrincipal(),
407 mPrincipal->GetIsContentPrincipal(),
408 mPrincipal->GetIsExpandedPrincipal(), mPrincipal->IsSystemPrincipal()));
409 for (PrincipalChangeObserver<MediaStreamTrack>* observer :
410 mPrincipalChangeObservers) {
411 observer->PrincipalChanged(this);
415 void MediaStreamTrack::PrincipalChanged() {
416 mPendingPrincipal = GetSource().GetPrincipal();
417 nsCOMPtr<nsIPrincipal> newPrincipal = mPrincipal;
418 LOG(LogLevel::Info, ("MediaStreamTrack %p Principal changed on main thread "
419 "to %p (pending). Combining with existing principal %p.",
420 this, mPendingPrincipal.get(), mPrincipal.get()));
421 if (nsContentUtils::CombineResourcePrincipals(&newPrincipal,
422 mPendingPrincipal)) {
423 SetPrincipal(newPrincipal);
427 void MediaStreamTrack::NotifyPrincipalHandleChanged(
428 const PrincipalHandle& aNewPrincipalHandle) {
429 PrincipalHandle handle(aNewPrincipalHandle);
430 LOG(LogLevel::Info, ("MediaStreamTrack %p principalHandle changed on "
431 "MediaTrackGraph thread to %p. Current principal: %p, "
432 "pending: %p",
433 this, GetPrincipalFromHandle(handle), mPrincipal.get(),
434 mPendingPrincipal.get()));
435 if (PrincipalHandleMatches(handle, mPendingPrincipal)) {
436 SetPrincipal(mPendingPrincipal);
437 mPendingPrincipal = nullptr;
441 void MediaStreamTrack::MutedChanged(bool aNewState) {
442 MOZ_ASSERT(NS_IsMainThread());
445 * 4.3.1 Life-cycle and Media flow - Media flow
446 * To set a track's muted state to newState, the User Agent MUST run the
447 * following steps:
448 * 1. Let track be the MediaStreamTrack in question.
449 * 2. Set track's muted attribute to newState.
450 * 3. If newState is true let eventName be mute, otherwise unmute.
451 * 4. Fire a simple event named eventName on track.
454 if (mMuted == aNewState) {
455 return;
458 LOG(LogLevel::Info,
459 ("MediaStreamTrack %p became %s", this, aNewState ? "muted" : "unmuted"));
461 mMuted = aNewState;
463 if (Ended()) {
464 return;
467 nsString eventName = aNewState ? u"mute"_ns : u"unmute"_ns;
468 DispatchTrustedEvent(eventName);
471 void MediaStreamTrack::NotifyEnded() {
472 MOZ_ASSERT(mReadyState == MediaStreamTrackState::Ended);
474 for (const auto& consumer : mConsumers.Clone()) {
475 if (consumer) {
476 consumer->NotifyEnded(this);
477 } else {
478 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
479 mConsumers.RemoveElement(consumer);
484 void MediaStreamTrack::NotifyEnabledChanged() {
485 GetSource().SinkEnabledStateChanged();
487 for (const auto& consumer : mConsumers.Clone()) {
488 if (consumer) {
489 consumer->NotifyEnabledChanged(this, Enabled());
490 } else {
491 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
492 mConsumers.RemoveElement(consumer);
497 bool MediaStreamTrack::AddPrincipalChangeObserver(
498 PrincipalChangeObserver<MediaStreamTrack>* aObserver) {
499 // XXX(Bug 1631371) Check if this should use a fallible operation as it
500 // pretended earlier.
501 mPrincipalChangeObservers.AppendElement(aObserver);
502 return true;
505 bool MediaStreamTrack::RemovePrincipalChangeObserver(
506 PrincipalChangeObserver<MediaStreamTrack>* aObserver) {
507 return mPrincipalChangeObservers.RemoveElement(aObserver);
510 void MediaStreamTrack::AddConsumer(MediaStreamTrackConsumer* aConsumer) {
511 MOZ_ASSERT(!mConsumers.Contains(aConsumer));
512 mConsumers.AppendElement(aConsumer);
514 // Remove destroyed consumers for cleanliness
515 while (mConsumers.RemoveElement(nullptr)) {
516 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
520 void MediaStreamTrack::RemoveConsumer(MediaStreamTrackConsumer* aConsumer) {
521 mConsumers.RemoveElement(aConsumer);
523 // Remove destroyed consumers for cleanliness
524 while (mConsumers.RemoveElement(nullptr)) {
525 MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
529 already_AddRefed<MediaStreamTrack> MediaStreamTrack::Clone() {
530 RefPtr<MediaStreamTrack> newTrack = CloneInternal();
531 newTrack->SetEnabled(Enabled());
532 newTrack->SetMuted(Muted());
533 return newTrack.forget();
536 void MediaStreamTrack::SetReadyState(MediaStreamTrackState aState) {
537 MOZ_ASSERT(!(mReadyState == MediaStreamTrackState::Ended &&
538 aState == MediaStreamTrackState::Live),
539 "We don't support overriding the ready state from ended to live");
541 if (Ended()) {
542 return;
545 if (mReadyState == MediaStreamTrackState::Live &&
546 aState == MediaStreamTrackState::Ended) {
547 if (mSource) {
548 mSource->UnregisterSink(mSink.get());
550 if (mMTGListener) {
551 RemoveListener(mMTGListener);
553 if (mPort) {
554 mPort->Destroy();
556 if (mTrack) {
557 mTrack->Destroy();
559 mPort = nullptr;
560 mTrack = nullptr;
561 mMTGListener = nullptr;
564 mReadyState = aState;
567 void MediaStreamTrack::OverrideEnded() {
568 MOZ_ASSERT(NS_IsMainThread());
570 if (Ended()) {
571 return;
574 LOG(LogLevel::Info, ("MediaStreamTrack %p ended", this));
576 SetReadyState(MediaStreamTrackState::Ended);
578 NotifyEnded();
580 DispatchTrustedEvent(u"ended"_ns);
583 void MediaStreamTrack::AddListener(MediaTrackListener* aListener) {
584 LOG(LogLevel::Debug,
585 ("MediaStreamTrack %p adding listener %p", this, aListener));
586 mTrackListeners.AppendElement(aListener);
588 if (Ended()) {
589 return;
591 mTrack->AddListener(aListener);
594 void MediaStreamTrack::RemoveListener(MediaTrackListener* aListener) {
595 LOG(LogLevel::Debug,
596 ("MediaStreamTrack %p removing listener %p", this, aListener));
597 mTrackListeners.RemoveElement(aListener);
599 if (Ended()) {
600 return;
602 mTrack->RemoveListener(aListener);
605 void MediaStreamTrack::AddDirectListener(DirectMediaTrackListener* aListener) {
606 LOG(LogLevel::Debug, ("MediaStreamTrack %p (%s) adding direct listener %p to "
607 "track %p",
608 this, AsAudioStreamTrack() ? "audio" : "video",
609 aListener, mTrack.get()));
610 mDirectTrackListeners.AppendElement(aListener);
612 if (Ended()) {
613 return;
615 mTrack->AddDirectListener(aListener);
618 void MediaStreamTrack::RemoveDirectListener(
619 DirectMediaTrackListener* aListener) {
620 LOG(LogLevel::Debug,
621 ("MediaStreamTrack %p removing direct listener %p from track %p", this,
622 aListener, mTrack.get()));
623 mDirectTrackListeners.RemoveElement(aListener);
625 if (Ended()) {
626 return;
628 mTrack->RemoveDirectListener(aListener);
631 already_AddRefed<MediaInputPort> MediaStreamTrack::ForwardTrackContentsTo(
632 ProcessedMediaTrack* aTrack) {
633 MOZ_ASSERT(NS_IsMainThread());
634 MOZ_RELEASE_ASSERT(aTrack);
635 return aTrack->AllocateInputPort(mTrack);
638 } // namespace mozilla::dom