HRTFDatabaseLoader is not an absolute condition to run audioContext
[WebKit-https.git] / Source / WebCore / Modules / plugins / QuickTimePluginReplacement.mm
1 /*
2  * Copyright (C) 2013-2014 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #import "config.h"
27
28 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
29
30 #import "QuickTimePluginReplacement.h"
31
32 #import "Event.h"
33 #import "HTMLPlugInElement.h"
34 #import "HTMLVideoElement.h"
35 #import "JSDOMBinding.h"
36 #import "JSDOMGlobalObject.h"
37 #import "JSHTMLVideoElement.h"
38 #import "JSQuickTimePluginReplacement.h"
39 #import "Logging.h"
40 #import "MainFrame.h"
41 #import "Page.h"
42 #import "RenderElement.h"
43 #import "ScriptController.h"
44 #import "ScriptSourceCode.h"
45 #import "SoftLinking.h"
46 #import "UserAgentScripts.h"
47 #import <objc/runtime.h>
48 #import <AVFoundation/AVFoundation.h>
49 #import <CoreMedia/CoreMedia.h>
50 #import <Foundation/NSString.h>
51 #import <JavaScriptCore/JavaScriptCore.h>
52 #import <JavaScriptCore/APICast.h>
53 #import <wtf/text/Base64.h>
54
55 SOFT_LINK_FRAMEWORK_OPTIONAL(CoreMedia)
56 SOFT_LINK(CoreMedia, CMTimeCopyAsDictionary, CFDictionaryRef, (CMTime time, CFAllocatorRef allocator), (time, allocator))
57
58 typedef AVMetadataItem AVMetadataItemType;
59 SOFT_LINK_FRAMEWORK_OPTIONAL(AVFoundation)
60 SOFT_LINK_CLASS(AVFoundation, AVMetadataItem)
61 #define AVMetadataItem getAVMetadataItemClass()
62
63 namespace WebCore {
64
65 #if PLATFORM(IOS)
66 static JSValue *jsValueWithValueInContext(id, JSContext *);
67 static JSValue *jsValueWithAVMetadataItemInContext(AVMetadataItemType *, JSContext *);
68 #endif
69
70 static String quickTimePluginReplacementScript()
71 {
72     DEPRECATED_DEFINE_STATIC_LOCAL(String, script, (QuickTimePluginReplacementJavaScript, sizeof(QuickTimePluginReplacementJavaScript)));
73     return script;
74 }
75
76 void QuickTimePluginReplacement::registerPluginReplacement(PluginReplacementRegistrar registrar)
77 {
78     registrar(ReplacementPlugin(create, supportsMimeType, supportsFileExtension, supportsURL));
79 }
80
81 PassRefPtr<PluginReplacement> QuickTimePluginReplacement::create(HTMLPlugInElement& plugin, const Vector<String>& paramNames, const Vector<String>& paramValues)
82 {
83     return adoptRef(new QuickTimePluginReplacement(plugin, paramNames, paramValues));
84 }
85
86 bool QuickTimePluginReplacement::supportsMimeType(const String& mimeType)
87 {
88     static const char* types[] = {
89         "application/vnd.apple.mpegurl", "application/x-mpegurl", "audio/3gpp", "audio/3gpp2", "audio/aac", "audio/aiff",
90         "audio/amr", "audio/basic", "audio/mp3", "audio/mp4", "audio/mpeg", "audio/mpeg3", "audio/mpegurl", "audio/scpls",
91         "audio/wav", "audio/x-aac", "audio/x-aiff", "audio/x-caf", "audio/x-m4a", "audio/x-m4b", "audio/x-m4p",
92         "audio/x-m4r", "audio/x-mp3", "audio/x-mpeg", "audio/x-mpeg3", "audio/x-mpegurl", "audio/x-scpls", "audio/x-wav",
93         "video/3gpp", "video/3gpp2", "video/mp4", "video/quicktime", "video/x-m4v"
94     };
95     DEPRECATED_DEFINE_STATIC_LOCAL(HashSet<String>, typeHash, ());
96     if (!typeHash.size()) {
97         for (size_t i = 0; i < WTF_ARRAY_LENGTH(types); ++i)
98             typeHash.add(types[i]);
99     }
100
101     return typeHash.contains(mimeType);
102 }
103
104 bool QuickTimePluginReplacement::supportsFileExtension(const String& extension)
105 {
106     static const char* extensions[] = {
107         "3g2", "3gp", "3gp2", "3gpp", "aac", "adts", "aif", "aifc", "aiff", "AMR", "au", "bwf", "caf", "cdda", "m3u",
108         "m3u8", "m4a", "m4b", "m4p", "m4r", "m4v", "mov", "mp3", "mp3", "mp4", "mpeg", "mpg", "mqv", "pls", "qt",
109         "snd", "swa", "ts", "ulw", "wav"
110     };
111     DEPRECATED_DEFINE_STATIC_LOCAL(HashSet<String>, extensionHash, ());
112     if (!extensionHash.size()) {
113         for (size_t i = 0; i < WTF_ARRAY_LENGTH(extensions); ++i)
114             extensionHash.add(extensions[i]);
115     }
116
117     return extensionHash.contains(extension);
118 }
119
120 QuickTimePluginReplacement::QuickTimePluginReplacement(HTMLPlugInElement& plugin, const Vector<String>& paramNames, const Vector<String>& paramValues)
121     :PluginReplacement()
122     , m_parentElement(&plugin)
123     , m_names(paramNames)
124     , m_values(paramValues)
125     , m_scriptObject(nullptr)
126 {
127 }
128
129 QuickTimePluginReplacement::~QuickTimePluginReplacement()
130 {
131     m_parentElement = nullptr;
132     m_scriptObject = nullptr;
133     m_mediaElement = nullptr;
134 }
135
136 RenderPtr<RenderElement> QuickTimePluginReplacement::createElementRenderer(HTMLPlugInElement& plugin, PassRef<RenderStyle> style)
137 {
138     ASSERT_UNUSED(plugin, m_parentElement == &plugin);
139
140     if (m_mediaElement)
141         return m_mediaElement->createElementRenderer(WTF::move(style));
142
143     return nullptr;
144 }
145
146 DOMWrapperWorld& QuickTimePluginReplacement::isolatedWorld()
147 {
148     static DOMWrapperWorld& isolatedWorld = *DOMWrapperWorld::create(JSDOMWindow::commonVM()).leakRef();
149     return isolatedWorld;
150 }
151
152 bool QuickTimePluginReplacement::ensureReplacementScriptInjected()
153 {
154     Page* page = m_parentElement->document().page();
155     if (!page)
156         return false;
157     
158     DOMWrapperWorld& world = isolatedWorld();
159     ScriptController& scriptController = page->mainFrame().script();
160     JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world));
161     JSC::ExecState* exec = globalObject->globalExec();
162     JSC::JSLockHolder lock(exec);
163     
164     JSC::JSValue replacementFunction = globalObject->get(exec, JSC::Identifier(exec, "createPluginReplacement"));
165     if (replacementFunction.isFunction())
166         return true;
167     
168     scriptController.evaluateInWorld(ScriptSourceCode(quickTimePluginReplacementScript()), world);
169     if (exec->hadException()) {
170         LOG(Plugins, "%p - Exception when evaluating QuickTime plugin replacement script", this);
171         exec->clearException();
172         return false;
173     }
174     
175     return true;
176 }
177
178 bool QuickTimePluginReplacement::installReplacement(ShadowRoot* root)
179 {
180     Page* page = m_parentElement->document().page();
181
182     if (!ensureReplacementScriptInjected())
183         return false;
184
185     DOMWrapperWorld& world = isolatedWorld();
186     ScriptController& scriptController = page->mainFrame().script();
187     JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world));
188     JSC::ExecState* exec = globalObject->globalExec();
189     JSC::JSLockHolder lock(exec);
190     
191     // Lookup the "createPluginReplacement" function.
192     JSC::JSValue replacementFunction = globalObject->get(exec, JSC::Identifier(exec, "createPluginReplacement"));
193     if (replacementFunction.isUndefinedOrNull())
194         return false;
195     JSC::JSObject* replacementObject = replacementFunction.toObject(exec);
196     JSC::CallData callData;
197     JSC::CallType callType = replacementObject->methodTable()->getCallData(replacementObject, callData);
198     if (callType == JSC::CallTypeNone)
199         return false;
200
201     JSC::MarkedArgumentBuffer argList;
202     argList.append(toJS(exec, globalObject, root));
203     argList.append(toJS(exec, globalObject, m_parentElement));
204     argList.append(toJS(exec, globalObject, this));
205     argList.append(toJS<String>(exec, globalObject, m_names));
206     argList.append(toJS<String>(exec, globalObject, m_values));
207     JSC::JSValue replacement = call(exec, replacementObject, callType, callData, globalObject, argList);
208     if (exec->hadException()) {
209         exec->clearException();
210         return false;
211     }
212
213     // Get the <video> created to replace the plug-in.
214     JSC::JSValue value = replacement.get(exec, JSC::Identifier(exec, "video"));
215     if (!exec->hadException() && !value.isUndefinedOrNull())
216         m_mediaElement = JSHTMLVideoElement::toWrapped(value);
217
218     if (!m_mediaElement) {
219         LOG(Plugins, "%p - Failed to find <video> element created by QuickTime plugin replacement script.", this);
220         exec->clearException();
221         return false;
222     }
223
224     // Get the scripting interface.
225     value = replacement.get(exec, JSC::Identifier(exec, "scriptObject"));
226     if (!exec->hadException() && !value.isUndefinedOrNull())
227         m_scriptObject = value.toObject(exec);
228
229     if (!m_scriptObject) {
230         LOG(Plugins, "%p - Failed to find script object created by QuickTime plugin replacement.", this);
231         exec->clearException();
232         return false;
233     }
234
235     return true;
236 }
237
238 unsigned long long QuickTimePluginReplacement::movieSize() const
239 {
240     if (m_mediaElement)
241         return m_mediaElement->fileSize();
242
243     return 0;
244 }
245
246 void QuickTimePluginReplacement::postEvent(const String& eventName)
247 {
248     Ref<HTMLPlugInElement> protect(*m_parentElement);
249     RefPtr<Event> event = Event::create(eventName, false, true);
250     m_parentElement->dispatchEvent(event.get());
251 }
252
253 #if PLATFORM(IOS)
254 static JSValue *jsValueWithDataInContext(NSData *data, const String& mimeType, JSContext *context)
255 {
256     Vector<char> base64Data;
257     base64Encode([data bytes], [data length], base64Data);
258
259     String data64;
260     if (!mimeType.isEmpty())
261         data64 = "data:" + mimeType + ";base64," + base64Data;
262     else
263         data64 = "data:text/plain;base64," + base64Data;
264
265     return [JSValue valueWithObject:(id)data64.createCFString().get() inContext:context];
266 }
267
268 static JSValue *jsValueWithArrayInContext(NSArray *array, JSContext *context)
269 {
270     JSValueRef exception = 0;
271     JSValue *result = [JSValue valueWithNewArrayInContext:context];
272     JSObjectRef resultObject = JSValueToObject([context JSGlobalContextRef], [result JSValueRef], &exception);
273     if (exception)
274         return [JSValue valueWithUndefinedInContext:context];
275
276     NSUInteger count = [array count];
277     for (NSUInteger i = 0; i < count; ++i) {
278         JSValue *value = jsValueWithValueInContext([array objectAtIndex:i], context);
279         if (!value)
280             continue;
281
282         JSObjectSetPropertyAtIndex([context JSGlobalContextRef], resultObject, (unsigned)i, [value JSValueRef], &exception);
283         if (exception)
284             continue;
285     }
286
287     return result;
288 }
289
290
291 static JSValue *jsValueWithDictionaryInContext(NSDictionary *dictionary, JSContext *context)
292 {
293     JSValueRef exception = 0;
294     JSValue *result = [JSValue valueWithNewObjectInContext:context];
295     JSObjectRef resultObject = JSValueToObject([context JSGlobalContextRef], [result JSValueRef], &exception);
296     if (exception)
297         return [JSValue valueWithUndefinedInContext:context];
298
299     for (id key in [dictionary keyEnumerator]) {
300         if (![key isKindOfClass:[NSString class]])
301             continue;
302
303         JSValue *value = jsValueWithValueInContext([dictionary objectForKey:key], context);
304         if (!value)
305             continue;
306
307         JSStringRef name = JSStringCreateWithCFString((CFStringRef)key);
308         JSObjectSetProperty([context JSGlobalContextRef], resultObject, name, [value JSValueRef], 0, &exception);
309         if (exception)
310             continue;
311     }
312
313     return result;
314 }
315
316 static JSValue *jsValueWithValueInContext(id value, JSContext *context)
317 {
318     if ([value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]])
319         return [JSValue valueWithObject:value inContext:context];
320     else if ([value isKindOfClass:[NSLocale class]])
321         return [JSValue valueWithObject:[value localeIdentifier] inContext:context];
322     else if ([value isKindOfClass:[NSDictionary class]])
323         return jsValueWithDictionaryInContext(value, context);
324     else if ([value isKindOfClass:[NSArray class]])
325         return jsValueWithArrayInContext(value, context);
326     else if ([value isKindOfClass:[NSData class]])
327         return jsValueWithDataInContext(value, emptyString(), context);
328     else if ([value isKindOfClass:[AVMetadataItem class]])
329         return jsValueWithAVMetadataItemInContext(value, context);
330
331     return nil;
332 }
333
334 static JSValue *jsValueWithAVMetadataItemInContext(AVMetadataItemType *item, JSContext *context)
335 {
336     NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithDictionary:[item extraAttributes]];
337
338     if (item.keySpace)
339         [dictionary setObject:item.keySpace forKey:@"keyspace"];
340
341     if (item.key)
342         [dictionary setObject:item.key forKey:@"key"];
343
344     if (item.locale)
345         [dictionary setObject:item.locale forKey:@"locale"];
346
347     if (CMTIME_IS_VALID(item.time)) {
348         CFDictionaryRef timeDict = CMTimeCopyAsDictionary(item.time, kCFAllocatorDefault);
349
350         if (timeDict) {
351             [dictionary setObject:(id)timeDict forKey:@"timestamp"];
352             CFRelease(timeDict);
353         }
354     }
355     
356     if (item.value) {
357         id value = item.value;
358         NSString *mimeType = [[item extraAttributes] objectForKey:@"MIMEtype"];
359         if ([value isKindOfClass:[NSData class]] && mimeType) {
360             Vector<char> base64Data;
361             base64Encode([value bytes], [value length], base64Data);
362             String data64 = "data:" + String(mimeType) + ";base64," + base64Data;
363             [dictionary setObject:(id)data64.createCFString().get() forKey:@"value"];
364         } else
365             [dictionary setObject:value forKey:@"value"];
366     }
367
368     return jsValueWithDictionaryInContext(dictionary, context);
369 }
370 #endif
371
372 JSC::JSValue JSQuickTimePluginReplacement::timedMetaData(JSC::ExecState* exec) const
373 {
374 #if PLATFORM(IOS)
375     HTMLVideoElement* parent = impl().parentElement();
376     if (!parent || !parent->player())
377         return JSC::jsNull();
378
379     Frame* frame = parent->document().frame();
380     if (!frame)
381         return JSC::jsNull();
382
383     NSArray *metaData = parent->player()->timedMetadata();
384     if (!metaData)
385         return JSC::jsNull();
386
387     JSContext *jsContext = frame->script().javaScriptContext();
388     JSValue *metaDataValue = jsValueWithValueInContext(metaData, jsContext);
389     
390     return toJS(exec, [metaDataValue JSValueRef]);
391 #else
392     UNUSED_PARAM(exec);
393     return JSC::jsNull();
394 #endif
395 }
396
397 JSC::JSValue JSQuickTimePluginReplacement::accessLog(JSC::ExecState* exec) const
398 {
399 #if PLATFORM(IOS)
400     HTMLVideoElement* parent = impl().parentElement();
401     if (!parent || !parent->player())
402         return JSC::jsNull();
403
404     Frame* frame = parent->document().frame();
405     if (!frame)
406         return JSC::jsNull();
407
408     JSValue *dictionary = [JSValue valueWithNewObjectInContext:frame->script().javaScriptContext()];
409     String accessLogString = parent->player()->accessLog();
410     [dictionary setValue:static_cast<NSString *>(accessLogString) forProperty:(NSString *)CFSTR("extendedLog")];
411
412     return toJS(exec, [dictionary JSValueRef]);
413 #else
414     UNUSED_PARAM(exec);
415     return JSC::jsNull();
416 #endif
417 }
418
419 JSC::JSValue JSQuickTimePluginReplacement::errorLog(JSC::ExecState* exec) const
420 {
421 #if PLATFORM(IOS)
422     HTMLVideoElement* parent = impl().parentElement();
423     if (!parent || !parent->player())
424         return JSC::jsNull();
425
426     Frame* frame = parent->document().frame();
427     if (!frame)
428         return JSC::jsNull();
429
430     JSValue *dictionary = [JSValue valueWithNewObjectInContext:frame->script().javaScriptContext()];
431     String errorLogString = parent->player()->errorLog();
432     [dictionary setValue:static_cast<NSString *>(errorLogString) forProperty:(NSString *)CFSTR("extendedLog")];
433
434     return toJS(exec, [dictionary JSValueRef]);
435 #else
436     UNUSED_PARAM(exec);
437     return JSC::jsNull();
438 #endif
439 }
440
441 }
442
443 #endif