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
isKeySystemSupportedWithContentType(const String & keySystem,const String & contentType)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
createRejectedPromise(ScriptState * scriptState,ExceptionCode error,const String & errorMessage)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
create(ScriptState * scriptState,const String & keySystem)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
MediaKeysInitializer(ScriptState * scriptState,const String & keySystem)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
~MediaKeysInitializer()109 MediaKeysInitializer::~MediaKeysInitializer()
110 {
111 WTF_LOG(Media, "MediaKeysInitializer::~MediaKeysInitializer");
112 }
113
timerFired(Timer<MediaKeysInitializer> *)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
create(ScriptState * scriptState,const String & keySystem)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
MediaKeys(ExecutionContext * context,const String & keySystem,PassOwnPtr<WebContentDecryptionModule> cdm)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
~MediaKeys()188 MediaKeys::~MediaKeys()
189 {
190 WTF_LOG(Media, "MediaKeys(%p)::~MediaKeys", this);
191 }
192
createSession(ScriptState * scriptState,const String & sessionType)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
isTypeSupported(const String & keySystem,const String & contentType)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
contentDecryptionModule()235 WebContentDecryptionModule* MediaKeys::contentDecryptionModule()
236 {
237 return m_cdm.get();
238 }
239
trace(Visitor * visitor)240 void MediaKeys::trace(Visitor* visitor)
241 {
242 }
243
contextDestroyed()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