Bug 1436694 - MozPromisify device initialization and move it to SourceListener. r?jib draft
authorAndreas Pehrson <pehrsons@mozilla.com>
Fri, 16 Feb 2018 11:55:27 +0100
changeset 759628 fe3142d0574912517e42c720c9e6e251a600249c
parent 759627 5301f1dcb16f89ba6e9a5815cc66645c3309e756
child 759629 4f8a025d2a83535b4a529bf5bb406c7fda15498c
push id100413
push userbmo:apehrson@mozilla.com
push dateMon, 26 Feb 2018 08:00:04 +0000
reviewersjib
bugs1436694
milestone60.0a1
Bug 1436694 - MozPromisify device initialization and move it to SourceListener. r?jib This so that SourceListener can keep its internal state in sync with the result of the start operation. MozReview-Commit-ID: Cgl5TFnpCeW
dom/media/MediaManager.cpp
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -30,16 +30,17 @@
 #include "nsICryptoHash.h"
 #include "nsICryptoHMAC.h"
 #include "nsIKeyModule.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsIInputStream.h"
 #include "nsILineInputStream.h"
 #include "nsPIDOMWindow.h"
 #include "mozilla/EventStateManager.h"
+#include "mozilla/MozPromise.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Types.h"
 #include "mozilla/PeerIdentity.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/MediaStreamBinding.h"
@@ -151,49 +152,48 @@ using media::Refcountable;
 
 static Atomic<bool> sHasShutdown;
 
 typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
 
 struct DeviceState {
   DeviceState(const RefPtr<MediaDevice>& aDevice, bool aOffWhileDisabled)
     : mOffWhileDisabled(aOffWhileDisabled)
-    , mDisableTimer(new MediaTimer())
     , mDevice(aDevice)
   {
     MOZ_ASSERT(mDevice);
   }
 
   // true if we have stopped mDevice, this is a terminal state.
   // MainThread only.
   bool mStopped = false;
 
   // true if mDevice is currently enabled, i.e., turned on and capturing.
   // MainThread only.
-  bool mDeviceEnabled = true;
+  bool mDeviceEnabled = false;
 
   // true if the application has currently enabled mDevice.
   // MainThread only.
-  bool mTrackEnabled = true;
+  bool mTrackEnabled = false;
 
   // true if an operation to Start() or Stop() mDevice has been dispatched to
   // the media thread and is not finished yet.
   // MainThread only.
   bool mOperationInProgress = false;
 
   // true if we are allowed to turn off the underlying source while all tracks
   // are disabled.
   // MainThread only.
   bool mOffWhileDisabled = false;
 
   // Timer triggered by a MediaStreamTrackSource signaling that all tracks got
   // disabled. When the timer fires we initiate Stop()ing mDevice.
   // If set we allow dynamically stopping and starting mDevice.
   // Any thread.
-  const RefPtr<MediaTimer> mDisableTimer;
+  const RefPtr<MediaTimer> mDisableTimer = new MediaTimer();
 
   // The underlying device we keep state for. Always non-null.
   // Threadsafe access, but see method declarations for individual constraints.
   const RefPtr<MediaDevice> mDevice;
 };
 
 /**
  * This mimics the capture state from nsIMediaManagerService.
@@ -234,16 +234,18 @@ FromCaptureState(CaptureState aState)
  * don't hold a reference to it during late shutdown.
  *
  * There's also a hard reference to the SourceListener through its
  * SourceStreamListener and the MediaStreamGraph. MediaStreamGraph
  * clears this on XPCOM_WILL_SHUTDOWN, before MediaManager enters shutdown.
  */
 class SourceListener : public SupportsWeakPtr<SourceListener> {
 public:
+  typedef MozPromise<bool /* aIgnored */, RefPtr<MediaMgrError>, true> InitPromise;
+
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(SourceListener)
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION(SourceListener)
 
   SourceListener();
 
   /**
    * Registers this source listener as belonging to the given window listener.
    */
@@ -252,16 +254,21 @@ public:
   /**
    * Marks this listener as active and adds itself as a listener to aStream.
    */
   void Activate(SourceMediaStream* aStream,
                 MediaDevice* aAudioDevice,
                 MediaDevice* aVideoDevice);
 
   /**
+   * Posts a task to initialize and start all associated devices.
+   */
+  RefPtr<InitPromise> InitializeAsync();
+
+  /**
    * Stops all live tracks, finishes the associated MediaStream and cleans up.
    */
   void Stop();
 
   /**
    * Removes this SourceListener from its associated MediaStream and marks it
    * removed. Also removes the weak reference to the associated window listener.
    */
@@ -1369,99 +1376,42 @@ public:
                                       mOnSuccess,
                                       mWindowID,
                                       domStream))));
 
     // Dispatch to the media thread to ask it to start the sources,
     // because that can take a while.
     // Pass ownership of domStream through the lambda to the nested chrome
     // notification lambda to ensure it's kept alive until that lambda runs or is discarded.
