/src/mozilla-central/dom/media/eme/MediaKeys.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 "mozilla/dom/MediaKeys.h" |
8 | | #include "GMPCrashHelper.h" |
9 | | #include "mozilla/dom/HTMLMediaElement.h" |
10 | | #include "mozilla/dom/MediaKeysBinding.h" |
11 | | #include "mozilla/dom/MediaKeyMessageEvent.h" |
12 | | #include "mozilla/dom/MediaKeyError.h" |
13 | | #include "mozilla/dom/MediaKeySession.h" |
14 | | #include "mozilla/dom/MediaKeyStatusMap.h" |
15 | | #include "mozilla/dom/DOMException.h" |
16 | | #include "mozilla/dom/UnionTypes.h" |
17 | | #include "mozilla/Telemetry.h" |
18 | | #ifdef MOZ_WIDGET_ANDROID |
19 | | #include "mozilla/MediaDrmCDMProxy.h" |
20 | | #endif |
21 | | #include "mozilla/EMEUtils.h" |
22 | | #include "nsContentUtils.h" |
23 | | #include "nsIScriptObjectPrincipal.h" |
24 | | #include "nsContentTypeParser.h" |
25 | | #ifdef XP_WIN |
26 | | #include "mozilla/WindowsVersion.h" |
27 | | #endif |
28 | | #include "nsContentCID.h" |
29 | | #include "nsServiceManagerUtils.h" |
30 | | #include "mozilla/dom/MediaKeySystemAccess.h" |
31 | | #include "nsPrintfCString.h" |
32 | | #include "ChromiumCDMProxy.h" |
33 | | |
34 | | namespace mozilla { |
35 | | |
36 | | namespace dom { |
37 | | |
38 | | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeys, |
39 | | mElement, |
40 | | mParent, |
41 | | mKeySessions, |
42 | | mPromises, |
43 | | mPendingSessions); |
44 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeys) |
45 | | NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeys) |
46 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeys) |
47 | 0 | NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY |
48 | 0 | NS_INTERFACE_MAP_ENTRY(nsISupports) |
49 | 0 | NS_INTERFACE_MAP_END |
50 | | |
51 | | MediaKeys::MediaKeys(nsPIDOMWindowInner* aParent, |
52 | | const nsAString& aKeySystem, |
53 | | const MediaKeySystemConfiguration& aConfig) |
54 | | : mParent(aParent) |
55 | | , mKeySystem(aKeySystem) |
56 | | , mCreatePromiseId(0) |
57 | | , mConfig(aConfig) |
58 | 0 | { |
59 | 0 | EME_LOG("MediaKeys[%p] constructed keySystem=%s", |
60 | 0 | this, NS_ConvertUTF16toUTF8(mKeySystem).get()); |
61 | 0 | } |
62 | | |
63 | | MediaKeys::~MediaKeys() |
64 | 0 | { |
65 | 0 | Shutdown(); |
66 | 0 | EME_LOG("MediaKeys[%p] destroyed", this); |
67 | 0 | } |
68 | | |
69 | | void |
70 | | MediaKeys::Terminated() |
71 | 0 | { |
72 | 0 | EME_LOG("MediaKeys[%p] CDM crashed unexpectedly", this); |
73 | 0 |
|
74 | 0 | KeySessionHashMap keySessions; |
75 | 0 | // Remove entries during iteration will screw it. Make a copy first. |
76 | 0 | for (auto iter = mKeySessions.Iter(); !iter.Done(); iter.Next()) { |
77 | 0 | RefPtr<MediaKeySession>& session = iter.Data(); |
78 | 0 | keySessions.Put(session->GetSessionId(), session); |
79 | 0 | } |
80 | 0 | for (auto iter = keySessions.Iter(); !iter.Done(); iter.Next()) { |
81 | 0 | RefPtr<MediaKeySession>& session = iter.Data(); |
82 | 0 | session->OnClosed(); |
83 | 0 | } |
84 | 0 | keySessions.Clear(); |
85 | 0 | MOZ_ASSERT(mKeySessions.Count() == 0); |
86 | 0 |
|
87 | 0 | // Notify the element about that CDM has terminated. |
88 | 0 | if (mElement) { |
89 | 0 | mElement->DecodeError(NS_ERROR_DOM_MEDIA_CDM_ERR); |
90 | 0 | } |
91 | 0 |
|
92 | 0 | Shutdown(); |
93 | 0 | } |
94 | | |
95 | | void |
96 | | MediaKeys::Shutdown() |
97 | 0 | { |
98 | 0 | if (mProxy) { |
99 | 0 | mProxy->Shutdown(); |
100 | 0 | mProxy = nullptr; |
101 | 0 | } |
102 | 0 |
|
103 | 0 | RefPtr<MediaKeys> kungFuDeathGrip = this; |
104 | 0 |
|
105 | 0 | for (auto iter = mPromises.Iter(); !iter.Done(); iter.Next()) { |
106 | 0 | RefPtr<dom::DetailedPromise>& promise = iter.Data(); |
107 | 0 | promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR, |
108 | 0 | NS_LITERAL_CSTRING("Promise still outstanding at MediaKeys shutdown")); |
109 | 0 | Release(); |
110 | 0 | } |
111 | 0 | mPromises.Clear(); |
112 | 0 | } |
113 | | |
114 | | nsPIDOMWindowInner* |
115 | | MediaKeys::GetParentObject() const |
116 | 0 | { |
117 | 0 | return mParent; |
118 | 0 | } |
119 | | |
120 | | JSObject* |
121 | | MediaKeys::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) |
122 | 0 | { |
123 | 0 | return MediaKeys_Binding::Wrap(aCx, this, aGivenProto); |
124 | 0 | } |
125 | | |
126 | | void |
127 | | MediaKeys::GetKeySystem(nsString& aOutKeySystem) const |
128 | 0 | { |
129 | 0 | aOutKeySystem.Assign(mKeySystem); |
130 | 0 | } |
131 | | |
132 | | already_AddRefed<DetailedPromise> |
133 | | MediaKeys::SetServerCertificate(const ArrayBufferViewOrArrayBuffer& aCert, ErrorResult& aRv) |
134 | 0 | { |
135 | 0 | RefPtr<DetailedPromise> promise(MakePromise(aRv, |
136 | 0 | NS_LITERAL_CSTRING("MediaKeys.setServerCertificate"))); |
137 | 0 | if (aRv.Failed()) { |
138 | 0 | return nullptr; |
139 | 0 | } |
140 | 0 | |
141 | 0 | if (!mProxy) { |
142 | 0 | NS_WARNING("Tried to use a MediaKeys without a CDM"); |
143 | 0 | promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR, |
144 | 0 | NS_LITERAL_CSTRING("Null CDM in MediaKeys.setServerCertificate()")); |
145 | 0 | return promise.forget(); |
146 | 0 | } |
147 | 0 |
|
148 | 0 | nsTArray<uint8_t> data; |
149 | 0 | CopyArrayBufferViewOrArrayBufferData(aCert, data); |
150 | 0 | if (data.IsEmpty()) { |
151 | 0 | promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR, |
152 | 0 | NS_LITERAL_CSTRING("Empty certificate passed to MediaKeys.setServerCertificate()")); |
153 | 0 | return promise.forget(); |
154 | 0 | } |
155 | 0 |
|
156 | 0 | mProxy->SetServerCertificate(StorePromise(promise), data); |
157 | 0 | return promise.forget(); |
158 | 0 | } |
159 | | |
160 | | already_AddRefed<DetailedPromise> |
161 | | MediaKeys::MakePromise(ErrorResult& aRv, const nsACString& aName) |
162 | 0 | { |
163 | 0 | nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject()); |
164 | 0 | if (!global) { |
165 | 0 | NS_WARNING("Passed non-global to MediaKeys ctor!"); |
166 | 0 | aRv.Throw(NS_ERROR_UNEXPECTED); |
167 | 0 | return nullptr; |
168 | 0 | } |
169 | 0 | return DetailedPromise::Create(global, aRv, aName); |
170 | 0 | } |
171 | | |
172 | | PromiseId |
173 | | MediaKeys::StorePromise(DetailedPromise* aPromise) |
174 | 0 | { |
175 | 0 | static uint32_t sEMEPromiseCount = 1; |
176 | 0 | MOZ_ASSERT(aPromise); |
177 | 0 | uint32_t id = sEMEPromiseCount++; |
178 | 0 |
|
179 | 0 | EME_LOG("MediaKeys[%p]::StorePromise() id=%d", this, id); |
180 | 0 |
|
181 | 0 | // Keep MediaKeys alive for the lifetime of its promises. Any still-pending |
182 | 0 | // promises are rejected in Shutdown(). |
183 | 0 | AddRef(); |
184 | 0 |
|
185 | | #ifdef DEBUG |
186 | | // We should not have already stored this promise! |
187 | | for (auto iter = mPromises.ConstIter(); !iter.Done(); iter.Next()) { |
188 | | MOZ_ASSERT(iter.Data() != aPromise); |
189 | | } |
190 | | #endif |
191 | |
|
192 | 0 | mPromises.Put(id, aPromise); |
193 | 0 | return id; |
194 | 0 | } |
195 | | |
196 | | void |
197 | | MediaKeys::ConnectPendingPromiseIdWithToken(PromiseId aId, uint32_t aToken) |
198 | 0 | { |
199 | 0 | // Should only be called from MediaKeySession::GenerateRequest. |
200 | 0 | mPromiseIdToken.Put(aId, aToken); |
201 | 0 | EME_LOG("MediaKeys[%p]::ConnectPendingPromiseIdWithToken() id=%u => token(%u)", |
202 | 0 | this, aId, aToken); |
203 | 0 | } |
204 | | |
205 | | already_AddRefed<DetailedPromise> |
206 | | MediaKeys::RetrievePromise(PromiseId aId) |
207 | 0 | { |
208 | 0 | if (!mPromises.Contains(aId)) { |
209 | 0 | NS_WARNING(nsPrintfCString("Tried to retrieve a non-existent promise id=%d", aId).get()); |
210 | 0 | return nullptr; |
211 | 0 | } |
212 | 0 | RefPtr<DetailedPromise> promise; |
213 | 0 | mPromises.Remove(aId, getter_AddRefs(promise)); |
214 | 0 | Release(); |
215 | 0 | return promise.forget(); |
216 | 0 | } |
217 | | |
218 | | void |
219 | | MediaKeys::RejectPromise(PromiseId aId, nsresult aExceptionCode, |
220 | | const nsCString& aReason) |
221 | 0 | { |
222 | 0 | EME_LOG("MediaKeys[%p]::RejectPromise(%d, 0x%" PRIx32 ")", |
223 | 0 | this, aId, static_cast<uint32_t>(aExceptionCode)); |
224 | 0 |
|
225 | 0 | RefPtr<DetailedPromise> promise(RetrievePromise(aId)); |
226 | 0 | if (!promise) { |
227 | 0 | return; |
228 | 0 | } |
229 | 0 | |
230 | 0 | // This promise could be a createSession or loadSession promise, |
231 | 0 | // so we might have a pending session waiting to be resolved into |
232 | 0 | // the promise on success. We've been directed to reject to promise, |
233 | 0 | // so we can throw away the corresponding session object. |
234 | 0 | uint32_t token = 0; |
235 | 0 | if (mPromiseIdToken.Get(aId, &token)) { |
236 | 0 | MOZ_ASSERT(mPendingSessions.Contains(token)); |
237 | 0 | mPendingSessions.Remove(token); |
238 | 0 | mPromiseIdToken.Remove(aId); |
239 | 0 | } |
240 | 0 |
|
241 | 0 | MOZ_ASSERT(NS_FAILED(aExceptionCode)); |
242 | 0 | promise->MaybeReject(aExceptionCode, aReason); |
243 | 0 |
|
244 | 0 | if (mCreatePromiseId == aId) { |
245 | 0 | // Note: This will probably destroy the MediaKeys object! |
246 | 0 | Release(); |
247 | 0 | } |
248 | 0 | } |
249 | | |
250 | | void |
251 | | MediaKeys::OnSessionIdReady(MediaKeySession* aSession) |
252 | 0 | { |
253 | 0 | if (!aSession) { |
254 | 0 | NS_WARNING("Invalid MediaKeySession passed to OnSessionIdReady()"); |
255 | 0 | return; |
256 | 0 | } |
257 | 0 | if (mKeySessions.Contains(aSession->GetSessionId())) { |
258 | 0 | NS_WARNING("MediaKeySession's made ready multiple times!"); |
259 | 0 | return; |
260 | 0 | } |
261 | 0 | if (mPendingSessions.Contains(aSession->Token())) { |
262 | 0 | NS_WARNING("MediaKeySession made ready when it wasn't waiting to be ready!"); |
263 | 0 | return; |
264 | 0 | } |
265 | 0 | if (aSession->GetSessionId().IsEmpty()) { |
266 | 0 | NS_WARNING("MediaKeySession with invalid sessionId passed to OnSessionIdReady()"); |
267 | 0 | return; |
268 | 0 | } |
269 | 0 | mKeySessions.Put(aSession->GetSessionId(), aSession); |
270 | 0 | } |
271 | | |
272 | | void |
273 | | MediaKeys::ResolvePromise(PromiseId aId) |
274 | 0 | { |
275 | 0 | EME_LOG("MediaKeys[%p]::ResolvePromise(%d)", this, aId); |
276 | 0 |
|
277 | 0 | RefPtr<DetailedPromise> promise(RetrievePromise(aId)); |
278 | 0 | MOZ_ASSERT(!mPromises.Contains(aId)); |
279 | 0 | if (!promise) { |
280 | 0 | return; |
281 | 0 | } |
282 | 0 | |
283 | 0 | uint32_t token = 0; |
284 | 0 | if (!mPromiseIdToken.Get(aId, &token)) { |
285 | 0 | promise->MaybeResolveWithUndefined(); |
286 | 0 | return; |
287 | 0 | } else if (!mPendingSessions.Contains(token)) { |
288 | 0 | // Pending session for CreateSession() should be removed when sessionId |
289 | 0 | // is ready. |
290 | 0 | promise->MaybeResolveWithUndefined(); |
291 | 0 | mPromiseIdToken.Remove(aId); |
292 | 0 | return; |
293 | 0 | } |
294 | 0 | mPromiseIdToken.Remove(aId); |
295 | 0 |
|
296 | 0 | // We should only resolve LoadSession calls via this path, |
297 | 0 | // not CreateSession() promises. |
298 | 0 | RefPtr<MediaKeySession> session; |
299 | 0 | mPendingSessions.Remove(token, getter_AddRefs(session)); |
300 | 0 | if (!session || session->GetSessionId().IsEmpty()) { |
301 | 0 | NS_WARNING("Received activation for non-existent session!"); |
302 | 0 | promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR, |
303 | 0 | NS_LITERAL_CSTRING("CDM LoadSession() returned a different session ID than requested")); |
304 | 0 | return; |
305 | 0 | } |
306 | 0 | mKeySessions.Put(session->GetSessionId(), session); |
307 | 0 | promise->MaybeResolve(session); |
308 | 0 | } |
309 | | |
310 | | class MediaKeysGMPCrashHelper : public GMPCrashHelper |
311 | | { |
312 | | public: |
313 | | explicit MediaKeysGMPCrashHelper(MediaKeys* aMediaKeys) |
314 | | : mMediaKeys(aMediaKeys) |
315 | 0 | { |
316 | 0 | MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe. |
317 | 0 | } |
318 | | already_AddRefed<nsPIDOMWindowInner> |
319 | | GetPluginCrashedEventTarget() override |
320 | 0 | { |
321 | 0 | MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe. |
322 | 0 | EME_LOG("MediaKeysGMPCrashHelper::GetPluginCrashedEventTarget()"); |
323 | 0 | return (mMediaKeys && mMediaKeys->GetParentObject()) ? |
324 | 0 | do_AddRef(mMediaKeys->GetParentObject()) : nullptr; |
325 | 0 | } |
326 | | private: |
327 | | WeakPtr<MediaKeys> mMediaKeys; |
328 | | }; |
329 | | |
330 | | already_AddRefed<CDMProxy> |
331 | | MediaKeys::CreateCDMProxy(nsIEventTarget* aMainThread) |
332 | 0 | { |
333 | 0 | RefPtr<CDMProxy> proxy; |
334 | | #ifdef MOZ_WIDGET_ANDROID |
335 | | if (IsWidevineKeySystem(mKeySystem)) { |
336 | | proxy = new MediaDrmCDMProxy(this, |
337 | | mKeySystem, |
338 | | mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required, |
339 | | mConfig.mPersistentState == MediaKeysRequirement::Required, |
340 | | aMainThread); |
341 | | } else |
342 | | #endif |
343 | | { |
344 | 0 | proxy = new ChromiumCDMProxy( |
345 | 0 | this, |
346 | 0 | mKeySystem, |
347 | 0 | new MediaKeysGMPCrashHelper(this), |
348 | 0 | mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required, |
349 | 0 | mConfig.mPersistentState == MediaKeysRequirement::Required, |
350 | 0 | aMainThread); |
351 | 0 | } |
352 | 0 | return proxy.forget(); |
353 | 0 | } |
354 | | |
355 | | already_AddRefed<DetailedPromise> |
356 | | MediaKeys::Init(ErrorResult& aRv) |
357 | 0 | { |
358 | 0 | RefPtr<DetailedPromise> promise(MakePromise(aRv, |
359 | 0 | NS_LITERAL_CSTRING("MediaKeys::Init()"))); |
360 | 0 | if (aRv.Failed()) { |
361 | 0 | return nullptr; |
362 | 0 | } |
363 | 0 | |
364 | 0 | // Determine principal (at creation time) of the MediaKeys object. |
365 | 0 | nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetParentObject()); |
366 | 0 | if (!sop) { |
367 | 0 | promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR, |
368 | 0 | NS_LITERAL_CSTRING("Couldn't get script principal in MediaKeys::Init")); |
369 | 0 | return promise.forget(); |
370 | 0 | } |
371 | 0 | mPrincipal = sop->GetPrincipal(); |
372 | 0 |
|
373 | 0 | // Determine principal of the "top-level" window; the principal of the |
374 | 0 | // page that will display in the URL bar. |
375 | 0 | nsCOMPtr<nsPIDOMWindowInner> window = GetParentObject(); |
376 | 0 | if (!window) { |
377 | 0 | promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR, |
378 | 0 | NS_LITERAL_CSTRING("Couldn't get top-level window in MediaKeys::Init")); |
379 | 0 | return promise.forget(); |
380 | 0 | } |
381 | 0 | nsCOMPtr<nsPIDOMWindowOuter> top = window->GetOuterWindow()->GetTop(); |
382 | 0 | if (!top || !top->GetExtantDoc()) { |
383 | 0 | promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR, |
384 | 0 | NS_LITERAL_CSTRING("Couldn't get document in MediaKeys::Init")); |
385 | 0 | return promise.forget(); |
386 | 0 | } |
387 | 0 |
|
388 | 0 | mTopLevelPrincipal = top->GetExtantDoc()->NodePrincipal(); |
389 | 0 |
|
390 | 0 | if (!mPrincipal || !mTopLevelPrincipal) { |
391 | 0 | NS_WARNING("Failed to get principals when creating MediaKeys"); |
392 | 0 | promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR, |
393 | 0 | NS_LITERAL_CSTRING("Couldn't get principal(s) in MediaKeys::Init")); |
394 | 0 | return promise.forget(); |
395 | 0 | } |
396 | 0 |
|
397 | 0 | nsAutoCString origin; |
398 | 0 | nsresult rv = mPrincipal->GetOrigin(origin); |
399 | 0 | if (NS_FAILED(rv)) { |
400 | 0 | promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR, |
401 | 0 | NS_LITERAL_CSTRING("Couldn't get principal origin string in MediaKeys::Init")); |
402 | 0 | return promise.forget(); |
403 | 0 | } |
404 | 0 | nsAutoCString topLevelOrigin; |
405 | 0 | rv = mTopLevelPrincipal->GetOrigin(topLevelOrigin); |
406 | 0 | if (NS_FAILED(rv)) { |
407 | 0 | promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR, |
408 | 0 | NS_LITERAL_CSTRING("Couldn't get top-level principal origin string in MediaKeys::Init")); |
409 | 0 | return promise.forget(); |
410 | 0 | } |
411 | 0 |
|
412 | 0 | EME_LOG("MediaKeys[%p]::Create() (%s, %s)", |
413 | 0 | this, |
414 | 0 | origin.get(), |
415 | 0 | topLevelOrigin.get()); |
416 | 0 |
|
417 | 0 | mProxy = CreateCDMProxy(top->GetExtantDoc()->EventTargetFor(TaskCategory::Other)); |
418 | 0 |
|
419 | 0 | // The CDMProxy's initialization is asynchronous. The MediaKeys is |
420 | 0 | // refcounted, and its instance is returned to JS by promise once |
421 | 0 | // it's been initialized. No external refs exist to the MediaKeys while |
422 | 0 | // we're waiting for the promise to be resolved, so we must hold a |
423 | 0 | // reference to the new MediaKeys object until it's been created, |
424 | 0 | // or its creation has failed. Store the id of the promise returned |
425 | 0 | // here, and hold a self-reference until that promise is resolved or |
426 | 0 | // rejected. |
427 | 0 | MOZ_ASSERT(!mCreatePromiseId, "Should only be created once!"); |
428 | 0 | mCreatePromiseId = StorePromise(promise); |
429 | 0 | AddRef(); |
430 | 0 | mProxy->Init(mCreatePromiseId, |
431 | 0 | NS_ConvertUTF8toUTF16(origin), |
432 | 0 | NS_ConvertUTF8toUTF16(topLevelOrigin), |
433 | 0 | KeySystemToGMPName(mKeySystem)); |
434 | 0 |
|
435 | 0 | return promise.forget(); |
436 | 0 | } |
437 | | |
438 | | void |
439 | | MediaKeys::OnCDMCreated(PromiseId aId, const uint32_t aPluginId) |
440 | 0 | { |
441 | 0 | RefPtr<DetailedPromise> promise(RetrievePromise(aId)); |
442 | 0 | if (!promise) { |
443 | 0 | return; |
444 | 0 | } |
445 | 0 | RefPtr<MediaKeys> keys(this); |
446 | 0 | EME_LOG("MediaKeys[%p]::OnCDMCreated() resolve promise id=%d", this, aId); |
447 | 0 | promise->MaybeResolve(keys); |
448 | 0 | if (mCreatePromiseId == aId) { |
449 | 0 | Release(); |
450 | 0 | } |
451 | 0 |
|
452 | 0 | MediaKeySystemAccess::NotifyObservers(mParent, |
453 | 0 | mKeySystem, |
454 | 0 | MediaKeySystemStatus::Cdm_created); |
455 | 0 | } |
456 | | |
457 | | static bool |
458 | | IsSessionTypeSupported(const MediaKeySessionType aSessionType, |
459 | | const MediaKeySystemConfiguration& aConfig) |
460 | 0 | { |
461 | 0 | if (aSessionType == MediaKeySessionType::Temporary) { |
462 | 0 | // Temporary is always supported. |
463 | 0 | return true; |
464 | 0 | } |
465 | 0 | if (!aConfig.mSessionTypes.WasPassed()) { |
466 | 0 | // No other session types supported. |
467 | 0 | return false; |
468 | 0 | } |
469 | 0 | return aConfig.mSessionTypes.Value().Contains(ToString(aSessionType)); |
470 | 0 | } |
471 | | |
472 | | already_AddRefed<MediaKeySession> |
473 | | MediaKeys::CreateSession(JSContext* aCx, |
474 | | MediaKeySessionType aSessionType, |
475 | | ErrorResult& aRv) |
476 | 0 | { |
477 | 0 | if (!IsSessionTypeSupported(aSessionType, mConfig)) { |
478 | 0 | EME_LOG("MediaKeys[%p] CreateSession() failed, unsupported session type", this); |
479 | 0 | aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); |
480 | 0 | return nullptr; |
481 | 0 | } |
482 | 0 |
|
483 | 0 | if (!mProxy) { |
484 | 0 | NS_WARNING("Tried to use a MediaKeys which lost its CDM"); |
485 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
486 | 0 | return nullptr; |
487 | 0 | } |
488 | 0 |
|
489 | 0 | EME_LOG("MediaKeys[%p] Creating session", this); |
490 | 0 |
|
491 | 0 | RefPtr<MediaKeySession> session = new MediaKeySession(aCx, |
492 | 0 | GetParentObject(), |
493 | 0 | this, |
494 | 0 | mKeySystem, |
495 | 0 | aSessionType, |
496 | 0 | aRv); |
497 | 0 |
|
498 | 0 | if (aRv.Failed()) { |
499 | 0 | return nullptr; |
500 | 0 | } |
501 | 0 | DDLINKCHILD("session", session.get()); |
502 | 0 |
|
503 | 0 | // Add session to the set of sessions awaiting their sessionId being ready. |
504 | 0 | mPendingSessions.Put(session->Token(), session); |
505 | 0 |
|
506 | 0 | return session.forget(); |
507 | 0 | } |
508 | | |
509 | | void |
510 | | MediaKeys::OnSessionLoaded(PromiseId aId, bool aSuccess) |
511 | 0 | { |
512 | 0 | RefPtr<DetailedPromise> promise(RetrievePromise(aId)); |
513 | 0 | if (!promise) { |
514 | 0 | return; |
515 | 0 | } |
516 | 0 | EME_LOG("MediaKeys[%p]::OnSessionLoaded() resolve promise id=%d", this, aId); |
517 | 0 |
|
518 | 0 | promise->MaybeResolve(aSuccess); |
519 | 0 | } |
520 | | |
521 | | void |
522 | | MediaKeys::OnSessionClosed(MediaKeySession* aSession) |
523 | 0 | { |
524 | 0 | nsAutoString id; |
525 | 0 | aSession->GetSessionId(id); |
526 | 0 | mKeySessions.Remove(id); |
527 | 0 | } |
528 | | |
529 | | already_AddRefed<MediaKeySession> |
530 | | MediaKeys::GetSession(const nsAString& aSessionId) |
531 | 0 | { |
532 | 0 | RefPtr<MediaKeySession> session; |
533 | 0 | mKeySessions.Get(aSessionId, getter_AddRefs(session)); |
534 | 0 | return session.forget(); |
535 | 0 | } |
536 | | |
537 | | already_AddRefed<MediaKeySession> |
538 | | MediaKeys::GetPendingSession(uint32_t aToken) |
539 | 0 | { |
540 | 0 | RefPtr<MediaKeySession> session; |
541 | 0 | mPendingSessions.Get(aToken, getter_AddRefs(session)); |
542 | 0 | mPendingSessions.Remove(aToken); |
543 | 0 | return session.forget(); |
544 | 0 | } |
545 | | |
546 | | bool |
547 | | MediaKeys::IsBoundToMediaElement() const |
548 | 0 | { |
549 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
550 | 0 | return mElement != nullptr; |
551 | 0 | } |
552 | | |
553 | | nsresult |
554 | | MediaKeys::Bind(HTMLMediaElement* aElement) |
555 | 0 | { |
556 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
557 | 0 | if (IsBoundToMediaElement()) { |
558 | 0 | return NS_ERROR_FAILURE; |
559 | 0 | } |
560 | 0 | |
561 | 0 | mElement = aElement; |
562 | 0 |
|
563 | 0 | return NS_OK; |
564 | 0 | } |
565 | | |
566 | | void |
567 | | MediaKeys::Unbind() |
568 | 0 | { |
569 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
570 | 0 | mElement = nullptr; |
571 | 0 | } |
572 | | |
573 | | void |
574 | | MediaKeys::GetSessionsInfo(nsString& sessionsInfo) |
575 | 0 | { |
576 | 0 | for (KeySessionHashMap::Iterator it = mKeySessions.Iter(); |
577 | 0 | !it.Done(); |
578 | 0 | it.Next()) { |
579 | 0 | MediaKeySession* keySession = it.Data(); |
580 | 0 | nsString sessionID; |
581 | 0 | keySession->GetSessionId(sessionID); |
582 | 0 | sessionsInfo.AppendLiteral("(sid="); |
583 | 0 | sessionsInfo.Append(sessionID); |
584 | 0 | MediaKeyStatusMap* keyStatusMap = keySession->KeyStatuses(); |
585 | 0 | for (uint32_t i = 0; i < keyStatusMap->GetIterableLength(); i++) { |
586 | 0 | nsString keyID = keyStatusMap->GetKeyIDAsHexString(i); |
587 | 0 | sessionsInfo.AppendLiteral("(kid="); |
588 | 0 | sessionsInfo.Append(keyID); |
589 | 0 | using IntegerType = typename std::underlying_type<MediaKeyStatus>::type; |
590 | 0 | auto idx = static_cast<IntegerType>(keyStatusMap->GetValueAtIndex(i)); |
591 | 0 | const char* keyStatus = MediaKeyStatusValues::strings[idx].value; |
592 | 0 | sessionsInfo.AppendLiteral(" status="); |
593 | 0 | sessionsInfo.Append( |
594 | 0 | NS_ConvertUTF8toUTF16((nsDependentCString(keyStatus)))); |
595 | 0 | sessionsInfo.AppendLiteral(")"); |
596 | 0 | } |
597 | 0 | sessionsInfo.AppendLiteral(")"); |
598 | 0 | } |
599 | 0 | } |
600 | | |
601 | | already_AddRefed<Promise> |
602 | | MediaKeys::GetStatusForPolicy(const MediaKeysPolicy& aPolicy, |
603 | | ErrorResult& aRv) |
604 | 0 | { |
605 | 0 | RefPtr<DetailedPromise> promise(MakePromise(aRv, |
606 | 0 | NS_LITERAL_CSTRING("MediaKeys::GetStatusForPolicy()"))); |
607 | 0 | if (aRv.Failed()) { |
608 | 0 | return nullptr; |
609 | 0 | } |
610 | 0 | |
611 | 0 | // Currently, only widevine CDM supports for this API. |
612 | 0 | if (!IsWidevineKeySystem(mKeySystem)) { |
613 | 0 | EME_LOG("MediaKeys[%p]::GetStatusForPolicy() HDCP policy check on unsupported keysystem ", this); |
614 | 0 | NS_WARNING("Tried to query without a CDM"); |
615 | 0 | promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR, |
616 | 0 | NS_LITERAL_CSTRING("HDCP policy check on unsupported keysystem")); |
617 | 0 | return promise.forget(); |
618 | 0 | } |
619 | 0 |
|
620 | 0 | if (!mProxy) { |
621 | 0 | NS_WARNING("Tried to use a MediaKeys without a CDM"); |
622 | 0 | promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR, |
623 | 0 | NS_LITERAL_CSTRING("Null CDM in MediaKeys.GetStatusForPolicy()")); |
624 | 0 | return promise.forget(); |
625 | 0 | } |
626 | 0 |
|
627 | 0 | EME_LOG("GetStatusForPolicy minHdcpVersion = %s.", NS_ConvertUTF16toUTF8(aPolicy.mMinHdcpVersion).get()); |
628 | 0 | mProxy->GetStatusForPolicy(StorePromise(promise), aPolicy.mMinHdcpVersion); |
629 | 0 | return promise.forget(); |
630 | 0 | } |
631 | | |
632 | | void |
633 | | MediaKeys::ResolvePromiseWithKeyStatus(PromiseId aId, MediaKeyStatus aMediaKeyStatus) |
634 | 0 | { |
635 | 0 | RefPtr<DetailedPromise> promise(RetrievePromise(aId)); |
636 | 0 | if (!promise) { |
637 | 0 | return; |
638 | 0 | } |
639 | 0 | RefPtr<MediaKeys> keys(this); |
640 | 0 | EME_LOG("MediaKeys[%p]::ResolvePromiseWithKeyStatus() resolve promise id=%d, keystatus=%" PRIu8, |
641 | 0 | this, |
642 | 0 | aId, |
643 | 0 | static_cast<uint8_t>(aMediaKeyStatus)); |
644 | 0 | promise->MaybeResolve(aMediaKeyStatus); |
645 | 0 | } |
646 | | |
647 | | } // namespace dom |
648 | | } // namespace mozilla |