// Copyright (c) 2011 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 "content/child/npapi/plugin_lib.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/stats_counters.h"
#include "base/strings/string_util.h"
#include "content/child/npapi/plugin_host.h"
#include "content/child/npapi/plugin_instance.h"
#include "content/common/plugin_list.h"
namespace content {
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 base::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].get();
}
WebPluginInfo info;
if (!PluginList::Singleton()->ReadPluginInfo(filename, &info))
return NULL;
return new PluginLib(info);
}
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)
: web_plugin_info_(info),
library_(NULL),
initialized_(false),
saved_data_(0),
instance_count_(0),
skip_unload_(false),
defer_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));
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();
}
NPError PluginLib::NP_ClearSiteData(const char* site,
uint64 flags,
uint64 max_age) {
DCHECK(initialized_);
if (plugin_funcs_.clearsitedata)
return plugin_funcs_.clearsitedata(site, flags, max_age);
return NPERR_INVALID_FUNCTABLE_ERROR;
}
char** PluginLib::NP_GetSitesWithData() {
DCHECK(initialized_);
if (plugin_funcs_.getsiteswithdata)
return plugin_funcs_.getsiteswithdata();
return NULL;
}
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) && !defer_unload_)
Unload();
}
bool PluginLib::Load() {
if (library_)
return true;
bool rv = false;
base::NativeLibrary library = 0;
base::NativeLibraryLoadError error;
#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, &error);
}
#else
library = base::LoadNativeLibrary(web_plugin_info_.path, &error);
#endif
if (!library) {
LOG_IF(ERROR, PluginList::DebugPluginLoading())
<< "Couldn't load plugin " << web_plugin_info_.path.value() << " "
<< error.ToString();
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;
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 (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 is a helper to help perform a delayed NP_Shutdown and FreeLibrary on the
// plugin dll.
void FreePluginLibraryHelper(const base::FilePath& path,
base::NativeLibrary library,
NP_ShutdownFunc shutdown_func) {
if (shutdown_func) {
// 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;
break;
}
}
}
if (!reloaded)
shutdown_func();
}
if (library) {
// Always call base::UnloadNativeLibrary so that the system reference
// count is decremented.
base::UnloadNativeLibrary(library);
}
}
void PluginLib::Unload() {
if (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.
/* 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_) {
LOG_IF(ERROR, PluginList::DebugPluginLoading())
<< "Scheduling delayed unload for plugin "
<< web_plugin_info_.path.value();
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&FreePluginLibraryHelper,
web_plugin_info_.path,
skip_unload_ ? NULL : library_,
entry_points_.np_shutdown));
} 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_) {
NP_Shutdown();
initialized_ = false;
}
}
} // namespace content