blob: b7f0489569c06148ed64ed188745c38af27fa9d6 [file] [log] [blame]
// 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 "build/build_config.h"
#include "chrome/browser/plugin_service.h"
#include "base/command_line.h"
#include "base/path_service.h"
#include "base/string_util.h"
#include "base/thread.h"
#include "base/values.h"
#include "base/waitable_event.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_plugin_host.h"
#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/extensions/extensions_service.h"
#include "chrome/browser/plugin_process_host.h"
#include "chrome/browser/plugin_updater.h"
#include "chrome/browser/pref_service.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/renderer_host/render_process_host.h"
#include "chrome/common/chrome_plugin_lib.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/default_plugin.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/gpu_plugin.h"
#include "chrome/common/logging_chrome.h"
#include "chrome/common/notification_type.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/pepper_plugin_registry.h"
#include "chrome/common/plugin_messages.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/render_messages.h"
#ifndef DISABLE_NACL
#include "native_client/src/trusted/plugin/nacl_entry_points.h"
#endif
#include "webkit/glue/plugins/plugin_constants_win.h"
#include "webkit/glue/plugins/plugin_list.h"
#if defined(OS_MACOSX)
static void NotifyPluginsOfActivation() {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
for (BrowserChildProcessHost::Iterator iter(ChildProcessInfo::PLUGIN_PROCESS);
!iter.Done(); ++iter) {
PluginProcessHost* plugin = static_cast<PluginProcessHost*>(*iter);
plugin->OnAppActivation();
}
}
#endif
// static
bool PluginService::enable_chrome_plugins_ = true;
// static
void PluginService::InitGlobalInstance(Profile* profile) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
// We first group the plugins and then figure out which groups to disable.
plugin_updater::DisablePluginGroupsFromPrefs(profile);
// Have Chrome plugins write their data to the profile directory.
GetInstance()->SetChromePluginDataDir(profile->GetPath());
}
// static
PluginService* PluginService::GetInstance() {
return Singleton<PluginService>::get();
}
// static
void PluginService::EnableChromePlugins(bool enable) {
enable_chrome_plugins_ = enable;
}
PluginService::PluginService()
: main_message_loop_(MessageLoop::current()),
resource_dispatcher_host_(NULL),
ui_locale_(ASCIIToWide(g_browser_process->GetApplicationLocale())) {
RegisterPepperPlugins();
// Have the NPAPI plugin list search for Chrome plugins as well.
ChromePluginLib::RegisterPluginsWithNPAPI();
// Load any specified on the command line as well.
const CommandLine* command_line = CommandLine::ForCurrentProcess();
FilePath path = command_line->GetSwitchValuePath(switches::kLoadPlugin);
if (!path.empty())
NPAPI::PluginList::Singleton()->AddExtraPluginPath(path);
path = command_line->GetSwitchValuePath(switches::kExtraPluginDir);
if (!path.empty())
NPAPI::PluginList::Singleton()->AddExtraPluginDir(path);
chrome::RegisterInternalDefaultPlugin();
// Register the internal Flash and PDF, if available.
if (!CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableInternalFlash) &&
PathService::Get(chrome::FILE_FLASH_PLUGIN, &path)) {
NPAPI::PluginList::Singleton()->AddExtraPluginPath(path);
}
#ifndef DISABLE_NACL
if (command_line->HasSwitch(switches::kInternalNaCl))
RegisterInternalNaClPlugin();
#endif
chrome::RegisterInternalGPUPlugin();
#if defined(OS_WIN)
hkcu_key_.Create(
HKEY_CURRENT_USER, kRegistryMozillaPlugins, KEY_NOTIFY);
hklm_key_.Create(
HKEY_LOCAL_MACHINE, kRegistryMozillaPlugins, KEY_NOTIFY);
if (hkcu_key_.StartWatching()) {
hkcu_event_.reset(new base::WaitableEvent(hkcu_key_.watch_event()));
hkcu_watcher_.StartWatching(hkcu_event_.get(), this);
}
if (hklm_key_.StartWatching()) {
hklm_event_.reset(new base::WaitableEvent(hklm_key_.watch_event()));
hklm_watcher_.StartWatching(hklm_event_.get(), this);
}
#elif defined(OS_POSIX) && !defined(OS_MACOSX)
// Also find plugins in a user-specific plugins dir,
// e.g. ~/.config/chromium/Plugins.
FilePath user_data_dir;
if (PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) {
NPAPI::PluginList::Singleton()->AddExtraPluginDir(
user_data_dir.Append("Plugins"));
}
#endif
registrar_.Add(this, NotificationType::EXTENSION_LOADED,
NotificationService::AllSources());
registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
NotificationService::AllSources());
#if defined(OS_MACOSX)
// We need to know when the browser comes forward so we can bring modal plugin
// windows forward too.
registrar_.Add(this, NotificationType::APP_ACTIVATED,
NotificationService::AllSources());
#endif
}
PluginService::~PluginService() {
#if defined(OS_WIN)
// Release the events since they're owned by RegKey, not WaitableEvent.
hkcu_watcher_.StopWatching();
hklm_watcher_.StopWatching();
hkcu_event_->Release();
hklm_event_->Release();
#endif
}
void PluginService::LoadChromePlugins(
ResourceDispatcherHost* resource_dispatcher_host) {
if (!enable_chrome_plugins_)
return;
resource_dispatcher_host_ = resource_dispatcher_host;
ChromePluginLib::LoadChromePlugins(GetCPBrowserFuncsForBrowser());
}
void PluginService::SetChromePluginDataDir(const FilePath& data_dir) {
chrome_plugin_data_dir_ = data_dir;
}
const FilePath& PluginService::GetChromePluginDataDir() {
return chrome_plugin_data_dir_;
}
const std::wstring& PluginService::GetUILocale() {
return ui_locale_;
}
PluginProcessHost* PluginService::FindPluginProcess(
const FilePath& plugin_path) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
if (plugin_path.value().empty()) {
NOTREACHED() << "should only be called if we have a plugin to load";
return NULL;
}
for (BrowserChildProcessHost::Iterator iter(ChildProcessInfo::PLUGIN_PROCESS);
!iter.Done(); ++iter) {
PluginProcessHost* plugin = static_cast<PluginProcessHost*>(*iter);
if (plugin->info().path == plugin_path)
return plugin;
}
return NULL;
}
PluginProcessHost* PluginService::FindOrStartPluginProcess(
const FilePath& plugin_path) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
PluginProcessHost *plugin_host = FindPluginProcess(plugin_path);
if (plugin_host)
return plugin_host;
WebPluginInfo info;
if (!NPAPI::PluginList::Singleton()->GetPluginInfoByPath(
plugin_path, &info)) {
DCHECK(false);
return NULL;
}
// This plugin isn't loaded by any plugin process, so create a new process.
plugin_host = new PluginProcessHost();
if (!plugin_host->Init(info, ui_locale_)) {
DCHECK(false); // Init is not expected to fail
delete plugin_host;
return NULL;
}
return plugin_host;
}
void PluginService::OpenChannelToPlugin(
ResourceMessageFilter* renderer_msg_filter,
const GURL& url,
const std::string& mime_type,
const std::wstring& locale,
IPC::Message* reply_msg) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
// We don't need a policy URL here because that was already checked by a
// previous call to GetPluginPath.
GURL policy_url;
FilePath plugin_path = GetPluginPath(url, policy_url, mime_type, NULL);
PluginProcessHost* plugin_host = FindOrStartPluginProcess(plugin_path);
if (plugin_host) {
plugin_host->OpenChannelToPlugin(renderer_msg_filter, mime_type, reply_msg);
} else {
PluginProcessHost::ReplyToRenderer(
renderer_msg_filter, IPC::ChannelHandle(), WebPluginInfo(), reply_msg);
}
}
FilePath PluginService::GetPluginPath(const GURL& url,
const GURL& policy_url,
const std::string& mime_type,
std::string* actual_mime_type) {
bool allow_wildcard = true;
WebPluginInfo info;
if (NPAPI::PluginList::Singleton()->GetPluginInfo(
url, mime_type, allow_wildcard, &info, actual_mime_type) &&
info.enabled && PluginAllowedForURL(info.path, policy_url)) {
return info.path;
}
return FilePath();
}
static void PurgePluginListCache(bool reload_pages) {
for (RenderProcessHost::iterator it = RenderProcessHost::AllHostsIterator();
!it.IsAtEnd(); it.Advance()) {
it.GetCurrentValue()->Send(new ViewMsg_PurgePluginListCache(reload_pages));
}
}
void PluginService::OnWaitableEventSignaled(
base::WaitableEvent* waitable_event) {
#if defined(OS_WIN)
if (waitable_event == hkcu_event_.get()) {
hkcu_key_.StartWatching();
} else {
hklm_key_.StartWatching();
}
NPAPI::PluginList::Singleton()->RefreshPlugins();
PurgePluginListCache(true);
#endif // defined(OS_WIN)
}
static void ForceShutdownPlugin(const FilePath& plugin_path) {
PluginProcessHost* plugin =
PluginService::GetInstance()->FindPluginProcess(plugin_path);
if (plugin)
plugin->ForceShutdown();
}
void PluginService::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
switch (type.value) {
case NotificationType::EXTENSION_LOADED: {
Extension* extension = Details<Extension>(details).ptr();
bool plugins_changed = false;
for (size_t i = 0; i < extension->plugins().size(); ++i) {
const Extension::PluginInfo& plugin = extension->plugins()[i];
NPAPI::PluginList::Singleton()->RefreshPlugins();
NPAPI::PluginList::Singleton()->AddExtraPluginPath(plugin.path);
plugins_changed = true;
if (!plugin.is_public)
private_plugins_[plugin.path] = extension->url();
}
if (plugins_changed)
PurgePluginListCache(false);
break;
}
case NotificationType::EXTENSION_UNLOADED: {
Extension* extension = Details<Extension>(details).ptr();
bool plugins_changed = false;
for (size_t i = 0; i < extension->plugins().size(); ++i) {
const Extension::PluginInfo& plugin = extension->plugins()[i];
ChromeThread::PostTask(ChromeThread::IO, FROM_HERE,
NewRunnableFunction(&ForceShutdownPlugin,
plugin.path));
NPAPI::PluginList::Singleton()->RefreshPlugins();
NPAPI::PluginList::Singleton()->RemoveExtraPluginPath(plugin.path);
plugins_changed = true;
if (!plugin.is_public)
private_plugins_.erase(plugin.path);
}
if (plugins_changed)
PurgePluginListCache(false);
break;
}
#if defined(OS_MACOSX)
case NotificationType::APP_ACTIVATED: {
ChromeThread::PostTask(ChromeThread::IO, FROM_HERE,
NewRunnableFunction(&NotifyPluginsOfActivation));
break;
}
#endif
default:
DCHECK(false);
}
}
bool PluginService::PluginAllowedForURL(const FilePath& plugin_path,
const GURL& url) {
if (url.is_empty())
return true; // Caller wants all plugins.
PrivatePluginMap::iterator it = private_plugins_.find(plugin_path);
if (it == private_plugins_.end())
return true; // This plugin is not private, so it's allowed everywhere.
// We do a dumb compare of scheme and host, rather than using the domain
// service, since we only care about this for extensions.
const GURL& required_url = it->second;
return (url.scheme() == required_url.scheme() &&
url.host() == required_url.host());
}
void PluginService::RegisterPepperPlugins() {
std::vector<PepperPluginInfo> plugins;
PepperPluginRegistry::GetList(&plugins);
for (size_t i = 0; i < plugins.size(); ++i) {
NPAPI::PluginVersionInfo info;
info.path = plugins[i].path;
info.product_name = plugins[i].name.empty() ?
plugins[i].path.BaseName().ToWStringHack() :
ASCIIToWide(plugins[i].name);
info.file_description = ASCIIToWide(plugins[i].description);
info.file_extensions = ASCIIToWide(plugins[i].file_extensions);
info.file_description = ASCIIToWide(plugins[i].type_descriptions);
info.mime_types = ASCIIToWide(JoinString(plugins[i].mime_types, '|'));
// These NPAPI entry points will never be called. TODO(darin): Come up
// with a cleaner way to register pepper plugins with the NPAPI PluginList,
// or perhaps refactor the PluginList to be less specific to NPAPI.
memset(&info.entry_points, 0, sizeof(info.entry_points));
NPAPI::PluginList::Singleton()->RegisterInternalPlugin(info);
}
}