Bug 1463919 - Move ask autoplay permission check into AutoplayPolicy. r=jya draft
authorChris Pearce <cpearce@mozilla.com>
Tue, 26 Jun 2018 14:16:13 +1200
changeset 813232 a2ccd2da32d77708fdeb6ea6361975a7759cb18d
parent 813231 f999b9a4a1ae7a5a7f1dd31efd3003e40d7fa102
child 813233 5cf26822c9e5f23a83d69f5f52c39be6ab6f9eb0
push id114832
push userbmo:cpearce@mozilla.com
push dateMon, 02 Jul 2018 19:32:21 +0000
reviewersjya
bugs1463919
milestone63.0a1
Bug 1463919 - Move ask autoplay permission check into AutoplayPolicy. r=jya MozReview-Commit-ID: KJcVI6gtGXw
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
dom/media/AutoplayPolicy.cpp
dom/media/AutoplayPolicy.h
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1993,17 +1993,17 @@ HTMLMediaElement::Load()
        "ownerDoc=%p (%s) ownerDocUserActivated=%d "
        "muted=%d volume=%f",
        this,
        !!mSrcAttrStream,
        HasAttr(kNameSpaceID_None, nsGkAtoms::src),
        HasSourceChildren(this),
        EventStateManager::IsHandlingUserInput(),
        HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay),
