Chromium Code Reviews| Index: chrome/browser/ui/hung_plugin_tab_helper.cc |
| 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 0000000000000000000000000000000000000000..b1922bfb64d8d4d5d7bdeb776f429ca96cb950d8 |
| --- /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 = tcw->infobar_tab_helper(); |
|
yzshen1
2012/04/11 20:08:10
GetInfoBarHelper()? (And also other places in the
brettw
2012/04/11 20:30:26
Thanks, this time I actually waited for the compil
|
| + 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 = tcw->infobar_tab_helper(); |
| + 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 = tcw->infobar_tab_helper(); |
| + 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 = tcw->infobar_tab_helper(); |
| + 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(); |
| +} |