Bug 1301055 - part2 : create a error sink to handle media element's error related event. draft
authorAlastor Wu <alwu@mozilla.com>
Thu, 24 Nov 2016 10:24:04 +0800
changeset 443244 bc90782a78a0e2f14773a66714f9192e61970ec9
parent 443243 5042392bd1939706a8eb947f16f32da7bba8c363
child 443245 cd018dfc828616aa925c6a8a62507ae537d8c506
push id36935
push useralwu@mozilla.com
push dateThu, 24 Nov 2016 03:29:08 +0000
bugs1301055
milestone53.0a1
Bug 1301055 - part2 : create a error sink to handle media element's error related event. Error sink would be response for the error handling, we could write different error dispatching stragedies for different situation. eg. if we can play unsupported type media with external app, we won't dispatch the "error" event on fennec. MozReview-Commit-ID: Lm4x9ksspAY
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -686,31 +686,91 @@ private:
   // Once the decoder is created, control over the channel passes to the
   // decoder, and we null out this reference. We must store this in case
   // we need to cancel the channel before control of it passes to the decoder.
   nsCOMPtr<nsIChannel> mChannel;
 
   bool mCancelled = false;
 };
 
+class HTMLMediaElement::ErrorSink
+{
+public:
+  explicit ErrorSink(HTMLMediaElement* aOwner)
+    : mOwner(aOwner)
+  {
+    MOZ_ASSERT(mOwner);
+  }
+
+  void SetError(uint16_t aErrorCode, const nsACString& aErrorDetails)
+  {
+    // Since we have multiple paths calling into DecodeError, e.g.
+    // MediaKeys::Terminated and EMEH264Decoder::Error. We should take the 1st
+    // one only in order not to fire multiple 'error' events.
+    if (mError) {
+      return;
+    }
+
+    if (!IsValidErrorCode(aErrorCode)) {
+      NS_ASSERTION(false, "Undefined MediaError codes!");
+      return;
+    }
+
+    mError = new MediaError(mOwner, aErrorCode, aErrorDetails);
+    mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("error"));
+    if (mOwner->ReadyState() == HAVE_NOTHING &&
+        aErrorCode == MEDIA_ERR_ABORTED) {
+      // https://html.spec.whatwg.org/multipage/embedded-content.html#media-data-processing-steps-list
+      // "If the media data fetching process is aborted by the user"
+      mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("abort"));
+      mOwner->ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY);
+      mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("emptied"));
+    } else if (aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED) {
+      mOwner->ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE);
+    } else {
+      mOwner->ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_IDLE);
+    }
+  }
+
+  void ResetError()
+  {
+    mError = nullptr;
+  }
+
+  RefPtr<MediaError> mError;
+
+private:
+  bool IsValidErrorCode(const uint16_t& aErrorCode) const
+  {
+    return (aErrorCode == MEDIA_ERR_DECODE  ||
+            aErrorCode == MEDIA_ERR_NETWORK ||
+            aErrorCode == MEDIA_ERR_ABORTED ||
+            aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED);
+  }
+
+  // Media elememt's life cycle would be longer than error sink, so we use the
+  // raw pointer and this class would only be referenced by media element.
+  HTMLMediaElement* mOwner;
+};
+
 NS_IMPL_ADDREF_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
 NS_IMPL_RELEASE_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaSource)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcMediaSource)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcStream)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcAttrStream)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourcePointer)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoadBlockedDoc)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceLoadCandidate)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelAgent)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mErrorSink->mError)
   for (uint32_t i = 0; i < tmp->mOutputStreams.Length(); ++i) {
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputStreams[i].mStream);
   }
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlayed);
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextTrackManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioTrackList)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys)
@@ -725,17 +785,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
   }
   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)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelAgent)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
+  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)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys)
@@ -960,17 +1020,17 @@ void HTMLMediaElement::AbortExistingLoad
   mMediaSource = nullptr;
 
   if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING ||
       mNetworkState == nsIDOMHTMLMediaElement::NETWORK_IDLE)
   {
     DispatchAsyncEvent(NS_LITERAL_STRING("abort"));
   }
 
-  mError = nullptr;
+  mErrorSink->ResetError();
   mCurrentPlayRangeStart = -1.0;
   mLoadedDataFired = false;
   mAutoplaying = true;
   mIsLoadingFromSourceChildren = false;
   mSuspendedAfterFirstFrame = false;
   mAllowSuspendAfterFirstFrame = true;
   mHaveQueuedSelectResource = false;
   mSuspendedForPreloadNone = false;
@@ -1021,22 +1081,19 @@ void HTMLMediaElement::AbortExistingLoad
   mPendingEvents.Clear();
 }
 
 void HTMLMediaElement::NoSupportedMediaSourceError(const nsACString& aErrorDetails)
 {
   if (mDecoder) {
     ShutdownDecoder();
   }
-  mError = new MediaError(this, MEDIA_ERR_SRC_NOT_SUPPORTED, aErrorDetails);
-  ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE);
-  DispatchAsyncEvent(NS_LITERAL_STRING("error"));
+  mErrorSink->SetError(MEDIA_ERR_SRC_NOT_SUPPORTED, aErrorDetails);
   ChangeDelayLoadStatus(false);
   UpdateAudioChannelPlayingState();