-       IsAllowedToPlay(),
+       AutoplayPolicy::IsAllowedToPlay(*this) == Authorization::Allowed,
        OwnerDoc(),
        DocumentOrigin(OwnerDoc()).get(),
        OwnerDoc() ? OwnerDoc()->HasBeenUserGestureActivated() : 0,
        mMuted,
        mVolume));
 
   if (mIsRunningLoadMethod) {
     return;
@@ -2515,17 +2515,17 @@ HTMLMediaElement::ResumeLoad(PreloadActi
 }
 
 void
 HTMLMediaElement::UpdatePreloadAction()
 {
   PreloadAction nextAction = PRELOAD_UNDEFINED;
   // If autoplay is set, or we're playing, we should always preload data,
   // as we'll need it to play.
-  if ((AutoplayPolicy::IsMediaElementAllowedToPlay(WrapNotNull(this)) &&
+  if ((AutoplayPolicy::IsAllowedToPlay(*this) == Authorization::Allowed &&
        HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay)) ||
       !mPaused) {
     nextAction = HTMLMediaElement::PRELOAD_ENOUGH;
   } else {
     // Find the appropriate preload action by looking at the attribute.
     const nsAttrValue* val =
       mAttrsAndChildren.GetAttr(nsGkAtoms::preload, kNameSpaceID_None);
     // MSE doesn't work if preload is none, so it ignores the pref when src is
@@ -3048,17 +3048,17 @@ HTMLMediaElement::SetMutedInternal(uint3
 }
 
 void
 HTMLMediaElement::PauseIfShouldNotBePlaying()
 {
   if (GetPaused()) {
     return;
   }
-  if (!AutoplayPolicy::IsMediaElementAllowedToPlay(WrapNotNull(this))) {
+  if (AutoplayPolicy::IsAllowedToPlay(*this) != Authorization::Allowed) {
     ErrorResult rv;
     Pause(rv);
   }
 }
 
 void
 HTMLMediaElement::SetVolumeInternal()
 {
@@ -4048,54 +4048,59 @@ HTMLMediaElement::Play(ErrorResult& aRv)
     // audio tab indicator is clicked, which will resolve the promise if we end
     // up playing.
     LOG(LogLevel::Debug, ("%p Play() call delayed by AudioChannelAgent", this));
     MaybeDoLoad();
     mPendingPlayPromises.AppendElement(promise);
     return promise.forget();
   }
 
-  const bool handlingUserInput = EventStateManager::IsHandlingUserInput();
-  if (IsAllowedToPlay()) {
-    mPendingPlayPromises.AppendElement(promise);
-    PlayInternal(handlingUserInput);
-    UpdateCustomPolicyAfterPlayed();
-    return promise.forget();
-  }
-
-  // Otherwise, not allowed to play. We may still be allowed to play if we
-  // ask for and are granted permission by the user.
-
-  if (!Preferences::GetBool("media.autoplay.ask-permission", false)) {
-    LOG(LogLevel::Debug, ("%p play not allowed and prompting disabled.", this));
+  if (AudioChannelAgentBlockedPlay()) {
+    LOG(LogLevel::Debug, ("%p play blocked by AudioChannelAgent.", this));
     promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
     return promise.forget();
   }
 
-  // Prompt the user for permission to play.
-  mPendingPlayPromises.AppendElement(promise);
-  EnsureAutoplayRequested(handlingUserInput);
+  const bool handlingUserInput = EventStateManager::IsHandlingUserInput();
+  switch (AutoplayPolicy::IsAllowedToPlay(*this)) {
+    case Authorization::Allowed: {
+      mPendingPlayPromises.AppendElement(promise);
+      PlayInternal(handlingUserInput);
+      UpdateCustomPolicyAfterPlayed();
+      break;
+    }
+    case Authorization::Blocked: {
+      LOG(LogLevel::Debug, ("%p play not blocked.", this));
+      promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
+      break;
+    }
+    case Authorization::Prompt: {
+      // Prompt the user for permission to play.
+      mPendingPlayPromises.AppendElement(promise);
+      EnsureAutoplayRequested(handlingUserInput);
+      break;
+    }
+  }
   return promise.forget();
 }
 
 void
 HTMLMediaElement::EnsureAutoplayRequested(bool aHandlingUserInput)
 {
   if (mAutoplayPermissionRequest.Exists()) {
     // Autoplay has already been requested in a previous play() call.
     // Await for the previous request to be approved or denied. This
     // play request's promise will be fulfilled with all other pending
     // promises when the permission prompt is resolved.
     LOG(LogLevel::Debug,
         ("%p EnsureAutoplayRequested() existing request, bailing.", this));
     return;
   }
 
-  RefPtr<AutoplayRequest> request =
-    AutoplayPolicy::RequestFor(WrapNotNull(OwnerDoc()));
+  RefPtr<AutoplayRequest> request = AutoplayPolicy::RequestFor(*OwnerDoc());
   if (!request) {
     AsyncRejectPendingPlayPromises(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
   RefPtr<HTMLMediaElement> self = this;
   request->RequestWithPrompt()
     ->Then(mAbstractMainThread,
            __func__,
@@ -6109,17 +6114,18 @@ HTMLMediaElement::ChangeReadyState(nsMed
     DispatchAsyncEvent(NS_LITERAL_STRING("loadeddata"));
     mLoadedDataFired = true;
   }
 
   if (oldState < HAVE_FUTURE_DATA && mReadyState >= HAVE_FUTURE_DATA) {
     DispatchAsyncEvent(NS_LITERAL_STRING("canplay"));
     if (!mPaused) {
       if (mDecoder && !mPausedForInactiveDocumentOrChannel) {
-        MOZ_ASSERT(IsAllowedToPlay());
+        MOZ_ASSERT(AutoplayPolicy::IsAllowedToPlay(*this) ==
+                   Authorization::Allowed);
         mDecoder->Play();
       }
       NotifyAboutPlaying();
     }
   }
 
   CheckAutoplayDataReady();
 
@@ -7012,58 +7018,32 @@ void
 HTMLMediaElement::UpdateAudioChannelPlayingState(bool aForcePlaying)
 {
   if (mAudioChannelWrapper) {
     mAudioChannelWrapper->UpdateAudioChannelPlayingState(aForcePlaying);
   }
 }
 
 bool
-HTMLMediaElement::IsAllowedToPlay()
-{
-  if (!AutoplayPolicy::IsMediaElementAllowedToPlay(WrapNotNull(this))) {
-#if defined(MOZ_WIDGET_ANDROID)
-    nsContentUtils::DispatchChromeEvent(
-      OwnerDoc(),
-      static_cast<nsIContent*>(this),
-      NS_LITERAL_STRING("MozAutoplayMediaBlocked"),
-      CanBubble::eNo,
-      Cancelable::eNo);
-#endif
+HTMLMediaElement::AudioChannelAgentBlockedPlay()
+{
+  if (!mAudioChannelWrapper) {
+    // If the mAudioChannelWrapper doesn't exist that means the CC happened.
     LOG(LogLevel::Debug,
-        ("%p %s AutoplayPolicy blocked autoplay.", this, __func__));
-    return false;
-  }
-
-  LOG(LogLevel::Debug,
-      ("%p %s AutoplayPolicy did not block autoplay.", this, __func__));
-
-  // Check our custom playback policy.
-  if (mAudioChannelWrapper) {
-    // Note: SUSPENDED_PAUSE and SUSPENDED_BLOCK will be merged into one single
-    // state.
-    if (mAudioChannelWrapper->GetSuspendType() ==
-          nsISuspendedTypes::SUSPENDED_PAUSE ||
-        mAudioChannelWrapper->GetSuspendType() ==
-          nsISuspendedTypes::SUSPENDED_BLOCK) {
-      LOG(LogLevel::Debug,
-          ("%p IsAllowedToPlay() returning false due to AudioChannelAgent.",
-           this));
-      return false;
-    }
-
-    LOG(LogLevel::Debug, ("%p IsAllowedToPlay() returning true.", this));
+        ("%p AudioChannelAgentBlockedPlay() returning true due to null "
+         "AudioChannelAgent.",
+         this));
     return true;
   }
 
-  // If the mAudioChannelWrapper doesn't exist that means the CC happened.
-  LOG(LogLevel::Debug,
-      ("%p IsAllowedToPlay() returning false due to null AudioChannelAgent.",
-       this));
-  return false;
+  // Note: SUSPENDED_PAUSE and SUSPENDED_BLOCK will be merged into one single
+  // state.
+  const auto suspendType = mAudioChannelWrapper->GetSuspendType();
+  return suspendType == nsISuspendedTypes::SUSPENDED_PAUSE ||
+         suspendType == nsISuspendedTypes::SUSPENDED_BLOCK;
 }
 
 static const char*
 VisibilityString(Visibility aVisibility)
 {
   switch (aVisibility) {
     case Visibility::UNTRACKED: {
       return "UNTRACKED";
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1279,17 +1279,17 @@ protected:
 
   // Determine if the element should be paused because of suspend conditions.
   bool ShouldElementBePaused();
 
   // Create or destroy the captured stream.
   void AudioCaptureStreamChange(bool aCapture);
 
   // A method to check whether the media element is allowed to start playback.
-  bool IsAllowedToPlay();
+  bool AudioChannelAgentBlockedPlay();
 
   // If the network state is empty and then we would trigger DoLoad().
   void MaybeDoLoad();
 
   // Anything we need to check after played success and not related with spec.
   void UpdateCustomPolicyAfterPlayed();
 
   // Returns a StreamCaptureType populated with the right bits, depending on the
--- a/dom/media/AutoplayPolicy.cpp
+++ b/dom/media/AutoplayPolicy.cpp
@@ -35,17 +35,17 @@ ApproverDocOf(const nsIDocument& aDocume
   if (!rootTreeItem) {
     return nullptr;
   }
 
   return rootTreeItem->GetDocument();
 }
 
 static bool
-IsAllowedToPlay(nsPIDOMWindowInner* aWindow)
+IsWindowAllowedToPlay(nsPIDOMWindowInner* aWindow)
 {
   if (!aWindow) {
     return false;
   }
 
   // Pages which have been granted permission to capture WebRTC camera or
   // microphone are assumed to be trusted, and are allowed to autoplay.
   MediaManager* manager = MediaManager::GetIfExists();
@@ -83,41 +83,46 @@ AutoplayPolicy::RequestFor(const nsIDocu
   }
   nsPIDOMWindowInner* window = document->GetInnerWindow();
   if (!window) {
     return nullptr;
   }
   return window->GetAutoplayRequest();
 }
 
-/* static */ bool
-AutoplayPolicy::IsMediaElementAllowedToPlay(NotNull<HTMLMediaElement*> aElement)
+/* static */ Authorization
+AutoplayPolicy::IsAllowedToPlay(const HTMLMediaElement& aElement)
 {
   if (Preferences::GetBool("media.autoplay.enabled")) {
-    return true;
+    return Authorization::Allowed;
   }
 
   // TODO : this old way would be removed when user-gestures-needed becomes
   // as a default option to block autoplay.
   if (!Preferences::GetBool("media.autoplay.enabled.user-gestures-needed", false)) {
     // If element is blessed, it would always be allowed to play().
-    return aElement->IsBlessed() ||
-           EventStateManager::IsHandlingUserInput();
+    return (aElement.IsBlessed() || EventStateManager::IsHandlingUserInput())
+             ? Authorization::Allowed
+             : Authorization::Blocked;
   }
 
   // Muted content
-  if (aElement->Volume() == 0.0 || aElement->Muted()) {
-    return true;
+  if (aElement.Volume() == 0.0 || aElement.Muted()) {
+    return Authorization::Allowed;
   }
 
-  if (IsAllowedToPlay(aElement->OwnerDoc()->GetInnerWindow())) {
-    return true;
+  if (IsWindowAllowedToPlay(aElement.OwnerDoc()->GetInnerWindow())) {
+    return Authorization::Allowed;
   }
 
-  return false;
+  if (Preferences::GetBool("media.autoplay.ask-permission", false)) {
+    return Authorization::Prompt;
+  }
+
+  return Authorization::Blocked;
 }
 
 /* static */ bool
 AutoplayPolicy::IsAudioContextAllowedToPlay(NotNull<AudioContext*> aContext)
 {
   if (Preferences::GetBool("media.autoplay.enabled")) {
     return true;
   }
@@ -126,17 +131,17 @@ AutoplayPolicy::IsAudioContextAllowedToP
     return true;
   }
 
   // Offline context won't directly output sound to audio devices.
   if (aContext->IsOffline()) {
     return true;
   }
 
-  if (IsAllowedToPlay(aContext->GetOwner())) {
+  if (IsWindowAllowedToPlay(aContext->GetOwner())) {
     return true;
   }
 
   return false;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/AutoplayPolicy.h
+++ b/dom/media/AutoplayPolicy.h
@@ -15,33 +15,40 @@ namespace mozilla {
 
 class AutoplayRequest;
 
 namespace dom {
 
 class HTMLMediaElement;
 class AudioContext;
 
+enum class Authorization
+{
+  Allowed,
+  Blocked,
+  Prompt
+};
+
 /**
  * AutoplayPolicy is used to manage autoplay logic for all kinds of media,
  * including MediaElement, Web Audio and Web Speech.
  *
  * Autoplay could be disable by turn off the pref "media.autoplay.enabled".
  * Once user disable autoplay, media could only be played if one of following
  * conditions is true.
  * 1) Owner document is activated by user gestures
  *    We restrict user gestures to "mouse click", "keyboard press" and "touch".
  * 2) Muted media content or video without audio content.
  * 3) Document's origin has the "autoplay-media" permission.
  */
 class AutoplayPolicy
 {
 public:
   // Returns whether a given media element is allowed to play.
-  static bool IsMediaElementAllowedToPlay(NotNull<HTMLMediaElement*> aElement);
+  static Authorization IsAllowedToPlay(const HTMLMediaElement& aElement);
 
   // Returns whether a given AudioContext is allowed to play.
   static bool IsAudioContextAllowedToPlay(NotNull<AudioContext*> aContext);
 
   // Returns the AutoplayRequest that a given document must request on
   // for autoplay permission.
   static already_AddRefed<AutoplayRequest> RequestFor(
     const nsIDocument& aDocument);