gecko-dev/dom/media/AutoplayPolicy.cpp
alwu d7a75adbdb Bug 1489278 - part1 : show doorhanger when create AudioContext r=padenot
In the ideal situation, sites should create AudioContext only when sites are going to produce
sound, so we would show doorhanger to ask users whether they want to allow autoplay.

We delay the AudioContext's state transition from `suspended` to `running` until
(1) user click 'Allow' button in doorhanger
(2) user interact with sites, and then AudioContext calls resume() again

Differential Revision: https://phabricator.services.mozilla.com/D5610

--HG--
extra : moz-landing-system : lando
2018-09-13 16:51:07 +00:00

226 lines
6.2 KiB
C++

/* -*- 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/Logging.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"
mozilla::LazyLogModule gAutoplayPermissionLog("Autoplay");
#define AUTOPLAY_LOG(msg, ...) \
MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
static const char*
AllowAutoplayToStr(const uint32_t state)
{
switch (state) {
case nsIAutoplay::ALLOWED:
return "allowed";
case nsIAutoplay::BLOCKED:
return "blocked";
case nsIAutoplay::PROMPT:
return "prompt";
default:
return "unknown";
}
}
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_LOG("Allow autoplay as document has autoplay permission.");
return true;
}
if (approver->HasBeenUserGestureActivated()) {
AUTOPLAY_LOG("Allow autoplay as document activated by user gesture.");
return true;
}
if (approver->IsExtensionPage()) {
AUTOPLAY_LOG("Allow autoplay as in extension document.");
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)
{
if ((aElement.Volume() == 0.0 || aElement.Muted()) &&
Preferences::GetBool("media.autoplay.allow-muted", true)) {
AUTOPLAY_LOG("Allow muted media %p to autoplay.", &aElement);
return true;
}
if (IsWindowAllowedToPlay(aElement.OwnerDoc()->GetInnerWindow())) {
AUTOPLAY_LOG("Autoplay allowed as activated/whitelisted window, media %p.", &aElement);
return true;
}
nsIDocument* topDocument = ApproverDocOf(*aElement.OwnerDoc());
if (topDocument &&
topDocument->MediaDocumentKind() == nsIDocument::MediaDocumentKind::Video) {
AUTOPLAY_LOG("Allow video document %p to autoplay", &aElement);
return true;
}
if (!aElement.HasAudio() &&
aElement.ReadyState() >= HTMLMediaElement_Binding::HAVE_METADATA) {
AUTOPLAY_LOG("Allow media %p without audio track to autoplay", &aElement);
return true;
}
if (!aElement.HasAudio() &&
aElement.ReadyState() >= HTMLMediaElement_Binding::HAVE_METADATA) {
AUTOPLAY_LOG("Allow media without audio track %p to autoplay\n", &aElement);
return true;
}
return false;
}
/* static */ bool
AutoplayPolicy::WouldBeAllowedToPlayIfAutoplayDisabled(const HTMLMediaElement& aElement)
{
return IsMediaElementAllowedToPlay(aElement);
}
/* static */ bool
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());
}
if (IsMediaElementAllowedToPlay(aElement)) {
return true;
}
const bool result = IsMediaElementAllowedToPlay(aElement) ||
autoplayDefault == nsIAutoplay::ALLOWED;
AUTOPLAY_LOG("IsAllowedToPlay, mediaElement=%p, isAllowToPlay=%s",
&aElement, AllowAutoplayToStr(result));
return result;
}
/* static */ bool
AutoplayPolicy::IsAllowedToPlay(const 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.GetParentObject())) {
return true;
}
return false;
}
} // namespace dom
} // namespace mozilla