2 * Copyright (C) 2013 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "modules/encryptedmedia/MediaKeySession.h"
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"
58 // The list of possible values for |sessionType|.
59 const char* kTemporary = "temporary";
60 const char* kPersistent = "persistent";
62 // Minimum and maximum length for session ids.
64 MinSessionIdLength = 1,
65 MaxSessionIdLength = 512
72 static bool isKeySystemSupportedWithInitDataType(const String& keySystem, const String& initDataType)
74 ASSERT(!keySystem.isEmpty());
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";
85 ContentType type(contentType);
86 return MIMETypeRegistry::isSupportedEncryptedMediaMIMEType(keySystem, type.type(), type.parameter("codecs"));
89 // Checks that |sessionId| looks correct and returns whether all checks pass.
90 static bool isValidSessionId(const String& sessionId)
92 if ((sessionId.length() < MinSessionIdLength) || (sessionId.length() > MaxSessionIdLength))
95 if (!sessionId.containsOnlyASCII())
98 // Check that the sessionId only contains alphanumeric characters.
99 for (unsigned i = 0; i < sessionId.length(); ++i) {
100 if (!isASCIIAlphanumeric(sessionId[i]))
107 // A class holding a pending action.
108 class MediaKeySession::PendingAction : public GarbageCollectedFinalized<MediaKeySession::PendingAction> {
118 Type type() const { return m_type; }
120 const Persistent<ContentDecryptionModuleResult> result() const
125 const PassRefPtr<DOMArrayBuffer> data() const
127 ASSERT(m_type == GenerateRequest || m_type == Update);
131 const String& initDataType() const
133 ASSERT(m_type == GenerateRequest);
137 const String& sessionId() const
139 ASSERT(m_type == Load);
143 static PendingAction* CreatePendingGenerateRequest(ContentDecryptionModuleResult* result, const String& initDataType, PassRefPtr<DOMArrayBuffer> initData)
147 return new PendingAction(GenerateRequest, result, initDataType, initData);
150 static PendingAction* CreatePendingLoadRequest(ContentDecryptionModuleResult* result, const String& sessionId)
153 return new PendingAction(Load, result, sessionId, PassRefPtr<DOMArrayBuffer>());
156 static PendingAction* CreatePendingUpdate(ContentDecryptionModuleResult* result, PassRefPtr<DOMArrayBuffer> data)
160 return new PendingAction(Update, result, String(), data);
163 static PendingAction* CreatePendingClose(ContentDecryptionModuleResult* result)
166 return new PendingAction(Close, result, String(), PassRefPtr<DOMArrayBuffer>());
169 static PendingAction* CreatePendingRemove(ContentDecryptionModuleResult* result)
172 return new PendingAction(Remove, result, String(), PassRefPtr<DOMArrayBuffer>());
179 void trace(Visitor* visitor)
181 visitor->trace(m_result);
185 PendingAction(Type type, ContentDecryptionModuleResult* result, const String& stringData, PassRefPtr<DOMArrayBuffer> data)
188 , m_stringData(stringData)
194 const Member<ContentDecryptionModuleResult> m_result;
195 const String m_stringData;
196 const RefPtr<DOMArrayBuffer> m_data;
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 {
206 NewSessionResult(ScriptState* scriptState, MediaKeySession* session)
207 : m_resolver(ScriptPromiseResolver::create(scriptState))
210 WTF_LOG(Media, "NewSessionResult(%p)", this);
213 virtual ~NewSessionResult()
215 WTF_LOG(Media, "~NewSessionResult(%p)", this);
218 // ContentDecryptionModuleResult implementation.
219 virtual void complete() override
221 ASSERT_NOT_REACHED();
222 completeWithDOMException(InvalidStateError, "Unexpected completion.");
225 virtual void completeWithSession(WebContentDecryptionModuleResult::SessionStatus status) override
227 if (status != WebContentDecryptionModuleResult::NewSession) {
228 ASSERT_NOT_REACHED();
229 completeWithDOMException(InvalidStateError, "Unexpected completion.");
232 m_session->finishGenerateRequest();
233 m_resolver->resolve();
237 virtual void completeWithError(WebContentDecryptionModuleException exceptionCode, unsigned long systemCode, const WebString& errorMessage) override
239 completeWithDOMException(WebCdmExceptionToExceptionCode(exceptionCode), errorMessage);
242 // It is only valid to call this before completion.
243 ScriptPromise promise() { return m_resolver->promise(); }
245 void trace(Visitor* visitor)
247 visitor->trace(m_session);
248 ContentDecryptionModuleResult::trace(visitor);
252 // Reject the promise with a DOMException.
253 void completeWithDOMException(ExceptionCode code, const String& errorMessage)
255 m_resolver->reject(DOMException::create(code, errorMessage));
259 RefPtr<ScriptPromiseResolver> m_resolver;
260 Member<MediaKeySession> m_session;
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 {
270 LoadSessionResult(ScriptState* scriptState, MediaKeySession* session)
271 : m_resolver(ScriptPromiseResolver::create(scriptState))
274 WTF_LOG(Media, "LoadSessionResult(%p)", this);
277 virtual ~LoadSessionResult()
279 WTF_LOG(Media, "~LoadSessionResult(%p)", this);
282 // ContentDecryptionModuleResult implementation.
283 virtual void complete() override
285 ASSERT_NOT_REACHED();
286 completeWithDOMException(InvalidStateError, "Unexpected completion.");
289 virtual void completeWithSession(WebContentDecryptionModuleResult::SessionStatus status) override
293 case WebContentDecryptionModuleResult::NewSession:
297 case WebContentDecryptionModuleResult::SessionNotFound:
301 case WebContentDecryptionModuleResult::SessionAlreadyExists:
302 ASSERT_NOT_REACHED();
303 completeWithDOMException(InvalidStateError, "Unexpected completion.");
307 m_session->finishLoad();
308 m_resolver->resolve(result);
312 virtual void completeWithError(WebContentDecryptionModuleException exceptionCode, unsigned long systemCode, const WebString& errorMessage) override
314 completeWithDOMException(WebCdmExceptionToExceptionCode(exceptionCode), errorMessage);
317 // It is only valid to call this before completion.
318 ScriptPromise promise() { return m_resolver->promise(); }
320 void trace(Visitor* visitor)
322 visitor->trace(m_session);
323 ContentDecryptionModuleResult::trace(visitor);
327 // Reject the promise with a DOMException.
328 void completeWithDOMException(ExceptionCode code, const String& errorMessage)
330 m_resolver->reject(DOMException::create(code, errorMessage));
334 RefPtr<ScriptPromiseResolver> m_resolver;
335 Member<MediaKeySession> m_session;
338 MediaKeySession* MediaKeySession::create(ScriptState* scriptState, MediaKeys* mediaKeys, const String& sessionType)
340 ASSERT(sessionType == kTemporary || sessionType == kPersistent);
341 RefPtrWillBeRawPtr<MediaKeySession> session = new MediaKeySession(scriptState, mediaKeys, sessionType);
342 session->suspendIfNeeded();
343 return session.get();
346 bool MediaKeySession::isValidSessionType(const String& sessionType)
348 return (sessionType == kTemporary || sessionType == kPersistent);
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)
361 , m_closedPromise(new ClosedPromise(scriptState->executionContext(), this, ClosedPromise::Closed))
362 , m_actionTimer(this, &MediaKeySession::actionTimerFired)
364 WTF_LOG(Media, "MediaKeySession(%p)::MediaKeySession", this);
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);
373 // MediaKeys::createSession(), step 2.
374 // 2.1 Let the sessionId attribute be the empty string.
375 ASSERT(sessionId().isEmpty());
377 // 2.2 Let the expiration attribute be NaN.
378 ASSERT(std::isnan(m_expiration));
380 // 2.3 Let the closed attribute be a new promise.
381 ASSERT(!closed(scriptState).isUndefinedOrNull());
383 // 2.4 Let the session type be sessionType.
384 ASSERT(isValidSessionType(sessionType));
386 // 2.5 Let uninitialized be true.
387 ASSERT(m_isUninitialized);
389 // 2.6 Let callable be false.
390 ASSERT(!m_isCallable);
393 MediaKeySession::~MediaKeySession()
395 WTF_LOG(Media, "MediaKeySession(%p)::~MediaKeySession", this);
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();
405 void MediaKeySession::setError(MediaKeyError* error)
410 String MediaKeySession::sessionId() const
412 return m_session->sessionId();
415 ScriptPromise MediaKeySession::closed(ScriptState* scriptState)
417 return m_closedPromise->promise(scriptState->world());
420 ScriptPromise MediaKeySession::generateRequest(ScriptState* scriptState, const String& initDataType, DOMArrayBuffer* initData)
422 RefPtr<DOMArrayBuffer> initDataCopy = DOMArrayBuffer::create(initData->data(), initData->byteLength());
423 return generateRequestInternal(scriptState, initDataType, initDataCopy.release());
426 ScriptPromise MediaKeySession::generateRequest(ScriptState* scriptState, const String& initDataType, DOMArrayBufferView* initData)
428 RefPtr<DOMArrayBuffer> initDataCopy = DOMArrayBuffer::create(initData->baseAddress(), initData->byteLength());
429 return generateRequestInternal(scriptState, initDataType, initDataCopy.release());
432 ScriptPromise MediaKeySession::generateRequestInternal(ScriptState* scriptState, const String& initDataType, PassRefPtr<DOMArrayBuffer> initData)
434 WTF_LOG(Media, "MediaKeySession(%p)::generateRequest %s", this, initDataType.ascii().data());
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:
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."));
447 // 2. Let this object's uninitialized be false.
448 m_isUninitialized = false;
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."));
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."));
464 // 5. Let media keys be the MediaKeys object that created this object.
465 // (Use m_mediaKey, which was set in the constructor.)
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."));
476 // 7. Let init data be a copy of the contents of the initData parameter.
477 // (Done before calling this method.)
479 // 8. Let session type be this object's session type.
480 // (Done in constructor.)
482 // 9. Let promise be a new promise.
483 NewSessionResult* result = new NewSessionResult(scriptState, this);
484 ScriptPromise promise = result->promise();
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);
492 // 11. Return promise.
496 ScriptPromise MediaKeySession::load(ScriptState* scriptState, const String& sessionId)
498 WTF_LOG(Media, "MediaKeySession(%p)::load %s", this, sessionId.ascii().data());
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:
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."));
511 // 2. Let this object's uninitialized be false.
512 m_isUninitialized = false;
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."));
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'."));
528 // 5. Let media keys be the MediaKeys object that created this object.
529 // (Done in constructor.)
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".
538 // 7. Let promise be a new promise.
539 LoadSessionResult* result = new LoadSessionResult(scriptState, this);
540 ScriptPromise promise = result->promise();
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);
548 // 9. Return promise.
552 ScriptPromise MediaKeySession::update(ScriptState* scriptState, DOMArrayBuffer* response)
554 RefPtr<DOMArrayBuffer> responseCopy = DOMArrayBuffer::create(response->data(), response->byteLength());
555 return updateInternal(scriptState, responseCopy.release());
558 ScriptPromise MediaKeySession::update(ScriptState* scriptState, DOMArrayBufferView* response)
560 RefPtr<DOMArrayBuffer> responseCopy = DOMArrayBuffer::create(response->baseAddress(), response->byteLength());
561 return updateInternal(scriptState, responseCopy.release());
564 ScriptPromise MediaKeySession::updateInternal(ScriptState* scriptState, PassRefPtr<DOMArrayBuffer> response)
566 WTF_LOG(Media, "MediaKeySession(%p)::update", this);
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:
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."));
581 // 2. Let message be a copy of the contents of the response parameter.
582 // (Copied in the caller.)
584 // 3. Let promise be a new promise.
585 SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState);
586 ScriptPromise promise = result->promise();
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);
594 // 5. Return promise.
598 ScriptPromise MediaKeySession::close(ScriptState* scriptState)
600 WTF_LOG(Media, "MediaKeySession(%p)::close", this);
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:
609 // 1. If this object's callable value is false, return a promise rejected
610 // with a new DOMException whose name is "InvalidStateError".
612 return ScriptPromise::rejectWithDOMException(
613 scriptState, DOMException::create(InvalidStateError, "The session is not callable."));
616 // 2. If the Session Close algorithm has been run on this object,
617 // return a resolved promise.
619 return ScriptPromise::cast(scriptState, ScriptValue());
621 // 3. Let promise be a new promise.
622 SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState);
623 ScriptPromise promise = result->promise();
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);
631 // 5. Return promise.
635 ScriptPromise MediaKeySession::remove(ScriptState* scriptState)
637 WTF_LOG(Media, "MediaKeySession(%p)::remove", this);
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:
643 // 1. If this object's callable value is false, return a promise rejected
644 // with a new DOMException whose name is "InvalidStateError".
646 return ScriptPromise::rejectWithDOMException(
647 scriptState, DOMException::create(InvalidStateError, "The session is not callable."));
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'."));
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".
661 return ScriptPromise::rejectWithDOMException(
662 scriptState, DOMException::create(InvalidStateError, "The session is already closed."));
665 // 4. Let promise be a new promise.
666 SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState);
667 ScriptPromise promise = result->promise();
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);
675 // 6. Return promise.
679 void MediaKeySession::actionTimerFired(Timer<MediaKeySession>*)
681 ASSERT(m_pendingActions.size());
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);
689 while (!pendingActions.isEmpty()) {
690 PendingAction* action = pendingActions.takeFirst();
692 switch (action->type()) {
693 case PendingAction::GenerateRequest:
694 WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: GenerateRequest", this);
696 // 10.1 Let request be null.
697 // 10.2 Let cdm be the CDM loaded during the initialization of
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());
711 // Remainder of steps executed in finishGenerateRequest(), called
712 // when |result| is resolved.
715 case PendingAction::Load:
716 // NOTE: Continue step 8 of MediaKeySession::load().
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");
730 // 8.3 Let expiration time be NaN.
731 // (Done in the constructor.)
732 ASSERT(std::isnan(m_expiration));
734 // 8.4 Let message be null.
735 // 8.5 Let message type be null.
736 // (Will be provided by the CDM if needed.)
738 // 8.6 Let origin be the origin of this object's Document.
739 // (Obtained previously when CDM created.)
741 // 8.7 Let cdm be the CDM loaded during the initialization of media
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
762 // 8.8.6.2 Let message type be the appropriate MediaKeyMessageType
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());
768 // Remainder of steps executed in finishLoad(), called
769 // when |result| is resolved.
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());
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
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());
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
811 // 5.3.1 If any of the preceding steps failed, reject promise
812 // with a new DOMException whose name is the appropriate
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());
822 void MediaKeySession::finishGenerateRequest()
824 // 10.4 Set the sessionId attribute to a unique Session ID string.
825 // It may be obtained from cdm.
826 ASSERT(!sessionId().isEmpty());
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()).
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
837 // 10.7 Run the Queue a "message" Event algorithm on the session,
838 // providing request and null.
839 // (Done by the CDM).
841 // 10.8 Let this object's callable be true.
845 void MediaKeySession::finishLoad()
847 // 8.10 Set the sessionId attribute to sanitized session ID.
848 ASSERT(!sessionId().isEmpty());
850 // 8.11 Let this object's callable be true.
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
858 // (Done by the CDM.)
860 // 8.13 Run the Update Expiration algorithm on the session,
861 // providing expiration time.
862 // (Done by the CDM.)
864 // 8.14 If message is not null, run the Queue a "message" Event
865 // algorithm on the session, providing message type and
867 // (Done by the CDM.)
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)
873 WTF_LOG(Media, "MediaKeySession(%p)::message", this);
875 // Verify that 'message' not fired before session initialization is complete.
876 ASSERT(m_isCallable);
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();
884 RefPtrWillBeRawPtr<MediaKeyMessageEvent> event = MediaKeyMessageEvent::create(EventTypeNames::message, init);
885 event->setTarget(this);
886 m_asyncEventQueue->enqueueEvent(event.release());
889 void MediaKeySession::ready()
891 WTF_LOG(Media, "MediaKeySession(%p)::ready", this);
893 RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::ready);
894 event->setTarget(this);
895 m_asyncEventQueue->enqueueEvent(event.release());
898 void MediaKeySession::close()
900 WTF_LOG(Media, "MediaKeySession(%p)::close", this);
902 // Once closed, the session can no longer be the target of events from
903 // the CDM so this object can be garbage collected.
906 // Resolve the closed promise.
907 m_closedPromise->resolve(V8UndefinedType());
910 // Queue a task to fire a simple event named keyadded at the MediaKeySession object.
911 void MediaKeySession::error(MediaKeyErrorCode errorCode, unsigned long systemCode)
913 WTF_LOG(Media, "MediaKeySession(%p)::error: errorCode=%d, systemCode=%lu", this, errorCode, systemCode);
915 MediaKeyError::Code mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
917 case MediaKeyErrorCodeUnknown:
918 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
920 case MediaKeyErrorCodeClient:
921 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_CLIENT;
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);
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());
937 void MediaKeySession::error(WebContentDecryptionModuleException exception, unsigned long systemCode, const WebString& errorMessage)
939 WTF_LOG(Media, "MediaKeySession::error: exception=%d, systemCode=%lu", exception, systemCode);
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;
947 case WebContentDecryptionModuleExceptionClientError:
948 errorCode = MediaKeyErrorCodeClient;
951 // All other exceptions get converted into Unknown.
952 errorCode = MediaKeyErrorCodeUnknown;
955 error(errorCode, systemCode);
958 void MediaKeySession::expirationChanged(double updatedExpiryTimeInMS)
960 m_expiration = updatedExpiryTimeInMS;
963 const AtomicString& MediaKeySession::interfaceName() const
965 return EventTargetNames::MediaKeySession;
968 ExecutionContext* MediaKeySession::executionContext() const
970 return ActiveDOMObject::executionContext();
973 bool MediaKeySession::hasPendingActivity() const
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" : "");
983 return ActiveDOMObject::hasPendingActivity()
984 || !m_pendingActions.isEmpty()
985 || m_asyncEventQueue->hasPendingEvents()
986 || (m_mediaKeys && !m_isClosed);
989 void MediaKeySession::stop()
991 // Stop the CDM from firing any more events for this session.
995 if (m_actionTimer.isActive())
996 m_actionTimer.stop();
997 m_pendingActions.clear();
998 m_asyncEventQueue->close();
1001 void MediaKeySession::trace(Visitor* visitor)
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);
1011 } // namespace blink