gecko-dev/dom/media/AutoplayPolicy.cpp

173 строки
4.7 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/Preferences.h"
#include "mozilla/dom/AudioContext.h"
Bug 1472580 - Ensure we always get a allow/cancel response to an autoplay media permission request. r=smaug The front end code can't always guarantee to give us an allow/cancel response to a permission request. In particular in these cases: * if we close a tab while showing a doorhanger, or * if we navigate a tab while showing a doorhanger, or * if the permission prompt requested in a background tab and never shown. Handling all of these cases is problematic; we don't get events for all of these where it's easy and cheap to determine that we should cancel the permission request. Canceling the permission request is important in the autoplay-media permission request case as there's objects waiting on the resolution of the permission request, and they leak in ASan builds while running chrome tests if the Gecko size of the permission request doesn't get a notification telling it to stop waiting. But we can however rely on the doorhanger code to drop its reference to the nsIContentPermissionRequest object that we pass to it when the doorhanger goes away. So we can cancel the permission request in our nsIContentPermissionRequest's implementation's destructor in order to easily catch all the above cases. In order to do that, we need to split AutoplayRequest into two; one part being the implementation of nsIContentPermissionRequest (AutoplayPermissionRequest), and the other part being the code to own the PromiseHolder and manage the permission request (AutoplayPermissionManager). AutoplayPermissionRequest keeps a weak reference to AutoplayPermissionManager, so that it can tell the AutoplayPermissionManager to reject the request promise when it's destroyed. This fixes the ASan leak for which I got backed out from earlier in this bug, and also fixes the cases above. MozReview-Commit-ID: KoVkgIqDleW --HG-- rename : dom/html/AutoplayRequest.cpp => dom/html/AutoplayPermissionManager.cpp rename : dom/html/AutoplayRequest.h => dom/html/AutoplayPermissionManager.h extra : rebase_source : dbca520a93d8c416f6d64c2da027630181bb5910
2018-07-06 12:15:20 +03:00
#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;
}
if (approver->IsExtensionPage()) {
// Always allow extension page to autoplay.
return true;
}
return false;
}
/* static */
Bug 1472580 - Ensure we always get a allow/cancel response to an autoplay media permission request. r=smaug The front end code can't always guarantee to give us an allow/cancel response to a permission request. In particular in these cases: * if we close a tab while showing a doorhanger, or * if we navigate a tab while showing a doorhanger, or * if the permission prompt requested in a background tab and never shown. Handling all of these cases is problematic; we don't get events for all of these where it's easy and cheap to determine that we should cancel the permission request. Canceling the permission request is important in the autoplay-media permission request case as there's objects waiting on the resolution of the permission request, and they leak in ASan builds while running chrome tests if the Gecko size of the permission request doesn't get a notification telling it to stop waiting. But we can however rely on the doorhanger code to drop its reference to the nsIContentPermissionRequest object that we pass to it when the doorhanger goes away. So we can cancel the permission request in our nsIContentPermissionRequest's implementation's destructor in order to easily catch all the above cases. In order to do that, we need to split AutoplayRequest into two; one part being the implementation of nsIContentPermissionRequest (AutoplayPermissionRequest), and the other part being the code to own the PromiseHolder and manage the permission request (AutoplayPermissionManager). AutoplayPermissionRequest keeps a weak reference to AutoplayPermissionManager, so that it can tell the AutoplayPermissionManager to reject the request promise when it's destroyed. This fixes the ASan leak for which I got backed out from earlier in this bug, and also fixes the cases above. MozReview-Commit-ID: KoVkgIqDleW --HG-- rename : dom/html/AutoplayRequest.cpp => dom/html/AutoplayPermissionManager.cpp rename : dom/html/AutoplayRequest.h => dom/html/AutoplayPermissionManager.h extra : rebase_source : dbca520a93d8c416f6d64c2da027630181bb5910
2018-07-06 12:15:20 +03:00
already_AddRefed<AutoplayPermissionManager>
AutoplayPolicy::RequestFor(const nsIDocument& aDocument)
{
nsIDocument* document = ApproverDocOf(aDocument);
if (!document) {
return nullptr;
}
nsPIDOMWindowInner* window = document->GetInnerWindow();
if (!window) {
return nullptr;
}
Bug 1472580 - Ensure we always get a allow/cancel response to an autoplay media permission request. r=smaug The front end code can't always guarantee to give us an allow/cancel response to a permission request. In particular in these cases: * if we close a tab while showing a doorhanger, or * if we navigate a tab while showing a doorhanger, or * if the permission prompt requested in a background tab and never shown. Handling all of these cases is problematic; we don't get events for all of these where it's easy and cheap to determine that we should cancel the permission request. Canceling the permission request is important in the autoplay-media permission request case as there's objects waiting on the resolution of the permission request, and they leak in ASan builds while running chrome tests if the Gecko size of the permission request doesn't get a notification telling it to stop waiting. But we can however rely on the doorhanger code to drop its reference to the nsIContentPermissionRequest object that we pass to it when the doorhanger goes away. So we can cancel the permission request in our nsIContentPermissionRequest's implementation's destructor in order to easily catch all the above cases. In order to do that, we need to split AutoplayRequest into two; one part being the implementation of nsIContentPermissionRequest (AutoplayPermissionRequest), and the other part being the code to own the PromiseHolder and manage the permission request (AutoplayPermissionManager). AutoplayPermissionRequest keeps a weak reference to AutoplayPermissionManager, so that it can tell the AutoplayPermissionManager to reject the request promise when it's destroyed. This fixes the ASan leak for which I got backed out from earlier in this bug, and also fixes the cases above. MozReview-Commit-ID: KoVkgIqDleW --HG-- rename : dom/html/AutoplayRequest.cpp => dom/html/AutoplayPermissionManager.cpp rename : dom/html/AutoplayRequest.h => dom/html/AutoplayPermissionManager.h extra : rebase_source : dbca520a93d8c416f6d64c2da027630181bb5910
2018-07-06 12:15:20 +03:00
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