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