Blob Blame History Raw
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "MediaStreamTrack.h"

#include "DOMMediaStream.h"
#include "MediaStreamError.h"
#include "MediaStreamGraph.h"
#include "MediaStreamListener.h"
#include "mozilla/dom/Promise.h"
#include "nsContentUtils.h"
#include "nsIUUIDGenerator.h"
#include "nsServiceManagerUtils.h"
#include "systemservices/MediaUtils.h"

#ifdef LOG
#undef LOG
#endif

static mozilla::LazyLogModule gMediaStreamTrackLog("MediaStreamTrack");
#define LOG(type, msg) MOZ_LOG(gMediaStreamTrackLog, type, msg)

using namespace mozilla::media;

namespace mozilla {
namespace dom {

NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaStreamTrackSource)
NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaStreamTrackSource)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrackSource)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrackSource)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaStreamTrackSource)
  tmp->Destroy();
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaStreamTrackSource)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

auto MediaStreamTrackSource::ApplyConstraints(
    nsPIDOMWindowInner* aWindow, const dom::MediaTrackConstraints& aConstraints,
    CallerType aCallerType) -> already_AddRefed<PledgeVoid> {
  RefPtr<PledgeVoid> p = new PledgeVoid();
  p->Reject(new MediaStreamError(aWindow,
                                 NS_LITERAL_STRING("OverconstrainedError"),
                                 NS_LITERAL_STRING("")));
  return p.forget();
}

/**
 * PrincipalHandleListener monitors changes in PrincipalHandle of the media
 * flowing through the MediaStreamGraph.
 *
 * When the main thread principal for a MediaStreamTrack changes, its principal
 * will be set to the combination of the previous principal and the new one.
 *
 * As a PrincipalHandle change later happens on the MediaStreamGraph thread, we
 * will be notified. If the latest principal on main thread matches the
 * PrincipalHandle we just saw on MSG thread, we will set the track's principal
 * to the new one.
 *
 * We know at this point that the old principal has been flushed out and data
 * under it cannot leak to consumers.
 *
 * In case of multiple changes to the main thread state, the track's principal
 * will be a combination of its old principal and all the new ones until the
 * latest main thread principal matches the PrincipalHandle on the MSG thread.
 */
class MediaStreamTrack::PrincipalHandleListener
    : public MediaStreamTrackListener {
 public:
  explicit PrincipalHandleListener(MediaStreamTrack* aTrack) : mTrack(aTrack) {}

  void Forget() {
    MOZ_ASSERT(NS_IsMainThread());
    mTrack = nullptr;
  }

  void DoNotifyPrincipalHandleChanged(
      const PrincipalHandle& aNewPrincipalHandle) {
    MOZ_ASSERT(NS_IsMainThread());

    if (!mTrack) {
      return;
    }

    mTrack->NotifyPrincipalHandleChanged(aNewPrincipalHandle);
  }

  void NotifyPrincipalHandleChanged(
      MediaStreamGraph* aGraph,
      const PrincipalHandle& aNewPrincipalHandle) override {
    aGraph->DispatchToMainThreadAfterStreamStateUpdate(
        NewRunnableMethod<StoreCopyPassByConstLRef<PrincipalHandle>>(
            "dom::MediaStreamTrack::PrincipalHandleListener::"
            "DoNotifyPrincipalHandleChanged",
            this, &PrincipalHandleListener::DoNotifyPrincipalHandleChanged,
            aNewPrincipalHandle));
  }

 protected:
  // These fields may only be accessed on the main thread
  MediaStreamTrack* mTrack;
};

MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID,
                                   TrackID aInputTrackID,
                                   MediaStreamTrackSource* aSource,
                                   const MediaTrackConstraints& aConstraints)
    : mOwningStream(aStream),
      mTrackID(aTrackID),
      mInputTrackID(aInputTrackID),
      mSource(aSource),
      mPrincipal(aSource->GetPrincipal()),
      mReadyState(MediaStreamTrackState::Live),
      mEnabled(true),
      mMuted(false),
      mConstraints(aConstraints) {
  GetSource().RegisterSink(this);

  if (GetOwnedStream()) {
    mPrincipalHandleListener = new PrincipalHandleListener(this);
    AddListener(mPrincipalHandleListener);
  }

  nsresult rv;
  nsCOMPtr<nsIUUIDGenerator> uuidgen =
      do_GetService("@mozilla.org/uuid-generator;1", &rv);

  nsID uuid;
  memset(&uuid, 0, sizeof(uuid));
  if (uuidgen) {
    uuidgen->GenerateUUIDInPlace(&uuid);
  }

  char chars[NSID_LENGTH];
  uuid.ToProvidedString(chars);
  mID = NS_ConvertASCIItoUTF16(chars);
}

MediaStreamTrack::~MediaStreamTrack() { Destroy(); }

void MediaStreamTrack::Destroy() {
  if (mSource) {
    mSource->UnregisterSink(this);
  }
  if (mPrincipalHandleListener) {
    if (GetOwnedStream()) {
      RemoveListener(mPrincipalHandleListener);
    }
    mPrincipalHandleListener->Forget();
    mPrincipalHandleListener = nullptr;
  }
  // Remove all listeners -- avoid iterating over the list we're removing from
  const nsTArray<RefPtr<MediaStreamTrackListener>> trackListeners(
      mTrackListeners);
  for (auto listener : trackListeners) {
    RemoveListener(listener);
  }
  // Do the same as above for direct listeners
  const nsTArray<RefPtr<DirectMediaStreamTrackListener>> directTrackListeners(
      mDirectTrackListeners);
  for (auto listener : directTrackListeners) {
    RemoveDirectListener(listener);
  }
}

NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrack)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaStreamTrack,
                                                DOMEventTargetHelper)
  tmp->Destroy();
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwningStream)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSource)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalTrack)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPrincipal)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaStreamTrack,
                                                  DOMEventTargetHelper)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwningStream)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSource)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalTrack)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPrincipal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_ADDREF_INHERITED(MediaStreamTrack, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(MediaStreamTrack, DOMEventTargetHelper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrack)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)

nsPIDOMWindowInner* MediaStreamTrack::GetParentObject() const {
  MOZ_RELEASE_ASSERT(mOwningStream);
  return mOwningStream->GetParentObject();
}

void MediaStreamTrack::GetId(nsAString& aID) const { aID = mID; }

void MediaStreamTrack::SetEnabled(bool aEnabled) {
  LOG(LogLevel::Info,
      ("MediaStreamTrack %p %s", this, aEnabled ? "Enabled" : "Disabled"));

  if (mEnabled == aEnabled) {
    return;
  }

  mEnabled = aEnabled;
  GetOwnedStream()->SetTrackEnabled(
      mTrackID,
      mEnabled ? DisabledTrackMode::ENABLED : DisabledTrackMode::SILENCE_BLACK);
  GetSource().SinkEnabledStateChanged();
}

void MediaStreamTrack::Stop() {
  LOG(LogLevel::Info, ("MediaStreamTrack %p Stop()", this));

  if (Ended()) {
    LOG(LogLevel::Warning, ("MediaStreamTrack %p Already ended", this));
    return;
  }

  if (!mSource) {
    MOZ_ASSERT(false);
    return;
  }

  mSource->UnregisterSink(this);

  MOZ_ASSERT(mOwningStream,
             "Every MediaStreamTrack needs an owning DOMMediaStream");
  DOMMediaStream::TrackPort* port = mOwningStream->FindOwnedTrackPort(*this);
  MOZ_ASSERT(port,
             "A MediaStreamTrack must exist in its owning DOMMediaStream");
  RefPtr<Pledge<bool>> p =
      port->BlockSourceTrackId(mInputTrackID, BlockingMode::CREATION);
  Unused << p;

  mReadyState = MediaStreamTrackState::Ended;

  NotifyEnded();
}

