• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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