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..df2cf8c0714fe765ed264744d2885d4badb5e3b2 |
--- /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(); |
+} |