Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / modules / encryptedmedia / MediaKeySession.cpp
1 /*
2  * Copyright (C) 2013 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "modules/encryptedmedia/MediaKeySession.h"
28
29 #include "bindings/core/v8/DOMWrapperWorld.h"
30 #include "bindings/core/v8/ScriptPromise.h"
31 #include "bindings/core/v8/ScriptPromiseResolver.h"
32 #include "bindings/core/v8/ScriptState.h"
33 #include "core/dom/DOMArrayBuffer.h"
34 #include "core/dom/DOMArrayBufferView.h"
35 #include "core/dom/ExceptionCode.h"
36 #include "core/events/Event.h"
37 #include "core/events/GenericEventQueue.h"
38 #include "core/html/MediaKeyError.h"
39 #include "modules/encryptedmedia/MediaKeyMessageEvent.h"
40 #include "modules/encryptedmedia/MediaKeys.h"
41 #include "modules/encryptedmedia/SimpleContentDecryptionModuleResult.h"
42 #include "platform/ContentDecryptionModuleResult.h"
43 #include "platform/ContentType.h"
44 #include "platform/Logging.h"
45 #include "platform/MIMETypeRegistry.h"
46 #include "platform/Timer.h"
47 #include "public/platform/WebContentDecryptionModule.h"
48 #include "public/platform/WebContentDecryptionModuleException.h"
49 #include "public/platform/WebContentDecryptionModuleSession.h"
50 #include "public/platform/WebString.h"
51 #include "public/platform/WebURL.h"
52 #include "wtf/ASCIICType.h"
53 #include <cmath>
54 #include <limits>
55
56 namespace {
57
58 // The list of possible values for |sessionType|.
59 const char* kTemporary = "temporary";
60 const char* kPersistent = "persistent";
61
62 // Minimum and maximum length for session ids.
63 enum {
64     MinSessionIdLength = 1,
65     MaxSessionIdLength = 512
66 };
67
68 } // namespace
69
70 namespace blink {
71
72 static bool isKeySystemSupportedWithInitDataType(const String& keySystem, const String& initDataType)
73 {
74     ASSERT(!keySystem.isEmpty());
75
76     // FIXME: initDataType != contentType. Implement this properly.
77     // http://crbug.com/385874.
78     String contentType = initDataType;
79     if (initDataType == "webm") {
80         contentType = "video/webm";
81     } else if (initDataType == "cenc") {
82         contentType = "video/mp4";
83     }
84
85     ContentType type(contentType);
86     return MIMETypeRegistry::isSupportedEncryptedMediaMIMEType(keySystem, type.type(), type.parameter("codecs"));
87 }
88
89 // Checks that |sessionId| looks correct and returns whether all checks pass.
90 static bool isValidSessionId(const String& sessionId)
91 {
92     if ((sessionId.length() < MinSessionIdLength) || (sessionId.length() > MaxSessionIdLength))
93         return false;
94
95     if (!sessionId.containsOnlyASCII())
96         return false;
97
98     // Check that the sessionId only contains alphanumeric characters.
99     for (unsigned i = 0; i < sessionId.length(); ++i) {
100         if (!isASCIIAlphanumeric(sessionId[i]))
101             return false;
102     }
103
104     return true;
105 }
106
107 // A class holding a pending action.
108 class MediaKeySession::PendingAction : public GarbageCollectedFinalized<MediaKeySession::PendingAction> {
109 public:
110     enum Type {
111         GenerateRequest,
112         Load,
113         Update,
114         Close,
115         Remove
116     };
117
118     Type type() const { return m_type; }
119
120     const Persistent<ContentDecryptionModuleResult> result() const
121     {
122         return m_result;
123     }
124
125     const PassRefPtr<DOMArrayBuffer> data() const
126     {
127         ASSERT(m_type == GenerateRequest || m_type == Update);
128         return m_data;
129     }
130
131     const String& initDataType() const
132     {
133         ASSERT(m_type == GenerateRequest);
134         return m_stringData;
135     }
136
137     const String& sessionId() const
138     {
139         ASSERT(m_type == Load);
140         return m_stringData;
141     }
142
143     static PendingAction* CreatePendingGenerateRequest(ContentDecryptionModuleResult* result, const String& initDataType, PassRefPtr<DOMArrayBuffer> initData)
144     {
145         ASSERT(result);
146         ASSERT(initData);
147         return new PendingAction(GenerateRequest, result, initDataType, initData);
148     }
149
150     static PendingAction* CreatePendingLoadRequest(ContentDecryptionModuleResult* result, const String& sessionId)
151     {
152         ASSERT(result);
153         return new PendingAction(Load, result, sessionId, PassRefPtr<DOMArrayBuffer>());
154     }
155
156     static PendingAction* CreatePendingUpdate(ContentDecryptionModuleResult* result, PassRefPtr<DOMArrayBuffer> data)
157     {
158         ASSERT(result);
159         ASSERT(data);
160         return new PendingAction(Update, result, String(), data);
161     }
162
163     static PendingAction* CreatePendingClose(ContentDecryptionModuleResult* result)
164     {
165         ASSERT(result);
166         return new PendingAction(Close, result, String(), PassRefPtr<DOMArrayBuffer>());
167     }
168
169     static PendingAction* CreatePendingRemove(ContentDecryptionModuleResult* result)
170     {
171         ASSERT(result);
172         return new PendingAction(Remove, result, String(), PassRefPtr<DOMArrayBuffer>());
173     }
174
175     ~PendingAction()
176     {
177     }
178
179     void trace(Visitor* visitor)
180     {
181         visitor->trace(m_result);
182     }
183
184 private:
185     PendingAction(Type type, ContentDecryptionModuleResult* result, const String& stringData, PassRefPtr<DOMArrayBuffer> data)
186         : m_type(type)
187         , m_result(result)
188         , m_stringData(stringData)
189         , m_data(data)
190     {
191     }
192
193     const Type m_type;
194     const Member<ContentDecryptionModuleResult> m_result;
195     const String m_stringData;
196     const RefPtr<DOMArrayBuffer> m_data;
197 };
198
199 // This class wraps the promise resolver used when initializing a new session
200 // and is passed to Chromium to fullfill the promise. This implementation of
201 // completeWithSession() will resolve the promise with void, while
202 // completeWithError() will reject the promise with an exception. complete()
203 // is not expected to be called, and will reject the promise.
204 class NewSessionResult : public ContentDecryptionModuleResult {
205 public:
206     NewSessionResult(ScriptState* scriptState, MediaKeySession* session)
207         : m_resolver(ScriptPromiseResolver::create(scriptState))
208         , m_session(session)
209     {
210         WTF_LOG(Media, "NewSessionResult(%p)", this);
211     }
212
213     virtual ~NewSessionResult()
214     {
215         WTF_LOG(Media, "~NewSessionResult(%p)", this);
216     }
217
218     // ContentDecryptionModuleResult implementation.
219     virtual void complete() override
220     {
221         ASSERT_NOT_REACHED();
222         completeWithDOMException(InvalidStateError, "Unexpected completion.");
223     }
224
225     virtual void completeWithSession(WebContentDecryptionModuleResult::SessionStatus status) override
226     {
227         if (status != WebContentDecryptionModuleResult::NewSession) {
228             ASSERT_NOT_REACHED();
229             completeWithDOMException(InvalidStateError, "Unexpected completion.");
230         }
231
232         m_session->finishGenerateRequest();
233         m_resolver->resolve();
234         m_resolver.clear();
235     }
236
237     virtual void completeWithError(WebContentDecryptionModuleException exceptionCode, unsigned long systemCode, const WebString& errorMessage) override
238     {
239         completeWithDOMException(WebCdmExceptionToExceptionCode(exceptionCode), errorMessage);
240     }
241
242     // It is only valid to call this before completion.
243     ScriptPromise promise() { return m_resolver->promise(); }
244
245     void trace(Visitor* visitor)
246     {
247         visitor->trace(m_session);
248         ContentDecryptionModuleResult::trace(visitor);
249     }
250
251 private:
252     // Reject the promise with a DOMException.
253     void completeWithDOMException(ExceptionCode code, const String& errorMessage)
254     {
255         m_resolver->reject(DOMException::create(code, errorMessage));
256         m_resolver.clear();
257     }
258
259     RefPtr<ScriptPromiseResolver> m_resolver;
260     Member<MediaKeySession> m_session;
261 };
262
263 // This class wraps the promise resolver used when loading a session
264 // and is passed to Chromium to fullfill the promise. This implementation of
265 // completeWithSession() will resolve the promise with true/false, while
266 // completeWithError() will reject the promise with an exception. complete()
267 // is not expected to be called, and will reject the promise.
268 class LoadSessionResult : public ContentDecryptionModuleResult {
269 public:
270     LoadSessionResult(ScriptState* scriptState, MediaKeySession* session)
271         : m_resolver(ScriptPromiseResolver::create(scriptState))
272         , m_session(session)
273     {
274         WTF_LOG(Media, "LoadSessionResult(%p)", this);
275     }
276
277     virtual ~LoadSessionResult()
278     {
279         WTF_LOG(Media, "~LoadSessionResult(%p)", this);
280     }
281
282     // ContentDecryptionModuleResult implementation.
283     virtual void complete() override
284     {
285         ASSERT_NOT_REACHED();
286         completeWithDOMException(InvalidStateError, "Unexpected completion.");
287     }
288
289     virtual void completeWithSession(WebContentDecryptionModuleResult::SessionStatus status) override
290     {
291         bool result = false;
292         switch (status) {
293         case WebContentDecryptionModuleResult::NewSession:
294             result = true;
295             break;
296
297         case WebContentDecryptionModuleResult::SessionNotFound:
298             result = false;
299             break;
300
301         case WebContentDecryptionModuleResult::SessionAlreadyExists:
302             ASSERT_NOT_REACHED();
303             completeWithDOMException(InvalidStateError, "Unexpected completion.");
304             return;
305         }
306
307         m_session->finishLoad();
308         m_resolver->resolve(result);
309         m_resolver.clear();
310     }
311
312     virtual void completeWithError(WebContentDecryptionModuleException exceptionCode, unsigned long systemCode, const WebString& errorMessage) override
313     {
314         completeWithDOMException(WebCdmExceptionToExceptionCode(exceptionCode), errorMessage);
315     }
316
317     // It is only valid to call this before completion.
318     ScriptPromise promise() { return m_resolver->promise(); }
319
320     void trace(Visitor* visitor)
321     {
322         visitor->trace(m_session);
323         ContentDecryptionModuleResult::trace(visitor);
324     }
325
326 private:
327     // Reject the promise with a DOMException.
328     void completeWithDOMException(ExceptionCode code, const String& errorMessage)
329     {
330         m_resolver->reject(DOMException::create(code, errorMessage));
331         m_resolver.clear();
332     }
333
334     RefPtr<ScriptPromiseResolver> m_resolver;
335     Member<MediaKeySession> m_session;
336 };
337
338 MediaKeySession* MediaKeySession::create(ScriptState* scriptState, MediaKeys* mediaKeys, const String& sessionType)
339 {
340     ASSERT(sessionType == kTemporary || sessionType == kPersistent);
341     RefPtrWillBeRawPtr<MediaKeySession> session = new MediaKeySession(scriptState, mediaKeys, sessionType);
342     session->suspendIfNeeded();
343     return session.get();
344 }
345
346 bool MediaKeySession::isValidSessionType(const String& sessionType)
347 {
348     return (sessionType == kTemporary || sessionType == kPersistent);
349 }
350
351 MediaKeySession::MediaKeySession(ScriptState* scriptState, MediaKeys* mediaKeys, const String& sessionType)
352     : ActiveDOMObject(scriptState->executionContext())
353     , m_keySystem(mediaKeys->keySystem())
354     , m_asyncEventQueue(GenericEventQueue::create(this))
355     , m_mediaKeys(mediaKeys)
356     , m_sessionType(sessionType)
357     , m_expiration(std::numeric_limits<double>::quiet_NaN())
358     , m_isUninitialized(true)
359     , m_isCallable(false)
360     , m_isClosed(false)
361     , m_closedPromise(new ClosedPromise(scriptState->executionContext(), this, ClosedPromise::Closed))
362     , m_actionTimer(this, &MediaKeySession::actionTimerFired)
363 {
364     WTF_LOG(Media, "MediaKeySession(%p)::MediaKeySession", this);
365
366     // Create the matching Chromium object. It will not be usable until
367     // initializeNewSession() is called in response to the user calling
368     // generateRequest().
369     WebContentDecryptionModule* cdm = mediaKeys->contentDecryptionModule();
370     m_session = adoptPtr(cdm->createSession());
371     m_session->setClientInterface(this);
372
373     // MediaKeys::createSession(), step 2.
374     // 2.1 Let the sessionId attribute be the empty string.
375     ASSERT(sessionId().isEmpty());
376
377     // 2.2 Let the expiration attribute be NaN.
378     ASSERT(std::isnan(m_expiration));
379
380     // 2.3 Let the closed attribute be a new promise.
381     ASSERT(!closed(scriptState).isUndefinedOrNull());
382
383     // 2.4 Let the session type be sessionType.
384     ASSERT(isValidSessionType(sessionType));
385
386     // 2.5 Let uninitialized be true.
387     ASSERT(m_isUninitialized);
388
389     // 2.6 Let callable be false.
390     ASSERT(!m_isCallable);
391 }
392
393 MediaKeySession::~MediaKeySession()
394 {
395     WTF_LOG(Media, "MediaKeySession(%p)::~MediaKeySession", this);
396     m_session.clear();
397 #if !ENABLE(OILPAN)
398     // MediaKeySession and m_asyncEventQueue always become unreachable
399     // together. So MediaKeySession and m_asyncEventQueue are destructed in the
400     // same GC. We don't need to call cancelAllEvents explicitly in Oilpan.
401     m_asyncEventQueue->cancelAllEvents();
402 #endif
403 }
404
405 void MediaKeySession::setError(MediaKeyError* error)
406 {
407     m_error = error;
408 }
409
410 String MediaKeySession::sessionId() const
411 {
412     return m_session->sessionId();
413 }
414
415 ScriptPromise MediaKeySession::closed(ScriptState* scriptState)
416 {
417     return m_closedPromise->promise(scriptState->world());
418 }
419
420 ScriptPromise MediaKeySession::generateRequest(ScriptState* scriptState, const String& initDataType, DOMArrayBuffer* initData)
421 {
422     RefPtr<DOMArrayBuffer> initDataCopy = DOMArrayBuffer::create(initData->data(), initData->byteLength());
423     return generateRequestInternal(scriptState, initDataType, initDataCopy.release());
424 }
425
426 ScriptPromise MediaKeySession::generateRequest(ScriptState* scriptState, const String& initDataType, DOMArrayBufferView* initData)
427 {
428     RefPtr<DOMArrayBuffer> initDataCopy = DOMArrayBuffer::create(initData->baseAddress(), initData->byteLength());
429     return generateRequestInternal(scriptState, initDataType, initDataCopy.release());
430 }
431
432 ScriptPromise MediaKeySession::generateRequestInternal(ScriptState* scriptState, const String& initDataType, PassRefPtr<DOMArrayBuffer> initData)
433 {
434     WTF_LOG(Media, "MediaKeySession(%p)::generateRequest %s", this, initDataType.ascii().data());
435
436     // From https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-generaterequest:
437     // The generateRequest(initDataType, initData) method creates a new session
438     // for the specified initData. It must run the following steps:
439
440     // 1. If this object's uninitialized value is false, return a promise
441     //    rejected with a new DOMException whose name is "InvalidStateError".
442     if (!m_isUninitialized) {
443         return ScriptPromise::rejectWithDOMException(
444             scriptState, DOMException::create(InvalidStateError, "The session is already initialized."));
445     }
446
447     // 2. Let this object's uninitialized be false.
448     m_isUninitialized = false;
449
450     // 3. If initDataType is an empty string, return a promise rejected with a
451     //    new DOMException whose name is "InvalidAccessError".
452     if (initDataType.isEmpty()) {
453         return ScriptPromise::rejectWithDOMException(
454             scriptState, DOMException::create(InvalidAccessError, "The initDataType parameter is empty."));
455     }
456
457     // 4. If initData is an empty array, return a promise rejected with a new
458     //    DOMException whose name is"InvalidAccessError".
459     if (!initData->byteLength()) {
460         return ScriptPromise::rejectWithDOMException(
461             scriptState, DOMException::create(InvalidAccessError, "The initData parameter is empty."));
462     }
463
464     // 5. Let media keys be the MediaKeys object that created this object.
465     //    (Use m_mediaKey, which was set in the constructor.)
466
467     // 6. If the content decryption module corresponding to media keys's
468     //    keySystem attribute does not support initDataType as an initialization
469     //    data type, return a promise rejected with a new DOMException whose
470     //    name is "NotSupportedError". String comparison is case-sensitive.
471     if (!isKeySystemSupportedWithInitDataType(m_keySystem, initDataType)) {
472         return ScriptPromise::rejectWithDOMException(
473             scriptState, DOMException::create(NotSupportedError, "The initialization data type '" + initDataType + "' is not supported by the key system."));
474     }
475
476     // 7. Let init data be a copy of the contents of the initData parameter.
477     //    (Done before calling this method.)
478
479     // 8. Let session type be this object's session type.
480     //    (Done in constructor.)
481
482     // 9. Let promise be a new promise.
483     NewSessionResult* result = new NewSessionResult(scriptState, this);
484     ScriptPromise promise = result->promise();
485
486     // 10. Run the following steps asynchronously (documented in
487     //     actionTimerFired())
488     m_pendingActions.append(PendingAction::CreatePendingGenerateRequest(result, initDataType, initData));
489     ASSERT(!m_actionTimer.isActive());
490     m_actionTimer.startOneShot(0, FROM_HERE);
491
492     // 11. Return promise.
493     return promise;
494 }
495
496 ScriptPromise MediaKeySession::load(ScriptState* scriptState, const String& sessionId)
497 {
498     WTF_LOG(Media, "MediaKeySession(%p)::load %s", this, sessionId.ascii().data());
499
500     // From https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-load:
501     // The load(sessionId) method loads the data stored for the sessionId into
502     // the session represented by the object. It must run the following steps:
503
504     // 1. If this object's uninitialized value is false, return a promise
505     //    rejected with a new DOMException whose name is "InvalidStateError".
506     if (!m_isUninitialized) {
507         return ScriptPromise::rejectWithDOMException(
508             scriptState, DOMException::create(InvalidStateError, "The session is already initialized."));
509     }
510
511     // 2. Let this object's uninitialized be false.
512     m_isUninitialized = false;
513
514     // 3. If sessionId is an empty string, return a promise rejected with a
515     //    new DOMException whose name is "InvalidAccessError".
516     if (sessionId.isEmpty()) {
517         return ScriptPromise::rejectWithDOMException(
518             scriptState, DOMException::create(InvalidAccessError, "The sessionId parameter is empty."));
519     }
520
521     // 4. If this object's session type is not "persistent", return a promise
522     //    rejected with a new DOMException whose name is "InvalidAccessError".
523     if (m_sessionType != kPersistent) {
524         return ScriptPromise::rejectWithDOMException(
525             scriptState, DOMException::create(InvalidAccessError, "The session type is not 'persistent'."));
526     }
527
528     // 5. Let media keys be the MediaKeys object that created this object.
529     //    (Done in constructor.)
530     ASSERT(m_mediaKeys);
531
532     // 6. If the content decryption module corresponding to media keys's
533     //    keySystem attribute does not support loading previous sessions,
534     //    return a promise rejected with a new DOMException whose name is
535     //    "NotSupportedError".
536     //    (Done by CDM.)
537
538     // 7. Let promise be a new promise.
539     LoadSessionResult* result = new LoadSessionResult(scriptState, this);
540     ScriptPromise promise = result->promise();
541
542     // 8. Run the following steps asynchronously (documented in
543     //    actionTimerFired())
544     m_pendingActions.append(PendingAction::CreatePendingLoadRequest(result, sessionId));
545     ASSERT(!m_actionTimer.isActive());
546     m_actionTimer.startOneShot(0, FROM_HERE);
547
548     // 9. Return promise.
549     return promise;
550 }
551
552 ScriptPromise MediaKeySession::update(ScriptState* scriptState, DOMArrayBuffer* response)
553 {
554     RefPtr<DOMArrayBuffer> responseCopy = DOMArrayBuffer::create(response->data(), response->byteLength());
555     return updateInternal(scriptState, responseCopy.release());
556 }
557
558 ScriptPromise MediaKeySession::update(ScriptState* scriptState, DOMArrayBufferView* response)
559 {
560     RefPtr<DOMArrayBuffer> responseCopy = DOMArrayBuffer::create(response->baseAddress(), response->byteLength());
561     return updateInternal(scriptState, responseCopy.release());
562 }
563
564 ScriptPromise MediaKeySession::updateInternal(ScriptState* scriptState, PassRefPtr<DOMArrayBuffer> response)
565 {
566     WTF_LOG(Media, "MediaKeySession(%p)::update", this);
567     ASSERT(!m_isClosed);
568
569     // From <https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-update>:
570     // The update(response) method provides messages, including licenses, to the
571     // CDM. It must run the following steps:
572     //
573     // 1. If response is an empty array, return a promise rejected with a new
574     //    DOMException whose name is "InvalidAccessError" and that has the
575     //    message "The response parameter is empty."
576     if (!response->byteLength()) {
577         return ScriptPromise::rejectWithDOMException(
578             scriptState, DOMException::create(InvalidAccessError, "The response parameter is empty."));
579     }
580
581     // 2. Let message be a copy of the contents of the response parameter.
582     //    (Copied in the caller.)
583
584     // 3. Let promise be a new promise.
585     SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState);
586     ScriptPromise promise = result->promise();
587
588     // 4. Run the following steps asynchronously (documented in
589     //    actionTimerFired())
590     m_pendingActions.append(PendingAction::CreatePendingUpdate(result, response));
591     if (!m_actionTimer.isActive())
592         m_actionTimer.startOneShot(0, FROM_HERE);
593
594     // 5. Return promise.
595     return promise;
596 }
597
598 ScriptPromise MediaKeySession::close(ScriptState* scriptState)
599 {
600     WTF_LOG(Media, "MediaKeySession(%p)::close", this);
601
602     // From https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-close:
603     // The close() method allows an application to indicate that it no longer
604     // needs the session and the CDM should release any resources associated
605     // with this object and close it. The returned promise is resolved when the
606     // request has been processed, and the closed attribute promise is resolved
607     // when the session is closed. It must run the following steps:
608     //
609     // 1. If this object's callable value is false, return a promise rejected
610     //    with a new DOMException whose name is "InvalidStateError".
611     if (!m_isCallable) {
612         return ScriptPromise::rejectWithDOMException(
613             scriptState, DOMException::create(InvalidStateError, "The session is not callable."));
614     }
615
616     // 2. If the Session Close algorithm has been run on this object,
617     //    return a resolved promise.
618     if (m_isClosed)
619         return ScriptPromise::cast(scriptState, ScriptValue());
620
621     // 3. Let promise be a new promise.
622     SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState);
623     ScriptPromise promise = result->promise();
624
625     // 4. Run the following steps asynchronously (documented in
626     //    actionTimerFired()).
627     m_pendingActions.append(PendingAction::CreatePendingClose(result));
628     if (!m_actionTimer.isActive())
629         m_actionTimer.startOneShot(0, FROM_HERE);
630
631     // 5. Return promise.
632     return promise;
633 }
634
635 ScriptPromise MediaKeySession::remove(ScriptState* scriptState)
636 {
637     WTF_LOG(Media, "MediaKeySession(%p)::remove", this);
638
639     // From https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-remove:
640     // The remove() method allows an application to remove stored session data
641     // associated with this object. It must run the following steps:
642
643     // 1. If this object's callable value is false, return a promise rejected
644     //    with a new DOMException whose name is "InvalidStateError".
645     if (!m_isCallable) {
646         return ScriptPromise::rejectWithDOMException(
647             scriptState, DOMException::create(InvalidStateError, "The session is not callable."));
648     }
649
650     // 2. If this object's session type is not "persistent", return a promise
651     //    rejected with a new DOMException whose name is "InvalidAccessError".
652     if (m_sessionType != kPersistent) {
653         return ScriptPromise::rejectWithDOMException(
654             scriptState, DOMException::create(InvalidAccessError, "The session type is not 'persistent'."));
655     }
656
657     // 3. If the Session Close algorithm has been run on this object, return a
658     //    promise rejected with a new DOMException whose name is
659     //    "InvalidStateError".
660     if (m_isClosed) {
661         return ScriptPromise::rejectWithDOMException(
662             scriptState, DOMException::create(InvalidStateError, "The session is already closed."));
663     }
664
665     // 4. Let promise be a new promise.
666     SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState);
667     ScriptPromise promise = result->promise();
668
669     // 5. Run the following steps asynchronously (documented in
670     //    actionTimerFired()).
671     m_pendingActions.append(PendingAction::CreatePendingRemove(result));
672     if (!m_actionTimer.isActive())
673         m_actionTimer.startOneShot(0, FROM_HERE);
674
675     // 6. Return promise.
676     return promise;
677 }
678
679 void MediaKeySession::actionTimerFired(Timer<MediaKeySession>*)
680 {
681     ASSERT(m_pendingActions.size());
682
683     // Resolving promises now run synchronously and may result in additional
684     // actions getting added to the queue. As a result, swap the queue to
685     // a local copy to avoid problems if this happens.
686     HeapDeque<Member<PendingAction> > pendingActions;
687     pendingActions.swap(m_pendingActions);
688
689     while (!pendingActions.isEmpty()) {
690         PendingAction* action = pendingActions.takeFirst();
691
692         switch (action->type()) {
693         case PendingAction::GenerateRequest:
694             WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: GenerateRequest", this);
695
696             // 10.1 Let request be null.
697             // 10.2 Let cdm be the CDM loaded during the initialization of
698             //      media keys.
699             // 10.3 Use the cdm to execute the following steps:
700             // 10.3.1 If the init data is not valid for initDataType, reject
701             //        promise with a new DOMException whose name is
702             //        "InvalidAccessError".
703             // 10.3.2 If the init data is not supported by the cdm, reject
704             //        promise with a new DOMException whose name is
705             //        "NotSupportedError".
706             // 10.3.3 Let request be a request (e.g. a license request)
707             //        generated based on the init data, which is interpreted
708             //        per initDataType, and session type.
709             m_session->initializeNewSession(action->initDataType(), static_cast<unsigned char*>(action->data()->data()), action->data()->byteLength(), m_sessionType, action->result()->result());
710
711             // Remainder of steps executed in finishGenerateRequest(), called
712             // when |result| is resolved.
713             break;
714
715         case PendingAction::Load:
716             // NOTE: Continue step 8 of MediaKeySession::load().
717
718             // 8.1 Let sanitized session ID be a validated and/or sanitized
719             //     version of sessionId. The user agent should thoroughly
720             //     validate the sessionId value before passing it to the CDM.
721             //     At a minimum, this should include checking that the length
722             //     and value (e.g. alphanumeric) are reasonable.
723             // 8.2 If the previous step failed, reject promise with a new
724             //     DOMException whose name is "InvalidAccessError".
725             if (!isValidSessionId(action->sessionId())) {
726                 action->result()->completeWithError(WebContentDecryptionModuleExceptionInvalidAccessError, 0, "Invalid sessionId");
727                 return;
728             }
729
730             // 8.3 Let expiration time be NaN.
731             //     (Done in the constructor.)
732             ASSERT(std::isnan(m_expiration));
733
734             // 8.4 Let message be null.
735             // 8.5 Let message type be null.
736             //     (Will be provided by the CDM if needed.)
737
738             // 8.6 Let origin be the origin of this object's Document.
739             //     (Obtained previously when CDM created.)
740
741             // 8.7 Let cdm be the CDM loaded during the initialization of media
742             //     keys.
743             // 8.8 Use the cdm to execute the following steps:
744             // 8.8.1 If there is no data stored for the sanitized session ID in
745             //       the origin, resolve promise with false.
746             // 8.8.2 Let session data be the data stored for the sanitized
747             //       session ID in the origin. This must not include data from
748             //       other origin(s) or that is not associated with an origin.
749             // 8.8.3 If there is an unclosed "persistent" session in any
750             //       Document representing the session data, reject promise
751             //       with a new DOMException whose name is "QuotaExceededError".
752             // 8.8.4 In other words, do not create a session if a non-closed
753             //       persistent session already exists for this sanitized
754             //       session ID in any browsing context.
755             // 8.8.5 Load the session data.
756             // 8.8.6 If the session data indicates an expiration time for the
757             //       session, let expiration time be the expiration time
758             //       in milliseconds since 01 January 1970 UTC.
759             // 8.8.6 If the CDM needs to send a message:
760             // 8.8.6.1 Let message be a message generated by the CDM based on
761             //         the session data.
762             // 8.8.6.2 Let message type be the appropriate MediaKeyMessageType
763             //         for the message.
764             // 8.9 If any of the preceding steps failed, reject promise with a
765             //     new DOMException whose name is the appropriate error name.
766             m_session->load(action->sessionId(), action->result()->result());
767
768             // Remainder of steps executed in finishLoad(), called
769             // when |result| is resolved.
770             break;
771
772         case PendingAction::Update:
773             WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: Update", this);
774             // NOTE: Continued from step 4 of MediaKeySession::update().
775             // Continue the update call by passing message to the cdm. Once
776             // completed, it will resolve/reject the promise.
777             m_session->update(static_cast<unsigned char*>(action->data()->data()), action->data()->byteLength(), action->result()->result());
778             break;
779
780         case PendingAction::Close:
781             WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: Close", this);
782             // NOTE: Continued from step 4 of MediaKeySession::close().
783             // 4.1 Let cdm be the CDM loaded during the initialization of the
784             //     MediaKeys object that created this object.
785             //     (Already captured when creating m_session).
786             // 4.2 Use the cdm to execute the following steps:
787             // 4.2.1 Process the close request. Do not remove stored session
788             //       data.
789             // 4.2.3 If the previous step caused the session to be closed,
790             //       run the Session Close algorithm on this object.
791             // 4.3 Resolve promise.
792             m_session->close(action->result()->result());
793             break;
794
795         case PendingAction::Remove:
796             WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: Remove", this);
797             // NOTE: Continued from step 5 of MediaKeySession::remove().
798             // 5.1 Let cdm be the CDM loaded during the initialization of the
799             //     MediaKeys object that created this object.
800             //     (Already captured when creating m_session).
801             // 5.2 Use the cdm to execute the following steps:
802             // 5.2.1 Process the remove request. This may involve exchanging
803             //       message(s) with the application. Unless this step fails,
804             //       the CDM must have cleared all stored session data
805             //       associated with this object, including the sessionId,
806             //       before proceeding to the next step. (A subsequent call
807             //       to load() with sessionId would fail because there is no
808             //       data stored for the sessionId.)
809             // 5.3 Run the following steps asynchronously once the above step
810             //     has completed:
811             // 5.3.1 If any of the preceding steps failed, reject promise
812             //       with a new DOMException whose name is the appropriate
813             //       error name.
814             // 5.3.2 Run the Session Close algorithm on this object.
815             // 5.3.3 Resolve promise.
816             m_session->remove(action->result()->result());
817             break;
818         }
819     }
820 }
821
822 void MediaKeySession::finishGenerateRequest()
823 {
824     // 10.4 Set the sessionId attribute to a unique Session ID string.
825     //      It may be obtained from cdm.
826     ASSERT(!sessionId().isEmpty());
827
828     // 10.5 If any of the preceding steps failed, reject promise with a new
829     //      DOMException whose name is the appropriate error name.
830     //      (Done by call to completeWithError()).
831
832     // 10.6 Add an entry for the value of the sessionId attribute to
833     //      media keys's list of active session IDs.
834     // FIXME: Is this required?
835     // https://www.w3.org/Bugs/Public/show_bug.cgi?id=26758
836
837     // 10.7 Run the Queue a "message" Event algorithm on the session,
838     //      providing request and null.
839     //      (Done by the CDM).
840
841     // 10.8 Let this object's callable be true.
842     m_isCallable = true;
843 }
844
845 void MediaKeySession::finishLoad()
846 {
847     // 8.10 Set the sessionId attribute to sanitized session ID.
848     ASSERT(!sessionId().isEmpty());
849
850     // 8.11 Let this object's callable be true.
851     m_isCallable = true;
852
853     // 8.12 If the loaded session contains usable keys, run the Usable
854     //      Keys Changed algorithm on the session. The algorithm may
855     //      also be run later should additional processing be necessary
856     //      to determine with certainty whether one or more keys is
857     //      usable.
858     //      (Done by the CDM.)
859
860     // 8.13 Run the Update Expiration algorithm on the session,
861     //      providing expiration time.
862     //      (Done by the CDM.)
863
864     // 8.14 If message is not null, run the Queue a "message" Event
865     //      algorithm on the session, providing message type and
866     //      message.
867     //      (Done by the CDM.)
868 }
869
870 // Queue a task to fire a simple event named keymessage at the new object.
871 void MediaKeySession::message(const unsigned char* message, size_t messageLength, const WebURL& destinationURL)
872 {
873     WTF_LOG(Media, "MediaKeySession(%p)::message", this);
874
875     // Verify that 'message' not fired before session initialization is complete.
876     ASSERT(m_isCallable);
877
878     MediaKeyMessageEventInit init;
879     init.bubbles = false;
880     init.cancelable = false;
881     init.message = DOMArrayBuffer::create(static_cast<const void*>(message), messageLength);
882     init.destinationURL = destinationURL.string();
883
884     RefPtrWillBeRawPtr<MediaKeyMessageEvent> event = MediaKeyMessageEvent::create(EventTypeNames::message, init);
885     event->setTarget(this);
886     m_asyncEventQueue->enqueueEvent(event.release());
887 }
888
889 void MediaKeySession::ready()
890 {
891     WTF_LOG(Media, "MediaKeySession(%p)::ready", this);
892
893     RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::ready);
894     event->setTarget(this);
895     m_asyncEventQueue->enqueueEvent(event.release());
896 }
897
898 void MediaKeySession::close()
899 {
900     WTF_LOG(Media, "MediaKeySession(%p)::close", this);
901
902     // Once closed, the session can no longer be the target of events from
903     // the CDM so this object can be garbage collected.
904     m_isClosed = true;
905
906     // Resolve the closed promise.
907     m_closedPromise->resolve(V8UndefinedType());
908 }
909
910 // Queue a task to fire a simple event named keyadded at the MediaKeySession object.
911 void MediaKeySession::error(MediaKeyErrorCode errorCode, unsigned long systemCode)
912 {
913     WTF_LOG(Media, "MediaKeySession(%p)::error: errorCode=%d, systemCode=%lu", this, errorCode, systemCode);
914
915     MediaKeyError::Code mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
916     switch (errorCode) {
917     case MediaKeyErrorCodeUnknown:
918         mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
919         break;
920     case MediaKeyErrorCodeClient:
921         mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_CLIENT;
922         break;
923     }
924
925     // 1. Create a new MediaKeyError object with the following attributes:
926     //    code = the appropriate MediaKeyError code
927     //    systemCode = a Key System-specific value, if provided, and 0 otherwise
928     // 2. Set the MediaKeySession object's error attribute to the error object created in the previous step.
929     m_error = MediaKeyError::create(mediaKeyErrorCode, systemCode);
930
931     // 3. queue a task to fire a simple event named keyerror at the MediaKeySession object.
932     RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::error);
933     event->setTarget(this);
934     m_asyncEventQueue->enqueueEvent(event.release());
935 }
936
937 void MediaKeySession::error(WebContentDecryptionModuleException exception, unsigned long systemCode, const WebString& errorMessage)
938 {
939     WTF_LOG(Media, "MediaKeySession::error: exception=%d, systemCode=%lu", exception, systemCode);
940
941     // FIXME: EME-WD MediaKeyError now derives from DOMException. Figure out how
942     // to implement this without breaking prefixed EME, which has a totally
943     // different definition. The spec may also change to be just a DOMException.
944     // For now, simply generate an existing MediaKeyError.
945     MediaKeyErrorCode errorCode;
946     switch (exception) {
947     case WebContentDecryptionModuleExceptionClientError:
948         errorCode = MediaKeyErrorCodeClient;
949         break;
950     default:
951         // All other exceptions get converted into Unknown.
952         errorCode = MediaKeyErrorCodeUnknown;
953         break;
954     }
955     error(errorCode, systemCode);
956 }
957
958 void MediaKeySession::expirationChanged(double updatedExpiryTimeInMS)
959 {
960     m_expiration = updatedExpiryTimeInMS;
961 }
962
963 const AtomicString& MediaKeySession::interfaceName() const
964 {
965     return EventTargetNames::MediaKeySession;
966 }
967
968 ExecutionContext* MediaKeySession::executionContext() const
969 {
970     return ActiveDOMObject::executionContext();
971 }
972
973 bool MediaKeySession::hasPendingActivity() const
974 {
975     // Remain around if there are pending events or MediaKeys is still around
976     // and we're not closed.
977     WTF_LOG(Media, "MediaKeySession(%p)::hasPendingActivity %s%s%s%s", this,
978         ActiveDOMObject::hasPendingActivity() ? " ActiveDOMObject::hasPendingActivity()" : "",
979         !m_pendingActions.isEmpty() ? " !m_pendingActions.isEmpty()" : "",
980         m_asyncEventQueue->hasPendingEvents() ? " m_asyncEventQueue->hasPendingEvents()" : "",
981         (m_mediaKeys && !m_isClosed) ? " m_mediaKeys && !m_isClosed" : "");
982
983     return ActiveDOMObject::hasPendingActivity()
984         || !m_pendingActions.isEmpty()
985         || m_asyncEventQueue->hasPendingEvents()
986         || (m_mediaKeys && !m_isClosed);
987 }
988
989 void MediaKeySession::stop()
990 {
991     // Stop the CDM from firing any more events for this session.
992     m_session.clear();
993     m_isClosed = true;
994
995     if (m_actionTimer.isActive())
996         m_actionTimer.stop();
997     m_pendingActions.clear();
998     m_asyncEventQueue->close();
999 }
1000
1001 void MediaKeySession::trace(Visitor* visitor)
1002 {
1003     visitor->trace(m_error);
1004     visitor->trace(m_asyncEventQueue);
1005     visitor->trace(m_pendingActions);
1006     visitor->trace(m_mediaKeys);
1007     visitor->trace(m_closedPromise);
1008     EventTargetWithInlineData::trace(visitor);
1009 }
1010
1011 } // namespace blink