dom/media/AutoplayPolicy.cpp
author alwu <alwu@mozilla.com>
Mon, 23 Jul 2018 11:17:00 -0700
changeset 822667 1c7b512ac526c3af59c94c340b50e593845c04c3
parent 821960 fdd6fe09e0d59b7dcecb578d34753ddb1a884fd5
child 825453 29f18aa2758b8d757fcf1cace12b7b79016f02df
permissions -rw-r--r--
Bug 1477415 - part1 : allow video document autoplay. Allow autoplay for video document when user is directly viewing a video/audio by visiting its url. MozReview-Commit-ID: GnSxx32h6hm

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "AutoplayPolicy.h"

#include "mozilla/EventStateManager.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/AudioContext.h"
#include "mozilla/AutoplayPermissionManager.h"
#include "mozilla/dom/HTMLMediaElement.h"
#include "mozilla/dom/HTMLMediaElementBinding.h"
#include "nsIAutoplay.h"
#include "nsContentUtils.h"
#include "nsIDocument.h"
#include "MediaManager.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeItem.h"
#include "nsPIDOMWindow.h"

namespace mozilla {
namespace dom {

static nsIDocument*
ApproverDocOf(const nsIDocument& aDocument)
{
  nsCOMPtr<nsIDocShell> ds = aDocument.GetDocShell();
  if (!ds) {
    return nullptr;
  }

  nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
  ds->GetSameTypeRootTreeItem(getter_AddRefs(rootTreeItem));
  if (!rootTreeItem) {
    return nullptr;
  }

  return rootTreeItem->GetDocument();
}

static bool
IsWindowAllowedToPlay(nsPIDOMWindowInner* aWindow)
{
  if (!aWindow) {
    return false;
  }

  // Pages which have been granted permission to capture WebRTC camera or
  // microphone are assumed to be trusted, and are allowed to autoplay.
  MediaManager* manager = MediaManager::GetIfExists();
  if (manager &&
      manager->IsActivelyCapturingOrHasAPermission(aWindow->WindowID())) {
    return true;
  }

  if (!aWindow->GetExtantDoc()) {
    return false;
  }

  nsIDocument* approver = ApproverDocOf(*aWindow->GetExtantDoc());
  if (nsContentUtils::IsExactSitePermAllow(approver->NodePrincipal(),
                                           "autoplay-media")) {
    // Autoplay permission has been granted already.
    return true;
  }

  if (approver->HasBeenUserGestureActivated()) {
    // Document has been activated by user gesture.
    return true;
  }

  return false;
}

/* static */
already_AddRefed<AutoplayPermissionManager>
AutoplayPolicy::RequestFor(const nsIDocument& aDocument)
{
  nsIDocument* document = ApproverDocOf(aDocument);
  if (!document) {
    return nullptr;
  }
  nsPIDOMWindowInner* window = document->GetInnerWindow();
  if (!window) {
    return nullptr;
  }
  return window->GetAutoplayPermissionManager();
}

static uint32_t
DefaultAutoplayBehaviour()
{
  int prefValue = Preferences::GetInt("media.autoplay.default", nsIAutoplay::ALLOWED);
  if (prefValue < nsIAutoplay::ALLOWED || prefValue > nsIAutoplay::PROMPT) {
    // Invalid pref values are just converted to ALLOWED.
    return nsIAutoplay::ALLOWED;
  }
  return prefValue;
}

static bool
IsMediaElementAllowedToPlay(const HTMLMediaElement& aElement)
{
  return ((aElement.Volume() == 0.0 || aElement.Muted()) &&
           Preferences::GetBool("media.autoplay.allow-muted", true)) ||
          IsWindowAllowedToPlay(aElement.OwnerDoc()->GetInnerWindow()) ||
          (aElement.OwnerDoc()->MediaDocumentKind() == nsIDocument::MediaDocumentKind::Video);
}

/* static */ bool
AutoplayPolicy::WouldBeAllowedToPlayIfAutoplayDisabled(const HTMLMediaElement& aElement)
{
  return IsMediaElementAllowedToPlay(aElement);
}

/* static */ uint32_t
AutoplayPolicy::IsAllowedToPlay(const HTMLMediaElement& aElement)
{
  const uint32_t autoplayDefault = DefaultAutoplayBehaviour();
  // TODO : this old way would be removed when user-gestures-needed becomes
  // as a default option to block autoplay.
  if (!Preferences::GetBool("media.autoplay.enabled.user-gestures-needed", false)) {
    // If element is blessed, it would always be allowed to play().
    return (autoplayDefault == nsIAutoplay::ALLOWED ||
            aElement.IsBlessed() ||
            EventStateManager::IsHandlingUserInput())
              ? nsIAutoplay::ALLOWED : nsIAutoplay::BLOCKED;
  }

  if (IsMediaElementAllowedToPlay(aElement)) {
    return nsIAutoplay::ALLOWED;
  }

  return autoplayDefault;
}

/* static */ bool
AutoplayPolicy::IsAudioContextAllowedToPlay(NotNull<AudioContext*> aContext)
{
  if (!Preferences::GetBool("media.autoplay.block-webaudio", false)) {
    return true;
  }

  if (DefaultAutoplayBehaviour() == nsIAutoplay::ALLOWED) {
    return true;
  }

  if (!Preferences::GetBool("media.autoplay.enabled.user-gestures-needed", false)) {
    return true;
  }

  // Offline context won't directly output sound to audio devices.
  if (aContext->IsOffline()) {
    return true;
  }

  if (IsWindowAllowedToPlay(aContext->GetOwner())) {
    return true;
  }

  return false;
}

} // namespace dom
} // namespace mozilla