-    RefPtr<GetUserMediaStreamRunnable> self = this;
-    MediaManager::PostTask(NewTaskFrom([self, domStream, callback]() mutable {
-      MOZ_ASSERT(MediaManager::IsInMediaThread());
-      RefPtr<SourceMediaStream> source =
-        self->mSourceListener->GetSourceStream();
-
-      RefPtr<MediaMgrError> error = nullptr;
-      if (self->mAudioDevice) {
-        nsresult rv = self->mAudioDevice->SetTrack(source,
-                                                   kAudioTrack,
-                                                   self->mSourceListener->GetPrincipalHandle());
-        if (NS_SUCCEEDED(rv)) {
-          rv = self->mAudioDevice->Start();
-        } else {
-          nsString log;
-          if (rv == NS_ERROR_NOT_AVAILABLE) {
-            log.AssignASCII("Concurrent mic process limit.");
-            error = new MediaMgrError(NS_LITERAL_STRING("NotReadableError"), log);
-          } else {
-            log.AssignASCII("Starting audio failed");
-            error = new MediaMgrError(NS_LITERAL_STRING("InternalError"), log);
-          }
-        }
-      }
-
-      if (!error && self->mVideoDevice) {
-        nsresult rv = self->mVideoDevice->SetTrack(source,
-                                                   kVideoTrack,
-                                                   self->mSourceListener->GetPrincipalHandle());
-        if (NS_SUCCEEDED(rv)) {
-          rv = self->mVideoDevice->Start();
-        }
-        if (NS_FAILED(rv)) {
-          nsString log;
-          log.AssignASCII("Starting video failed");
-          error = new MediaMgrError(NS_LITERAL_STRING("InternalError"), log);
-        }
-      }
-
-      if (error) {
-        // Dispatch the error callback on main thread.
-        NS_DispatchToMainThread(MakeAndAddRef<ErrorCallbackRunnable>(
-          self->mOnFailure, *error, self->mWindowID));
-        return NS_OK;
-      }
-
-      // Start() queued the tracks to be added synchronously to avoid races
-      source->FinishAddTracks();
-
-      source->AdvanceKnownTracksTime(STREAM_TIME_MAX);
-
-      LOG(("started all sources"));
-
-      // onTracksAvailableCallback must be added to domStream on the main thread.
-      uint64_t windowID = self->mWindowID;
-      NS_DispatchToMainThread(NS_NewRunnableFunction("MediaManager::NotifyChromeOfStart",
-                                                     [source, domStream, callback, windowID]() mutable {
-        source->SetPullEnabled(true);
-
-        MediaManager* manager = MediaManager::GetIfExists();
-        if (!manager) {
+    mSourceListener->InitializeAsync()->Then(
+      GetMainThreadSerialEventTarget(), __func__,
+      [manager = mManager, domStream, callback,
+       windowListener = mWindowListener]()
+      {
+        // Initiating and starting devices succeeded.
+        // onTracksAvailableCallback must be added to domStream on main thread.
+        domStream->OnTracksAvailable(callback->release());
+        windowListener->ChromeAffectingStateChanged();
+        manager->SendPendingGUMRequest();
+      },[manager = mManager, windowID = mWindowID,
+         onFailure = Move(mOnFailure)](const RefPtr<MediaMgrError>& error)
+      {
+        // Initiating and starting devices failed.
+
+        // Only run if the window is still active for our window listener.
+        if (!(manager->IsWindowStillActive(windowID))) {
           return;
         }
-
-        nsGlobalWindowInner* window =
-          nsGlobalWindowInner::GetInnerWindowWithId(windowID);
-        if (!window) {
-          MOZ_ASSERT_UNREACHABLE("Should have window");
-          return;
+        // This is safe since we're on main-thread, and the windowlist can only
+        // be invalidated from the main-thread (see OnNavigation)
+        if (auto* window = nsGlobalWindowInner::GetInnerWindowWithId(windowID)) {
+          auto streamError = MakeRefPtr<MediaStreamError>(window->AsInner(), *error);
+          onFailure->OnError(streamError);
         }
-
-        domStream->OnTracksAvailable(callback->release());
-
-        nsresult rv = MediaManager::NotifyRecordingStatusChange(window->AsInner());
-        if (NS_FAILED(rv)) {
-          MOZ_ASSERT_UNREACHABLE("Should be able to notify chrome");
-          return;
-        }
-
-        manager->SendPendingGUMRequest();
-      }));
-      return NS_OK;
-    }));
+      });
 
     if (!IsPincipalInfoPrivate(mPrincipalInfo)) {
       // Call GetPrincipalKey again, this time w/persist = true, to promote
       // deviceIds to persistent, in case they're not already. Fire'n'forget.
       RefPtr<Pledge<nsCString>> p =
         media::GetPrincipalKey(mPrincipalInfo, true);
     }
     return NS_OK;
@@ -3869,16 +3819,119 @@ SourceListener::Activate(SourceMediaStre
           aVideoDevice,
           aVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Camera &&
           Preferences::GetBool("media.getusermedia.camera.off_while_disabled.enabled", true));
   }
 
   mStream->AddListener(mStreamListener);
 }
 
