Bug 1943650 - Command-line --help output misformatted after --dbus-service. r=emilio
[gecko.git] / dom / media / autoplay / AutoplayPolicy.cpp
blob695b7da74ec73842ff6f73864779e8df414ce46f
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
46 // autoplay.
47 if (MediaManager::GetIfExists()) {
48 return MediaManager::GetIfExists()->IsActivelyCapturingOrHasAPermission(
49 aWindow->WindowID());
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();
65 if (!topContext) {
66 return nsIPermissionManager::UNKNOWN_ACTION;
68 return topContext->GetAutoplayPermission();
71 static bool IsWindowAllowedToPlayByUserGesture(nsPIDOMWindowInner* aWindow) {
72 if (!aWindow) {
73 return false;
76 WindowContext* topContext =
77 aWindow->GetBrowsingContext()->GetTopWindowContext();
78 if (topContext && topContext->HasBeenUserGestureActivated()) {
79 AUTOPLAY_LOG(
80 "Allow autoplay as top-level context has been activated by user "
81 "gesture.");
82 return true;
84 return false;
87 static bool IsWindowAllowedToPlayByTraits(nsPIDOMWindowInner* aWindow) {
88 if (!aWindow) {
89 return false;
92 if (IsActivelyCapturingOrHasAPermission(aWindow)) {
93 AUTOPLAY_LOG(
94 "Allow autoplay as document has camera or microphone or screen"
95 " permission.");
96 return true;
99 Document* currentDoc = aWindow->GetExtantDoc();
100 if (!currentDoc) {
101 return false;
104 bool isTopLevelContent = !aWindow->GetBrowsingContext()->GetParent();
105 if (currentDoc->MediaDocumentKind() == Document::MediaDocumentKind::Video &&
106 isTopLevelContent) {
107 AUTOPLAY_LOG("Allow top-level video document to autoplay.");
108 return true;
111 if (StaticPrefs::media_autoplay_allow_extension_background_pages() &&
112 currentDoc->IsExtensionPage()) {
113 AUTOPLAY_LOG("Allow autoplay as in extension document.");
114 return true;
117 if (currentDoc->GetPrincipal()->Equals(
118 nsContentUtils::GetFingerprintingProtectionPrincipal())) {
119 AUTOPLAY_LOG("Allow autoplay as in fingerprinting protection document.");
120 return true;
123 return false;
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);
145 return true;
148 if (!aElement.HasAudio() &&
149 aElement.ReadyState() >= HTMLMediaElement_Binding::HAVE_METADATA) {
150 AUTOPLAY_LOG("Media %p has no audio track", &aElement);
151 return true;
154 return false;
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);
168 return 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();
180 AUTOPLAY_LOG(
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
190 // manager.
191 #if defined(MOZ_WIDGET_ANDROID)
192 using RType = GVAutoplayRequestType;
194 static bool IsGVAutoplayRequestAllowed(nsPIDOMWindowInner* aWindow,
195 RType aType) {
196 if (!aWindow) {
197 return false;
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,
208 RType aType) {
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)) {
213 return true;
216 RefPtr<nsPIDOMWindowInner> window = aElement.OwnerDoc()->GetInnerWindow();
217 if (!window) {
218 return false;
220 return IsGVAutoplayRequestAllowed(window, aType);
222 #endif
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
229 : RType::eAUDIBLE);
231 #endif
232 bool isInaudible = IsMediaElementInaudible(aElement);
233 bool isUsingAutoplayModel = IsAllowedToPlayByBlockingModel(aElement);
235 uint32_t defaultBehaviour = DefaultAutoplayBehaviour();
236 uint32_t sitePermission =
237 SiteAutoplayPerm(aElement.OwnerDoc()->GetInnerWindow());
239 AUTOPLAY_LOG(
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) {
248 return true;
251 if (sitePermission == nsIPermissionManager::DENY_ACTION) {
252 return isInaudible || isUsingAutoplayModel;
255 if (sitePermission == nsIAutoplay::BLOCKED_ALL) {
256 return isUsingAutoplayModel;
259 if (defaultBehaviour == nsIAutoplay::ALLOWED) {
260 return true;
263 if (defaultBehaviour == nsIAutoplay::BLOCKED) {
264 return isInaudible || isUsingAutoplayModel;
267 MOZ_ASSERT(defaultBehaviour == nsIAutoplay::BLOCKED_ALL);
268 return isUsingAutoplayModel;
271 /* static */
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");
276 return result;
279 /* static */
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()) {
293 return true;
296 if (!IsEnableBlockingWebAudioByUserGesturePolicy()) {
297 return true;
300 nsPIDOMWindowInner* window = aContext.GetOwnerWindow();
301 uint32_t sitePermission = SiteAutoplayPerm(window);
303 if (sitePermission == nsIPermissionManager::ALLOW_ACTION) {
304 AUTOPLAY_LOG(
305 "Allow autoplay as document has permanent autoplay permission.");
306 return true;
309 if (DefaultAutoplayBehaviour() == nsIAutoplay::ALLOWED &&
310 sitePermission != nsIPermissionManager::DENY_ACTION &&
311 sitePermission != nsIAutoplay::BLOCKED_ALL) {
312 AUTOPLAY_LOG(
313 "Allow autoplay as global autoplay setting is allowing autoplay by "
314 "default.");
315 return true;
318 return IsWindowAllowedToPlayOverall(window);
321 enum class DocumentAutoplayPolicy : uint8_t {
322 Allowed,
323 Allowed_muted,
324 Disallowed
327 /* static */
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;
346 #endif
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);
356 AUTOPLAY_LOG(
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;
380 /* static */
381 uint32_t AutoplayPolicy::GetSiteAutoplayPermission(nsIPrincipal* aPrincipal) {
382 if (!aPrincipal) {
383 return nsIPermissionManager::DENY_ACTION;
386 nsCOMPtr<nsIPermissionManager> permMgr =
387 components::PermissionManager::Service();
388 if (!permMgr) {
389 return nsIPermissionManager::DENY_ACTION;
392 uint32_t perm = nsIPermissionManager::DENY_ACTION;
393 permMgr->TestExactPermissionFromPrincipal(aPrincipal, "autoplay-media"_ns,
394 &perm);
395 return perm;
398 /* static */
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,
407 // BLOCKED_ALL}
408 const uint32_t sitePermission =
409 SiteAutoplayPerm(aElement.OwnerDoc()->GetInnerWindow());
410 const uint32_t globalPermission = DefaultAutoplayBehaviour();
411 const bool isAllowedToPlayByBlockingModel =
412 IsAllowedToPlayByBlockingModel(aElement);
414 AUTOPLAY_LOG(
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;
427 } else {
428 return isAllowedToPlayByBlockingModel ? dom::AutoplayPolicy::Allowed
429 : dom::AutoplayPolicy::Disallowed;
432 #endif
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
437 // disallowed
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
448 // inaudible.
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;
461 /* static */
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;
470 /* static */
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