1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "AutoplayPolicy.h"
9 #include "mozilla/dom/AudioContext.h"
10 #include "mozilla/dom/Document.h"
11 #include "mozilla/dom/FeaturePolicyUtils.h"
12 #include "mozilla/dom/HTMLMediaElement.h"
13 #include "mozilla/dom/HTMLMediaElementBinding.h"
14 #include "mozilla/dom/NavigatorBinding.h"
15 #include "mozilla/dom/UserActivation.h"
16 #include "mozilla/dom/WindowContext.h"
17 #include "mozilla/Logging.h"
18 #include "mozilla/MediaManager.h"
19 #include "mozilla/Components.h"
20 #include "mozilla/StaticPrefs_media.h"
21 #include "nsContentUtils.h"
22 #include "nsGlobalWindowInner.h"
23 #include "nsIAutoplay.h"
24 #include "nsIDocShell.h"
25 #include "nsIDocShellTreeItem.h"
26 #include "nsIPermissionManager.h"
27 #include "nsIPrincipal.h"
28 #include "nsPIDOMWindow.h"
30 mozilla::LazyLogModule
gAutoplayPermissionLog("Autoplay");
32 #define AUTOPLAY_LOG(msg, ...) \
33 MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
35 using namespace mozilla::dom
;
37 namespace mozilla::media
{
39 static const uint32_t sPOLICY_STICKY_ACTIVATION
= 0;
40 // static const uint32_t sPOLICY_TRANSIENT_ACTIVATION = 1;
41 static const uint32_t sPOLICY_USER_INPUT_DEPTH
= 2;
43 static bool IsActivelyCapturingOrHasAPermission(nsPIDOMWindowInner
* aWindow
) {
44 // Pages which have been granted permission to capture WebRTC camera or
45 // microphone or screen are assumed to be trusted, and are allowed to
47 if (MediaManager::GetIfExists()) {
48 return MediaManager::GetIfExists()->IsActivelyCapturingOrHasAPermission(
52 auto principal
= nsGlobalWindowInner::Cast(aWindow
)->GetPrincipal();
53 return (nsContentUtils::IsExactSitePermAllow(principal
, "camera"_ns
) ||
54 nsContentUtils::IsExactSitePermAllow(principal
, "microphone"_ns
) ||
55 nsContentUtils::IsExactSitePermAllow(principal
, "screen"_ns
));
58 static uint32_t SiteAutoplayPerm(nsPIDOMWindowInner
* aWindow
) {
59 if (!aWindow
|| !aWindow
->GetBrowsingContext()) {
60 return nsIPermissionManager::UNKNOWN_ACTION
;
63 WindowContext
* topContext
=
64 aWindow
->GetBrowsingContext()->GetTopWindowContext();
66 return nsIPermissionManager::UNKNOWN_ACTION
;
68 return topContext
->GetAutoplayPermission();
71 static bool IsWindowAllowedToPlayByUserGesture(nsPIDOMWindowInner
* aWindow
) {
76 WindowContext
* topContext
=
77 aWindow
->GetBrowsingContext()->GetTopWindowContext();
78 if (topContext
&& topContext
->HasBeenUserGestureActivated()) {
80 "Allow autoplay as top-level context has been activated by user "
87 static bool IsWindowAllowedToPlayByTraits(nsPIDOMWindowInner
* aWindow
) {
92 if (IsActivelyCapturingOrHasAPermission(aWindow
)) {
94 "Allow autoplay as document has camera or microphone or screen"
99 Document
* currentDoc
= aWindow
->GetExtantDoc();
104 bool isTopLevelContent
= !aWindow
->GetBrowsingContext()->GetParent();
105 if (currentDoc
->MediaDocumentKind() == Document::MediaDocumentKind::Video
&&
107 AUTOPLAY_LOG("Allow top-level video document to autoplay.");
111 if (StaticPrefs::media_autoplay_allow_extension_background_pages() &&
112 currentDoc
->IsExtensionPage()) {
113 AUTOPLAY_LOG("Allow autoplay as in extension document.");
117 if (currentDoc
->GetPrincipal()->Equals(
118 nsContentUtils::GetFingerprintingProtectionPrincipal())) {
119 AUTOPLAY_LOG("Allow autoplay as in fingerprinting protection document.");
126 static bool IsWindowAllowedToPlayOverall(nsPIDOMWindowInner
* aWindow
) {
127 return IsWindowAllowedToPlayByUserGesture(aWindow
) ||
128 IsWindowAllowedToPlayByTraits(aWindow
);
131 static uint32_t DefaultAutoplayBehaviour() {
132 int32_t prefValue
= StaticPrefs::media_autoplay_default();
133 if (prefValue
== nsIAutoplay::ALLOWED
) {
134 return nsIAutoplay::ALLOWED
;
136 if (prefValue
== nsIAutoplay::BLOCKED_ALL
) {
137 return nsIAutoplay::BLOCKED_ALL
;
139 return nsIAutoplay::BLOCKED
;
142 static bool IsMediaElementInaudible(const HTMLMediaElement
& aElement
) {
143 if (aElement
.Volume() == 0.0 || aElement
.Muted()) {
144 AUTOPLAY_LOG("Media %p is muted.", &aElement
);
148 if (!aElement
.HasAudio() &&
149 aElement
.ReadyState() >= HTMLMediaElement_Binding::HAVE_METADATA
) {
150 AUTOPLAY_LOG("Media %p has no audio track", &aElement
);
157 static bool IsEnableBlockingWebAudioByUserGesturePolicy() {
158 return StaticPrefs::media_autoplay_blocking_policy() ==
159 sPOLICY_STICKY_ACTIVATION
;
162 static bool IsAllowedToPlayByBlockingModel(const HTMLMediaElement
& aElement
) {
163 const uint32_t policy
= StaticPrefs::media_autoplay_blocking_policy();
164 if (policy
== sPOLICY_STICKY_ACTIVATION
) {
165 const bool isAllowed
=
166 IsWindowAllowedToPlayOverall(aElement
.OwnerDoc()->GetInnerWindow());
167 AUTOPLAY_LOG("Use 'sticky-activation', isAllowed=%d", isAllowed
);
170 // If element is blessed, it would always be allowed to play().
171 const bool isElementBlessed
= aElement
.IsBlessed();
172 if (policy
== sPOLICY_USER_INPUT_DEPTH
) {
173 const bool isUserInput
= UserActivation::IsHandlingUserInput();
174 AUTOPLAY_LOG("Use 'User-Input-Depth', isBlessed=%d, isUserInput=%d",
175 isElementBlessed
, isUserInput
);
176 return isElementBlessed
|| isUserInput
;
178 const bool hasTransientActivation
=
179 aElement
.OwnerDoc()->HasValidTransientUserGestureActivation();
181 "Use 'transient-activation', isBlessed=%d, "
182 "hasValidTransientActivation=%d",
183 isElementBlessed
, hasTransientActivation
);
184 return isElementBlessed
|| hasTransientActivation
;
187 // On GeckoView, we don't store any site's permission in permission manager, we
188 // would check the GV request status to know if the site can be allowed to play.
189 // But on other platforms, we would store the site's permission in permission
191 #if defined(MOZ_WIDGET_ANDROID)
192 using RType
= GVAutoplayRequestType
;
194 static bool IsGVAutoplayRequestAllowed(nsPIDOMWindowInner
* aWindow
,
200 RefPtr
<BrowsingContext
> context
= aWindow
->GetBrowsingContext()->Top();
201 GVAutoplayRequestStatus status
=
202 aType
== RType::eAUDIBLE
? context
->GetGVAudibleAutoplayRequestStatus()
203 : context
->GetGVInaudibleAutoplayRequestStatus();
204 return status
== GVAutoplayRequestStatus::eALLOWED
;
207 static bool IsGVAutoplayRequestAllowed(const HTMLMediaElement
& aElement
,
209 // On GV, blocking model is the first thing we would check inside Gecko, and
210 // if the media is not allowed by that, then we would check the response from
211 // the embedding app to decide the final result.
212 if (IsAllowedToPlayByBlockingModel(aElement
)) {
216 RefPtr
<nsPIDOMWindowInner
> window
= aElement
.OwnerDoc()->GetInnerWindow();
220 return IsGVAutoplayRequestAllowed(window
, aType
);
224 static bool IsAllowedToPlayInternal(const HTMLMediaElement
& aElement
) {
225 #if defined(MOZ_WIDGET_ANDROID)
226 if (StaticPrefs::media_geckoview_autoplay_request()) {
227 return IsGVAutoplayRequestAllowed(
228 aElement
, IsMediaElementInaudible(aElement
) ? RType::eINAUDIBLE
232 bool isInaudible
= IsMediaElementInaudible(aElement
);
233 bool isUsingAutoplayModel
= IsAllowedToPlayByBlockingModel(aElement
);
235 uint32_t defaultBehaviour
= DefaultAutoplayBehaviour();
236 uint32_t sitePermission
=
237 SiteAutoplayPerm(aElement
.OwnerDoc()->GetInnerWindow());
240 "IsAllowedToPlayInternal, isInaudible=%d,"
241 "isUsingAutoplayModel=%d, sitePermission=%d, defaultBehaviour=%d",
242 isInaudible
, isUsingAutoplayModel
, sitePermission
, defaultBehaviour
);
244 // For site permissions we store permissionManager values except
245 // for BLOCKED_ALL, for the default pref values we store
246 // nsIAutoplay values.
247 if (sitePermission
== nsIPermissionManager::ALLOW_ACTION
) {
251 if (sitePermission
== nsIPermissionManager::DENY_ACTION
) {
252 return isInaudible
|| isUsingAutoplayModel
;
255 if (sitePermission
== nsIAutoplay::BLOCKED_ALL
) {
256 return isUsingAutoplayModel
;
259 if (defaultBehaviour
== nsIAutoplay::ALLOWED
) {
263 if (defaultBehaviour
== nsIAutoplay::BLOCKED
) {
264 return isInaudible
|| isUsingAutoplayModel
;
267 MOZ_ASSERT(defaultBehaviour
== nsIAutoplay::BLOCKED_ALL
);
268 return isUsingAutoplayModel
;
272 bool AutoplayPolicy::IsAllowedToPlay(const HTMLMediaElement
& aElement
) {
273 const bool result
= IsAllowedToPlayInternal(aElement
);
274 AUTOPLAY_LOG("IsAllowedToPlay, mediaElement=%p, isAllowToPlay=%s", &aElement
,
275 result
? "allowed" : "blocked");
280 bool AutoplayPolicy::IsAllowedToPlay(const AudioContext
& aContext
) {
282 * The autoplay checking has 5 different phases,
283 * 1. check whether audio context itself meets the autoplay condition
284 * 2. check if we enable blocking web audio or not
285 * (only support blocking when using user-gesture-activation model)
286 * 3. check whether the site is in the autoplay whitelist
287 * 4. check global autoplay setting and check wether the site is in the
288 * autoplay blacklist.
289 * 5. check whether media is allowed under current blocking model
290 * (only support user-gesture-activation model)
292 if (aContext
.IsOffline()) {
296 if (!IsEnableBlockingWebAudioByUserGesturePolicy()) {
300 nsPIDOMWindowInner
* window
= aContext
.GetOwnerWindow();
301 uint32_t sitePermission
= SiteAutoplayPerm(window
);
303 if (sitePermission
== nsIPermissionManager::ALLOW_ACTION
) {
305 "Allow autoplay as document has permanent autoplay permission.");
309 if (DefaultAutoplayBehaviour() == nsIAutoplay::ALLOWED
&&
310 sitePermission
!= nsIPermissionManager::DENY_ACTION
&&
311 sitePermission
!= nsIAutoplay::BLOCKED_ALL
) {
313 "Allow autoplay as global autoplay setting is allowing autoplay by "
318 return IsWindowAllowedToPlayOverall(window
);
321 enum class DocumentAutoplayPolicy
: uint8_t {
328 DocumentAutoplayPolicy
IsDocAllowedToPlay(const Document
& aDocument
) {
329 RefPtr
<nsPIDOMWindowInner
> window
= aDocument
.GetInnerWindow();
331 #if defined(MOZ_WIDGET_ANDROID)
332 if (StaticPrefs::media_geckoview_autoplay_request()) {
333 const bool isWindowAllowedToPlay
= IsWindowAllowedToPlayOverall(window
);
334 if (IsGVAutoplayRequestAllowed(window
, RType::eAUDIBLE
)) {
335 return DocumentAutoplayPolicy::Allowed
;
338 if (IsGVAutoplayRequestAllowed(window
, RType::eINAUDIBLE
)) {
339 return isWindowAllowedToPlay
? DocumentAutoplayPolicy::Allowed
340 : DocumentAutoplayPolicy::Allowed_muted
;
343 return isWindowAllowedToPlay
? DocumentAutoplayPolicy::Allowed
344 : DocumentAutoplayPolicy::Disallowed
;
347 const uint32_t sitePermission
= SiteAutoplayPerm(window
);
348 const uint32_t globalPermission
= DefaultAutoplayBehaviour();
349 const uint32_t policy
= StaticPrefs::media_autoplay_blocking_policy();
350 const bool isWindowAllowedToPlayByGesture
=
351 policy
!= sPOLICY_USER_INPUT_DEPTH
&&
352 IsWindowAllowedToPlayByUserGesture(window
);
353 const bool isWindowAllowedToPlayByTraits
=
354 IsWindowAllowedToPlayByTraits(window
);
357 "IsDocAllowedToPlay(), policy=%d, sitePermission=%d, "
358 "globalPermission=%d, isWindowAllowedToPlayByGesture=%d, "
359 "isWindowAllowedToPlayByTraits=%d",
360 policy
, sitePermission
, globalPermission
, isWindowAllowedToPlayByGesture
,
361 isWindowAllowedToPlayByTraits
);
363 if ((globalPermission
== nsIAutoplay::ALLOWED
&&
364 (sitePermission
!= nsIPermissionManager::DENY_ACTION
&&
365 sitePermission
!= nsIAutoplay::BLOCKED_ALL
)) ||
366 sitePermission
== nsIPermissionManager::ALLOW_ACTION
||
367 isWindowAllowedToPlayByGesture
|| isWindowAllowedToPlayByTraits
) {
368 return DocumentAutoplayPolicy::Allowed
;
371 if ((globalPermission
== nsIAutoplay::BLOCKED
&&
372 sitePermission
!= nsIAutoplay::BLOCKED_ALL
) ||
373 sitePermission
== nsIPermissionManager::DENY_ACTION
) {
374 return DocumentAutoplayPolicy::Allowed_muted
;
377 return DocumentAutoplayPolicy::Disallowed
;
381 uint32_t AutoplayPolicy::GetSiteAutoplayPermission(nsIPrincipal
* aPrincipal
) {
383 return nsIPermissionManager::DENY_ACTION
;
386 nsCOMPtr
<nsIPermissionManager
> permMgr
=
387 components::PermissionManager::Service();
389 return nsIPermissionManager::DENY_ACTION
;
392 uint32_t perm
= nsIPermissionManager::DENY_ACTION
;
393 permMgr
->TestExactPermissionFromPrincipal(aPrincipal
, "autoplay-media"_ns
,
399 dom::AutoplayPolicy
AutoplayPolicy::GetAutoplayPolicy(
400 const dom::HTMLMediaElement
& aElement
) {
401 // Note, the site permission can contain following values :
402 // - UNKNOWN_ACTION : no permission set for this site
403 // - ALLOW_ACTION : allowed to autoplay
404 // - DENY_ACTION : allowed inaudible autoplay, disallowed inaudible autoplay
405 // - nsIAutoplay::BLOCKED_ALL : autoplay disallowed
406 // and the global permissions would be nsIAutoplay::{BLOCKED, ALLOWED,
408 const uint32_t sitePermission
=
409 SiteAutoplayPerm(aElement
.OwnerDoc()->GetInnerWindow());
410 const uint32_t globalPermission
= DefaultAutoplayBehaviour();
411 const bool isAllowedToPlayByBlockingModel
=
412 IsAllowedToPlayByBlockingModel(aElement
);
415 "IsAllowedToPlay(element), sitePermission=%d, globalPermission=%d, "
416 "isAllowedToPlayByBlockingModel=%d",
417 sitePermission
, globalPermission
, isAllowedToPlayByBlockingModel
);
419 #if defined(MOZ_WIDGET_ANDROID)
420 if (StaticPrefs::media_geckoview_autoplay_request()) {
421 if (IsGVAutoplayRequestAllowed(aElement
, RType::eAUDIBLE
)) {
422 return dom::AutoplayPolicy::Allowed
;
423 } else if (IsGVAutoplayRequestAllowed(aElement
, RType::eINAUDIBLE
)) {
424 return isAllowedToPlayByBlockingModel
425 ? dom::AutoplayPolicy::Allowed
426 : dom::AutoplayPolicy::Allowed_muted
;
428 return isAllowedToPlayByBlockingModel
? dom::AutoplayPolicy::Allowed
429 : dom::AutoplayPolicy::Disallowed
;
434 // These are situations when an element is allowed to autoplay
435 // 1. The site permission is explicitly allowed
436 // 2. The global permission is allowed, and the site isn't explicitly
438 // 3. The blocking model is explicitly allowed this element
439 if (sitePermission
== nsIPermissionManager::ALLOW_ACTION
||
440 (globalPermission
== nsIAutoplay::ALLOWED
&&
441 (sitePermission
!= nsIPermissionManager::DENY_ACTION
&&
442 sitePermission
!= nsIAutoplay::BLOCKED_ALL
)) ||
443 isAllowedToPlayByBlockingModel
) {
444 return dom::AutoplayPolicy::Allowed
;
447 // These are situations when a element is allowed to autoplay only when it's
449 // 1. The site permission is block-audible-autoplay
450 // 2. The global permission is block-audible-autoplay, and the site permission
451 // isn't block-all-autoplay
452 if (sitePermission
== nsIPermissionManager::DENY_ACTION
||
453 (globalPermission
== nsIAutoplay::BLOCKED
&&
454 sitePermission
!= nsIAutoplay::BLOCKED_ALL
)) {
455 return dom::AutoplayPolicy::Allowed_muted
;
458 return dom::AutoplayPolicy::Disallowed
;
462 dom::AutoplayPolicy
AutoplayPolicy::GetAutoplayPolicy(
463 const dom::AudioContext
& aContext
) {
464 if (AutoplayPolicy::IsAllowedToPlay(aContext
)) {
465 return dom::AutoplayPolicy::Allowed
;
467 return dom::AutoplayPolicy::Disallowed
;
471 dom::AutoplayPolicy
AutoplayPolicy::GetAutoplayPolicy(
472 const dom::AutoplayPolicyMediaType
& aType
, const dom::Document
& aDoc
) {
473 DocumentAutoplayPolicy policy
= IsDocAllowedToPlay(aDoc
);
474 // https://w3c.github.io/autoplay/#query-by-a-media-type
475 if (aType
== dom::AutoplayPolicyMediaType::Audiocontext
) {
476 return policy
== DocumentAutoplayPolicy::Allowed
477 ? dom::AutoplayPolicy::Allowed
478 : dom::AutoplayPolicy::Disallowed
;
480 MOZ_ASSERT(aType
== dom::AutoplayPolicyMediaType::Mediaelement
);
481 if (policy
== DocumentAutoplayPolicy::Allowed
) {
482 return dom::AutoplayPolicy::Allowed
;
484 if (policy
== DocumentAutoplayPolicy::Allowed_muted
) {
485 return dom::AutoplayPolicy::Allowed_muted
;
487 return dom::AutoplayPolicy::Disallowed
;
490 } // namespace mozilla::media