AutoplayPolicy.cpp (17478B)
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/. */ 6 7 #include "AutoplayPolicy.h" 8 9 #include "mozilla/Components.h" 10 #include "mozilla/Logging.h" 11 #include "mozilla/MediaManager.h" 12 #include "mozilla/StaticPrefs_media.h" 13 #include "mozilla/dom/AudioContext.h" 14 #include "mozilla/dom/Document.h" 15 #include "mozilla/dom/FeaturePolicyUtils.h" 16 #include "mozilla/dom/HTMLMediaElement.h" 17 #include "mozilla/dom/HTMLMediaElementBinding.h" 18 #include "mozilla/dom/NavigatorBinding.h" 19 #include "mozilla/dom/UserActivation.h" 20 #include "mozilla/dom/WindowContext.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" 29 30 mozilla::LazyLogModule gAutoplayPermissionLog("Autoplay"); 31 32 #define AUTOPLAY_LOG(msg, ...) \ 33 MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) 34 35 using namespace mozilla::dom; 36 37 namespace mozilla::media { 38 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; 42 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()); 50 } 51 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)); 56 } 57 58 static uint32_t SiteAutoplayPerm(nsPIDOMWindowInner* aWindow) { 59 if (!aWindow || !aWindow->GetBrowsingContext()) { 60 return nsIPermissionManager::UNKNOWN_ACTION; 61 } 62 63 WindowContext* topContext = 64 aWindow->GetBrowsingContext()->GetTopWindowContext(); 65 if (!topContext) { 66 return nsIPermissionManager::UNKNOWN_ACTION; 67 } 68 return topContext->GetAutoplayPermission(); 69 } 70 71 static bool IsWindowAllowedToPlayByUserGesture(nsPIDOMWindowInner* aWindow) { 72 if (!aWindow) { 73 return false; 74 } 75 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; 83 } 84 return false; 85 } 86 87 static bool IsWindowAllowedToPlayByTraits(nsPIDOMWindowInner* aWindow) { 88 if (!aWindow) { 89 return false; 90 } 91 92 if (IsActivelyCapturingOrHasAPermission(aWindow)) { 93 AUTOPLAY_LOG( 94 "Allow autoplay as document has camera or microphone or screen" 95 " permission."); 96 return true; 97 } 98 99 Document* currentDoc = aWindow->GetExtantDoc(); 100 if (!currentDoc) { 101 return false; 102 } 103 104 #ifndef MOZ_WIDGET_ANDROID 105 // On Android, we'd like to prevent top level video document from autoplaying. 106 bool isTopLevelContent = !aWindow->GetBrowsingContext()->GetParent(); 107 if (currentDoc->MediaDocumentKind() == Document::MediaDocumentKind::Video && 108 isTopLevelContent) { 109 AUTOPLAY_LOG("Allow top-level video document to autoplay."); 110 return true; 111 } 112 #endif 113 114 if (StaticPrefs::media_autoplay_allow_extension_background_pages() && 115 currentDoc->IsExtensionPage()) { 116 AUTOPLAY_LOG("Allow autoplay as in extension document."); 117 return true; 118 } 119 120 if (currentDoc->GetPrincipal()->Equals( 121 nsContentUtils::GetFingerprintingProtectionPrincipal())) { 122 AUTOPLAY_LOG("Allow autoplay as in fingerprinting protection document."); 123 return true; 124 } 125 126 return false; 127 } 128 129 static bool IsWindowAllowedToPlayOverall(nsPIDOMWindowInner* aWindow) { 130 return IsWindowAllowedToPlayByUserGesture(aWindow) || 131 IsWindowAllowedToPlayByTraits(aWindow); 132 } 133 134 static uint32_t DefaultAutoplayBehaviour() { 135 int32_t prefValue = StaticPrefs::media_autoplay_default(); 136 if (prefValue == nsIAutoplay::ALLOWED) { 137 return nsIAutoplay::ALLOWED; 138 } 139 if (prefValue == nsIAutoplay::BLOCKED_ALL) { 140 return nsIAutoplay::BLOCKED_ALL; 141 } 142 return nsIAutoplay::BLOCKED; 143 } 144 145 static bool IsMediaElementInaudible(const HTMLMediaElement& aElement) { 146 if (aElement.Volume() == 0.0 || aElement.Muted()) { 147 AUTOPLAY_LOG("Media %p is muted.", &aElement); 148 return true; 149 } 150 151 if (!aElement.HasAudio() && 152 aElement.ReadyState() >= HTMLMediaElement_Binding::HAVE_METADATA) { 153 AUTOPLAY_LOG("Media %p has no audio track", &aElement); 154 return true; 155 } 156 157 return false; 158 } 159 160 static bool IsEnableBlockingWebAudioByUserGesturePolicy() { 161 return StaticPrefs::media_autoplay_blocking_policy() == 162 sPOLICY_STICKY_ACTIVATION; 163 } 164 165 static bool IsAllowedToPlayByBlockingModel(const HTMLMediaElement& aElement) { 166 const uint32_t policy = StaticPrefs::media_autoplay_blocking_policy(); 167 if (policy == sPOLICY_STICKY_ACTIVATION) { 168 const bool isAllowed = 169 IsWindowAllowedToPlayOverall(aElement.OwnerDoc()->GetInnerWindow()); 170 AUTOPLAY_LOG("Use 'sticky-activation', isAllowed=%d", isAllowed); 171 return isAllowed; 172 } 173 // If element is blessed, it would always be allowed to play(). 174 const bool isElementBlessed = aElement.IsBlessed(); 175 if (policy == sPOLICY_USER_INPUT_DEPTH) { 176 const bool isUserInput = UserActivation::IsHandlingUserInput(); 177 AUTOPLAY_LOG("Use 'User-Input-Depth', isBlessed=%d, isUserInput=%d", 178 isElementBlessed, isUserInput); 179 return isElementBlessed || isUserInput; 180 } 181 const bool hasTransientActivation = 182 aElement.OwnerDoc()->HasValidTransientUserGestureActivation(); 183 AUTOPLAY_LOG( 184 "Use 'transient-activation', isBlessed=%d, " 185 "hasValidTransientActivation=%d", 186 isElementBlessed, hasTransientActivation); 187 return isElementBlessed || hasTransientActivation; 188 } 189 190 // On GeckoView, we don't store any site's permission in permission manager, we 191 // would check the GV request status to know if the site can be allowed to play. 192 // But on other platforms, we would store the site's permission in permission 193 // manager. 194 #if defined(MOZ_WIDGET_ANDROID) 195 using RType = GVAutoplayRequestType; 196 197 static bool IsGVAutoplayRequestAllowed(nsPIDOMWindowInner* aWindow, 198 RType aType) { 199 if (!aWindow) { 200 return false; 201 } 202 203 RefPtr<BrowsingContext> context = aWindow->GetBrowsingContext()->Top(); 204 GVAutoplayRequestStatus status = 205 aType == RType::eAUDIBLE ? context->GetGVAudibleAutoplayRequestStatus() 206 : context->GetGVInaudibleAutoplayRequestStatus(); 207 return status == GVAutoplayRequestStatus::eALLOWED; 208 } 209 210 static bool IsGVAutoplayRequestAllowed(const HTMLMediaElement& aElement, 211 RType aType) { 212 // On GV, blocking model is the first thing we would check inside Gecko, and 213 // if the media is not allowed by that, then we would check the response from 214 // the embedding app to decide the final result. 215 if (IsAllowedToPlayByBlockingModel(aElement)) { 216 return true; 217 } 218 219 RefPtr<nsPIDOMWindowInner> window = aElement.OwnerDoc()->GetInnerWindow(); 220 if (!window) { 221 return false; 222 } 223 return IsGVAutoplayRequestAllowed(window, aType); 224 } 225 #endif 226 227 static bool IsAllowedToPlayInternal(const HTMLMediaElement& aElement) { 228 #if defined(MOZ_WIDGET_ANDROID) 229 if (StaticPrefs::media_geckoview_autoplay_request()) { 230 return IsGVAutoplayRequestAllowed( 231 aElement, IsMediaElementInaudible(aElement) ? RType::eINAUDIBLE 232 : RType::eAUDIBLE); 233 } 234 #endif 235 bool isInaudible = IsMediaElementInaudible(aElement); 236 bool isUsingAutoplayModel = IsAllowedToPlayByBlockingModel(aElement); 237 238 uint32_t defaultBehaviour = DefaultAutoplayBehaviour(); 239 uint32_t sitePermission = 240 SiteAutoplayPerm(aElement.OwnerDoc()->GetInnerWindow()); 241 242 AUTOPLAY_LOG( 243 "IsAllowedToPlayInternal, isInaudible=%d," 244 "isUsingAutoplayModel=%d, sitePermission=%d, defaultBehaviour=%d", 245 isInaudible, isUsingAutoplayModel, sitePermission, defaultBehaviour); 246 247 // For site permissions we store permissionManager values except 248 // for BLOCKED_ALL, for the default pref values we store 249 // nsIAutoplay values. 250 if (sitePermission == nsIPermissionManager::ALLOW_ACTION) { 251 return true; 252 } 253 254 if (sitePermission == nsIPermissionManager::DENY_ACTION) { 255 return isInaudible || isUsingAutoplayModel; 256 } 257 258 if (sitePermission == nsIAutoplay::BLOCKED_ALL) { 259 return isUsingAutoplayModel; 260 } 261 262 if (defaultBehaviour == nsIAutoplay::ALLOWED) { 263 return true; 264 } 265 266 if (defaultBehaviour == nsIAutoplay::BLOCKED) { 267 return isInaudible || isUsingAutoplayModel; 268 } 269 270 MOZ_ASSERT(defaultBehaviour == nsIAutoplay::BLOCKED_ALL); 271 return isUsingAutoplayModel; 272 } 273 274 /* static */ 275 bool AutoplayPolicy::IsAllowedToPlay(const HTMLMediaElement& aElement) { 276 const bool result = IsAllowedToPlayInternal(aElement); 277 AUTOPLAY_LOG("IsAllowedToPlay, mediaElement=%p, isAllowToPlay=%s", &aElement, 278 result ? "allowed" : "blocked"); 279 return result; 280 } 281 282 /* static */ 283 bool AutoplayPolicy::IsAllowedToPlay(const AudioContext& aContext) { 284 /** 285 * The autoplay checking has 5 different phases, 286 * 1. check whether audio context itself meets the autoplay condition 287 * 2. check if we enable blocking web audio or not 288 * (only support blocking when using user-gesture-activation model) 289 * 3. check whether the site is in the autoplay whitelist 290 * 4. check global autoplay setting and check wether the site is in the 291 * autoplay blacklist. 292 * 5. check whether media is allowed under current blocking model 293 * (only support user-gesture-activation model) 294 */ 295 if (aContext.IsOffline()) { 296 return true; 297 } 298 299 if (!IsEnableBlockingWebAudioByUserGesturePolicy()) { 300 return true; 301 } 302 303 nsPIDOMWindowInner* window = aContext.GetOwnerWindow(); 304 uint32_t sitePermission = SiteAutoplayPerm(window); 305 306 if (sitePermission == nsIPermissionManager::ALLOW_ACTION) { 307 AUTOPLAY_LOG( 308 "Allow autoplay as document has permanent autoplay permission."); 309 return true; 310 } 311 312 if (DefaultAutoplayBehaviour() == nsIAutoplay::ALLOWED && 313 sitePermission != nsIPermissionManager::DENY_ACTION && 314 sitePermission != nsIAutoplay::BLOCKED_ALL) { 315 AUTOPLAY_LOG( 316 "Allow autoplay as global autoplay setting is allowing autoplay by " 317 "default."); 318 return true; 319 } 320 321 return IsWindowAllowedToPlayOverall(window); 322 } 323 324 enum class DocumentAutoplayPolicy : uint8_t { 325 Allowed, 326 Allowed_muted, 327 Disallowed 328 }; 329 330 /* static */ 331 DocumentAutoplayPolicy IsDocAllowedToPlay(const Document& aDocument) { 332 RefPtr<nsPIDOMWindowInner> window = aDocument.GetInnerWindow(); 333 334 #if defined(MOZ_WIDGET_ANDROID) 335 if (StaticPrefs::media_geckoview_autoplay_request()) { 336 const bool isWindowAllowedToPlay = IsWindowAllowedToPlayOverall(window); 337 if (IsGVAutoplayRequestAllowed(window, RType::eAUDIBLE)) { 338 return DocumentAutoplayPolicy::Allowed; 339 } 340 341 if (IsGVAutoplayRequestAllowed(window, RType::eINAUDIBLE)) { 342 return isWindowAllowedToPlay ? DocumentAutoplayPolicy::Allowed 343 : DocumentAutoplayPolicy::Allowed_muted; 344 } 345 346 return isWindowAllowedToPlay ? DocumentAutoplayPolicy::Allowed 347 : DocumentAutoplayPolicy::Disallowed; 348 } 349 #endif 350 const uint32_t sitePermission = SiteAutoplayPerm(window); 351 const uint32_t globalPermission = DefaultAutoplayBehaviour(); 352 const uint32_t policy = StaticPrefs::media_autoplay_blocking_policy(); 353 const bool isWindowAllowedToPlayByGesture = 354 policy != sPOLICY_USER_INPUT_DEPTH && 355 IsWindowAllowedToPlayByUserGesture(window); 356 const bool isWindowAllowedToPlayByTraits = 357 IsWindowAllowedToPlayByTraits(window); 358 359 AUTOPLAY_LOG( 360 "IsDocAllowedToPlay(), policy=%d, sitePermission=%d, " 361 "globalPermission=%d, isWindowAllowedToPlayByGesture=%d, " 362 "isWindowAllowedToPlayByTraits=%d", 363 policy, sitePermission, globalPermission, isWindowAllowedToPlayByGesture, 364 isWindowAllowedToPlayByTraits); 365 366 if ((globalPermission == nsIAutoplay::ALLOWED && 367 (sitePermission != nsIPermissionManager::DENY_ACTION && 368 sitePermission != nsIAutoplay::BLOCKED_ALL)) || 369 sitePermission == nsIPermissionManager::ALLOW_ACTION || 370 isWindowAllowedToPlayByGesture || isWindowAllowedToPlayByTraits) { 371 return DocumentAutoplayPolicy::Allowed; 372 } 373 374 if ((globalPermission == nsIAutoplay::BLOCKED && 375 sitePermission != nsIAutoplay::BLOCKED_ALL) || 376 sitePermission == nsIPermissionManager::DENY_ACTION) { 377 return DocumentAutoplayPolicy::Allowed_muted; 378 } 379 380 return DocumentAutoplayPolicy::Disallowed; 381 } 382 383 /* static */ 384 uint32_t AutoplayPolicy::GetSiteAutoplayPermission(nsIPrincipal* aPrincipal) { 385 if (!aPrincipal) { 386 return nsIPermissionManager::DENY_ACTION; 387 } 388 389 nsCOMPtr<nsIPermissionManager> permMgr = 390 components::PermissionManager::Service(); 391 if (!permMgr) { 392 return nsIPermissionManager::DENY_ACTION; 393 } 394 395 uint32_t perm = nsIPermissionManager::DENY_ACTION; 396 permMgr->TestExactPermissionFromPrincipal(aPrincipal, "autoplay-media"_ns, 397 &perm); 398 return perm; 399 } 400 401 /* static */ 402 dom::AutoplayPolicy AutoplayPolicy::GetAutoplayPolicy( 403 const dom::HTMLMediaElement& aElement) { 404 // Note, the site permission can contain following values : 405 // - UNKNOWN_ACTION : no permission set for this site 406 // - ALLOW_ACTION : allowed to autoplay 407 // - DENY_ACTION : allowed inaudible autoplay, disallowed inaudible autoplay 408 // - nsIAutoplay::BLOCKED_ALL : autoplay disallowed 409 // and the global permissions would be nsIAutoplay::{BLOCKED, ALLOWED, 410 // BLOCKED_ALL} 411 const uint32_t sitePermission = 412 SiteAutoplayPerm(aElement.OwnerDoc()->GetInnerWindow()); 413 const uint32_t globalPermission = DefaultAutoplayBehaviour(); 414 const bool isAllowedToPlayByBlockingModel = 415 IsAllowedToPlayByBlockingModel(aElement); 416 417 AUTOPLAY_LOG( 418 "IsAllowedToPlay(element), sitePermission=%d, globalPermission=%d, " 419 "isAllowedToPlayByBlockingModel=%d", 420 sitePermission, globalPermission, isAllowedToPlayByBlockingModel); 421 422 #if defined(MOZ_WIDGET_ANDROID) 423 if (StaticPrefs::media_geckoview_autoplay_request()) { 424 if (IsGVAutoplayRequestAllowed(aElement, RType::eAUDIBLE)) { 425 return dom::AutoplayPolicy::Allowed; 426 } else if (IsGVAutoplayRequestAllowed(aElement, RType::eINAUDIBLE)) { 427 return isAllowedToPlayByBlockingModel 428 ? dom::AutoplayPolicy::Allowed 429 : dom::AutoplayPolicy::Allowed_muted; 430 } else { 431 return isAllowedToPlayByBlockingModel ? dom::AutoplayPolicy::Allowed 432 : dom::AutoplayPolicy::Disallowed; 433 } 434 } 435 #endif 436 437 // These are situations when an element is allowed to autoplay 438 // 1. The site permission is explicitly allowed 439 // 2. The global permission is allowed, and the site isn't explicitly 440 // disallowed 441 // 3. The blocking model is explicitly allowed this element 442 if (sitePermission == nsIPermissionManager::ALLOW_ACTION || 443 (globalPermission == nsIAutoplay::ALLOWED && 444 (sitePermission != nsIPermissionManager::DENY_ACTION && 445 sitePermission != nsIAutoplay::BLOCKED_ALL)) || 446 isAllowedToPlayByBlockingModel) { 447 return dom::AutoplayPolicy::Allowed; 448 } 449 450 // These are situations when a element is allowed to autoplay only when it's 451 // inaudible. 452 // 1. The site permission is block-audible-autoplay 453 // 2. The global permission is block-audible-autoplay, and the site permission 454 // isn't block-all-autoplay 455 if (sitePermission == nsIPermissionManager::DENY_ACTION || 456 (globalPermission == nsIAutoplay::BLOCKED && 457 sitePermission != nsIAutoplay::BLOCKED_ALL)) { 458 return dom::AutoplayPolicy::Allowed_muted; 459 } 460 461 return dom::AutoplayPolicy::Disallowed; 462 } 463 464 /* static */ 465 dom::AutoplayPolicy AutoplayPolicy::GetAutoplayPolicy( 466 const dom::AudioContext& aContext) { 467 if (AutoplayPolicy::IsAllowedToPlay(aContext)) { 468 return dom::AutoplayPolicy::Allowed; 469 } 470 return dom::AutoplayPolicy::Disallowed; 471 } 472 473 /* static */ 474 dom::AutoplayPolicy AutoplayPolicy::GetAutoplayPolicy( 475 const dom::AutoplayPolicyMediaType& aType, const dom::Document& aDoc) { 476 DocumentAutoplayPolicy policy = IsDocAllowedToPlay(aDoc); 477 // https://w3c.github.io/autoplay/#query-by-a-media-type 478 if (aType == dom::AutoplayPolicyMediaType::Audiocontext) { 479 return policy == DocumentAutoplayPolicy::Allowed 480 ? dom::AutoplayPolicy::Allowed 481 : dom::AutoplayPolicy::Disallowed; 482 } 483 MOZ_ASSERT(aType == dom::AutoplayPolicyMediaType::Mediaelement); 484 if (policy == DocumentAutoplayPolicy::Allowed) { 485 return dom::AutoplayPolicy::Allowed; 486 } 487 if (policy == DocumentAutoplayPolicy::Allowed_muted) { 488 return dom::AutoplayPolicy::Allowed_muted; 489 } 490 return dom::AutoplayPolicy::Disallowed; 491 } 492 493 } // namespace mozilla::media