Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/media/eme/MediaKeySystemAccessManager.cpp
Line
Count
Source (jump to first uncovered line)
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include "MediaKeySystemAccessManager.h"
6
#include "DecoderDoctorDiagnostics.h"
7
#include "mozilla/EMEUtils.h"
8
#include "nsServiceManagerUtils.h"
9
#include "nsComponentManagerUtils.h"
10
#include "nsIObserverService.h"
11
#include "mozilla/Services.h"
12
#include "mozilla/StaticPrefs.h"
13
#include "mozilla/DetailedPromise.h"
14
#ifdef XP_WIN
15
#include "mozilla/WindowsVersion.h"
16
#endif
17
#ifdef XP_MACOSX
18
#include "nsCocoaFeatures.h"
19
#endif
20
#include "nsPrintfCString.h"
21
#include "nsContentUtils.h"
22
#include "nsIScriptError.h"
23
#include "mozilla/Unused.h"
24
#include "nsDataHashtable.h"
25
26
namespace mozilla {
27
namespace dom {
28
29
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeySystemAccessManager)
30
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
31
0
  NS_INTERFACE_MAP_ENTRY(nsIObserver)
32
0
NS_INTERFACE_MAP_END
33
34
NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccessManager)
35
NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccessManager)
36
37
NS_IMPL_CYCLE_COLLECTION_CLASS(MediaKeySystemAccessManager)
38
39
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaKeySystemAccessManager)
40
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
41
0
  for (size_t i = 0; i < tmp->mRequests.Length(); i++) {
42
0
    tmp->mRequests[i].RejectPromise(NS_LITERAL_CSTRING("Promise still outstanding at MediaKeySystemAccessManager GC"));
43
0
    tmp->mRequests[i].CancelTimer();
44
0
    NS_IMPL_CYCLE_COLLECTION_UNLINK(mRequests[i].mPromise)
45
0
  }
46
0
  tmp->mRequests.Clear();
47
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
48
49
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaKeySystemAccessManager)
50
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
51
0
  for (size_t i = 0; i < tmp->mRequests.Length(); i++) {
52
0
    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRequests[i].mPromise)
53
0
  }
54
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
55
56
MediaKeySystemAccessManager::MediaKeySystemAccessManager(nsPIDOMWindowInner* aWindow)
57
  : mWindow(aWindow)
58
  , mAddedObservers(false)
59
0
{
60
0
}
61
62
MediaKeySystemAccessManager::~MediaKeySystemAccessManager()
63
0
{
64
0
  Shutdown();
65
0
}
66
67
void
68
MediaKeySystemAccessManager::Request(DetailedPromise* aPromise,
69
                                     const nsAString& aKeySystem,
70
                                     const Sequence<MediaKeySystemConfiguration>& aConfigs)
71
0
{
72
0
  Request(aPromise, aKeySystem, aConfigs, RequestType::Initial);
73
0
}
74
75
void
76
MediaKeySystemAccessManager::Request(DetailedPromise* aPromise,
77
                                     const nsAString& aKeySystem,
78
                                     const Sequence<MediaKeySystemConfiguration>& aConfigs,
79
                                     RequestType aType)
80
0
{
81
0
  EME_LOG("MediaKeySystemAccessManager::Request %s", NS_ConvertUTF16toUTF8(aKeySystem).get());
82
0
83
0
  if (aKeySystem.IsEmpty()) {
84
0
    aPromise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
85
0
                          NS_LITERAL_CSTRING("Key system string is empty"));
86
0
    // Don't notify DecoderDoctor, as there's nothing we or the user can
87
0
    // do to fix this situation; the site is using the API wrong.
88
0
    return;
89
0
  }
90
0
  if (aConfigs.IsEmpty()) {
91
0
    aPromise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
92
0
                          NS_LITERAL_CSTRING("Candidate MediaKeySystemConfigs is empty"));
93
0
    // Don't notify DecoderDoctor, as there's nothing we or the user can
94
0
    // do to fix this situation; the site is using the API wrong.
95
0
    return;
96
0
  }
97
0
98
0
  DecoderDoctorDiagnostics diagnostics;
99
0
100
0
  // Ensure keysystem is supported.
101
0
  if (!IsWidevineKeySystem(aKeySystem) && !IsClearkeyKeySystem(aKeySystem)) {
102
0
    // Not to inform user, because nothing to do if the keySystem is not
103
0
    // supported.
104
0
    aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
105
0
                          NS_LITERAL_CSTRING("Key system is unsupported"));
106
0
    diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
107
0
                                          aKeySystem, false, __func__);
