Line data Source code
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/HTMLMediaElement.h"
8 : #include "mozilla/dom/MediaKeySession.h"
9 : #include "mozilla/dom/MediaKeyError.h"
10 : #include "mozilla/dom/MediaKeyMessageEvent.h"
11 : #include "mozilla/dom/MediaEncryptedEvent.h"
12 : #include "mozilla/dom/MediaKeyStatusMap.h"
13 : #include "mozilla/dom/MediaKeySystemAccess.h"
14 : #include "mozilla/dom/KeyIdsInitDataBinding.h"
15 : #include "nsCycleCollectionParticipant.h"
16 : #include "mozilla/CDMProxy.h"
17 : #include "mozilla/AsyncEventDispatcher.h"
18 : #include "mozilla/Move.h"
19 : #include "mozilla/EMEUtils.h"
20 : #include "mozilla/Encoding.h"
21 : #include "GMPUtils.h"
22 : #include "nsPrintfCString.h"
23 : #include "psshparser/PsshParser.h"
24 : #include <ctime>
25 :
26 : namespace mozilla {
27 : namespace dom {
28 :
29 0 : NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaKeySession,
30 : DOMEventTargetHelper,
31 : mMediaKeyError,
32 : mKeys,
33 : mKeyStatusMap,
34 : mClosed)
35 :
36 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaKeySession)
37 0 : NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
38 :
39 0 : NS_IMPL_ADDREF_INHERITED(MediaKeySession, DOMEventTargetHelper)
40 0 : NS_IMPL_RELEASE_INHERITED(MediaKeySession, DOMEventTargetHelper)
41 :
42 : // Count of number of instances. Used to give each instance a
43 : // unique token.
44 : static uint32_t sMediaKeySessionNum = 0;
45 :
46 : // Max length of keyId in EME "keyIds" or WebM init data format, as enforced
47 : // by web platform tests.
48 : static const uint32_t MAX_KEY_ID_LENGTH = 512;
49 :
50 : // Max length of CENC PSSH init data tolerated, as enforced by web
51 : // platform tests.
52 : static const uint32_t MAX_CENC_INIT_DATA_LENGTH = 64 * 1024;
53 :
54 :
55 0 : MediaKeySession::MediaKeySession(JSContext* aCx,
56 : nsPIDOMWindowInner* aParent,
57 : MediaKeys* aKeys,
58 : const nsAString& aKeySystem,
59 : MediaKeySessionType aSessionType,
60 0 : ErrorResult& aRv)
61 : : DOMEventTargetHelper(aParent)
62 : , mKeys(aKeys)
63 : , mKeySystem(aKeySystem)
64 : , mSessionType(aSessionType)
65 0 : , mToken(sMediaKeySessionNum++)
66 : , mIsClosed(false)
67 : , mUninitialized(true)
68 0 : , mKeyStatusMap(new MediaKeyStatusMap(aParent))
69 0 : , mExpiration(JS::GenericNaN())
70 : {
71 0 : EME_LOG("MediaKeySession[%p,''] ctor", this);
72 :
73 0 : MOZ_ASSERT(aParent);
74 0 : if (aRv.Failed()) {
75 0 : return;
76 : }
77 0 : mClosed = MakePromise(aRv, NS_LITERAL_CSTRING("MediaKeys.createSession"));
78 : }
79 :
80 0 : void MediaKeySession::SetSessionId(const nsAString& aSessionId)
81 : {
82 0 : EME_LOG("MediaKeySession[%p,'%s'] session Id set",
83 : this, NS_ConvertUTF16toUTF8(aSessionId).get());
84 :
85 0 : if (NS_WARN_IF(!mSessionId.IsEmpty())) {
86 0 : return;
87 : }
88 0 : mSessionId = aSessionId;
89 0 : mKeys->OnSessionIdReady(this);
90 : }
91 :
92 0 : MediaKeySession::~MediaKeySession()
93 : {
94 0 : }
95 :
96 : MediaKeyError*
97 0 : MediaKeySession::GetError() const
98 : {
99 0 : return mMediaKeyError;
100 : }
101 :
102 : void
103 0 : MediaKeySession::GetSessionId(nsString& aSessionId) const
104 : {
105 0 : aSessionId = GetSessionId();
106 0 : }
107 :
108 : const nsString&
109 0 : MediaKeySession::GetSessionId() const
110 : {
111 0 : return mSessionId;
112 : }
113 :
114 : JSObject*
115 0 : MediaKeySession::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
116 : {
117 0 : return MediaKeySessionBinding::Wrap(aCx, this, aGivenProto);
118 : }
119 :
120 : double
121 0 : MediaKeySession::Expiration() const
122 : {
123 0 : return mExpiration;
124 : }
125 :
126 : Promise*
127 0 : MediaKeySession::Closed() const
128 : {
129 0 : return mClosed;
130 : }
131 :
132 : void
133 0 : MediaKeySession::UpdateKeyStatusMap()
134 : {
135 0 : MOZ_ASSERT(!IsClosed());
136 0 : if (!mKeys->GetCDMProxy()) {
137 0 : return;
138 : }
139 :
140 0 : nsTArray<CDMCaps::KeyStatus> keyStatuses;
141 : {
142 0 : CDMCaps::AutoLock caps(mKeys->GetCDMProxy()->Capabilites());
143 0 : caps.GetKeyStatusesForSession(mSessionId, keyStatuses);
144 : }
145 :
146 0 : mKeyStatusMap->Update(keyStatuses);
147 :
148 0 : if (EME_LOG_ENABLED()) {
149 : nsAutoCString message(
150 0 : nsPrintfCString("MediaKeySession[%p,'%s'] key statuses change {",
151 0 : this, NS_ConvertUTF16toUTF8(mSessionId).get()));
152 : using IntegerType = typename std::underlying_type<MediaKeyStatus>::type;
153 0 : for (const CDMCaps::KeyStatus& status : keyStatuses) {
154 0 : message.Append(nsPrintfCString(" (%s,%s)", ToHexString(status.mId).get(),
155 0 : MediaKeyStatusValues::strings[static_cast<IntegerType>(status.mStatus)].value));
156 : }
157 0 : message.Append(" }");
158 : // Use %s so we aren't exposing random strings to printf interpolation.
159 0 : EME_LOG("%s", message.get());
160 : }
161 : }
162 :
163 : MediaKeyStatusMap*
164 0 : MediaKeySession::KeyStatuses() const
165 : {
166 0 : return mKeyStatusMap;
167 : }
168 :
169 : // The user agent MUST thoroughly validate the Initialization Data before
170 : // passing it to the CDM. This includes verifying that the length and
171 : // values of fields are reasonable, verifying that values are within
172 : // reasonable limits, and stripping irrelevant, unsupported, or unknown
173 : // data or fields. It is RECOMMENDED that user agents pre-parse, sanitize,
174 : // and/or generate a fully sanitized version of the Initialization Data.
175 : // If the Initialization Data format specified by initDataType supports
176 : // multiple entries, the user agent SHOULD remove entries that are not
177 : // needed by the CDM. The user agent MUST NOT re-order entries within
178 : // the Initialization Data.
179 : static bool
180 0 : ValidateInitData(const nsTArray<uint8_t>& aInitData, const nsAString& aInitDataType)
181 : {
182 0 : if (aInitDataType.LowerCaseEqualsLiteral("webm")) {
183 : // WebM initData consists of a single keyId. Ensure it's of reasonable length.
184 0 : return aInitData.Length() <= MAX_KEY_ID_LENGTH;
185 0 : } else if (aInitDataType.LowerCaseEqualsLiteral("cenc")) {
186 : // Limit initData to less than 64KB.
187 0 : if (aInitData.Length() > MAX_CENC_INIT_DATA_LENGTH) {
188 0 : return false;
189 : }
190 0 : std::vector<std::vector<uint8_t>> keyIds;
191 0 : return ParseCENCInitData(aInitData.Elements(), aInitData.Length(), keyIds);
192 0 : } else if (aInitDataType.LowerCaseEqualsLiteral("keyids")) {
193 0 : if (aInitData.Length() > MAX_KEY_ID_LENGTH) {
194 0 : return false;
195 : }
196 : // Ensure that init data matches the expected JSON format.
197 0 : mozilla::dom::KeyIdsInitData keyIds;
198 0 : nsString json;
199 0 : nsDependentCSubstring raw(reinterpret_cast<const char*>(aInitData.Elements()), aInitData.Length());
200 0 : if (NS_FAILED(UTF_8_ENCODING->DecodeWithBOMRemoval(raw, json))) {
201 0 : return false;
202 : }
203 0 : if (!keyIds.Init(json)) {
204 0 : return false;
205 : }
206 0 : if (keyIds.mKids.Length() == 0) {
207 0 : return false;
208 : }
209 0 : for (const auto& kid : keyIds.mKids) {
210 0 : if (kid.IsEmpty()) {
211 0 : return false;
212 : }
213 : }
214 : }
215 0 : return true;
216 : }
217 :
218 : // Generates a license request based on the initData. A message of type
219 : // "license-request" or "individualization-request" will always be queued
220 : // if the algorithm succeeds and the promise is resolved.
221 : already_AddRefed<Promise>
222 0 : MediaKeySession::GenerateRequest(const nsAString& aInitDataType,
223 : const ArrayBufferViewOrArrayBuffer& aInitData,
224 : ErrorResult& aRv)
225 : {
226 0 : RefPtr<DetailedPromise> promise(MakePromise(aRv,
227 0 : NS_LITERAL_CSTRING("MediaKeySession.generateRequest")));
228 0 : if (aRv.Failed()) {
229 0 : return nullptr;
230 : }
231 :
232 : // If this object is closed, return a promise rejected with an InvalidStateError.
233 0 : if (IsClosed()) {
234 0 : EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() failed, closed",
235 : this, NS_ConvertUTF16toUTF8(mSessionId).get());
236 0 : promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
237 0 : NS_LITERAL_CSTRING("Session is closed in MediaKeySession.generateRequest()"));
238 0 : return promise.forget();
239 : }
240 :
241 : // If this object's uninitialized value is false, return a promise rejected
242 : // with an InvalidStateError.
243 0 : if (!mUninitialized) {
244 0 : EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() failed, uninitialized",
245 : this, NS_ConvertUTF16toUTF8(mSessionId).get());
246 0 : promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
247 0 : NS_LITERAL_CSTRING("Session is already initialized in MediaKeySession.generateRequest()"));
248 0 : return promise.forget();
249 : }
250 :
251 : // Let this object's uninitialized value be false.
252 0 : mUninitialized = false;
253 :
254 : // If initDataType is the empty string, return a promise rejected
255 : // with a newly created TypeError.
256 0 : if (aInitDataType.IsEmpty()) {
257 0 : promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
258 0 : NS_LITERAL_CSTRING("Empty initDataType passed to MediaKeySession.generateRequest()"));
259 0 : EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() failed, empty initDataType",
260 : this, NS_ConvertUTF16toUTF8(mSessionId).get());
261 0 : return promise.forget();
262 : }
263 :
264 : // If initData is an empty array, return a promise rejected with
265 : // a newly created TypeError.
266 0 : nsTArray<uint8_t> data;
267 0 : CopyArrayBufferViewOrArrayBufferData(aInitData, data);
268 0 : if (data.IsEmpty()) {
269 0 : promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
270 0 : NS_LITERAL_CSTRING("Empty initData passed to MediaKeySession.generateRequest()"));
271 0 : EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() failed, empty initData",
272 : this, NS_ConvertUTF16toUTF8(mSessionId).get());
273 0 : return promise.forget();
274 : }
275 :
276 : // If the Key System implementation represented by this object's
277 : // cdm implementation value does not support initDataType as an
278 : // Initialization Data Type, return a promise rejected with a
279 : // NotSupportedError. String comparison is case-sensitive.
280 0 : if (!MediaKeySystemAccess::KeySystemSupportsInitDataType(mKeySystem, aInitDataType)) {
281 0 : promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
282 0 : NS_LITERAL_CSTRING("Unsupported initDataType passed to MediaKeySession.generateRequest()"));
283 0 : EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() failed, unsupported initDataType",
284 : this, NS_ConvertUTF16toUTF8(mSessionId).get());
285 0 : return promise.forget();
286 : }
287 :
288 : // Let init data be a copy of the contents of the initData parameter.
289 : // Note: Handled by the CopyArrayBufferViewOrArrayBufferData call above.
290 :
291 : // Let session type be this object's session type.
292 :
293 : // Let promise be a new promise.
294 :
295 : // Run the following steps in parallel:
296 :
297 : // If the init data is not valid for initDataType, reject promise with
298 : // a newly created TypeError.
299 0 : if (!ValidateInitData(data, aInitDataType)) {
300 : // If the preceding step failed, reject promise with a newly created TypeError.
301 0 : promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
302 0 : NS_LITERAL_CSTRING("initData sanitization failed in MediaKeySession.generateRequest()"));
303 0 : EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() initData sanitization failed",
304 : this, NS_ConvertUTF16toUTF8(mSessionId).get());
305 0 : return promise.forget();
306 : }
307 :
308 : // Let sanitized init data be a validated and sanitized version of init data.
309 :
310 : // If sanitized init data is empty, reject promise with a NotSupportedError.
311 :
312 : // Note: Remaining steps of generateRequest method continue in CDM.
313 :
314 : // Convert initData to hex for easier logging.
315 : // Note: CreateSession() Move()s the data out of the array, so we have
316 : // to copy it here.
317 0 : nsAutoCString hexInitData(ToHexString(data));
318 0 : PromiseId pid = mKeys->StorePromise(promise);
319 0 : mKeys->ConnectPendingPromiseIdWithToken(pid, Token());
320 0 : mKeys->GetCDMProxy()->CreateSession(Token(),
321 0 : mSessionType,
322 : pid,
323 0 : aInitDataType, data);
324 :
325 0 : EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() sent, "
326 : "promiseId=%d initData='%s' initDataType='%s'",
327 : this,
328 : NS_ConvertUTF16toUTF8(mSessionId).get(),
329 : pid,
330 : hexInitData.get(),
331 : NS_ConvertUTF16toUTF8(aInitDataType).get());
332 :
333 0 : return promise.forget();
334 : }
335 :
336 : already_AddRefed<Promise>
337 0 : MediaKeySession::Load(const nsAString& aSessionId, ErrorResult& aRv)
338 : {
339 0 : RefPtr<DetailedPromise> promise(MakePromise(aRv,
340 0 : NS_LITERAL_CSTRING("MediaKeySession.load")));
341 0 : if (aRv.Failed()) {
342 0 : return nullptr;
343 : }
344 :
345 : // 1. If this object is closed, return a promise rejected with an InvalidStateError.
346 0 : if (IsClosed()) {
347 0 : promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
348 0 : NS_LITERAL_CSTRING("Session is closed in MediaKeySession.load()"));
349 0 : EME_LOG("MediaKeySession[%p,'%s'] Load() failed, closed",
350 : this, NS_ConvertUTF16toUTF8(aSessionId).get());
351 0 : return promise.forget();
352 : }
353 :
354 : // 2.If this object's uninitialized value is false, return a promise rejected
355 : // with an InvalidStateError.
356 0 : if (!mUninitialized) {
357 0 : promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
358 0 : NS_LITERAL_CSTRING("Session is already initialized in MediaKeySession.load()"));
359 0 : EME_LOG("MediaKeySession[%p,'%s'] Load() failed, uninitialized",
360 : this, NS_ConvertUTF16toUTF8(aSessionId).get());
361 0 : return promise.forget();
362 : }
363 :
364 : // 3.Let this object's uninitialized value be false.
365 0 : mUninitialized = false;
366 :
367 : // 4. If sessionId is the empty string, return a promise rejected with a newly created TypeError.
368 0 : if (aSessionId.IsEmpty()) {
369 0 : promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
370 0 : NS_LITERAL_CSTRING("Trying to load a session with empty session ID"));
371 : // "The sessionId parameter is empty."
372 0 : EME_LOG("MediaKeySession[%p,''] Load() failed, no sessionId", this);
373 0 : return promise.forget();
374 : }
375 :
376 : // 5. If the result of running the Is persistent session type? algorithm
377 : // on this object's session type is false, return a promise rejected with
378 : // a newly created TypeError.
379 0 : if (mSessionType == MediaKeySessionType::Temporary) {
380 0 : promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
381 0 : NS_LITERAL_CSTRING("Trying to load() into a non-persistent session"));
382 0 : EME_LOG("MediaKeySession[%p,''] Load() failed, can't load in a non-persistent session", this);
383 0 : return promise.forget();
384 : }
385 :
386 : // Note: We don't support persistent sessions in any keysystem, so all calls
387 : // to Load() should reject with a TypeError in the preceding check. Omitting
388 : // implementing the rest of the specified MediaKeySession::Load() algorithm.
389 :
390 : // We now know the sessionId being loaded into this session. Remove the
391 : // session from its owning MediaKey's set of sessions awaiting a sessionId.
392 0 : RefPtr<MediaKeySession> session(mKeys->GetPendingSession(Token()));
393 0 : MOZ_ASSERT(session == this, "Session should be awaiting id on its own token");
394 :
395 : // Associate with the known sessionId.
396 0 : SetSessionId(aSessionId);
397 :
398 0 : PromiseId pid = mKeys->StorePromise(promise);
399 0 : mKeys->GetCDMProxy()->LoadSession(pid, mSessionType, aSessionId);
400 :
401 0 : EME_LOG("MediaKeySession[%p,'%s'] Load() sent to CDM, promiseId=%d",
402 : this, NS_ConvertUTF16toUTF8(mSessionId).get(), pid);
403 :
404 0 : return promise.forget();
405 : }
406 :
407 : already_AddRefed<Promise>
408 0 : MediaKeySession::Update(const ArrayBufferViewOrArrayBuffer& aResponse, ErrorResult& aRv)
409 : {
410 0 : RefPtr<DetailedPromise> promise(MakePromise(aRv,
411 0 : NS_LITERAL_CSTRING("MediaKeySession.update")));
412 0 : if (aRv.Failed()) {
413 0 : return nullptr;
414 : }
415 :
416 0 : if (!IsCallable()) {
417 : // If this object's callable value is false, return a promise rejected
418 : // with a new DOMException whose name is InvalidStateError.
419 0 : EME_LOG("MediaKeySession[%p,''] Update() called before sessionId set by CDM", this);
420 0 : promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
421 0 : NS_LITERAL_CSTRING("MediaKeySession.Update() called before sessionId set by CDM"));
422 0 : return promise.forget();
423 : }
424 :
425 0 : nsTArray<uint8_t> data;
426 0 : if (IsClosed() || !mKeys->GetCDMProxy()) {
427 0 : promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
428 0 : NS_LITERAL_CSTRING("Session is closed or was not properly initialized"));
429 0 : EME_LOG("MediaKeySession[%p,'%s'] Update() failed, session is closed or was not properly initialised.",
430 : this, NS_ConvertUTF16toUTF8(mSessionId).get());
431 0 : return promise.forget();
432 : }
433 0 : CopyArrayBufferViewOrArrayBufferData(aResponse, data);
434 0 : if (data.IsEmpty()) {
435 0 : promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
436 0 : NS_LITERAL_CSTRING("Empty response buffer passed to MediaKeySession.update()"));
437 0 : EME_LOG("MediaKeySession[%p,'%s'] Update() failed, empty response buffer",
438 : this, NS_ConvertUTF16toUTF8(mSessionId).get());
439 0 : return promise.forget();
440 : }
441 :
442 :
443 : // Convert response to hex for easier logging.
444 : // Note: UpdateSession() Move()s the data out of the array, so we have
445 : // to copy it here.
446 0 : nsAutoCString hexResponse(ToHexString(data));
447 :
448 0 : PromiseId pid = mKeys->StorePromise(promise);
449 0 : mKeys->GetCDMProxy()->UpdateSession(mSessionId,
450 : pid,
451 0 : data);
452 :
453 0 : EME_LOG("MediaKeySession[%p,'%s'] Update() sent to CDM, "
454 : "promiseId=%d Response='%s'",
455 : this,
456 : NS_ConvertUTF16toUTF8(mSessionId).get(),
457 : pid,
458 : hexResponse.get());
459 :
460 0 : return promise.forget();
461 : }
462 :
463 : already_AddRefed<Promise>
464 0 : MediaKeySession::Close(ErrorResult& aRv)
465 : {
466 0 : RefPtr<DetailedPromise> promise(MakePromise(aRv,
467 0 : NS_LITERAL_CSTRING("MediaKeySession.close")));
468 0 : if (aRv.Failed()) {
469 0 : return nullptr;
470 : }
471 : // 1. Let session be the associated MediaKeySession object.
472 : // 2. If session is closed, return a resolved promise.
473 0 : if (IsClosed()) {
474 0 : EME_LOG("MediaKeySession[%p,'%s'] Close() already closed",
475 : this, NS_ConvertUTF16toUTF8(mSessionId).get());
476 0 : promise->MaybeResolveWithUndefined();
477 0 : return promise.forget();
478 : }
479 : // 3. If session's callable value is false, return a promise rejected
480 : // with an InvalidStateError.
481 0 : if (!IsCallable()) {
482 0 : EME_LOG("MediaKeySession[%p,''] Close() called before sessionId set by CDM", this);
483 0 : promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
484 0 : NS_LITERAL_CSTRING("MediaKeySession.Close() called before sessionId set by CDM"));
485 0 : return promise.forget();
486 : }
487 0 : if (!mKeys->GetCDMProxy()) {
488 0 : EME_LOG("MediaKeySession[%p,'%s'] Close() null CDMProxy",
489 : this, NS_ConvertUTF16toUTF8(mSessionId).get());
490 0 : promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
491 0 : NS_LITERAL_CSTRING("MediaKeySession.Close() lost reference to CDM"));
492 0 : return promise.forget();
493 : }
494 : // 4. Let promise be a new promise.
495 0 : PromiseId pid = mKeys->StorePromise(promise);
496 : // 5. Run the following steps in parallel:
497 : // 5.1 Let cdm be the CDM instance represented by session's cdm instance value.
498 : // 5.2 Use cdm to close the session associated with session.
499 0 : mKeys->GetCDMProxy()->CloseSession(mSessionId, pid);
500 :
501 0 : EME_LOG("MediaKeySession[%p,'%s'] Close() sent to CDM, promiseId=%d",
502 : this, NS_ConvertUTF16toUTF8(mSessionId).get(), pid);
503 :
504 : // Session Closed algorithm is run when CDM causes us to run OnSessionClosed().
505 :
506 : // 6. Return promise.
507 0 : return promise.forget();
508 : }
509 :
510 : void
511 0 : MediaKeySession::OnClosed()
512 : {
513 0 : if (IsClosed()) {
514 0 : return;
515 : }
516 0 : EME_LOG("MediaKeySession[%p,'%s'] session close operation complete.",
517 : this, NS_ConvertUTF16toUTF8(mSessionId).get());
518 0 : mIsClosed = true;
519 0 : mKeys->OnSessionClosed(this);
520 0 : mKeys = nullptr;
521 0 : mClosed->MaybeResolveWithUndefined();
522 : }
523 :
524 : bool
525 0 : MediaKeySession::IsClosed() const
526 : {
527 0 : return mIsClosed;
528 : }
529 :
530 : already_AddRefed<Promise>
531 0 : MediaKeySession::Remove(ErrorResult& aRv)
532 : {
533 0 : RefPtr<DetailedPromise> promise(MakePromise(aRv,
534 0 : NS_LITERAL_CSTRING("MediaKeySession.remove")));
535 0 : if (aRv.Failed()) {
536 0 : return nullptr;
537 : }
538 0 : if (!IsCallable()) {
539 : // If this object's callable value is false, return a promise rejected
540 : // with a new DOMException whose name is InvalidStateError.
541 0 : EME_LOG("MediaKeySession[%p,''] Remove() called before sessionId set by CDM", this);
542 0 : promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
543 0 : NS_LITERAL_CSTRING("MediaKeySession.Remove() called before sessionId set by CDM"));
544 0 : return promise.forget();
545 : }
546 0 : if (mSessionType != MediaKeySessionType::Persistent_license) {
547 0 : promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR,
548 0 : NS_LITERAL_CSTRING("Calling MediaKeySession.remove() on non-persistent session"));
549 : // "The operation is not supported on session type sessions."
550 0 : EME_LOG("MediaKeySession[%p,'%s'] Remove() failed, sesion not persisrtent.",
551 : this, NS_ConvertUTF16toUTF8(mSessionId).get());
552 0 : return promise.forget();
553 : }
554 0 : if (IsClosed() || !mKeys->GetCDMProxy()) {
555 0 : promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
556 0 : NS_LITERAL_CSTRING("MediaKeySesison.remove() called but session is not active"));
557 : // "The session is closed."
558 0 : EME_LOG("MediaKeySession[%p,'%s'] Remove() failed, already session closed.",
559 : this, NS_ConvertUTF16toUTF8(mSessionId).get());
560 0 : return promise.forget();
561 : }
562 0 : PromiseId pid = mKeys->StorePromise(promise);
563 0 : mKeys->GetCDMProxy()->RemoveSession(mSessionId, pid);
564 0 : EME_LOG("MediaKeySession[%p,'%s'] Remove() sent to CDM, promiseId=%d.",
565 : this, NS_ConvertUTF16toUTF8(mSessionId).get(), pid);
566 :
567 0 : return promise.forget();
568 : }
569 :
570 : void
571 0 : MediaKeySession::DispatchKeyMessage(MediaKeyMessageType aMessageType,
572 : const nsTArray<uint8_t>& aMessage)
573 : {
574 0 : if (EME_LOG_ENABLED()) {
575 0 : EME_LOG("MediaKeySession[%p,'%s'] DispatchKeyMessage() type=%s message='%s'",
576 : this, NS_ConvertUTF16toUTF8(mSessionId).get(),
577 : MediaKeyMessageTypeValues::strings[uint32_t(aMessageType)].value,
578 : ToHexString(aMessage).get());
579 : }
580 :
581 : RefPtr<MediaKeyMessageEvent> event(
582 0 : MediaKeyMessageEvent::Constructor(this, aMessageType, aMessage));
583 : RefPtr<AsyncEventDispatcher> asyncDispatcher =
584 0 : new AsyncEventDispatcher(this, event);
585 0 : asyncDispatcher->PostDOMEvent();
586 0 : }
587 :
588 : void
589 0 : MediaKeySession::DispatchKeyError(uint32_t aSystemCode)
590 : {
591 0 : EME_LOG("MediaKeySession[%p,'%s'] DispatchKeyError() systemCode=%u.",
592 : this, NS_ConvertUTF16toUTF8(mSessionId).get(), aSystemCode);
593 :
594 0 : RefPtr<MediaKeyError> event(new MediaKeyError(this, aSystemCode));
595 : RefPtr<AsyncEventDispatcher> asyncDispatcher =
596 0 : new AsyncEventDispatcher(this, event);
597 0 : asyncDispatcher->PostDOMEvent();
598 0 : }
599 :
600 : void
601 0 : MediaKeySession::DispatchKeyStatusesChange()
602 : {
603 0 : if (IsClosed()) {
604 0 : return;
605 : }
606 :
607 0 : UpdateKeyStatusMap();
608 :
609 : RefPtr<AsyncEventDispatcher> asyncDispatcher =
610 0 : new AsyncEventDispatcher(this, NS_LITERAL_STRING("keystatuseschange"), false);
611 0 : asyncDispatcher->PostDOMEvent();
612 : }
613 :
614 : uint32_t
615 0 : MediaKeySession::Token() const
616 : {
617 0 : return mToken;
618 : }
619 :
620 : already_AddRefed<DetailedPromise>
621 0 : MediaKeySession::MakePromise(ErrorResult& aRv, const nsACString& aName)
622 : {
623 0 : nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
624 0 : if (!global) {
625 0 : NS_WARNING("Passed non-global to MediaKeys ctor!");
626 0 : aRv.Throw(NS_ERROR_UNEXPECTED);
627 0 : return nullptr;
628 : }
629 0 : return DetailedPromise::Create(global, aRv, aName);
630 : }
631 :
632 : void
633 0 : MediaKeySession::SetExpiration(double aExpiration)
634 : {
635 0 : EME_LOG("MediaKeySession[%p,'%s'] SetExpiry(%.12lf) (%.2lf hours from now)",
636 : this,
637 : NS_ConvertUTF16toUTF8(mSessionId).get(),
638 : aExpiration,
639 : (aExpiration - 1000.0 * double(time(0))) / (1000.0 * 60 * 60));
640 0 : mExpiration = aExpiration;
641 0 : }
642 :
643 : EventHandlerNonNull*
644 0 : MediaKeySession::GetOnkeystatuseschange()
645 : {
646 0 : return GetEventHandler(nsGkAtoms::onkeystatuseschange, EmptyString());
647 : }
648 :
649 : void
650 0 : MediaKeySession::SetOnkeystatuseschange(EventHandlerNonNull* aCallback)
651 : {
652 0 : SetEventHandler(nsGkAtoms::onkeystatuseschange, EmptyString(), aCallback);
653 0 : }
654 :
655 : EventHandlerNonNull*
656 0 : MediaKeySession::GetOnmessage()
657 : {
658 0 : return GetEventHandler(nsGkAtoms::onmessage, EmptyString());
659 : }
660 :
661 : void
662 0 : MediaKeySession::SetOnmessage(EventHandlerNonNull* aCallback)
663 : {
664 0 : SetEventHandler(nsGkAtoms::onmessage, EmptyString(), aCallback);
665 0 : }
666 :
667 : nsCString
668 0 : ToCString(MediaKeySessionType aType)
669 : {
670 : using IntegerType = typename std::underlying_type<MediaKeySessionType>::type;
671 0 : auto idx = static_cast<IntegerType>(aType);
672 0 : return nsDependentCString(MediaKeySessionTypeValues::strings[idx].value);
673 : }
674 :
675 : nsString
676 0 : ToString(MediaKeySessionType aType)
677 : {
678 0 : return NS_ConvertUTF8toUTF16(ToCString(aType));
679 : }
680 :
681 : } // namespace dom
682 : } // namespace mozilla
|