| // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "webkit/glue/plugins/plugin_lib.h" |
| |
| #include "base/logging.h" |
| #include "base/message_loop.h" |
| #include "base/metrics/stats_counters.h" |
| #include "base/string_util.h" |
| #include "webkit/glue/webkit_glue.h" |
| #include "webkit/glue/plugins/plugin_instance.h" |
| #include "webkit/glue/plugins/plugin_host.h" |
| #include "webkit/glue/plugins/plugin_list.h" |
| |
| namespace NPAPI { |
| |
| const char kPluginLibrariesLoadedCounter[] = "PluginLibrariesLoaded"; |
| const char kPluginInstancesActiveCounter[] = "PluginInstancesActive"; |
| |
| // A list of all the instantiated plugins. |
| static std::vector<scoped_refptr<PluginLib> >* g_loaded_libs; |
| |
| PluginLib* PluginLib::CreatePluginLib(const FilePath& filename) { |
| // We can only have one PluginLib object per plugin as it controls the per |
| // instance function calls (i.e. NP_Initialize and NP_Shutdown). So we keep |
| // a map of PluginLib objects. |
| if (!g_loaded_libs) |
| g_loaded_libs = new std::vector<scoped_refptr<PluginLib> >; |
| |
| for (size_t i = 0; i < g_loaded_libs->size(); ++i) { |
| if ((*g_loaded_libs)[i]->plugin_info().path == filename) |
| return (*g_loaded_libs)[i]; |
| } |
| |
| WebPluginInfo info; |
| const PluginEntryPoints* entry_points = NULL; |
| if (!PluginList::Singleton()->ReadPluginInfo(filename, &info, &entry_points)) |
| return NULL; |
| |
| return new PluginLib(info, entry_points); |
| } |
| |
| void PluginLib::UnloadAllPlugins() { |
| if (g_loaded_libs) { |
| // PluginLib::Unload() can remove items from the list and even delete |
| // the list when it removes the last item, so we must work with a copy |
| // of the list so that we don't get the carpet removed under our feet. |
| std::vector<scoped_refptr<PluginLib> > loaded_libs(*g_loaded_libs); |
| for (size_t i = 0; i < loaded_libs.size(); ++i) |
| loaded_libs[i]->Unload(); |
| |
| if (g_loaded_libs && g_loaded_libs->empty()) { |
| delete g_loaded_libs; |
| g_loaded_libs = NULL; |
| } |
| } |
| } |
| |
| void PluginLib::ShutdownAllPlugins() { |
| if (g_loaded_libs) { |
| for (size_t i = 0; i < g_loaded_libs->size(); ++i) |
| (*g_loaded_libs)[i]->Shutdown(); |
| } |
| } |
| |
| PluginLib::PluginLib(const WebPluginInfo& info, |
| const PluginEntryPoints* entry_points) |
| : web_plugin_info_(info), |
| library_(NULL), |
| initialized_(false), |
| saved_data_(0), |
| instance_count_(0), |
| skip_unload_(false) { |
| base::StatsCounter(kPluginLibrariesLoadedCounter).Increment(); |
| memset(static_cast<void*>(&plugin_funcs_), 0, sizeof(plugin_funcs_)); |
| g_loaded_libs->push_back(make_scoped_refptr(this)); |
| |
| if (entry_points) { |
| internal_ = true; |
| entry_points_ = *entry_points; |
| } else { |
| internal_ = false; |
| // We will read the entry points from the plugin directly. |
| memset(&entry_points_, 0, sizeof(entry_points_)); |
| } |
| } |
| |
| PluginLib::~PluginLib() { |
| base::StatsCounter(kPluginLibrariesLoadedCounter).Decrement(); |
| if (saved_data_ != 0) { |
| // TODO - delete the savedData object here |
| } |
| } |
| |
| NPPluginFuncs* PluginLib::functions() { |
| return &plugin_funcs_; |
| } |
| |
| NPError PluginLib::NP_Initialize() { |
| LOG_IF(ERROR, PluginList::DebugPluginLoading()) |
| << "PluginLib::NP_Initialize(" << web_plugin_info_.path.value() |
| << "): initialized=" << initialized_; |
| if (initialized_) |
| return NPERR_NO_ERROR; |
| |
| if (!Load()) |
| return NPERR_MODULE_LOAD_FAILED_ERROR; |
| |
| PluginHost* host = PluginHost::Singleton(); |
| if (host == 0) |
| return NPERR_GENERIC_ERROR; |
| |
| #if defined(OS_POSIX) && !defined(OS_MACOSX) |
| NPError rv = entry_points_.np_initialize(host->host_functions(), |
| &plugin_funcs_); |
| #else |
| NPError rv = entry_points_.np_initialize(host->host_functions()); |
| #if defined(OS_MACOSX) |
| // On the Mac, we need to get entry points after calling np_initialize to |
| // match the behavior of other browsers. |
| if (rv == NPERR_NO_ERROR) { |
| rv = entry_points_.np_getentrypoints(&plugin_funcs_); |
| } |
| #endif // OS_MACOSX |
| #endif |
| LOG_IF(ERROR, PluginList::DebugPluginLoading()) |
| << "PluginLib::NP_Initialize(" << web_plugin_info_.path.value() |
| << "): result=" << rv; |
| initialized_ = (rv == NPERR_NO_ERROR); |
| return rv; |
| } |
| |
| void PluginLib::NP_Shutdown(void) { |
| DCHECK(initialized_); |
| entry_points_.np_shutdown(); |
| } |
| |
| void PluginLib::PreventLibraryUnload() { |
| skip_unload_ = true; |
| } |
| |
| PluginInstance* PluginLib::CreateInstance(const std::string& mime_type) { |
| PluginInstance* new_instance = new PluginInstance(this, mime_type); |
| instance_count_++; |
| base::StatsCounter(kPluginInstancesActiveCounter).Increment(); |
| DCHECK_NE(static_cast<PluginInstance*>(NULL), new_instance); |
| return new_instance; |
| } |
| |
| void PluginLib::CloseInstance() { |
| base::StatsCounter(kPluginInstancesActiveCounter).Decrement(); |
| instance_count_--; |
| // If a plugin is running in its own process it will get unloaded on process |
| // shutdown. |
| if ((instance_count_ == 0) && webkit_glue::IsPluginRunningInRendererProcess()) |
| Unload(); |
| } |
| |
| bool PluginLib::Load() { |
| if (library_) |
| return true; |
| |
| bool rv = false; |
| base::NativeLibrary library = 0; |
| |
| if (!internal_) { |
| #if defined(OS_WIN) |
| // This is to work around a bug in the Real player recorder plugin which |
| // intercepts LoadLibrary calls from chrome.dll and wraps NPAPI functions |
| // provided by the plugin. It crashes if the media player plugin is being |
| // loaded. Workaround is to load the dll dynamically by getting the |
| // LoadLibrary API address from kernel32.dll which bypasses the recorder |
| // plugin. |
| if (web_plugin_info_.name.find(L"Windows Media Player") != |
| std::wstring::npos) { |
| library = base::LoadNativeLibraryDynamically(web_plugin_info_.path); |
| } else { |
| library = base::LoadNativeLibrary(web_plugin_info_.path); |
| } |
| #else // OS_WIN |
| library = base::LoadNativeLibrary(web_plugin_info_.path); |
| #endif // OS_WIN |
| if (library == 0) { |
| LOG_IF(ERROR, PluginList::DebugPluginLoading()) |
| << "Couldn't load plugin " << web_plugin_info_.path.value(); |
| return rv; |
| } |
| |
| #if defined(OS_MACOSX) |
| // According to the WebKit source, QuickTime at least requires us to call |
| // UseResFile on the plugin resources before loading. |
| if (library->bundle_resource_ref != -1) |
| UseResFile(library->bundle_resource_ref); |
| #endif |
| |
| rv = true; // assume success now |
| |
| entry_points_.np_initialize = |
| (NP_InitializeFunc)base::GetFunctionPointerFromNativeLibrary(library, |
| "NP_Initialize"); |
| if (entry_points_.np_initialize == 0) |
| rv = false; |
| |
| #if defined(OS_WIN) || defined(OS_MACOSX) |
| entry_points_.np_getentrypoints = |
| (NP_GetEntryPointsFunc)base::GetFunctionPointerFromNativeLibrary( |
| library, "NP_GetEntryPoints"); |
| if (entry_points_.np_getentrypoints == 0) |
| rv = false; |
| #endif |
| |
| entry_points_.np_shutdown = |
| (NP_ShutdownFunc)base::GetFunctionPointerFromNativeLibrary(library, |
| "NP_Shutdown"); |
| if (entry_points_.np_shutdown == 0) |
| rv = false; |
| } else { |
| rv = true; |
| } |
| |
| if (rv) { |
| plugin_funcs_.size = sizeof(plugin_funcs_); |
| plugin_funcs_.version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; |
| #if !defined(OS_POSIX) |
| if (entry_points_.np_getentrypoints(&plugin_funcs_) != NPERR_NO_ERROR) |
| rv = false; |
| #else |
| // On Linux and Mac, we get the plugin entry points during NP_Initialize. |
| #endif |
| } |
| |
| if (!internal_) { |
| if (rv) { |
| LOG_IF(ERROR, PluginList::DebugPluginLoading()) |
| << "Plugin " << web_plugin_info_.path.value() |
| << " loaded successfully."; |
| library_ = library; |
| } else { |
| LOG_IF(ERROR, PluginList::DebugPluginLoading()) |
| << "Plugin " << web_plugin_info_.path.value() |
| << " failed to load, unloading."; |
| base::UnloadNativeLibrary(library); |
| } |
| } |
| |
| return rv; |
| } |
| |
| // This class implements delayed NP_Shutdown and FreeLibrary on the plugin dll. |
| class FreePluginLibraryTask : public Task { |
| public: |
| FreePluginLibraryTask(const FilePath& path, |
| base::NativeLibrary library, |
| NP_ShutdownFunc shutdown_func) |
| : path_(path), |
| library_(library), |
| NP_Shutdown_(shutdown_func) { |
| } |
| |
| ~FreePluginLibraryTask() {} |
| |
| void Run() { |
| if (NP_Shutdown_) { |
| // Don't call NP_Shutdown if the library has been reloaded since this task |
| // was posted. |
| bool reloaded = false; |
| if (g_loaded_libs) { |
| for (size_t i = 0; i < g_loaded_libs->size(); ++i) { |
| if ((*g_loaded_libs)[i]->plugin_info().path == path_) |
| reloaded = true; |
| } |
| } |
| if (!reloaded) |
| NP_Shutdown_(); |
| } |
| |
| if (library_) { |
| // Always call base::UnloadNativeLibrary so that the system reference |
| // count is decremented. |
| base::UnloadNativeLibrary(library_); |
| library_ = NULL; |
| } |
| } |
| |
| private: |
| FilePath path_; |
| base::NativeLibrary library_; |
| NP_ShutdownFunc NP_Shutdown_; |
| DISALLOW_COPY_AND_ASSIGN(FreePluginLibraryTask); |
| }; |
| |
| void PluginLib::Unload() { |
| if (!internal_ && library_) { |
| // In case of single process mode, a plugin can delete itself |
| // by executing a script. So delay the unloading of the library |
| // so that the plugin will have a chance to unwind. |
| bool defer_unload = webkit_glue::IsPluginRunningInRendererProcess(); |
| |
| /* TODO(dglazkov): Revisit when re-enabling the JSC build. |
| #if USE(JSC) |
| // The plugin NPAPI instances may still be around. Delay the |
| // NP_Shutdown and FreeLibrary calls at least till the next |
| // peek message. |
| defer_unload = true; |
| #endif |
| */ |
| |
| if (defer_unload) { |
| FreePluginLibraryTask* free_library_task = |
| new FreePluginLibraryTask(web_plugin_info_.path, |
| skip_unload_ ? NULL : library_, |
| entry_points_.np_shutdown); |
| LOG_IF(ERROR, PluginList::DebugPluginLoading()) |
| << "Scheduling delayed unload for plugin " |
| << web_plugin_info_.path.value(); |
| MessageLoop::current()->PostTask(FROM_HERE, free_library_task); |
| } else { |
| Shutdown(); |
| if (!skip_unload_) { |
| LOG_IF(ERROR, PluginList::DebugPluginLoading()) |
| << "Unloading plugin " << web_plugin_info_.path.value(); |
| base::UnloadNativeLibrary(library_); |
| } |
| } |
| |
| library_ = NULL; |
| } |
| |
| for (size_t i = 0; i < g_loaded_libs->size(); ++i) { |
| if ((*g_loaded_libs)[i].get() == this) { |
| g_loaded_libs->erase(g_loaded_libs->begin() + i); |
| break; |
| } |
| } |
| if (g_loaded_libs->empty()) { |
| delete g_loaded_libs; |
| g_loaded_libs = NULL; |
| } |
| } |
| |
| void PluginLib::Shutdown() { |
| if (initialized_ && !internal_) { |
| NP_Shutdown(); |
| initialized_ = false; |
| } |
| } |
| |
| } // namespace NPAPI |