/* * Copyright (C) 2013-2017 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #if ENABLE(MEDIA_CONTROLS_SCRIPT) #import "QuickTimePluginReplacement.h" #import "CommonVM.h" #import "Event.h" #import "HTMLPlugInElement.h" #import "HTMLVideoElement.h" #import "JSDOMBinding.h" #import "JSDOMConvertNullable.h" #import "JSDOMConvertSequences.h" #import "JSDOMConvertStrings.h" #import "JSDOMGlobalObject.h" #import "JSHTMLVideoElement.h" #import "JSQuickTimePluginReplacement.h" #import "Logging.h" #import "MainFrame.h" #import "RenderElement.h" #import "ScriptController.h" #import "ScriptSourceCode.h" #import "Settings.h" #import "ShadowRoot.h" #import "UserAgentScripts.h" #import #import #import #import #import #import #import #import "CoreMediaSoftLink.h" typedef AVMetadataItem AVMetadataItemType; SOFT_LINK_FRAMEWORK_OPTIONAL(AVFoundation) SOFT_LINK_CLASS(AVFoundation, AVMetadataItem) #define AVMetadataItem getAVMetadataItemClass() namespace WebCore { #if PLATFORM(IOS) static JSValue *jsValueWithValueInContext(id, JSContext *); static JSValue *jsValueWithAVMetadataItemInContext(AVMetadataItemType *, JSContext *); #endif static String quickTimePluginReplacementScript() { static NeverDestroyed script(QuickTimePluginReplacementJavaScript, sizeof(QuickTimePluginReplacementJavaScript)); return script; } void QuickTimePluginReplacement::registerPluginReplacement(PluginReplacementRegistrar registrar) { registrar(ReplacementPlugin(create, supportsMimeType, supportsFileExtension, supportsURL, isEnabledBySettings)); } Ref QuickTimePluginReplacement::create(HTMLPlugInElement& plugin, const Vector& paramNames, const Vector& paramValues) { return adoptRef(*new QuickTimePluginReplacement(plugin, paramNames, paramValues)); } bool QuickTimePluginReplacement::supportsMimeType(const String& mimeType) { static NeverDestroyed> typeHash = []() { static const char* const types[] = { "application/vnd.apple.mpegurl", "application/x-mpegurl", "audio/3gpp", "audio/3gpp2", "audio/aac", "audio/aiff", "audio/amr", "audio/basic", "audio/mp3", "audio/mp4", "audio/mpeg", "audio/mpeg3", "audio/mpegurl", "audio/scpls", "audio/wav", "audio/x-aac", "audio/x-aiff", "audio/x-caf", "audio/x-m4a", "audio/x-m4b", "audio/x-m4p", "audio/x-m4r", "audio/x-mp3", "audio/x-mpeg", "audio/x-mpeg3", "audio/x-mpegurl", "audio/x-scpls", "audio/x-wav", "video/3gpp", "video/3gpp2", "video/mp4", "video/quicktime", "video/x-m4v" }; HashSet set; for (auto& type : types) set.add(type); return set; }(); return typeHash.get().contains(mimeType); } bool QuickTimePluginReplacement::supportsFileExtension(const String& extension) { static NeverDestroyed> extensionSet = []() { static const char* const extensions[] = { "3g2", "3gp", "3gp2", "3gpp", "aac", "adts", "aif", "aifc", "aiff", "AMR", "au", "bwf", "caf", "cdda", "m3u", "m3u8", "m4a", "m4b", "m4p", "m4r", "m4v", "mov", "mp3", "mp3", "mp4", "mpeg", "mpg", "mqv", "pls", "qt", "snd", "swa", "ts", "ulw", "wav" }; HashSet set; for (auto& extension : extensions) set.add(extension); return set; }(); return extensionSet.get().contains(extension); } bool QuickTimePluginReplacement::isEnabledBySettings(const Settings& settings) { return settings.quickTimePluginReplacementEnabled(); } QuickTimePluginReplacement::QuickTimePluginReplacement(HTMLPlugInElement& plugin, const Vector& paramNames, const Vector& paramValues) :PluginReplacement() , m_parentElement(&plugin) , m_names(paramNames) , m_values(paramValues) , m_scriptObject(nullptr) { } QuickTimePluginReplacement::~QuickTimePluginReplacement() { m_parentElement = nullptr; m_scriptObject = nullptr; m_mediaElement = nullptr; } RenderPtr QuickTimePluginReplacement::createElementRenderer(HTMLPlugInElement& plugin, RenderStyle&& style, const RenderTreePosition& insertionPosition) { ASSERT_UNUSED(plugin, m_parentElement == &plugin); if (m_mediaElement) return m_mediaElement->createElementRenderer(WTFMove(style), insertionPosition); return nullptr; } DOMWrapperWorld& QuickTimePluginReplacement::isolatedWorld() { static DOMWrapperWorld& isolatedWorld = DOMWrapperWorld::create(commonVM()).leakRef(); return isolatedWorld; } bool QuickTimePluginReplacement::ensureReplacementScriptInjected() { if (!m_parentElement->document().frame()) return false; DOMWrapperWorld& world = isolatedWorld(); ScriptController& scriptController = m_parentElement->document().frame()->script(); JSDOMGlobalObject* globalObject = JSC::jsCast(scriptController.globalObject(world)); JSC::VM& vm = globalObject->vm(); JSC::JSLockHolder lock(vm); auto scope = DECLARE_CATCH_SCOPE(vm); JSC::ExecState* exec = globalObject->globalExec(); JSC::JSValue replacementFunction = globalObject->get(exec, JSC::Identifier::fromString(exec, "createPluginReplacement")); if (replacementFunction.isFunction()) return true; scriptController.evaluateInWorld(ScriptSourceCode(quickTimePluginReplacementScript()), world); if (UNLIKELY(scope.exception())) { LOG(Plugins, "%p - Exception when evaluating QuickTime plugin replacement script", this); scope.clearException(); return false; } return true; } bool QuickTimePluginReplacement::installReplacement(ShadowRoot& root) { if (!ensureReplacementScriptInjected()) return false; if (!m_parentElement->document().frame()) return false; DOMWrapperWorld& world = isolatedWorld(); ScriptController& scriptController = m_parentElement->document().frame()->script(); JSDOMGlobalObject* globalObject = JSC::jsCast(scriptController.globalObject(world)); JSC::VM& vm = globalObject->vm(); JSC::JSLockHolder lock(vm); auto scope = DECLARE_CATCH_SCOPE(vm); JSC::ExecState* exec = globalObject->globalExec(); // Lookup the "createPluginReplacement" function. JSC::JSValue replacementFunction = globalObject->get(exec, JSC::Identifier::fromString(exec, "createPluginReplacement")); if (replacementFunction.isUndefinedOrNull()) return false; JSC::JSObject* replacementObject = replacementFunction.toObject(exec); scope.assertNoException(); JSC::CallData callData; JSC::CallType callType = replacementObject->methodTable()->getCallData(replacementObject, callData); if (callType == JSC::CallType::None) return false; JSC::MarkedArgumentBuffer argList; argList.append(toJS(exec, globalObject, &root)); argList.append(toJS(exec, globalObject, m_parentElement)); argList.append(toJS(exec, globalObject, this)); argList.append(toJS>>(*exec, *globalObject, m_names)); argList.append(toJS>>(*exec, *globalObject, m_values)); JSC::JSValue replacement = call(exec, replacementObject, callType, callData, globalObject, argList); if (UNLIKELY(scope.exception())) { scope.clearException(); return false; } // Get the