diff options
31 files changed, 838 insertions, 59 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 55e9bf9..a365f8c 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -6098,6 +6098,12 @@ The following plug-in is unresponsive: <ph name="PLUGIN_NAME">$1 <message name="IDS_BROWSER_HANGMONITOR_RENDERER_END" desc="The label of the 'kill' button"> Kill pages </message> + <message name="IDS_BROWSER_HANGMONITOR_PLUGIN_INFOBAR" desc="The text of the infobar notifying the user that a plugin has hung"> + A plug-in (<ph name="PLUGIN_NAME">$1<ex>Flash</ex></ph>) isn't responding. + </message> + <message name="IDS_BROWSER_HANGMONITOR_PLUGIN_INFOBAR_KILLBUTTON" desc="The button on the hung plugin infobar (...PLUGIN_INFOBAR above) to terminate the plugin"> + Stop plug-in + </message> <!-- Passwords and Exceptions Dialog --> <message name="IDS_PASSWORDS_EXCEPTIONS_WINDOW_TITLE" desc="Title for 'Passwords and exceptions dialog'"> diff --git a/chrome/browser/memory_details.cc b/chrome/browser/memory_details.cc index 04b248a..74e99f2 100644 --- a/chrome/browser/memory_details.cc +++ b/chrome/browser/memory_details.cc @@ -107,7 +107,7 @@ ProcessData& ProcessData::operator=(const ProcessData& rhs) { // // This operation will hit no fewer than 3 threads. // -// The ChildProcessInfo::Iterator can only be accessed from the IO thread. +// The BrowserChildProcessHostIterator can only be accessed from the IO thread. // // The RenderProcessHostIterator can only be accessed from the UI thread. // diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc index 686cd20..0d1b529 100644 --- a/chrome/browser/ui/browser.cc +++ b/chrome/browser/ui/browser.cc @@ -118,6 +118,7 @@ #include "chrome/browser/ui/global_error.h" #include "chrome/browser/ui/global_error_service.h" #include "chrome/browser/ui/global_error_service_factory.h" +#include "chrome/browser/ui/hung_plugin_tab_helper.h" #include "chrome/browser/ui/intents/web_intent_picker_controller.h" #include "chrome/browser/ui/omnibox/location_bar.h" #include "chrome/browser/ui/panels/panel.h" @@ -2918,22 +2919,14 @@ void Browser::CrashedPluginHelper(WebContents* tab, if (!tcw) return; + // Tell the hung plugin infobars about this crash so they can close any + // related ones. + tcw->hung_plugin_tab_helper()->PluginCrashed(plugin_path); + DCHECK(!plugin_path.value().empty()); - string16 plugin_name = plugin_path.LossyDisplayName(); - webkit::WebPluginInfo plugin_info; - if (PluginService::GetInstance()->GetPluginInfoByPath( - plugin_path, &plugin_info) && - !plugin_info.name.empty()) { - plugin_name = plugin_info.name; -#if defined(OS_MACOSX) - // Many plugins on the Mac have .plugin in the actual name, which looks - // terrible, so look for that and strip it off if present. - const std::string kPluginExtension = ".plugin"; - if (EndsWith(plugin_name, ASCIIToUTF16(kPluginExtension), true)) - plugin_name.erase(plugin_name.length() - kPluginExtension.length()); -#endif // OS_MACOSX - } + string16 plugin_name = + PluginService::GetInstance()->GetPluginDisplayNameByPath(plugin_path); gfx::Image* icon = &ResourceBundle::GetSharedInstance().GetNativeImageNamed( IDR_INFOBAR_PLUGIN_CRASHED); InfoBarTabHelper* infobar_helper = tcw->infobar_tab_helper(); @@ -4284,6 +4277,16 @@ void Browser::CrashedPlugin(WebContents* tab, const FilePath& plugin_path) { CrashedPluginHelper(tab, plugin_path); } +void Browser::PluginHungStatusChanged(WebContents* tab, + int plugin_child_id, + const FilePath& plugin_path, + bool is_hung) { + TabContentsWrapper* tcw = + TabContentsWrapper::GetCurrentWrapperForContents(tab); + tcw->hung_plugin_tab_helper()->PluginHungStatusChanged( + plugin_child_id, plugin_path, is_hung); +} + void Browser::UpdatePreferredSize(WebContents* source, const gfx::Size& pref_size) { window_->UpdatePreferredSize(source, pref_size); diff --git a/chrome/browser/ui/browser.h b/chrome/browser/ui/browser.h index 54b9e86..e6e3911 100644 --- a/chrome/browser/ui/browser.h +++ b/chrome/browser/ui/browser.h @@ -1085,6 +1085,10 @@ class Browser : public TabHandlerDelegate, virtual void CrashedPlugin(content::WebContents* tab, const FilePath& plugin_path) OVERRIDE; + virtual void PluginHungStatusChanged(content::WebContents* tab, + int plugin_child_id, + const FilePath& plugin_path, + bool is_hung) OVERRIDE; virtual void RequestToLockMouse(content::WebContents* tab) OVERRIDE; virtual void LostMouseLock() OVERRIDE; diff --git a/chrome/browser/ui/hung_plugin_tab_helper.cc b/chrome/browser/ui/hung_plugin_tab_helper.cc new file mode 100644 index 0000000..df2cf8c --- /dev/null +++ b/chrome/browser/ui/hung_plugin_tab_helper.cc @@ -0,0 +1,268 @@ +// Copyright (c) 2012 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 "chrome/browser/ui/hung_plugin_tab_helper.h" + +#include "base/bind.h" +#include "base/process_util.h" +#include "chrome/browser/infobars/infobar_tab_helper.h" +#include "chrome/browser/tab_contents/confirm_infobar_delegate.h" +#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/browser_child_process_host_iterator.h" +#include "content/public/browser/child_process_data.h" +#include "content/public/browser/plugin_service.h" +#include "content/public/common/result_codes.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "grit/locale_settings.h" +#include "grit/theme_resources_standard.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" + +namespace { + +// Delay in seconds before re-showing the hung plugin message. This will be +// increased each time. +const int kInitialReshowDelaySec = 10; + +// Called on the I/O thread to actually kill the plugin with the given child +// ID. We specifically don't want this to be a member function since if the +// user chooses to kill the plugin, we want to kill it even if they close the +// tab first. +// +// Be careful with the child_id. It's supplied by the renderer which might be +// hacked. +void KillPluginOnIOThread(int child_id) { + content::BrowserChildProcessHostIterator iter( + content::PROCESS_TYPE_PPAPI_PLUGIN); + while (!iter.Done()) { + const content::ChildProcessData& data = iter.GetData(); + if (data.id == child_id) { + // TODO(brettw) bug 123021: it might be nice to do some stuff to capture + // a stack. The NPAPI Windows hang monitor does some cool stuff in + // hung_window_detector.cc. + base::KillProcess(data.handle, content::RESULT_CODE_HUNG, false); + break; + } + ++iter; + } + // Ignore the case where we didn't find the plugin, it may have terminated + // before this function could run. +} + +} // namespace + +class HungPluginTabHelper::InfoBarDelegate : public ConfirmInfoBarDelegate { + public: + InfoBarDelegate(HungPluginTabHelper* helper, + InfoBarTabHelper* infobar_helper, + int plugin_child_id, + const string16& plugin_name); + virtual ~InfoBarDelegate(); + + // ConfirmInfoBarDelegate: + virtual gfx::Image* GetIcon() const OVERRIDE; + virtual string16 GetMessageText() const OVERRIDE; + virtual int GetButtons() const OVERRIDE; + virtual string16 GetButtonLabel(InfoBarButton button) const OVERRIDE; + virtual bool Accept() OVERRIDE; + virtual void InfoBarDismissed() OVERRIDE; + + private: + HungPluginTabHelper* helper_; + int plugin_child_id_; + + string16 message_; + string16 button_text_; + gfx::Image* icon_; +}; + +HungPluginTabHelper::InfoBarDelegate::InfoBarDelegate( + HungPluginTabHelper* helper, + InfoBarTabHelper* infobar_helper, + int plugin_child_id, + const string16& plugin_name) + : ConfirmInfoBarDelegate(infobar_helper), + helper_(helper), + plugin_child_id_(plugin_child_id) { + message_ = l10n_util::GetStringFUTF16(IDS_BROWSER_HANGMONITOR_PLUGIN_INFOBAR, + plugin_name); + button_text_ = l10n_util::GetStringUTF16( + IDS_BROWSER_HANGMONITOR_PLUGIN_INFOBAR_KILLBUTTON); + icon_ = &ResourceBundle::GetSharedInstance().GetNativeImageNamed( + IDR_INFOBAR_PLUGIN_CRASHED); +} + +HungPluginTabHelper::InfoBarDelegate::~InfoBarDelegate() { +} + +gfx::Image* HungPluginTabHelper::InfoBarDelegate::GetIcon() const { + return icon_; +} + +string16 HungPluginTabHelper::InfoBarDelegate::GetMessageText() const { + return message_; +} + +int HungPluginTabHelper::InfoBarDelegate::GetButtons() const { + return BUTTON_OK; +} + +string16 HungPluginTabHelper::InfoBarDelegate::GetButtonLabel( + InfoBarButton button) const { + return button_text_; +} + +bool HungPluginTabHelper::InfoBarDelegate::Accept() { + helper_->KillPlugin(plugin_child_id_); + return true; +} + +void HungPluginTabHelper::InfoBarDelegate::InfoBarDismissed() { + helper_->BarClosed(plugin_child_id_); +} + +// ----------------------------------------------------------------------------- + +HungPluginTabHelper::PluginState::PluginState(const FilePath& p, + const string16& n) + : path(p), + name(n), + info_bar(NULL), + next_reshow_delay(base::TimeDelta::FromSeconds(kInitialReshowDelaySec)), + timer(false, false) { +} + +HungPluginTabHelper::PluginState::~PluginState() { +} + +// ----------------------------------------------------------------------------- + +HungPluginTabHelper::HungPluginTabHelper(content::WebContents* contents) + : contents_(contents) { +} + +HungPluginTabHelper::~HungPluginTabHelper() { +} + +void HungPluginTabHelper::PluginCrashed(const FilePath& plugin_path) { + // TODO(brettw) ideally this would take the child process ID. When we do this + // for NaCl plugins, we'll want to know exactly which process it was since + // the path won't be useful. + InfoBarTabHelper* infobar_helper = GetInfoBarHelper(); + if (!infobar_helper) + return; + + // For now, just do a brute-force search to see if we have this plugin. Since + // we'll normally have 0 or 1, this is fast. + for (PluginStateMap::iterator i = hung_plugins_.begin(); + i != hung_plugins_.end(); ++i) { + if (i->second->path == plugin_path) { + if (i->second->info_bar) + infobar_helper->RemoveInfoBar(i->second->info_bar); + hung_plugins_.erase(i); + break; + } + } +} + +void HungPluginTabHelper::PluginHungStatusChanged(int plugin_child_id, + const FilePath& plugin_path, + bool is_hung) { + InfoBarTabHelper* infobar_helper = GetInfoBarHelper(); + if (!infobar_helper) + return; + + PluginStateMap::iterator found = hung_plugins_.find(plugin_child_id); + if (found != hung_plugins_.end()) { + if (!is_hung) { + // Hung plugin became un-hung, close the infobar and delete our info. + if (found->second->info_bar) + infobar_helper->RemoveInfoBar(found->second->info_bar); + hung_plugins_.erase(found); + } + return; + } + + string16 plugin_name = + content::PluginService::GetInstance()->GetPluginDisplayNameByPath( + plugin_path); + + linked_ptr<PluginState> state(new PluginState(plugin_path, plugin_name)); + hung_plugins_[plugin_child_id] = state; + ShowBar(plugin_child_id, state.get()); +} + +void HungPluginTabHelper::KillPlugin(int child_id) { + PluginStateMap::iterator found = hung_plugins_.find(child_id); + if (found == hung_plugins_.end()) { + NOTREACHED(); + return; + } + + content::BrowserThread::PostTask(content::BrowserThread::IO, + FROM_HERE, + base::Bind(&KillPluginOnIOThread, child_id)); + CloseBar(found->second.get()); +} + +void HungPluginTabHelper::BarClosed(int child_id) { + PluginStateMap::iterator found = hung_plugins_.find(child_id); + if (found == hung_plugins_.end() || !found->second->info_bar) { + NOTREACHED(); + return; + } + found->second->info_bar = NULL; + + // Schedule the timer to re-show the infobar if the plugin continues to be + // hung. + found->second->timer.Start(FROM_HERE, found->second->next_reshow_delay, + base::Bind(&HungPluginTabHelper::OnReshowTimer, + base::Unretained(this), + child_id)); + + // Next time we do this, delay it twice as long to avoid being annoying. + found->second->next_reshow_delay *= 2; +} + +void HungPluginTabHelper::OnReshowTimer(int child_id) { + PluginStateMap::iterator found = hung_plugins_.find(child_id); + if (found == hung_plugins_.end() || found->second->info_bar) { + // The timer should be cancelled if the record isn't in our map anymore. + NOTREACHED(); + return; + } + ShowBar(child_id, found->second.get()); +} + +void HungPluginTabHelper::ShowBar(int child_id, PluginState* state) { + InfoBarTabHelper* infobar_helper = GetInfoBarHelper(); + if (!infobar_helper) + return; + + DCHECK(!state->info_bar); + state->info_bar = new InfoBarDelegate(this, infobar_helper, + child_id, state->name); + infobar_helper->AddInfoBar(state->info_bar); +} + +void HungPluginTabHelper::CloseBar(PluginState* state) { + InfoBarTabHelper* infobar_helper = GetInfoBarHelper(); + if (!infobar_helper) + return; + + if (state->info_bar) { + infobar_helper->RemoveInfoBar(state->info_bar); + state->info_bar = NULL; + } +} + +InfoBarTabHelper* HungPluginTabHelper::GetInfoBarHelper() { + TabContentsWrapper* tcw = + TabContentsWrapper::GetCurrentWrapperForContents(contents_); + if (!tcw) + return NULL; + return tcw->infobar_tab_helper(); +} diff --git a/chrome/browser/ui/hung_plugin_tab_helper.h b/chrome/browser/ui/hung_plugin_tab_helper.h new file mode 100644 index 0000000..bea4e38 --- /dev/null +++ b/chrome/browser/ui/hung_plugin_tab_helper.h @@ -0,0 +1,108 @@ +// Copyright (c) 2012 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. + +#ifndef CHROME_BROWSER_UI_HUNG_PLUGIN_TAB_HELPER_H_ +#define CHROME_BROWSER_UI_HUNG_PLUGIN_TAB_HELPER_H_ + +#include <map> + +#include "base/file_path.h" +#include "base/memory/linked_ptr.h" +#include "base/string16.h" +#include "base/time.h" +#include "base/timer.h" + +class FilePath; +class InfoBarTabHelper; + +namespace content { +class WebContents; +} + +// Manages per-tab state with regard to hung plugins. This only handles +// Pepper plugins which we know are windowless. Hung NPAPI plugins (which +// may have native windows) can not be handled with infobars and have a +// separate OS-specific hang monitoring. +// +// Our job is: +// - Pop up an infobar when a plugin is hung. +// - Terminate the plugin process if the user so chooses. +// - Periodically re-show the hung plugin infobar if the user closes it without +// terminating the plugin. +// - Hide the infobar if the plugin starts responding again. +// - Keep track of all of this for any number of plugins. +class HungPluginTabHelper { + public: + explicit HungPluginTabHelper(content::WebContents* contents); + ~HungPluginTabHelper(); + + void PluginCrashed(const FilePath& plugin_path); + + void PluginHungStatusChanged(int plugin_child_id, + const FilePath& plugin_path, + bool is_hung); + + private: + class InfoBarDelegate; + friend class InfoBarDelegate; + + // Per-plugin state (since there could be more than one plugin hung). The + // integer key is the child process ID of the plugin process. This maintains + // the state for all plugins on this page that are currently hung, whether or + // not we're currently showing the infobar. + struct PluginState { + // Initializes the plugin state to be a hung plugin. + PluginState(const FilePath& p, const string16& n); + ~PluginState(); + + FilePath path; + string16 name; + + // Possibly-null if we're not showing an infobar right now. + InfoBarDelegate* info_bar; + + // Time to delay before re-showing the infobar for a hung plugin. This is + // increased each time the user cancels it. + base::TimeDelta next_reshow_delay; + + // Handles calling the helper when the infobar should be re-shown. + base::Timer timer; + + private: + // Since the scope of the timer manages our callback, this struct should + // not be copied. + DISALLOW_COPY_AND_ASSIGN(PluginState); + }; + typedef std::map<int, linked_ptr<PluginState> > PluginStateMap; + + // Called by an infobar when the user selects to kill the plugin. + void KillPlugin(int child_id); + + // Called by an infobar when the user presses the close button to dismiss the + // bar without doing anything. + void BarClosed(int child_id); + + // Called on a timer for a hung plugin to re-show the bar. + void OnReshowTimer(int child_id); + + // Shows the bar for the plugin identified by the given state, updating the + // state accordingly. The plugin must not have an infobar already. + void ShowBar(int child_id, PluginState* state); + + // Closes the infobar associated with the given state. Note that this can + // be called even if the bar is not opened, in which case it will do nothing. + void CloseBar(PluginState* state); + + // Possibly returns null. + InfoBarTabHelper* GetInfoBarHelper(); + + content::WebContents* contents_; + + // All currently hung plugins. + PluginStateMap hung_plugins_; + + DISALLOW_COPY_AND_ASSIGN(HungPluginTabHelper); +}; + +#endif // CHROME_BROWSER_UI_HUNG_PLUGIN_TAB_HELPER_H_ diff --git a/chrome/browser/ui/tab_contents/tab_contents_wrapper.cc b/chrome/browser/ui/tab_contents/tab_contents_wrapper.cc index 43e49d3..3d8aaa7 100644 --- a/chrome/browser/ui/tab_contents/tab_contents_wrapper.cc +++ b/chrome/browser/ui/tab_contents/tab_contents_wrapper.cc @@ -35,6 +35,7 @@ #include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h" #include "chrome/browser/ui/constrained_window_tab_helper.h" #include "chrome/browser/ui/find_bar/find_tab_helper.h" +#include "chrome/browser/ui/hung_plugin_tab_helper.h" #include "chrome/browser/ui/intents/web_intent_picker_controller.h" #include "chrome/browser/ui/pdf/pdf_tab_observer.h" #include "chrome/browser/ui/prefs/prefs_tab_helper.h" @@ -92,6 +93,7 @@ TabContentsWrapper::TabContentsWrapper(WebContents* contents) favicon_tab_helper_.reset(new FaviconTabHelper(contents)); find_tab_helper_.reset(new FindTabHelper(contents)); history_tab_helper_.reset(new HistoryTabHelper(contents)); + hung_plugin_tab_helper_.reset(new HungPluginTabHelper(contents)); infobar_tab_helper_.reset(new InfoBarTabHelper(contents)); password_manager_delegate_.reset(new PasswordManagerDelegateImpl(this)); password_manager_.reset( diff --git a/chrome/browser/ui/tab_contents/tab_contents_wrapper.h b/chrome/browser/ui/tab_contents/tab_contents_wrapper.h index bb48bf9..d573d62 100644 --- a/chrome/browser/ui/tab_contents/tab_contents_wrapper.h +++ b/chrome/browser/ui/tab_contents/tab_contents_wrapper.h @@ -31,6 +31,7 @@ class ExternalProtocolObserver; class FaviconTabHelper; class FindTabHelper; class HistoryTabHelper; +class HungPluginTabHelper; class InfoBarTabHelper; class OmniboxSearchHint; class PasswordManager; @@ -148,6 +149,9 @@ class TabContentsWrapper : public content::WebContentsObserver { FaviconTabHelper* favicon_tab_helper() { return favicon_tab_helper_.get(); } FindTabHelper* find_tab_helper() { return find_tab_helper_.get(); } HistoryTabHelper* history_tab_helper() { return history_tab_helper_.get(); } + HungPluginTabHelper* hung_plugin_tab_helper() { + return hung_plugin_tab_helper_.get(); + } InfoBarTabHelper* infobar_tab_helper() { return infobar_tab_helper_.get(); } #if defined(ENABLE_ONE_CLICK_SIGNIN) @@ -229,6 +233,7 @@ class TabContentsWrapper : public content::WebContentsObserver { scoped_ptr<FaviconTabHelper> favicon_tab_helper_; scoped_ptr<FindTabHelper> find_tab_helper_; scoped_ptr<HistoryTabHelper> history_tab_helper_; + scoped_ptr<HungPluginTabHelper> hung_plugin_tab_helper_; scoped_ptr<InfoBarTabHelper> infobar_tab_helper_; // PasswordManager and its delegate. The delegate must outlive the manager, diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 1b8f255..7a8ee76 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -3038,6 +3038,8 @@ 'browser/ui/gtk/web_intent_picker_gtk.h', 'browser/ui/gtk/website_settings_popup_gtk.cc', 'browser/ui/gtk/website_settings_popup_gtk.h', + 'browser/ui/hung_plugin_tab_helper.cc', + 'browser/ui/hung_plugin_tab_helper.h', 'browser/ui/intents/web_intent_inline_disposition_delegate.cc', 'browser/ui/intents/web_intent_inline_disposition_delegate.h', 'browser/ui/intents/web_intent_picker.cc', diff --git a/content/browser/plugin_data_remover_impl.cc b/content/browser/plugin_data_remover_impl.cc index 0d83135..fd62447 100644 --- a/content/browser/plugin_data_remover_impl.cc +++ b/content/browser/plugin_data_remover_impl.cc @@ -168,7 +168,8 @@ class PluginDataRemoverImpl::Context virtual void OnPpapiChannelOpened( base::ProcessHandle plugin_process_handle, - const IPC::ChannelHandle& channel_handle) OVERRIDE { + const IPC::ChannelHandle& channel_handle, + int /* child_id */) OVERRIDE { if (plugin_process_handle != base::kNullProcessHandle) ConnectToChannel(channel_handle, true); diff --git a/content/browser/plugin_service_impl.cc b/content/browser/plugin_service_impl.cc index 99924b1..29909c2 100644 --- a/content/browser/plugin_service_impl.cc +++ b/content/browser/plugin_service_impl.cc @@ -342,7 +342,7 @@ void PluginServiceImpl::OpenChannelToPpapiPlugin( } else { // Send error. client->OnPpapiChannelOpened(base::kNullProcessHandle, - IPC::ChannelHandle()); + IPC::ChannelHandle(), 0); } } @@ -355,7 +355,7 @@ void PluginServiceImpl::OpenChannelToPpapiBroker( } else { // Send error. client->OnPpapiChannelOpened(base::kNullProcessHandle, - IPC::ChannelHandle()); + IPC::ChannelHandle(), 0); } } @@ -483,6 +483,23 @@ bool PluginServiceImpl::GetPluginInfoByPath(const FilePath& plugin_path, return false; } +string16 PluginServiceImpl::GetPluginDisplayNameByPath(const FilePath& path) { + string16 plugin_name = path.LossyDisplayName(); + webkit::WebPluginInfo info; + if (PluginService::GetInstance()->GetPluginInfoByPath(path, &info) && + !info.name.empty()) { + plugin_name = info.name; +#if defined(OS_MACOSX) + // Many plugins on the Mac have .plugin in the actual name, which looks + // terrible, so look for that and strip it off if present. + const std::string kPluginExtension = ".plugin"; + if (EndsWith(plugin_name, ASCIIToUTF16(kPluginExtension), true)) + plugin_name.erase(plugin_name.length() - kPluginExtension.length()); +#endif // OS_MACOSX + } + return plugin_name; +} + void PluginServiceImpl::GetPlugins(const GetPluginsCallback& callback) { scoped_refptr<base::MessageLoopProxy> target_loop( MessageLoop::current()->message_loop_proxy()); diff --git a/content/browser/plugin_service_impl.h b/content/browser/plugin_service_impl.h index be36075..bc8f112 100644 --- a/content/browser/plugin_service_impl.h +++ b/content/browser/plugin_service_impl.h @@ -97,6 +97,7 @@ class CONTENT_EXPORT PluginServiceImpl std::string* actual_mime_type) OVERRIDE; virtual bool GetPluginInfoByPath(const FilePath& plugin_path, webkit::WebPluginInfo* info) OVERRIDE; + virtual string16 GetPluginDisplayNameByPath(const FilePath& path) OVERRIDE; virtual void GetPlugins(const GetPluginsCallback& callback) OVERRIDE; virtual void GetPluginGroups( const GetPluginGroupsCallback& callback) OVERRIDE; diff --git a/content/browser/ppapi_plugin_process_host.cc b/content/browser/ppapi_plugin_process_host.cc index ceee322..98218ad 100644 --- a/content/browser/ppapi_plugin_process_host.cc +++ b/content/browser/ppapi_plugin_process_host.cc @@ -111,16 +111,14 @@ PpapiPluginProcessHost::PpapiPluginProcessHost(net::HostResolver* host_resolver) : filter_(new PepperMessageFilter(PepperMessageFilter::PLUGIN, host_resolver)), network_observer_(new PluginNetworkObserver(this)), - is_broker_(false), - process_id_(ChildProcessHostImpl::GenerateChildProcessUniqueId()) { + is_broker_(false) { process_.reset(new BrowserChildProcessHostImpl( content::PROCESS_TYPE_PPAPI_PLUGIN, this)); process_->GetHost()->AddFilter(filter_.get()); } PpapiPluginProcessHost::PpapiPluginProcessHost() - : is_broker_(true), - process_id_(ChildProcessHostImpl::GenerateChildProcessUniqueId()) { + : is_broker_(true) { process_.reset(new BrowserChildProcessHostImpl( content::PROCESS_TYPE_PPAPI_BROKER, this)); } @@ -211,7 +209,7 @@ void PpapiPluginProcessHost::RequestPluginChannel(Client* client) { sent_requests_.push(client); } else { client->OnPpapiChannelOpened(base::kNullProcessHandle, - IPC::ChannelHandle()); + IPC::ChannelHandle(), 0); } } @@ -259,13 +257,13 @@ void PpapiPluginProcessHost::CancelRequests() { << "CancelRequests()"; for (size_t i = 0; i < pending_requests_.size(); i++) { pending_requests_[i]->OnPpapiChannelOpened(base::kNullProcessHandle, - IPC::ChannelHandle()); + IPC::ChannelHandle(), 0); } pending_requests_.clear(); while (!sent_requests_.empty()) { sent_requests_.front()->OnPpapiChannelOpened(base::kNullProcessHandle, - IPC::ChannelHandle()); + IPC::ChannelHandle(), 0); sent_requests_.pop(); } } @@ -297,5 +295,6 @@ void PpapiPluginProcessHost::OnRendererPluginChannelCreated( base::ProcessHandle renderers_plugin_handle = plugin_process; #endif - client->OnPpapiChannelOpened(renderers_plugin_handle, channel_handle); + client->OnPpapiChannelOpened(renderers_plugin_handle, channel_handle, + process_->GetData().id); } diff --git a/content/browser/ppapi_plugin_process_host.h b/content/browser/ppapi_plugin_process_host.h index 02e1f83..3d7436e 100644 --- a/content/browser/ppapi_plugin_process_host.h +++ b/content/browser/ppapi_plugin_process_host.h @@ -42,10 +42,12 @@ class PpapiPluginProcessHost : public content::BrowserChildProcessHostDelegate, // Called when the channel is asynchronously opened to the plugin or on // error. On error, the parameters should be: // base::kNullProcessHandle - // IPC::ChannelHandle() + // IPC::ChannelHandle(), + // 0 virtual void OnPpapiChannelOpened( base::ProcessHandle plugin_process_handle, - const IPC::ChannelHandle& channel_handle) = 0; + const IPC::ChannelHandle& channel_handle, + int plugin_child_id) = 0; }; class PluginClient : public Client { @@ -120,9 +122,6 @@ class PpapiPluginProcessHost : public content::BrowserChildProcessHostDelegate, const bool is_broker_; - // The unique id created for the process. - int process_id_; - scoped_ptr<BrowserChildProcessHostImpl> process_; DISALLOW_COPY_AND_ASSIGN(PpapiPluginProcessHost); diff --git a/content/browser/renderer_host/render_message_filter.cc b/content/browser/renderer_host/render_message_filter.cc index 98904ba..8d0eb4b 100644 --- a/content/browser/renderer_host/render_message_filter.cc +++ b/content/browser/renderer_host/render_message_filter.cc @@ -131,9 +131,10 @@ class OpenChannelToPpapiPluginCallback } virtual void OnPpapiChannelOpened(base::ProcessHandle plugin_process_handle, - const IPC::ChannelHandle& channel_handle) { + const IPC::ChannelHandle& channel_handle, + int plugin_child_id) { ViewHostMsg_OpenChannelToPepperPlugin::WriteReplyParams( - reply_msg(), plugin_process_handle, channel_handle); + reply_msg(), plugin_process_handle, channel_handle, plugin_child_id); SendReplyAndDeleteThis(); } @@ -165,7 +166,8 @@ class OpenChannelToPpapiBrokerCallback } virtual void OnPpapiChannelOpened(base::ProcessHandle broker_process_handle, - const IPC::ChannelHandle& channel_handle) { + const IPC::ChannelHandle& channel_handle, + int /* plugin_child_id */) { filter_->Send(new ViewMsg_PpapiBrokerChannelCreated(routing_id_, request_id_, broker_process_handle, diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc index 48ffa6c..fa1907c 100644 --- a/content/browser/web_contents/web_contents_impl.cc +++ b/content/browser/web_contents/web_contents_impl.cc @@ -574,6 +574,7 @@ bool WebContentsImpl::OnMessageReceived(const IPC::Message& message) { IPC_MESSAGE_HANDLER(ViewHostMsg_EndColorChooser, OnEndColorChooser) IPC_MESSAGE_HANDLER(ViewHostMsg_SetSelectedColorInColorChooser, OnSetSelectedColorInColorChooser) + IPC_MESSAGE_HANDLER(ViewHostMsg_PepperPluginHung, OnPepperPluginHung) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP_EX() @@ -1769,6 +1770,13 @@ void WebContentsImpl::OnSetSelectedColorInColorChooser(int color_chooser_id, color_chooser_->SetSelectedColor(color); } +void TabContents::OnPepperPluginHung(int plugin_child_id, + const FilePath& path, + bool is_hung) { + if (delegate_) + delegate_->PluginHungStatusChanged(this, plugin_child_id, path, is_hung); +} + // Notifies the RenderWidgetHost instance about the fact that the page is // loading, or done loading and calls the base implementation. void WebContentsImpl::SetIsLoading(bool is_loading, diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h index 1c0287b..d37dc44 100644 --- a/content/browser/web_contents/web_contents_impl.h +++ b/content/browser/web_contents/web_contents_impl.h @@ -461,6 +461,9 @@ class CONTENT_EXPORT WebContentsImpl void OnEndColorChooser(int color_chooser_id); void OnSetSelectedColorInColorChooser(int color_chooser_id, const SkColor& color); + void OnPepperPluginHung(int plugin_child_id, + const FilePath& path, + bool is_hung); // Changes the IsLoading state and notifies delegate as needed // |details| is used to provide details on the load that just finished diff --git a/content/common/view_messages.h b/content/common/view_messages.h index ea01f54..8d75190 100644 --- a/content/common/view_messages.h +++ b/content/common/view_messages.h @@ -1175,7 +1175,12 @@ IPC_MESSAGE_ROUTED2(ViewMsg_SavePageAsMHTML, IPC_MESSAGE_CONTROL1(ViewMsg_TempCrashWithData, GURL /* data */) +// Enable or disable inverting of web content pixels, for users who prefer +// white-on-black. +IPC_MESSAGE_ROUTED1(ViewMsg_InvertWebContent, + bool /* invert */) +// ----------------------------------------------------------------------------- // Messages sent from the renderer to the browser. // Sent by the renderer when it is creating a new window. The browser creates @@ -1642,14 +1647,21 @@ IPC_MESSAGE_ROUTED3(ViewHostMsg_WebUISend, std::string /* message */, base::ListValue /* args */) -// A renderer sends this to the browser process when it wants to -// create a ppapi plugin. The browser will create the plugin process if -// necessary, and will return a handle to the channel on success. -// On error an empty string is returned. -IPC_SYNC_MESSAGE_CONTROL1_2(ViewHostMsg_OpenChannelToPepperPlugin, +// A renderer sends this to the browser process when it wants to create a ppapi +// plugin. The browser will create the plugin process if necessary, and will +// return a handle to the channel on success. +// +// The plugin_child_id is the ChildProcessHost ID assigned in the browser +// process. This ID is valid only in the context of the browser process and is +// used to identify the proper process when the renderer notifies it that the +// plugin is hung. +// +// On error an empty string and null handles are returned. +IPC_SYNC_MESSAGE_CONTROL1_3(ViewHostMsg_OpenChannelToPepperPlugin, FilePath /* path */, base::ProcessHandle /* plugin_process_handle */, - IPC::ChannelHandle /* handle to channel */) + IPC::ChannelHandle /* handle to channel */, + int /* plugin_child_id */) // A renderer sends this to the browser process when it wants to // create a ppapi broker. The browser will create the broker process @@ -1992,7 +2004,11 @@ IPC_MESSAGE_ROUTED2(ViewHostMsg_DomOperationResponse, std::string /* json_string */, int /* automation_id */) -// Enable or disable inverting of web content pixels, for users who prefer -// white-on-black. -IPC_MESSAGE_ROUTED1(ViewMsg_InvertWebContent, - bool /* invert */) +// Sent to the browser when the renderer detects it is blocked on a pepper +// plugin message for too long. This is also sent when it becomes unhung +// (according to the value of is_hung). The browser can give the user the +// option of killing the plugin. +IPC_MESSAGE_ROUTED3(ViewHostMsg_PepperPluginHung, + int /* plugin_child_id */, + FilePath /* path */, + bool /* is_hung */) diff --git a/content/content_renderer.gypi b/content/content_renderer.gypi index d64fe40..b0a2037 100644 --- a/content/content_renderer.gypi +++ b/content/content_renderer.gypi @@ -123,6 +123,8 @@ 'renderer/pepper/pepper_broker_impl.h', 'renderer/pepper/pepper_device_enumeration_event_handler.cc', 'renderer/pepper/pepper_device_enumeration_event_handler.h', + 'renderer/pepper/pepper_hung_plugin_filter.cc', + 'renderer/pepper/pepper_hung_plugin_filter.h', 'renderer/pepper/pepper_parent_context_provider.cc', 'renderer/pepper/pepper_parent_context_provider.h', 'renderer/pepper/pepper_platform_audio_input_impl.cc', diff --git a/content/public/browser/plugin_service.h b/content/public/browser/plugin_service.h index 5be3d08..862aca4 100644 --- a/content/public/browser/plugin_service.h +++ b/content/public/browser/plugin_service.h @@ -93,6 +93,11 @@ class PluginService { virtual bool GetPluginInfoByPath(const FilePath& plugin_path, webkit::WebPluginInfo* info) = 0; + // Returns the display name for the plugin identified by the given path. If + // the path doesn't identify a plugin, or the plugin has no display name, + // this will attempt to generate a display name from the path. + virtual string16 GetPluginDisplayNameByPath(const FilePath& plugin_path) = 0; + // Asynchronously loads plugins if necessary and then calls back to the // provided function on the calling MessageLoop on completion. virtual void GetPlugins(const GetPluginsCallback& callback) = 0; diff --git a/content/public/browser/render_process_host.h b/content/public/browser/render_process_host.h index dc64905..eec6a6b 100644 --- a/content/public/browser/render_process_host.h +++ b/content/public/browser/render_process_host.h @@ -131,7 +131,7 @@ class CONTENT_EXPORT RenderProcessHost : public IPC::Message::Sender, // sending non-threadsafe pointers to other threads). // // This ID will be unique for all child processes, including workers, plugins, - // etc. It is generated by ChildProcessInfo. + // etc. virtual int GetID() const = 0; // Returns the render widget host for the routing id passed in. diff --git a/content/public/browser/web_contents_delegate.h b/content/public/browser/web_contents_delegate.h index 3972c2d..682b38438 100644 --- a/content/public/browser/web_contents_delegate.h +++ b/content/public/browser/web_contents_delegate.h @@ -372,6 +372,17 @@ class CONTENT_EXPORT WebContentsDelegate { // Notification that a plugin has crashed. virtual void CrashedPlugin(WebContents* tab, const FilePath& plugin_path) {} + // Notication that the given plugin has hung or become unhung. This + // notification is only for Pepper plugins. + // + // The plugin_child_id is the unique child process ID from the plugin. Note + // that this ID is supplied by the renderer, so should be validated before + // it's used for anything in case there's an exploited renderer. + virtual void PluginHungStatusChanged(WebContents* tab, + int plugin_child_id, + const FilePath& plugin_path, + bool is_hung) {} + // Invoked when the preferred size of the contents has been changed. virtual void UpdatePreferredSize(WebContents* tab, const gfx::Size& pref_size) {} diff --git a/content/renderer/pepper/pepper_hung_plugin_filter.cc b/content/renderer/pepper/pepper_hung_plugin_filter.cc new file mode 100644 index 0000000..4da00fc --- /dev/null +++ b/content/renderer/pepper/pepper_hung_plugin_filter.cc @@ -0,0 +1,153 @@ +// Copyright (c) 2012 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/renderer/pepper/pepper_hung_plugin_filter.h" + +#include "base/bind.h" +#include "content/common/view_messages.h" +#include "content/renderer/render_thread_impl.h" + +namespace content { + +namespace { + +// We'll consider the plugin hung after not hearing anything for this long. +const int kHungThresholdSec = 10; + +// If we ever are blocked for this long, we'll consider the plugin hung, even +// if we continue to get messages (which is why the above hung threshold never +// kicked in). Maybe the plugin is spamming us with events and never unblocking +// and never processing our sync message. +const int kBlockedHardThresholdSec = kHungThresholdSec * 1.5; + +} // namespace + +PepperHungPluginFilter::PepperHungPluginFilter(const FilePath& plugin_path, + int view_routing_id, + int plugin_child_id) + : plugin_path_(plugin_path), + view_routing_id_(view_routing_id), + plugin_child_id_(plugin_child_id), + filter_(RenderThread::Get()->GetSyncMessageFilter()), + io_loop_(RenderThreadImpl::current()->GetIOLoopProxy()), + pending_sync_message_count_(0), + hung_plugin_showing_(false), + timer_task_pending_(false) { +} + +PepperHungPluginFilter::~PepperHungPluginFilter() { +} + +void PepperHungPluginFilter::BeginBlockOnSyncMessage() { + base::AutoLock lock(lock_); + if (pending_sync_message_count_ == 0) + began_blocking_time_ = base::TimeTicks::Now(); + pending_sync_message_count_++; + + EnsureTimerScheduled(); +} + +void PepperHungPluginFilter::EndBlockOnSyncMessage() { + base::AutoLock lock(lock_); + pending_sync_message_count_--; + DCHECK(pending_sync_message_count_ >= 0); + + MayHaveBecomeUnhung(); +} + +void PepperHungPluginFilter::OnFilterAdded(IPC::Channel* channel) { +} + +void PepperHungPluginFilter::OnFilterRemoved() { + base::AutoLock lock(lock_); + MayHaveBecomeUnhung(); +} + +void PepperHungPluginFilter::OnChannelError() { + base::AutoLock lock(lock_); + MayHaveBecomeUnhung(); +} + +bool PepperHungPluginFilter::OnMessageReceived(const IPC::Message& message) { + // Just track incoming message times but don't handle any messages. + base::AutoLock lock(lock_); + last_message_received_ = base::TimeTicks::Now(); + MayHaveBecomeUnhung(); + return false; +} + +void PepperHungPluginFilter::EnsureTimerScheduled() { + lock_.AssertAcquired(); + if (timer_task_pending_) + return; + + timer_task_pending_ = true; + io_loop_->PostDelayedTask(FROM_HERE, + base::Bind(&PepperHungPluginFilter::OnHangTimer, this), + base::TimeDelta::FromSeconds(kHungThresholdSec)); +} + +void PepperHungPluginFilter::MayHaveBecomeUnhung() { + if (!hung_plugin_showing_ || IsHung()) + return; + + SendHungMessage(false); + hung_plugin_showing_ = false; +} + +bool PepperHungPluginFilter::IsHung() const { + lock_.AssertAcquired(); + + if (!pending_sync_message_count_) + return false; // Not blocked on a sync message. + + base::TimeTicks now = base::TimeTicks::Now(); + + DCHECK(!began_blocking_time_.is_null()); + if (now - began_blocking_time_ >= + base::TimeDelta::FromSeconds(kBlockedHardThresholdSec)) + return true; // Been blocked too long regardless of what the plugin is + // sending us. + + base::TimeDelta hung_threshold = + base::TimeDelta::FromSeconds(kHungThresholdSec); + if (now - began_blocking_time_ >= hung_threshold && + (last_message_received_.is_null() || + now - last_message_received_ >= hung_threshold)) + return true; // Blocked and haven't received a message in too long. + + return false; +} + +void PepperHungPluginFilter::OnHangTimer() { + base::AutoLock lock(lock_); + timer_task_pending_ = false; + + if (!IsHung()) { + if (pending_sync_message_count_ > 0) { + // Got a timer message while we're waiting on a sync message. We need + // to schedule another timer message because the latest sync message + // would not have scheduled one (we only have one out-standing timer at + // a time). + DCHECK(!began_blocking_time_.is_null()); + base::TimeDelta delay = + base::TimeDelta::FromSeconds(kHungThresholdSec) - + (base::TimeTicks::Now() - began_blocking_time_); + io_loop_->PostDelayedTask(FROM_HERE, + base::Bind(&PepperHungPluginFilter::OnHangTimer, this), + delay); + } + return; + } + + hung_plugin_showing_ = true; + SendHungMessage(true); +} + +void PepperHungPluginFilter::SendHungMessage(bool is_hung) { + filter_->Send(new ViewHostMsg_PepperPluginHung( + view_routing_id_, plugin_child_id_, plugin_path_, is_hung)); +} + +} // namespace content diff --git a/content/renderer/pepper/pepper_hung_plugin_filter.h b/content/renderer/pepper/pepper_hung_plugin_filter.h new file mode 100644 index 0000000..5fb63ef --- /dev/null +++ b/content/renderer/pepper/pepper_hung_plugin_filter.h @@ -0,0 +1,109 @@ +// Copyright (c) 2012 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. + +#ifndef CONTENT_RENDERER_PEPPER_PEPPER_HUNG_PLUGIN_FILTER_H_ +#define CONTENT_RENDERER_PEPPER_PEPPER_HUNG_PLUGIN_FILTER_H_ + +#include "base/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop_proxy.h" +#include "base/synchronization/lock.h" +#include "ipc/ipc_channel_proxy.h" +#include "ipc/ipc_sync_message_filter.h" +#include "ppapi/proxy/host_dispatcher.h" + +namespace content { + +// This class monitors a renderer <-> pepper plugin channel on the I/O thread +// of the renderer for a hung plugin. +// +// If the plugin is not responding to sync messages, it will notify the browser +// process and give the user the option to kill the hung plugin. +// +// Note that this class must be threadsafe since it will get the begin/end +// block notifications on the main thread, but the filter is run on the I/O +// thread. This is important since when we're blocked on a sync message to a +// hung plugin, the main thread is frozen. +// +// NOTE: This class is refcounted (via SyncMessageStatusReceiver). +class PepperHungPluginFilter + : public ppapi::proxy::HostDispatcher::SyncMessageStatusReceiver { + public: + // The |view_routing_id| is the ID of the render_view so that this class can + // send messages to the browser via that view's route. The |plugin_child_id| + // is the ID in the browser process of the pepper plugin process host. We use + // this to identify the proper plugin process to terminate. + PepperHungPluginFilter(const FilePath& plugin_path, + int view_routing_id, + int plugin_child_id); + virtual ~PepperHungPluginFilter(); + + // SyncMessageStatusReceiver implementation. + virtual void BeginBlockOnSyncMessage() OVERRIDE; + virtual void EndBlockOnSyncMessage() OVERRIDE; + + // MessageFilter implementation. + virtual void OnFilterAdded(IPC::Channel* channel) OVERRIDE; + virtual void OnFilterRemoved() OVERRIDE; + virtual void OnChannelError() OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + + private: + // Makes sure that the hung timer is scheduled. + void EnsureTimerScheduled(); + + // Checks whether the plugin could have transitioned from hung to unhung and + // notifies the browser if so. + void MayHaveBecomeUnhung(); + + // Checks if the plugin is considered hung based on whether it has been + // blocked for long enough. + bool IsHung() const; + + // Timer handler that checks for a hang after a timeout. + void OnHangTimer(); + + // Sends the hung/unhung message to the browser process. + void SendHungMessage(bool is_hung); + + base::Lock lock_; + + FilePath plugin_path_; + int view_routing_id_; + int plugin_child_id_; + + // Used to post messages to the renderer <-> browser message channel from + // other threads without having to worry about the lifetime of that object. + // We don't actually use the "sync" feature of this filter. + scoped_refptr<IPC::SyncMessageFilter> filter_; + + scoped_refptr<base::MessageLoopProxy> io_loop_; + + // The time when we start being blocked on a sync message. If there are + // nested ones, this is the time of the outermost one. + // + // This will be is_null() if we've never blocked. + base::TimeTicks began_blocking_time_; + + // Time that the last message was received from the plugin. + // + // This will be is_null() if we've never received any messages. + base::TimeTicks last_message_received_; + + // Number of nested sync messages that we're blocked on. + int pending_sync_message_count_; + + // True when we've sent the "plugin is hung" message to the browser. We track + // this so we know to look for it becoming unhung and send the corresponding + // message to the browser. + bool hung_plugin_showing_; + + bool timer_task_pending_; + + DISALLOW_COPY_AND_ASSIGN(PepperHungPluginFilter); +}; + +} // namespace content + +#endif // CONTENT_RENDERER_PEPPER_PEPPER_HUNG_PLUGIN_FILTER_H_ diff --git a/content/renderer/pepper/pepper_plugin_delegate_impl.cc b/content/renderer/pepper/pepper_plugin_delegate_impl.cc index bb023e8..30c4806 100644 --- a/content/renderer/pepper/pepper_plugin_delegate_impl.cc +++ b/content/renderer/pepper/pepper_plugin_delegate_impl.cc @@ -41,6 +41,7 @@ #include "content/renderer/p2p/socket_dispatcher.h" #include "content/renderer/pepper/pepper_broker_impl.h" #include "content/renderer/pepper/pepper_device_enumeration_event_handler.h" +#include "content/renderer/pepper/pepper_hung_plugin_filter.h" #include "content/renderer/pepper/pepper_platform_audio_input_impl.h" #include "content/renderer/pepper/pepper_platform_audio_output_impl.h" #include "content/renderer/pepper/pepper_platform_context_3d_impl.h" @@ -105,7 +106,8 @@ class HostDispatcherWrapper const IPC::ChannelHandle& channel_handle, PP_Module pp_module, ppapi::proxy::Dispatcher::GetInterfaceFunc local_get_interface, - const ppapi::Preferences& preferences) { + const ppapi::Preferences& preferences, + PepperHungPluginFilter* filter) { if (channel_handle.name.empty()) return false; @@ -117,7 +119,7 @@ class HostDispatcherWrapper dispatcher_delegate_.reset(new PepperProxyChannelDelegateImpl); dispatcher_.reset(new ppapi::proxy::HostDispatcher( - plugin_process_handle, pp_module, local_get_interface)); + plugin_process_handle, pp_module, local_get_interface, filter)); if (!dispatcher_->InitHostWithChannel(dispatcher_delegate_.get(), channel_handle, @@ -234,13 +236,18 @@ PepperPluginDelegateImpl::CreatePepperPluginModule( // Out of process: have the browser start the plugin process for us. base::ProcessHandle plugin_process_handle = base::kNullProcessHandle; IPC::ChannelHandle channel_handle; + int plugin_child_id = 0; render_view_->Send(new ViewHostMsg_OpenChannelToPepperPlugin( - path, &plugin_process_handle, &channel_handle)); + path, &plugin_process_handle, &channel_handle, &plugin_child_id)); if (channel_handle.name.empty()) { // Couldn't be initialized. return scoped_refptr<webkit::ppapi::PluginModule>(); } + scoped_refptr<PepperHungPluginFilter> hung_filter( + new PepperHungPluginFilter(path, render_view_->routing_id(), + plugin_child_id)); + // Create a new HostDispatcher for the proxying, and hook it to a new // PluginModule. Note that AddLiveModule must be called before any early // returns since the module's destructor will remove itself. @@ -253,7 +260,8 @@ PepperPluginDelegateImpl::CreatePepperPluginModule( channel_handle, module->pp_module(), webkit::ppapi::PluginModule::GetLocalGetInterfaceFunc(), - GetPreferences())) + GetPreferences(), + hung_filter.get())) return scoped_refptr<webkit::ppapi::PluginModule>(); module->InitAsProxied(dispatcher.release()); return module; diff --git a/ppapi/proxy/dispatcher.cc b/ppapi/proxy/dispatcher.cc index a2a1d71..39b1a06 100644 --- a/ppapi/proxy/dispatcher.cc +++ b/ppapi/proxy/dispatcher.cc @@ -50,6 +50,9 @@ base::MessageLoopProxy* Dispatcher::GetIPCMessageLoop() { void Dispatcher::AddIOThreadMessageFilter( IPC::ChannelProxy::MessageFilter* filter) { + // Our filter is refcounted. The channel will call the destruct method on the + // filter when the channel is done with it, so the corresponding Release() + // happens there. channel()->AddFilter(filter); } diff --git a/ppapi/proxy/dispatcher.h b/ppapi/proxy/dispatcher.h index 35a82ab..df0f484 100644 --- a/ppapi/proxy/dispatcher.h +++ b/ppapi/proxy/dispatcher.h @@ -68,9 +68,6 @@ class PPAPI_PROXY_EXPORT Dispatcher : public ProxyChannel { base::MessageLoopProxy* GetIPCMessageLoop(); // Adds the given filter to the IO thread. Takes ownership of the pointer. - // TODO(brettw) remove this. It's a hack to support the Flash - // ModuleLocalThreadAdapter. When the thread stuff is sorted out, this - // implementation detail should be hidden. void AddIOThreadMessageFilter(IPC::ChannelProxy::MessageFilter* filter); // TODO(brettw): What is this comment referring to? diff --git a/ppapi/proxy/host_dispatcher.cc b/ppapi/proxy/host_dispatcher.cc index d1c28f7..9256fe9 100644 --- a/ppapi/proxy/host_dispatcher.cc +++ b/ppapi/proxy/host_dispatcher.cc @@ -63,8 +63,10 @@ class BoolRestorer { HostDispatcher::HostDispatcher(base::ProcessHandle remote_process_handle, PP_Module module, - GetInterfaceFunc local_get_interface) + GetInterfaceFunc local_get_interface, + SyncMessageStatusReceiver* sync_status) : Dispatcher(remote_process_handle, local_get_interface), + sync_status_(sync_status), pp_module_(module), ppb_proxy_(NULL), allow_plugin_reentrancy_(false) { @@ -92,6 +94,8 @@ bool HostDispatcher::InitHostWithChannel( const ppapi::Preferences& preferences) { if (!Dispatcher::InitWithChannel(delegate, channel_handle, is_client)) return false; + AddIOThreadMessageFilter(sync_status_.get()); + Send(new PpapiMsg_SetPreferences(preferences)); return true; } @@ -152,7 +156,12 @@ bool HostDispatcher::Send(IPC::Message* msg) { // waiting for the reply, dispatches an incoming ExecuteScript call which // destroys the plugin module and in turn the dispatcher. ScopedModuleReference scoped_ref(this); - return Dispatcher::Send(msg); + + sync_status_->BeginBlockOnSyncMessage(); + bool result = Dispatcher::Send(msg); + sync_status_->EndBlockOnSyncMessage(); + + return result; } else { // We don't want to have a scoped ref for async message cases since since // async messages are sent during module desruction. In this case, the diff --git a/ppapi/proxy/host_dispatcher.h b/ppapi/proxy/host_dispatcher.h index 1bacdfa..2d44979 100644 --- a/ppapi/proxy/host_dispatcher.h +++ b/ppapi/proxy/host_dispatcher.h @@ -9,8 +9,10 @@ #include <string> #include <vector> +#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/process.h" +#include "ipc/ipc_channel_proxy.h" #include "ppapi/c/pp_instance.h" #include "ppapi/proxy/dispatcher.h" #include "ppapi/shared_impl/function_group_base.h" @@ -25,12 +27,31 @@ namespace proxy { class PPAPI_PROXY_EXPORT HostDispatcher : public Dispatcher { public: - // Constructor for the renderer side. + // This interface receives notifications about sync messages being sent by + // the dispatcher to the plugin process. It is used to detect a hung plugin. + // + // Note that there can be nested sync messages, so the begin/end status + // actually represents a stack of blocking messages. + class SyncMessageStatusReceiver : public IPC::ChannelProxy::MessageFilter { + public: + virtual ~SyncMessageStatusReceiver() {} + + // Notification that a sync message is about to be sent out. + virtual void BeginBlockOnSyncMessage() = 0; + + // Notification that a sync message reply was received and the dispatcher + // is no longer blocked on a sync message. + virtual void EndBlockOnSyncMessage() = 0; + }; + + // Constructor for the renderer side. This will take a reference to the + // SyncMessageStatusReceiver. // // You must call InitHostWithChannel after the constructor. HostDispatcher(base::ProcessHandle host_process_handle, PP_Module module, - GetInterfaceFunc local_get_interface); + GetInterfaceFunc local_get_interface, + SyncMessageStatusReceiver* sync_status); ~HostDispatcher(); // You must call this function before anything else. Returns true on success. @@ -90,6 +111,8 @@ class PPAPI_PROXY_EXPORT HostDispatcher : public Dispatcher { const std::string& source, const std::string& value); + scoped_refptr<SyncMessageStatusReceiver> sync_status_; + PP_Module pp_module_; // Maps interface name to whether that interface is supported. If an interface diff --git a/ppapi/proxy/ppapi_proxy_test.cc b/ppapi/proxy/ppapi_proxy_test.cc index 7bfacc5..98baafd 100644 --- a/ppapi/proxy/ppapi_proxy_test.cc +++ b/ppapi/proxy/ppapi_proxy_test.cc @@ -5,6 +5,7 @@ #include "ppapi/proxy/ppapi_proxy_test.h" #include "base/bind.h" +#include "base/compiler_specific.h" #include "base/message_loop_proxy.h" #include "base/observer_list.h" #include "ipc/ipc_sync_channel.h" @@ -240,8 +241,16 @@ void PluginProxyTest::TearDown() { // HostProxyTestHarness -------------------------------------------------------- +class HostProxyTestHarness::MockSyncMessageStatusReceiver + : public HostDispatcher::SyncMessageStatusReceiver { + public: + virtual void BeginBlockOnSyncMessage() OVERRIDE {} + virtual void EndBlockOnSyncMessage() OVERRIDE {} +}; + HostProxyTestHarness::HostProxyTestHarness() - : host_globals_(PpapiGlobals::ForTest()) { + : host_globals_(PpapiGlobals::ForTest()), + status_receiver_(new MockSyncMessageStatusReceiver) { } HostProxyTestHarness::~HostProxyTestHarness() { @@ -257,7 +266,8 @@ void HostProxyTestHarness::SetUpHarness() { host_dispatcher_.reset(new HostDispatcher( base::Process::Current().handle(), pp_module(), - &MockGetInterface)); + &MockGetInterface, + status_receiver_.get())); host_dispatcher_->InitWithTestSink(&sink()); HostDispatcher::SetForInstance(pp_instance(), host_dispatcher_.get()); } @@ -274,7 +284,8 @@ void HostProxyTestHarness::SetUpHarnessWithChannel( host_dispatcher_.reset(new HostDispatcher( base::Process::Current().handle(), pp_module(), - &MockGetInterface)); + &MockGetInterface, + status_receiver_.get())); ppapi::Preferences preferences; host_dispatcher_->InitHostWithChannel(&delegate_mock_, channel_handle, is_client, preferences); diff --git a/ppapi/proxy/ppapi_proxy_test.h b/ppapi/proxy/ppapi_proxy_test.h index 6278f71..b2a3f36 100644 --- a/ppapi/proxy/ppapi_proxy_test.h +++ b/ppapi/proxy/ppapi_proxy_test.h @@ -198,9 +198,13 @@ class HostProxyTestHarness : public ProxyTestHarnessBase { }; private: + class MockSyncMessageStatusReceiver; + ppapi::TestGlobals host_globals_; scoped_ptr<HostDispatcher> host_dispatcher_; DelegateMock delegate_mock_; + + scoped_ptr<MockSyncMessageStatusReceiver> status_receiver_; }; class HostProxyTest : public HostProxyTestHarness, public testing::Test { |