Upstream version 10.39.225.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/ExceptionCode.h"
34 #include "core/events/Event.h"
35 #include "core/events/GenericEventQueue.h"
36 #include "core/html/MediaKeyError.h"
37 #include "modules/encryptedmedia/MediaKeyMessageEvent.h"
38 #include "modules/encryptedmedia/MediaKeys.h"
39 #include "modules/encryptedmedia/SimpleContentDecryptionModuleResult.h"
40 #include "platform/ContentDecryptionModuleResult.h"
41 #include "platform/ContentType.h"
42 #include "platform/Logging.h"
43 #include "platform/MIMETypeRegistry.h"
44 #include "platform/Timer.h"
45 #include "public/platform/WebContentDecryptionModule.h"
46 #include "public/platform/WebContentDecryptionModuleException.h"
47 #include "public/platform/WebContentDecryptionModuleSession.h"
48 #include "public/platform/WebString.h"
49 #include "public/platform/WebURL.h"
50 #include "wtf/ArrayBuffer.h"
51 #include "wtf/ArrayBufferView.h"
52
53 namespace blink {
54
55 static bool isKeySystemSupportedWithInitDataType(const String& keySystem, const String& initDataType)
56 {
57     ASSERT(!keySystem.isEmpty());
58
59     // FIXME: initDataType != contentType. Implement this properly.
60     // http://crbug.com/385874.
61     String contentType = initDataType;
62     if (initDataType == "webm") {
63         contentType = "video/webm";
64     } else if (initDataType == "cenc") {
65         contentType = "video/mp4";
66     }
67
68     ContentType type(contentType);
69     return MIMETypeRegistry::isSupportedEncryptedMediaMIMEType(keySystem, type.type(), type.parameter("codecs"));
70 }
71
72 // A class holding a pending action.
73 class MediaKeySession::PendingAction : public GarbageCollectedFinalized<MediaKeySession::PendingAction> {
74 public:
75     enum Type {
76         GenerateRequest,
77         Update,
78         Release
79     };
80
81     Type type() const { return m_type; }
82
83     const Persistent<ContentDecryptionModuleResult> result() const
84     {
85         return m_result;
86     }
87
88     const RefPtr<ArrayBuffer> data() const
89     {
90         ASSERT(m_type == GenerateRequest || m_type == Update);
91         return m_data;
92     }
93
94     const String& initDataType() const
95     {
96         ASSERT(m_type == GenerateRequest);
97         return m_initDataType;
98     }
99
100     static PendingAction* CreatePendingGenerateRequest(ContentDecryptionModuleResult* result, const String& initDataType, PassRefPtr<ArrayBuffer> initData)
101     {
102         ASSERT(result);
103         ASSERT(initData);
104         return new PendingAction(GenerateRequest, result, initDataType, initData);
105     }
106
107     static PendingAction* CreatePendingUpdate(ContentDecryptionModuleResult* result, PassRefPtr<ArrayBuffer> data)
108     {
109         ASSERT(result);
110         ASSERT(data);
111         return new PendingAction(Update, result, String(), data);
112     }
113
114     static PendingAction* CreatePendingRelease(ContentDecryptionModuleResult* result)
115     {
116         ASSERT(result);
117         return new PendingAction(Release, result, String(), PassRefPtr<ArrayBuffer>());
118     }
119
120     ~PendingAction()
121     {
122     }
123
124     void trace(Visitor* visitor)
125     {
126         visitor->trace(m_result);
127     }
128
129 private:
130     PendingAction(Type type, ContentDecryptionModuleResult* result, const String& initDataType, PassRefPtr<ArrayBuffer> data)
131         : m_type(type)
132         , m_result(result)
133         , m_initDataType(initDataType)
134         , m_data(data)
135     {
136     }
137
138     const Type m_type;
139     const Member<ContentDecryptionModuleResult> m_result;
140     const String m_initDataType;
141     const RefPtr<ArrayBuffer> m_data;
142 };
143
144 // This class wraps the promise resolver used when initializing a new session
145 // and is passed to Chromium to fullfill the promise. This implementation of
146 // completeWithSession() will resolve the promise with void, while
147 // completeWithError() will reject the promise with an exception. complete()
148 // is not expected to be called, and will reject the promise.
149 class NewSessionResult : public ContentDecryptionModuleResult {
150 public:
151     NewSessionResult(ScriptState* scriptState, MediaKeySession* session)
152         : m_resolver(ScriptPromiseResolver::create(scriptState))
153         , m_session(session)
154     {
155         WTF_LOG(Media, "NewSessionResult(%p)", this);
156     }
157
158     virtual ~NewSessionResult()
159     {
160         WTF_LOG(Media, "~NewSessionResult(%p)", this);
161     }
162
163     // ContentDecryptionModuleResult implementation.
164     virtual void complete() OVERRIDE
165     {
166         ASSERT_NOT_REACHED();
167         completeWithDOMException(InvalidStateError, "Unexpected completion.");
168     }
169
170     virtual void completeWithSession(WebContentDecryptionModuleResult::SessionStatus status) OVERRIDE
171     {
172         if (status != WebContentDecryptionModuleResult::NewSession) {
173             ASSERT_NOT_REACHED();
174             completeWithDOMException(InvalidStateError, "Unexpected completion.");
175         }
176
177         m_session->finishGenerateRequest();
178         m_resolver->resolve();
179         m_resolver.clear();
180     }
181
182     virtual void completeWithError(WebContentDecryptionModuleException exceptionCode, unsigned long systemCode, const WebString& errorMessage) OVERRIDE
183     {
184         completeWithDOMException(WebCdmExceptionToExceptionCode(exceptionCode), errorMessage);
185     }
186
187     // It is only valid to call this before completion.
188     ScriptPromise promise() { return m_resolver->promise(); }
189
190     void trace(Visitor* visitor)
191     {
192         visitor->trace(m_session);
193         ContentDecryptionModuleResult::trace(visitor);
194     }
195
196 private:
197     // Reject the promise with a DOMException.
198     void completeWithDOMException(ExceptionCode code, const String& errorMessage)
199     {
200         m_resolver->reject(DOMException::create(code, errorMessage));
201         m_resolver.clear();
202     }
203
204     RefPtr<ScriptPromiseResolver> m_resolver;
205     Member<MediaKeySession> m_session;
206 };
207
208 MediaKeySession* MediaKeySession::create(ScriptState* scriptState, MediaKeys* mediaKeys, const String& sessionType)
209 {
210     RefPtrWillBeRawPtr<MediaKeySession> session = adoptRefCountedGarbageCollectedWillBeNoop(new MediaKeySession(scriptState, mediaKeys, sessionType));
211     session->suspendIfNeeded();
212     return session.get();
213 }
214
215 MediaKeySession::MediaKeySession(ScriptState* scriptState, MediaKeys* mediaKeys, const String& sessionType)
216     : ActiveDOMObject(scriptState->executionContext())
217     , m_keySystem(mediaKeys->keySystem())
218     , m_asyncEventQueue(GenericEventQueue::create(this))
219     , m_mediaKeys(mediaKeys)
220     , m_sessionType(sessionType)
221     , m_isUninitialized(true)
222     , m_isCallable(false)
223     , m_isClosed(false)
224     , m_closedPromise(new ClosedPromise(scriptState->executionContext(), this, ClosedPromise::Closed))
225     , m_actionTimer(this, &MediaKeySession::actionTimerFired)
226 {
227     WTF_LOG(Media, "MediaKeySession(%p)::MediaKeySession", this);
228
229     // Create the matching Chromium object. It will not be usable until
230     // initializeNewSession() is called in response to the user calling
231     // generateRequest().
232     WebContentDecryptionModule* cdm = mediaKeys->contentDecryptionModule();
233     m_session = adoptPtr(cdm->createSession());
234     m_session->setClientInterface(this);
235
236     // MediaKeys::createSession(), step 2.
237     // 2.1 Let the sessionId attribute be the empty string.
238     ASSERT(sessionId().isEmpty());
239
240     // 2.2 Let the expiration attribute be NaN.
241     // FIXME: Add expiration property.
242
243     // 2.3 Let the closed attribute be a new promise.
244     ASSERT(!closed(scriptState).isUndefinedOrNull());
245
246     // 2.4 Let the session type be sessionType.
247     ASSERT(sessionType == m_sessionType);
248
249     // 2.5 Let uninitialized be true.
250     ASSERT(m_isUninitialized);
251
252     // 2.6 Let callable be false.
253     ASSERT(!m_isCallable);
254 }
255
256 MediaKeySession::~MediaKeySession()
257 {
258     WTF_LOG(Media, "MediaKeySession(%p)::~MediaKeySession", this);
259     m_session.clear();
260 #if !ENABLE(OILPAN)
261     // MediaKeySession and m_asyncEventQueue always become unreachable
262     // together. So MediaKeySession and m_asyncEventQueue are destructed in the
263     // same GC. We don't need to call cancelAllEvents explicitly in Oilpan.
264     m_asyncEventQueue->cancelAllEvents();
265 #endif
266 }
267
268 void MediaKeySession::setError(MediaKeyError* error)
269 {
270     m_error = error;
271 }
272
273 String MediaKeySession::sessionId() const
274 {
275     return m_session->sessionId();
276 }
277
278 ScriptPromise MediaKeySession::closed(ScriptState* scriptState)
279 {
280     return m_closedPromise->promise(scriptState->world());
281 }
282
283 ScriptPromise MediaKeySession::generateRequest(ScriptState* scriptState, const String& initDataType, ArrayBuffer* initData)
284 {
285     RefPtr<ArrayBuffer> initDataCopy = ArrayBuffer::create(initData->data(), initData->byteLength());
286     return generateRequestInternal(scriptState, initDataType, initDataCopy.release());
287 }
288
289 ScriptPromise MediaKeySession::generateRequest(ScriptState* scriptState, const String& initDataType, ArrayBufferView* initData)
290 {
291     RefPtr<ArrayBuffer> initDataCopy = ArrayBuffer::create(initData->baseAddress(), initData->byteLength());
292     return generateRequestInternal(scriptState, initDataType, initDataCopy.release());
293 }
294
295 ScriptPromise MediaKeySession::generateRequestInternal(ScriptState* scriptState, const String& initDataType, PassRefPtr<ArrayBuffer> initData)
296 {
297     WTF_LOG(Media, "MediaKeySession(%p)::generateRequest %s", this, initDataType.ascii().data());
298
299     // From https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-generaterequest:
300     // The generateRequest(initDataType, initData) method creates a new session
301     // for the specified initData. It must run the following steps:
302
303     // 1. If this object's uninitialized value is false, return a promise
304     //    rejected with a new DOMException whose name is "InvalidStateError".
305     if (!m_isUninitialized) {
306         return ScriptPromise::rejectWithDOMException(
307             scriptState, DOMException::create(InvalidStateError, "The session is already initialized."));
308     }
309
310     // 2. Let this object's uninitialized be false.
311     m_isUninitialized = false;
312
313     // 3. If initDataType is an empty string, return a promise rejected with a
314     //    new DOMException whose name is "InvalidAccessError".
315     if (initDataType.isEmpty()) {
316         return ScriptPromise::rejectWithDOMException(
317             scriptState, DOMException::create(InvalidAccessError, "The initDataType parameter is empty."));
318     }
319
320     // 4. If initData is an empty array, return a promise rejected with a new
321     //    DOMException whose name is"InvalidAccessError".
322     if (!initData->byteLength()) {
323         return ScriptPromise::rejectWithDOMException(
324             scriptState, DOMException::create(InvalidAccessError, "The initData parameter is empty."));
325     }
326
327     // 5. Let media keys be the MediaKeys object that created this object.
328     //    (Use m_mediaKey, which was set in the constructor.)
329
330     // 6. If the content decryption module corresponding to media keys's
331     //    keySystem attribute does not support initDataType as an initialization
332     //    data type, return a promise rejected with a new DOMException whose
333     //    name is "NotSupportedError". String comparison is case-sensitive.
334     if (!isKeySystemSupportedWithInitDataType(m_keySystem, initDataType)) {
335         return ScriptPromise::rejectWithDOMException(
336             scriptState, DOMException::create(NotSupportedError, "The initialization data type '" + initDataType + "' is not supported by the key system."));
337     }
338
339     // 7. Let init data be a copy of the contents of the initData parameter.
340     //    (Done before calling this method.)
341
342     // 8. Let session type be this object's session type.
343     //    (Done in constructor.)
344
345     // 9. Let promise be a new promise.
346     NewSessionResult* result = new NewSessionResult(scriptState, this);
347     ScriptPromise promise = result->promise();
348
349     // 10. Run the following steps asynchronously (documented in
350     //     actionTimerFired())
351     m_pendingActions.append(PendingAction::CreatePendingGenerateRequest(result, initDataType, initData));
352     ASSERT(!m_actionTimer.isActive());
353     m_actionTimer.startOneShot(0, FROM_HERE);
354
355     // 11. Return promise.
356     return promise;
357 }
358
359 ScriptPromise MediaKeySession::update(ScriptState* scriptState, ArrayBuffer* response)
360 {
361     RefPtr<ArrayBuffer> responseCopy = ArrayBuffer::create(response->data(), response->byteLength());
362     return updateInternal(scriptState, responseCopy.release());
363 }
364
365 ScriptPromise MediaKeySession::update(ScriptState* scriptState, ArrayBufferView* response)
366 {
367     RefPtr<ArrayBuffer> responseCopy = ArrayBuffer::create(response->baseAddress(), response->byteLength());
368     return updateInternal(scriptState, responseCopy.release());
369 }
370
371 ScriptPromise MediaKeySession::updateInternal(ScriptState* scriptState, PassRefPtr<ArrayBuffer> response)
372 {
373     WTF_LOG(Media, "MediaKeySession(%p)::update", this);
374     ASSERT(!m_isClosed);
375
376     // From <https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-update>:
377     // The update(response) method provides messages, including licenses, to the
378     // CDM. It must run the following steps:
379     //
380     // 1. If response is an empty array, return a promise rejected with a new
381     //    DOMException whose name is "InvalidAccessError" and that has the
382     //    message "The response parameter is empty."
383     if (!response->byteLength()) {
384         return ScriptPromise::rejectWithDOMException(
385             scriptState, DOMException::create(InvalidAccessError, "The response parameter is empty."));
386     }
387
388     // 2. Let message be a copy of the contents of the response parameter.
389     //    (Copied in the caller.)
390
391     // 3. Let promise be a new promise.
392     SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState);
393     ScriptPromise promise = result->promise();
394
395     // 4. Run the following steps asynchronously (documented in
396     //    actionTimerFired())
397     m_pendingActions.append(PendingAction::CreatePendingUpdate(result, response));
398     if (!m_actionTimer.isActive())
399         m_actionTimer.startOneShot(0, FROM_HERE);
400
401     // 5. Return promise.
402     return promise;
403 }
404
405 ScriptPromise MediaKeySession::release(ScriptState* scriptState)
406 {
407     WTF_LOG(Media, "MediaKeySession(%p)::release", this);
408     SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState);
409     ScriptPromise promise = result->promise();
410
411     // From <https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-close>:
412     // The close() method allows an application to indicate that it no longer
413     // needs the session and the CDM should release any resources associated
414     // with this object and close it. The returned promise is resolved when the
415     // request has been processed, and the closed attribute promise is resolved
416     // when the session is closed. It must run the following steps:
417     //
418     // 1. If the Session Close algorithm has been run on this object, return a
419     //    promise fulfilled with undefined.
420     if (m_isClosed) {
421         result->complete();
422         return promise;
423     }
424
425     // 2. Let promise be a new promise.
426     // (Created earlier so it was available in step 1.)
427
428     // 3. Run the following steps asynchronously (documented in
429     //    actionTimerFired()).
430     m_pendingActions.append(PendingAction::CreatePendingRelease(result));
431     if (!m_actionTimer.isActive())
432         m_actionTimer.startOneShot(0, FROM_HERE);
433
434     // 4. Return promise.
435     return promise;
436 }
437
438 void MediaKeySession::actionTimerFired(Timer<MediaKeySession>*)
439 {
440     ASSERT(m_pendingActions.size());
441
442     // Resolving promises now run synchronously and may result in additional
443     // actions getting added to the queue. As a result, swap the queue to
444     // a local copy to avoid problems if this happens.
445     HeapDeque<Member<PendingAction> > pendingActions;
446     pendingActions.swap(m_pendingActions);
447
448     while (!pendingActions.isEmpty()) {
449         PendingAction* action = pendingActions.takeFirst();
450
451         switch (action->type()) {
452         case PendingAction::GenerateRequest:
453             WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: GenerateRequest", this);
454
455             // 10.1 Let request be null.
456             // 10.2 Let cdm be the CDM loaded during the initialization of
457             //      media keys.
458             // 10.3 Use the cdm to execute the following steps:
459             // 10.3.1 If the init data is not valid for initDataType, reject
460             //        promise with a new DOMException whose name is
461             //        "InvalidAccessError".
462             // 10.3.2 If the init data is not supported by the cdm, reject
463             //        promise with a new DOMException whose name is
464             //        "NotSupportedError".
465             // 10.3.3 Let request be a request (e.g. a license request)
466             //        generated based on the init data, which is interpreted
467             //        per initDataType, and session type.
468             m_session->initializeNewSession(action->initDataType(), static_cast<unsigned char*>(action->data()->data()), action->data()->byteLength(), m_sessionType, action->result()->result());
469
470             // Remainder of steps executed in finishGenerateRequest(), called
471             // when |result| is resolved.
472             break;
473
474         case PendingAction::Update:
475             WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: Update", this);
476             // NOTE: Continued from step 4 of MediaKeySession::update().
477             // Continue the update call by passing message to the cdm. Once
478             // completed, it will resolve/reject the promise.
479             m_session->update(static_cast<unsigned char*>(action->data()->data()), action->data()->byteLength(), action->result()->result());
480             break;
481
482         case PendingAction::Release:
483             WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: Release", this);
484             // NOTE: Continued from step 3 of MediaKeySession::release().
485             // 3.1 Let cdm be the cdm loaded in create().
486             // 3.2 Use the cdm to execute the following steps:
487             // 3.2.1 Process the close request. Do not remove stored session data.
488             // 3.2.2 If the previous step caused the session to be closed, run the
489             //       Session Close algorithm on this object.
490             // 3.3 Resolve promise with undefined.
491             m_session->release(action->result()->result());
492             break;
493         }
494     }
495 }
496
497 void MediaKeySession::finishGenerateRequest()
498 {
499     // 10.4 Set the sessionId attribute to a unique Session ID string.
500     //      It may be obtained from cdm.
501     ASSERT(!sessionId().isEmpty());
502
503     // 10.5 If any of the preceding steps failed, reject promise with a new
504     //      DOMException whose name is the appropriate error name.
505     //      (Done by call to completeWithError()).
506
507     // 10.6 Add an entry for the value of the sessionId attribute to
508     //      media keys's list of active session IDs.
509     // FIXME: Is this required?
510     // https://www.w3.org/Bugs/Public/show_bug.cgi?id=26758
511
512     // 10.7 Run the Queue a "message" Event algorithm on the session,
513     //      providing request and null.
514     //      (Done by the CDM).
515
516     // 10.8 Let this object's callable be true.
517     m_isCallable = true;
518 }
519
520 // Queue a task to fire a simple event named keymessage at the new object.
521 void MediaKeySession::message(const unsigned char* message, size_t messageLength, const WebURL& destinationURL)
522 {
523     WTF_LOG(Media, "MediaKeySession(%p)::message", this);
524
525     // Verify that 'message' not fired before session initialization is complete.
526     ASSERT(m_isCallable);
527
528     MediaKeyMessageEventInit init;
529     init.bubbles = false;
530     init.cancelable = false;
531     init.message = ArrayBuffer::create(static_cast<const void*>(message), messageLength);
532     init.destinationURL = destinationURL.string();
533
534     RefPtrWillBeRawPtr<MediaKeyMessageEvent> event = MediaKeyMessageEvent::create(EventTypeNames::message, init);
535     event->setTarget(this);
536     m_asyncEventQueue->enqueueEvent(event.release());
537 }
538
539 void MediaKeySession::ready()
540 {
541     WTF_LOG(Media, "MediaKeySession(%p)::ready", this);
542
543     RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::ready);
544     event->setTarget(this);
545     m_asyncEventQueue->enqueueEvent(event.release());
546 }
547
548 void MediaKeySession::close()
549 {
550     WTF_LOG(Media, "MediaKeySession(%p)::close", this);
551
552     // Once closed, the session can no longer be the target of events from
553     // the CDM so this object can be garbage collected.
554     m_isClosed = true;
555
556     // Resolve the closed promise.
557     m_closedPromise->resolve(V8UndefinedType());
558 }
559
560 // Queue a task to fire a simple event named keyadded at the MediaKeySession object.
561 void MediaKeySession::error(MediaKeyErrorCode errorCode, unsigned long systemCode)
562 {
563     WTF_LOG(Media, "MediaKeySession(%p)::error: errorCode=%d, systemCode=%lu", this, errorCode, systemCode);
564
565     MediaKeyError::Code mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
566     switch (errorCode) {
567     case MediaKeyErrorCodeUnknown:
568         mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
569         break;
570     case MediaKeyErrorCodeClient:
571         mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_CLIENT;
572         break;
573     }
574
575     // 1. Create a new MediaKeyError object with the following attributes:
576     //    code = the appropriate MediaKeyError code
577     //    systemCode = a Key System-specific value, if provided, and 0 otherwise
578     // 2. Set the MediaKeySession object's error attribute to the error object created in the previous step.
579     m_error = MediaKeyError::create(mediaKeyErrorCode, systemCode);
580
581     // 3. queue a task to fire a simple event named keyerror at the MediaKeySession object.
582     RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::error);
583     event->setTarget(this);
584     m_asyncEventQueue->enqueueEvent(event.release());
585 }
586
587 void MediaKeySession::error(WebContentDecryptionModuleException exception, unsigned long systemCode, const WebString& errorMessage)
588 {
589     WTF_LOG(Media, "MediaKeySession::error: exception=%d, systemCode=%lu", exception, systemCode);
590
591     // FIXME: EME-WD MediaKeyError now derives from DOMException. Figure out how
592     // to implement this without breaking prefixed EME, which has a totally
593     // different definition. The spec may also change to be just a DOMException.
594     // For now, simply generate an existing MediaKeyError.
595     MediaKeyErrorCode errorCode;
596     switch (exception) {
597     case WebContentDecryptionModuleExceptionClientError:
598         errorCode = MediaKeyErrorCodeClient;
599         break;
600     default:
601         // All other exceptions get converted into Unknown.
602         errorCode = MediaKeyErrorCodeUnknown;
603         break;
604     }
605     error(errorCode, systemCode);
606 }
607
608 const AtomicString& MediaKeySession::interfaceName() const
609 {
610     return EventTargetNames::MediaKeySession;
611 }
612
613 ExecutionContext* MediaKeySession::executionContext() const
614 {
615     return ActiveDOMObject::executionContext();
616 }
617
618 bool MediaKeySession::hasPendingActivity() const
619 {
620     // Remain around if there are pending events or MediaKeys is still around
621     // and we're not closed.
622     WTF_LOG(Media, "MediaKeySession(%p)::hasPendingActivity %s%s%s%s", this,
623         ActiveDOMObject::hasPendingActivity() ? " ActiveDOMObject::hasPendingActivity()" : "",
624         !m_pendingActions.isEmpty() ? " !m_pendingActions.isEmpty()" : "",
625         m_asyncEventQueue->hasPendingEvents() ? " m_asyncEventQueue->hasPendingEvents()" : "",
626         (m_mediaKeys && !m_isClosed) ? " m_mediaKeys && !m_isClosed" : "");
627
628     return ActiveDOMObject::hasPendingActivity()
629         || !m_pendingActions.isEmpty()
630         || m_asyncEventQueue->hasPendingEvents()
631         || (m_mediaKeys && !m_isClosed);
632 }
633
634 void MediaKeySession::stop()
635 {
636     // Stop the CDM from firing any more events for this session.
637     m_session.clear();
638     m_isClosed = true;
639
640     if (m_actionTimer.isActive())
641         m_actionTimer.stop();
642     m_pendingActions.clear();
643     m_asyncEventQueue->close();
644 }
645
646 void MediaKeySession::trace(Visitor* visitor)
647 {
648     visitor->trace(m_error);
649     visitor->trace(m_asyncEventQueue);
650     visitor->trace(m_pendingActions);
651     visitor->trace(m_mediaKeys);
652     visitor->trace(m_closedPromise);
653     EventTargetWithInlineData::trace(visitor);
654 }
655
656 } // namespace blink