dom/media/MediaManager.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/media/MediaManager.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,2072 @@
     1.4 +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
     1.5 +/* vim: set ts=2 et sw=2 tw=80: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.8 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "MediaManager.h"
    1.11 +
    1.12 +#include "MediaStreamGraph.h"
    1.13 +#include "GetUserMediaRequest.h"
    1.14 +#include "nsHashPropertyBag.h"
    1.15 +#ifdef MOZ_WIDGET_GONK
    1.16 +#include "nsIAudioManager.h"
    1.17 +#endif
    1.18 +#include "nsIDOMFile.h"
    1.19 +#include "nsIEventTarget.h"
    1.20 +#include "nsIUUIDGenerator.h"
    1.21 +#include "nsIScriptGlobalObject.h"
    1.22 +#include "nsIPermissionManager.h"
    1.23 +#include "nsIPopupWindowManager.h"
    1.24 +#include "nsISupportsArray.h"
    1.25 +#include "nsIDocShell.h"
    1.26 +#include "nsIDocument.h"
    1.27 +#include "nsISupportsPrimitives.h"
    1.28 +#include "nsIInterfaceRequestorUtils.h"
    1.29 +#include "mozilla/Types.h"
    1.30 +#include "mozilla/dom/ContentChild.h"
    1.31 +#include "mozilla/dom/MediaStreamBinding.h"
    1.32 +#include "mozilla/dom/MediaStreamTrackBinding.h"
    1.33 +#include "mozilla/dom/GetUserMediaRequestBinding.h"
    1.34 +#include "MediaTrackConstraints.h"
    1.35 +
    1.36 +#include "Latency.h"
    1.37 +
    1.38 +// For PR_snprintf
    1.39 +#include "prprf.h"
    1.40 +
    1.41 +#include "nsJSUtils.h"
    1.42 +#include "nsDOMFile.h"
    1.43 +#include "nsGlobalWindow.h"
    1.44 +
    1.45 +/* Using WebRTC backend on Desktops (Mac, Windows, Linux), otherwise default */
    1.46 +#include "MediaEngineDefault.h"
    1.47 +#if defined(MOZ_WEBRTC)
    1.48 +#include "MediaEngineWebRTC.h"
    1.49 +#endif
    1.50 +
    1.51 +#ifdef MOZ_B2G
    1.52 +#include "MediaPermissionGonk.h"
    1.53 +#endif
    1.54 +
    1.55 +// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
    1.56 +// GetTickCount() and conflicts with MediaStream::GetCurrentTime.
    1.57 +#ifdef GetCurrentTime
    1.58 +#undef GetCurrentTime
    1.59 +#endif
    1.60 +
    1.61 +namespace mozilla {
    1.62 +
    1.63 +#ifdef LOG
    1.64 +#undef LOG
    1.65 +#endif
    1.66 +
    1.67 +#ifdef PR_LOGGING
    1.68 +PRLogModuleInfo*
    1.69 +GetMediaManagerLog()
    1.70 +{
    1.71 +  static PRLogModuleInfo *sLog;
    1.72 +  if (!sLog)
    1.73 +    sLog = PR_NewLogModule("MediaManager");
    1.74 +  return sLog;
    1.75 +}
    1.76 +#define LOG(msg) PR_LOG(GetMediaManagerLog(), PR_LOG_DEBUG, msg)
    1.77 +#else
    1.78 +#define LOG(msg)
    1.79 +#endif
    1.80 +
    1.81 +using dom::MediaStreamConstraints;         // Outside API (contains JSObject)
    1.82 +using dom::MediaTrackConstraintSet;        // Mandatory or optional constraints
    1.83 +using dom::MediaTrackConstraints;          // Raw mMandatory (as JSObject)
    1.84 +using dom::GetUserMediaRequest;
    1.85 +using dom::Sequence;
    1.86 +using dom::OwningBooleanOrMediaTrackConstraints;
    1.87 +using dom::SupportedAudioConstraints;
    1.88 +using dom::SupportedVideoConstraints;
    1.89 +
    1.90 +ErrorCallbackRunnable::ErrorCallbackRunnable(
    1.91 +  nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess,
    1.92 +  nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError,
    1.93 +  const nsAString& aErrorMsg, uint64_t aWindowID)
    1.94 +  : mErrorMsg(aErrorMsg)
    1.95 +  , mWindowID(aWindowID)
    1.96 +  , mManager(MediaManager::GetInstance())
    1.97 +{
    1.98 +  mSuccess.swap(aSuccess);
    1.99 +  mError.swap(aError);
   1.100 +}
   1.101 +
   1.102 +ErrorCallbackRunnable::~ErrorCallbackRunnable()
   1.103 +{
   1.104 +  MOZ_ASSERT(!mSuccess && !mError);
   1.105 +}
   1.106 +
   1.107 +NS_IMETHODIMP
   1.108 +ErrorCallbackRunnable::Run()
   1.109 +{
   1.110 +  // Only run if the window is still active.
   1.111 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   1.112 +
   1.113 +  nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success = mSuccess.forget();
   1.114 +  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget();
   1.115 +
   1.116 +  if (!(mManager->IsWindowStillActive(mWindowID))) {
   1.117 +    return NS_OK;
   1.118 +  }
   1.119 +  // This is safe since we're on main-thread, and the windowlist can only
   1.120 +  // be invalidated from the main-thread (see OnNavigation)
   1.121 +  error->OnError(mErrorMsg);
   1.122 +  return NS_OK;
   1.123 +}
   1.124 +
   1.125 +/**
   1.126 + * Invoke the "onSuccess" callback in content. The callback will take a
   1.127 + * DOMBlob in the case of {picture:true}, and a MediaStream in the case of
   1.128 + * {audio:true} or {video:true}. There is a constructor available for each
   1.129 + * form. Do this only on the main thread.
   1.130 + */
   1.131 +class SuccessCallbackRunnable : public nsRunnable
   1.132 +{
   1.133 +public:
   1.134 +  SuccessCallbackRunnable(
   1.135 +    nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess,
   1.136 +    nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError,
   1.137 +    nsIDOMFile* aFile, uint64_t aWindowID)
   1.138 +    : mFile(aFile)
   1.139 +    , mWindowID(aWindowID)
   1.140 +    , mManager(MediaManager::GetInstance())
   1.141 +  {
   1.142 +    mSuccess.swap(aSuccess);
   1.143 +    mError.swap(aError);
   1.144 +  }
   1.145 +
   1.146 +  NS_IMETHOD
   1.147 +  Run()
   1.148 +  {
   1.149 +    // Only run if the window is still active.
   1.150 +    NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   1.151 +
   1.152 +    nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success = mSuccess.forget();
   1.153 +    nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget();
   1.154 +
   1.155 +    if (!(mManager->IsWindowStillActive(mWindowID))) {
   1.156 +      return NS_OK;
   1.157 +    }
   1.158 +    // This is safe since we're on main-thread, and the windowlist can only
   1.159 +    // be invalidated from the main-thread (see OnNavigation)
   1.160 +    success->OnSuccess(mFile);
   1.161 +    return NS_OK;
   1.162 +  }
   1.163 +
   1.164 +private:
   1.165 +  nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess;
   1.166 +  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
   1.167 +  nsCOMPtr<nsIDOMFile> mFile;
   1.168 +  uint64_t mWindowID;
   1.169 +  nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
   1.170 +};
   1.171 +
   1.172 +/**
   1.173 + * Invoke the GetUserMediaDevices success callback. Wrapped in a runnable
   1.174 + * so that it may be called on the main thread. The error callback is also
   1.175 + * passed so it can be released correctly.
   1.176 + */
   1.177 +class DeviceSuccessCallbackRunnable: public nsRunnable
   1.178 +{
   1.179 +public:
   1.180 +  DeviceSuccessCallbackRunnable(
   1.181 +    uint64_t aWindowID,
   1.182 +    nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback>& aSuccess,
   1.183 +    nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError,
   1.184 +    nsTArray<nsCOMPtr<nsIMediaDevice> >* aDevices)
   1.185 +    : mDevices(aDevices)
   1.186 +    , mWindowID(aWindowID)
   1.187 +    , mManager(MediaManager::GetInstance())
   1.188 +  {
   1.189 +    mSuccess.swap(aSuccess);
   1.190 +    mError.swap(aError);
   1.191 +  }
   1.192 +
   1.193 +  NS_IMETHOD
   1.194 +  Run()
   1.195 +  {
   1.196 +    NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   1.197 +
   1.198 +    // Only run if window is still on our active list.
   1.199 +    if (!mManager->IsWindowStillActive(mWindowID)) {
   1.200 +      return NS_OK;
   1.201 +    }
   1.202 +
   1.203 +    nsCOMPtr<nsIWritableVariant> devices =
   1.204 +      do_CreateInstance("@mozilla.org/variant;1");
   1.205 +
   1.206 +    int32_t len = mDevices->Length();
   1.207 +    if (len == 0) {
   1.208 +      // XXX
   1.209 +      // We should in the future return an empty array, and dynamically add
   1.210 +      // devices to the dropdowns if things are hotplugged while the
   1.211 +      // requester is up.
   1.212 +      mError->OnError(NS_LITERAL_STRING("NO_DEVICES_FOUND"));
   1.213 +      return NS_OK;
   1.214 +    }
   1.215 +
   1.216 +    nsTArray<nsIMediaDevice*> tmp(len);
   1.217 +    for (int32_t i = 0; i < len; i++) {
   1.218 +      tmp.AppendElement(mDevices->ElementAt(i));
   1.219 +    }
   1.220 +
   1.221 +    devices->SetAsArray(nsIDataType::VTYPE_INTERFACE,
   1.222 +                        &NS_GET_IID(nsIMediaDevice),
   1.223 +                        mDevices->Length(),
   1.224 +                        const_cast<void*>(
   1.225 +                          static_cast<const void*>(tmp.Elements())
   1.226 +                        ));
   1.227 +
   1.228 +    mSuccess->OnSuccess(devices);
   1.229 +    return NS_OK;
   1.230 +  }
   1.231 +
   1.232 +private:
   1.233 +  nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> mSuccess;
   1.234 +  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
   1.235 +  nsAutoPtr<nsTArray<nsCOMPtr<nsIMediaDevice> > > mDevices;
   1.236 +  uint64_t mWindowID;
   1.237 +  nsRefPtr<MediaManager> mManager;
   1.238 +};
   1.239 +
   1.240 +// Handle removing GetUserMediaCallbackMediaStreamListener from main thread
   1.241 +class GetUserMediaListenerRemove: public nsRunnable
   1.242 +{
   1.243 +public:
   1.244 +  GetUserMediaListenerRemove(uint64_t aWindowID,
   1.245 +    GetUserMediaCallbackMediaStreamListener *aListener)
   1.246 +    : mWindowID(aWindowID)
   1.247 +    , mListener(aListener) {}
   1.248 +
   1.249 +  NS_IMETHOD
   1.250 +  Run()
   1.251 +  {
   1.252 +    NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   1.253 +    nsRefPtr<MediaManager> manager(MediaManager::GetInstance());
   1.254 +    manager->RemoveFromWindowList(mWindowID, mListener);
   1.255 +    return NS_OK;
   1.256 +  }
   1.257 +
   1.258 +protected:
   1.259 +  uint64_t mWindowID;
   1.260 +  nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
   1.261 +};
   1.262 +
   1.263 +/**
   1.264 + * nsIMediaDevice implementation.
   1.265 + */
   1.266 +NS_IMPL_ISUPPORTS(MediaDevice, nsIMediaDevice)
   1.267 +
   1.268 +MediaDevice* MediaDevice::Create(MediaEngineVideoSource* source) {
   1.269 +  return new VideoDevice(source);
   1.270 +}
   1.271 +
   1.272 +MediaDevice* MediaDevice::Create(MediaEngineAudioSource* source) {
   1.273 +  return new AudioDevice(source);
   1.274 +}
   1.275 +
   1.276 +MediaDevice::MediaDevice(MediaEngineSource* aSource)
   1.277 +  : mHasFacingMode(false)
   1.278 +  , mSource(aSource) {
   1.279 +  mSource->GetName(mName);
   1.280 +  mSource->GetUUID(mID);
   1.281 +}
   1.282 +
   1.283 +VideoDevice::VideoDevice(MediaEngineVideoSource* aSource)
   1.284 +  : MediaDevice(aSource) {
   1.285 +#ifdef MOZ_B2G_CAMERA
   1.286 +  if (mName.EqualsLiteral("back")) {
   1.287 +    mHasFacingMode = true;
   1.288 +    mFacingMode = dom::VideoFacingModeEnum::Environment;
   1.289 +  } else if (mName.EqualsLiteral("front")) {
   1.290 +    mHasFacingMode = true;
   1.291 +    mFacingMode = dom::VideoFacingModeEnum::User;
   1.292 +  }
   1.293 +#endif // MOZ_B2G_CAMERA
   1.294 +
   1.295 +  // Kludge to test user-facing cameras on OSX.
   1.296 +  if (mName.Find(NS_LITERAL_STRING("Face")) != -1) {
   1.297 +    mHasFacingMode = true;
   1.298 +    mFacingMode = dom::VideoFacingModeEnum::User;
   1.299 +  }
   1.300 +}
   1.301 +
   1.302 +AudioDevice::AudioDevice(MediaEngineAudioSource* aSource)
   1.303 +  : MediaDevice(aSource) {}
   1.304 +
   1.305 +NS_IMETHODIMP
   1.306 +MediaDevice::GetName(nsAString& aName)
   1.307 +{
   1.308 +  aName.Assign(mName);
   1.309 +  return NS_OK;
   1.310 +}
   1.311 +
   1.312 +NS_IMETHODIMP
   1.313 +MediaDevice::GetType(nsAString& aType)
   1.314 +{
   1.315 +  return NS_OK;
   1.316 +}
   1.317 +
   1.318 +NS_IMETHODIMP
   1.319 +VideoDevice::GetType(nsAString& aType)
   1.320 +{
   1.321 +  aType.Assign(NS_LITERAL_STRING("video"));
   1.322 +  return NS_OK;
   1.323 +}
   1.324 +
   1.325 +NS_IMETHODIMP
   1.326 +AudioDevice::GetType(nsAString& aType)
   1.327 +{
   1.328 +  aType.Assign(NS_LITERAL_STRING("audio"));
   1.329 +  return NS_OK;
   1.330 +}
   1.331 +
   1.332 +NS_IMETHODIMP
   1.333 +MediaDevice::GetId(nsAString& aID)
   1.334 +{
   1.335 +  aID.Assign(mID);
   1.336 +  return NS_OK;
   1.337 +}
   1.338 +
   1.339 +NS_IMETHODIMP
   1.340 +MediaDevice::GetFacingMode(nsAString& aFacingMode)
   1.341 +{
   1.342 +  if (mHasFacingMode) {
   1.343 +    aFacingMode.Assign(NS_ConvertUTF8toUTF16(
   1.344 +        dom::VideoFacingModeEnumValues::strings[uint32_t(mFacingMode)].value));
   1.345 +  } else {
   1.346 +    aFacingMode.Truncate(0);
   1.347 +  }
   1.348 +  return NS_OK;
   1.349 +}
   1.350 +
   1.351 +MediaEngineVideoSource*
   1.352 +VideoDevice::GetSource()
   1.353 +{
   1.354 +  return static_cast<MediaEngineVideoSource*>(&*mSource);
   1.355 +}
   1.356 +
   1.357 +MediaEngineAudioSource*
   1.358 +AudioDevice::GetSource()
   1.359 +{
   1.360 +  return static_cast<MediaEngineAudioSource*>(&*mSource);
   1.361 +}
   1.362 +
   1.363 +/**
   1.364 + * A subclass that we only use to stash internal pointers to MediaStreamGraph objects
   1.365 + * that need to be cleaned up.
   1.366 + */
   1.367 +class nsDOMUserMediaStream : public DOMLocalMediaStream
   1.368 +{
   1.369 +public:
   1.370 +  static already_AddRefed<nsDOMUserMediaStream>
   1.371 +  CreateTrackUnionStream(nsIDOMWindow* aWindow,
   1.372 +                         MediaEngineSource *aAudioSource,
   1.373 +                         MediaEngineSource *aVideoSource)
   1.374 +  {
   1.375 +    DOMMediaStream::TrackTypeHints hints =
   1.376 +      (aAudioSource ? DOMMediaStream::HINT_CONTENTS_AUDIO : 0) |
   1.377 +      (aVideoSource ? DOMMediaStream::HINT_CONTENTS_VIDEO : 0);
   1.378 +
   1.379 +    nsRefPtr<nsDOMUserMediaStream> stream = new nsDOMUserMediaStream(aAudioSource);
   1.380 +    stream->InitTrackUnionStream(aWindow, hints);
   1.381 +    return stream.forget();
   1.382 +  }
   1.383 +
   1.384 +  nsDOMUserMediaStream(MediaEngineSource *aAudioSource) :
   1.385 +    mAudioSource(aAudioSource),
   1.386 +    mEchoOn(true),
   1.387 +    mAgcOn(false),
   1.388 +    mNoiseOn(true),
   1.389 +#ifdef MOZ_WEBRTC
   1.390 +    mEcho(webrtc::kEcDefault),
   1.391 +    mAgc(webrtc::kAgcDefault),
   1.392 +    mNoise(webrtc::kNsDefault),
   1.393 +#else
   1.394 +    mEcho(0),
   1.395 +    mAgc(0),
   1.396 +    mNoise(0),
   1.397 +#endif
   1.398 +    mPlayoutDelay(20)
   1.399 +  {}
   1.400 +
   1.401 +  virtual ~nsDOMUserMediaStream()
   1.402 +  {
   1.403 +    Stop();
   1.404 +
   1.405 +    if (mPort) {
   1.406 +      mPort->Destroy();
   1.407 +    }
   1.408 +    if (mSourceStream) {
   1.409 +      mSourceStream->Destroy();
   1.410 +    }
   1.411 +  }
   1.412 +
   1.413 +  virtual void Stop()
   1.414 +  {
   1.415 +    if (mSourceStream) {
   1.416 +      mSourceStream->EndAllTrackAndFinish();
   1.417 +    }
   1.418 +  }
   1.419 +
   1.420 +  // Allow getUserMedia to pass input data directly to PeerConnection/MediaPipeline
   1.421 +  virtual bool AddDirectListener(MediaStreamDirectListener *aListener) MOZ_OVERRIDE
   1.422 +  {
   1.423 +    if (mSourceStream) {
   1.424 +      mSourceStream->AddDirectListener(aListener);
   1.425 +      return true; // application should ignore NotifyQueuedTrackData
   1.426 +    }
   1.427 +    return false;
   1.428 +  }
   1.429 +
   1.430 +  virtual void
   1.431 +  AudioConfig(bool aEchoOn, uint32_t aEcho,
   1.432 +              bool aAgcOn, uint32_t aAgc,
   1.433 +              bool aNoiseOn, uint32_t aNoise,
   1.434 +              int32_t aPlayoutDelay)
   1.435 +  {
   1.436 +    mEchoOn = aEchoOn;
   1.437 +    mEcho = aEcho;
   1.438 +    mAgcOn = aAgcOn;
   1.439 +    mAgc = aAgc;
   1.440 +    mNoiseOn = aNoiseOn;
   1.441 +    mNoise = aNoise;
   1.442 +    mPlayoutDelay = aPlayoutDelay;
   1.443 +  }
   1.444 +
   1.445 +  virtual void RemoveDirectListener(MediaStreamDirectListener *aListener) MOZ_OVERRIDE
   1.446 +  {
   1.447 +    if (mSourceStream) {
   1.448 +      mSourceStream->RemoveDirectListener(aListener);
   1.449 +    }
   1.450 +  }
   1.451 +
   1.452 +  // let us intervene for direct listeners when someone does track.enabled = false
   1.453 +  virtual void SetTrackEnabled(TrackID aID, bool aEnabled) MOZ_OVERRIDE
   1.454 +  {
   1.455 +    // We encapsulate the SourceMediaStream and TrackUnion into one entity, so
   1.456 +    // we can handle the disabling at the SourceMediaStream
   1.457 +
   1.458 +    // We need to find the input track ID for output ID aID, so we let the TrackUnion
   1.459 +    // forward the request to the source and translate the ID
   1.460 +    GetStream()->AsProcessedStream()->ForwardTrackEnabled(aID, aEnabled);
   1.461 +  }
   1.462 +
   1.463 +  // The actual MediaStream is a TrackUnionStream. But these resources need to be
   1.464 +  // explicitly destroyed too.
   1.465 +  nsRefPtr<SourceMediaStream> mSourceStream;
   1.466 +  nsRefPtr<MediaInputPort> mPort;
   1.467 +  nsRefPtr<MediaEngineSource> mAudioSource; // so we can turn on AEC
   1.468 +  bool mEchoOn;
   1.469 +  bool mAgcOn;
   1.470 +  bool mNoiseOn;
   1.471 +  uint32_t mEcho;
   1.472 +  uint32_t mAgc;
   1.473 +  uint32_t mNoise;
   1.474 +  uint32_t mPlayoutDelay;
   1.475 +};
   1.476 +
   1.477 +/**
   1.478 + * Creates a MediaStream, attaches a listener and fires off a success callback
   1.479 + * to the DOM with the stream. We also pass in the error callback so it can
   1.480 + * be released correctly.
   1.481 + *
   1.482 + * All of this must be done on the main thread!
   1.483 + *
   1.484 + * Note that the various GetUserMedia Runnable classes currently allow for
   1.485 + * two streams.  If we ever need to support getting more than two streams
   1.486 + * at once, we could convert everything to nsTArray<nsRefPtr<blah> >'s,
   1.487 + * though that would complicate the constructors some.  Currently the
   1.488 + * GetUserMedia spec does not allow for more than 2 streams to be obtained in
   1.489 + * one call, to simplify handling of constraints.
   1.490 + */
   1.491 +class GetUserMediaStreamRunnable : public nsRunnable
   1.492 +{
   1.493 +public:
   1.494 +  GetUserMediaStreamRunnable(
   1.495 +    nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess,
   1.496 +    nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError,
   1.497 +    uint64_t aWindowID,
   1.498 +    GetUserMediaCallbackMediaStreamListener* aListener,
   1.499 +    MediaEngineSource* aAudioSource,
   1.500 +    MediaEngineSource* aVideoSource)
   1.501 +    : mAudioSource(aAudioSource)
   1.502 +    , mVideoSource(aVideoSource)
   1.503 +    , mWindowID(aWindowID)
   1.504 +    , mListener(aListener)
   1.505 +    , mManager(MediaManager::GetInstance())
   1.506 +  {
   1.507 +    mSuccess.swap(aSuccess);
   1.508 +    mError.swap(aError);
   1.509 +  }
   1.510 +
   1.511 +  ~GetUserMediaStreamRunnable() {}
   1.512 +
   1.513 +  class TracksAvailableCallback : public DOMMediaStream::OnTracksAvailableCallback
   1.514 +  {
   1.515 +  public:
   1.516 +    TracksAvailableCallback(MediaManager* aManager,
   1.517 +                            nsIDOMGetUserMediaSuccessCallback* aSuccess,
   1.518 +                            uint64_t aWindowID,
   1.519 +                            DOMMediaStream* aStream)
   1.520 +      : mWindowID(aWindowID), mSuccess(aSuccess), mManager(aManager),
   1.521 +        mStream(aStream) {}
   1.522 +    virtual void NotifyTracksAvailable(DOMMediaStream* aStream) MOZ_OVERRIDE
   1.523 +    {
   1.524 +      // We're in the main thread, so no worries here.
   1.525 +      if (!(mManager->IsWindowStillActive(mWindowID))) {
   1.526 +        return;
   1.527 +      }
   1.528 +
   1.529 +      // Start currentTime from the point where this stream was successfully
   1.530 +      // returned.
   1.531 +      aStream->SetLogicalStreamStartTime(aStream->GetStream()->GetCurrentTime());
   1.532 +
   1.533 +      // This is safe since we're on main-thread, and the windowlist can only
   1.534 +      // be invalidated from the main-thread (see OnNavigation)
   1.535 +      LOG(("Returning success for getUserMedia()"));
   1.536 +      mSuccess->OnSuccess(aStream);
   1.537 +    }
   1.538 +    uint64_t mWindowID;
   1.539 +    nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess;
   1.540 +    nsRefPtr<MediaManager> mManager;
   1.541 +    // Keep the DOMMediaStream alive until the NotifyTracksAvailable callback
   1.542 +    // has fired, otherwise we might immediately destroy the DOMMediaStream and
   1.543 +    // shut down the underlying MediaStream prematurely.
   1.544 +    // This creates a cycle which is broken when NotifyTracksAvailable
   1.545 +    // is fired (which will happen unless the browser shuts down,
   1.546 +    // since we only add this callback when we've successfully appended
   1.547 +    // the desired tracks in the MediaStreamGraph) or when
   1.548 +    // DOMMediaStream::NotifyMediaStreamGraphShutdown is called.
   1.549 +    nsRefPtr<DOMMediaStream> mStream;
   1.550 +  };
   1.551 +
   1.552 +  NS_IMETHOD
   1.553 +  Run()
   1.554 +  {
   1.555 +#ifdef MOZ_WEBRTC
   1.556 +    int32_t aec = (int32_t) webrtc::kEcUnchanged;
   1.557 +    int32_t agc = (int32_t) webrtc::kAgcUnchanged;
   1.558 +    int32_t noise = (int32_t) webrtc::kNsUnchanged;
   1.559 +#else
   1.560 +    int32_t aec = 0, agc = 0, noise = 0;
   1.561 +#endif
   1.562 +    bool aec_on = false, agc_on = false, noise_on = false;
   1.563 +    int32_t playout_delay = 0;
   1.564 +
   1.565 +    NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   1.566 +    nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
   1.567 +      (nsGlobalWindow::GetInnerWindowWithId(mWindowID));
   1.568 +
   1.569 +    // We're on main-thread, and the windowlist can only
   1.570 +    // be invalidated from the main-thread (see OnNavigation)
   1.571 +    StreamListeners* listeners = mManager->GetWindowListeners(mWindowID);
   1.572 +    if (!listeners || !window || !window->GetExtantDoc()) {
   1.573 +      // This window is no longer live.  mListener has already been removed
   1.574 +      return NS_OK;
   1.575 +    }
   1.576 +
   1.577 +#ifdef MOZ_WEBRTC
   1.578 +    // Right now these configs are only of use if webrtc is available
   1.579 +    nsresult rv;
   1.580 +    nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
   1.581 +    if (NS_SUCCEEDED(rv)) {
   1.582 +      nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
   1.583 +
   1.584 +      if (branch) {
   1.585 +        branch->GetBoolPref("media.getusermedia.aec_enabled", &aec_on);
   1.586 +        branch->GetIntPref("media.getusermedia.aec", &aec);
   1.587 +        branch->GetBoolPref("media.getusermedia.agc_enabled", &agc_on);
   1.588 +        branch->GetIntPref("media.getusermedia.agc", &agc);
   1.589 +        branch->GetBoolPref("media.getusermedia.noise_enabled", &noise_on);
   1.590 +        branch->GetIntPref("media.getusermedia.noise", &noise);
   1.591 +        branch->GetIntPref("media.getusermedia.playout_delay", &playout_delay);
   1.592 +      }
   1.593 +    }
   1.594 +#endif
   1.595 +    // Create a media stream.
   1.596 +    nsRefPtr<nsDOMUserMediaStream> trackunion =
   1.597 +      nsDOMUserMediaStream::CreateTrackUnionStream(window, mAudioSource,
   1.598 +                                                   mVideoSource);
   1.599 +    if (!trackunion) {
   1.600 +      nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget();
   1.601 +      LOG(("Returning error for getUserMedia() - no stream"));
   1.602 +      error->OnError(NS_LITERAL_STRING("NO_STREAM"));
   1.603 +      return NS_OK;
   1.604 +    }
   1.605 +    trackunion->AudioConfig(aec_on, (uint32_t) aec,
   1.606 +                            agc_on, (uint32_t) agc,
   1.607 +                            noise_on, (uint32_t) noise,
   1.608 +                            playout_delay);
   1.609 +
   1.610 +
   1.611 +    MediaStreamGraph* gm = MediaStreamGraph::GetInstance();
   1.612 +    nsRefPtr<SourceMediaStream> stream = gm->CreateSourceStream(nullptr);
   1.613 +
   1.614 +    // connect the source stream to the track union stream to avoid us blocking
   1.615 +    trackunion->GetStream()->AsProcessedStream()->SetAutofinish(true);
   1.616 +    nsRefPtr<MediaInputPort> port = trackunion->GetStream()->AsProcessedStream()->
   1.617 +      AllocateInputPort(stream, MediaInputPort::FLAG_BLOCK_OUTPUT);
   1.618 +    trackunion->mSourceStream = stream;
   1.619 +    trackunion->mPort = port.forget();
   1.620 +    // Log the relationship between SourceMediaStream and TrackUnion stream
   1.621 +    // Make sure logger starts before capture
   1.622 +    AsyncLatencyLogger::Get(true);
   1.623 +    LogLatency(AsyncLatencyLogger::MediaStreamCreate,
   1.624 +               reinterpret_cast<uint64_t>(stream.get()),
   1.625 +               reinterpret_cast<int64_t>(trackunion->GetStream()));
   1.626 +
   1.627 +    trackunion->CombineWithPrincipal(window->GetExtantDoc()->NodePrincipal());
   1.628 +
   1.629 +    // The listener was added at the begining in an inactive state.
   1.630 +    // Activate our listener. We'll call Start() on the source when get a callback
   1.631 +    // that the MediaStream has started consuming. The listener is freed
   1.632 +    // when the page is invalidated (on navigation or close).
   1.633 +    mListener->Activate(stream.forget(), mAudioSource, mVideoSource);
   1.634 +
   1.635 +    // Note: includes JS callbacks; must be released on MainThread
   1.636 +    TracksAvailableCallback* tracksAvailableCallback =
   1.637 +      new TracksAvailableCallback(mManager, mSuccess, mWindowID, trackunion);
   1.638 +
   1.639 +    mListener->AudioConfig(aec_on, (uint32_t) aec,
   1.640 +                           agc_on, (uint32_t) agc,
   1.641 +                           noise_on, (uint32_t) noise,
   1.642 +                           playout_delay);
   1.643 +
   1.644 +    // Dispatch to the media thread to ask it to start the sources,
   1.645 +    // because that can take a while.
   1.646 +    // Pass ownership of trackunion to the MediaOperationRunnable
   1.647 +    // to ensure it's kept alive until the MediaOperationRunnable runs (at least).
   1.648 +    nsIThread *mediaThread = MediaManager::GetThread();
   1.649 +    nsRefPtr<MediaOperationRunnable> runnable(
   1.650 +      new MediaOperationRunnable(MEDIA_START, mListener, trackunion,
   1.651 +                                 tracksAvailableCallback,
   1.652 +                                 mAudioSource, mVideoSource, false, mWindowID,
   1.653 +                                 mError.forget()));
   1.654 +    mediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
   1.655 +
   1.656 +    // We won't need mError now.
   1.657 +    mError = nullptr;
   1.658 +    return NS_OK;
   1.659 +  }
   1.660 +
   1.661 +private:
   1.662 +  nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess;
   1.663 +  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
   1.664 +  nsRefPtr<MediaEngineSource> mAudioSource;
   1.665 +  nsRefPtr<MediaEngineSource> mVideoSource;
   1.666 +  uint64_t mWindowID;
   1.667 +  nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
   1.668 +  nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
   1.669 +};
   1.670 +
   1.671 +static bool
   1.672 +IsOn(const OwningBooleanOrMediaTrackConstraints &aUnion) {
   1.673 +  return !aUnion.IsBoolean() || aUnion.GetAsBoolean();
   1.674 +}
   1.675 +
   1.676 +static const MediaTrackConstraints&
   1.677 +GetInvariant(const OwningBooleanOrMediaTrackConstraints &aUnion) {
   1.678 +  static const MediaTrackConstraints empty;
   1.679 +  return aUnion.IsMediaTrackConstraints() ?
   1.680 +      aUnion.GetAsMediaTrackConstraints() : empty;
   1.681 +}
   1.682 +
   1.683 +/**
   1.684 + * Helper functions that implement the constraints algorithm from
   1.685 + * http://dev.w3.org/2011/webrtc/editor/getusermedia.html#methods-5
   1.686 + */
   1.687 +
   1.688 +// Reminder: add handling for new constraints both here and in GetSources below!
   1.689 +
   1.690 +static bool SatisfyConstraintSet(const MediaEngineVideoSource *,
   1.691 +                                 const MediaTrackConstraintSet &aConstraints,
   1.692 +                                 nsIMediaDevice &aCandidate)
   1.693 +{
   1.694 +  if (aConstraints.mFacingMode.WasPassed()) {
   1.695 +    nsString s;
   1.696 +    aCandidate.GetFacingMode(s);
   1.697 +    if (!s.EqualsASCII(dom::VideoFacingModeEnumValues::strings[
   1.698 +        uint32_t(aConstraints.mFacingMode.Value())].value)) {
   1.699 +      return false;
   1.700 +    }
   1.701 +  }
   1.702 +  // TODO: Add more video-specific constraints
   1.703 +  return true;
   1.704 +}
   1.705 +
   1.706 +static bool SatisfyConstraintSet(const MediaEngineAudioSource *,
   1.707 +                                 const MediaTrackConstraintSet &aConstraints,
   1.708 +                                 nsIMediaDevice &aCandidate)
   1.709 +{
   1.710 +  // TODO: Add audio-specific constraints
   1.711 +  return true;
   1.712 +}
   1.713 +
   1.714 +typedef nsTArray<nsCOMPtr<nsIMediaDevice> > SourceSet;
   1.715 +
   1.716 +// Source getter that constrains list returned
   1.717 +
   1.718 +template<class SourceType, class ConstraintsType>
   1.719 +static SourceSet *
   1.720 +  GetSources(MediaEngine *engine,
   1.721 +             ConstraintsType &aConstraints,
   1.722 +             void (MediaEngine::* aEnumerate)(nsTArray<nsRefPtr<SourceType> >*),
   1.723 +             char* media_device_name = nullptr)
   1.724 +{
   1.725 +  ScopedDeletePtr<SourceSet> result(new SourceSet);
   1.726 +
   1.727 +  const SourceType * const type = nullptr;
   1.728 +  nsString deviceName;
   1.729 +  // First collect sources
   1.730 +  SourceSet candidateSet;
   1.731 +  {
   1.732 +    nsTArray<nsRefPtr<SourceType> > sources;
   1.733 +    (engine->*aEnumerate)(&sources);
   1.734 +    /**
   1.735 +      * We're allowing multiple tabs to access the same camera for parity
   1.736 +      * with Chrome.  See bug 811757 for some of the issues surrounding
   1.737 +      * this decision.  To disallow, we'd filter by IsAvailable() as we used
   1.738 +      * to.
   1.739 +      */
   1.740 +    for (uint32_t len = sources.Length(), i = 0; i < len; i++) {
   1.741 +#ifdef DEBUG
   1.742 +      sources[i]->GetName(deviceName);
   1.743 +      if (media_device_name && strlen(media_device_name) > 0)  {
   1.744 +        if (deviceName.EqualsASCII(media_device_name)) {
   1.745 +          candidateSet.AppendElement(MediaDevice::Create(sources[i]));
   1.746 +          break;
   1.747 +        }
   1.748 +      } else {
   1.749 +#endif
   1.750 +        candidateSet.AppendElement(MediaDevice::Create(sources[i]));
   1.751 +#ifdef DEBUG
   1.752 +      }
   1.753 +#endif
   1.754 +    }
   1.755 +  }
   1.756 +
   1.757 +  // Apply constraints to the list of sources.
   1.758 +
   1.759 +  auto& c = aConstraints;
   1.760 +  if (c.mUnsupportedRequirement) {
   1.761 +    // Check upfront the names of required constraints that are unsupported for
   1.762 +    // this media-type. The spec requires these to fail, so getting them out of
   1.763 +    // the way early provides a necessary invariant for the remaining algorithm
   1.764 +    // which maximizes code-reuse by ignoring constraints of the other type
   1.765 +    // (specifically, SatisfyConstraintSet is reused for the advanced algorithm
   1.766 +    // where the spec requires it to ignore constraints of the other type)
   1.767 +    return result.forget();
   1.768 +  }
   1.769 +
   1.770 +  // Now on to the actual algorithm: First apply required constraints.
   1.771 +
   1.772 +  for (uint32_t i = 0; i < candidateSet.Length();) {
   1.773 +    // Overloading instead of template specialization keeps things local
   1.774 +    if (!SatisfyConstraintSet(type, c.mRequired, *candidateSet[i])) {
   1.775 +      candidateSet.RemoveElementAt(i);
   1.776 +    } else {
   1.777 +      ++i;
   1.778 +    }
   1.779 +  }
   1.780 +
   1.781 +  // TODO(jib): Proper non-ordered handling of nonrequired constraints (907352)
   1.782 +  //
   1.783 +  // For now, put nonrequired constraints at tail of Advanced list.
   1.784 +  // This isn't entirely accurate, as order will matter, but few will notice
   1.785 +  // the difference until we get camera selection and a few more constraints.
   1.786 +  if (c.mNonrequired.Length()) {
   1.787 +    if (!c.mAdvanced.WasPassed()) {
   1.788 +      c.mAdvanced.Construct();
   1.789 +    }
   1.790 +    c.mAdvanced.Value().MoveElementsFrom(c.mNonrequired);
   1.791 +  }
   1.792 +
   1.793 +  // Then apply advanced (formerly known as optional) constraints.
   1.794 +  //
   1.795 +  // These are only effective when there are multiple sources to pick from.
   1.796 +  // Spec as-of-this-writing says to run algorithm on "all possible tracks
   1.797 +  // of media type T that the browser COULD RETURN" (emphasis added).
   1.798 +  //
   1.799 +  // We think users ultimately control which devices we could return, so after
   1.800 +  // determining the webpage's preferred list, we add the remaining choices
   1.801 +  // to the tail, reasoning that they would all have passed individually,
   1.802 +  // i.e. if the user had any one of them as their sole device (enabled).
   1.803 +  //
   1.804 +  // This avoids users having to unplug/disable devices should a webpage pick
   1.805 +  // the wrong one (UX-fail). Webpage-preferred devices will be listed first.
   1.806 +
   1.807 +  SourceSet tailSet;
   1.808 +
   1.809 +  if (c.mAdvanced.WasPassed()) {
   1.810 +    auto &array = c.mAdvanced.Value();
   1.811 +
   1.812 +    for (int i = 0; i < int(array.Length()); i++) {
   1.813 +      SourceSet rejects;
   1.814 +      for (uint32_t j = 0; j < candidateSet.Length();) {
   1.815 +        if (!SatisfyConstraintSet(type, array[i], *candidateSet[j])) {
   1.816 +          rejects.AppendElement(candidateSet[j]);
   1.817 +          candidateSet.RemoveElementAt(j);
   1.818 +        } else {
   1.819 +          ++j;
   1.820 +        }
   1.821 +      }
   1.822 +      (candidateSet.Length()? tailSet : candidateSet).MoveElementsFrom(rejects);
   1.823 +    }
   1.824 +  }
   1.825 +
   1.826 +  // TODO: Proper non-ordered handling of nonrequired constraints (Bug 907352)
   1.827 +
   1.828 +  result->MoveElementsFrom(candidateSet);
   1.829 +  result->MoveElementsFrom(tailSet);
   1.830 +  return result.forget();
   1.831 +}
   1.832 +
   1.833 +/**
   1.834 + * Runs on a seperate thread and is responsible for enumerating devices.
   1.835 + * Depending on whether a picture or stream was asked for, either
   1.836 + * ProcessGetUserMedia or ProcessGetUserMediaSnapshot is called, and the results
   1.837 + * are sent back to the DOM.
   1.838 + *
   1.839 + * Do not run this on the main thread. The success and error callbacks *MUST*
   1.840 + * be dispatched on the main thread!
   1.841 + */
   1.842 +class GetUserMediaRunnable : public nsRunnable
   1.843 +{
   1.844 +public:
   1.845 +  GetUserMediaRunnable(
   1.846 +    const MediaStreamConstraints& aConstraints,
   1.847 +    already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
   1.848 +    already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
   1.849 +    uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener,
   1.850 +    MediaEnginePrefs &aPrefs)
   1.851 +    : mConstraints(aConstraints)
   1.852 +    , mSuccess(aSuccess)
   1.853 +    , mError(aError)
   1.854 +    , mWindowID(aWindowID)
   1.855 +    , mListener(aListener)
   1.856 +    , mPrefs(aPrefs)
   1.857 +    , mDeviceChosen(false)
   1.858 +    , mBackend(nullptr)
   1.859 +    , mManager(MediaManager::GetInstance())
   1.860 +  {}
   1.861 +
   1.862 +  /**
   1.863 +   * The caller can also choose to provide their own backend instead of
   1.864 +   * using the one provided by MediaManager::GetBackend.
   1.865 +   */
   1.866 +  GetUserMediaRunnable(
   1.867 +    const MediaStreamConstraints& aConstraints,
   1.868 +    already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
   1.869 +    already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
   1.870 +    uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener,
   1.871 +    MediaEnginePrefs &aPrefs,
   1.872 +    MediaEngine* aBackend)
   1.873 +    : mConstraints(aConstraints)
   1.874 +    , mSuccess(aSuccess)
   1.875 +    , mError(aError)
   1.876 +    , mWindowID(aWindowID)
   1.877 +    , mListener(aListener)
   1.878 +    , mPrefs(aPrefs)
   1.879 +    , mDeviceChosen(false)
   1.880 +    , mBackend(aBackend)
   1.881 +    , mManager(MediaManager::GetInstance())
   1.882 +  {}
   1.883 +
   1.884 +  ~GetUserMediaRunnable() {
   1.885 +  }
   1.886 +
   1.887 +  void
   1.888 +  Fail(const nsAString& aMessage) {
   1.889 +    nsRefPtr<ErrorCallbackRunnable> runnable =
   1.890 +      new ErrorCallbackRunnable(mSuccess, mError, aMessage, mWindowID);
   1.891 +    // These should be empty now
   1.892 +    MOZ_ASSERT(!mSuccess);
   1.893 +    MOZ_ASSERT(!mError);
   1.894 +
   1.895 +    NS_DispatchToMainThread(runnable);
   1.896 +  }
   1.897 +
   1.898 +  NS_IMETHOD
   1.899 +  Run()
   1.900 +  {
   1.901 +    NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
   1.902 +    MOZ_ASSERT(mSuccess);
   1.903 +    MOZ_ASSERT(mError);
   1.904 +
   1.905 +    MediaEngine* backend = mBackend;
   1.906 +    // Was a backend provided?
   1.907 +    if (!backend) {
   1.908 +      backend = mManager->GetBackend(mWindowID);
   1.909 +    }
   1.910 +
   1.911 +    // Was a device provided?
   1.912 +    if (!mDeviceChosen) {
   1.913 +      nsresult rv = SelectDevice(backend);
   1.914 +      if (rv != NS_OK) {
   1.915 +        return rv;
   1.916 +      }
   1.917 +    }
   1.918 +
   1.919 +    // It is an error if audio or video are requested along with picture.
   1.920 +    if (mConstraints.mPicture &&
   1.921 +        (IsOn(mConstraints.mAudio) || IsOn(mConstraints.mVideo))) {
   1.922 +      Fail(NS_LITERAL_STRING("NOT_SUPPORTED_ERR"));
   1.923 +      return NS_OK;
   1.924 +    }
   1.925 +
   1.926 +    if (mConstraints.mPicture) {
   1.927 +      ProcessGetUserMediaSnapshot(mVideoDevice->GetSource(), 0);
   1.928 +      return NS_OK;
   1.929 +    }
   1.930 +
   1.931 +    // There's a bug in the permission code that can leave us with mAudio but no audio device
   1.932 +    ProcessGetUserMedia(((IsOn(mConstraints.mAudio) && mAudioDevice) ?
   1.933 +                         mAudioDevice->GetSource() : nullptr),
   1.934 +                        ((IsOn(mConstraints.mVideo) && mVideoDevice) ?
   1.935 +                         mVideoDevice->GetSource() : nullptr));
   1.936 +    return NS_OK;
   1.937 +  }
   1.938 +
   1.939 +  nsresult
   1.940 +  Denied(const nsAString& aErrorMsg)
   1.941 +  {
   1.942 +    MOZ_ASSERT(mSuccess);
   1.943 +    MOZ_ASSERT(mError);
   1.944 +
   1.945 +    // We add a disabled listener to the StreamListeners array until accepted
   1.946 +    // If this was the only active MediaStream, remove the window from the list.
   1.947 +    if (NS_IsMainThread()) {
   1.948 +      // This is safe since we're on main-thread, and the window can only
   1.949 +      // be invalidated from the main-thread (see OnNavigation)
   1.950 +      nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success = mSuccess.forget();
   1.951 +      nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget();
   1.952 +      error->OnError(aErrorMsg);
   1.953 +
   1.954 +      // Should happen *after* error runs for consistency, but may not matter
   1.955 +      nsRefPtr<MediaManager> manager(MediaManager::GetInstance());
   1.956 +      manager->RemoveFromWindowList(mWindowID, mListener);
   1.957 +    } else {
   1.958 +      // This will re-check the window being alive on main-thread
   1.959 +      // Note: we must remove the listener on MainThread as well
   1.960 +      Fail(aErrorMsg);
   1.961 +
   1.962 +      // MUST happen after ErrorCallbackRunnable Run()s, as it checks the active window list
   1.963 +      NS_DispatchToMainThread(new GetUserMediaListenerRemove(mWindowID, mListener));
   1.964 +    }
   1.965 +
   1.966 +    MOZ_ASSERT(!mSuccess);
   1.967 +    MOZ_ASSERT(!mError);
   1.968 +
   1.969 +    return NS_OK;
   1.970 +  }
   1.971 +
   1.972 +  nsresult
   1.973 +  SetContraints(const MediaStreamConstraints& aConstraints)
   1.974 +  {
   1.975 +    mConstraints = aConstraints;
   1.976 +    return NS_OK;
   1.977 +  }
   1.978 +
   1.979 +  nsresult
   1.980 +  SetAudioDevice(AudioDevice* aAudioDevice)
   1.981 +  {
   1.982 +    mAudioDevice = aAudioDevice;
   1.983 +    mDeviceChosen = true;
   1.984 +    return NS_OK;
   1.985 +  }
   1.986 +
   1.987 +  nsresult
   1.988 +  SetVideoDevice(VideoDevice* aVideoDevice)
   1.989 +  {
   1.990 +    mVideoDevice = aVideoDevice;
   1.991 +    mDeviceChosen = true;
   1.992 +    return NS_OK;
   1.993 +  }
   1.994 +
   1.995 +  nsresult
   1.996 +  SelectDevice(MediaEngine* backend)
   1.997 +  {
   1.998 +    MOZ_ASSERT(mSuccess);
   1.999 +    MOZ_ASSERT(mError);
  1.1000 +    if (mConstraints.mPicture || IsOn(mConstraints.mVideo)) {
  1.1001 +      VideoTrackConstraintsN constraints(GetInvariant(mConstraints.mVideo));
  1.1002 +      ScopedDeletePtr<SourceSet> sources (GetSources(backend, constraints,
  1.1003 +          &MediaEngine::EnumerateVideoDevices));
  1.1004 +
  1.1005 +      if (!sources->Length()) {
  1.1006 +        Fail(NS_LITERAL_STRING("NO_DEVICES_FOUND"));
  1.1007 +        return NS_ERROR_FAILURE;
  1.1008 +      }
  1.1009 +      // Pick the first available device.
  1.1010 +      mVideoDevice = do_QueryObject((*sources)[0]);
  1.1011 +      LOG(("Selected video device"));
  1.1012 +    }
  1.1013 +
  1.1014 +    if (IsOn(mConstraints.mAudio)) {
  1.1015 +      AudioTrackConstraintsN constraints(GetInvariant(mConstraints.mAudio));
  1.1016 +      ScopedDeletePtr<SourceSet> sources (GetSources(backend, constraints,
  1.1017 +          &MediaEngine::EnumerateAudioDevices));
  1.1018 +
  1.1019 +      if (!sources->Length()) {
  1.1020 +        Fail(NS_LITERAL_STRING("NO_DEVICES_FOUND"));
  1.1021 +        return NS_ERROR_FAILURE;
  1.1022 +      }
  1.1023 +      // Pick the first available device.
  1.1024 +      mAudioDevice = do_QueryObject((*sources)[0]);
  1.1025 +      LOG(("Selected audio device"));
  1.1026 +    }
  1.1027 +
  1.1028 +    return NS_OK;
  1.1029 +  }
  1.1030 +
  1.1031 +  /**
  1.1032 +   * Allocates a video or audio device and returns a MediaStream via
  1.1033 +   * a GetUserMediaStreamRunnable. Runs off the main thread.
  1.1034 +   */
  1.1035 +  void
  1.1036 +  ProcessGetUserMedia(MediaEngineAudioSource* aAudioSource,
  1.1037 +                      MediaEngineVideoSource* aVideoSource)
  1.1038 +  {
  1.1039 +    MOZ_ASSERT(mSuccess);
  1.1040 +    MOZ_ASSERT(mError);
  1.1041 +    nsresult rv;
  1.1042 +    if (aAudioSource) {
  1.1043 +      rv = aAudioSource->Allocate(GetInvariant(mConstraints.mAudio), mPrefs);
  1.1044 +      if (NS_FAILED(rv)) {
  1.1045 +        LOG(("Failed to allocate audiosource %d",rv));
  1.1046 +        Fail(NS_LITERAL_STRING("HARDWARE_UNAVAILABLE"));
  1.1047 +        return;
  1.1048 +      }
  1.1049 +    }
  1.1050 +    if (aVideoSource) {
  1.1051 +      rv = aVideoSource->Allocate(GetInvariant(mConstraints.mVideo), mPrefs);
  1.1052 +      if (NS_FAILED(rv)) {
  1.1053 +        LOG(("Failed to allocate videosource %d\n",rv));
  1.1054 +        if (aAudioSource) {
  1.1055 +          aAudioSource->Deallocate();
  1.1056 +        }
  1.1057 +        Fail(NS_LITERAL_STRING("HARDWARE_UNAVAILABLE"));
  1.1058 +        return;
  1.1059 +      }
  1.1060 +    }
  1.1061 +
  1.1062 +    NS_DispatchToMainThread(new GetUserMediaStreamRunnable(
  1.1063 +      mSuccess, mError, mWindowID, mListener, aAudioSource, aVideoSource
  1.1064 +    ));
  1.1065 +
  1.1066 +    MOZ_ASSERT(!mSuccess);
  1.1067 +    MOZ_ASSERT(!mError);
  1.1068 +
  1.1069 +    return;
  1.1070 +  }
  1.1071 +
  1.1072 +  /**
  1.1073 +   * Allocates a video device, takes a snapshot and returns a DOMFile via
  1.1074 +   * a SuccessRunnable or an error via the ErrorRunnable. Off the main thread.
  1.1075 +   */
  1.1076 +  void
  1.1077 +  ProcessGetUserMediaSnapshot(MediaEngineVideoSource* aSource, int aDuration)
  1.1078 +  {
  1.1079 +    MOZ_ASSERT(mSuccess);
  1.1080 +    MOZ_ASSERT(mError);
  1.1081 +    nsresult rv = aSource->Allocate(GetInvariant(mConstraints.mVideo), mPrefs);
  1.1082 +    if (NS_FAILED(rv)) {
  1.1083 +      Fail(NS_LITERAL_STRING("HARDWARE_UNAVAILABLE"));
  1.1084 +      return;
  1.1085 +    }
  1.1086 +
  1.1087 +    /**
  1.1088 +     * Display picture capture UI here before calling Snapshot() - Bug 748835.
  1.1089 +     */
  1.1090 +    nsCOMPtr<nsIDOMFile> file;
  1.1091 +    aSource->Snapshot(aDuration, getter_AddRefs(file));
  1.1092 +    aSource->Deallocate();
  1.1093 +
  1.1094 +    NS_DispatchToMainThread(new SuccessCallbackRunnable(
  1.1095 +      mSuccess, mError, file, mWindowID
  1.1096 +    ));
  1.1097 +
  1.1098 +    MOZ_ASSERT(!mSuccess);
  1.1099 +    MOZ_ASSERT(!mError);
  1.1100 +
  1.1101 +    return;
  1.1102 +  }
  1.1103 +
  1.1104 +private:
  1.1105 +  MediaStreamConstraints mConstraints;
  1.1106 +
  1.1107 +  nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess;
  1.1108 +  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
  1.1109 +  uint64_t mWindowID;
  1.1110 +  nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
  1.1111 +  nsRefPtr<AudioDevice> mAudioDevice;
  1.1112 +  nsRefPtr<VideoDevice> mVideoDevice;
  1.1113 +  MediaEnginePrefs mPrefs;
  1.1114 +
  1.1115 +  bool mDeviceChosen;
  1.1116 +
  1.1117 +  RefPtr<MediaEngine> mBackend;
  1.1118 +  nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
  1.1119 +};
  1.1120 +
  1.1121 +/**
  1.1122 + * Similar to GetUserMediaRunnable, but used for the chrome-only
  1.1123 + * GetUserMediaDevices function. Enumerates a list of audio & video devices,
  1.1124 + * wraps them up in nsIMediaDevice objects and returns it to the success
  1.1125 + * callback.
  1.1126 + */
  1.1127 +class GetUserMediaDevicesRunnable : public nsRunnable
  1.1128 +{
  1.1129 +public:
  1.1130 +  GetUserMediaDevicesRunnable(
  1.1131 +    const MediaStreamConstraints& aConstraints,
  1.1132 +    already_AddRefed<nsIGetUserMediaDevicesSuccessCallback> aSuccess,
  1.1133 +    already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
  1.1134 +    uint64_t aWindowId, char* aAudioLoopbackDev, char* aVideoLoopbackDev)
  1.1135 +    : mConstraints(aConstraints)
  1.1136 +    , mSuccess(aSuccess)
  1.1137 +    , mError(aError)
  1.1138 +    , mManager(MediaManager::GetInstance())
  1.1139 +    , mWindowId(aWindowId)
  1.1140 +    , mLoopbackAudioDevice(aAudioLoopbackDev)
  1.1141 +    , mLoopbackVideoDevice(aVideoLoopbackDev) {}
  1.1142 +
  1.1143 +  NS_IMETHOD
  1.1144 +  Run()
  1.1145 +  {
  1.1146 +    NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
  1.1147 +
  1.1148 +    nsRefPtr<MediaEngine> backend;
  1.1149 +    if (mConstraints.mFake)
  1.1150 +      backend = new MediaEngineDefault();
  1.1151 +    else
  1.1152 +      backend = mManager->GetBackend(mWindowId);
  1.1153 +
  1.1154 +    ScopedDeletePtr<SourceSet> final(new SourceSet);
  1.1155 +    if (IsOn(mConstraints.mVideo)) {
  1.1156 +      VideoTrackConstraintsN constraints(GetInvariant(mConstraints.mVideo));
  1.1157 +      ScopedDeletePtr<SourceSet> s(GetSources(backend, constraints,
  1.1158 +          &MediaEngine::EnumerateVideoDevices,
  1.1159 +          mLoopbackVideoDevice));
  1.1160 +      final->MoveElementsFrom(*s);
  1.1161 +    }
  1.1162 +    if (IsOn(mConstraints.mAudio)) {
  1.1163 +      AudioTrackConstraintsN constraints(GetInvariant(mConstraints.mAudio));
  1.1164 +      ScopedDeletePtr<SourceSet> s (GetSources(backend, constraints,
  1.1165 +          &MediaEngine::EnumerateAudioDevices,
  1.1166 +          mLoopbackAudioDevice));
  1.1167 +      final->MoveElementsFrom(*s);
  1.1168 +    }
  1.1169 +    NS_DispatchToMainThread(new DeviceSuccessCallbackRunnable(mWindowId,
  1.1170 +                                                              mSuccess, mError,
  1.1171 +                                                              final.forget()));
  1.1172 +    // DeviceSuccessCallbackRunnable should have taken these.
  1.1173 +    MOZ_ASSERT(!mSuccess && !mError);
  1.1174 +    return NS_OK;
  1.1175 +  }
  1.1176 +
  1.1177 +private:
  1.1178 +  MediaStreamConstraints mConstraints;
  1.1179 +  nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> mSuccess;
  1.1180 +  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
  1.1181 +  nsRefPtr<MediaManager> mManager;
  1.1182 +  uint64_t mWindowId;
  1.1183 +  const nsString mCallId;
  1.1184 +  // Audio & Video loopback devices to be used based on
  1.1185 +  // the preference settings. This is currently used for
  1.1186 +  // automated media tests only.
  1.1187 +  char* mLoopbackAudioDevice;
  1.1188 +  char* mLoopbackVideoDevice;
  1.1189 +};
  1.1190 +
  1.1191 +MediaManager::MediaManager()
  1.1192 +  : mMediaThread(nullptr)
  1.1193 +  , mMutex("mozilla::MediaManager")
  1.1194 +  , mBackend(nullptr) {
  1.1195 +  mPrefs.mWidth  = 0; // adaptive default
  1.1196 +  mPrefs.mHeight = 0; // adaptive default
  1.1197 +  mPrefs.mFPS    = MediaEngine::DEFAULT_VIDEO_FPS;
  1.1198 +  mPrefs.mMinFPS = MediaEngine::DEFAULT_VIDEO_MIN_FPS;
  1.1199 +
  1.1200 +  nsresult rv;
  1.1201 +  nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
  1.1202 +  if (NS_SUCCEEDED(rv)) {
  1.1203 +    nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
  1.1204 +    if (branch) {
  1.1205 +      GetPrefs(branch, nullptr);
  1.1206 +    }
  1.1207 +  }
  1.1208 +  LOG(("%s: default prefs: %dx%d @%dfps (min %d)", __FUNCTION__,
  1.1209 +       mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS));
  1.1210 +}
  1.1211 +
  1.1212 +NS_IMPL_ISUPPORTS(MediaManager, nsIMediaManagerService, nsIObserver)
  1.1213 +
  1.1214 +/* static */ StaticRefPtr<MediaManager> MediaManager::sSingleton;
  1.1215 +
  1.1216 +// NOTE: never Dispatch(....,NS_DISPATCH_SYNC) to the MediaManager
  1.1217 +// thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread
  1.1218 +// from MediaManager thread.
  1.1219 +/* static */  MediaManager*
  1.1220 +MediaManager::Get() {
  1.1221 +  if (!sSingleton) {
  1.1222 +    sSingleton = new MediaManager();
  1.1223 +
  1.1224 +    NS_NewNamedThread("MediaManager", getter_AddRefs(sSingleton->mMediaThread));
  1.1225 +    LOG(("New Media thread for gum"));
  1.1226 +
  1.1227 +    NS_ASSERTION(NS_IsMainThread(), "Only create MediaManager on main thread");
  1.1228 +    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  1.1229 +    if (obs) {
  1.1230 +      obs->AddObserver(sSingleton, "xpcom-shutdown", false);
  1.1231 +      obs->AddObserver(sSingleton, "getUserMedia:response:allow", false);
  1.1232 +      obs->AddObserver(sSingleton, "getUserMedia:response:deny", false);
  1.1233 +      obs->AddObserver(sSingleton, "getUserMedia:revoke", false);
  1.1234 +      obs->AddObserver(sSingleton, "phone-state-changed", false);
  1.1235 +    }
  1.1236 +    // else MediaManager won't work properly and will leak (see bug 837874)
  1.1237 +    nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
  1.1238 +    if (prefs) {
  1.1239 +      prefs->AddObserver("media.navigator.video.default_width", sSingleton, false);
  1.1240 +      prefs->AddObserver("media.navigator.video.default_height", sSingleton, false);
  1.1241 +      prefs->AddObserver("media.navigator.video.default_fps", sSingleton, false);
  1.1242 +      prefs->AddObserver("media.navigator.video.default_minfps", sSingleton, false);
  1.1243 +    }
  1.1244 +  }
  1.1245 +  return sSingleton;
  1.1246 +}
  1.1247 +
  1.1248 +/* static */ already_AddRefed<MediaManager>
  1.1249 +MediaManager::GetInstance()
  1.1250 +{
  1.1251 +  // so we can have non-refcounted getters
  1.1252 +  nsRefPtr<MediaManager> service = MediaManager::Get();
  1.1253 +  return service.forget();
  1.1254 +}
  1.1255 +
  1.1256 +/* static */ nsresult
  1.1257 +MediaManager::NotifyRecordingStatusChange(nsPIDOMWindow* aWindow,
  1.1258 +                                          const nsString& aMsg,
  1.1259 +                                          const bool& aIsAudio,
  1.1260 +                                          const bool& aIsVideo)
  1.1261 +{
  1.1262 +  NS_ENSURE_ARG(aWindow);
  1.1263 +
  1.1264 +  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  1.1265 +  if (!obs) {
  1.1266 +    NS_WARNING("Could not get the Observer service for GetUserMedia recording notification.");
  1.1267 +    return NS_ERROR_FAILURE;
  1.1268 +  }
  1.1269 +
  1.1270 +  nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
  1.1271 +  props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), aIsAudio);
  1.1272 +  props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), aIsVideo);
  1.1273 +
  1.1274 +  bool isApp = false;
  1.1275 +  nsString requestURL;
  1.1276 +
  1.1277 +  if (nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell()) {
  1.1278 +    nsresult rv = docShell->GetIsApp(&isApp);
  1.1279 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1280 +
  1.1281 +    if (isApp) {
  1.1282 +      rv = docShell->GetAppManifestURL(requestURL);
  1.1283 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1284 +    }
  1.1285 +  }
  1.1286 +
  1.1287 +  if (!isApp) {
  1.1288 +    nsCString pageURL;
  1.1289 +    nsCOMPtr<nsIURI> docURI = aWindow->GetDocumentURI();
  1.1290 +    NS_ENSURE_TRUE(docURI, NS_ERROR_FAILURE);
  1.1291 +
  1.1292 +    nsresult rv = docURI->GetSpec(pageURL);
  1.1293 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1294 +
  1.1295 +    requestURL = NS_ConvertUTF8toUTF16(pageURL);
  1.1296 +  }
  1.1297 +
  1.1298 +  props->SetPropertyAsBool(NS_LITERAL_STRING("isApp"), isApp);
  1.1299 +  props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL);
  1.1300 +
  1.1301 +  obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
  1.1302 +		       "recording-device-events",
  1.1303 +		       aMsg.get());
  1.1304 +
  1.1305 +  // Forward recording events to parent process.
  1.1306 +  // The events are gathered in chrome process and used for recording indicator
  1.1307 +  if (XRE_GetProcessType() != GeckoProcessType_Default) {
  1.1308 +    unused <<
  1.1309 +      dom::ContentChild::GetSingleton()->SendRecordingDeviceEvents(aMsg,
  1.1310 +                                                                   requestURL,
  1.1311 +                                                                   aIsAudio,
  1.1312 +                                                                   aIsVideo);
  1.1313 +  }
  1.1314 +
  1.1315 +  return NS_OK;
  1.1316 +}
  1.1317 +
  1.1318 +/**
  1.1319 + * The entry point for this file. A call from Navigator::mozGetUserMedia
  1.1320 + * will end up here. MediaManager is a singleton that is responsible
  1.1321 + * for handling all incoming getUserMedia calls from every window.
  1.1322 + */
  1.1323 +nsresult
  1.1324 +MediaManager::GetUserMedia(bool aPrivileged,
  1.1325 +  nsPIDOMWindow* aWindow, const MediaStreamConstraints& aConstraints,
  1.1326 +  nsIDOMGetUserMediaSuccessCallback* aOnSuccess,
  1.1327 +  nsIDOMGetUserMediaErrorCallback* aOnError)
  1.1328 +{
  1.1329 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.1330 +
  1.1331 +  NS_ENSURE_TRUE(aWindow, NS_ERROR_NULL_POINTER);
  1.1332 +  NS_ENSURE_TRUE(aOnError, NS_ERROR_NULL_POINTER);
  1.1333 +  NS_ENSURE_TRUE(aOnSuccess, NS_ERROR_NULL_POINTER);
  1.1334 +
  1.1335 +  nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onSuccess(aOnSuccess);
  1.1336 +  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError(aOnError);
  1.1337 +
  1.1338 +  MediaStreamConstraints c(aConstraints); // copy
  1.1339 +
  1.1340 +  /**
  1.1341 +   * If we were asked to get a picture, before getting a snapshot, we check if
  1.1342 +   * the calling page is allowed to open a popup. We do this because
  1.1343 +   * {picture:true} will open a new "window" to let the user preview or select
  1.1344 +   * an image, on Android. The desktop UI for {picture:true} is TBD, at which
  1.1345 +   * may point we can decide whether to extend this test there as well.
  1.1346 +   */
  1.1347 +#if !defined(MOZ_WEBRTC)
  1.1348 +  if (c.mPicture && !aPrivileged) {
  1.1349 +    if (aWindow->GetPopupControlState() > openControlled) {
  1.1350 +      nsCOMPtr<nsIPopupWindowManager> pm =
  1.1351 +        do_GetService(NS_POPUPWINDOWMANAGER_CONTRACTID);
  1.1352 +      if (!pm) {
  1.1353 +        return NS_OK;
  1.1354 +      }
  1.1355 +      uint32_t permission;
  1.1356 +      nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
  1.1357 +      pm->TestPermission(doc->NodePrincipal(), &permission);
  1.1358 +      if (permission == nsIPopupWindowManager::DENY_POPUP) {
  1.1359 +        nsGlobalWindow::FirePopupBlockedEvent(
  1.1360 +          doc, aWindow, nullptr, EmptyString(), EmptyString()
  1.1361 +        );
  1.1362 +        return NS_OK;
  1.1363 +      }
  1.1364 +    }
  1.1365 +  }
  1.1366 +#endif
  1.1367 +
  1.1368 +  static bool created = false;
  1.1369 +  if (!created) {
  1.1370 +    // Force MediaManager to startup before we try to access it from other threads
  1.1371 +    // Hack: should init singleton earlier unless it's expensive (mem or CPU)
  1.1372 +    (void) MediaManager::Get();
  1.1373 +#ifdef MOZ_B2G
  1.1374 +    // Initialize MediaPermissionManager before send out any permission request.
  1.1375 +    (void) MediaPermissionManager::GetInstance();
  1.1376 +#endif //MOZ_B2G
  1.1377 +  }
  1.1378 +
  1.1379 +  // Store the WindowID in a hash table and mark as active. The entry is removed
  1.1380 +  // when this window is closed or navigated away from.
  1.1381 +  uint64_t windowID = aWindow->WindowID();
  1.1382 +  // This is safe since we're on main-thread, and the windowlist can only
  1.1383 +  // be invalidated from the main-thread (see OnNavigation)
  1.1384 +  StreamListeners* listeners = GetActiveWindows()->Get(windowID);
  1.1385 +  if (!listeners) {
  1.1386 +    listeners = new StreamListeners;
  1.1387 +    GetActiveWindows()->Put(windowID, listeners);
  1.1388 +  }
  1.1389 +
  1.1390 +  // Ensure there's a thread for gum to proxy to off main thread
  1.1391 +  nsIThread *mediaThread = MediaManager::GetThread();
  1.1392 +
  1.1393 +  // Create a disabled listener to act as a placeholder
  1.1394 +  GetUserMediaCallbackMediaStreamListener* listener =
  1.1395 +    new GetUserMediaCallbackMediaStreamListener(mediaThread, windowID);
  1.1396 +
  1.1397 +  // No need for locking because we always do this in the main thread.
  1.1398 +  listeners->AppendElement(listener);
  1.1399 +
  1.1400 +  // Developer preference for turning off permission check.
  1.1401 +  if (Preferences::GetBool("media.navigator.permission.disabled", false)) {
  1.1402 +    aPrivileged = true;
  1.1403 +  }
  1.1404 +  if (!Preferences::GetBool("media.navigator.video.enabled", true)) {
  1.1405 +    c.mVideo.SetAsBoolean() = false;
  1.1406 +  }
  1.1407 +
  1.1408 +#if defined(ANDROID) || defined(MOZ_WIDGET_GONK)
  1.1409 +  // Be backwards compatible only on mobile and only for facingMode.
  1.1410 +  if (c.mVideo.IsMediaTrackConstraints()) {
  1.1411 +    auto& tc = c.mVideo.GetAsMediaTrackConstraints();
  1.1412 +    if (!tc.mRequire.WasPassed() &&
  1.1413 +        tc.mMandatory.mFacingMode.WasPassed() && !tc.mFacingMode.WasPassed()) {
  1.1414 +      tc.mFacingMode.Construct(tc.mMandatory.mFacingMode.Value());
  1.1415 +      tc.mRequire.Construct().AppendElement(NS_LITERAL_STRING("facingMode"));
  1.1416 +    }
  1.1417 +    if (tc.mOptional.WasPassed() && !tc.mAdvanced.WasPassed()) {
  1.1418 +      tc.mAdvanced.Construct();
  1.1419 +      for (uint32_t i = 0; i < tc.mOptional.Value().Length(); i++) {
  1.1420 +        if (tc.mOptional.Value()[i].mFacingMode.WasPassed()) {
  1.1421 +          MediaTrackConstraintSet n;
  1.1422 +          n.mFacingMode.Construct(tc.mOptional.Value()[i].mFacingMode.Value());
  1.1423 +          tc.mAdvanced.Value().AppendElement(n);
  1.1424 +        }
  1.1425 +      }
  1.1426 +    }
  1.1427 +  }
  1.1428 +#endif
  1.1429 +
  1.1430 +  // Pass callbacks and MediaStreamListener along to GetUserMediaRunnable.
  1.1431 +  nsRefPtr<GetUserMediaRunnable> runnable;
  1.1432 +  if (c.mFake) {
  1.1433 +    // Fake stream from default backend.
  1.1434 +    runnable = new GetUserMediaRunnable(c, onSuccess.forget(),
  1.1435 +      onError.forget(), windowID, listener, mPrefs, new MediaEngineDefault());
  1.1436 +  } else {
  1.1437 +    // Stream from default device from WebRTC backend.
  1.1438 +    runnable = new GetUserMediaRunnable(c, onSuccess.forget(),
  1.1439 +      onError.forget(), windowID, listener, mPrefs);
  1.1440 +  }
  1.1441 +
  1.1442 +#ifdef MOZ_B2G_CAMERA
  1.1443 +  if (mCameraManager == nullptr) {
  1.1444 +    mCameraManager = nsDOMCameraManager::CreateInstance(aWindow);
  1.1445 +  }
  1.1446 +#endif
  1.1447 +
  1.1448 +#if defined(ANDROID) && !defined(MOZ_WIDGET_GONK)
  1.1449 +  if (c.mPicture) {
  1.1450 +    // ShowFilePickerForMimeType() must run on the Main Thread! (on Android)
  1.1451 +    NS_DispatchToMainThread(runnable);
  1.1452 +    return NS_OK;
  1.1453 +  }
  1.1454 +#endif
  1.1455 +  // XXX No full support for picture in Desktop yet (needs proper UI)
  1.1456 +  if (aPrivileged ||
  1.1457 +      (c.mFake && !Preferences::GetBool("media.navigator.permission.fake"))) {
  1.1458 +    mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
  1.1459 +  } else {
  1.1460 +    bool isHTTPS = false;
  1.1461 +    nsIURI* docURI = aWindow->GetDocumentURI();
  1.1462 +    if (docURI) {
  1.1463 +      docURI->SchemeIs("https", &isHTTPS);
  1.1464 +    }
  1.1465 +
  1.1466 +    // Check if this site has persistent permissions.
  1.1467 +    nsresult rv;
  1.1468 +    nsCOMPtr<nsIPermissionManager> permManager =
  1.1469 +      do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
  1.1470 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1471 +
  1.1472 +    uint32_t audioPerm = nsIPermissionManager::UNKNOWN_ACTION;
  1.1473 +    if (IsOn(c.mAudio)) {
  1.1474 +      rv = permManager->TestExactPermissionFromPrincipal(
  1.1475 +        aWindow->GetExtantDoc()->NodePrincipal(), "microphone", &audioPerm);
  1.1476 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1477 +    }
  1.1478 +
  1.1479 +    uint32_t videoPerm = nsIPermissionManager::UNKNOWN_ACTION;
  1.1480 +    if (IsOn(c.mVideo)) {
  1.1481 +      rv = permManager->TestExactPermissionFromPrincipal(
  1.1482 +        aWindow->GetExtantDoc()->NodePrincipal(), "camera", &videoPerm);
  1.1483 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1484 +    }
  1.1485 +
  1.1486 +    if ((!IsOn(c.mAudio) || audioPerm == nsIPermissionManager::DENY_ACTION) &&
  1.1487 +        (!IsOn(c.mVideo) || videoPerm == nsIPermissionManager::DENY_ACTION)) {
  1.1488 +      return runnable->Denied(NS_LITERAL_STRING("PERMISSION_DENIED"));
  1.1489 +    }
  1.1490 +
  1.1491 +    // Ask for user permission, and dispatch runnable (or not) when a response
  1.1492 +    // is received via an observer notification. Each call is paired with its
  1.1493 +    // runnable by a GUID.
  1.1494 +    nsCOMPtr<nsIUUIDGenerator> uuidgen =
  1.1495 +      do_GetService("@mozilla.org/uuid-generator;1", &rv);
  1.1496 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1497 +
  1.1498 +    // Generate a call ID.
  1.1499 +    nsID id;
  1.1500 +    rv = uuidgen->GenerateUUIDInPlace(&id);
  1.1501 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1502 +
  1.1503 +    char buffer[NSID_LENGTH];
  1.1504 +    id.ToProvidedString(buffer);
  1.1505 +    NS_ConvertUTF8toUTF16 callID(buffer);
  1.1506 +
  1.1507 +    // Store the current unarmed runnable w/callbacks.
  1.1508 +    mActiveCallbacks.Put(callID, runnable);
  1.1509 +
  1.1510 +    // Add a WindowID cross-reference so OnNavigation can tear things down
  1.1511 +    nsTArray<nsString>* array;
  1.1512 +    if (!mCallIds.Get(windowID, &array)) {
  1.1513 +      array = new nsTArray<nsString>();
  1.1514 +      array->AppendElement(callID);
  1.1515 +      mCallIds.Put(windowID, array);
  1.1516 +    } else {
  1.1517 +      array->AppendElement(callID);
  1.1518 +    }
  1.1519 +    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  1.1520 +    nsRefPtr<GetUserMediaRequest> req = new GetUserMediaRequest(aWindow,
  1.1521 +                                                                callID, c, isHTTPS);
  1.1522 +    obs->NotifyObservers(req, "getUserMedia:request", nullptr);
  1.1523 +  }
  1.1524 +
  1.1525 +  return NS_OK;
  1.1526 +}
  1.1527 +
  1.1528 +nsresult
  1.1529 +MediaManager::GetUserMediaDevices(nsPIDOMWindow* aWindow,
  1.1530 +  const MediaStreamConstraints& aConstraints,
  1.1531 +  nsIGetUserMediaDevicesSuccessCallback* aOnSuccess,
  1.1532 +  nsIDOMGetUserMediaErrorCallback* aOnError,
  1.1533 +  uint64_t aInnerWindowID)
  1.1534 +{
  1.1535 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.1536 +
  1.1537 +  NS_ENSURE_TRUE(aOnError, NS_ERROR_NULL_POINTER);
  1.1538 +  NS_ENSURE_TRUE(aOnSuccess, NS_ERROR_NULL_POINTER);
  1.1539 +
  1.1540 +  nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess(aOnSuccess);
  1.1541 +  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError(aOnError);
  1.1542 +  char* loopbackAudioDevice = nullptr;
  1.1543 +  char* loopbackVideoDevice = nullptr;
  1.1544 +
  1.1545 +#ifdef DEBUG
  1.1546 +  nsresult rv;
  1.1547 +
  1.1548 +  // Check if the preference for using loopback devices is enabled.
  1.1549 +  nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
  1.1550 +  if (NS_SUCCEEDED(rv)) {
  1.1551 +    nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
  1.1552 +    if (branch) {
  1.1553 +      branch->GetCharPref("media.audio_loopback_dev", &loopbackAudioDevice);
  1.1554 +      branch->GetCharPref("media.video_loopback_dev", &loopbackVideoDevice);
  1.1555 +    }
  1.1556 +  }
  1.1557 +#endif
  1.1558 +
  1.1559 +  nsCOMPtr<nsIRunnable> gUMDRunnable = new GetUserMediaDevicesRunnable(
  1.1560 +    aConstraints, onSuccess.forget(), onError.forget(),
  1.1561 +    (aInnerWindowID ? aInnerWindowID : aWindow->WindowID()),
  1.1562 +    loopbackAudioDevice, loopbackVideoDevice);
  1.1563 +
  1.1564 +  mMediaThread->Dispatch(gUMDRunnable, NS_DISPATCH_NORMAL);
  1.1565 +  return NS_OK;
  1.1566 +}
  1.1567 +
  1.1568 +MediaEngine*
  1.1569 +MediaManager::GetBackend(uint64_t aWindowId)
  1.1570 +{
  1.1571 +  // Plugin backends as appropriate. The default engine also currently
  1.1572 +  // includes picture support for Android.
  1.1573 +  // This IS called off main-thread.
  1.1574 +  MutexAutoLock lock(mMutex);
  1.1575 +  if (!mBackend) {
  1.1576 +#if defined(MOZ_WEBRTC)
  1.1577 +    mBackend = new MediaEngineWebRTC(mPrefs);
  1.1578 +#else
  1.1579 +    mBackend = new MediaEngineDefault();
  1.1580 +#endif
  1.1581 +  }
  1.1582 +  return mBackend;
  1.1583 +}
  1.1584 +
  1.1585 +static void
  1.1586 +StopSharingCallback(MediaManager *aThis,
  1.1587 +                    uint64_t aWindowID,
  1.1588 +                    StreamListeners *aListeners,
  1.1589 +                    void *aData)
  1.1590 +{
  1.1591 +  if (aListeners) {
  1.1592 +    auto length = aListeners->Length();
  1.1593 +    for (size_t i = 0; i < length; ++i) {
  1.1594 +      GetUserMediaCallbackMediaStreamListener *listener = aListeners->ElementAt(i);
  1.1595 +
  1.1596 +      if (listener->Stream()) { // aka HasBeenActivate()ed
  1.1597 +        listener->Invalidate();
  1.1598 +      }
  1.1599 +      listener->Remove();
  1.1600 +    }
  1.1601 +    aListeners->Clear();
  1.1602 +    aThis->RemoveWindowID(aWindowID);
  1.1603 +  }
  1.1604 +}
  1.1605 +
  1.1606 +
  1.1607 +void
  1.1608 +MediaManager::OnNavigation(uint64_t aWindowID)
  1.1609 +{
  1.1610 +  NS_ASSERTION(NS_IsMainThread(), "OnNavigation called off main thread");
  1.1611 +  LOG(("OnNavigation for %llu", aWindowID));
  1.1612 +
  1.1613 +  // Invalidate this window. The runnables check this value before making
  1.1614 +  // a call to content.
  1.1615 +
  1.1616 +  nsTArray<nsString>* callIds;
  1.1617 +  if (mCallIds.Get(aWindowID, &callIds)) {
  1.1618 +    for (int i = 0, len = callIds->Length(); i < len; ++i) {
  1.1619 +      mActiveCallbacks.Remove((*callIds)[i]);
  1.1620 +    }
  1.1621 +    mCallIds.Remove(aWindowID);
  1.1622 +  }
  1.1623 +
  1.1624 +  // This is safe since we're on main-thread, and the windowlist can only
  1.1625 +  // be added to from the main-thread
  1.1626 +  nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
  1.1627 +      (nsGlobalWindow::GetInnerWindowWithId(aWindowID));
  1.1628 +  if (window) {
  1.1629 +    IterateWindowListeners(window, StopSharingCallback, nullptr);
  1.1630 +  } else {
  1.1631 +    RemoveWindowID(aWindowID);
  1.1632 +  }
  1.1633 +}
  1.1634 +
  1.1635 +void
  1.1636 +MediaManager::RemoveFromWindowList(uint64_t aWindowID,
  1.1637 +  GetUserMediaCallbackMediaStreamListener *aListener)
  1.1638 +{
  1.1639 +  NS_ASSERTION(NS_IsMainThread(), "RemoveFromWindowList called off main thread");
  1.1640 +
  1.1641 +  // This is defined as safe on an inactive GUMCMSListener
  1.1642 +  aListener->Remove(); // really queues the remove
  1.1643 +
  1.1644 +  StreamListeners* listeners = GetWindowListeners(aWindowID);
  1.1645 +  if (!listeners) {
  1.1646 +    return;
  1.1647 +  }
  1.1648 +  listeners->RemoveElement(aListener);
  1.1649 +  if (listeners->Length() == 0) {
  1.1650 +    RemoveWindowID(aWindowID);
  1.1651 +    // listeners has been deleted here
  1.1652 +
  1.1653 +    // get outer windowID
  1.1654 +    nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
  1.1655 +      (nsGlobalWindow::GetInnerWindowWithId(aWindowID));
  1.1656 +    if (window) {
  1.1657 +      nsPIDOMWindow *outer = window->GetOuterWindow();
  1.1658 +      if (outer) {
  1.1659 +        uint64_t outerID = outer->WindowID();
  1.1660 +
  1.1661 +        // Notify the UI that this window no longer has gUM active
  1.1662 +        char windowBuffer[32];
  1.1663 +        PR_snprintf(windowBuffer, sizeof(windowBuffer), "%llu", outerID);
  1.1664 +        nsAutoString data;
  1.1665 +        data.Append(NS_ConvertUTF8toUTF16(windowBuffer));
  1.1666 +
  1.1667 +        nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  1.1668 +        obs->NotifyObservers(nullptr, "recording-window-ended", data.get());
  1.1669 +        LOG(("Sent recording-window-ended for window %llu (outer %llu)",
  1.1670 +             aWindowID, outerID));
  1.1671 +      } else {
  1.1672 +        LOG(("No outer window for inner %llu", aWindowID));
  1.1673 +      }
  1.1674 +    } else {
  1.1675 +      LOG(("No inner window for %llu", aWindowID));
  1.1676 +    }
  1.1677 +  }
  1.1678 +}
  1.1679 +
  1.1680 +void
  1.1681 +MediaManager::GetPref(nsIPrefBranch *aBranch, const char *aPref,
  1.1682 +                      const char *aData, int32_t *aVal)
  1.1683 +{
  1.1684 +  int32_t temp;
  1.1685 +  if (aData == nullptr || strcmp(aPref,aData) == 0) {
  1.1686 +    if (NS_SUCCEEDED(aBranch->GetIntPref(aPref, &temp))) {
  1.1687 +      *aVal = temp;
  1.1688 +    }
  1.1689 +  }
  1.1690 +}
  1.1691 +
  1.1692 +void
  1.1693 +MediaManager::GetPrefBool(nsIPrefBranch *aBranch, const char *aPref,
  1.1694 +                          const char *aData, bool *aVal)
  1.1695 +{
  1.1696 +  bool temp;
  1.1697 +  if (aData == nullptr || strcmp(aPref,aData) == 0) {
  1.1698 +    if (NS_SUCCEEDED(aBranch->GetBoolPref(aPref, &temp))) {
  1.1699 +      *aVal = temp;
  1.1700 +    }
  1.1701 +  }
  1.1702 +}
  1.1703 +
  1.1704 +void
  1.1705 +MediaManager::GetPrefs(nsIPrefBranch *aBranch, const char *aData)
  1.1706 +{
  1.1707 +  GetPref(aBranch, "media.navigator.video.default_width", aData, &mPrefs.mWidth);
  1.1708 +  GetPref(aBranch, "media.navigator.video.default_height", aData, &mPrefs.mHeight);
  1.1709 +  GetPref(aBranch, "media.navigator.video.default_fps", aData, &mPrefs.mFPS);
  1.1710 +  GetPref(aBranch, "media.navigator.video.default_minfps", aData, &mPrefs.mMinFPS);
  1.1711 +}
  1.1712 +
  1.1713 +nsresult
  1.1714 +MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
  1.1715 +  const char16_t* aData)
  1.1716 +{
  1.1717 +  NS_ASSERTION(NS_IsMainThread(), "Observer invoked off the main thread");
  1.1718 +  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  1.1719 +
  1.1720 +  if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
  1.1721 +    nsCOMPtr<nsIPrefBranch> branch( do_QueryInterface(aSubject) );
  1.1722 +    if (branch) {
  1.1723 +      GetPrefs(branch,NS_ConvertUTF16toUTF8(aData).get());
  1.1724 +      LOG(("%s: %dx%d @%dfps (min %d)", __FUNCTION__,
  1.1725 +           mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS));
  1.1726 +    }
  1.1727 +  } else if (!strcmp(aTopic, "xpcom-shutdown")) {
  1.1728 +    obs->RemoveObserver(this, "xpcom-shutdown");
  1.1729 +    obs->RemoveObserver(this, "getUserMedia:response:allow");
  1.1730 +    obs->RemoveObserver(this, "getUserMedia:response:deny");
  1.1731 +    obs->RemoveObserver(this, "getUserMedia:revoke");
  1.1732 +
  1.1733 +    nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
  1.1734 +    if (prefs) {
  1.1735 +      prefs->RemoveObserver("media.navigator.video.default_width", this);
  1.1736 +      prefs->RemoveObserver("media.navigator.video.default_height", this);
  1.1737 +      prefs->RemoveObserver("media.navigator.video.default_fps", this);
  1.1738 +      prefs->RemoveObserver("media.navigator.video.default_minfps", this);
  1.1739 +    }
  1.1740 +
  1.1741 +    // Close off any remaining active windows.
  1.1742 +    {
  1.1743 +      MutexAutoLock lock(mMutex);
  1.1744 +      GetActiveWindows()->Clear();
  1.1745 +      mActiveCallbacks.Clear();
  1.1746 +      mCallIds.Clear();
  1.1747 +      LOG(("Releasing MediaManager singleton and thread"));
  1.1748 +      // Note: won't be released immediately as the Observer has a ref to us
  1.1749 +      sSingleton = nullptr;
  1.1750 +      mBackend = nullptr;
  1.1751 +    }
  1.1752 +
  1.1753 +    return NS_OK;
  1.1754 +
  1.1755 +  } else if (!strcmp(aTopic, "getUserMedia:response:allow")) {
  1.1756 +    nsString key(aData);
  1.1757 +    nsRefPtr<GetUserMediaRunnable> runnable;
  1.1758 +    if (!mActiveCallbacks.Get(key, getter_AddRefs(runnable))) {
  1.1759 +      return NS_OK;
  1.1760 +    }
  1.1761 +    mActiveCallbacks.Remove(key);
  1.1762 +
  1.1763 +    if (aSubject) {
  1.1764 +      // A particular device or devices were chosen by the user.
  1.1765 +      // NOTE: does not allow setting a device to null; assumes nullptr
  1.1766 +      nsCOMPtr<nsISupportsArray> array(do_QueryInterface(aSubject));
  1.1767 +      MOZ_ASSERT(array);
  1.1768 +      uint32_t len = 0;
  1.1769 +      array->Count(&len);
  1.1770 +      MOZ_ASSERT(len);
  1.1771 +      if (!len) {
  1.1772 +        // neither audio nor video were selected
  1.1773 +        runnable->Denied(NS_LITERAL_STRING("PERMISSION_DENIED"));
  1.1774 +        return NS_OK;
  1.1775 +      }
  1.1776 +      for (uint32_t i = 0; i < len; i++) {
  1.1777 +        nsCOMPtr<nsISupports> supports;
  1.1778 +        array->GetElementAt(i,getter_AddRefs(supports));
  1.1779 +        nsCOMPtr<nsIMediaDevice> device(do_QueryInterface(supports));
  1.1780 +        MOZ_ASSERT(device); // shouldn't be returning anything else...
  1.1781 +        if (device) {
  1.1782 +          nsString type;
  1.1783 +          device->GetType(type);
  1.1784 +          if (type.EqualsLiteral("video")) {
  1.1785 +            runnable->SetVideoDevice(static_cast<VideoDevice*>(device.get()));
  1.1786 +          } else if (type.EqualsLiteral("audio")) {
  1.1787 +            runnable->SetAudioDevice(static_cast<AudioDevice*>(device.get()));
  1.1788 +          } else {
  1.1789 +            NS_WARNING("Unknown device type in getUserMedia");
  1.1790 +          }
  1.1791 +        }
  1.1792 +      }
  1.1793 +    }
  1.1794 +
  1.1795 +    // Reuse the same thread to save memory.
  1.1796 +    mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
  1.1797 +    return NS_OK;
  1.1798 +
  1.1799 +  } else if (!strcmp(aTopic, "getUserMedia:response:deny")) {
  1.1800 +    nsString errorMessage(NS_LITERAL_STRING("PERMISSION_DENIED"));
  1.1801 +
  1.1802 +    if (aSubject) {
  1.1803 +      nsCOMPtr<nsISupportsString> msg(do_QueryInterface(aSubject));
  1.1804 +      MOZ_ASSERT(msg);
  1.1805 +      msg->GetData(errorMessage);
  1.1806 +      if (errorMessage.IsEmpty())
  1.1807 +        errorMessage.Assign(NS_LITERAL_STRING("UNKNOWN_ERROR"));
  1.1808 +    }
  1.1809 +
  1.1810 +    nsString key(aData);
  1.1811 +    nsRefPtr<GetUserMediaRunnable> runnable;
  1.1812 +    if (!mActiveCallbacks.Get(key, getter_AddRefs(runnable))) {
  1.1813 +      return NS_OK;
  1.1814 +    }
  1.1815 +    mActiveCallbacks.Remove(key);
  1.1816 +    runnable->Denied(errorMessage);
  1.1817 +    return NS_OK;
  1.1818 +
  1.1819 +  } else if (!strcmp(aTopic, "getUserMedia:revoke")) {
  1.1820 +    nsresult rv;
  1.1821 +    uint64_t windowID = nsString(aData).ToInteger64(&rv);
  1.1822 +    MOZ_ASSERT(NS_SUCCEEDED(rv));
  1.1823 +    if (NS_SUCCEEDED(rv)) {
  1.1824 +      LOG(("Revoking MediaCapture access for window %llu",windowID));
  1.1825 +      OnNavigation(windowID);
  1.1826 +    }
  1.1827 +
  1.1828 +    return NS_OK;
  1.1829 +  }
  1.1830 +#ifdef MOZ_WIDGET_GONK
  1.1831 +  else if (!strcmp(aTopic, "phone-state-changed")) {
  1.1832 +    nsString state(aData);
  1.1833 +    if (atoi((const char*)state.get()) == nsIAudioManager::PHONE_STATE_IN_CALL) {
  1.1834 +      StopMediaStreams();
  1.1835 +    }
  1.1836 +    return NS_OK;
  1.1837 +  }
  1.1838 +#endif
  1.1839 +
  1.1840 +  return NS_OK;
  1.1841 +}
  1.1842 +
  1.1843 +static PLDHashOperator
  1.1844 +WindowsHashToArrayFunc (const uint64_t& aId,
  1.1845 +                        StreamListeners* aData,
  1.1846 +                        void *userArg)
  1.1847 +{
  1.1848 +  nsISupportsArray *array =
  1.1849 +    static_cast<nsISupportsArray *>(userArg);
  1.1850 +  nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
  1.1851 +    (nsGlobalWindow::GetInnerWindowWithId(aId));
  1.1852 +
  1.1853 +  MOZ_ASSERT(window);
  1.1854 +  if (window) {
  1.1855 +    // mActiveWindows contains both windows that have requested device
  1.1856 +    // access and windows that are currently capturing media. We want
  1.1857 +    // to return only the latter. See bug 975177.
  1.1858 +    bool capturing = false;
  1.1859 +    if (aData) {
  1.1860 +      uint32_t length = aData->Length();
  1.1861 +      for (uint32_t i = 0; i < length; ++i) {
  1.1862 +        nsRefPtr<GetUserMediaCallbackMediaStreamListener> listener =
  1.1863 +          aData->ElementAt(i);
  1.1864 +        if (listener->CapturingVideo() || listener->CapturingAudio()) {
  1.1865 +          capturing = true;
  1.1866 +          break;
  1.1867 +        }
  1.1868 +      }
  1.1869 +    }
  1.1870 +
  1.1871 +    if (capturing)
  1.1872 +      array->AppendElement(window);
  1.1873 +  }
  1.1874 +  return PL_DHASH_NEXT;
  1.1875 +}
  1.1876 +
  1.1877 +
  1.1878 +nsresult
  1.1879 +MediaManager::GetActiveMediaCaptureWindows(nsISupportsArray **aArray)
  1.1880 +{
  1.1881 +  MOZ_ASSERT(aArray);
  1.1882 +  nsISupportsArray *array;
  1.1883 +  nsresult rv = NS_NewISupportsArray(&array); // AddRefs
  1.1884 +  if (NS_FAILED(rv))
  1.1885 +    return rv;
  1.1886 +
  1.1887 +  mActiveWindows.EnumerateRead(WindowsHashToArrayFunc, array);
  1.1888 +
  1.1889 +  *aArray = array;
  1.1890 +  return NS_OK;
  1.1891 +}
  1.1892 +
  1.1893 +// XXX flags might be better...
  1.1894 +struct CaptureWindowStateData {
  1.1895 +  bool *mVideo;
  1.1896 +  bool *mAudio;
  1.1897 +};
  1.1898 +
  1.1899 +static void
  1.1900 +CaptureWindowStateCallback(MediaManager *aThis,
  1.1901 +                           uint64_t aWindowID,
  1.1902 +                           StreamListeners *aListeners,
  1.1903 +                           void *aData)
  1.1904 +{
  1.1905 +  struct CaptureWindowStateData *data = (struct CaptureWindowStateData *) aData;
  1.1906 +
  1.1907 +  if (aListeners) {
  1.1908 +    auto length = aListeners->Length();
  1.1909 +    for (size_t i = 0; i < length; ++i) {
  1.1910 +      GetUserMediaCallbackMediaStreamListener *listener = aListeners->ElementAt(i);
  1.1911 +
  1.1912 +      if (listener->CapturingVideo()) {
  1.1913 +        *data->mVideo = true;
  1.1914 +      }
  1.1915 +      if (listener->CapturingAudio()) {
  1.1916 +        *data->mAudio = true;
  1.1917 +      }
  1.1918 +    }
  1.1919 +  }
  1.1920 +}
  1.1921 +
  1.1922 +
  1.1923 +NS_IMETHODIMP
  1.1924 +MediaManager::MediaCaptureWindowState(nsIDOMWindow* aWindow, bool* aVideo,
  1.1925 +                                      bool* aAudio)
  1.1926 +{
  1.1927 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.1928 +  struct CaptureWindowStateData data;
  1.1929 +  data.mVideo = aVideo;
  1.1930 +  data.mAudio = aAudio;
  1.1931 +
  1.1932 +  *aVideo = false;
  1.1933 +  *aAudio = false;
  1.1934 +
  1.1935 +  nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow);
  1.1936 +  if (piWin) {
  1.1937 +    IterateWindowListeners(piWin, CaptureWindowStateCallback, &data);
  1.1938 +  }
  1.1939 +#ifdef DEBUG
  1.1940 +  LOG(("%s: window %lld capturing %s %s", __FUNCTION__, piWin ? piWin->WindowID() : -1,
  1.1941 +       *aVideo ? "video" : "", *aAudio ? "audio" : ""));
  1.1942 +#endif
  1.1943 +  return NS_OK;
  1.1944 +}
  1.1945 +
  1.1946 +// lets us do all sorts of things to the listeners
  1.1947 +void
  1.1948 +MediaManager::IterateWindowListeners(nsPIDOMWindow *aWindow,
  1.1949 +                                     WindowListenerCallback aCallback,
  1.1950 +                                     void *aData)
  1.1951 +{
  1.1952 +  // Iterate the docshell tree to find all the child windows, and for each
  1.1953 +  // invoke the callback
  1.1954 +  nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow);
  1.1955 +  if (piWin) {
  1.1956 +    if (piWin->IsInnerWindow() || piWin->GetCurrentInnerWindow()) {
  1.1957 +      uint64_t windowID;
  1.1958 +      if (piWin->IsInnerWindow()) {
  1.1959 +        windowID = piWin->WindowID();
  1.1960 +      } else {
  1.1961 +        windowID = piWin->GetCurrentInnerWindow()->WindowID();
  1.1962 +      }
  1.1963 +      StreamListeners* listeners = GetActiveWindows()->Get(windowID);
  1.1964 +      // pass listeners so it can modify/delete the list
  1.1965 +      (*aCallback)(this, windowID, listeners, aData);
  1.1966 +    }
  1.1967 +
  1.1968 +    // iterate any children of *this* window (iframes, etc)
  1.1969 +    nsCOMPtr<nsIDocShell> docShell = piWin->GetDocShell();
  1.1970 +    if (docShell) {
  1.1971 +      int32_t i, count;
  1.1972 +      docShell->GetChildCount(&count);
  1.1973 +      for (i = 0; i < count; ++i) {
  1.1974 +        nsCOMPtr<nsIDocShellTreeItem> item;
  1.1975 +        docShell->GetChildAt(i, getter_AddRefs(item));
  1.1976 +        nsCOMPtr<nsPIDOMWindow> win = do_GetInterface(item);
  1.1977 +
  1.1978 +        if (win) {
  1.1979 +          IterateWindowListeners(win, aCallback, aData);
  1.1980 +        }
  1.1981 +      }
  1.1982 +    }
  1.1983 +  }
  1.1984 +}
  1.1985 +
  1.1986 +void
  1.1987 +MediaManager::StopMediaStreams()
  1.1988 +{
  1.1989 +  nsCOMPtr<nsISupportsArray> array;
  1.1990 +  GetActiveMediaCaptureWindows(getter_AddRefs(array));
  1.1991 +  uint32_t len;
  1.1992 +  array->Count(&len);
  1.1993 +  for (uint32_t i = 0; i < len; i++) {
  1.1994 +    nsCOMPtr<nsISupports> window;
  1.1995 +    array->GetElementAt(i, getter_AddRefs(window));
  1.1996 +    nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(window));
  1.1997 +    if (win) {
  1.1998 +      OnNavigation(win->WindowID());
  1.1999 +    }
  1.2000 +  }
  1.2001 +}
  1.2002 +
  1.2003 +// Can be invoked from EITHER MainThread or MSG thread
  1.2004 +void
  1.2005 +GetUserMediaCallbackMediaStreamListener::Invalidate()
  1.2006 +{
  1.2007 +
  1.2008 +  nsRefPtr<MediaOperationRunnable> runnable;
  1.2009 +  // We can't take a chance on blocking here, so proxy this to another
  1.2010 +  // thread.
  1.2011 +  // Pass a ref to us (which is threadsafe) so it can query us for the
  1.2012 +  // source stream info.
  1.2013 +  runnable = new MediaOperationRunnable(MEDIA_STOP,
  1.2014 +                                        this, nullptr, nullptr,
  1.2015 +                                        mAudioSource, mVideoSource,
  1.2016 +                                        mFinished, mWindowID, nullptr);
  1.2017 +  mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
  1.2018 +}
  1.2019 +
  1.2020 +// Called from the MediaStreamGraph thread
  1.2021 +void
  1.2022 +GetUserMediaCallbackMediaStreamListener::NotifyFinished(MediaStreamGraph* aGraph)
  1.2023 +{
  1.2024 +  mFinished = true;
  1.2025 +  Invalidate(); // we know it's been activated
  1.2026 +  NS_DispatchToMainThread(new GetUserMediaListenerRemove(mWindowID, this));
  1.2027 +}
  1.2028 +
  1.2029 +// Called from the MediaStreamGraph thread
  1.2030 +// this can be in response to our own RemoveListener() (via ::Remove()), or
  1.2031 +// because the DOM GC'd the DOMLocalMediaStream/etc we're attached to.
  1.2032 +void
  1.2033 +GetUserMediaCallbackMediaStreamListener::NotifyRemoved(MediaStreamGraph* aGraph)
  1.2034 +{
  1.2035 +  {
  1.2036 +    MutexAutoLock lock(mLock); // protect access to mRemoved
  1.2037 +    MM_LOG(("Listener removed by DOM Destroy(), mFinished = %d", (int) mFinished));
  1.2038 +    mRemoved = true;
  1.2039 +  }
  1.2040 +  if (!mFinished) {
  1.2041 +    NotifyFinished(aGraph);
  1.2042 +  }
  1.2043 +}
  1.2044 +
  1.2045 +NS_IMETHODIMP
  1.2046 +GetUserMediaNotificationEvent::Run()
  1.2047 +{
  1.2048 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.2049 +  // Make sure mStream is cleared and our reference to the DOMMediaStream
  1.2050 +  // is dropped on the main thread, no matter what happens in this method.
  1.2051 +  // Otherwise this object might be destroyed off the main thread,
  1.2052 +  // releasing DOMMediaStream off the main thread, which is not allowed.
  1.2053 +  nsRefPtr<DOMMediaStream> stream = mStream.forget();
  1.2054 +
  1.2055 +  nsString msg;
  1.2056 +  switch (mStatus) {
  1.2057 +  case STARTING:
  1.2058 +    msg = NS_LITERAL_STRING("starting");
  1.2059 +    stream->OnTracksAvailable(mOnTracksAvailableCallback.forget());
  1.2060 +    break;
  1.2061 +  case STOPPING:
  1.2062 +    msg = NS_LITERAL_STRING("shutdown");
  1.2063 +    if (mListener) {
  1.2064 +      mListener->SetStopped();
  1.2065 +    }
  1.2066 +    break;
  1.2067 +  }
  1.2068 +
  1.2069 +  nsCOMPtr<nsPIDOMWindow> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
  1.2070 +  NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
  1.2071 +
  1.2072 +  return MediaManager::NotifyRecordingStatusChange(window, msg, mIsAudio, mIsVideo);
  1.2073 +}
  1.2074 +
  1.2075 +} // namespace mozilla

mercurial