-  OpenUnsupportedMediaWithExtenalAppIfNeeded();
 }
 
 typedef void (HTMLMediaElement::*SyncSectionFn)();
 
 // Runs a "synchronous section", a function that must run once the event loop
 // has reached a "stable state". See:
 // http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section
 class nsSyncSection : public nsMediaEvent
@@ -2925,17 +2982,18 @@ HTMLMediaElement::HTMLMediaElement(alrea
     mAudioChannelVolume(1.0),
     mPlayingThroughTheAudioChannel(false),
     mDisableVideo(false),
     mHasUserInteraction(false),
     mFirstFrameLoaded(false),
     mDefaultPlaybackStartPosition(0.0),
     mIsAudioTrackAudible(false),
     mAudible(IsAudible()),
-    mVisibilityState(Visibility::APPROXIMATELY_NONVISIBLE)
+    mVisibilityState(Visibility::APPROXIMATELY_NONVISIBLE),
+    mErrorSink(new ErrorSink(this))
 {
   ErrorResult rv;
 
   double defaultVolume = Preferences::GetFloat("media.default_volume", 1.0);
   SetVolume(defaultVolume, rv);
 
   mAudioChannel = AudioChannelService::GetDefaultAudioChannel();
 
@@ -4424,17 +4482,17 @@ void HTMLMediaElement::DecodeError(const
   nsAutoString src;
   GetCurrentSrc(src);
   const char16_t* params[] = { src.get() };
   ReportLoadError("MediaLoadDecodeError", params, ArrayLength(params));
 
   AudioTracks()->EmptyTracks();
   VideoTracks()->EmptyTracks();
   if (mIsLoadingFromSourceChildren) {
-    mError = nullptr;
+    mErrorSink->ResetError();
     if (mSourceLoadCandidate) {
       DispatchAsyncSourceError(mSourceLoadCandidate);
       QueueLoadFromSourceTask();
     } else {
       NS_WARNING("Should know the source we were loading from!");
     }
   } else if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
     NoSupportedMediaSourceError(aError.Description());
@@ -4451,39 +4509,17 @@ bool HTMLMediaElement::HasError() const
 void HTMLMediaElement::LoadAborted()
 {
   Error(MEDIA_ERR_ABORTED);
 }
 
 void HTMLMediaElement::Error(uint16_t aErrorCode,
                              const nsACString& aErrorDetails)
 {
-  NS_ASSERTION(aErrorCode == MEDIA_ERR_DECODE ||
-               aErrorCode == MEDIA_ERR_NETWORK ||
-               aErrorCode == MEDIA_ERR_ABORTED,
-               "Only use MediaError codes!");
-
-  // Since we have multiple paths calling into DecodeError, e.g.
-  // MediaKeys::Terminated and EMEH264Decoder::Error. We should take the 1st
-  // one only in order not to fire multiple 'error' events.
-  if (mError) {
-    return;
-  }
-  mError = new MediaError(this, aErrorCode, aErrorDetails);
-
-  DispatchAsyncEvent(NS_LITERAL_STRING("error"));
-  if (mReadyState == HAVE_NOTHING && aErrorCode == MEDIA_ERR_ABORTED) {
-    // https://html.spec.whatwg.org/multipage/embedded-content.html#media-data-processing-steps-list
-    // "If the media data fetching process is aborted by the user"
-    DispatchAsyncEvent(NS_LITERAL_STRING("abort"));
-    ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY);
-    DispatchAsyncEvent(NS_LITERAL_STRING("emptied"));
-  } else {
-    ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_IDLE);
-  }
+  mErrorSink->SetError(aErrorCode, aErrorDetails);
   ChangeDelayLoadStatus(false);
   UpdateAudioChannelPlayingState();
 }
 
 void HTMLMediaElement::PlaybackEnded()
 {
   // We changed state which can affect AddRemoveSelfReference
   AddRemoveSelfReference();
@@ -4880,17 +4916,17 @@ void HTMLMediaElement::ChangeReadyState(
   // paused for user interaction, or paused for in-band content, the user agent
   // must queue a task to fire a simple event named timeupdate at the element,
   // and queue a task to fire a simple event named waiting at the element."
   if (mPlayingBeforeSeek &&
       mReadyState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) {
     DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
   } else if (oldState >= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
              mReadyState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
-             !Paused() && !Ended() && !mError) {
+             !Paused() && !Ended() && !mErrorSink->mError) {
     FireTimeUpdate(false);
     DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
   }
 
   if (oldState < nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
       mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
       !mLoadedDataFired) {
     DispatchAsyncEvent(NS_LITERAL_STRING("loadeddata"));
@@ -4941,17 +4977,18 @@ void HTMLMediaElement::ChangeNetworkStat
     StopProgress();
   }
 
   if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING) {
     // Download is begun.
     mBegun = true;
     // Start progress notification when entering NETWORK_LOADING.
     StartProgress();
-  } else if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_IDLE && !mError) {
+  } else if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_IDLE &&
+             !mErrorSink->mError) {
     // Fire 'suspend' event when entering NETWORK_IDLE and no error presented.
     DispatchAsyncEvent(NS_LITERAL_STRING("suspend"));
   }
 
   // Changing mNetworkState affects AddRemoveSelfReference().
   AddRemoveSelfReference();
 }
 
