summaryrefslogtreecommitdiffstats
path: root/WebCore/plugins/android/PluginPackageAndroid.cpp
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-01-15 16:12:09 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-01-15 16:12:09 -0800
commit7a355dabbffb876b2e08cf63ac4fc28a39c19c6a (patch)
tree9e94e34b23ead3c0e7f052ee2d3785404d349981 /WebCore/plugins/android/PluginPackageAndroid.cpp
parente933faefa1e899dbd5bf371f499cc682aff46c83 (diff)
downloadexternal_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.cpp582
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