/* * 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/Logging.h" #include "platform/Timer.h" #include "public/platform/WebContentDecryptionModule.h" #include "wtf/RefPtr.h" namespace blink { // A class holding a pending action. class MediaKeys::PendingAction : public GarbageCollectedFinalized { public: const Persistent result() const { return m_result; } const RefPtr data() const { return m_data; } static PendingAction* CreatePendingSetServerCertificate(ContentDecryptionModuleResult* result, PassRefPtr serverCertificate) { ASSERT(result); ASSERT(serverCertificate); return new PendingAction(result, serverCertificate); } ~PendingAction() { } DEFINE_INLINE_TRACE() { visitor->trace(m_result); } private: PendingAction(ContentDecryptionModuleResult* result, PassRefPtr data) : m_result(result) , m_data(data) { } const Member m_result; const RefPtr m_data; }; MediaKeys* MediaKeys::create(ExecutionContext* context, const WebVector& supportedSessionTypes, PassOwnPtr cdm) { MediaKeys* mediaKeys = new MediaKeys(context, supportedSessionTypes, cdm); mediaKeys->suspendIfNeeded(); return mediaKeys; } MediaKeys::MediaKeys(ExecutionContext* context, const WebVector& supportedSessionTypes, PassOwnPtr cdm) : ActiveDOMObject(context) , m_supportedSessionTypes(supportedSessionTypes) , m_cdm(cdm) , m_mediaElement(nullptr) , m_reservedForMediaElement(false) , m_timer(this, &MediaKeys::timerFired) { WTF_LOG(Media, "MediaKeys(%p)::MediaKeys", this); } MediaKeys::~MediaKeys() { WTF_LOG(Media, "MediaKeys(%p)::~MediaKeys", this); } MediaKeySession* MediaKeys::createSession(ScriptState* scriptState, const String& sessionTypeString, ExceptionState& exceptionState) { WTF_LOG(Media, "MediaKeys(%p)::createSession %s", this, sessionTypeString.utf8().data()); // 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://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-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. RefPtr serverCertificateBuffer = DOMArrayBuffer::create(serverCertificate.data(), serverCertificate.byteLength()); // 4. Let promise be a new promise. SimpleContentDecryptionModuleResultPromise* result = new SimpleContentDecryptionModuleResultPromise(scriptState); ScriptPromise promise = result->promise(); // 5. Run the following steps asynchronously (documented in timerFired()). m_pendingActions.append(PendingAction::CreatePendingSetServerCertificate(result, serverCertificateBuffer.release())); 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() { ASSERT(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(Timer*) { ASSERT(m_pendingActions.size()); // Swap the queue to a local copy to avoid problems if resolving promises // run synchronously. HeapDeque> pendingActions; pendingActions.swap(m_pendingActions); while (!pendingActions.isEmpty()) { PendingAction* action = pendingActions.takeFirst(); WTF_LOG(Media, "MediaKeys(%p)::timerFired: Certificate", this); // 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(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.clear(); } bool MediaKeys::hasPendingActivity() const { // Remain around if there are pending events. WTF_LOG(Media, "MediaKeys(%p)::hasPendingActivity %s%s%s", this, ScriptWrappable::hasPendingActivity() ? " ScriptWrappable::hasPendingActivity()" : "", !m_pendingActions.isEmpty() ? " !m_pendingActions.isEmpty()" : "", m_reservedForMediaElement ? " m_reservedForMediaElement" : ""); return ScriptWrappable::hasPendingActivity() || !m_pendingActions.isEmpty() || m_reservedForMediaElement; } void MediaKeys::stop() { ActiveDOMObject::stop(); if (m_timer.isActive()) m_timer.stop(); m_pendingActions.clear(); } } // namespace blink