Bug 1436694 - MozPromisify device initialization and move it to SourceListener. r=jib
authorAndreas Pehrson <pehrsons@mozilla.com>
Fri, 16 Feb 2018 11:55:27 +0100
changeset 406788 0451fe123f5b9168de34b3f927bb421c8e9864ca
parent 406787 59f19fc034cf7fc035710ff97e5b0a983ca920e6
child 406789 04b165b560de7746ab20988189619bd213c7c01b
push id33580
push userdluca@mozilla.com
push dateTue, 06 Mar 2018 21:54:45 +0000
treeherdermozilla-central@bccdc6842104 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjib
bugs1436694
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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;
@@ -3833,16 +3783,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;
   }
@@ -3965,17 +4018,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;
   }
 
@@ -3999,17 +4051,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",
@@ -4028,17 +4080,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) {
@@ -4088,17 +4140,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());