From ea95e39bc95b4f387d0274996ec504c11ccfece5 Mon Sep 17 00:00:00 2001 From: Chris Pearce Date: Tue, 26 Jun 2018 14:16:13 +1200 Subject: [PATCH] Bug 1463919 - Move ask autoplay permission check into AutoplayPolicy. r=jya MozReview-Commit-ID: KJcVI6gtGXw --HG-- extra : rebase_source : a2ccd2da32d77708fdeb6ea6361975a7759cb18d extra : source : 9b1c40f3e61ab351707f2d320ce8f87951e4868e --- dom/html/HTMLMediaElement.cpp | 98 ++++++++++++++--------------------- dom/html/HTMLMediaElement.h | 2 +- dom/media/AutoplayPolicy.cpp | 29 ++++++----- dom/media/AutoplayPolicy.h | 9 +++- 4 files changed, 65 insertions(+), 73 deletions(-) diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp index 3a30ec2a312e..dbf4026dcf89 100644 --- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -1998,7 +1998,7 @@ HTMLMediaElement::Load() HasSourceChildren(this), EventStateManager::IsHandlingUserInput(), HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay), - IsAllowedToPlay(), + AutoplayPolicy::IsAllowedToPlay(*this) == Authorization::Allowed, OwnerDoc(), DocumentOrigin(OwnerDoc()).get(), OwnerDoc() ? OwnerDoc()->HasBeenUserGestureActivated() : 0, @@ -2520,7 +2520,7 @@ 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; @@ -3053,7 +3053,7 @@ HTMLMediaElement::PauseIfShouldNotBePlaying() if (GetPaused()) { return; } - if (!AutoplayPolicy::IsMediaElementAllowedToPlay(WrapNotNull(this))) { + if (AutoplayPolicy::IsAllowedToPlay(*this) != Authorization::Allowed) { ErrorResult rv; Pause(rv); } @@ -4053,26 +4053,32 @@ HTMLMediaElement::Play(ErrorResult& aRv) 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(); } @@ -4089,8 +4095,7 @@ HTMLMediaElement::EnsureAutoplayRequested(bool aHandlingUserInput) return; } - RefPtr request = - AutoplayPolicy::RequestFor(WrapNotNull(OwnerDoc())); + RefPtr request = AutoplayPolicy::RequestFor(*OwnerDoc()); if (!request) { AsyncRejectPendingPlayPromises(NS_ERROR_DOM_INVALID_STATE_ERR); return; @@ -6114,7 +6119,8 @@ HTMLMediaElement::ChangeReadyState(nsMediaReadyState aState) DispatchAsyncEvent(NS_LITERAL_STRING("canplay")); if (!mPaused) { if (mDecoder && !mPausedForInactiveDocumentOrChannel) { - MOZ_ASSERT(IsAllowedToPlay()); + MOZ_ASSERT(AutoplayPolicy::IsAllowedToPlay(*this) == + Authorization::Allowed); mDecoder->Play(); } NotifyAboutPlaying(); @@ -7017,48 +7023,22 @@ HTMLMediaElement::UpdateAudioChannelPlayingState(bool aForcePlaying) } bool -HTMLMediaElement::IsAllowedToPlay() +HTMLMediaElement::AudioChannelAgentBlockedPlay() { - if (!AutoplayPolicy::IsMediaElementAllowedToPlay(WrapNotNull(this))) { -#if defined(MOZ_WIDGET_ANDROID) - nsContentUtils::DispatchChromeEvent( - OwnerDoc(), - static_cast(this), - NS_LITERAL_STRING("MozAutoplayMediaBlocked"), - CanBubble::eNo, - Cancelable::eNo); -#endif + 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* diff --git a/dom/html/HTMLMediaElement.h b/dom/html/HTMLMediaElement.h index 583097aca460..328e3f424b02 100644 --- a/dom/html/HTMLMediaElement.h +++ b/dom/html/HTMLMediaElement.h @@ -1284,7 +1284,7 @@ protected: 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(); diff --git a/dom/media/AutoplayPolicy.cpp b/dom/media/AutoplayPolicy.cpp index 3f6421474489..96d293a99bb0 100644 --- a/dom/media/AutoplayPolicy.cpp +++ b/dom/media/AutoplayPolicy.cpp @@ -40,7 +40,7 @@ ApproverDocOf(const nsIDocument& aDocument) } static bool -IsAllowedToPlay(nsPIDOMWindowInner* aWindow) +IsWindowAllowedToPlay(nsPIDOMWindowInner* aWindow) { if (!aWindow) { return false; @@ -88,31 +88,36 @@ AutoplayPolicy::RequestFor(const nsIDocument& aDocument) return window->GetAutoplayRequest(); } -/* static */ bool -AutoplayPolicy::IsMediaElementAllowedToPlay(NotNull 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 @@ -131,7 +136,7 @@ AutoplayPolicy::IsAudioContextAllowedToPlay(NotNull aContext) return true; } - if (IsAllowedToPlay(aContext->GetOwner())) { + if (IsWindowAllowedToPlay(aContext->GetOwner())) { return true; } diff --git a/dom/media/AutoplayPolicy.h b/dom/media/AutoplayPolicy.h index 9fcbaee44ebe..0f765e76c12f 100644 --- a/dom/media/AutoplayPolicy.h +++ b/dom/media/AutoplayPolicy.h @@ -20,6 +20,13 @@ 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. @@ -36,7 +43,7 @@ class AutoplayPolicy { public: // Returns whether a given media element is allowed to play. - static bool IsMediaElementAllowedToPlay(NotNull aElement); + static Authorization IsAllowedToPlay(const HTMLMediaElement& aElement); // Returns whether a given AudioContext is allowed to play. static bool IsAudioContextAllowedToPlay(NotNull aContext);