108
0
    return;
109
0
  }
110
0
111
0
  if (!StaticPrefs::MediaEmeEnabled() && !IsClearkeyKeySystem(aKeySystem)) {
112
0
    // EME disabled by user, send notification to chrome so UI can inform user.
113
0
    // Clearkey is allowed even when EME is disabled because we want the pref
114
0
    // "media.eme.enabled" only taking effect on proprietary DRMs.
115
0
    MediaKeySystemAccess::NotifyObservers(mWindow,
116
0
                                          aKeySystem,
117
0
                                          MediaKeySystemStatus::Api_disabled);
118
0
    aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
119
0
                          NS_LITERAL_CSTRING("EME has been preffed off"));
120
0
    diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
121
0
                                          aKeySystem, false, __func__);
122
0
    return;
123
0
  }
124
0
125
0
  nsAutoCString message;
126
0
  MediaKeySystemStatus status =
127
0
    MediaKeySystemAccess::GetKeySystemStatus(aKeySystem, message);
128
0
129
0
  nsPrintfCString msg("MediaKeySystemAccess::GetKeySystemStatus(%s) "
130
0
                      "result=%s msg='%s'",
131
0
                      NS_ConvertUTF16toUTF8(aKeySystem).get(),
132
0
                      MediaKeySystemStatusValues::strings[(size_t)status].value,
133
0
                      message.get());
134
0
  LogToBrowserConsole(NS_ConvertUTF8toUTF16(msg));
135
0
136
0
  if (status == MediaKeySystemStatus::Cdm_not_installed &&
137
0
      IsWidevineKeySystem(aKeySystem)) {
138
0
    // These are cases which could be resolved by downloading a new(er) CDM.
139
0
    // When we send the status to chrome, chrome's GMPProvider will attempt to
140
0
    // download or update the CDM. In AwaitInstall() we add listeners to wait
141
0
    // for the update to complete, and we'll call this function again with
142
0
    // aType==Subsequent once the download has completed and the GMPService
143
0
    // has had a new plugin added. AwaitInstall() sets a timer to fail if the
144
0
    // update/download takes too long or fails.
145
0
    if (aType == RequestType::Initial &&
146
0
        AwaitInstall(aPromise, aKeySystem, aConfigs)) {
147
0
      // Notify chrome that we're going to wait for the CDM to download/update.
148
0
      // Note: If we're re-trying, we don't re-send the notification,
149
0
      // as chrome is already displaying the "we can't play, updating"
150
0
      // notification.
151
0
      MediaKeySystemAccess::NotifyObservers(mWindow, aKeySystem, status);
152
0
    } else {
153
0
      // We waited or can't wait for an update and we still can't service
154
0
      // the request. Give up. Chrome will still be showing a "I can't play,
155
0
      // updating" notification.
156
0
      aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
157
0
                            NS_LITERAL_CSTRING("Gave up while waiting for a CDM update"));
158
0
    }
159
0
    diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
160
0
                                          aKeySystem, false, __func__);
161
0
    return;
162
0
  }
163
0
  if (status != MediaKeySystemStatus::Available) {
164
0
    // Failed due to user disabling something, send a notification to
165
0
    // chrome, so we can show some UI to explain how the user can rectify
166
0
    // the situation.
167
0
    MediaKeySystemAccess::NotifyObservers(mWindow, aKeySystem, status);
168
0
    aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR, message);
169
0
    return;
170
0
  }
171
0
172
0
  nsCOMPtr<nsIDocument> doc = mWindow->GetExtantDoc();
173
0
  nsDataHashtable<nsCharPtrHashKey, bool> warnings;
174
0
  std::function<void(const char*)> deprecationWarningLogFn =
175
0
    [&](const char* aMsgName) {
176
0
      EME_LOG("Logging deprecation warning '%s' to WebConsole.", aMsgName);
177
0
      warnings.Put(aMsgName, true);
178
0
      nsString uri;
179
0
      if (doc) {
180
0
        Unused << doc->GetDocumentURI(uri);
181
0
      }
182
0
      const char16_t* params[] = { uri.get() };
183
0
      nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
184
0
                                      NS_LITERAL_CSTRING("Media"),
185
0
                                      doc,
186
0
                                      nsContentUtils::eDOM_PROPERTIES,
187
0
                                      aMsgName,
188
0
                                      params,
189
0
                                      ArrayLength(params));
190
0
    };
191
0
192
0
  bool isPrivateBrowsing =
193
0
    mWindow->GetExtantDoc() &&
194
0
    mWindow->GetExtantDoc()->NodePrincipal()->GetPrivateBrowsingId() > 0;
