diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-01-15 16:12:09 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-01-15 16:12:09 -0800 |
commit | 7a355dabbffb876b2e08cf63ac4fc28a39c19c6a (patch) | |
tree | 9e94e34b23ead3c0e7f052ee2d3785404d349981 /WebCore/plugins/android/PluginPackageAndroid.cpp | |
parent | e933faefa1e899dbd5bf371f499cc682aff46c83 (diff) | |
download | external_webkit-7a355dabbffb876b2e08cf63ac4fc28a39c19c6a.zip external_webkit-7a355dabbffb876b2e08cf63ac4fc28a39c19c6a.tar.gz external_webkit-7a355dabbffb876b2e08cf63ac4fc28a39c19c6a.tar.bz2 |
auto import from //branches/cupcake/...@126645
Diffstat (limited to 'WebCore/plugins/android/PluginPackageAndroid.cpp')
-rw-r--r-- | WebCore/plugins/android/PluginPackageAndroid.cpp | 582 |
1 files changed, 582 insertions, 0 deletions
diff --git a/WebCore/plugins/android/PluginPackageAndroid.cpp b/WebCore/plugins/android/PluginPackageAndroid.cpp new file mode 100644 index 0000000..aac687c --- /dev/null +++ b/WebCore/plugins/android/PluginPackageAndroid.cpp @@ -0,0 +1,582 @@ +/* + * Copyright 2009, The Android Open Source Project + * Copyright (C) 2006, 2007 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 COMPUTER, 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 COMPUTER, 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. + */ + +#ifdef ANDROID_PLUGINS + +#define LOG_TAG "WebKit" + +#include "config.h" +#include "PluginDatabase.h" +#include "PluginPackage.h" + +#include "Timer.h" +#include "PlatformString.h" +#include "PluginMainThreadScheduler.h" +#include "CString.h" +#include "jni_utility.h" +#include "npruntime_impl.h" +#include "npfunctions.h" +#include <dlfcn.h> +#include <errno.h> + +#include "PluginDebug.h" +#include "PluginDebugAndroid.h" + +namespace WebCore { + +// Simple class which calls dlclose() on a dynamic library when going +// out of scope. Call ok() if the handle should stay open. +class DynamicLibraryCloser +{ + public: + DynamicLibraryCloser(PlatformModule *module) : m_module(module) { } + ~DynamicLibraryCloser() + { + // Close the library if non-NULL reference and open. + if (m_module && *m_module) + { + dlclose(*m_module); + *m_module = 0; + } + } + void ok() { m_module = NULL; } + + private: + PlatformModule *m_module; +}; + +// A container for a dummy npp instance. This is used to allow +// NPN_PluginThreadAsyncCall() to be used with NULL passed as the npp +// instance. This is substituted instead, and is shared between all +// plugins which behave in this way. This will be lazily created in +// the first call to NPN_PluginThreadAsyncCall(). +class DummyNpp { + public: + DummyNpp() { + m_npp = new NPP_t(); + m_npp->pdata = NULL; + m_npp->ndata = NULL; + PluginMainThreadScheduler::scheduler().registerPlugin(m_npp); + } + ~DummyNpp() { + PluginMainThreadScheduler::scheduler().unregisterPlugin(m_npp); + delete m_npp; + } + NPP_t *getInstance() { return m_npp; } + + private: + NPP_t *m_npp; +}; + +static bool getEntryPoint(PlatformModule module, + const char *name, + void **entry_point) +{ + dlerror(); + *entry_point = dlsym(module, name); + const char *error = dlerror(); + if(error == NULL && *entry_point != NULL) { + return true; + } else { + PLUGIN_LOG("Couldn't get entry point \"%s\": %s\n", + name, error); + return false; + } +} + +int PluginPackage::compareFileVersion( + const PlatformModuleVersion& compareVersion) const +{ + // return -1, 0, or 1 if plug-in version is less than, equal to, + // or greater than the passed version + if (m_moduleVersion != compareVersion) + return m_moduleVersion > compareVersion ? 1 : -1; + else + return 0; +} + +bool PluginPackage::isPluginBlacklisted() +{ + // No blacklisted Android plugins... yet! + return false; +} + +void PluginPackage::determineQuirks(const String& mimeType) +{ + // The Gears implementation relies on it being loaded *all the time*, + // so check to see if this package represents the Gears plugin and + // load it. + if (mimeType == "application/x-googlegears") { + m_quirks.add(PluginQuirkDontUnloadPlugin); + } +} + +static void Android_NPN_PluginThreadAsyncCall(NPP instance, + void (*func) (void *), + void *userData) +{ + // Translate all instance == NULL to a dummy actual npp. + static DummyNpp dummyNpp; + if (instance == NULL) { + instance = dummyNpp.getInstance(); + } + // Call through to the wrapped function. + NPN_PluginThreadAsyncCall(instance, func, userData); +} + +static void initializeBrowserFuncs(NPNetscapeFuncs *funcs) +{ + // Initialize the NPN function pointers that we hand over to the + // plugin. + memset(funcs, 0, sizeof(*funcs)); + + funcs->size = sizeof(*funcs); + funcs->version = NP_VERSION_MINOR; + funcs->geturl = NPN_GetURL; + funcs->posturl = NPN_PostURL; + funcs->requestread = NPN_RequestRead; + funcs->newstream = NPN_NewStream; + funcs->write = NPN_Write; + funcs->destroystream = NPN_DestroyStream; + funcs->status = NPN_Status; + funcs->uagent = NPN_UserAgent; + funcs->memalloc = NPN_MemAlloc; + funcs->memfree = NPN_MemFree; + funcs->memflush = NPN_MemFlush; + funcs->reloadplugins = NPN_ReloadPlugins; + funcs->geturlnotify = NPN_GetURLNotify; + funcs->posturlnotify = NPN_PostURLNotify; + funcs->getvalue = NPN_GetValue; + funcs->setvalue = NPN_SetValue; + funcs->invalidaterect = NPN_InvalidateRect; + funcs->invalidateregion = NPN_InvalidateRegion; + funcs->forceredraw = NPN_ForceRedraw; + funcs->getJavaEnv = NPN_GetJavaEnv; + funcs->getJavaPeer = NPN_GetJavaPeer; + funcs->pushpopupsenabledstate = NPN_PushPopupsEnabledState; + funcs->poppopupsenabledstate = NPN_PopPopupsEnabledState; + funcs->pluginthreadasynccall = Android_NPN_PluginThreadAsyncCall; + funcs->scheduletimer = NPN_ScheduleTimer; + funcs->unscheduletimer = NPN_UnscheduleTimer; + + funcs->releasevariantvalue = _NPN_ReleaseVariantValue; + funcs->getstringidentifier = _NPN_GetStringIdentifier; + funcs->getstringidentifiers = _NPN_GetStringIdentifiers; + funcs->getintidentifier = _NPN_GetIntIdentifier; + funcs->identifierisstring = _NPN_IdentifierIsString; + funcs->utf8fromidentifier = _NPN_UTF8FromIdentifier; + funcs->intfromidentifier = _NPN_IntFromIdentifier; + funcs->createobject = _NPN_CreateObject; + funcs->retainobject = _NPN_RetainObject; + funcs->releaseobject = _NPN_ReleaseObject; + funcs->invoke = _NPN_Invoke; + funcs->invokeDefault = _NPN_InvokeDefault; + funcs->evaluate = _NPN_Evaluate; + funcs->getproperty = _NPN_GetProperty; + funcs->setproperty = _NPN_SetProperty; + funcs->removeproperty = _NPN_RemoveProperty; + funcs->hasproperty = _NPN_HasProperty; + funcs->hasmethod = _NPN_HasMethod; + funcs->setexception = _NPN_SetException; + funcs->enumerate = _NPN_Enumerate; +} + +static jobject createPluginObject(const char *name, + const char *path, + const char *fileName, + const char *description) +{ + JNIEnv *env = JSC::Bindings::getJNIEnv(); + // Create a Java "class Plugin" object instance + jclass pluginClass = env->FindClass("android/webkit/Plugin"); + if(!pluginClass) { + PLUGIN_LOG("Couldn't find class android.webkit.Plugin\n"); + return 0; + } + // Get Plugin(String, String, String, String, Context) + jmethodID pluginConstructor = env->GetMethodID( + pluginClass, + "<init>", + "(Ljava/lang/String;" + "Ljava/lang/String;" + "Ljava/lang/String;" + "Ljava/lang/String;)V"); + if(!pluginConstructor) { + PLUGIN_LOG("Couldn't get android.webkit.Plugin constructor\n"); + return 0; + } + // Make Java strings of name, path, fileName, description + jstring javaName = env->NewStringUTF(name); + jstring javaPath = env->NewStringUTF(path); + jstring javaFileName = env->NewStringUTF(fileName); + jstring javaDescription = env->NewStringUTF(description); + // Make a plugin instance + jobject pluginObject = env->NewObject(pluginClass, + pluginConstructor, + javaName, + javaPath, + javaFileName, + javaDescription); + return pluginObject; +} + +static jobject getPluginListObject() +{ + JNIEnv *env = JSC::Bindings::getJNIEnv(); + // Get WebView.getPluginList() + jclass webViewClass = env->FindClass("android/webkit/WebView"); + if(!webViewClass) { + PLUGIN_LOG("Couldn't find class android.webkit.WebView\n"); + return 0; + } + jmethodID getPluginList = env->GetStaticMethodID( + webViewClass, + "getPluginList", + "()Landroid/webkit/PluginList;"); + if(!getPluginList) { + PLUGIN_LOG("Couldn't find android.webkit.WebView.getPluginList()\n"); + return 0; + } + // Get the PluginList instance + jobject pluginListObject = env->CallStaticObjectMethod(webViewClass, + getPluginList); + if(!pluginListObject) { + PLUGIN_LOG("Couldn't get PluginList object\n"); + return 0; + } + return pluginListObject; +} + +static bool addPluginObjectToList(jobject pluginList, jobject plugin) +{ + // Add the Plugin object + JNIEnv *env = JSC::Bindings::getJNIEnv(); + jclass pluginListClass = env->FindClass("android/webkit/PluginList"); + if(!pluginListClass) { + PLUGIN_LOG("Couldn't find class android.webkit.PluginList\n"); + return false; + } + jmethodID addPlugin = env->GetMethodID( + pluginListClass, + "addPlugin", + "(Landroid/webkit/Plugin;)V"); + if(!addPlugin) { + PLUGIN_LOG("Couldn't find android.webkit.PluginList.addPlugin()\n"); + return false; + } + env->CallVoidMethod(pluginList, addPlugin, plugin); + return true; +} + +static void removePluginObjectFromList(jobject pluginList, jobject plugin) +{ + // Remove the Plugin object + JNIEnv *env = JSC::Bindings::getJNIEnv(); + jclass pluginListClass = env->FindClass("android/webkit/PluginList"); + if(!pluginListClass) { + PLUGIN_LOG("Couldn't find class android.webkit.PluginList\n"); + return; + } + jmethodID removePlugin = env->GetMethodID( + pluginListClass, + "removePlugin", + "(Landroid/webkit/Plugin;)V"); + if(!removePlugin) { + PLUGIN_LOG("Couldn't find android.webkit.PluginList.removePlugin()\n"); + return; + } + env->CallVoidMethod(pluginList, removePlugin, plugin); +} + +bool PluginPackage::load() +{ + PLUGIN_LOG("tid:%d isActive:%d isLoaded:%d loadCount:%d\n", + gettid(), + m_freeLibraryTimer.isActive(), + m_isLoaded, + m_loadCount); + if (m_freeLibraryTimer.isActive()) { + ASSERT(m_module); + m_freeLibraryTimer.stop(); + } else if (m_isLoaded) { + if (m_quirks.contains(PluginQuirkDontAllowMultipleInstances)) + return false; + m_loadCount++; + PLUGIN_LOG("Already loaded, count now %d\n", m_loadCount); + return true; + } + ASSERT(m_loadCount == 0); + ASSERT(m_module == NULL); + + PLUGIN_LOG("Loading \"%s\"\n", m_path.utf8().data()); + + // Open the library + void *handle = dlopen(m_path.utf8().data(), RTLD_NOW); + if(!handle) { + PLUGIN_LOG("Couldn't load plugin library \"%s\": %s\n", + m_path.utf8().data(), dlerror()); + return false; + } + m_module = handle; + PLUGIN_LOG("Fetch Info Loaded %p\n", m_module); + // This object will call dlclose() and set m_module to NULL + // when going out of scope. + DynamicLibraryCloser dlCloser(&m_module); + + + NP_InitializeFuncPtr NP_Initialize; + if(!getEntryPoint(m_module, "NP_Initialize", (void **) &NP_Initialize) || + !getEntryPoint(handle, "NP_Shutdown", (void **) &m_NPP_Shutdown)) { + PLUGIN_LOG("Couldn't find Initialize function\n"); + return false; + } + + // Provide the plugin with our browser function table and grab its + // plugin table. Provide the Java environment and the Plugin which + // can be used to override the defaults if the plugin wants. + initializeBrowserFuncs(&m_browserFuncs); + memset(&m_pluginFuncs, 0, sizeof(m_pluginFuncs)); + m_pluginFuncs.size = sizeof(m_pluginFuncs); + if(NP_Initialize(&m_browserFuncs, + &m_pluginFuncs, + JSC::Bindings::getJNIEnv(), + m_pluginObject) != NPERR_NO_ERROR) { + PLUGIN_LOG("Couldn't initialize plugin\n"); + return false; + } + + // Don't close the library - loaded OK. + dlCloser.ok(); + // Retain the handle so we can close it in the future. + m_module = handle; + m_isLoaded = true; + ++m_loadCount; + PLUGIN_LOG("Initial load ok, count now %d\n", m_loadCount); + return true; +} + +void PluginPackage::unregisterPluginObject() +{ + PLUGIN_LOG("unregisterPluginObject\n"); + // Called by unloadWithoutShutdown(). Remove the plugin from the + // PluginList + if(m_pluginObject) { + jobject pluginListObject = getPluginListObject(); + if(pluginListObject) { + removePluginObjectFromList(pluginListObject, m_pluginObject); + } + // Remove a reference to the Plugin object so it can + // garbage collect. + JSC::Bindings::getJNIEnv()->DeleteGlobalRef(m_pluginObject); + m_pluginObject = 0; + } +} + +bool PluginPackage::fetchInfo() +{ + PLUGIN_LOG("Fetch Info Loading \"%s\"\n", m_path.utf8().data()); + + // Open the library + void *handle = dlopen(m_path.utf8().data(), RTLD_NOW); + if(!handle) { + PLUGIN_LOG("Couldn't load plugin library \"%s\": %s\n", + m_path.utf8().data(), dlerror()); + return false; + } + PLUGIN_LOG("Fetch Info Loaded %p\n", handle); + + // This object will call dlclose() and set m_module to NULL + // when going out of scope. + DynamicLibraryCloser dlCloser(&handle); + + // Get the three entry points we need for Linux Netscape Plug-ins + NP_GetMIMEDescriptionFuncPtr NP_GetMIMEDescription; + NPP_GetValueProcPtr NP_GetValue; + if(!getEntryPoint(handle, "NP_GetMIMEDescription", + (void **) &NP_GetMIMEDescription) || + !getEntryPoint(handle, "NP_GetValue", (void **) &NP_GetValue)) { + // If any of those failed to resolve, fail the entire load + return false; + } + + // Get the plugin name and description using NP_GetValue + const char *name; + const char *description; + if(NP_GetValue(NULL, NPPVpluginNameString, &name) != NPERR_NO_ERROR || + NP_GetValue(NULL, NPPVpluginDescriptionString, &description) != + NPERR_NO_ERROR) { + PLUGIN_LOG("Couldn't get name/description using NP_GetValue\n"); + return false; + } + + PLUGIN_LOG("Plugin name: \"%s\"\n", name); + PLUGIN_LOG("Plugin description: \"%s\"\n", description); + m_name = name; + m_description = description; + + // fileName is just the trailing part of the path + int last_slash = m_path.reverseFind('/'); + if(last_slash < 0) + m_fileName = m_path; + else + m_fileName = m_path.substring(last_slash + 1); + + // Grab the MIME description. This is in the format, e.g: + // application/x-somescriptformat:ssf:Some Script Format + String mimeDescription(NP_GetMIMEDescription()); + PLUGIN_LOG("MIME description: \"%s\"\n", mimeDescription.utf8().data()); + // Clear out the current mappings. + m_mimeToDescriptions.clear(); + m_mimeToExtensions.clear(); + // Split the description into its component entries, separated by + // semicolons. + Vector<String> mimeEntries; + mimeDescription.split(';', true, mimeEntries); + // Iterate through the entries, adding them to the MIME mappings. + for(Vector<String>::const_iterator it = mimeEntries.begin(); + it != mimeEntries.end(); ++it) { + // Each part is split into 3 fields separated by colons + // Field 1 is the MIME type (e.g "application/x-shockwave-flash"). + // Field 2 is a comma separated list of file extensions. + // Field 3 is a human readable short description. + const String &mimeEntry = *it; + Vector<String> fields; + mimeEntry.split(':', true, fields); + if(fields.size() != 3) { + PLUGIN_LOG("Bad MIME entry \"%s\"\n", mimeEntry.utf8().data()); + return false; + } + + const String& mimeType = fields[0]; + Vector<String> extensions; + fields[1].split(',', true, extensions); + const String& description = fields[2]; + + determineQuirks(mimeType); + + PLUGIN_LOG("mime_type: \"%s\"\n", mimeType.utf8().data()); + PLUGIN_LOG("extensions: \"%s\"\n", fields[1].utf8().data()); + PLUGIN_LOG("description: \"%s\"\n", description.utf8().data()); + + // Map the mime type to the vector of extensions and the description + if(!extensions.isEmpty()) + m_mimeToExtensions.set(mimeType, extensions); + if(!description.isEmpty()) + m_mimeToDescriptions.set(mimeType, description); + } + + // Create a new Java Plugin object, this object is an instance of + // android.os.WebView.Plugin + CString path = m_path.utf8(); + CString filename = m_fileName.utf8(); + jobject pluginObject = createPluginObject(name, + path.data(), + filename.data(), + description); + if(!pluginObject) { + PLUGIN_LOG("Couldn't create Java Plugin\n"); + return false; + } + + // Add the Plugin to the PluginList. This list is used to show the + // user the list of plugins installed in the webkit. + + // The list of plugins are also available from the global static + // function PluginDatabase::installedPlugins(). However, the method + // on WebView to get the plugin list is a static method, and runs in the + // UI thread. We can not easily drop all the GlobalRefs this implementation + // has and switch to just calling through JNI to aforementioned API as + // WebKit runs in another thread and the WebView call would need to change + // to being async. + jobject pluginListObject = getPluginListObject(); + if(!pluginListObject) { + PLUGIN_LOG("Couldn't get PluginList object\n"); + return false; + } + if(!addPluginObjectToList(pluginListObject, pluginObject)) { + PLUGIN_LOG("Couldn't add Plugin to PluginList\n"); + m_NPP_Shutdown(); + return false; + } + + // Retain the Java Plugin object + m_pluginObject = JSC::Bindings::getJNIEnv()->NewGlobalRef(pluginObject); + + PLUGIN_LOG("Fetch Info Loaded plugin details ok \"%s\"\n", + m_path.utf8().data()); + + // If this plugin needs to be kept in memory, unload the module now + // and load it permanently. + if (m_quirks.contains(PluginQuirkDontUnloadPlugin)) { + dlCloser.ok(); + dlclose(handle); + load(); + } + + // dlCloser will unload the plugin if required. + return true; +} + +unsigned PluginPackage::hash() const +{ + const unsigned hashCodes[] = { + m_name.impl()->hash(), + m_description.impl()->hash(), + m_mimeToExtensions.size(), + }; + + return StringImpl::computeHash(reinterpret_cast<const UChar*>(hashCodes), + sizeof(hashCodes) / sizeof(UChar)); +} + +bool PluginPackage::equal(const PluginPackage& a, const PluginPackage& b) +{ + if (a.m_name != b.m_name) + return false; + + if (a.m_description != b.m_description) + return false; + + if (a.m_mimeToExtensions.size() != b.m_mimeToExtensions.size()) + return false; + + MIMEToExtensionsMap::const_iterator::Keys end = + a.m_mimeToExtensions.end().keys(); + for (MIMEToExtensionsMap::const_iterator::Keys it = + a.m_mimeToExtensions.begin().keys(); + it != end; + ++it) { + if (!b.m_mimeToExtensions.contains(*it)) { + return false; + } + } + + return true; +} + +} // namespace WebCore + +#endif |