Bug 1321410 - stop agent after cycle collection. r=jwwang
authorAlastor Wu <alwu@mozilla.com>
Sun, 04 Dec 2016 11:02:10 +0800
changeset 325241 4fe28bb060cf815e9ec038644a35d77c7462f5d0
parent 325240 60f747f5ce44e6d9aa9c2ffdf004e0187b447f2b
child 325242 96c519a83bf03d4f54a63113fbd6f7548b9df111
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
reviewersjwwang
bugs1321410
milestone53.0a1
Bug 1321410 - stop agent after cycle collection. r=jwwang 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,73 +643,92 @@ 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
   IsPlaybackBlocked()
   {
+    MOZ_ASSERT(!mIsShutDown);
     // If the tab hasn't been activated yet, the media element in that tab can't
     // be playback now until the tab goes to foreground first time or user clicks
     // the unblocking tab icon.
     if (!IsTabActivated()) {
       // Even we haven't start playing yet, we still need to notify the audio
       // channe system because we need to receive the resume notification later.
       UpdateAudioChannelPlayingState(true /* force to start */);
       return true;
     }
 
     return false;
   }
 
+  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;
     }
 
@@ -954,16 +976,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 should 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
 
@@ -1267,16 +1290,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)
@@ -3515,16 +3541,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;