195
0
  MediaKeySystemConfiguration config;
196
0
  if (MediaKeySystemAccess::GetSupportedConfig(
197
0
        aKeySystem, aConfigs, config, &diagnostics, isPrivateBrowsing, deprecationWarningLogFn)) {
198
0
    RefPtr<MediaKeySystemAccess> access(
199
0
      new MediaKeySystemAccess(mWindow, aKeySystem, config));
200
0
    aPromise->MaybeResolve(access);
201
0
    diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
202
0
                                          aKeySystem, true, __func__);
203
0
204
0
    // Accumulate telemetry to report whether we hit deprecation warnings.
205
0
    if (warnings.Get("MediaEMENoCapabilitiesDeprecatedWarning")) {
206
0
      Telemetry::Accumulate(
207
0
        Telemetry::HistogramID::MEDIA_EME_REQUEST_DEPRECATED_WARNINGS, 1);
208
0
      EME_LOG("MEDIA_EME_REQUEST_DEPRECATED_WARNINGS "
209
0
              "MediaEMENoCapabilitiesDeprecatedWarning");
210
0
    } else if (warnings.Get("MediaEMENoCodecsDeprecatedWarning")) {
211
0
      Telemetry::Accumulate(
212
0
        Telemetry::HistogramID::MEDIA_EME_REQUEST_DEPRECATED_WARNINGS, 2);
213
0
      EME_LOG("MEDIA_EME_REQUEST_DEPRECATED_WARNINGS "
214
0
              "MediaEMENoCodecsDeprecatedWarning");
215
0
    } else {
216
0
      Telemetry::Accumulate(
217
0
        Telemetry::HistogramID::MEDIA_EME_REQUEST_DEPRECATED_WARNINGS, 0);
218
0
      EME_LOG("MEDIA_EME_REQUEST_DEPRECATED_WARNINGS No warnings");
219
0
    }
220
0
    return;
221
0
  }
222
0
  // Not to inform user, because nothing to do if the corresponding keySystem
223
0
  // configuration is not supported.
224
0
  aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
225
0
                        NS_LITERAL_CSTRING("Key system configuration is not supported"));
226
0
  diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
227
0
                                        aKeySystem, false, __func__);
228
0
}
229
230
MediaKeySystemAccessManager::PendingRequest::PendingRequest(DetailedPromise* aPromise,
231
                                                            const nsAString& aKeySystem,
232
                                                            const Sequence<MediaKeySystemConfiguration>& aConfigs,
233
                                                            nsITimer* aTimer)
234
  : mPromise(aPromise)
235
  , mKeySystem(aKeySystem)
236
  , mConfigs(aConfigs)
237
  , mTimer(aTimer)
238
0
{
239
0
  MOZ_COUNT_CTOR(MediaKeySystemAccessManager::PendingRequest);
240
0
}
241
242
MediaKeySystemAccessManager::PendingRequest::PendingRequest(const PendingRequest& aOther)
243
  : mPromise(aOther.mPromise)
244
  , mKeySystem(aOther.mKeySystem)
245
  , mConfigs(aOther.mConfigs)
246
  , mTimer(aOther.mTimer)
247
0
{
248
0
  MOZ_COUNT_CTOR(MediaKeySystemAccessManager::PendingRequest);
249
0
}
250
251
MediaKeySystemAccessManager::PendingRequest::~PendingRequest()
252
0
{
253
0
  MOZ_COUNT_DTOR(MediaKeySystemAccessManager::PendingRequest);
254
0
}
255
256
void
257
MediaKeySystemAccessManager::PendingRequest::CancelTimer()
258
0
{
259
0
  if (mTimer) {
260
0
    mTimer->Cancel();
261
0
  }
262
0
}
263
264
void
265
MediaKeySystemAccessManager::PendingRequest::RejectPromise(const nsCString& aReason)
266
0
{
267
0
  if (mPromise) {
268
0
    mPromise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR, aReason);
269
0
  }
270
0
}
271
272
bool
273
MediaKeySystemAccessManager::AwaitInstall(DetailedPromise* aPromise,
274
                                          const nsAString& aKeySystem,
275
                                          const Sequence<MediaKeySystemConfiguration>& aConfigs)
