From db2a9a2552265f408f8555e88af091a41eb10fa8 Mon Sep 17 00:00:00 2001 From: alwu Date: Mon, 19 Dec 2022 20:57:55 +0000 Subject: [PATCH] Bug 1773551 - part2 : implement the navigator autoplay policy API. r=media-playback-reviewers,webidl,smaug,padenot Differential Revision: https://phabricator.services.mozilla.com/D164750 --- dom/base/Navigator.cpp | 23 ++++- dom/base/Navigator.h | 9 ++ dom/media/autoplay/AutoplayPolicy.cpp | 105 +++++++++++++++++++++-- dom/media/autoplay/AutoplayPolicy.h | 14 +++ dom/webidl/Navigator.webidl | 24 ++++++ modules/libpref/init/StaticPrefList.yaml | 6 ++ 6 files changed, 174 insertions(+), 7 deletions(-) diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp index b3200900ea2b..270e439b2c3d 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -91,7 +91,6 @@ #include "nsJSUtils.h" -#include "mozilla/dom/NavigatorBinding.h" #include "mozilla/dom/Promise.h" #include "nsIUploadChannel2.h" @@ -113,6 +112,9 @@ #include "mozilla/dom/WindowGlobalChild.h" #include "mozilla/intl/LocaleService.h" +#include "mozilla/dom/AudioContext.h" +#include "mozilla/dom/HTMLMediaElement.h" +#include "AutoplayPolicy.h" namespace mozilla::dom { @@ -2271,4 +2273,23 @@ bool Navigator::Webdriver() { return false; } +AutoplayPolicy Navigator::GetAutoplayPolicy(AutoplayPolicyMediaType aType) { + if (!mWindow) { + return AutoplayPolicy::Disallowed; + } + nsCOMPtr doc = mWindow->GetExtantDoc(); + if (!doc) { + return AutoplayPolicy::Disallowed; + } + return media::AutoplayPolicy::GetAutoplayPolicy(aType, *doc); +} + +AutoplayPolicy Navigator::GetAutoplayPolicy(HTMLMediaElement& aElement) { + return media::AutoplayPolicy::GetAutoplayPolicy(aElement); +} + +AutoplayPolicy Navigator::GetAutoplayPolicy(AudioContext& aContext) { + return media::AutoplayPolicy::GetAutoplayPolicy(aContext); +} + } // namespace mozilla::dom diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h index fd4b1276ab05..9f6b85ecfac7 100644 --- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h @@ -11,6 +11,7 @@ #include "mozilla/dom/AddonManagerBinding.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/Fetch.h" +#include "mozilla/dom/NavigatorBinding.h" #include "mozilla/dom/Nullable.h" #include "nsWrapperCache.h" #include "nsHashKeys.h" @@ -43,6 +44,8 @@ class DOMRequest; class CredentialsContainer; class Clipboard; class LockManager; +class HTMLMediaElement; +class AudioContext; } // namespace dom namespace webgpu { class Instance; @@ -244,6 +247,12 @@ class Navigator final : public nsISupports, public nsWrapperCache { bool HasCreatedMediaSession() const; + // Following methods are for the Autoplay Policy Detection API. + // https://w3c.github.io/autoplay/#autoplay-detection-methods + AutoplayPolicy GetAutoplayPolicy(AutoplayPolicyMediaType aType); + AutoplayPolicy GetAutoplayPolicy(HTMLMediaElement& aElement); + AutoplayPolicy GetAutoplayPolicy(AudioContext& aContext); + private: void ValidateShareData(const ShareData& aData, ErrorResult& aRv); RefPtr mMediaKeySystemAccessManager; diff --git a/dom/media/autoplay/AutoplayPolicy.cpp b/dom/media/autoplay/AutoplayPolicy.cpp index 0a6fa8a25f7f..7e983704cf61 100644 --- a/dom/media/autoplay/AutoplayPolicy.cpp +++ b/dom/media/autoplay/AutoplayPolicy.cpp @@ -11,6 +11,7 @@ #include "mozilla/dom/FeaturePolicyUtils.h" #include "mozilla/dom/HTMLMediaElement.h" #include "mozilla/dom/HTMLMediaElementBinding.h" +#include "mozilla/dom/NavigatorBinding.h" #include "mozilla/dom/UserActivation.h" #include "mozilla/dom/WindowContext.h" #include "mozilla/Logging.h" @@ -192,7 +193,8 @@ static bool IsGVAutoplayRequestAllowed(nsPIDOMWindowInner* aWindow, return status == GVAutoplayRequestStatus::eALLOWED; } -static bool IsGVAutoplayRequestAllowed(const HTMLMediaElement& aElement) { +static bool IsGVAutoplayRequestAllowed(const HTMLMediaElement& aElement, + RType aType) { // On GV, blocking model is the first thing we would check inside Gecko, and // if the media is not allowed by that, then we would check the response from // the embedding app to decide the final result. @@ -204,17 +206,16 @@ static bool IsGVAutoplayRequestAllowed(const HTMLMediaElement& aElement) { if (!window) { return false; } - - const RType type = - IsMediaElementInaudible(aElement) ? RType::eINAUDIBLE : RType::eAUDIBLE; - return IsGVAutoplayRequestAllowed(window, type); + return IsGVAutoplayRequestAllowed(window, aType); } #endif static bool IsAllowedToPlayInternal(const HTMLMediaElement& aElement) { #if defined(MOZ_WIDGET_ANDROID) if (StaticPrefs::media_geckoview_autoplay_request()) { - return IsGVAutoplayRequestAllowed(aElement); + return IsGVAutoplayRequestAllowed( + aElement, IsMediaElementInaudible(aElement) ? RType::eINAUDIBLE + : RType::eAUDIBLE); } #endif bool isInaudible = IsMediaElementInaudible(aElement); @@ -359,4 +360,96 @@ bool AutoplayPolicyTelemetryUtils::WouldBeAllowedToPlayIfAutoplayDisabled( return IsAudioContextAllowedToPlay(aContext); } +/* static */ +dom::AutoplayPolicy AutoplayPolicy::GetAutoplayPolicy( + const dom::HTMLMediaElement& aElement) { + // Note, the site permission can contain following values : + // - UNKNOWN_ACTION : no permission set for this site + // - ALLOW_ACTION : allowed to autoplay + // - DENY_ACTION : allowed inaudible autoplay, disallowed inaudible autoplay + // - nsIAutoplay::BLOCKED_ALL : autoplay disallowed + // and the global permissions would be nsIAutoplay::{BLOCKED, ALLOWED, + // BLOCKED_ALL} + const uint32_t sitePermission = + SiteAutoplayPerm(aElement.OwnerDoc()->GetInnerWindow()); + const uint32_t globalPermission = DefaultAutoplayBehaviour(); + const bool isAllowedToPlayByBlockingModel = + IsAllowedToPlayByBlockingModel(aElement); + + AUTOPLAY_LOG( + "IsAllowedToPlay(element), sitePermission=%d, globalPermission=%d, " + "isAllowedToPlayByBlockingModel=%d", + sitePermission, globalPermission, isAllowedToPlayByBlockingModel); + +#if defined(MOZ_WIDGET_ANDROID) + if (StaticPrefs::media_geckoview_autoplay_request()) { + if (IsGVAutoplayRequestAllowed(aElement, RType::eAUDIBLE)) { + return dom::AutoplayPolicy::Allowed; + } else if (IsGVAutoplayRequestAllowed(aElement, RType::eINAUDIBLE)) { + return isAllowedToPlayByBlockingModel + ? dom::AutoplayPolicy::Allowed + : dom::AutoplayPolicy::Allowed_muted; + } else { + return isAllowedToPlayByBlockingModel ? dom::AutoplayPolicy::Allowed + : dom::AutoplayPolicy::Disallowed; + } + } +#endif + + // These are situations when an element is allowed to autoplay + // 1. The site permission is explicitly allowed + // 2. The global permission is allowed, and the site isn't explicitly + // disallowed + // 3. The blocking model is explicitly allowed this element + if (sitePermission == nsIPermissionManager::ALLOW_ACTION || + (globalPermission == nsIAutoplay::ALLOWED && + (sitePermission != nsIPermissionManager::DENY_ACTION && + sitePermission != nsIAutoplay::BLOCKED_ALL)) || + isAllowedToPlayByBlockingModel) { + return dom::AutoplayPolicy::Allowed; + } + + // These are situations when a element is allowed to autoplay only when it's + // inaudible. + // 1. The site permission is block-audible-autoplay + // 2. The global permission is block-audible-autoplay, and the site permission + // isn't block-all-autoplay + if (sitePermission == nsIPermissionManager::DENY_ACTION || + (globalPermission == nsIAutoplay::BLOCKED && + sitePermission != nsIAutoplay::BLOCKED_ALL)) { + return dom::AutoplayPolicy::Allowed_muted; + } + + return dom::AutoplayPolicy::Disallowed; +} + +/* static */ +dom::AutoplayPolicy AutoplayPolicy::GetAutoplayPolicy( + const dom::AudioContext& aContext) { + if (AutoplayPolicy::IsAllowedToPlay(aContext)) { + return dom::AutoplayPolicy::Allowed; + } + return dom::AutoplayPolicy::Disallowed; +} + +/* static */ +dom::AutoplayPolicy AutoplayPolicy::GetAutoplayPolicy( + const dom::AutoplayPolicyMediaType& aType, const dom::Document& aDoc) { + dom::DocumentAutoplayPolicy policy = AutoplayPolicy::IsAllowedToPlay(aDoc); + // https://w3c.github.io/autoplay/#query-by-a-media-type + if (aType == dom::AutoplayPolicyMediaType::Audiocontext) { + return policy == dom::DocumentAutoplayPolicy::Allowed + ? dom::AutoplayPolicy::Allowed + : dom::AutoplayPolicy::Disallowed; + } + MOZ_ASSERT(aType == dom::AutoplayPolicyMediaType::Mediaelement); + if (policy == dom::DocumentAutoplayPolicy::Allowed) { + return dom::AutoplayPolicy::Allowed; + } + if (policy == dom::DocumentAutoplayPolicy::Allowed_muted) { + return dom::AutoplayPolicy::Allowed_muted; + } + return dom::AutoplayPolicy::Disallowed; +} + } // namespace mozilla::media diff --git a/dom/media/autoplay/AutoplayPolicy.h b/dom/media/autoplay/AutoplayPolicy.h index c4a5090f662d..31b29181b02d 100644 --- a/dom/media/autoplay/AutoplayPolicy.h +++ b/dom/media/autoplay/AutoplayPolicy.h @@ -16,6 +16,8 @@ namespace mozilla::dom { class HTMLMediaElement; class AudioContext; class Document; +enum class AutoplayPolicy : uint8_t; +enum class AutoplayPolicyMediaType : uint8_t; enum class DocumentAutoplayPolicy : uint8_t; } // namespace mozilla::dom @@ -48,6 +50,18 @@ class AutoplayPolicy { // Return the value of the autoplay permission for given principal. The return // value can be 0=unknown, 1=allow, 2=block audio, 5=block audio and video. static uint32_t GetSiteAutoplayPermission(nsIPrincipal* aPrincipal); + + // Following methods are used for the internal implementation for the Autoplay + // Policy Detection API, the public JS interfaces are in exposed on Navigator. + // https://w3c.github.io/autoplay/#autoplay-detection-methods + static dom::AutoplayPolicy GetAutoplayPolicy( + const dom::HTMLMediaElement& aElement); + + static dom::AutoplayPolicy GetAutoplayPolicy( + const dom::AudioContext& aContext); + + static dom::AutoplayPolicy GetAutoplayPolicy( + const dom::AutoplayPolicyMediaType& aType, const dom::Document& aDoc); }; /** diff --git a/dom/webidl/Navigator.webidl b/dom/webidl/Navigator.webidl index 2ceb14716390..dd24042c8064 100644 --- a/dom/webidl/Navigator.webidl +++ b/dom/webidl/Navigator.webidl @@ -344,3 +344,27 @@ interface mixin NavigatorLocks { readonly attribute LockManager locks; }; Navigator includes NavigatorLocks; + +// https://w3c.github.io/autoplay/#autoplay-policy +enum AutoplayPolicy { + "allowed", + "allowed-muted", + "disallowed" +}; + +enum AutoplayPolicyMediaType { + "mediaelement", + "audiocontext" +}; + +// https://w3c.github.io/autoplay/#autoplay-detection-methods +partial interface Navigator { + [Pref="dom.media.autoplay-policy-detection.enabled"] + AutoplayPolicy getAutoplayPolicy(AutoplayPolicyMediaType type); + + [Pref="dom.media.autoplay-policy-detection.enabled"] + AutoplayPolicy getAutoplayPolicy(HTMLMediaElement element); + + [Pref="dom.media.autoplay-policy-detection.enabled"] + AutoplayPolicy getAutoplayPolicy(AudioContext context); +}; diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index ba4340fdb7f7..a8d00ff776ed 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -3000,6 +3000,12 @@ value: false mirror: always +# Autoplay Policy Detection https://w3c.github.io/autoplay/ +- name: dom.media.autoplay-policy-detection.enabled + type: RelaxedAtomicBool + value: @IS_NIGHTLY_BUILD@ + mirror: always + # Media Session API - name: dom.media.mediasession.enabled type: bool