| /* |
| * Copyright (C) 2013 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "modules/encryptedmedia/MediaKeys.h" |
| |
| #include "bindings/core/v8/ScriptState.h" |
| #include "core/dom/DOMArrayBuffer.h" |
| #include "core/dom/DOMException.h" |
| #include "core/dom/ExceptionCode.h" |
| #include "core/dom/ExecutionContext.h" |
| #include "core/html/HTMLMediaElement.h" |
| #include "modules/encryptedmedia/EncryptedMediaUtils.h" |
| #include "modules/encryptedmedia/MediaKeySession.h" |
| #include "modules/encryptedmedia/SimpleContentDecryptionModuleResultPromise.h" |
| #include "platform/Timer.h" |
| #include "public/platform/WebContentDecryptionModule.h" |
| #include "wtf/RefPtr.h" |
| #include <memory> |
| |
| #define MEDIA_KEYS_LOG_LEVEL 3 |
| |
| namespace blink { |
| |
| // A class holding a pending action. |
| class MediaKeys::PendingAction final |
| : public GarbageCollected<MediaKeys::PendingAction> { |
| public: |
| const Persistent<ContentDecryptionModuleResult> result() const { |
| return m_result; |
| } |
| |
| DOMArrayBuffer* data() const { return m_data; } |
| |
| static PendingAction* CreatePendingSetServerCertificate( |
| ContentDecryptionModuleResult* result, |
| DOMArrayBuffer* serverCertificate) { |
| DCHECK(result); |
| DCHECK(serverCertificate); |
| return new PendingAction(result, serverCertificate); |
| } |
| |
| DEFINE_INLINE_TRACE() { |
| visitor->trace(m_result); |
| visitor->trace(m_data); |
| } |
| |
| private: |
| PendingAction(ContentDecryptionModuleResult* result, DOMArrayBuffer* data) |
| : m_result(result), m_data(data) {} |
| |
| const Member<ContentDecryptionModuleResult> m_result; |
| const Member<DOMArrayBuffer> m_data; |
| }; |
| |
| // This class wraps the promise resolver used when setting the certificate |
| // and is passed to Chromium to fullfill the promise. This implementation of |
| // complete() will resolve the promise with true, while completeWithError() |
| // will reject the promise with an exception. completeWithSession() |
| // is not expected to be called, and will reject the promise. |
| class SetCertificateResultPromise |
| : public ContentDecryptionModuleResultPromise { |
| public: |
| SetCertificateResultPromise(ScriptState* scriptState) |
| : ContentDecryptionModuleResultPromise(scriptState) {} |
| |
| ~SetCertificateResultPromise() override {} |
| |
| // ContentDecryptionModuleResult implementation. |
| void complete() override { resolve(true); } |
| |
| void completeWithError(WebContentDecryptionModuleException exceptionCode, |
| unsigned long systemCode, |
| const WebString& errorMessage) override { |
| // The EME spec specifies that "If the Key System implementation does |
| // not support server certificates, return a promise resolved with |
| // false." So convert any NOTSUPPORTEDERROR into resolving with false. |
| if (exceptionCode == WebContentDecryptionModuleExceptionNotSupportedError) { |
| resolve(false); |
| return; |
| } |
| |
| ContentDecryptionModuleResultPromise::completeWithError( |
| exceptionCode, systemCode, errorMessage); |
| } |
| }; |
| |
| MediaKeys* MediaKeys::create( |
| ExecutionContext* context, |
| const WebVector<WebEncryptedMediaSessionType>& supportedSessionTypes, |
| std::unique_ptr<WebContentDecryptionModule> cdm) { |
| MediaKeys* mediaKeys = |
| new MediaKeys(context, supportedSessionTypes, std::move(cdm)); |
| mediaKeys->suspendIfNeeded(); |
| return mediaKeys; |
| } |
| |
| MediaKeys::MediaKeys( |
| ExecutionContext* context, |
| const WebVector<WebEncryptedMediaSessionType>& supportedSessionTypes, |
| std::unique_ptr<WebContentDecryptionModule> cdm) |
| : ActiveScriptWrappable(this), |
| ActiveDOMObject(context), |
| m_supportedSessionTypes(supportedSessionTypes), |
| m_cdm(std::move(cdm)), |
| m_mediaElement(nullptr), |
| m_reservedForMediaElement(false), |
| m_timer(this, &MediaKeys::timerFired) { |
| DVLOG(MEDIA_KEYS_LOG_LEVEL) << __func__ << "(" << this << ")"; |
| } |
| |
| MediaKeys::~MediaKeys() { |
| DVLOG(MEDIA_KEYS_LOG_LEVEL) << __func__ << "(" << this << ")"; |
| } |
| |
| MediaKeySession* MediaKeys::createSession(ScriptState* scriptState, |
| const String& sessionTypeString, |
| ExceptionState& exceptionState) { |
| DVLOG(MEDIA_KEYS_LOG_LEVEL) << __func__ << "(" << this << ") " |
| << sessionTypeString; |
| |
| // From http://w3c.github.io/encrypted-media/#createSession |
| |
| // When this method is invoked, the user agent must run the following steps: |
| // 1. If this object's persistent state allowed value is false and |
| // sessionType is not "temporary", throw a new DOMException whose name is |
| // NotSupportedError. |
| // (Chromium ensures that only session types supported by the |
| // configuration are listed in supportedSessionTypes.) |
| // 2. If the Key System implementation represented by this object's cdm |
| // implementation value does not support sessionType, throw a new |
| // DOMException whose name is NotSupportedError. |
| WebEncryptedMediaSessionType sessionType = |
| EncryptedMediaUtils::convertToSessionType(sessionTypeString); |
| if (!sessionTypeSupported(sessionType)) { |
| exceptionState.throwDOMException(NotSupportedError, |
| "Unsupported session type."); |
| return nullptr; |
| } |
| |
| // 3. Let session be a new MediaKeySession object, and initialize it as |
| // follows: |
| // (Initialization is performed in the constructor.) |
| // 4. Return session. |
| return MediaKeySession::create(scriptState, this, sessionType); |
| } |
| |
| ScriptPromise MediaKeys::setServerCertificate( |
| ScriptState* scriptState, |
| const DOMArrayPiece& serverCertificate) { |
| // From https://w3c.github.io/encrypted-media/#setServerCertificate |
| // The setServerCertificate(serverCertificate) method provides a server |
| // certificate to be used to encrypt messages to the license server. |
| // It must run the following steps: |
| // 1. If serverCertificate is an empty array, return a promise rejected |
| // with a new DOMException whose name is "InvalidAccessError". |
| if (!serverCertificate.byteLength()) { |
| return ScriptPromise::rejectWithDOMException( |
| scriptState, |
| DOMException::create(InvalidAccessError, |
| "The serverCertificate parameter is empty.")); |
| } |
| |
| // 2. If the keySystem does not support server certificates, return a |
| // promise rejected with a new DOMException whose name is |
| // "NotSupportedError". |
| // (Let the CDM decide whether to support this or not.) |
| |
| // 3. Let certificate be a copy of the contents of the serverCertificate |
| // parameter. |
| DOMArrayBuffer* serverCertificateBuffer = DOMArrayBuffer::create( |
| serverCertificate.data(), serverCertificate.byteLength()); |
| |
| // 4. Let promise be a new promise. |
| SetCertificateResultPromise* result = |
| new SetCertificateResultPromise(scriptState); |
| ScriptPromise promise = result->promise(); |
| |
| // 5. Run the following steps asynchronously (documented in timerFired()). |
| m_pendingActions.append(PendingAction::CreatePendingSetServerCertificate( |
| result, serverCertificateBuffer)); |
| if (!m_timer.isActive()) |
| m_timer.startOneShot(0, BLINK_FROM_HERE); |
| |
| // 6. Return promise. |
| return promise; |
| } |
| |
| bool MediaKeys::reserveForMediaElement(HTMLMediaElement* mediaElement) { |
| // If some other HtmlMediaElement already has a reference to us, fail. |
| if (m_mediaElement) |
| return false; |
| |
| m_mediaElement = mediaElement; |
| m_reservedForMediaElement = true; |
| return true; |
| } |
| |
| void MediaKeys::acceptReservation() { |
| m_reservedForMediaElement = false; |
| } |
| |
| void MediaKeys::cancelReservation() { |
| m_reservedForMediaElement = false; |
| m_mediaElement.clear(); |
| } |
| |
| void MediaKeys::clearMediaElement() { |
| DCHECK(m_mediaElement); |
| m_mediaElement.clear(); |
| } |
| |
| bool MediaKeys::sessionTypeSupported(WebEncryptedMediaSessionType sessionType) { |
| for (size_t i = 0; i < m_supportedSessionTypes.size(); i++) { |
| if (m_supportedSessionTypes[i] == sessionType) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void MediaKeys::timerFired(TimerBase*) { |
| DCHECK(m_pendingActions.size()); |
| |
| // Swap the queue to a local copy to avoid problems if resolving promises |
| // run synchronously. |
| HeapDeque<Member<PendingAction>> pendingActions; |
| pendingActions.swap(m_pendingActions); |
| |
| while (!pendingActions.isEmpty()) { |
| PendingAction* action = pendingActions.takeFirst(); |
| DVLOG(MEDIA_KEYS_LOG_LEVEL) << __func__ << "(" << this << ") Certificate"; |
| |
| // 5.1 Let cdm be the cdm during the initialization of this object. |
| WebContentDecryptionModule* cdm = contentDecryptionModule(); |
| |
| // 5.2 Use the cdm to process certificate. |
| cdm->setServerCertificate( |
| static_cast<unsigned char*>(action->data()->data()), |
| action->data()->byteLength(), action->result()->result()); |
| // 5.3 If any of the preceding steps failed, reject promise with a |
| // new DOMException whose name is the appropriate error name. |
| // 5.4 Resolve promise. |
| // (These are handled by Chromium and the CDM.) |
| } |
| } |
| |
| WebContentDecryptionModule* MediaKeys::contentDecryptionModule() { |
| return m_cdm.get(); |
| } |
| |
| DEFINE_TRACE(MediaKeys) { |
| visitor->trace(m_pendingActions); |
| visitor->trace(m_mediaElement); |
| ActiveDOMObject::trace(visitor); |
| } |
| |
| void MediaKeys::contextDestroyed() { |
| ActiveDOMObject::contextDestroyed(); |
| |
| // We don't need the CDM anymore. Only destroyed after all related |
| // ActiveDOMObjects have been stopped. |
| m_cdm.reset(); |
| } |
| |
| bool MediaKeys::hasPendingActivity() const { |
| // Remain around if there are pending events. |
| DVLOG(MEDIA_KEYS_LOG_LEVEL) |
| << __func__ << "(" << this << ")" |
| << (!m_pendingActions.isEmpty() ? " !m_pendingActions.isEmpty()" : "") |
| << (m_reservedForMediaElement ? " m_reservedForMediaElement" : ""); |
| |
| return !m_pendingActions.isEmpty() || m_reservedForMediaElement; |
| } |
| |
| void MediaKeys::stop() { |
| ActiveDOMObject::stop(); |
| |
| if (m_timer.isActive()) |
| m_timer.stop(); |
| m_pendingActions.clear(); |
| } |
| |
| } // namespace blink |