Bug 1463919 - Move ask autoplay permission check into AutoplayPolicy. r=jya
MozReview-Commit-ID: KJcVI6gtGXw
--- 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);