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