blob: 220f6dbed7e27fee142b87227ae59b9191da2db4 [file] [log] [blame]
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +00001/*
2 * Copyright (C) 2016 Metrological Group B.V.
3 * Copyright (C) 2016 Igalia S.L.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following
13 * disclaimer in the documentation and/or other materials provided
14 * with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30#include "MediaKeySession.h"
31
32#if ENABLE(ENCRYPTED_MEDIA)
33
jer.noble@apple.com726186a2017-01-10 18:41:31 +000034#include "CDM.h"
35#include "CDMInstance.h"
zandobersek@gmail.coma79fad82017-02-10 10:40:29 +000036#include "Document.h"
zandobersek@gmail.comfe384592017-01-17 10:11:09 +000037#include "EventNames.h"
jer.noble@apple.com50f1bd62017-01-10 20:09:21 +000038#include "MediaKeyMessageEvent.h"
39#include "MediaKeyMessageType.h"
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +000040#include "MediaKeyStatusMap.h"
41#include "NotImplemented.h"
zandobersek@gmail.coma79fad82017-02-10 10:40:29 +000042#include "SecurityOrigin.h"
jer.noble@apple.com50f1bd62017-01-10 20:09:21 +000043#include "SharedBuffer.h"
44#include <wtf/NeverDestroyed.h>
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +000045
46namespace WebCore {
47
jer.noble@apple.com726186a2017-01-10 18:41:31 +000048Ref<MediaKeySession> MediaKeySession::create(ScriptExecutionContext& context, MediaKeySessionType sessionType, bool useDistinctiveIdentifier, Ref<CDM>&& implementation, Ref<CDMInstance>&& instance)
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +000049{
jer.noble@apple.com726186a2017-01-10 18:41:31 +000050 auto session = adoptRef(*new MediaKeySession(context, sessionType, useDistinctiveIdentifier, WTFMove(implementation), WTFMove(instance)));
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +000051 session->suspendIfNeeded();
52 return session;
53}
54
jer.noble@apple.com726186a2017-01-10 18:41:31 +000055MediaKeySession::MediaKeySession(ScriptExecutionContext& context, MediaKeySessionType sessionType, bool useDistinctiveIdentifier, Ref<CDM>&& implementation, Ref<CDMInstance>&& instance)
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +000056 : ActiveDOMObject(&context)
jer.noble@apple.com726186a2017-01-10 18:41:31 +000057 , m_expiration(std::numeric_limits<double>::quiet_NaN())
zandobersek@gmail.comec9a4f12017-02-10 10:22:50 +000058 , m_keyStatuses(MediaKeyStatusMap::create(*this))
jer.noble@apple.com726186a2017-01-10 18:41:31 +000059 , m_useDistinctiveIdentifier(useDistinctiveIdentifier)
60 , m_sessionType(sessionType)
61 , m_implementation(WTFMove(implementation))
62 , m_instance(WTFMove(instance))
jer.noble@apple.com50f1bd62017-01-10 20:09:21 +000063 , m_eventQueue(*this)
64 , m_weakPtrFactory(this)
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +000065{
jer.noble@apple.com726186a2017-01-10 18:41:31 +000066 // https://w3c.github.io/encrypted-media/#dom-mediakeys-setservercertificate
67 // W3C Editor's Draft 09 November 2016
68 // createSession(), ctd.
69
70 // 3.1. Let the sessionId attribute be the empty string.
71 // 3.2. Let the expiration attribute be NaN.
72 // 3.3. Let the closed attribute be a new promise.
73 // 3.4. Let key status be a new empty MediaKeyStatusMap object, and initialize it as follows:
74 // 3.4.1. Let the size attribute be 0.
75 // 3.5. Let the session type value be sessionType.
76 // 3.6. Let the uninitialized value be true.
77 // 3.7. Let the callable value be false.
78 // 3.8. Let the use distinctive identifier value be this object's use distinctive identifier value.
79 // 3.9. Let the cdm implementation value be this object's cdm implementation.
80 // 3.10. Let the cdm instance value be this object's cdm instance.
81
82 UNUSED_PARAM(m_callable);
83 UNUSED_PARAM(m_sessionType);
84 UNUSED_PARAM(m_useDistinctiveIdentifier);
85 UNUSED_PARAM(m_closed);
86 UNUSED_PARAM(m_uninitialized);
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +000087}
88
zandobersek@gmail.comec9a4f12017-02-10 10:22:50 +000089MediaKeySession::~MediaKeySession()
90{
91 m_keyStatuses->detachSession();
92}
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +000093
94const String& MediaKeySession::sessionId() const
95{
jer.noble@apple.com726186a2017-01-10 18:41:31 +000096 return m_sessionId;
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +000097}
98
99double MediaKeySession::expiration() const
100{
jer.noble@apple.com726186a2017-01-10 18:41:31 +0000101 return m_expiration;
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +0000102}
103
jer.noble@apple.com726186a2017-01-10 18:41:31 +0000104Ref<MediaKeyStatusMap> MediaKeySession::keyStatuses() const
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +0000105{
jer.noble@apple.com726186a2017-01-10 18:41:31 +0000106 return m_keyStatuses.copyRef();
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +0000107}
108
jer.noble@apple.com50f1bd62017-01-10 20:09:21 +0000109void MediaKeySession::generateRequest(const AtomicString& initDataType, const BufferSource& initData, Ref<DeferredPromise>&& promise)
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +0000110{
jer.noble@apple.com50f1bd62017-01-10 20:09:21 +0000111 // https://w3c.github.io/encrypted-media/#dom-mediakeysession-generaterequest
112 // W3C Editor's Draft 09 November 2016
113
114 // When this method is invoked, the user agent must run the following steps:
115 // 1. If this object is closed, return a promise rejected with an InvalidStateError.
116 // 2. If this object's uninitialized value is false, return a promise rejected with an InvalidStateError.
117 if (m_closed || !m_uninitialized) {
118 promise->reject(INVALID_STATE_ERR);
119 return;
120 }
121
122 // 3. Let this object's uninitialized value be false.
123 m_uninitialized = false;
124
125 // 4. If initDataType is the empty string, return a promise rejected with a newly created TypeError.
126 // 5. If initData is an empty array, return a promise rejected with a newly created TypeError.
127 if (initDataType.isEmpty() || !initData.length()) {
128 promise->reject(TypeError);
129 return;
130 }
131
132 // 6. If the Key System implementation represented by this object's cdm implementation value does not support
133 // initDataType as an Initialization Data Type, return a promise rejected with a NotSupportedError. String
134 // comparison is case-sensitive.
135 if (!m_implementation->supportsInitDataType(initDataType)) {
136 promise->reject(NOT_SUPPORTED_ERR);
137 return;
138 }
139
140 // 7. Let init data be a copy of the contents of the initData parameter.
141 // 8. Let session type be this object's session type.
142 // 9. Let promise be a new promise.
143 // 10. Run the following steps in parallel:
144 m_taskQueue.enqueueTask([this, initData = SharedBuffer::create(initData.data(), initData.length()), initDataType, promise = WTFMove(promise)] () mutable {
145 // 10.1. If the init data is not valid for initDataType, reject promise with a newly created TypeError.
146 // 10.2. Let sanitized init data be a validated and sanitized version of init data.
147 RefPtr<SharedBuffer> sanitizedInitData = m_implementation->sanitizeInitData(initDataType, initData);
148
149 // 10.3. If the preceding step failed, reject promise with a newly created TypeError.
150 if (!sanitizedInitData) {
151 promise->reject(TypeError);
152 return;
153 }
154
155 // 10.4. If sanitized init data is empty, reject promise with a NotSupportedError.
156 if (sanitizedInitData->isEmpty()) {
157 promise->reject(NOT_SUPPORTED_ERR);
158 return;
159 }
160
161 // 10.5. Let session id be the empty string.
162 // 10.6. Let message be null.
163 // 10.7. Let message type be null.
164 // 10.8. Let cdm be the CDM instance represented by this object's cdm instance value.
165 // 10.9. Use the cdm to execute the following steps:
166 // 10.9.1. If the sanitized init data is not supported by the cdm, reject promise with a NotSupportedError.
167 if (!m_implementation->supportsInitData(initDataType, *sanitizedInitData)) {
168 promise->reject(NOT_SUPPORTED_ERR);
169 return;
170 }
171
172 // 10.9.2 Follow the steps for the value of session type from the following list:
zandobersek@gmail.comff58d7c2017-02-08 09:34:44 +0000173 // ↳ "temporary"
174 // Let requested license type be a temporary non-persistable license.
175 // ↳ "persistent-license"
176 // Let requested license type be a persistable license.
177 // ↳ "persistent-usage-record"
178 // 1. Initialize this object's record of key usage as follows.
179 // Set the list of key IDs known to the session to an empty list.
180 // Set the first decrypt time to null.
181 // Set the latest decrypt time to null.
182 // 2. Let requested license type be a non-persistable license that will
183 // persist a record of key usage.
184
185 if (m_sessionType == MediaKeySessionType::PersistentUsageRecord) {
jer.noble@apple.com50f1bd62017-01-10 20:09:21 +0000186 m_recordOfKeyUsage.clear();
jer.noble@apple.com50f1bd62017-01-10 20:09:21 +0000187 m_firstDecryptTime = 0;
jer.noble@apple.com50f1bd62017-01-10 20:09:21 +0000188 m_latestDecryptTime = 0;
jer.noble@apple.com50f1bd62017-01-10 20:09:21 +0000189 }
190
zandobersek@gmail.comff58d7c2017-02-08 09:34:44 +0000191 m_instance->requestLicense(m_sessionType, initDataType, WTFMove(initData), [this, weakThis = m_weakPtrFactory.createWeakPtr(), promise = WTFMove(promise)] (Ref<SharedBuffer>&& message, const String& sessionId, bool needsIndividualization, CDMInstance::SuccessValue succeeded) mutable {
jer.noble@apple.com50f1bd62017-01-10 20:09:21 +0000192 if (!weakThis)
193 return;
194
195 // 10.9.3. Let session id be a unique Session ID string.
196
197 MediaKeyMessageType messageType;
198 if (!needsIndividualization) {
199 // 10.9.4. If a license request for the requested license type can be generated based on the sanitized init data:
200 // 10.9.4.1. Let message be a license request for the requested license type generated based on the sanitized init data interpreted per initDataType.
201 // 10.9.4.2. Let message type be "license-request".
202 messageType = MediaKeyMessageType::LicenseRequest;
203 } else {
204 // 10.9.5. Otherwise:
205 // 10.9.5.1. Let message be the request that needs to be processed before a license request request for the requested license
206 // type can be generated based on the sanitized init data.
207 // 10.9.5.2. Let message type reflect the type of message, either "license-request" or "individualization-request".
208 messageType = MediaKeyMessageType::IndividualizationRequest;
209 }
210
211 // 10.10. Queue a task to run the following steps:
212 m_taskQueue.enqueueTask([this, promise = WTFMove(promise), message = WTFMove(message), messageType, sessionId, succeeded] () mutable {
213 // 10.10.1. If any of the preceding steps failed, reject promise with a new DOMException whose name is the appropriate error name.
214 if (succeeded == CDMInstance::SuccessValue::Failed) {
215 promise->reject(NOT_SUPPORTED_ERR);
216 return;
217 }
218 // 10.10.2. Set the sessionId attribute to session id.
219 m_sessionId = sessionId;
220
221 // 10.9.3. Let this object's callable value be true.
222 m_callable = true;
223
224 // 10.9.3. Run the Queue a "message" Event algorithm on the session, providing message type and message.
225 enqueueMessage(messageType, message);
226
227 // 10.9.3. Resolve promise.
228 promise->resolve();
229 });
230 });
231 });
232
233 // 11. Return promise.
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +0000234}
235
zandobersek@gmail.coma79fad82017-02-10 10:40:29 +0000236void MediaKeySession::load(const String& sessionId, Ref<DeferredPromise>&& promise)
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +0000237{
zandobersek@gmail.coma79fad82017-02-10 10:40:29 +0000238 // https://w3c.github.io/encrypted-media/#dom-mediakeysession-load
239 // W3C Editor's Draft 09 November 2016
240
241 // 1. If this object is closed, return a promise rejected with an InvalidStateError.
242 // 2. If this object's uninitialized value is false, return a promise rejected with an InvalidStateError.
243 if (m_closed || !m_uninitialized) {
244 promise->reject(INVALID_STATE_ERR);
245 return;
246 }
247
248 // 3. Let this object's uninitialized value be false.
249 m_uninitialized = false;
250
251 // 4. If sessionId is the empty string, return a promise rejected with a newly created TypeError.
252 // 5. If the result of running the Is persistent session type? algorithm on this object's session type is false, return a promise rejected with a newly created TypeError.
253 if (sessionId.isEmpty() || m_sessionType == MediaKeySessionType::Temporary) {
254 promise->reject(TypeError);
255 return;
256 }
257
258 // 6. Let origin be the origin of this object's Document.
259 // This is retrieved in the following task.
260
261 // 7. Let promise be a new promise.
262 // 8. Run the following steps in parallel:
263 m_taskQueue.enqueueTask([this, sessionId, promise = WTFMove(promise)] () mutable {
264 // 8.1. Let sanitized session ID be a validated and/or sanitized version of sessionId.
265 // 8.2. If the preceding step failed, or if sanitized session ID is empty, reject promise with a newly created TypeError.
266 std::optional<String> sanitizedSessionId = m_implementation->sanitizeSessionId(sessionId);
267 if (!sanitizedSessionId || sanitizedSessionId->isEmpty()) {
268 promise->reject(TypeError);
269 return;
270 }
271
272 // 8.3. If there is a MediaKeySession object that is not closed in this object's Document whose sessionId attribute is sanitized session ID, reject promise with a QuotaExceededError.
273 // FIXME: This needs a global MediaKeySession tracker.
274
275 String origin;
276 if (auto* document = downcast<Document>(scriptExecutionContext()))
277 origin = document->securityOrigin().toString();
278
279 // 8.4. Let expiration time be NaN.
280 // 8.5. Let message be null.
281 // 8.6. Let message type be null.
282 // 8.7. Let cdm be the CDM instance represented by this object's cdm instance value.
283 // 8.8. Use the cdm to execute the following steps:
284 m_instance->loadSession(m_sessionType, *sanitizedSessionId, origin, [this, weakThis = m_weakPtrFactory.createWeakPtr(), promise = WTFMove(promise), sanitizedSessionId = *sanitizedSessionId] (std::optional<CDMInstance::KeyStatusVector>&& knownKeys, std::optional<double>&& expiration, std::optional<CDMInstance::Message>&& message, CDMInstance::SuccessValue succeeded, CDMInstance::SessionLoadFailure failure) mutable {
285 // 8.8.1. If there is no data stored for the sanitized session ID in the origin, resolve promise with false and abort these steps.
286 // 8.8.2. If the stored session's session type is not the same as the current MediaKeySession session type, reject promise with a newly created TypeError.
287 // 8.8.3. Let session data be the data stored for the sanitized session ID in the origin. This must not include data from other origin(s) or that is not associated with an origin.
288 // 8.8.4. If there is a MediaKeySession object that is not closed in any Document and that represents the session data, reject promise with a QuotaExceededError.
289 // 8.8.5. Load the session data.
290 // 8.8.6. If the session data indicates an expiration time for the session, let expiration time be the expiration time in milliseconds since 01 January 1970 UTC.
291 // 8.8.7. If the CDM needs to send a message:
292 // 8.8.7.1. Let message be a message generated by the CDM based on the session data.
293 // 8.8.7.2. Let message type be the appropriate MediaKeyMessageType for the message.
294 // NOTE: Steps 8.8.1. through 8.8.7. should be implemented in CDMInstance.
295
296 if (succeeded == CDMInstance::SuccessValue::Failed) {
297 switch (failure) {
298 case CDMInstance::SessionLoadFailure::NoSessionData:
299 promise->resolve<IDLBoolean>(false);
300 return;
301 case CDMInstance::SessionLoadFailure::MismatchedSessionType:
302 promise->reject(TypeError);
303 return;
304 case CDMInstance::SessionLoadFailure::QuotaExceeded:
305 promise->reject(QUOTA_EXCEEDED_ERR);
306 return;
307 case CDMInstance::SessionLoadFailure::None:
308 case CDMInstance::SessionLoadFailure::Other:
309 // In any other case, the session load failure will cause a rejection in the following task.
310 break;
311 }
312 }
313
314 // 8.9. Queue a task to run the following steps:
315 m_taskQueue.enqueueTask([this, knownKeys = WTFMove(knownKeys), expiration = WTFMove(expiration), message = WTFMove(message), sanitizedSessionId, succeeded, promise = WTFMove(promise)] () mutable {
316 // 8.9.1. If any of the preceding steps failed, reject promise with a the appropriate error name.
317 if (succeeded == CDMInstance::SuccessValue::Failed) {
318 promise->reject(NOT_SUPPORTED_ERR);
319 return;
320 }
321
322 // 8.9.2. Set the sessionId attribute to sanitized session ID.
323 // 8.9.3. Let this object's callable value be true.
324 m_sessionId = sanitizedSessionId;
325 m_callable = true;
326
327 // 8.9.4. If the loaded session contains information about any keys (there are known keys), run the Update Key Statuses algorithm on the session, providing each key's key ID along with the appropriate MediaKeyStatus.
328 if (knownKeys)
329 updateKeyStatuses(WTFMove(*knownKeys));
330
331 // 8.9.5. Run the Update Expiration algorithm on the session, providing expiration time.
332 // This must be run, and NaN is the default value if the CDM instance doesn't provide one.
333 updateExpiration(expiration.value_or(std::numeric_limits<double>::quiet_NaN()));
334
335 // 8.9.6. If message is not null, run the Queue a "message" Event algorithm on the session, providing message type and message.
336 if (message)
337 enqueueMessage(message->first, WTFMove(message->second));
338
339 // 8.9.7. Resolve promise with true.
340 promise->resolve<IDLBoolean>(true);
341 });
342 });
343 });
344
345 // 9. Return promise.
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +0000346}
347
zandobersek@gmail.come89b6962017-02-02 06:28:03 +0000348void MediaKeySession::update(const BufferSource& response, Ref<DeferredPromise>&& promise)
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +0000349{
zandobersek@gmail.come89b6962017-02-02 06:28:03 +0000350 // https://w3c.github.io/encrypted-media/#dom-mediakeysession-update
351 // W3C Editor's Draft 09 November 2016
352
353 // When this method is invoked, the user agent must run the following steps:
354 // 1. If this object is closed, return a promise rejected with an InvalidStateError.
355 // 2. If this object's callable value is false, return a promise rejected with an InvalidStateError.
356 if (m_closed || !m_callable) {
357 promise->reject(INVALID_STATE_ERR);
358 return;
359 }
360
361 // 3. If response is an empty array, return a promise rejected with a newly created TypeError.
362 if (!response.length()) {
363 promise->reject(TypeError);
364 return;
365 }
366
367 // 4. Let response copy be a copy of the contents of the response parameter.
368 // 5. Let promise be a new promise.
369 // 6. Run the following steps in parallel:
370 m_taskQueue.enqueueTask([this, response = SharedBuffer::create(response.data(), response.length()), promise = WTFMove(promise)] () mutable {
371 // 6.1. Let sanitized response be a validated and/or sanitized version of response copy.
372 RefPtr<SharedBuffer> sanitizedResponse = m_implementation->sanitizeResponse(response);
373
374 // 6.2. If the preceding step failed, or if sanitized response is empty, reject promise with a newly created TypeError.
375 if (!sanitizedResponse || sanitizedResponse->isEmpty()) {
376 promise->reject(TypeError);
377 return;
378 }
379
zandobersek@gmail.come89b6962017-02-02 06:28:03 +0000380 // 6.3. Let message be null.
381 // 6.4. Let message type be null.
382 // 6.5. Let session closed be false.
383 // 6.6. Let cdm be the CDM instance represented by this object's cdm instance value.
384 // 6.7. Use the cdm to execute the following steps:
zandobersek@gmail.comec9a4f12017-02-10 10:22:50 +0000385 m_instance->updateLicense(m_sessionId, m_sessionType, *sanitizedResponse, [this, weakThis = m_weakPtrFactory.createWeakPtr(), promise = WTFMove(promise)] (bool sessionWasClosed, std::optional<CDMInstance::KeyStatusVector>&& changedKeys, std::optional<double>&& changedExpiration, std::optional<CDMInstance::Message>&& message, CDMInstance::SuccessValue succeeded) mutable {
zandobersek@gmail.come89b6962017-02-02 06:28:03 +0000386 if (!weakThis)
387 return;
388
389 // 6.7.1. If the format of sanitized response is invalid in any way, reject promise with a newly created TypeError.
390 // 6.7.2. Process sanitized response, following the stipulation for the first matching condition from the following list:
391 // ↳ If sanitized response contains a license or key(s)
392 // Process sanitized response, following the stipulation for the first matching condition from the following list:
393 // ↳ If sessionType is "temporary" and sanitized response does not specify that session data, including any license, key(s), or similar session data it contains, should be stored
394 // Process sanitized response, not storing any session data.
395 // ↳ If sessionType is "persistent-license" and sanitized response contains a persistable license
396 // Process sanitized response, storing the license/key(s) and related session data contained in sanitized response. Such data must be stored such that only the origin of this object's Document can access it.
397 // ↳ If sessionType is "persistent-usage-record" and sanitized response contains a non-persistable license
398 // Run the following steps:
399 // 6.7.2.3.1. Process sanitized response, not storing any session data.
400 // 6.7.2.3.2. If processing sanitized response results in the addition of keys to the set of known keys, add the key IDs of these keys to this object's record of key usage.
401 // ↳ Otherwise
402 // Reject promise with a newly created TypeError.
403 // ↳ If sanitized response contains a record of license destruction acknowledgement and sessionType is "persistent-license"
404 // Run the following steps:
405 // 6.7.2.1. Close the key session and clear all stored session data associated with this object, including the sessionId and record of license destruction.
406 // 6.7.2.2. Set session closed to true.
407 // ↳ Otherwise
408 // Process sanitized response, not storing any session data.
409 // NOTE: Steps 6.7.1. and 6.7.2. should be implemented in CDMInstance.
410
411 if (succeeded == CDMInstance::SuccessValue::Failed) {
412 promise->reject(TypeError);
413 return;
414 }
415
416 // 6.7.3. If a message needs to be sent to the server, execute the following steps:
417 // 6.7.3.1. Let message be that message.
418 // 6.7.3.2. Let message type be the appropriate MediaKeyMessageType for the message.
419 // 6.8. Queue a task to run the following steps:
420 m_taskQueue.enqueueTask([this, sessionWasClosed, changedKeys = WTFMove(changedKeys), changedExpiration = WTFMove(changedExpiration), message = WTFMove(message), promise = WTFMove(promise)] () mutable {
421 // 6.8.1.
422 if (sessionWasClosed) {
423 // ↳ If session closed is true:
424 // Run the Session Closed algorithm on this object.
425 sessionClosed();
426 } else {
427 // ↳ Otherwise:
428 // Run the following steps:
429 // 6.8.1.1. If the set of keys known to the CDM for this object changed or the status of any key(s) changed, run the Update Key Statuses
430 // algorithm on the session, providing each known key's key ID along with the appropriate MediaKeyStatus. Should additional
431 // processing be necessary to determine with certainty the status of a key, use "status-pending". Once the additional processing
432 // for one or more keys has completed, run the Update Key Statuses algorithm again with the actual status(es).
433 if (changedKeys)
434 updateKeyStatuses(WTFMove(*changedKeys));
435
436 // 6.8.1.2. If the expiration time for the session changed, run the Update Expiration algorithm on the session, providing the new expiration time.
437 if (changedExpiration)
438 updateExpiration(*changedExpiration);
439
440 // 6.8.1.3. If any of the preceding steps failed, reject promise with a new DOMException whose name is the appropriate error name.
441 // FIXME: At this point the implementations of preceding steps can't fail.
442
443 // 6.8.1.4. If message is not null, run the Queue a "message" Event algorithm on the session, providing message type and message.
444 if (message) {
445 MediaKeyMessageType messageType;
446 switch (message->first) {
447 case CDMInstance::MessageType::LicenseRequest:
448 messageType = MediaKeyMessageType::LicenseRequest;
449 break;
450 case CDMInstance::MessageType::LicenseRenewal:
451 messageType = MediaKeyMessageType::LicenseRenewal;
452 break;
453 case CDMInstance::MessageType::LicenseRelease:
454 messageType = MediaKeyMessageType::LicenseRelease;
455 break;
456 case CDMInstance::MessageType::IndividualizationRequest:
457 messageType = MediaKeyMessageType::IndividualizationRequest;
458 break;
459 }
460
461 enqueueMessage(messageType, WTFMove(message->second));
462 }
463 }
464
465 // 6.8.2. Resolve promise.
466 promise->resolve();
467 });
468 });
469 });
470
471 // 7. Return promise.
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +0000472}
473
zandobersek@gmail.com5ee2b102017-02-08 10:25:23 +0000474void MediaKeySession::close(Ref<DeferredPromise>&& promise)
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +0000475{
zandobersek@gmail.com5ee2b102017-02-08 10:25:23 +0000476 // https://w3c.github.io/encrypted-media/#dom-mediakeysession-close
477 // W3C Editor's Draft 09 November 2016
478
479 // 1. Let session be the associated MediaKeySession object.
480 // 2. If session is closed, return a resolved promise.
481 if (m_closed) {
482 promise->resolve();
483 return;
484 }
485
486 // 3. If session's callable value is false, return a promise rejected with an InvalidStateError.
487 if (!m_callable) {
488 promise->reject(INVALID_STATE_ERR);
489 return;
490 }
491
492 // 4. Let promise be a new promise.
493 // 5. Run the following steps in parallel:
494 m_taskQueue.enqueueTask([this, promise = WTFMove(promise)] () mutable {
495 // 5.1. Let cdm be the CDM instance represented by session's cdm instance value.
496 // 5.2. Use cdm to close the key session associated with session.
497 m_instance->closeSession(m_sessionId, [this, weakThis = m_weakPtrFactory.createWeakPtr(), promise = WTFMove(promise)] () mutable {
498 if (!weakThis)
499 return;
500
501 // 5.3. Queue a task to run the following steps:
502 m_taskQueue.enqueueTask([this, promise = WTFMove(promise)] () mutable {
503 // 5.3.1. Run the Session Closed algorithm on the session.
504 sessionClosed();
505
506 // 5.3.2. Resolve promise.
507 promise->resolve();
508 });
509 });
510 });
511
512 // 6. Return promise.
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +0000513}
514
zandobersek@gmail.comabb443e2017-02-08 10:39:49 +0000515void MediaKeySession::remove(Ref<DeferredPromise>&& promise)
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +0000516{
zandobersek@gmail.comabb443e2017-02-08 10:39:49 +0000517 // https://w3c.github.io/encrypted-media/#dom-mediakeysession-remove
518 // W3C Editor's Draft 09 November 2016
519
520 // 1. If this object is closed, return a promise rejected with an InvalidStateError.
521 // 2. If this object's callable value is false, return a promise rejected with an InvalidStateError.
522 if (m_closed || !m_callable) {
523 promise->reject(INVALID_STATE_ERR);
524 return;
525 }
526
527 // 3. Let promise be a new promise.
528 // 4. Run the following steps in parallel:
529 m_taskQueue.enqueueTask([this, promise = WTFMove(promise)] () mutable {
530 // 4.1. Let cdm be the CDM instance represented by this object's cdm instance value.
531 // 4.2. Let message be null.
532 // 4.3. Let message type be null.
533
534 // 4.4. Use the cdm to execute the following steps:
535 m_instance->removeSessionData(m_sessionId, m_sessionType, [this, weakThis = m_weakPtrFactory.createWeakPtr(), promise = WTFMove(promise)] (CDMInstance::KeyStatusVector&& keys, std::optional<Ref<SharedBuffer>>&& message, CDMInstance::SuccessValue succeeded) mutable {
536 if (!weakThis)
537 return;
538
539 // 4.4.1. If any license(s) and/or key(s) are associated with the session:
540 // 4.4.1.1. Destroy the license(s) and/or key(s) associated with the session.
541 // 4.4.1.2. Follow the steps for the value of this object's session type from the following list:
542 // ↳ "temporary"
543 // 4.4.1.2.1.1 Continue with the following steps.
544 // ↳ "persistent-license"
545 // 4.4.1.2.2.1. Let record of license destruction be a record of license destruction for the license represented by this object.
546 // 4.4.1.2.2.2. Store the record of license destruction.
547 // 4.4.1.2.2.3. Let message be a message containing or reflecting the record of license destruction.
548 // ↳ "persistent-usage-record"
549 // 4.4.1.2.3.1. Store this object's record of key usage.
550 // 4.4.1.2.3.2. Let message be a message containing or reflecting this object's record of key usage.
551 // NOTE: Step 4.4.1. should be implemented in CDMInstance.
552
553 // 4.5. Queue a task to run the following steps:
554 m_taskQueue.enqueueTask([this, keys = WTFMove(keys), message = WTFMove(message), succeeded, promise = WTFMove(promise)] () mutable {
555 // 4.5.1. Run the Update Key Statuses algorithm on the session, providing all key ID(s) in the session along with the "released" MediaKeyStatus value for each.
556 updateKeyStatuses(WTFMove(keys));
557
558 // 4.5.2. Run the Update Expiration algorithm on the session, providing NaN.
559 updateExpiration(std::numeric_limits<double>::quiet_NaN());
560
561 // 4.5.3. If any of the preceding steps failed, reject promise with a new DOMException whose name is the appropriate error name.
562 if (succeeded == CDMInstance::SuccessValue::Failed) {
563 promise->reject(NOT_SUPPORTED_ERR);
564 return;
565 }
566
567 // 4.5.4. Let message type be "license-release".
568 // 4.5.5. If message is not null, run the Queue a "message" Event algorithm on the session, providing message type and message.
569 if (message)
570 enqueueMessage(MediaKeyMessageType::LicenseRelease, *message);
571
572 // 4.5.6. Resolve promise.
573 promise->resolve();
574 });
575 });
576 });
577
578 // 5. Return promise.
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +0000579}
580
zandobersek@gmail.coma065da02017-02-10 10:31:57 +0000581void MediaKeySession::registerClosedPromise(ClosedPromise&& promise)
582{
583 ASSERT(!m_closedPromise);
584 if (m_closed) {
585 promise.resolve();
586 return;
587 }
588 m_closedPromise = WTFMove(promise);
589}
590
jer.noble@apple.com50f1bd62017-01-10 20:09:21 +0000591void MediaKeySession::enqueueMessage(MediaKeyMessageType messageType, const SharedBuffer& message)
592{
593 // 6.4.1 Queue a "message" Event
594 // https://w3c.github.io/encrypted-media/#queue-message
595 // W3C Editor's Draft 09 November 2016
596
597 // The following steps are run:
598 // 1. Let the session be the specified MediaKeySession object.
599 // 2. Queue a task to create an event named message that does not bubble and is not cancellable using the MediaKeyMessageEvent
600 // interface with its type attribute set to message and its isTrusted attribute initialized to true, and dispatch it at the
601 // session.
zandobersek@gmail.comfe384592017-01-17 10:11:09 +0000602 auto messageEvent = MediaKeyMessageEvent::create(eventNames().messageEvent, {messageType, message.createArrayBuffer()}, Event::IsTrusted::Yes);
jer.noble@apple.com50f1bd62017-01-10 20:09:21 +0000603 m_eventQueue.enqueueEvent(WTFMove(messageEvent));
604}
605
zandobersek@gmail.comec9a4f12017-02-10 10:22:50 +0000606void MediaKeySession::updateKeyStatuses(CDMInstance::KeyStatusVector&& inputStatuses)
zandobersek@gmail.come89b6962017-02-02 06:28:03 +0000607{
zandobersek@gmail.comec9a4f12017-02-10 10:22:50 +0000608 // https://w3c.github.io/encrypted-media/#update-key-statuses
609 // W3C Editor's Draft 09 November 2016
610
611 // 1. Let the session be the associated MediaKeySession object.
612 // 2. Let the input statuses be the sequence of pairs key ID and associated MediaKeyStatus pairs.
613 // 3. Let the statuses be session's keyStatuses attribute.
614 // 4. Run the following steps to replace the contents of statuses:
615 // 4.1. Empty statuses.
616 // 4.2. For each pair in input statuses.
617 // 4.2.1. Let pair be the pair.
618 // 4.2.2. Insert an entry for pair's key ID into statuses with the value of pair's MediaKeyStatus value.
619
620 static auto toMediaKeyStatus = [] (CDMInstance::KeyStatus status) -> MediaKeyStatus {
621 switch (status) {
622 case CDMInstance::KeyStatus::Usable:
623 return MediaKeyStatus::Usable;
624 case CDMInstance::KeyStatus::Expired:
625 return MediaKeyStatus::Expired;
626 case CDMInstance::KeyStatus::Released:
627 return MediaKeyStatus::Released;
628 case CDMInstance::KeyStatus::OutputRestricted:
629 return MediaKeyStatus::OutputRestricted;
630 case CDMInstance::KeyStatus::OutputDownscaled:
631 return MediaKeyStatus::OutputDownscaled;
632 case CDMInstance::KeyStatus::StatusPending:
633 return MediaKeyStatus::StatusPending;
634 case CDMInstance::KeyStatus::InternalError:
635 return MediaKeyStatus::InternalError;
636 };
637 };
638
639 m_statuses.clear();
640 m_statuses.reserveCapacity(inputStatuses.size());
641 for (auto& status : inputStatuses)
642 m_statuses.uncheckedAppend({ WTFMove(status.first), toMediaKeyStatus(status.second) });
643
644 // 5. Queue a task to fire a simple event named keystatuseschange at the session.
645 m_eventQueue.enqueueEvent(Event::create(eventNames().keystatuseschangeEvent, false, false));
646
647 // 6. Queue a task to run the Attempt to Resume Playback If Necessary algorithm on each of the media element(s) whose mediaKeys attribute is the MediaKeys object that created the session.
648 // FIXME: Implement.
zandobersek@gmail.come89b6962017-02-02 06:28:03 +0000649}
650
651void MediaKeySession::updateExpiration(double)
652{
653 notImplemented();
654}
655
656void MediaKeySession::sessionClosed()
657{
zandobersek@gmail.coma065da02017-02-10 10:31:57 +0000658 // https://w3c.github.io/encrypted-media/#session-closed
659 // W3C Editor's Draft 09 November 2016
660
661 // 1. Let session be the associated MediaKeySession object.
662 // 2. If session's session type is "persistent-usage-record", execute the following steps in parallel:
663 if (m_sessionType == MediaKeySessionType::PersistentUsageRecord) {
664 // 2.1. Let cdm be the CDM instance represented by session's cdm instance value.
665 // 2.2. Use cdm to store session's record of key usage, if it exists.
666 m_instance->storeRecordOfKeyUsage(m_sessionId);
667 }
668
669 // 3. Run the Update Key Statuses algorithm on the session, providing an empty sequence.
670 updateKeyStatuses({ });
671
672 // 4. Run the Update Expiration algorithm on the session, providing NaN.
673 updateExpiration(std::numeric_limits<double>::quiet_NaN());
674
675 // Let's consider the session closed before any promise on the 'closed' attribute is resolved.
676 m_closed = true;
677
678 // 5. Let promise be the closed attribute of the session.
679 // 6. Resolve promise.
680 if (m_closedPromise)
681 m_closedPromise->resolve();
zandobersek@gmail.come89b6962017-02-02 06:28:03 +0000682}
683
zandobersek@gmail.come4e74e92016-11-10 17:08:41 +0000684bool MediaKeySession::hasPendingActivity() const
685{
686 notImplemented();
687 return false;
688}
689
690const char* MediaKeySession::activeDOMObjectName() const
691{
692 notImplemented();
693 return "MediaKeySession";
694}
695
696bool MediaKeySession::canSuspendForDocumentSuspension() const
697{
698 notImplemented();
699 return false;
700}
701
702void MediaKeySession::stop()
703{
704 notImplemented();
705}
706
707} // namespace WebCore
708
709#endif