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