Home | History | Annotate | Download | only in encryptedmedia
      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/MediaKeys.h"
     28 
     29 #include "bindings/core/v8/ScriptPromiseResolver.h"
     30 #include "bindings/core/v8/ScriptState.h"
     31 #include "core/dom/DOMException.h"
     32 #include "core/dom/Document.h"
     33 #include "core/dom/ExceptionCode.h"
     34 #include "core/dom/ExecutionContext.h"
     35 #include "modules/encryptedmedia/MediaKeyMessageEvent.h"
     36 #include "modules/encryptedmedia/MediaKeySession.h"
     37 #include "modules/encryptedmedia/MediaKeysController.h"
     38 #include "platform/ContentType.h"
     39 #include "platform/Logging.h"
     40 #include "platform/MIMETypeRegistry.h"
     41 #include "platform/Timer.h"
     42 #include "platform/UUID.h"
     43 #include "public/platform/Platform.h"
     44 #include "public/platform/WebContentDecryptionModule.h"
     45 #include "wtf/ArrayBuffer.h"
     46 #include "wtf/ArrayBufferView.h"
     47 #include "wtf/RefPtr.h"
     48 
     49 #if ENABLE(ASSERT)
     50 namespace {
     51 
     52 // The list of possible values for |sessionType| passed to createSession().
     53 const char* kTemporary = "temporary";
     54 const char* kPersistent = "persistent";
     55 
     56 } // namespace
     57 #endif
     58 
     59 namespace blink {
     60 
     61 static bool isKeySystemSupportedWithContentType(const String& keySystem, const String& contentType)
     62 {
     63     ASSERT(!keySystem.isEmpty());
     64 
     65     ContentType type(contentType);
     66     String codecs = type.parameter("codecs");
     67     return MIMETypeRegistry::isSupportedEncryptedMediaMIMEType(keySystem, type.type(), codecs);
     68 }
     69 
     70 static ScriptPromise createRejectedPromise(ScriptState* scriptState, ExceptionCode error, const String& errorMessage)
     71 {
     72     return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(error, errorMessage));
     73 }
     74 
     75 // This class allows a MediaKeys object to be created asynchronously.
     76 class MediaKeysInitializer : public ScriptPromiseResolver {
     77     WTF_MAKE_NONCOPYABLE(MediaKeysInitializer);
     78 
     79 public:
     80     static ScriptPromise create(ScriptState*, const String& keySystem);
     81     virtual ~MediaKeysInitializer();
     82 
     83 private:
     84     MediaKeysInitializer(ScriptState*, const String& keySystem);
     85     void timerFired(Timer<MediaKeysInitializer>*);
     86 
     87     const String m_keySystem;
     88     Timer<MediaKeysInitializer> m_timer;
     89 };
     90 
     91 ScriptPromise MediaKeysInitializer::create(ScriptState* scriptState, const String& keySystem)
     92 {
     93     RefPtr<MediaKeysInitializer> initializer = adoptRef(new MediaKeysInitializer(scriptState, keySystem));
     94     initializer->suspendIfNeeded();
     95     initializer->keepAliveWhilePending();
     96     return initializer->promise();
     97 }
     98 
     99 MediaKeysInitializer::MediaKeysInitializer(ScriptState* scriptState, const String& keySystem)
    100     : ScriptPromiseResolver(scriptState)
    101     , m_keySystem(keySystem)
    102     , m_timer(this, &MediaKeysInitializer::timerFired)
    103 {
    104     WTF_LOG(Media, "MediaKeysInitializer::MediaKeysInitializer");
    105     // Start the timer so that MediaKeys can be created asynchronously.
    106     m_timer.startOneShot(0, FROM_HERE);
    107 }
    108 
    109 MediaKeysInitializer::~MediaKeysInitializer()
    110 {
    111     WTF_LOG(Media, "MediaKeysInitializer::~MediaKeysInitializer");
    112 }
    113 
    114 void MediaKeysInitializer::timerFired(Timer<MediaKeysInitializer>*)
    115 {
    116     WTF_LOG(Media, "MediaKeysInitializer::timerFired");
    117 
    118     // NOTE: Continued from step 4. of MediaKeys::create().
    119     // 4.1 Let cdm be the content decryption module corresponding to
    120     //     keySystem.
    121     // 4.2 Load and initialize the cdm if necessary.
    122     Document* document = toDocument(executionContext());
    123     MediaKeysController* controller = MediaKeysController::from(document->page());
    124     // FIXME: make createContentDecryptionModule() asynchronous.
    125     OwnPtr<WebContentDecryptionModule> cdm = controller->createContentDecryptionModule(executionContext(), m_keySystem);
    126 
    127     // 4.3 If cdm fails to load or initialize, reject promise with a new
    128     //     DOMException whose name is the appropriate error name and that
    129     //     has an appropriate message.
    130     if (!cdm) {
    131         String message("A content decryption module could not be loaded for the '" + m_keySystem + "' key system.");
    132         reject(DOMException::create(UnknownError, message));
    133         return;
    134     }
    135 
    136     // 4.4 Let media keys be a new MediaKeys object.
    137     MediaKeys* mediaKeys = new MediaKeys(executionContext(), m_keySystem, cdm.release());
    138 
    139     // 4.5. Resolve promise with media keys.
    140     resolve(mediaKeys);
    141 
    142     // Note: As soon as the promise is resolved (or rejected), the
    143     // ScriptPromiseResolver object (|this|) is freed. So access to
    144     // any members will crash once the promise is fulfilled.
    145 }
    146 
    147 ScriptPromise MediaKeys::create(ScriptState* scriptState, const String& keySystem)
    148 {
    149     WTF_LOG(Media, "MediaKeys::create(%s)", keySystem.ascii().data());
    150 
    151     // From https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-create:
    152     // The create(keySystem) method creates a new MediaKeys object for keySystem. It must run the following steps:
    153 
    154     // 1. If keySystem is an empty string, return a promise rejected with a new
    155     // DOMException whose name is "InvalidAccessError" and that has the message
    156     // "The keySystem parameter is empty."
    157     if (keySystem.isEmpty()) {
    158         return createRejectedPromise(scriptState, InvalidAccessError, "The keySystem parameter is empty.");
    159     }
    160 
    161     // 2. If keySystem is not one of the Key Systems supported by the user
    162     // agent, return a promise rejected with a new DOMException whose name is
    163     // "NotSupportedError" and that has the message "The key system keySystem
    164     // is not supported." String comparison is case-sensitive.
    165     if (!isKeySystemSupportedWithContentType(keySystem, "")) {
    166         // String message("The key system '" + keySystem + "' is not supported.");
    167         return createRejectedPromise(scriptState, NotSupportedError, "The key system '" + keySystem + "' is not supported.");
    168     }
    169 
    170     // 3. Let promise be a new promise.
    171     // 4. Asynchronously create and initialize the MediaKeys.
    172     // 5. Return promise.
    173     return MediaKeysInitializer::create(scriptState, keySystem);
    174 }
    175 
    176 MediaKeys::MediaKeys(ExecutionContext* context, const String& keySystem, PassOwnPtr<WebContentDecryptionModule> cdm)
    177     : ContextLifecycleObserver(context)
    178     , m_keySystem(keySystem)
    179     , m_cdm(cdm)
    180 {
    181     WTF_LOG(Media, "MediaKeys(%p)::MediaKeys", this);
    182 
    183     // Step 4.4 of MediaKeys::create():
    184     // 4.4.1 Set the keySystem attribute to keySystem.
    185     ASSERT(!m_keySystem.isEmpty());
    186 }
    187 
    188 MediaKeys::~MediaKeys()
    189 {
    190     WTF_LOG(Media, "MediaKeys(%p)::~MediaKeys", this);
    191 }
    192 
    193 MediaKeySession* MediaKeys::createSession(ScriptState* scriptState, const String& sessionType)
    194 {
    195     WTF_LOG(Media, "MediaKeys(%p)::createSession", this);
    196 
    197     // From <http://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-createsession>:
    198     // The createSession(sessionType) method returns a new MediaKeySession
    199     // object. It must run the following steps:
    200     // 1. If sessionType is not supported by the content decryption module
    201     //    corresponding to the keySystem, throw a DOMException whose name is
    202     //    "NotSupportedError".
    203     // FIXME: Check whether sessionType is actually supported by the CDM.
    204     ASSERT(sessionType == kTemporary || sessionType == kPersistent);
    205 
    206     // 2. Let session be a new MediaKeySession object, and initialize it as
    207     //    follows:
    208     //    (Initialization is performed in the constructor.)
    209     // 3. Return session.
    210     return MediaKeySession::create(scriptState, this, sessionType);
    211 }
    212 
    213 bool MediaKeys::isTypeSupported(const String& keySystem, const String& contentType)
    214 {
    215     WTF_LOG(Media, "MediaKeys::isTypeSupported(%s, %s)", keySystem.ascii().data(), contentType.ascii().data());
    216 
    217     // 1. If keySystem is an empty string, return false and abort these steps.
    218     if (keySystem.isEmpty())
    219         return false;
    220 
    221     // 2. If keySystem contains an unrecognized or unsupported Key System, return false and abort
    222     // these steps. Key system string comparison is case-sensitive.
    223     if (!isKeySystemSupportedWithContentType(keySystem, ""))
    224         return false;
    225 
    226     // 3. If contentType is an empty string, return true and abort these steps.
    227     if (contentType.isEmpty())
    228         return true;
    229 
    230     // 4. If the Key System specified by keySystem does not support decrypting the container and/or
    231     // codec specified by contentType, return false and abort these steps.
    232     return isKeySystemSupportedWithContentType(keySystem, contentType);
    233 }
    234 
    235 WebContentDecryptionModule* MediaKeys::contentDecryptionModule()
    236 {
    237     return m_cdm.get();
    238 }
    239 
    240 void MediaKeys::trace(Visitor* visitor)
    241 {
    242 }
    243 
    244 void MediaKeys::contextDestroyed()
    245 {
    246     ContextLifecycleObserver::contextDestroyed();
    247 
    248     // We don't need the CDM anymore.
    249     m_cdm.clear();
    250 }
    251 
    252 } // namespace blink
    253