/src/mozilla-central/dom/media/eme/MediaKeySession.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/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 | | 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(MediaKeySession) |
37 | 0 | NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) |
38 | | |
39 | | NS_IMPL_ADDREF_INHERITED(MediaKeySession, DOMEventTargetHelper) |
40 | | 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 | | MediaKeySession::MediaKeySession(JSContext* aCx, |
56 | | nsPIDOMWindowInner* aParent, |
57 | | MediaKeys* aKeys, |
58 | | const nsAString& aKeySystem, |
59 | | MediaKeySessionType aSessionType, |
60 | | ErrorResult& aRv) |
61 | | : DOMEventTargetHelper(aParent) |
62 | | , mKeys(aKeys) |
63 | | , mKeySystem(aKeySystem) |
64 | | , mSessionType(aSessionType) |
65 | | , mToken(sMediaKeySessionNum++) |
66 | | , mIsClosed(false) |
67 | | , mUninitialized(true) |
68 | | , mKeyStatusMap(new MediaKeyStatusMap(aParent)) |
69 | | , mExpiration(JS::GenericNaN()) |
70 | 0 | { |
71 | 0 | EME_LOG("MediaKeySession[%p,''] ctor", this); |
72 | 0 |
|
73 | 0 | MOZ_ASSERT(aParent); |
74 | 0 | if (aRv.Failed()) { |
75 | 0 | return; |
76 | 0 | } |
77 | 0 | mClosed = MakePromise(aRv, NS_LITERAL_CSTRING("MediaKeys.createSession")); |
78 | 0 | } |
79 | | |
80 | | void MediaKeySession::SetSessionId(const nsAString& aSessionId) |
81 | 0 | { |
82 | 0 | EME_LOG("MediaKeySession[%p,'%s'] session Id set", |
83 | 0 | this, NS_ConvertUTF16toUTF8(aSessionId).get()); |
84 | 0 |
|
85 | 0 | if (NS_WARN_IF(!mSessionId.IsEmpty())) { |
86 | 0 | return; |
87 | 0 | } |
88 | 0 | mSessionId = aSessionId; |
89 | 0 | mKeys->OnSessionIdReady(this); |
90 | 0 | } |
91 | | |
92 | | MediaKeySession::~MediaKeySession() |
93 | 0 | { |
94 | 0 | } |
95 | | |
96 | | MediaKeyError* |
97 | | MediaKeySession::GetError() const |
98 | 0 | { |
99 | 0 | return mMediaKeyError; |
100 | 0 | } |
101 | | |
102 | | void |
103 | | MediaKeySession::GetSessionId(nsString& aSessionId) const |
104 | 0 | { |
105 | 0 | aSessionId = GetSessionId(); |
106 | 0 | } |
107 | | |
108 | | const nsString& |
109 | | MediaKeySession::GetSessionId() const |
110 | 0 | { |
111 | 0 | return mSessionId; |
112 | 0 | } |
113 | | |
114 | | JSObject* |
115 | | MediaKeySession::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) |
116 | 0 | { |
117 | 0 | return MediaKeySession_Binding::Wrap(aCx, this, aGivenProto); |
118 | 0 | } |
119 | | |
120 | | double |
121 | | MediaKeySession::Expiration() const |
122 | 0 | { |
123 | 0 | return mExpiration; |
124 | 0 | } |
125 | | |
126 | | Promise* |
127 | | MediaKeySession::Closed() const |
128 | 0 | { |
129 | 0 | return mClosed; |
130 | 0 | } |
131 | | |
132 | | void |
133 | | MediaKeySession::UpdateKeyStatusMap() |
134 | 0 | { |
135 | 0 | MOZ_ASSERT(!IsClosed()); |
136 | 0 | if (!mKeys->GetCDMProxy()) { |
137 | 0 | return; |
138 | 0 | } |
139 | 0 | |
140 | 0 | nsTArray<CDMCaps::KeyStatus> keyStatuses; |
141 | 0 | { |
142 | 0 | auto caps = mKeys->GetCDMProxy()->Capabilites().Lock(); |
143 | 0 | caps->GetKeyStatusesForSession(mSessionId, keyStatuses); |
144 | 0 | } |
145 | 0 |
|
146 | 0 | mKeyStatusMap->Update(keyStatuses); |
147 | 0 |
|
148 | 0 | if (EME_LOG_ENABLED()) { |
149 | 0 | nsAutoCString message( |
150 | 0 | nsPrintfCString("MediaKeySession[%p,'%s'] key statuses change {", |
151 | 0 | this, NS_ConvertUTF16toUTF8(mSessionId).get())); |
152 | 0 | 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 | 0 | } |
157 | 0 | message.AppendLiteral(" }"); |
158 | 0 | // Use %s so we aren't exposing random strings to printf interpolation. |
159 | 0 | EME_LOG("%s", message.get()); |
160 | 0 | } |
161 | 0 | } |
162 | | |
163 | | MediaKeyStatusMap* |
164 | | MediaKeySession::KeyStatuses() const |
165 | 0 | { |
166 | 0 | return mKeyStatusMap; |
167 | 0 | } |
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 | | ValidateInitData(const nsTArray<uint8_t>& aInitData, const nsAString& aInitDataType) |
181 | 0 | { |
182 | 0 | if (aInitDataType.LowerCaseEqualsLiteral("webm")) { |
183 | 0 | // 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 | 0 | // Limit initData to less than 64KB. |
187 | 0 | if (aInitData.Length() > MAX_CENC_INIT_DATA_LENGTH) { |
188 | 0 | return false; |
189 | 0 | } |
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 | 0 | } |
196 | 0 | // 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 | 0 | } |
203 | 0 | if (!keyIds.Init(json)) { |
204 | 0 | return false; |
205 | 0 | } |
206 | 0 | if (keyIds.mKids.Length() == 0) { |
207 | 0 | return false; |
208 | 0 | } |
209 | 0 | for (const auto& kid : keyIds.mKids) { |
210 | 0 | if (kid.IsEmpty()) { |
211 | 0 | return false; |
212 | 0 | } |
213 | 0 | } |
214 | 0 | } |
215 | 0 | return true; |
216 | 0 | } |
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 | | MediaKeySession::GenerateRequest(const nsAString& aInitDataType, |
223 | | const ArrayBufferViewOrArrayBuffer& aInitData, |
224 | | ErrorResult& aRv) |
225 | 0 | { |
226 | 0 | RefPtr<DetailedPromise> promise(MakePromise(aRv, |
227 | 0 | NS_LITERAL_CSTRING("MediaKeySession.generateRequest"))); |
228 | 0 | if (aRv.Failed()) { |
229 | 0 | return nullptr; |
230 | 0 | } |
231 | 0 | |
232 | 0 | // 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 | 0 | 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 | 0 | } |
240 | 0 |
|
241 | 0 | // If this object's uninitialized value is false, return a promise rejected |
242 | 0 | // with an InvalidStateError. |
243 | 0 | if (!mUninitialized) { |
244 | 0 | EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() failed, uninitialized", |
245 | 0 | 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 | 0 | } |
250 | 0 |
|
251 | 0 | // Let this object's uninitialized value be false. |
252 | 0 | mUninitialized = false; |
253 | 0 |
|
254 | 0 | // If initDataType is the empty string, return a promise rejected |
255 | 0 | // 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 | 0 | this, NS_ConvertUTF16toUTF8(mSessionId).get()); |
261 | 0 | return promise.forget(); |
262 | 0 | } |
263 | 0 |
|
264 | 0 | // If initData is an empty array, return a promise rejected with |
265 | 0 | // 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 | 0 | this, NS_ConvertUTF16toUTF8(mSessionId).get()); |
273 | 0 | return promise.forget(); |
274 | 0 | } |
275 | 0 |
|
276 | 0 | // If the Key System implementation represented by this object's |
277 | 0 | // cdm implementation value does not support initDataType as an |
278 | 0 | // Initialization Data Type, return a promise rejected with a |
279 | 0 | // 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 | 0 | this, NS_ConvertUTF16toUTF8(mSessionId).get()); |
285 | 0 | return promise.forget(); |
286 | 0 | } |
287 | 0 |
|
288 | 0 | // Let init data be a copy of the contents of the initData parameter. |
289 | 0 | // Note: Handled by the CopyArrayBufferViewOrArrayBufferData call above. |
290 | 0 |
|
291 | 0 | // Let session type be this object's session type. |
292 | 0 |
|
293 | 0 | // Let promise be a new promise. |
294 | 0 |
|
295 | 0 | // Run the following steps in parallel: |
296 | 0 |
|
297 | 0 | // If the init data is not valid for initDataType, reject promise with |
298 | 0 | // a newly created TypeError. |
299 | 0 | if (!ValidateInitData(data, aInitDataType)) { |
300 | 0 | // 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 | 0 | this, NS_ConvertUTF16toUTF8(mSessionId).get()); |
305 | 0 | return promise.forget(); |
306 | 0 | } |
307 | 0 |
|
308 | 0 | // Let sanitized init data be a validated and sanitized version of init data. |
309 | 0 |
|
310 | 0 | // If sanitized init data is empty, reject promise with a NotSupportedError. |
311 | 0 |
|
312 | 0 | // Note: Remaining steps of generateRequest method continue in CDM. |
313 | 0 |
|
314 | 0 | // Convert initData to hex for easier logging. |
315 | 0 | // Note: CreateSession() std::move()s the data out of the array, so we have |
316 | 0 | // 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 | 0 | pid, |
323 | 0 | aInitDataType, data); |
324 | 0 |
|
325 | 0 | EME_LOG("MediaKeySession[%p,'%s'] GenerateRequest() sent, " |
326 | 0 | "promiseId=%d initData='%s' initDataType='%s'", |
327 | 0 | this, |
328 | 0 | NS_ConvertUTF16toUTF8(mSessionId).get(), |
329 | 0 | pid, |
330 | 0 | hexInitData.get(), |
331 | 0 | NS_ConvertUTF16toUTF8(aInitDataType).get()); |
332 | 0 |
|
333 | 0 | return promise.forget(); |
334 | 0 | } |
335 | | |
336 | | already_AddRefed<Promise> |
337 | | MediaKeySession::Load(const nsAString& aSessionId, ErrorResult& aRv) |
338 | 0 | { |
339 | 0 | RefPtr<DetailedPromise> promise(MakePromise(aRv, |
340 | 0 | NS_LITERAL_CSTRING("MediaKeySession.load"))); |
341 | 0 | if (aRv.Failed()) { |
342 | 0 | return nullptr; |
343 | 0 | } |
344 | 0 | |
345 | 0 | // 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 | 0 | this, NS_ConvertUTF16toUTF8(aSessionId).get()); |
351 | 0 | return promise.forget(); |
352 | 0 | } |
353 | 0 |
|
354 | 0 | // 2.If this object's uninitialized value is false, return a promise rejected |
355 | 0 | // 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 | 0 | this, NS_ConvertUTF16toUTF8(aSessionId).get()); |
361 | 0 | return promise.forget(); |
362 | 0 | } |
363 | 0 |
|
364 | 0 | // 3.Let this object's uninitialized value be false. |
365 | 0 | mUninitialized = false; |
366 | 0 |
|
367 | 0 | // 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 | 0 | // "The sessionId parameter is empty." |
372 | 0 | EME_LOG("MediaKeySession[%p,''] Load() failed, no sessionId", this); |
373 | 0 | return promise.forget(); |
374 | 0 | } |
375 | 0 |
|
376 | 0 | // 5. If the result of running the Is persistent session type? algorithm |
377 | 0 | // on this object's session type is false, return a promise rejected with |
378 | 0 | // 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 | 0 | } |
385 | 0 |
|
386 | 0 | // Note: We don't support persistent sessions in any keysystem, so all calls |
387 | 0 | // to Load() should reject with a TypeError in the preceding check. Omitting |
388 | 0 | // implementing the rest of the specified MediaKeySession::Load() algorithm. |
389 | 0 |
|
390 | 0 | // We now know the sessionId being loaded into this session. Remove the |
391 | 0 | // 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 | 0 |
|
395 | 0 | // Associate with the known sessionId. |
396 | 0 | SetSessionId(aSessionId); |
397 | 0 |
|
398 | 0 | PromiseId pid = mKeys->StorePromise(promise); |
399 | 0 | mKeys->GetCDMProxy()->LoadSession(pid, mSessionType, aSessionId); |
400 | 0 |
|
401 | 0 | EME_LOG("MediaKeySession[%p,'%s'] Load() sent to CDM, promiseId=%d", |
402 | 0 | this, NS_ConvertUTF16toUTF8(mSessionId).get(), pid); |
403 | 0 |
|
404 | 0 | return promise.forget(); |
405 | 0 | } |
406 | | |
407 | | already_AddRefed<Promise> |
408 | | MediaKeySession::Update(const ArrayBufferViewOrArrayBuffer& aResponse, ErrorResult& aRv) |
409 | 0 | { |
410 | 0 | RefPtr<DetailedPromise> promise(MakePromise(aRv, |
411 | 0 | NS_LITERAL_CSTRING("MediaKeySession.update"))); |
412 | 0 | if (aRv.Failed()) { |
413 | 0 | return nullptr; |
414 | 0 | } |
415 | 0 | |
416 | 0 | if (!IsCallable()) { |
417 | 0 | // If this object's callable value is false, return a promise rejected |
418 | 0 | // 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 | 0 | } |
424 | 0 |
|
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 | 0 | this, NS_ConvertUTF16toUTF8(mSessionId).get()); |
431 | 0 | return promise.forget(); |
432 | 0 | } |
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 | 0 | this, NS_ConvertUTF16toUTF8(mSessionId).get()); |
439 | 0 | return promise.forget(); |
440 | 0 | } |
441 | 0 |
|
442 | 0 |
|
443 | 0 | // Convert response to hex for easier logging. |
444 | 0 | // Note: UpdateSession() std::move()s the data out of the array, so we have |
445 | 0 | // to copy it here. |
446 | 0 | nsAutoCString hexResponse(ToHexString(data)); |
447 | 0 |
|
448 | 0 | PromiseId pid = mKeys->StorePromise(promise); |
449 | 0 | mKeys->GetCDMProxy()->UpdateSession(mSessionId, |
450 | 0 | pid, |
451 | 0 | data); |
452 | 0 |
|
453 | 0 | EME_LOG("MediaKeySession[%p,'%s'] Update() sent to CDM, " |
454 | 0 | "promiseId=%d Response='%s'", |
455 | 0 | this, |
456 | 0 | NS_ConvertUTF16toUTF8(mSessionId).get(), |
457 | 0 | pid, |
458 | 0 | hexResponse.get()); |
459 | 0 |
|
460 | 0 | return promise.forget(); |
461 | 0 | } |
462 | | |
463 | | already_AddRefed<Promise> |
464 | | MediaKeySession::Close(ErrorResult& aRv) |
465 | 0 | { |
466 | 0 | RefPtr<DetailedPromise> promise(MakePromise(aRv, |
467 | 0 | NS_LITERAL_CSTRING("MediaKeySession.close"))); |
468 | 0 | if (aRv.Failed()) { |
469 | 0 | return nullptr; |
470 | 0 | } |
471 | 0 | // 1. Let session be the associated MediaKeySession object. |
472 | 0 | // 2. If session is closed, return a resolved promise. |
473 | 0 | if (IsClosed()) { |
474 | 0 | EME_LOG("MediaKeySession[%p,'%s'] Close() already closed", |
475 | 0 | this, NS_ConvertUTF16toUTF8(mSessionId).get()); |
476 | 0 | promise->MaybeResolveWithUndefined(); |
477 | 0 | return promise.forget(); |
478 | 0 | } |
479 | 0 | // 3. If session's callable value is false, return a promise rejected |
480 | 0 | // 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 | 0 | } |
487 | 0 | if (!mKeys->GetCDMProxy()) { |
488 | 0 | EME_LOG("MediaKeySession[%p,'%s'] Close() null CDMProxy", |
489 | 0 | 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 | 0 | } |
494 | 0 | // 4. Let promise be a new promise. |
495 | 0 | PromiseId pid = mKeys->StorePromise(promise); |
496 | 0 | // 5. Run the following steps in parallel: |
497 | 0 | // 5.1 Let cdm be the CDM instance represented by session's cdm instance value. |
498 | 0 | // 5.2 Use cdm to close the session associated with session. |
499 | 0 | mKeys->GetCDMProxy()->CloseSession(mSessionId, pid); |
500 | 0 |
|
501 | 0 | EME_LOG("MediaKeySession[%p,'%s'] Close() sent to CDM, promiseId=%d", |
502 | 0 | this, NS_ConvertUTF16toUTF8(mSessionId).get(), pid); |
503 | 0 |
|
504 | 0 | // Session Closed algorithm is run when CDM causes us to run OnSessionClosed(). |
505 | 0 |
|
506 | 0 | // 6. Return promise. |
507 | 0 | return promise.forget(); |
508 | 0 | } |
509 | | |
510 | | void |
511 | | MediaKeySession::OnClosed() |
512 | 0 | { |
513 | 0 | if (IsClosed()) { |
514 | 0 | return; |
515 | 0 | } |
516 | 0 | EME_LOG("MediaKeySession[%p,'%s'] session close operation complete.", |
517 | 0 | this, NS_ConvertUTF16toUTF8(mSessionId).get()); |
518 | 0 | mIsClosed = true; |
519 | 0 | mKeys->OnSessionClosed(this); |
520 | 0 | mKeys = nullptr; |
521 | 0 | mClosed->MaybeResolveWithUndefined(); |
522 | 0 | } |
523 | | |
524 | | bool |
525 | | MediaKeySession::IsClosed() const |
526 | 0 | { |
527 | 0 | return mIsClosed; |
528 | 0 | } |
529 | | |
530 | | already_AddRefed<Promise> |
531 | | MediaKeySession::Remove(ErrorResult& aRv) |
532 | 0 | { |
533 | 0 | RefPtr<DetailedPromise> promise(MakePromise(aRv, |
534 | 0 | NS_LITERAL_CSTRING("MediaKeySession.remove"))); |
535 | 0 | if (aRv.Failed()) { |
536 | 0 | return nullptr; |
537 | 0 | } |
538 | 0 | if (!IsCallable()) { |
539 | 0 | // If this object's callable value is false, return a promise rejected |
540 | 0 | // 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 | 0 | } |
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 | 0 | // "The operation is not supported on session type sessions." |
550 | 0 | EME_LOG("MediaKeySession[%p,'%s'] Remove() failed, sesion not persisrtent.", |
551 | 0 | this, NS_ConvertUTF16toUTF8(mSessionId).get()); |
552 | 0 | return promise.forget(); |
553 | 0 | } |
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 | 0 | // "The session is closed." |
558 | 0 | EME_LOG("MediaKeySession[%p,'%s'] Remove() failed, already session closed.", |
559 | 0 | this, NS_ConvertUTF16toUTF8(mSessionId).get()); |
560 | 0 | return promise.forget(); |
561 | 0 | } |
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 | 0 | this, NS_ConvertUTF16toUTF8(mSessionId).get(), pid); |
566 | 0 |
|
567 | 0 | return promise.forget(); |
568 | 0 | } |
569 | | |
570 | | void |
571 | | MediaKeySession::DispatchKeyMessage(MediaKeyMessageType aMessageType, |
572 | | const nsTArray<uint8_t>& aMessage) |
573 | 0 | { |
574 | 0 | if (EME_LOG_ENABLED()) { |
575 | 0 | EME_LOG("MediaKeySession[%p,'%s'] DispatchKeyMessage() type=%s message='%s'", |
576 | 0 | this, NS_ConvertUTF16toUTF8(mSessionId).get(), |
577 | 0 | MediaKeyMessageTypeValues::strings[uint32_t(aMessageType)].value, |
578 | 0 | ToHexString(aMessage).get()); |
579 | 0 | } |
580 | 0 |
|
581 | 0 | RefPtr<MediaKeyMessageEvent> event( |
582 | 0 | MediaKeyMessageEvent::Constructor(this, aMessageType, aMessage)); |
583 | 0 | RefPtr<AsyncEventDispatcher> asyncDispatcher = |
584 | 0 | new AsyncEventDispatcher(this, event); |
585 | 0 | asyncDispatcher->PostDOMEvent(); |
586 | 0 | } |
587 | | |
588 | | void |
589 | | MediaKeySession::DispatchKeyError(uint32_t aSystemCode) |
590 | 0 | { |
591 | 0 | EME_LOG("MediaKeySession[%p,'%s'] DispatchKeyError() systemCode=%u.", |
592 | 0 | this, NS_ConvertUTF16toUTF8(mSessionId).get(), aSystemCode); |
593 | 0 |
|
594 | 0 | RefPtr<MediaKeyError> event(new MediaKeyError(this, aSystemCode)); |
595 | 0 | RefPtr<AsyncEventDispatcher> asyncDispatcher = |
596 | 0 | new AsyncEventDispatcher(this, event); |
597 | 0 | asyncDispatcher->PostDOMEvent(); |
598 | 0 | } |
599 | | |
600 | | void |
601 | | MediaKeySession::DispatchKeyStatusesChange() |
602 | 0 | { |
603 | 0 | if (IsClosed()) { |
604 | 0 | return; |
605 | 0 | } |
606 | 0 | |
607 | 0 | UpdateKeyStatusMap(); |
608 | 0 |
|
609 | 0 | RefPtr<AsyncEventDispatcher> asyncDispatcher = |
610 | 0 | new AsyncEventDispatcher(this, |
611 | 0 | NS_LITERAL_STRING("keystatuseschange"), |
612 | 0 | CanBubble::eNo); |
613 | 0 | asyncDispatcher->PostDOMEvent(); |
614 | 0 | } |
615 | | |
616 | | uint32_t |
617 | | MediaKeySession::Token() const |
618 | 0 | { |
619 | 0 | return mToken; |
620 | 0 | } |
621 | | |
622 | | already_AddRefed<DetailedPromise> |
623 | | MediaKeySession::MakePromise(ErrorResult& aRv, const nsACString& aName) |
624 | 0 | { |
625 | 0 | nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject()); |
626 | 0 | if (!global) { |
627 | 0 | NS_WARNING("Passed non-global to MediaKeys ctor!"); |
628 | 0 | aRv.Throw(NS_ERROR_UNEXPECTED); |
629 | 0 | return nullptr; |
630 | 0 | } |
631 | 0 | return DetailedPromise::Create(global, aRv, aName); |
632 | 0 | } |
633 | | |
634 | | void |
635 | | MediaKeySession::SetExpiration(double aExpiration) |
636 | 0 | { |
637 | 0 | EME_LOG("MediaKeySession[%p,'%s'] SetExpiry(%.12lf) (%.2lf hours from now)", |
638 | 0 | this, |
639 | 0 | NS_ConvertUTF16toUTF8(mSessionId).get(), |
640 | 0 | aExpiration, |
641 | 0 | (aExpiration - 1000.0 * double(time(0))) / (1000.0 * 60 * 60)); |
642 | 0 | mExpiration = aExpiration; |
643 | 0 | } |
644 | | |
645 | | EventHandlerNonNull* |
646 | | MediaKeySession::GetOnkeystatuseschange() |
647 | 0 | { |
648 | 0 | return GetEventHandler(nsGkAtoms::onkeystatuseschange); |
649 | 0 | } |
650 | | |
651 | | void |
652 | | MediaKeySession::SetOnkeystatuseschange(EventHandlerNonNull* aCallback) |
653 | 0 | { |
654 | 0 | SetEventHandler(nsGkAtoms::onkeystatuseschange, aCallback); |
655 | 0 | } |
656 | | |
657 | | EventHandlerNonNull* |
658 | | MediaKeySession::GetOnmessage() |
659 | 0 | { |
660 | 0 | return GetEventHandler(nsGkAtoms::onmessage); |
661 | 0 | } |
662 | | |
663 | | void |
664 | | MediaKeySession::SetOnmessage(EventHandlerNonNull* aCallback) |
665 | 0 | { |
666 | 0 | SetEventHandler(nsGkAtoms::onmessage, aCallback); |
667 | 0 | } |
668 | | |
669 | | nsCString |
670 | | ToCString(MediaKeySessionType aType) |
671 | 0 | { |
672 | 0 | using IntegerType = typename std::underlying_type<MediaKeySessionType>::type; |
673 | 0 | auto idx = static_cast<IntegerType>(aType); |
674 | 0 | return nsDependentCString(MediaKeySessionTypeValues::strings[idx].value); |
675 | 0 | } |
676 | | |
677 | | nsString |
678 | | ToString(MediaKeySessionType aType) |
679 | 0 | { |
680 | 0 | return NS_ConvertUTF8toUTF16(ToCString(aType)); |
681 | 0 | } |
682 | | |
683 | | } // namespace dom |
684 | | } // namespace mozilla |