d5acedae3635b7871e7e14c1229093ab088ecf21
[WebKit-https.git] / Source / WebCore / Modules / encryptedmedia / MediaKeySession.cpp
1 /*
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
34 #include "CDM.h"
35 #include "CDMInstance.h"
36 #include "Document.h"
37 #include "EventNames.h"
38 #include "MediaKeyMessageEvent.h"
39 #include "MediaKeyMessageType.h"
40 #include "MediaKeyStatusMap.h"
41 #include "MediaKeys.h"
42 #include "NotImplemented.h"
43 #include "Page.h"
44 #include "SecurityOrigin.h"
45 #include "SecurityOriginData.h"
46 #include "Settings.h"
47 #include "SharedBuffer.h"
48
49 namespace WebCore {
50
51 Ref<MediaKeySession> MediaKeySession::create(ScriptExecutionContext& context, WeakPtr<MediaKeys>&& keys, MediaKeySessionType sessionType, bool useDistinctiveIdentifier, Ref<CDM>&& implementation, Ref<CDMInstance>&& instance)
52 {
53     auto session = adoptRef(*new MediaKeySession(context, WTFMove(keys), sessionType, useDistinctiveIdentifier, WTFMove(implementation), WTFMove(instance)));
54     session->suspendIfNeeded();
55     return session;
56 }
57
58 MediaKeySession::MediaKeySession(ScriptExecutionContext& context, WeakPtr<MediaKeys>&& keys, MediaKeySessionType sessionType, bool useDistinctiveIdentifier, Ref<CDM>&& implementation, Ref<CDMInstance>&& instance)
59     : ActiveDOMObject(&context)
60     , m_keys(WTFMove(keys))
61     , m_expiration(std::numeric_limits<double>::quiet_NaN())
62     , m_keyStatuses(MediaKeyStatusMap::create(*this))
63     , m_useDistinctiveIdentifier(useDistinctiveIdentifier)
64     , m_sessionType(sessionType)
65     , m_implementation(WTFMove(implementation))
66     , m_instance(WTFMove(instance))
67     , m_eventQueue(*this)
68 {
69     // https://w3c.github.io/encrypted-media/#dom-mediakeys-setservercertificate
70     // W3C Editor's Draft 09 November 2016
71     // createSession(), ctd.
72
73     // 3.1. Let the sessionId attribute be the empty string.
74     // 3.2. Let the expiration attribute be NaN.
75     // 3.3. Let the closed attribute be a new promise.
76     // 3.4. Let key status be a new empty MediaKeyStatusMap object, and initialize it as follows:
77     // 3.4.1. Let the size attribute be 0.
78     // 3.5. Let the session type value be sessionType.
79     // 3.6. Let the uninitialized value be true.
80     // 3.7. Let the callable value be false.
81     // 3.8. Let the use distinctive identifier value be this object's use distinctive identifier value.
82     // 3.9. Let the cdm implementation value be this object's cdm implementation.
83     // 3.10. Let the cdm instance value be this object's cdm instance.
84
85     UNUSED_PARAM(m_callable);
86     UNUSED_PARAM(m_sessionType);
87     UNUSED_PARAM(m_useDistinctiveIdentifier);
88     UNUSED_PARAM(m_closed);
89     UNUSED_PARAM(m_uninitialized);
90
91     m_instance->setClient(*this);
92 }
93
94 MediaKeySession::~MediaKeySession()
95 {
96     m_keyStatuses->detachSession();
97     m_instance->clearClient();
98 }
99
100 const String& MediaKeySession::sessionId() const
101 {
102     return m_sessionId;
103 }
104
105 double MediaKeySession::expiration() const
106 {
107     return m_expiration;
108 }
109
110 Ref<MediaKeyStatusMap> MediaKeySession::keyStatuses() const
111 {
112     return m_keyStatuses.copyRef();
113 }
114
115 void MediaKeySession::generateRequest(const AtomicString& initDataType, const BufferSource& initData, Ref<DeferredPromise>&& promise)
116 {
117     // https://w3c.github.io/encrypted-media/#dom-mediakeysession-generaterequest
118     // W3C Editor's Draft 09 November 2016
119
120     // When this method is invoked, the user agent must run the following steps:
121     // 1. If this object is closed, return a promise rejected with an InvalidStateError.
122     // 2. If this object's uninitialized value is false, return a promise rejected with an InvalidStateError.
123     if (m_closed || !m_uninitialized) {
124         promise->reject(InvalidStateError);
125         return;
126     }
127
128     // 3. Let this object's uninitialized value be false.
129     m_uninitialized = false;
130
131     // 4. If initDataType is the empty string, return a promise rejected with a newly created TypeError.
132     // 5. If initData is an empty array, return a promise rejected with a newly created TypeError.
133     if (initDataType.isEmpty() || !initData.length()) {
134         promise->reject(TypeError);
135         return;
136     }
137
138     // 6. If the Key System implementation represented by this object's cdm implementation value does not support
139     //    initDataType as an Initialization Data Type, return a promise rejected with a NotSupportedError. String
140     //    comparison is case-sensitive.
141     if (!m_implementation->supportsInitDataType(initDataType)) {
142         promise->reject(NotSupportedError);
143         return;
144     }
145
146     // 7. Let init data be a copy of the contents of the initData parameter.
147     // 8. Let session type be this object's session type.
148     // 9. Let promise be a new promise.
149     // 10. Run the following steps in parallel:
150     m_taskQueue.enqueueTask([this, initData = SharedBuffer::create(initData.data(), initData.length()), initDataType, promise = WTFMove(promise)] () mutable {
151         // 10.1. If the init data is not valid for initDataType, reject promise with a newly created TypeError.
152         // 10.2. Let sanitized init data be a validated and sanitized version of init data.
153         RefPtr<SharedBuffer> sanitizedInitData = m_implementation->sanitizeInitData(initDataType, initData);
154
155         // 10.3. If the preceding step failed, reject promise with a newly created TypeError.
156         if (!sanitizedInitData) {
157             promise->reject(TypeError);
158             return;
159         }
160
161         // 10.4. If sanitized init data is empty, reject promise with a NotSupportedError.
162         if (sanitizedInitData->isEmpty()) {
163             promise->reject(NotSupportedError);
164             return;
165         }
166
167         // 10.5. Let session id be the empty string.
168         // 10.6. Let message be null.
169         // 10.7. Let message type be null.
170         // 10.8. Let cdm be the CDM instance represented by this object's cdm instance value.
171         // 10.9. Use the cdm to execute the following steps:
172         // 10.9.1. If the sanitized init data is not supported by the cdm, reject promise with a NotSupportedError.
173         if (!m_implementation->supportsInitData(initDataType, *sanitizedInitData)) {
174             promise->reject(NotSupportedError);
175             return;
176         }
177
178         // 10.9.2 Follow the steps for the value of session type from the following list:
179         //   ↳ "temporary"
180         //     Let requested license type be a temporary non-persistable license.
181         //   ↳ "persistent-license"
182         //     Let requested license type be a persistable license.
183         //   ↳ "persistent-usage-record"
184         //     1. Initialize this object's record of key usage as follows.
185         //        Set the list of key IDs known to the session to an empty list.
186         //        Set the first decrypt time to null.
187         //        Set the latest decrypt time to null.
188         //     2. Let requested license type be a non-persistable license that will
189         //        persist a record of key usage.
190
191         if (m_sessionType == MediaKeySessionType::PersistentUsageRecord) {
192             m_recordOfKeyUsage.clear();
193             m_firstDecryptTime = 0;
194             m_latestDecryptTime = 0;
195         }
196
197         m_instance->requestLicense(m_sessionType, initDataType, WTFMove(initData), [this, weakThis = m_weakPtrFactory.createWeakPtr(*this), promise = WTFMove(promise)] (Ref<SharedBuffer>&& message, const String& sessionId, bool needsIndividualization, CDMInstance::SuccessValue succeeded) mutable {
198             if (!weakThis)
199                 return;
200
201             // 10.9.3. Let session id be a unique Session ID string.
202
203             MediaKeyMessageType messageType;
204             if (!needsIndividualization) {
205                 // 10.9.4. If a license request for the requested license type can be generated based on the sanitized init data:
206                 // 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.
207                 // 10.9.4.2. Let message type be "license-request".
208                 messageType = MediaKeyMessageType::LicenseRequest;
209             } else {
210                 // 10.9.5. Otherwise:
211                 // 10.9.5.1. Let message be the request that needs to be processed before a license request request for the requested license
212                 //           type can be generated based on the sanitized init data.
213                 // 10.9.5.2. Let message type reflect the type of message, either "license-request" or "individualization-request".
214                 messageType = MediaKeyMessageType::IndividualizationRequest;
215             }
216
217             // 10.10. Queue a task to run the following steps:
218             m_taskQueue.enqueueTask([this, promise = WTFMove(promise), message = WTFMove(message), messageType, sessionId, succeeded] () mutable {
219                 // 10.10.1. If any of the preceding steps failed, reject promise with a new DOMException whose name is the appropriate error name.
220                 if (succeeded == CDMInstance::SuccessValue::Failed) {
221                     promise->reject(NotSupportedError);
222                     return;
223                 }
224                 // 10.10.2. Set the sessionId attribute to session id.
225                 m_sessionId = sessionId;
226
227                 // 10.9.3. Let this object's callable value be true.
228                 m_callable = true;
229
230                 // 10.9.3. Run the Queue a "message" Event algorithm on the session, providing message type and message.
231                 enqueueMessage(messageType, message);
232
233                 // 10.9.3. Resolve promise.
234                 promise->resolve();
235             });
236         });
237     });
238
239     // 11. Return promise.
240 }
241
242 void MediaKeySession::load(const String& sessionId, Ref<DeferredPromise>&& promise)
243 {
244     // https://w3c.github.io/encrypted-media/#dom-mediakeysession-load
245     // W3C Editor's Draft 09 November 2016
246
247     // 1. If this object is closed, return a promise rejected with an InvalidStateError.
248     // 2. If this object's uninitialized value is false, return a promise rejected with an InvalidStateError.
249     if (m_closed || !m_uninitialized) {
250         promise->reject(InvalidStateError);
251         return;
252     }
253
254     // 3. Let this object's uninitialized value be false.
255     m_uninitialized = false;
256
257     // 4. If sessionId is the empty string, return a promise rejected with a newly created TypeError.
258     // 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.
259     if (sessionId.isEmpty() || m_sessionType == MediaKeySessionType::Temporary) {
260         promise->reject(TypeError);
261         return;
262     }
263
264     // 6. Let origin be the origin of this object's Document.
265     // This is retrieved in the following task.
266
267     // 7. Let promise be a new promise.
268     // 8. Run the following steps in parallel:
269     m_taskQueue.enqueueTask([this, sessionId, promise = WTFMove(promise)] () mutable {
270         // 8.1. Let sanitized session ID be a validated and/or sanitized version of sessionId.
271         // 8.2. If the preceding step failed, or if sanitized session ID is empty, reject promise with a newly created TypeError.
272         std::optional<String> sanitizedSessionId = m_implementation->sanitizeSessionId(sessionId);
273         if (!sanitizedSessionId || sanitizedSessionId->isEmpty()) {
274             promise->reject(TypeError);
275             return;
276         }
277
278         // 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.
279         // FIXME: This needs a global MediaKeySession tracker.
280
281         String origin;
282         if (auto* document = downcast<Document>(scriptExecutionContext()))
283             origin = document->securityOrigin().toString();
284
285         // 8.4. Let expiration time be NaN.
286         // 8.5. Let message be null.
287         // 8.6. Let message type be null.
288         // 8.7. Let cdm be the CDM instance represented by this object's cdm instance value.
289         // 8.8. Use the cdm to execute the following steps:
290         m_instance->loadSession(m_sessionType, *sanitizedSessionId, origin, [this, weakThis = m_weakPtrFactory.createWeakPtr(*this), 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 {
291             // 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.
292             // 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.
293             // 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.
294             // 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.
295             // 8.8.5. Load the session data.
296             // 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.
297             // 8.8.7. If the CDM needs to send a message:
298             //   8.8.7.1. Let message be a message generated by the CDM based on the session data.
299             //   8.8.7.2. Let message type be the appropriate MediaKeyMessageType for the message.
300             // NOTE: Steps 8.8.1. through 8.8.7. should be implemented in CDMInstance.
301
302             if (succeeded == CDMInstance::SuccessValue::Failed) {
303                 switch (failure) {
304                 case CDMInstance::SessionLoadFailure::NoSessionData:
305                     promise->resolve<IDLBoolean>(false);
306                     return;
307                 case CDMInstance::SessionLoadFailure::MismatchedSessionType:
308                     promise->reject(TypeError);
309                     return;
310                 case CDMInstance::SessionLoadFailure::QuotaExceeded:
311                     promise->reject(QuotaExceededError);
312                     return;
313                 case CDMInstance::SessionLoadFailure::None:
314                 case CDMInstance::SessionLoadFailure::Other:
315                     // In any other case, the session load failure will cause a rejection in the following task.
316                     break;
317                 }
318             }
319
320             // 8.9. Queue a task to run the following steps:
321             m_taskQueue.enqueueTask([this, knownKeys = WTFMove(knownKeys), expiration = WTFMove(expiration), message = WTFMove(message), sanitizedSessionId, succeeded, promise = WTFMove(promise)] () mutable {
322                 // 8.9.1. If any of the preceding steps failed, reject promise with a the appropriate error name.
323                 if (succeeded == CDMInstance::SuccessValue::Failed) {
324                     promise->reject(NotSupportedError);
325                     return;
326                 }
327
328                 // 8.9.2. Set the sessionId attribute to sanitized session ID.
329                 // 8.9.3. Let this object's callable value be true.
330                 m_sessionId = sanitizedSessionId;
331                 m_callable = true;
332
333                 // 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.
334                 if (knownKeys)
335                     updateKeyStatuses(WTFMove(*knownKeys));
336
337                 // 8.9.5. Run the Update Expiration algorithm on the session, providing expiration time.
338                 // This must be run, and NaN is the default value if the CDM instance doesn't provide one.
339                 updateExpiration(expiration.value_or(std::numeric_limits<double>::quiet_NaN()));
340
341                 // 8.9.6. If message is not null, run the Queue a "message" Event algorithm on the session, providing message type and message.
342                 if (message)
343                     enqueueMessage(message->first, WTFMove(message->second));
344
345                 // 8.9.7. Resolve promise with true.
346                 promise->resolve<IDLBoolean>(true);
347             });
348         });
349     });
350
351     // 9. Return promise.
352 }
353
354 void MediaKeySession::update(const BufferSource& response, Ref<DeferredPromise>&& promise)
355 {
356     // https://w3c.github.io/encrypted-media/#dom-mediakeysession-update
357     // W3C Editor's Draft 09 November 2016
358
359     // When this method is invoked, the user agent must run the following steps:
360     // 1. If this object is closed, return a promise rejected with an InvalidStateError.
361     // 2. If this object's callable value is false, return a promise rejected with an InvalidStateError.
362     if (m_closed || !m_callable) {
363         promise->reject(InvalidStateError);
364         return;
365     }
366
367     // 3. If response is an empty array, return a promise rejected with a newly created TypeError.
368     if (!response.length()) {
369         promise->reject(TypeError);
370         return;
371     }
372
373     // 4. Let response copy be a copy of the contents of the response parameter.
374     // 5. Let promise be a new promise.
375     // 6. Run the following steps in parallel:
376     m_taskQueue.enqueueTask([this, response = SharedBuffer::create(response.data(), response.length()), promise = WTFMove(promise)] () mutable {
377         // 6.1. Let sanitized response be a validated and/or sanitized version of response copy.
378         RefPtr<SharedBuffer> sanitizedResponse = m_implementation->sanitizeResponse(response);
379
380         // 6.2. If the preceding step failed, or if sanitized response is empty, reject promise with a newly created TypeError.
381         if (!sanitizedResponse || sanitizedResponse->isEmpty()) {
382             promise->reject(TypeError);
383             return;
384         }
385
386         // 6.3. Let message be null.
387         // 6.4. Let message type be null.
388         // 6.5. Let session closed be false.
389         // 6.6. Let cdm be the CDM instance represented by this object's cdm instance value.
390         // 6.7. Use the cdm to execute the following steps:
391         m_instance->updateLicense(m_sessionId, m_sessionType, *sanitizedResponse, [this, weakThis = m_weakPtrFactory.createWeakPtr(*this), promise = WTFMove(promise)] (bool sessionWasClosed, std::optional<CDMInstance::KeyStatusVector>&& changedKeys, std::optional<double>&& changedExpiration, std::optional<CDMInstance::Message>&& message, CDMInstance::SuccessValue succeeded) mutable {
392             if (!weakThis)
393                 return;
394
395             // 6.7.1. If the format of sanitized response is invalid in any way, reject promise with a newly created TypeError.
396             // 6.7.2. Process sanitized response, following the stipulation for the first matching condition from the following list:
397             //   ↳ If sanitized response contains a license or key(s)
398             //     Process sanitized response, following the stipulation for the first matching condition from the following list:
399             //     ↳ 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
400             //       Process sanitized response, not storing any session data.
401             //     ↳ If sessionType is "persistent-license" and sanitized response contains a persistable license
402             //       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.
403             //     ↳ If sessionType is "persistent-usage-record" and sanitized response contains a non-persistable license
404             //       Run the following steps:
405             //         6.7.2.3.1. Process sanitized response, not storing any session data.
406             //         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.
407             //     ↳ Otherwise
408             //       Reject promise with a newly created TypeError.
409             //   ↳ If sanitized response contains a record of license destruction acknowledgement and sessionType is "persistent-license"
410             //     Run the following steps:
411             //       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.
412             //       6.7.2.2. Set session closed to true.
413             //   ↳ Otherwise
414             //     Process sanitized response, not storing any session data.
415             // NOTE: Steps 6.7.1. and 6.7.2. should be implemented in CDMInstance.
416
417             if (succeeded == CDMInstance::SuccessValue::Failed) {
418                 promise->reject(TypeError);
419                 return;
420             }
421
422             // 6.7.3. If a message needs to be sent to the server, execute the following steps:
423             //   6.7.3.1. Let message be that message.
424             //   6.7.3.2. Let message type be the appropriate MediaKeyMessageType for the message.
425             // 6.8. Queue a task to run the following steps:
426             m_taskQueue.enqueueTask([this, sessionWasClosed, changedKeys = WTFMove(changedKeys), changedExpiration = WTFMove(changedExpiration), message = WTFMove(message), promise = WTFMove(promise)] () mutable {
427                 // 6.8.1.
428                 if (sessionWasClosed) {
429                     // ↳ If session closed is true:
430                     //   Run the Session Closed algorithm on this object.
431                     sessionClosed();
432                 } else {
433                     // ↳ Otherwise:
434                     //   Run the following steps:
435                     //     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
436                     //              algorithm on the session, providing each known key's key ID along with the appropriate MediaKeyStatus. Should additional
437                     //              processing be necessary to determine with certainty the status of a key, use "status-pending". Once the additional processing
438                     //              for one or more keys has completed, run the Update Key Statuses algorithm again with the actual status(es).
439                     if (changedKeys)
440                         updateKeyStatuses(WTFMove(*changedKeys));
441
442                     //     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.
443                     if (changedExpiration)
444                         updateExpiration(*changedExpiration);
445
446                     //     6.8.1.3. If any of the preceding steps failed, reject promise with a new DOMException whose name is the appropriate error name.
447                     // FIXME: At this point the implementations of preceding steps can't fail.
448
449                     //     6.8.1.4. If message is not null, run the Queue a "message" Event algorithm on the session, providing message type and message.
450                     if (message) {
451                         MediaKeyMessageType messageType;
452                         switch (message->first) {
453                         case CDMInstance::MessageType::LicenseRequest:
454                             messageType = MediaKeyMessageType::LicenseRequest;
455                             break;
456                         case CDMInstance::MessageType::LicenseRenewal:
457                             messageType = MediaKeyMessageType::LicenseRenewal;
458                             break;
459                         case CDMInstance::MessageType::LicenseRelease:
460                             messageType = MediaKeyMessageType::LicenseRelease;
461                             break;
462                         case CDMInstance::MessageType::IndividualizationRequest:
463                             messageType = MediaKeyMessageType::IndividualizationRequest;
464                             break;
465                         }
466
467                         enqueueMessage(messageType, WTFMove(message->second));
468                     }
469                 }
470
471                 // 6.8.2. Resolve promise.
472                 promise->resolve();
473             });
474         });
475     });
476
477     // 7. Return promise.
478 }
479
480 void MediaKeySession::close(Ref<DeferredPromise>&& promise)
481 {
482     // https://w3c.github.io/encrypted-media/#dom-mediakeysession-close
483     // W3C Editor's Draft 09 November 2016
484
485     // 1. Let session be the associated MediaKeySession object.
486     // 2. If session is closed, return a resolved promise.
487     if (m_closed) {
488         promise->resolve();
489         return;
490     }
491
492     // 3. If session's callable value is false, return a promise rejected with an InvalidStateError.
493     if (!m_callable) {
494         promise->reject(InvalidStateError);
495         return;
496     }
497
498     // 4. Let promise be a new promise.
499     // 5. Run the following steps in parallel:
500     m_taskQueue.enqueueTask([this, promise = WTFMove(promise)] () mutable {
501         // 5.1. Let cdm be the CDM instance represented by session's cdm instance value.
502         // 5.2. Use cdm to close the key session associated with session.
503         m_instance->closeSession(m_sessionId, [this, weakThis = m_weakPtrFactory.createWeakPtr(*this), promise = WTFMove(promise)] () mutable {
504             if (!weakThis)
505                 return;
506
507             // 5.3. Queue a task to run the following steps:
508             m_taskQueue.enqueueTask([this, promise = WTFMove(promise)] () mutable {
509                 // 5.3.1. Run the Session Closed algorithm on the session.
510                 sessionClosed();
511
512                 // 5.3.2. Resolve promise.
513                 promise->resolve();
514             });
515         });
516     });
517
518     // 6. Return promise.
519 }
520
521 void MediaKeySession::remove(Ref<DeferredPromise>&& promise)
522 {
523     // https://w3c.github.io/encrypted-media/#dom-mediakeysession-remove
524     // W3C Editor's Draft 09 November 2016
525
526     // 1. If this object is closed, return a promise rejected with an InvalidStateError.
527     // 2. If this object's callable value is false, return a promise rejected with an InvalidStateError.
528     if (m_closed || !m_callable) {
529         promise->reject(InvalidStateError);
530         return;
531     }
532
533     // 3. Let promise be a new promise.
534     // 4. Run the following steps in parallel:
535     m_taskQueue.enqueueTask([this, promise = WTFMove(promise)] () mutable {
536         // 4.1. Let cdm be the CDM instance represented by this object's cdm instance value.
537         // 4.2. Let message be null.
538         // 4.3. Let message type be null.
539
540         // 4.4. Use the cdm to execute the following steps:
541         m_instance->removeSessionData(m_sessionId, m_sessionType, [this, weakThis = m_weakPtrFactory.createWeakPtr(*this), promise = WTFMove(promise)] (CDMInstance::KeyStatusVector&& keys, std::optional<Ref<SharedBuffer>>&& message, CDMInstance::SuccessValue succeeded) mutable {
542             if (!weakThis)
543                 return;
544
545             // 4.4.1. If any license(s) and/or key(s) are associated with the session:
546             //   4.4.1.1. Destroy the license(s) and/or key(s) associated with the session.
547             //   4.4.1.2. Follow the steps for the value of this object's session type from the following list:
548             //     ↳ "temporary"
549             //       4.4.1.2.1.1 Continue with the following steps.
550             //     ↳ "persistent-license"
551             //       4.4.1.2.2.1. Let record of license destruction be a record of license destruction for the license represented by this object.
552             //       4.4.1.2.2.2. Store the record of license destruction.
553             //       4.4.1.2.2.3. Let message be a message containing or reflecting the record of license destruction.
554             //     ↳ "persistent-usage-record"
555             //       4.4.1.2.3.1. Store this object's record of key usage.
556             //       4.4.1.2.3.2. Let message be a message containing or reflecting this object's record of key usage.
557             // NOTE: Step 4.4.1. should be implemented in CDMInstance.
558
559             // 4.5. Queue a task to run the following steps:
560             m_taskQueue.enqueueTask([this, keys = WTFMove(keys), message = WTFMove(message), succeeded, promise = WTFMove(promise)] () mutable {
561                 // 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.
562                 updateKeyStatuses(WTFMove(keys));
563
564                 // 4.5.2. Run the Update Expiration algorithm on the session, providing NaN.
565                 updateExpiration(std::numeric_limits<double>::quiet_NaN());
566
567                 // 4.5.3. If any of the preceding steps failed, reject promise with a new DOMException whose name is the appropriate error name.
568                 if (succeeded == CDMInstance::SuccessValue::Failed) {
569                     promise->reject(NotSupportedError);
570                     return;
571                 }
572
573                 // 4.5.4. Let message type be "license-release".
574                 // 4.5.5. If message is not null, run the Queue a "message" Event algorithm on the session, providing message type and message.
575                 if (message)
576                     enqueueMessage(MediaKeyMessageType::LicenseRelease, *message);
577
578                 // 4.5.6. Resolve promise.
579                 promise->resolve();
580             });
581         });
582     });
583
584     // 5. Return promise.
585 }
586
587 void MediaKeySession::enqueueMessage(MediaKeyMessageType messageType, const SharedBuffer& message)
588 {
589     // 6.4.1 Queue a "message" Event
590     // https://w3c.github.io/encrypted-media/#queue-message
591     // W3C Editor's Draft 09 November 2016
592
593     // The following steps are run:
594     // 1. Let the session be the specified MediaKeySession object.
595     // 2. Queue a task to create an event named message that does not bubble and is not cancellable using the MediaKeyMessageEvent
596     //    interface with its type attribute set to message and its isTrusted attribute initialized to true, and dispatch it at the
597     //    session.
598     auto messageEvent = MediaKeyMessageEvent::create(eventNames().messageEvent, {messageType, message.tryCreateArrayBuffer()}, Event::IsTrusted::Yes);
599     m_eventQueue.enqueueEvent(WTFMove(messageEvent));
600 }
601
602 void MediaKeySession::updateKeyStatuses(CDMInstanceClient::KeyStatusVector&& inputStatuses)
603 {
604     // https://w3c.github.io/encrypted-media/#update-key-statuses
605     // W3C Editor's Draft 09 November 2016
606
607     // 1. Let the session be the associated MediaKeySession object.
608     // 2. Let the input statuses be the sequence of pairs key ID and associated MediaKeyStatus pairs.
609     // 3. Let the statuses be session's keyStatuses attribute.
610     // 4. Run the following steps to replace the contents of statuses:
611     //   4.1. Empty statuses.
612     //   4.2. For each pair in input statuses.
613     //     4.2.1. Let pair be the pair.
614     //     4.2.2. Insert an entry for pair's key ID into statuses with the value of pair's MediaKeyStatus value.
615
616     static auto toMediaKeyStatus = [] (CDMInstance::KeyStatus status) -> MediaKeyStatus {
617         switch (status) {
618         case CDMInstance::KeyStatus::Usable:
619             return MediaKeyStatus::Usable;
620         case CDMInstance::KeyStatus::Expired:
621             return MediaKeyStatus::Expired;
622         case CDMInstance::KeyStatus::Released:
623             return MediaKeyStatus::Released;
624         case CDMInstance::KeyStatus::OutputRestricted:
625             return MediaKeyStatus::OutputRestricted;
626         case CDMInstance::KeyStatus::OutputDownscaled:
627             return MediaKeyStatus::OutputDownscaled;
628         case CDMInstance::KeyStatus::StatusPending:
629             return MediaKeyStatus::StatusPending;
630         case CDMInstance::KeyStatus::InternalError:
631             return MediaKeyStatus::InternalError;
632         };
633
634         ASSERT_NOT_REACHED();
635         return MediaKeyStatus::InternalError;
636     };
637
638     m_statuses.clear();
639     m_statuses.reserveCapacity(inputStatuses.size());
640     for (auto& status : inputStatuses)
641         m_statuses.uncheckedAppend({ WTFMove(status.first), toMediaKeyStatus(status.second) });
642
643     // 5. Queue a task to fire a simple event named keystatuseschange at the session.
644     m_eventQueue.enqueueEvent(Event::create(eventNames().keystatuseschangeEvent, false, false));
645
646     // 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.
647     m_taskQueue.enqueueTask(
648         [this] () mutable {
649             if (m_keys)
650                 m_keys->attemptToResumePlaybackOnClients();
651         });
652 }
653
654 void MediaKeySession::updateExpiration(double)
655 {
656     notImplemented();
657 }
658
659 void MediaKeySession::sessionClosed()
660 {
661     // https://w3c.github.io/encrypted-media/#session-closed
662     // W3C Editor's Draft 09 November 2016
663
664     // 1. Let session be the associated MediaKeySession object.
665     // 2. If session's session type is "persistent-usage-record", execute the following steps in parallel:
666     if (m_sessionType == MediaKeySessionType::PersistentUsageRecord) {
667         // 2.1. Let cdm be the CDM instance represented by session's cdm instance value.
668         // 2.2. Use cdm to store session's record of key usage, if it exists.
669         m_instance->storeRecordOfKeyUsage(m_sessionId);
670     }
671
672     // 3. Run the Update Key Statuses algorithm on the session, providing an empty sequence.
673     updateKeyStatuses({ });
674
675     // 4. Run the Update Expiration algorithm on the session, providing NaN.
676     updateExpiration(std::numeric_limits<double>::quiet_NaN());
677
678     // Let's consider the session closed before any promise on the 'closed' attribute is resolved.
679     m_closed = true;
680
681     // 5. Let promise be the closed attribute of the session.
682     // 6. Resolve promise.
683     m_closedPromise.resolve();
684 }
685
686 String MediaKeySession::mediaKeysStorageDirectory() const
687 {
688     auto* document = downcast<Document>(scriptExecutionContext());
689     if (!document)
690         return emptyString();
691
692     auto* page = document->page();
693     if (!page || page->usesEphemeralSession())
694         return emptyString();
695
696     auto storageDirectory = document->settings().mediaKeysStorageDirectory();
697     if (storageDirectory.isEmpty())
698         return emptyString();
699
700     return FileSystem::pathByAppendingComponent(storageDirectory, SecurityOriginData::fromSecurityOrigin(document->securityOrigin()).databaseIdentifier());
701 }
702
703 bool MediaKeySession::hasPendingActivity() const
704 {
705     notImplemented();
706     return false;
707 }
708
709 const char* MediaKeySession::activeDOMObjectName() const
710 {
711     notImplemented();
712     return "MediaKeySession";
713 }
714
715 bool MediaKeySession::canSuspendForDocumentSuspension() const
716 {
717     notImplemented();
718     return false;
719 }
720
721 void MediaKeySession::stop()
722 {
723     notImplemented();
724 }
725
726 } // namespace WebCore
727
728 #endif