Bug 1321410 - stop agent after cycle collection. draft
authorAlastor Wu <alwu@mozilla.com>
Sun, 04 Dec 2016 11:02:10 +0800
changeset 447354 f978058e7283e678c39b3aca54fbb9d85a05ae6c
parent 447335 6bdef7ba8b4108a996b9f61ef9f81c5ea6c93017
child 539017 aac192c3b2f8172cf5d0b8990fe8bcdc3b09178c
push id38041
push useralwu@mozilla.com
push dateSun, 04 Dec 2016 03:02:34 +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,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;