tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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