gecko-dev/dom/media/AutoplayPolicy.cpp
alwu bb11a649df Bug 1483703 - part1 : allow media without audio track to autoplay. r=cpearce
We would allow media without audio track to autoplay after it had loaded the metadata.

If media hasn't loaded metadata yet, we would treat it as audible media and then block it.

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

--HG--
extra : moz-landing-system : lando
2018-08-22 23:31:58 +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::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