void MediaStreamTrack::GetConstraints(dom::MediaTrackConstraints& aResult) {
  aResult = mConstraints;
}

void MediaStreamTrack::GetSettings(dom::MediaTrackSettings& aResult,
                                   CallerType aCallerType) {
  GetSource().GetSettings(aResult);

  // Spoof values when privacy.resistFingerprinting is true.
  if (!nsContentUtils::ResistFingerprinting(aCallerType)) {
    return;
  }
  if (aResult.mFacingMode.WasPassed()) {
    aResult.mFacingMode.Value().Assign(NS_ConvertASCIItoUTF16(
        VideoFacingModeEnumValues::strings[uint8_t(VideoFacingModeEnum::User)]
            .value));
  }
}

already_AddRefed<Promise> MediaStreamTrack::ApplyConstraints(
    const MediaTrackConstraints& aConstraints, CallerType aCallerType,
    ErrorResult& aRv) {
  if (MOZ_LOG_TEST(gMediaStreamTrackLog, LogLevel::Info)) {
    nsString str;
    aConstraints.ToJSON(str);

    LOG(LogLevel::Info, ("MediaStreamTrack %p ApplyConstraints() with "
                         "constraints %s",
                         this, NS_ConvertUTF16toUTF8(str).get()));
  }

  typedef media::Pledge<bool, MediaStreamError*> PledgeVoid;

  nsPIDOMWindowInner* window = mOwningStream->GetParentObject();
  nsIGlobalObject* go = window ? window->AsGlobal() : nullptr;

  RefPtr<Promise> promise = Promise::Create(go, aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  // Forward constraints to the source.
  //
  // After GetSource().ApplyConstraints succeeds (after it's been to
  // media-thread and back), and no sooner, do we set mConstraints to the newly
  // applied values.

  // Keep a reference to this, to make sure it's still here when we get back.
  RefPtr<MediaStreamTrack> that = this;
  RefPtr<PledgeVoid> p =
      GetSource().ApplyConstraints(window, aConstraints, aCallerType);
  p->Then(
      [this, that, promise, aConstraints](bool& aDummy) mutable {
        mConstraints = aConstraints;
        promise->MaybeResolve(false);
      },
      [promise](MediaStreamError*& reason) mutable {
        promise->MaybeReject(reason);
      });
  return promise.forget();
}

MediaStreamGraph* MediaStreamTrack::Graph() {
  return GetOwnedStream()->Graph();
}

MediaStreamGraphImpl* MediaStreamTrack::GraphImpl() {
  return GetOwnedStream()->GraphImpl();
}

void MediaStreamTrack::SetPrincipal(nsIPrincipal* aPrincipal) {
  if (aPrincipal == mPrincipal) {
    return;
  }
  mPrincipal = aPrincipal;

  LOG(LogLevel::Info, ("MediaStreamTrack %p principal changed to %p. Now: "
                       "null=%d, codebase=%d, expanded=%d, system=%d",
                       this, mPrincipal.get(), mPrincipal->GetIsNullPrincipal(),
                       mPrincipal->GetIsCodebasePrincipal(),
                       mPrincipal->GetIsExpandedPrincipal(),
                       mPrincipal->GetIsSystemPrincipal()));
  for (PrincipalChangeObserver<MediaStreamTrack>* observer :
       mPrincipalChangeObservers) {
    observer->PrincipalChanged(this);
  }
}

void MediaStreamTrack::PrincipalChanged() {
  mPendingPrincipal = GetSource().GetPrincipal();
  nsCOMPtr<nsIPrincipal> newPrincipal = mPrincipal;
  LOG(LogLevel::Info, ("MediaStreamTrack %p Principal changed on main thread "
                       "to %p (pending). Combining with existing principal %p.",
                       this, mPendingPrincipal.get(), mPrincipal.get()));
  if (nsContentUtils::CombineResourcePrincipals(&newPrincipal,
                                                mPendingPrincipal)) {
    SetPrincipal(newPrincipal);
  }
}

void MediaStreamTrack::NotifyPrincipalHandleChanged(
    const PrincipalHandle& aNewPrincipalHandle) {
  PrincipalHandle handle(aNewPrincipalHandle);
  LOG(LogLevel::Info, ("MediaStreamTrack %p principalHandle changed on "
                       "MediaStreamGraph thread to %p. Current principal: %p, "
                       "pending: %p",
                       this, GetPrincipalFromHandle(handle), mPrincipal.get(),
                       mPendingPrincipal.get()));
  if (PrincipalHandleMatches(handle, mPendingPrincipal)) {
    SetPrincipal(mPendingPrincipal);
    mPendingPrincipal = nullptr;
  }
}

void MediaStreamTrack::MutedChanged(bool aNewState) {
  MOZ_ASSERT(NS_IsMainThread());

  if (mMuted == aNewState) {
    MOZ_ASSERT_UNREACHABLE("Muted state didn't actually change");
    return;
  }

  LOG(LogLevel::Info,
      ("MediaStreamTrack %p became %s", this, aNewState ? "muted" : "unmuted"));

  mMuted = aNewState;
  nsString eventName =
      aNewState ? NS_LITERAL_STRING("mute") : NS_LITERAL_STRING("unmute");
  DispatchTrustedEvent(eventName);
}

void MediaStreamTrack::NotifyEnded() {
  MOZ_ASSERT(mReadyState == MediaStreamTrackState::Ended);

  auto consumers(mConsumers);
  for (const auto& consumer : consumers) {
    if (consumer) {
      consumer->NotifyEnded(this);
    } else {
      MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
      mConsumers.RemoveElement(consumer);
    }
  }
}

bool MediaStreamTrack::AddPrincipalChangeObserver(
    PrincipalChangeObserver<MediaStreamTrack>* aObserver) {
  return mPrincipalChangeObservers.AppendElement(aObserver) != nullptr;
}

bool MediaStreamTrack::RemovePrincipalChangeObserver(
    PrincipalChangeObserver<MediaStreamTrack>* aObserver) {
  return mPrincipalChangeObservers.RemoveElement(aObserver);
}

void MediaStreamTrack::AddConsumer(MediaStreamTrackConsumer* aConsumer) {
  MOZ_ASSERT(!mConsumers.Contains(aConsumer));
  mConsumers.AppendElement(aConsumer);

  // Remove destroyed consumers for cleanliness
  while (mConsumers.RemoveElement(nullptr)) {
    MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
  }
}

void MediaStreamTrack::RemoveConsumer(MediaStreamTrackConsumer* aConsumer) {
  mConsumers.RemoveElement(aConsumer);

  // Remove destroyed consumers for cleanliness
  while (mConsumers.RemoveElement(nullptr)) {
    MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
  }
}

already_AddRefed<MediaStreamTrack> MediaStreamTrack::Clone() {
  // MediaStreamTracks are currently governed by streams, so we need a dummy
  // DOMMediaStream to own our track clone. The dummy will never see any
  // dynamically created tracks (no input stream) so no need for a SourceGetter.
  RefPtr<DOMMediaStream> newStream =
      new DOMMediaStream(mOwningStream->GetParentObject(), nullptr);

  MediaStreamGraph* graph = Graph();
  newStream->InitOwnedStreamCommon(graph);
  newStream->InitPlaybackStreamCommon(graph);

  return newStream->CloneDOMTrack(*this, mTrackID);
}

void MediaStreamTrack::SetReadyState(MediaStreamTrackState aState) {
  MOZ_ASSERT(!(mReadyState == MediaStreamTrackState::Ended &&
               aState == MediaStreamTrackState::Live),
             "We don't support overriding the ready state from ended to live");

  if (mReadyState == MediaStreamTrackState::Live &&
      aState == MediaStreamTrackState::Ended && mSource) {
    mSource->UnregisterSink(this);
  }

  mReadyState = aState;
}

void MediaStreamTrack::OverrideEnded() {
  MOZ_ASSERT(NS_IsMainThread());

  if (Ended()) {
    return;
  }

  LOG(LogLevel::Info, ("MediaStreamTrack %p ended", this));

  if (!mSource) {
    MOZ_ASSERT(false);
    return;
  }

  mSource->UnregisterSink(this);

  mReadyState = MediaStreamTrackState::Ended;

  NotifyEnded();

  DispatchTrustedEvent(NS_LITERAL_STRING("ended"));
}

DOMMediaStream* MediaStreamTrack::GetInputDOMStream() {
  MediaStreamTrack* originalTrack =
      mOriginalTrack ? mOriginalTrack.get() : this;
  MOZ_RELEASE_ASSERT(originalTrack->mOwningStream);
  return originalTrack->mOwningStream;
}

MediaStream* MediaStreamTrack::GetInputStream() {
  DOMMediaStream* inputDOMStream = GetInputDOMStream();
  MOZ_RELEASE_ASSERT(inputDOMStream->GetInputStream());
  return inputDOMStream->GetInputStream();
}

ProcessedMediaStream* MediaStreamTrack::GetOwnedStream() {
  if (!mOwningStream) {
    return nullptr;
  }

  return mOwningStream->GetOwnedStream();
}

void MediaStreamTrack::AddListener(MediaStreamTrackListener* aListener) {
  LOG(LogLevel::Debug,
      ("MediaStreamTrack %p adding listener %p", this, aListener));
  MOZ_ASSERT(GetOwnedStream());

  GetOwnedStream()->AddTrackListener(aListener, mTrackID);
  mTrackListeners.AppendElement(aListener);
}

void MediaStreamTrack::RemoveListener(MediaStreamTrackListener* aListener) {
  LOG(LogLevel::Debug,
      ("MediaStreamTrack %p removing listener %p", this, aListener));

  if (GetOwnedStream()) {
    GetOwnedStream()->RemoveTrackListener(aListener, mTrackID);
    mTrackListeners.RemoveElement(aListener);
  }
}

void MediaStreamTrack::AddDirectListener(
    DirectMediaStreamTrackListener* aListener) {
  LOG(LogLevel::Debug, ("MediaStreamTrack %p (%s) adding direct listener %p to "
                        "stream %p, track %d",
                        this, AsAudioStreamTrack() ? "audio" : "video",
                        aListener, GetOwnedStream(), mTrackID));
  MOZ_ASSERT(GetOwnedStream());

  GetOwnedStream()->AddDirectTrackListener(aListener, mTrackID);
  mDirectTrackListeners.AppendElement(aListener);
}

void MediaStreamTrack::RemoveDirectListener(
    DirectMediaStreamTrackListener* aListener) {
  LOG(LogLevel::Debug,
      ("MediaStreamTrack %p removing direct listener %p from stream %p", this,
       aListener, GetOwnedStream()));

  if (GetOwnedStream()) {
    GetOwnedStream()->RemoveDirectTrackListener(aListener, mTrackID);
    mDirectTrackListeners.RemoveElement(aListener);
  }
}

already_AddRefed<MediaInputPort> MediaStreamTrack::ForwardTrackContentsTo(
    ProcessedMediaStream* aStream, TrackID aDestinationTrackID) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_RELEASE_ASSERT(aStream);
  RefPtr<MediaInputPort> port = aStream->AllocateInputPort(
      GetOwnedStream(), mTrackID, aDestinationTrackID);
  return port.forget();
}

bool MediaStreamTrack::IsForwardedThrough(MediaInputPort* aPort) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(aPort);
  if (!aPort) {
    return false;
  }
  return aPort->GetSource() == GetOwnedStream() &&
         aPort->PassTrackThrough(mTrackID);
}

}  // namespace dom
}  // namespace mozilla