276
0
{
277
0
  EME_LOG("MediaKeySystemAccessManager::AwaitInstall %s", NS_ConvertUTF16toUTF8(aKeySystem).get());
278
0
279
0
  if (!EnsureObserversAdded()) {
280
0
    NS_WARNING("Failed to add pref observer");
281
0
    return false;
282
0
  }
283
0
284
0
  nsCOMPtr<nsITimer> timer;
285
0
  NS_NewTimerWithObserver(getter_AddRefs(timer),
286
0
                          this, 60 * 1000, nsITimer::TYPE_ONE_SHOT);
287
0
  if (!timer) {
288
0
    NS_WARNING("Failed to create timer to await CDM install.");
289
0
    return false;
290
0
  }
291
0
292
0
  mRequests.AppendElement(PendingRequest(aPromise, aKeySystem, aConfigs, timer));
293
0
  return true;
294
0
}
295
296
void
297
MediaKeySystemAccessManager::RetryRequest(PendingRequest& aRequest)
298
0
{
299
0
  aRequest.CancelTimer();
300
0
  Request(aRequest.mPromise, aRequest.mKeySystem, aRequest.mConfigs, RequestType::Subsequent);
301
0
}
302
303
nsresult
304
MediaKeySystemAccessManager::Observe(nsISupports* aSubject,
305
                                     const char* aTopic,
306
                                     const char16_t* aData)
307
0
{
308
0
  EME_LOG("MediaKeySystemAccessManager::Observe %s", aTopic);
309
0
310
0
  if (!strcmp(aTopic, "gmp-changed")) {
311
0
    // Filter out the requests where the CDM's install-status is no longer
312
0
    // "unavailable". This will be the CDMs which have downloaded since the
313
0
    // initial request.
314
0
    // Note: We don't have a way to communicate from chrome that the CDM has
315
0
    // failed to download, so we'll just let the timeout fail us in that case.
316
0
    nsTArray<PendingRequest> requests;
317
0
    for (size_t i = mRequests.Length(); i-- > 0; ) {
318
0
      PendingRequest& request = mRequests[i];
319
0
      nsAutoCString message;
320
0
      MediaKeySystemStatus status =
321
0
        MediaKeySystemAccess::GetKeySystemStatus(request.mKeySystem, message);
322
0
      if (status == MediaKeySystemStatus::Cdm_not_installed) {
323
0
        // Not yet installed, don't retry. Keep waiting until timeout.
324
0
        continue;
325
0
      }
326
0
      // Status has changed, retry request.
327
0
      requests.AppendElement(std::move(request));
328
0
      mRequests.RemoveElementAt(i);
329
0
    }
330
0
    // Retry all pending requests, but this time fail if the CDM is not installed.
331
0
    for (PendingRequest& request : requests) {
332
0
      RetryRequest(request);
333
0
    }
334
0
  } else if (!strcmp(aTopic, "timer-callback")) {
335
0
    // Find the timer that expired and re-run the request for it.
336
0
    nsCOMPtr<nsITimer> timer(do_QueryInterface(aSubject));
337
0
    for (size_t i = 0; i < mRequests.Length(); i++) {
338
0
      if (mRequests[i].mTimer == timer) {
339
0
        EME_LOG("MediaKeySystemAccessManager::AwaitInstall resuming request");
340
0
        PendingRequest request = mRequests[i];
341
0
        mRequests.RemoveElementAt(i);
342
0
        RetryRequest(request);
343
0
        break;
344
0
      }
345
0
    }
346
0
  }
347
0
  return NS_OK;
348
0
}
349
350
bool
351
MediaKeySystemAccessManager::EnsureObserversAdded()
352
0
{
353
0
  if (mAddedObservers) {
354
0
    return true;
355
0
  }
356
0
357
0
  nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
358
0
  if (NS_WARN_IF(!obsService)) {
359
0
    return false;
360
0
  }
361
0
  mAddedObservers = NS_SUCCEEDED(obsService->AddObserver(this, "gmp-changed", false));
362
0
  return mAddedObservers;
363
0
}
364
365
void
366
MediaKeySystemAccessManager::Shutdown()
367
0
{
368
0
  EME_LOG("MediaKeySystemAccessManager::Shutdown");
369
0
  nsTArray<PendingRequest> requests(std::move(mRequests));
370
0
  for (PendingRequest& request : requests) {
371
0
    // Cancel all requests; we're shutting down.
372
0
    request.CancelTimer();
373
0
    request.RejectPromise(NS_LITERAL_CSTRING("Promise still outstanding at MediaKeySystemAccessManager shutdown"));
374
0
  }
375
0
  if (mAddedObservers) {
376
0
    nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
377
0
    if (obsService) {
378
0
      obsService->RemoveObserver(this, "gmp-changed");
379
0
      mAddedObservers = false;
380
0
    }
381
0
  }
382
0
}
383
384
} // namespace dom
385
} // namespace mozilla