Bug 1420192 - when disable autoplay, allow script calls play() once user triggered load() or seek(). draft
authorAlastor Wu <alwu@mozilla.com>
Mon, 27 Nov 2017 10:55:02 +0800
changeset 703555 53537ad3144777567b72bd34b4288bff8c2d2e3e
parent 703549 da90245d47b17c750560dedb5cbe1973181166e3
child 741808 0695a482bb522d590e65332d7a4f07655c254ff5
push id90855
push useralwu@mozilla.com
push dateMon, 27 Nov 2017 02:55:12 +0000
bugs1420192, 1382574
milestone59.0a1
Bug 1420192 - when disable autoplay, allow script calls play() once user triggered load() or seek(). This patch is mainly reverting the changing of bug1382574 part3, but not all the same. Since youtube would call load() when user clicks to play, and then call play() later. For the old pref (checking user-input-play), we should still allow the following play() even it's not triggered via user input. It's also same for seeking, Youtube would call play() after seeking completed. In this patch, we would allow the script-calling once play() if user has called load() or seek() before that. MozReview-Commit-ID: 1UcxRCVfhnR
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
dom/media/AutoplayPolicy.cpp
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1974,17 +1974,22 @@ void HTMLMediaElement::DoLoad()
     LOG(LogLevel::Debug, ("%p Media not allowed", this));
     return;
   }
 
   if (mIsRunningLoadMethod) {
     return;
   }
 
+  // Detect if user has interacted with element so that play will not be
+  // blocked when initiated by a script. This enables sites to capture user
+  // intent to play by calling load() in the click handler of a "catalog
+  // view" of a gallery of videos.
   if (EventStateManager::IsHandlingUserInput()) {
+    mHasUserInteractedLoadOrSeek = true;
     // Mark the channel as urgent-start when autopaly so that it will play the
     // media from src after loading enough resource.
     if (HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay)) {
       mUseUrgentStartForChannel = true;
     }
   }
 
   SetPlayedOrSeeked(false);
@@ -2744,16 +2749,22 @@ HTMLMediaElement::Seek(double aTime,
   MOZ_ASSERT(!mozilla::IsNaN(aTime));
 
   RefPtr<Promise> promise = CreateDOMPromise(aRv);
 
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
+  // Detect if user has interacted with element by seeking so that
+  // play will not be blocked when initiated by a script.
+  if (EventStateManager::IsHandlingUserInput()) {
+    mHasUserInteractedLoadOrSeek = true;
+  }
+
   StopSuspendingAfterFirstFrame();
 
   if (mSrcStream) {
     // do nothing since media streams have an empty Seekable range.
     promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
     return promise.forget();
   }
 
@@ -4027,16 +4038,17 @@ HTMLMediaElement::HTMLMediaElement(alrea
     mShuttingDown(false),
     mSuspendedForPreloadNone(false),
     mSrcStreamIsPlaying(false),
     mMediaSecurityVerified(false),
     mCORSMode(CORS_NONE),
     mIsEncrypted(false),
     mWaitingForKey(NOT_WAITING_FOR_KEY),
     mDisableVideo(false),
+    mHasUserInteractedLoadOrSeek(false),
     mFirstFrameLoaded(false),
     mDefaultPlaybackStartPosition(0.0),
     mHasSuspendTaint(false),
     mMediaTracksConstructed(false),
     mVisibilityState(Visibility::UNTRACKED),
     mErrorSink(new ErrorSink(this)),
     mAudioChannelWrapper(new AudioChannelAgentCallback(this))
 {
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -736,16 +736,23 @@ public:
   void NotifyCueUpdated(TextTrackCue *aCue) {
     if (mTextTrackManager) {
       mTextTrackManager->NotifyCueUpdated(aCue);
     }
   }
 
   void NotifyCueDisplayStatesChanged();
 
+  bool GetAndClearHasUserInteractedLoadOrSeek()
+  {
+    bool result = mHasUserInteractedLoadOrSeek;
+    mHasUserInteractedLoadOrSeek = false;
+    return result;
+  }
+
   // A method to check whether we are currently playing.
   bool IsCurrentlyPlaying() const;
 
   // Returns true if the media element is being destroyed. Used in
   // dormancy checks to prevent dormant processing for an element
   // that will soon be gone.
   bool IsBeingDestroyed();
 
@@ -1773,16 +1780,20 @@ private:
   TimeDurationAccumulator mPlayTime;
 
   // Total time a video has spent playing while hidden.
   TimeDurationAccumulator mHiddenPlayTime;
 
   // Total time a video has (or would have) spent in video-decode-suspend mode.
   TimeDurationAccumulator mVideoDecodeSuspendTime;
 
+  // True if user has called load() or seek() via user input.
+  // It's *only* use for checking autoplay policy
+  bool mHasUserInteractedLoadOrSeek;
+
   // True if the first frame has been successfully loaded.
   bool mFirstFrameLoaded;
 
   // Media elements also have a default playback start position, which must
   // initially be set to zero seconds. This time is used to allow the element to
   // be seeked even before the media is loaded.
   double mDefaultPlaybackStartPosition;
 
--- a/dom/media/AutoplayPolicy.cpp
+++ b/dom/media/AutoplayPolicy.cpp
@@ -33,13 +33,16 @@ AutoplayPolicy::IsMediaElementAllowedToP
   }
 
   if (Preferences::GetBool("media.autoplay.enabled.user-gestures-needed")) {
     return AutoplayPolicy::IsDocumentAllowedToPlay(aElement->OwnerDoc());
   }
 
   // TODO : this old way would be removed when user-gestures-needed becomes
   // as a default option to block autoplay.
-  return EventStateManager::IsHandlingUserInput();
+  // If user triggers load() or seek() before play(), we would also allow the
+  // following play().
+  return aElement->GetAndClearHasUserInteractedLoadOrSeek() ||
+         EventStateManager::IsHandlingUserInput();
 }
 
 } // namespace dom
 } // namespace mozilla
\ No newline at end of file