Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/media/AutoplayPolicy.cpp
Line
Count
Source (jump to first uncovered line)
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/EventStateManager.h"
10
#include "mozilla/Logging.h"
11
#include "mozilla/Preferences.h"
12
#include "mozilla/dom/AudioContext.h"
13
#include "mozilla/AutoplayPermissionManager.h"
14
#include "mozilla/dom/HTMLMediaElement.h"
15
#include "mozilla/dom/HTMLMediaElementBinding.h"
16
#include "nsGlobalWindowInner.h"
17
#include "nsIAutoplay.h"
18
#include "nsContentUtils.h"
19
#include "nsIDocument.h"
20
#include "MediaManager.h"
21
#include "nsIDocShell.h"
22
#include "nsIDocShellTreeItem.h"
23
#include "nsPIDOMWindow.h"
24
25
mozilla::LazyLogModule gAutoplayPermissionLog("Autoplay");
26
27
#define AUTOPLAY_LOG(msg, ...)                                             \
28
0
  MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
29
30
static const char*
31
AllowAutoplayToStr(const uint32_t state)
32
{
33
  switch (state) {
34
    case nsIAutoplay::ALLOWED:
35
      return "allowed";
36
    case nsIAutoplay::BLOCKED:
37
      return "blocked";
38
    case nsIAutoplay::PROMPT:
39
      return "prompt";
40
    default:
41
      return "unknown";
42
  }
43
}
44
45
namespace mozilla {
46
namespace dom {
47
48
static nsIDocument*
49
ApproverDocOf(const nsIDocument& aDocument)
50
0
{
51
0
  nsCOMPtr<nsIDocShell> ds = aDocument.GetDocShell();
52
0
  if (!ds) {
53
0
    return nullptr;
54
0
  }
55
0
56
0
  nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
57
0
  ds->GetSameTypeRootTreeItem(getter_AddRefs(rootTreeItem));
58
0
  if (!rootTreeItem) {
59
0
    return nullptr;
60
0
  }
61
0
62
0
  return rootTreeItem->GetDocument();
63
0
}
64
65
static bool
66
IsActivelyCapturingOrHasAPermission(nsPIDOMWindowInner* aWindow)
67
0
{
68
0
  // Pages which have been granted permission to capture WebRTC camera or
69
0
  // microphone or screen are assumed to be trusted, and are allowed to autoplay.
70
0
  if (MediaManager::GetIfExists()) {
71
0
    return MediaManager::GetIfExists()->IsActivelyCapturingOrHasAPermission(aWindow->WindowID());
72
0
  }
73
0
74
0
  auto principal = nsGlobalWindowInner::Cast(aWindow)->GetPrincipal();
75
0
  return (nsContentUtils::IsExactSitePermAllow(principal, "camera") ||
76
0
          nsContentUtils::IsExactSitePermAllow(principal, "microphone") ||
77
0
          nsContentUtils::IsExactSitePermAllow(principal, "screen"));
78
0
}
79
80
static bool
81
IsWindowAllowedToPlay(nsPIDOMWindowInner* aWindow)
82
0
{
83
0
  if (!aWindow) {
84
0
    return false;
85
0
  }
86
0
87
0
  if (IsActivelyCapturingOrHasAPermission(aWindow)) {
88
0
    AUTOPLAY_LOG("Allow autoplay as document has camera or microphone or screen"
89
0
                 " permission.");
90
0
    return true;
91
0
  }
92
0
93
0
  if (!aWindow->GetExtantDoc()) {
94
0
    return false;
95
0
  }
96
0
97
0
  nsIDocument* approver = ApproverDocOf(*aWindow->GetExtantDoc());
98
0
  if (nsContentUtils::IsExactSitePermAllow(approver->NodePrincipal(),
99
0
                                           "autoplay-media")) {
100
0
    AUTOPLAY_LOG("Allow autoplay as document has autoplay permission.");
101
0
    return true;
102
0
  }
103
0
104
0
  if (approver->HasBeenUserGestureActivated()) {
105
0
    AUTOPLAY_LOG("Allow autoplay as document activated by user gesture.");
106
0
    return true;
107
0
  }
108
0
109
0
  if (approver->IsExtensionPage()) {
110
0
    AUTOPLAY_LOG("Allow autoplay as in extension document.");
111
0
    return true;
112
0
  }
113
0
114
0
  return false;
115
0
}
116
117
/* static */
118
already_AddRefed<AutoplayPermissionManager>
119
AutoplayPolicy::RequestFor(const nsIDocument& aDocument)
120
0
{
121
0
  nsIDocument* document = ApproverDocOf(aDocument);
122
0
  if (!document) {
123
0
    return nullptr;
124
0
  }
125
0
  nsPIDOMWindowInner* window = document->GetInnerWindow();
126
0
  if (!window) {
127
0
    return nullptr;
128
0
  }
129
0
  return window->GetAutoplayPermissionManager();
130
0
}
131
132
static uint32_t
133
DefaultAutoplayBehaviour()
134
0
{
135
0
  int prefValue = Preferences::GetInt("media.autoplay.default", nsIAutoplay::ALLOWED);
136
0
  if (prefValue < nsIAutoplay::ALLOWED || prefValue > nsIAutoplay::PROMPT) {
137
0
    // Invalid pref values are just converted to ALLOWED.
138
0
    return nsIAutoplay::ALLOWED;
139
0
  }
140
0
  return prefValue;
141
0
}
142
143
static bool
144
IsMediaElementAllowedToPlay(const HTMLMediaElement& aElement)
145
0
{
146
0
  if ((aElement.Volume() == 0.0 || aElement.Muted()) &&
147
0
       Preferences::GetBool("media.autoplay.allow-muted", true)) {
148
0
    AUTOPLAY_LOG("Allow muted media %p to autoplay.", &aElement);
149
0
    return true;
150
0
  }
151
0
152
0
  if (IsWindowAllowedToPlay(aElement.OwnerDoc()->GetInnerWindow())) {
153
0
    AUTOPLAY_LOG("Autoplay allowed as activated/whitelisted window, media %p.", &aElement);
154
0
    return true;
155
0
  }
156
0
157
0
  nsIDocument* topDocument = ApproverDocOf(*aElement.OwnerDoc());
158
0
  if (topDocument &&
159
0
      topDocument->MediaDocumentKind() == nsIDocument::MediaDocumentKind::Video) {
160
0
    AUTOPLAY_LOG("Allow video document %p to autoplay", &aElement);
161
0
    return true;
162
0
  }
163
0
164
0
  if (!aElement.HasAudio() &&
165
0
      aElement.ReadyState() >= HTMLMediaElement_Binding::HAVE_METADATA) {
166
0
    AUTOPLAY_LOG("Allow media %p without audio track to autoplay", &aElement);
167
0
    return true;
168
0
  }
169
0
170
0
  if (!aElement.HasAudio() &&
171
0
      aElement.ReadyState() >= HTMLMediaElement_Binding::HAVE_METADATA) {
172
0
    AUTOPLAY_LOG("Allow media without audio track %p to autoplay\n", &aElement);
173
0
    return true;
174
0
  }
175
0
176
0
  return false;
177
0
}
178
179
/* static */ bool
180
AutoplayPolicy::WouldBeAllowedToPlayIfAutoplayDisabled(const HTMLMediaElement& aElement)
181
0
{
182
0
  return IsMediaElementAllowedToPlay(aElement);
183
0
}
184
185
/* static */ bool
186
AutoplayPolicy::IsAllowedToPlay(const HTMLMediaElement& aElement)
187
0
{
188
0
  const uint32_t autoplayDefault = DefaultAutoplayBehaviour();
189
0
  // TODO : this old way would be removed when user-gestures-needed becomes
190
0
  // as a default option to block autoplay.
191
0
  if (!Preferences::GetBool("media.autoplay.enabled.user-gestures-needed", false)) {
192
0
    // If element is blessed, it would always be allowed to play().
193
0
    return (autoplayDefault == nsIAutoplay::ALLOWED ||
194
0
            aElement.IsBlessed() ||
195
0
            EventStateManager::IsHandlingUserInput());
196
0
  }
197
0
198
0
  if (IsMediaElementAllowedToPlay(aElement)) {
199
0
    return true;
200
0
  }
201
0
202
0
  const bool result = IsMediaElementAllowedToPlay(aElement) ||
203
0
    autoplayDefault == nsIAutoplay::ALLOWED;
204
0
205
0
  AUTOPLAY_LOG("IsAllowedToPlay, mediaElement=%p, isAllowToPlay=%s",
206
0
                &aElement, AllowAutoplayToStr(result));
207
0
208
0
  return result;
209
0
}
210
211
/* static */ bool
212
AutoplayPolicy::IsAllowedToPlay(const AudioContext& aContext)
213
0
{
214
0
  if (!Preferences::GetBool("media.autoplay.block-webaudio", false)) {
215
0
    return true;
216
0
  }
217
0
218
0
  if (DefaultAutoplayBehaviour() == nsIAutoplay::ALLOWED) {
219
0
    return true;
220
0
  }
221
0
222
0
  if (!Preferences::GetBool("media.autoplay.enabled.user-gestures-needed", false)) {
223
0
    return true;
224
0
  }
225
0
226
0
  // Offline context won't directly output sound to audio devices.
227
0
  if (aContext.IsOffline()) {
228
0
    return true;
229
0
  }
230
0
231
0
  if (IsWindowAllowedToPlay(aContext.GetParentObject())) {
232
0
    return true;
233
0
  }
234
0
235
0
  return false;
236
0
}
237
238
} // namespace dom
239
} // namespace mozilla