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