+RefPtr<SourceListener::InitPromise>
+SourceListener::InitializeAsync()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
+  MOZ_DIAGNOSTIC_ASSERT(!mStopped);
+
+  RefPtr<InitPromise> init = MediaManager::PostTask<InitPromise>(__func__,
+    [ stream = mStream
+    , principal = GetPrincipalHandle()
+    , audioDevice = mAudioDeviceState ? mAudioDeviceState->mDevice : nullptr
+    , videoDevice = mVideoDeviceState ? mVideoDeviceState->mDevice : nullptr
+    ](MozPromiseHolder<InitPromise>& aHolder)
+    {
+      if (audioDevice) {
+        nsresult rv = audioDevice->SetTrack(stream, kAudioTrack, principal);
+        if (NS_SUCCEEDED(rv)) {
+          rv = audioDevice->Start();
+        }
+        if (NS_FAILED(rv)) {
+          nsString log;
+          if (rv == NS_ERROR_NOT_AVAILABLE) {
+            log.AssignASCII("Concurrent mic process limit.");
+            aHolder.Reject(MakeRefPtr<MediaMgrError>(
+                  NS_LITERAL_STRING("NotReadableError"), log), __func__);
+            return;
+          }
+          log.AssignASCII("Starting audio failed");
+          aHolder.Reject(MakeRefPtr<MediaMgrError>(
+                NS_LITERAL_STRING("InternalError"), log), __func__);
+          return;
+        }
+      }
+
+      if (videoDevice) {
+        nsresult rv = videoDevice->SetTrack(stream, kVideoTrack, principal);
+        if (NS_SUCCEEDED(rv)) {
+          rv = videoDevice->Start();
+        }
+        if (NS_FAILED(rv)) {
+          if (audioDevice) {
+            if (NS_WARN_IF(NS_FAILED(audioDevice->Stop()))) {
+              MOZ_ASSERT_UNREACHABLE("Stopping audio failed");
+            }
+          }
+          nsString log;
+          log.AssignASCII("Starting video failed");
+          aHolder.Reject(MakeRefPtr<MediaMgrError>(NS_LITERAL_STRING("InternalError"), log), __func__);
+          return;
+        }
+      }
+
+      // Start() queued the tracks to be added synchronously to avoid races
+      stream->FinishAddTracks();
+      stream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
+      LOG(("started all sources"));
+
+      aHolder.Resolve(true, __func__);
+    });
+
+  return init->Then(GetMainThreadSerialEventTarget(), __func__,
+    [self = RefPtr<SourceListener>(this), this]()
+    {
+      if (mStopped) {
+        // We were shut down during the async init
+        return InitPromise::CreateAndResolve(true, __func__);
+      }
+
+      mStream->SetPullEnabled(true);
+
+      for (DeviceState* state : {mAudioDeviceState.get(),
+                                 mVideoDeviceState.get()}) {
+        if (!state) {
+          continue;
+        }
+        MOZ_DIAGNOSTIC_ASSERT(!state->mTrackEnabled);
+        MOZ_DIAGNOSTIC_ASSERT(!state->mDeviceEnabled);
+        MOZ_DIAGNOSTIC_ASSERT(!state->mStopped);
+
+        state->mDeviceEnabled = true;
+        state->mTrackEnabled = true;
+      }
+      return InitPromise::CreateAndResolve(true, __func__);
+    }, [self = RefPtr<SourceListener>(this), this](RefPtr<MediaMgrError>&& aResult)
+    {
+      if (mStopped) {
+        return InitPromise::CreateAndReject(Move(aResult), __func__);
+      }
+
+      for (DeviceState* state : {mAudioDeviceState.get(),
+                                 mVideoDeviceState.get()}) {
+        if (!state) {
+          continue;
+        }
+        MOZ_DIAGNOSTIC_ASSERT(!state->mTrackEnabled);
+        MOZ_DIAGNOSTIC_ASSERT(!state->mDeviceEnabled);
+        MOZ_DIAGNOSTIC_ASSERT(!state->mStopped);
+
+        state->mStopped = true;
+      }
+      return InitPromise::CreateAndReject(Move(aResult), __func__);
+    });
+}
+
 void
 SourceListener::Stop()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
 
   if (mStopped) {
     return;
   }