@@ -5641,16 +5678,22 @@ void HTMLMediaElement::FireTimeUpdate(bo
 MediaStream* HTMLMediaElement::GetSrcMediaStream() const
 {
   if (!mSrcStream) {
     return nullptr;
   }
   return mSrcStream->GetPlaybackStream();
 }
 
+MediaError*
+HTMLMediaElement::GetError() const
+{
+  return mErrorSink->mError;
+}
+
 void HTMLMediaElement::GetCurrentSpec(nsCString& aString)
 {
   if (mLoadingSrc) {
     mLoadingSrc->GetSpec(aString);
   } else {
     aString.Truncate();
   }
 }
@@ -5791,17 +5834,17 @@ HTMLMediaElement::MaybeCreateAudioChanne
 
   return true;
 }
 
 bool
 HTMLMediaElement::IsPlayingThroughTheAudioChannel() const
 {
   // If we have an error, we are not playing.
-  if (mError) {
+  if (mErrorSink->mError) {
     return false;
   }
 
   // It might be resumed from remote, we should keep the audio channel agent.
   if (IsSuspendedByAudioChannel()) {
     return true;
   }
 
@@ -6553,21 +6596,22 @@ HTMLMediaElement::MaybeNotifyMediaResume
                                      "media-playback-resumed",
                                      u"active");
   }));
 }
 
 bool
 HTMLMediaElement::HaveFailedWithSourceNotSupportedError() const
 {
-  if (!mError) {
+  const MediaError* error = mErrorSink->mError;
+  if (!error) {
     return false;
   }
 
-  uint16_t errorCode = mError->Code();
+  uint16_t errorCode = error->Code();
   return (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE &&
           errorCode == MEDIA_ERR_SRC_NOT_SUPPORTED);
 }
 
 void
 HTMLMediaElement::OpenUnsupportedMediaWithExtenalAppIfNeeded()
 {
   if (!Preferences::GetBool("media.openUnsupportedTypeWithExternalApp")) {
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -416,20 +416,17 @@ public:
    * This will return null if mSrcStream is null, or if mSrcStream is not
    * null but its GetPlaybackStream() returns null --- which can happen during
    * cycle collection unlinking!
    */
   MediaStream* GetSrcMediaStream() const;
 
   // WebIDL
 
-  MediaError* GetError() const
-  {
-    return mError;
-  }
+  MediaError* GetError() const;
 
   // XPCOM GetSrc() is OK
   void SetSrc(const nsAString& aSrc, ErrorResult& aRv)
   {
     SetHTMLAttr(nsGkAtoms::src, aSrc, aRv);
   }
 
   // XPCOM GetCurrentSrc() is OK
@@ -756,16 +753,17 @@ public:
     CAPTURE_STREAM,
   };
   void MarkAsContentSource(CallerAPI aAPI);
 
 protected:
   virtual ~HTMLMediaElement();
 
   class ChannelLoader;
+  class ErrorSink;
   class MediaLoadListener;
   class MediaStreamTracksAvailableCallback;
   class MediaStreamTrackListener;
   class StreamListener;
   class StreamSizeListener;
   class ShutdownObserver;
 
   MediaDecoderOwner::NextFrameStatus NextFrameStatus();
@@ -1352,19 +1350,16 @@ protected:
   // Holds a reference to the MediaSource supplying data for playback.  This
   // may either match mSrcMediaSource or come from Source element children.
   // This is set when and only when mLoadingSrc corresponds to an object url
   // that resolved to a MediaSource.
   RefPtr<MediaSource> mMediaSource;
 
   RefPtr<ChannelLoader> mChannelLoader;
 
-  // Error attribute
-  RefPtr<MediaError> mError;
-
   // The current media load ID. This is incremented every time we start a
   // new load. Async events note the ID when they're first sent, and only fire
   // if the ID is unchanged when they come to fire.
   uint32_t mCurrentLoadID;
 
   // Points to the child source elements, used to iterate through the children
   // when selecting a resource to load.
   RefPtr<nsRange> mSourcePointer;
@@ -1740,14 +1735,16 @@ private:
 
   // True if the audio track is not silent.
   bool mIsAudioTrackAudible;
 
   // True if media element is audible for users.
   bool mAudible;
 
   Visibility mVisibilityState;
+
+  UniquePtr<ErrorSink> mErrorSink;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_HTMLMediaElement_h