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
53namespace blink {
54
55static 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.
73class MediaKeySession::PendingAction : public GarbageCollectedFinalized<MediaKeySession::PendingAction> {
74public:
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
129private:
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.
149class NewSessionResult : public ContentDecryptionModuleResult {
150public:
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
196private:
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
208MediaKeySession* 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
215MediaKeySession::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
256MediaKeySession::~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
268void MediaKeySession::setError(MediaKeyError* error)
269{
270    m_error = error;
271}
272
273String MediaKeySession::sessionId() const
274{
275    return m_session->sessionId();
276}
277
278ScriptPromise MediaKeySession::closed(ScriptState* scriptState)
279{
280    return m_closedPromise->promise(scriptState->world());
281}
282
283ScriptPromise 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
289ScriptPromise 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
295ScriptPromise 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
359ScriptPromise 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
365ScriptPromise 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
371ScriptPromise 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
405ScriptPromise 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
438void 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
497void 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.
521void 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
539void 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
548void 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.
561void 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
587void 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
608const AtomicString& MediaKeySession::interfaceName() const
609{
610    return EventTargetNames::MediaKeySession;
611}
612
613ExecutionContext* MediaKeySession::executionContext() const
614{
615    return ActiveDOMObject::executionContext();
616}
617
618bool 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
634void 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
646void 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
657