OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "chrome/browser/ui/hung_plugin_tab_helper.h" |
| 6 |
| 7 #include "base/bind.h" |
| 8 #include "base/process_util.h" |
| 9 #include "chrome/browser/infobars/infobar_tab_helper.h" |
| 10 #include "chrome/browser/tab_contents/confirm_infobar_delegate.h" |
| 11 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" |
| 12 #include "content/public/browser/browser_thread.h" |
| 13 #include "content/public/browser/browser_child_process_host_iterator.h" |
| 14 #include "content/public/browser/child_process_data.h" |
| 15 #include "content/public/browser/plugin_service.h" |
| 16 #include "content/public/common/result_codes.h" |
| 17 #include "grit/chromium_strings.h" |
| 18 #include "grit/generated_resources.h" |
| 19 #include "grit/locale_settings.h" |
| 20 #include "grit/theme_resources_standard.h" |
| 21 #include "ui/base/l10n/l10n_util.h" |
| 22 #include "ui/base/resource/resource_bundle.h" |
| 23 |
| 24 namespace { |
| 25 |
| 26 // Delay in seconds before re-showing the hung plugin message. This will be |
| 27 // increased each time. |
| 28 const int kInitialReshowDelaySec = 10; |
| 29 |
| 30 // Called on the I/O thread to actually kill the plugin with the given child |
| 31 // ID. We specifically don't want this to be a member function since if the |
| 32 // user chooses to kill the plugin, we want to kill it even if they close the |
| 33 // tab first. |
| 34 // |
| 35 // Be careful with the child_id. It's supplied by the renderer which might be |
| 36 // hacked. |
| 37 void KillPluginOnIOThread(int child_id) { |
| 38 content::BrowserChildProcessHostIterator iter( |
| 39 content::PROCESS_TYPE_PPAPI_PLUGIN); |
| 40 while (!iter.Done()) { |
| 41 const content::ChildProcessData& data = iter.GetData(); |
| 42 if (data.id == child_id) { |
| 43 // TODO(brettw) bug 123021: it might be nice to do some stuff to capture |
| 44 // a stack. The NPAPI Windows hang monitor does some cool stuff in |
| 45 // hung_window_detector.cc. |
| 46 base::KillProcess(data.handle, content::RESULT_CODE_HUNG, false); |
| 47 break; |
| 48 } |
| 49 ++iter; |
| 50 } |
| 51 // Ignore the case where we didn't find the plugin, it may have terminated |
| 52 // before this function could run. |
| 53 } |
| 54 |
| 55 } // namespace |
| 56 |
| 57 class HungPluginTabHelper::InfoBarDelegate : public ConfirmInfoBarDelegate { |
| 58 public: |
| 59 InfoBarDelegate(HungPluginTabHelper* helper, |
| 60 InfoBarTabHelper* infobar_helper, |
| 61 int plugin_child_id, |
| 62 const string16& plugin_name); |
| 63 virtual ~InfoBarDelegate(); |
| 64 |
| 65 // ConfirmInfoBarDelegate: |
| 66 virtual gfx::Image* GetIcon() const OVERRIDE; |
| 67 virtual string16 GetMessageText() const OVERRIDE; |
| 68 virtual int GetButtons() const OVERRIDE; |
| 69 virtual string16 GetButtonLabel(InfoBarButton button) const OVERRIDE; |
| 70 virtual bool Accept() OVERRIDE; |
| 71 virtual void InfoBarDismissed() OVERRIDE; |
| 72 |
| 73 private: |
| 74 HungPluginTabHelper* helper_; |
| 75 int plugin_child_id_; |
| 76 |
| 77 string16 message_; |
| 78 string16 button_text_; |
| 79 gfx::Image* icon_; |
| 80 }; |
| 81 |
| 82 HungPluginTabHelper::InfoBarDelegate::InfoBarDelegate( |
| 83 HungPluginTabHelper* helper, |
| 84 InfoBarTabHelper* infobar_helper, |
| 85 int plugin_child_id, |
| 86 const string16& plugin_name) |
| 87 : ConfirmInfoBarDelegate(infobar_helper), |
| 88 helper_(helper), |
| 89 plugin_child_id_(plugin_child_id) { |
| 90 message_ = l10n_util::GetStringFUTF16(IDS_BROWSER_HANGMONITOR_PLUGIN_INFOBAR, |
| 91 plugin_name); |
| 92 button_text_ = l10n_util::GetStringUTF16( |
| 93 IDS_BROWSER_HANGMONITOR_PLUGIN_INFOBAR_KILLBUTTON); |
| 94 icon_ = &ResourceBundle::GetSharedInstance().GetNativeImageNamed( |
| 95 IDR_INFOBAR_PLUGIN_CRASHED); |
| 96 } |
| 97 |
| 98 HungPluginTabHelper::InfoBarDelegate::~InfoBarDelegate() { |
| 99 } |
| 100 |
| 101 gfx::Image* HungPluginTabHelper::InfoBarDelegate::GetIcon() const { |
| 102 return icon_; |
| 103 } |
| 104 |
| 105 string16 HungPluginTabHelper::InfoBarDelegate::GetMessageText() const { |
| 106 return message_; |
| 107 } |
| 108 |
| 109 int HungPluginTabHelper::InfoBarDelegate::GetButtons() const { |
| 110 return BUTTON_OK; |
| 111 } |
| 112 |
| 113 string16 HungPluginTabHelper::InfoBarDelegate::GetButtonLabel( |
| 114 InfoBarButton button) const { |
| 115 return button_text_; |
| 116 } |
| 117 |
| 118 bool HungPluginTabHelper::InfoBarDelegate::Accept() { |
| 119 helper_->KillPlugin(plugin_child_id_); |
| 120 return true; |
| 121 } |
| 122 |
| 123 void HungPluginTabHelper::InfoBarDelegate::InfoBarDismissed() { |
| 124 helper_->BarClosed(plugin_child_id_); |
| 125 } |
| 126 |
| 127 // ----------------------------------------------------------------------------- |
| 128 |
| 129 HungPluginTabHelper::PluginState::PluginState(const FilePath& p, |
| 130 const string16& n) |
| 131 : path(p), |
| 132 name(n), |
| 133 info_bar(NULL), |
| 134 next_reshow_delay(base::TimeDelta::FromSeconds(kInitialReshowDelaySec)), |
| 135 timer(false, false) { |
| 136 } |
| 137 |
| 138 HungPluginTabHelper::PluginState::~PluginState() { |
| 139 } |
| 140 |
| 141 // ----------------------------------------------------------------------------- |
| 142 |
| 143 HungPluginTabHelper::HungPluginTabHelper(content::WebContents* contents) |
| 144 : contents_(contents) { |
| 145 } |
| 146 |
| 147 HungPluginTabHelper::~HungPluginTabHelper() { |
| 148 } |
| 149 |
| 150 void HungPluginTabHelper::PluginCrashed(const FilePath& plugin_path) { |
| 151 // TODO(brettw) ideally this would take the child process ID. When we do this |
| 152 // for NaCl plugins, we'll want to know exactly which process it was since |
| 153 // the path won't be useful. |
| 154 InfoBarTabHelper* infobar_helper = GetInfoBarHelper(); |
| 155 if (!infobar_helper) |
| 156 return; |
| 157 |
| 158 // For now, just do a brute-force search to see if we have this plugin. Since |
| 159 // we'll normally have 0 or 1, this is fast. |
| 160 for (PluginStateMap::iterator i = hung_plugins_.begin(); |
| 161 i != hung_plugins_.end(); ++i) { |
| 162 if (i->second->path == plugin_path) { |
| 163 if (i->second->info_bar) |
| 164 infobar_helper->RemoveInfoBar(i->second->info_bar); |
| 165 hung_plugins_.erase(i); |
| 166 break; |
| 167 } |
| 168 } |
| 169 } |
| 170 |
| 171 void HungPluginTabHelper::PluginHungStatusChanged(int plugin_child_id, |
| 172 const FilePath& plugin_path, |
| 173 bool is_hung) { |
| 174 InfoBarTabHelper* infobar_helper = GetInfoBarHelper(); |
| 175 if (!infobar_helper) |
| 176 return; |
| 177 |
| 178 PluginStateMap::iterator found = hung_plugins_.find(plugin_child_id); |
| 179 if (found != hung_plugins_.end()) { |
| 180 if (!is_hung) { |
| 181 // Hung plugin became un-hung, close the infobar and delete our info. |
| 182 if (found->second->info_bar) |
| 183 infobar_helper->RemoveInfoBar(found->second->info_bar); |
| 184 hung_plugins_.erase(found); |
| 185 } |
| 186 return; |
| 187 } |
| 188 |
| 189 string16 plugin_name = |
| 190 content::PluginService::GetInstance()->GetPluginDisplayNameByPath( |
| 191 plugin_path); |
| 192 |
| 193 linked_ptr<PluginState> state(new PluginState(plugin_path, plugin_name)); |
| 194 hung_plugins_[plugin_child_id] = state; |
| 195 ShowBar(plugin_child_id, state.get()); |
| 196 } |
| 197 |
| 198 void HungPluginTabHelper::KillPlugin(int child_id) { |
| 199 PluginStateMap::iterator found = hung_plugins_.find(child_id); |
| 200 if (found == hung_plugins_.end()) { |
| 201 NOTREACHED(); |
| 202 return; |
| 203 } |
| 204 |
| 205 content::BrowserThread::PostTask(content::BrowserThread::IO, |
| 206 FROM_HERE, |
| 207 base::Bind(&KillPluginOnIOThread, child_id)); |
| 208 CloseBar(found->second.get()); |
| 209 } |
| 210 |
| 211 void HungPluginTabHelper::BarClosed(int child_id) { |
| 212 PluginStateMap::iterator found = hung_plugins_.find(child_id); |
| 213 if (found == hung_plugins_.end() || !found->second->info_bar) { |
| 214 NOTREACHED(); |
| 215 return; |
| 216 } |
| 217 found->second->info_bar = NULL; |
| 218 |
| 219 // Schedule the timer to re-show the infobar if the plugin continues to be |
| 220 // hung. |
| 221 found->second->timer.Start(FROM_HERE, found->second->next_reshow_delay, |
| 222 base::Bind(&HungPluginTabHelper::OnReshowTimer, |
| 223 base::Unretained(this), |
| 224 child_id)); |
| 225 |
| 226 // Next time we do this, delay it twice as long to avoid being annoying. |
| 227 found->second->next_reshow_delay *= 2; |
| 228 } |
| 229 |
| 230 void HungPluginTabHelper::OnReshowTimer(int child_id) { |
| 231 PluginStateMap::iterator found = hung_plugins_.find(child_id); |
| 232 if (found == hung_plugins_.end() || found->second->info_bar) { |
| 233 // The timer should be cancelled if the record isn't in our map anymore. |
| 234 NOTREACHED(); |
| 235 return; |
| 236 } |
| 237 ShowBar(child_id, found->second.get()); |
| 238 } |
| 239 |
| 240 void HungPluginTabHelper::ShowBar(int child_id, PluginState* state) { |
| 241 InfoBarTabHelper* infobar_helper = GetInfoBarHelper(); |
| 242 if (!infobar_helper) |
| 243 return; |
| 244 |
| 245 DCHECK(!state->info_bar); |
| 246 state->info_bar = new InfoBarDelegate(this, infobar_helper, |
| 247 child_id, state->name); |
| 248 infobar_helper->AddInfoBar(state->info_bar); |
| 249 } |
| 250 |
| 251 void HungPluginTabHelper::CloseBar(PluginState* state) { |
| 252 InfoBarTabHelper* infobar_helper = GetInfoBarHelper(); |
| 253 if (!infobar_helper) |
| 254 return; |
| 255 |
| 256 if (state->info_bar) { |
| 257 infobar_helper->RemoveInfoBar(state->info_bar); |
| 258 state->info_bar = NULL; |
| 259 } |
| 260 } |
| 261 |
| 262 InfoBarTabHelper* HungPluginTabHelper::GetInfoBarHelper() { |
| 263 TabContentsWrapper* tcw = |
| 264 TabContentsWrapper::GetCurrentWrapperForContents(contents_); |
| 265 if (!tcw) |
| 266 return NULL; |
| 267 return tcw->infobar_tab_helper(); |
| 268 } |
OLD | NEW |