// 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/browser/plugin_service.h" #include "base/bind.h" #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/file_path.h" #include "base/message_loop.h" #include "base/message_loop_proxy.h" #include "base/path_service.h" #include "base/string_util.h" #include "base/synchronization/waitable_event.h" #include "base/threading/thread.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "content/browser/browser_thread.h" #include "content/browser/content_browser_client.h" #include "content/browser/plugin_service_filter.h" #include "content/browser/ppapi_plugin_process_host.h" #include "content/browser/renderer_host/render_process_host.h" #include "content/browser/renderer_host/render_view_host.h" #include "content/browser/resource_context.h" #include "content/browser/utility_process_host.h" #include "content/common/content_notification_types.h" #include "content/common/content_switches.h" #include "content/common/notification_service.h" #include "content/common/pepper_plugin_registry.h" #include "content/common/plugin_messages.h" #include "content/common/utility_messages.h" #include "content/common/view_messages.h" #include "webkit/plugins/npapi/plugin_constants_win.h" #include "webkit/plugins/npapi/plugin_group.h" #include "webkit/plugins/npapi/plugin_list.h" #include "webkit/plugins/webplugininfo.h" #if defined(OS_POSIX) && !defined(OS_MACOSX) using ::base::files::FilePathWatcher; #endif using content::PluginServiceFilter; namespace { // Helper function that merely runs the callback with the result. Called on the // thread on which the original GetPlugins() call was made. static void RunGetPluginsCallback( const PluginService::GetPluginsCallback& callback, const std::vector& result) { callback.Run(result); } // A callback for GetPlugins() that then gets the freshly loaded plugin groups // and runs the callback for GetPluginGroups(). static void GetPluginsForGroupsCallback( const PluginService::GetPluginGroupsCallback& callback, const std::vector& plugins) { std::vector groups; webkit::npapi::PluginList::Singleton()->GetPluginGroups(false, &groups); callback.Run(groups); } // Callback set on the PluginList to assert that plugin loading happens on the // correct thread. void WillLoadPluginsCallback() { // TODO(rsesek): Change these to CHECKs. #if defined(OS_WIN) || (defined(OS_POSIX) && !defined(OS_MACOSX)) LOG_IF(ERROR, !BrowserThread::CurrentlyOn(BrowserThread::FILE)); #else LOG(ERROR) << "Plugin loading should happen out-of-process."; #endif } #if defined(OS_POSIX) // Utility child process client that manages the IPC for loading plugins out of // process. class PluginLoaderClient : public UtilityProcessHost::Client { public: // Meant to be called on the IO thread. Will invoke the callback on the target // loop when the plugins have been loaded. static void LoadPluginsOutOfProcess( base::MessageLoopProxy* target_loop, const PluginService::GetPluginsCallback& callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); PluginLoaderClient* client = new PluginLoaderClient(target_loop, callback); UtilityProcessHost* process_host = new UtilityProcessHost(client, BrowserThread::IO); process_host->set_no_sandbox(true); #if defined(OS_MACOSX) process_host->set_child_flags(ChildProcessHost::CHILD_ALLOW_HEAP_EXECUTION); #endif std::vector extra_plugin_paths; std::vector extra_plugin_dirs; std::vector internal_plugins; webkit::npapi::PluginList::Singleton()->GetPluginPathListsToLoad( &extra_plugin_paths, &extra_plugin_dirs, &internal_plugins); process_host->Send(new UtilityMsg_LoadPlugins( extra_plugin_paths, extra_plugin_dirs, internal_plugins)); } virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { bool handled = true; IPC_BEGIN_MESSAGE_MAP(PluginLoaderClient, message) IPC_MESSAGE_HANDLER(UtilityHostMsg_LoadedPlugins, OnGotPlugins) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } virtual void OnProcessCrashed(int exit_code) OVERRIDE { LOG(ERROR) << "Out-of-process plugin loader crashed with code " << exit_code << ". You will have no plugins!"; // Don't leave callers hanging. OnGotPlugins(std::vector()); } virtual void OnGotPlugins(const std::vector& plugins) { webkit::npapi::PluginList::Singleton()->SetPlugins(plugins); target_loop_->PostTask(FROM_HERE, base::Bind(&RunGetPluginsCallback, callback_, plugins)); } private: PluginLoaderClient(base::MessageLoopProxy* target_loop, const PluginService::GetPluginsCallback& callback) : target_loop_(target_loop), callback_(callback) { } scoped_refptr target_loop_; PluginService::GetPluginsCallback callback_; DISALLOW_COPY_AND_ASSIGN(PluginLoaderClient); }; #endif // OS_POSIX } // namespace #if defined(OS_MACOSX) static void NotifyPluginsOfActivation() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); for (BrowserChildProcessHost::Iterator iter(ChildProcessInfo::PLUGIN_PROCESS); !iter.Done(); ++iter) { PluginProcessHost* plugin = static_cast(*iter); plugin->OnAppActivation(); } } #elif defined(OS_POSIX) // Delegate class for monitoring directories. class PluginDirWatcherDelegate : public FilePathWatcher::Delegate { virtual void OnFilePathChanged(const FilePath& path) OVERRIDE { VLOG(1) << "Watched path changed: " << path.value(); // Make the plugin list update itself webkit::npapi::PluginList::Singleton()->RefreshPlugins(); } virtual void OnFilePathError(const FilePath& path) OVERRIDE { // TODO(pastarmovj): Add some sensible error handling. Maybe silently // stopping the watcher would be enough. Or possibly restart it. NOTREACHED(); } }; #endif // static PluginService* PluginService::GetInstance() { return Singleton::get(); } PluginService::PluginService() : ui_locale_( content::GetContentClient()->browser()->GetApplicationLocale()), filter_(NULL) { webkit::npapi::PluginList::Singleton()->set_will_load_plugins_callback( base::Bind(&WillLoadPluginsCallback)); RegisterPepperPlugins(); // 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()) webkit::npapi::PluginList::Singleton()->AddExtraPluginPath(path); path = command_line->GetSwitchValuePath(switches::kExtraPluginDir); if (!path.empty()) webkit::npapi::PluginList::Singleton()->AddExtraPluginDir(path); #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, content::NOTIFICATION_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(); if (hkcu_event_.get()) hkcu_event_->Release(); if (hklm_event_.get()) hklm_event_->Release(); #endif // Make sure no plugin channel requests have been leaked. DCHECK(pending_plugin_clients_.empty()); } void PluginService::StartWatchingPlugins() { // Start watching for changes in the plugin list. This means watching // for changes in the Windows registry keys and on both Windows and POSIX // watch for changes in the paths that are expected to contain plugins. #if defined(OS_WIN) if (hkcu_key_.Create(HKEY_CURRENT_USER, webkit::npapi::kRegistryMozillaPlugins, KEY_NOTIFY) == ERROR_SUCCESS) { if (hkcu_key_.StartWatching() == ERROR_SUCCESS) { hkcu_event_.reset(new base::WaitableEvent(hkcu_key_.watch_event())); hkcu_watcher_.StartWatching(hkcu_event_.get(), this); } } if (hklm_key_.Create(HKEY_LOCAL_MACHINE, webkit::npapi::kRegistryMozillaPlugins, KEY_NOTIFY) == ERROR_SUCCESS) { if (hklm_key_.StartWatching() == ERROR_SUCCESS) { hklm_event_.reset(new base::WaitableEvent(hklm_key_.watch_event())); hklm_watcher_.StartWatching(hklm_event_.get(), this); } } #elif defined(OS_POSIX) && !defined(OS_MACOSX) // The FilePathWatcher produces too many false positives on MacOS (access time // updates?) which will lead to enforcing updates of the plugins way too often. // On ChromeOS the user can't install plugins anyway and on Windows all // important plugins register themselves in the registry so no need to do that. file_watcher_delegate_ = new PluginDirWatcherDelegate(); // Get the list of all paths for registering the FilePathWatchers // that will track and if needed reload the list of plugins on runtime. std::vector plugin_dirs; webkit::npapi::PluginList::Singleton()->GetPluginDirectories( &plugin_dirs); for (size_t i = 0; i < plugin_dirs.size(); ++i) { // FilePathWatcher can not handle non-absolute paths under windows. // We don't watch for file changes in windows now but if this should ever // be extended to Windows these lines might save some time of debugging. #if defined(OS_WIN) if (!plugin_dirs[i].IsAbsolute()) continue; #endif FilePathWatcher* watcher = new FilePathWatcher(); VLOG(1) << "Watching for changes in: " << plugin_dirs[i].value(); BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, NewRunnableFunction( &PluginService::RegisterFilePathWatcher, watcher, plugin_dirs[i], file_watcher_delegate_)); file_watchers_.push_back(watcher); } #endif } const std::string& PluginService::GetUILocale() { return ui_locale_; } PluginProcessHost* PluginService::FindNpapiPluginProcess( const FilePath& plugin_path) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); for (BrowserChildProcessHost::Iterator iter(ChildProcessInfo::PLUGIN_PROCESS); !iter.Done(); ++iter) { PluginProcessHost* plugin = static_cast(*iter); if (plugin->info().path == plugin_path) return plugin; } return NULL; } PpapiPluginProcessHost* PluginService::FindPpapiPluginProcess( const FilePath& plugin_path) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); for (BrowserChildProcessHost::Iterator iter( ChildProcessInfo::PPAPI_PLUGIN_PROCESS); !iter.Done(); ++iter) { PpapiPluginProcessHost* plugin = static_cast(*iter); if (plugin->plugin_path() == plugin_path) return plugin; } return NULL; } PpapiPluginProcessHost* PluginService::FindPpapiBrokerProcess( const FilePath& broker_path) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); for (BrowserChildProcessHost::Iterator iter( ChildProcessInfo::PPAPI_BROKER_PROCESS); !iter.Done(); ++iter) { PpapiPluginProcessHost* broker = static_cast(*iter); if (broker->plugin_path() == broker_path) return broker; } return NULL; } PluginProcessHost* PluginService::FindOrStartNpapiPluginProcess( const FilePath& plugin_path) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); PluginProcessHost* plugin_host = FindNpapiPluginProcess(plugin_path); if (plugin_host) return plugin_host; webkit::WebPluginInfo info; if (!webkit::npapi::PluginList::Singleton()->GetPluginInfoByPath( plugin_path, &info)) { return NULL; } // This plugin isn't loaded by any plugin process, so create a new process. scoped_ptr new_host(new PluginProcessHost()); if (!new_host->Init(info, ui_locale_)) { NOTREACHED(); // Init is not expected to fail. return NULL; } return new_host.release(); } PpapiPluginProcessHost* PluginService::FindOrStartPpapiPluginProcess( const FilePath& plugin_path, PpapiPluginProcessHost::PluginClient* client) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); PpapiPluginProcessHost* plugin_host = FindPpapiPluginProcess(plugin_path); if (plugin_host) return plugin_host; // Validate that the plugin is actually registered. PepperPluginInfo* info = GetRegisteredPpapiPluginInfo(plugin_path); if (!info) return NULL; // This plugin isn't loaded by any plugin process, so create a new process. return PpapiPluginProcessHost::CreatePluginHost( *info, client->GetResourceContext()->host_resolver()); } PpapiPluginProcessHost* PluginService::FindOrStartPpapiBrokerProcess( const FilePath& plugin_path) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); PpapiPluginProcessHost* plugin_host = FindPpapiBrokerProcess(plugin_path); if (plugin_host) return plugin_host; // Validate that the plugin is actually registered. PepperPluginInfo* info = GetRegisteredPpapiPluginInfo(plugin_path); if (!info) return NULL; // TODO(ddorwin): Uncomment once out of process is supported. // DCHECK(info->is_out_of_process); // This broker isn't loaded by any broker process, so create a new process. return PpapiPluginProcessHost::CreateBrokerHost(*info); } void PluginService::OpenChannelToNpapiPlugin( int render_process_id, int render_view_id, const GURL& url, const GURL& page_url, const std::string& mime_type, PluginProcessHost::Client* client) { DCHECK(!ContainsKey(pending_plugin_clients_, client)); pending_plugin_clients_.insert(client); // The PluginList::GetPluginInfo may need to load the plugins. Don't do it on // the IO thread. BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, NewRunnableMethod( this, &PluginService::GetAllowedPluginForOpenChannelToPlugin, render_process_id, render_view_id, url, page_url, mime_type, client, &client->GetResourceContext())); } void PluginService::OpenChannelToPpapiPlugin( const FilePath& path, PpapiPluginProcessHost::PluginClient* client) { PpapiPluginProcessHost* plugin_host = FindOrStartPpapiPluginProcess( path, client); if (plugin_host) plugin_host->OpenChannelToPlugin(client); else // Send error. client->OnChannelOpened(base::kNullProcessHandle, IPC::ChannelHandle()); } void PluginService::OpenChannelToPpapiBroker( const FilePath& path, PpapiPluginProcessHost::BrokerClient* client) { PpapiPluginProcessHost* plugin_host = FindOrStartPpapiBrokerProcess(path); if (plugin_host) plugin_host->OpenChannelToPlugin(client); else // Send error. client->OnChannelOpened(base::kNullProcessHandle, IPC::ChannelHandle()); } void PluginService::CancelOpenChannelToNpapiPlugin( PluginProcessHost::Client* client) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DCHECK(ContainsKey(pending_plugin_clients_, client)); pending_plugin_clients_.erase(client); } void PluginService::GetAllowedPluginForOpenChannelToPlugin( int render_process_id, int render_view_id, const GURL& url, const GURL& page_url, const std::string& mime_type, PluginProcessHost::Client* client, const content::ResourceContext* resource_context) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DCHECK(resource_context); webkit::WebPluginInfo info; bool allow_wildcard = true; bool found = GetPluginInfo( render_process_id, render_view_id, *resource_context, url, page_url, mime_type, allow_wildcard, NULL, &info, NULL); FilePath plugin_path; if (found) plugin_path = info.path; // Now we jump back to the IO thread to finish opening the channel. BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, NewRunnableMethod( this, &PluginService::FinishOpenChannelToPlugin, plugin_path, client)); } void PluginService::FinishOpenChannelToPlugin( const FilePath& plugin_path, PluginProcessHost::Client* client) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); // Make sure it hasn't been canceled yet. if (!ContainsKey(pending_plugin_clients_, client)) return; pending_plugin_clients_.erase(client); PluginProcessHost* plugin_host = FindOrStartNpapiPluginProcess(plugin_path); if (plugin_host) { client->OnFoundPluginProcessHost(plugin_host); plugin_host->OpenChannelToPlugin(client); } else { client->OnError(); } } bool PluginService::GetPluginInfoArray( const GURL& url, const std::string& mime_type, bool allow_wildcard, std::vector* plugins, std::vector* actual_mime_types) { bool use_stale = false; webkit::npapi::PluginList::Singleton()->GetPluginInfoArray( url, mime_type, allow_wildcard, &use_stale, plugins, actual_mime_types); return use_stale; } bool PluginService::GetPluginInfo(int render_process_id, int render_view_id, const content::ResourceContext& context, const GURL& url, const GURL& page_url, const std::string& mime_type, bool allow_wildcard, bool* use_stale, webkit::WebPluginInfo* info, std::string* actual_mime_type) { webkit::npapi::PluginList* plugin_list = webkit::npapi::PluginList::Singleton(); // GetPluginInfoArray may need to load the plugins, so we need to be // on the FILE thread. DCHECK(use_stale || BrowserThread::CurrentlyOn(BrowserThread::FILE)); std::vector plugins; std::vector mime_types; plugin_list->GetPluginInfoArray( url, mime_type, allow_wildcard, use_stale, &plugins, &mime_types); if (plugins.size() > 1 && plugins.back().path == FilePath(webkit::npapi::kDefaultPluginLibraryName)) { // If there is at least one plug-in handling the required MIME type (apart // from the default plug-in), we don't need the default plug-in. plugins.pop_back(); } for (size_t i = 0; i < plugins.size(); ++i) { if (!filter_ || filter_->ShouldUsePlugin(render_process_id, render_view_id, &context, url, page_url, &plugins[i])) { *info = plugins[i]; if (actual_mime_type) *actual_mime_type = mime_types[i]; return true; } } return false; } void PluginService::RefreshPluginList() { webkit::npapi::PluginList::Singleton()->RefreshPlugins(); } void PluginService::GetPlugins(const GetPluginsCallback& callback) { scoped_refptr target_loop( MessageLoop::current()->message_loop_proxy()); #if defined(OS_WIN) || (defined(OS_POSIX) && !defined(OS_MACOSX)) BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(&PluginService::GetPluginsInternal, base::Unretained(this), target_loop, callback)); #else std::vector cached_plugins; if (webkit::npapi::PluginList::Singleton()->GetPluginsIfNoRefreshNeeded( &cached_plugins)) { // Can't assume the caller is reentrant. target_loop->PostTask(FROM_HERE, base::Bind(&RunGetPluginsCallback, callback, cached_plugins)); } else { BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&PluginLoaderClient::LoadPluginsOutOfProcess, target_loop, callback)); } #endif } void PluginService::GetPluginGroups(const GetPluginGroupsCallback& callback) { GetPlugins(base::Bind(&GetPluginsForGroupsCallback, callback)); } void PluginService::GetPluginsInternal( base::MessageLoopProxy* target_loop, const PluginService::GetPluginsCallback& callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); std::vector plugins; webkit::npapi::PluginList::Singleton()->GetPlugins(&plugins); target_loop->PostTask(FROM_HERE, base::Bind(&RunGetPluginsCallback, callback, plugins)); } void PluginService::OnWaitableEventSignaled( base::WaitableEvent* waitable_event) { #if defined(OS_WIN) if (waitable_event == hkcu_event_.get()) { hkcu_key_.StartWatching(); } else { hklm_key_.StartWatching(); } webkit::npapi::PluginList::Singleton()->RefreshPlugins(); PurgePluginListCache(true); #else // This event should only get signaled on a Windows machine. NOTREACHED(); #endif // defined(OS_WIN) } void PluginService::Observe(int type, const NotificationSource& source, const NotificationDetails& details) { #if defined(OS_MACOSX) if (type == content::NOTIFICATION_APP_ACTIVATED) { BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, NewRunnableFunction(&NotifyPluginsOfActivation)); return; } #endif NOTREACHED(); } void PluginService::PurgePluginListCache(bool reload_pages) { for (RenderProcessHost::iterator it = RenderProcessHost::AllHostsIterator(); !it.IsAtEnd(); it.Advance()) { it.GetCurrentValue()->Send(new ViewMsg_PurgePluginListCache(reload_pages)); } } void PluginService::RegisterPepperPlugins() { // TODO(abarth): It seems like the PepperPluginRegistry should do this work. PepperPluginRegistry::ComputeList(&ppapi_plugins_); for (size_t i = 0; i < ppapi_plugins_.size(); ++i) { webkit::npapi::PluginList::Singleton()->RegisterInternalPlugin( ppapi_plugins_[i].ToWebPluginInfo()); } } // There should generally be very few plugins so a brute-force search is fine. PepperPluginInfo* PluginService::GetRegisteredPpapiPluginInfo( const FilePath& plugin_path) { PepperPluginInfo* info = NULL; for (size_t i = 0; i < ppapi_plugins_.size(); i++) { if (ppapi_plugins_[i].path == plugin_path) { info = &ppapi_plugins_[i]; break; } } if (info) return info; // We did not find the plugin in our list. But wait! the plugin can also // be a latecomer, as it happens with pepper flash. This information // can be obtained from the PluginList singleton and we can use it to // construct it and add it to the list. This same deal needs to be done // in the renderer side in PepperPluginRegistry. webkit::WebPluginInfo webplugin_info; if (!webkit::npapi::PluginList::Singleton()->GetPluginInfoByPath( plugin_path, &webplugin_info)) return NULL; PepperPluginInfo new_pepper_info; if (!MakePepperPluginInfo(webplugin_info, &new_pepper_info)) return NULL; ppapi_plugins_.push_back(new_pepper_info); return &ppapi_plugins_[ppapi_plugins_.size() - 1]; } #if defined(OS_POSIX) && !defined(OS_MACOSX) // static void PluginService::RegisterFilePathWatcher( FilePathWatcher *watcher, const FilePath& path, FilePathWatcher::Delegate* delegate) { bool result = watcher->Watch(path, delegate); DCHECK(result); } #endif