@@ -4001,17 +4054,16 @@ SourceListener::SetEnabledFor(TrackID aT
 
   state.mTrackEnabled = aEnable;
 
   if (state.mStopped) {
     // Device terminally stopped. Updating device state is pointless.
     return;
   }
 
-
   if (state.mOperationInProgress) {
     // If a timer is in progress, it needs to be canceled now so the next
     // DisableTrack() gets a fresh start. Canceling will trigger another
     // operation.
     state.mDisableTimer->Cancel();
     return;
   }
 
@@ -4035,17 +4087,17 @@ SourceListener::SetEnabledFor(TrackID aT
           : "media.getusermedia.camera.off_while_disabled.delay_ms",
         3000));
     timerPromise = state.mDisableTimer->WaitFor(offDelay, __func__);
   }
 
   typedef MozPromise<nsresult, bool, /* IsExclusive = */ true> DeviceOperationPromise;
   RefPtr<SourceListener> self = this;
   timerPromise->Then(GetMainThreadSerialEventTarget(), __func__,
-    [self, this, &state, aTrackID, aEnable](bool aDummy) mutable {
+    [self, this, &state, aTrackID, aEnable]() mutable {
       MOZ_ASSERT(state.mDeviceEnabled != aEnable,
                  "Device operation hasn't started");
       MOZ_ASSERT(state.mOperationInProgress,
                  "It's our responsibility to reset the inProgress state");
 
       LOG(("SourceListener %p %s %s track %d - starting device operation",
            this, aEnable ? "enabling" : "disabling",
            aTrackID == kAudioTrack ? "audio" : "video",
@@ -4064,17 +4116,17 @@ SourceListener::SetEnabledFor(TrackID aT
         return DeviceOperationPromise::CreateAndResolve(NS_OK, __func__);
       }
 
       return MediaManager::PostTask<DeviceOperationPromise>(__func__,
           [self, device = state.mDevice, aEnable]
           (MozPromiseHolder<DeviceOperationPromise>& h) {
             h.Resolve(aEnable ? device->Start() : device->Stop(), __func__);
           });
-    }, [](bool aDummy) {
+    }, []() {
       // Timer was canceled by us. We signal this with NS_ERROR_ABORT.
       return DeviceOperationPromise::CreateAndResolve(NS_ERROR_ABORT, __func__);
     })->Then(GetMainThreadSerialEventTarget(), __func__,
     [self, this, &state, aTrackID, aEnable](nsresult aResult) mutable {
       MOZ_ASSERT(state.mOperationInProgress);
       state.mOperationInProgress = false;
 
       if (state.mStopped) {
@@ -4124,17 +4176,17 @@ SourceListener::SetEnabledFor(TrackID aT
       }
 
       // Track state changed during this operation. We'll start over.
       if (state.mTrackEnabled) {
         SetEnabledFor(aTrackID, true);
       } else {
         SetEnabledFor(aTrackID, false);
       }
-    }, [](bool aDummy) {
+    }, []() {
       MOZ_ASSERT_UNREACHABLE("Unexpected and unhandled reject");
     });
 }
 
 void
 SourceListener::StopSharing()
 {
   MOZ_ASSERT(NS_IsMainThread());