Bug 1321410 - stop agent after cycle collection. draft
authorAlastor Wu <alwu@mozilla.com>
Fri, 02 Dec 2016 18:33:41 +0800
changeset 447039 eb9e1196840996e5269d48a89506a6da72a6a6e8
parent 445465 8d8846f63b74eb930e48b410730ae088e9bdbee8
child 538934 678afc530a71bb45b78ec78f98f21512e563fd43
push id37950
push useralwu@mozilla.com
push dateFri, 02 Dec 2016 10:34:04 +0000
bugs1321410
milestone53.0a1
Bug 1321410 - stop agent after cycle collection. The crash reason seems the mOwner has been released, so we shouldn't call any method which would call mOwner. The AudioChannelAgentCallback could only be called from two parts, one is from mOwner, another is from AudioChannelService (via AudioChannelAgent). We don't want this class be called after mOwner was released, we should disconnect it from AudioChannelService. Calling NotifyStoppedPlaying() can unregister agent from service, so service won't call agent anymore. Therefore, no one would call AudioChannelAgentCallback after CC happened. MozReview-Commit-ID: 7HY4KpciacB
dom/html/HTMLMediaElement.cpp
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -541,40 +541,43 @@ public:
                             AudioChannel aChannel)
     : mOwner(aOwner)
     , mAudioChannel(aChannel)
     , mAudioChannelVolume(1.0)
     , mPlayingThroughTheAudioChannel(false)
     , mAudioCapturedByWindow(false)
     , mSuspended(nsISuspendedTypes::NONE_SUSPENDED)
     , mIsOwnerAudible(IsOwnerAudible())
+    , mIsShutDown(false)
   {
     MOZ_ASSERT(mOwner);
     MaybeCreateAudioChannelAgent();
   }
 
   void
   UpdateAudioChannelPlayingState(bool aForcePlaying = false)
   {
+    MOZ_ASSERT(!mIsShutDown);
     bool playingThroughTheAudioChannel =
       aForcePlaying || IsPlayingThroughTheAudioChannel();
 
     if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) {
       if (!MaybeCreateAudioChannelAgent()) {
         return;
       }
 
       mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
       NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel);
     }
   }
 
   void
   NotifyPlayStarted()
   {
+    MOZ_ASSERT(!mIsShutDown);
     // Reset the suspend type because the media element might be paused by
     // audio channel before calling play(). eg. paused by Fennec media control,
     // but resumed it from page.
     SetSuspended(nsISuspendedTypes::NONE_SUSPENDED);
     UpdateAudioChannelPlayingState();
   }
 
   NS_IMETHODIMP
@@ -640,46 +643,49 @@ public:
       AudioCaptureStreamChangeIfNeeded();
     }
     return NS_OK;
   }
 
   void
   AudioCaptureStreamChangeIfNeeded()
   {
+    MOZ_ASSERT(!mIsShutDown);
     if (!IsPlayingStarted()) {
       return;
     }
 
     if (!mOwner->HasAudio()) {
       return;
     }
 
     mOwner->AudioCaptureStreamChange(mAudioCapturedByWindow);
   }
 
   void
   NotifyAudioPlaybackChanged(AudibleChangedReasons aReason)
   {
+    MOZ_ASSERT(!mIsShutDown);
     if (!IsPlayingStarted()) {
       return;
     }
 
     bool newAudibleState = IsOwnerAudible();
     if (mIsOwnerAudible == newAudibleState) {
       return;
     }
 
     mIsOwnerAudible = newAudibleState;
     mAudioChannelAgent->NotifyStartedAudible(mIsOwnerAudible, aReason);
   }
 
   bool
   IsAllowedToPlay()
   {
+    MOZ_ASSERT(!mIsShutDown);
     // The media element has already been paused or blocked, so it can't start
     // playback again by script or user's intend until resuming by audio channel.
     if (mSuspended == nsISuspendedTypes::SUSPENDED_PAUSE ||
         mSuspended == nsISuspendedTypes::SUSPENDED_BLOCK) {
       return false;
     }
 
     // If the tab hasn't been activated yet, the media element in that tab can't
@@ -690,30 +696,46 @@ public:
       // channe system because we need to receive the resume notification later.
       UpdateAudioChannelPlayingState(true /* force to start */);
       return false;
     }
 
     return true;
   }
 
+  void
+  Shutdown()
+  {
+    MOZ_ASSERT(!mIsShutDown);
+    if (mAudioChannelAgent) {
+      mAudioChannelAgent->NotifyStoppedPlaying();
+      mAudioChannelAgent = nullptr;
+    }
+    mIsShutDown = true;
+  }
+
   float
   GetEffectiveVolume() const
   {
+    MOZ_ASSERT(!mIsShutDown);
     return mOwner->Volume() * mAudioChannelVolume;
   }
 
   SuspendTypes
   GetSuspendType() const
   {
+    MOZ_ASSERT(!mIsShutDown);
     return mSuspended;
   }
 
 private:
-  ~AudioChannelAgentCallback() {};
+  ~AudioChannelAgentCallback()
+  {
+    MOZ_ASSERT(mIsShutDown);
+  };
 
   bool
   MaybeCreateAudioChannelAgent()
   {
     if (mAudioChannelAgent) {
       return true;
     }
 
@@ -961,16 +983,17 @@ private:
   // be resumed when the page is active. See bug647429 for more details.
   // - SUSPENDED_STOP_DISPOSABLE
   // When we permanently lost platform audio focus, we shuold stop playing
   // and stop the audio channel agent. MediaElement can only be restarted by
   // play().
   SuspendTypes mSuspended;
   // True if media element is audible for users.
   bool mIsOwnerAudible;
+  bool mIsShutDown;
 };
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement::AudioChannelAgentCallback)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(HTMLMediaElement::AudioChannelAgentCallback)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelAgent)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
@@ -1274,16 +1297,19 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
     tmp->EndSrcMediaStreamPlayback();
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcAttrStream)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaSource)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcMediaSource)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourcePointer)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoadBlockedDoc)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceLoadCandidate)
+  if (tmp->mAudioChannelWrapper) {
+    tmp->mAudioChannelWrapper->Shutdown();
+  }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelWrapper)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mErrorSink->mError)
   for (uint32_t i = 0; i < tmp->mOutputStreams.Length(); ++i) {
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputStreams[i].mStream)
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlayed)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextTrackManager)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioTrackList)
@@ -3522,16 +3548,21 @@ HTMLMediaElement::~HTMLMediaElement()
 
   NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
     "Destroyed media element should no longer be in element table");
 
   if (mChannelLoader) {
     mChannelLoader->Cancel();
   }
 
+  if (mAudioChannelWrapper) {
+    mAudioChannelWrapper->Shutdown();
+    mAudioChannelWrapper = nullptr;
+  }
+
   WakeLockRelease();
 }
 
 void HTMLMediaElement::StopSuspendingAfterFirstFrame()
 {
   mAllowSuspendAfterFirstFrame = false;
   if (!mSuspendedAfterFirstFrame)
     return;