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) it might be nice to do some stuff to capture a stack. | |
yzshen1
2012/04/11 18:30:46
Please do so soon. :)
Besides, please record some
brettw
2012/04/11 19:56:08
I filed a bug for this and updated the comment.
| |
44 // 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 : is_hung(true), | |
132 path(p), | |
133 name(n), | |
134 info_bar(NULL), | |
135 next_reshow_delay(base::TimeDelta::FromSeconds(kInitialReshowDelaySec)), | |
136 timer(false, false) { | |
137 } | |
138 | |
139 HungPluginTabHelper::PluginState::~PluginState() { | |
140 } | |
141 | |
142 // ----------------------------------------------------------------------------- | |
143 | |
144 HungPluginTabHelper::HungPluginTabHelper(content::WebContents* contents) | |
145 : contents_(contents) { | |
146 } | |
147 | |
148 HungPluginTabHelper::~HungPluginTabHelper() { | |
149 } | |
150 | |
151 void HungPluginTabHelper::PluginCrashed(const FilePath& plugin_path) { | |
152 // TODO(brettw) ideally this would take the child process ID. When we do this | |
153 // for NaCl plugins, we'll want to know exactly which process it was since | |
154 // the path won't be useful. | |
155 TabContentsWrapper* tcw = | |
yzshen1
2012/04/11 18:30:46
line 155-159 can be extracted as a separate method
| |
156 TabContentsWrapper::GetCurrentWrapperForContents(contents_); | |
157 if (!tcw) | |
158 return; | |
159 InfoBarTabHelper* infobar_helper = tcw->infobar_tab_helper(); | |
160 | |
161 // For now, just do a brute-force search to see if we have this plugin. Since | |
162 // we'll normally have 0 or 1, this is fast. | |
163 for (PluginStateMap::iterator i = hung_plugins_.begin(); | |
164 i != hung_plugins_.end(); ++i) { | |
165 if (i->second->path == plugin_path) { | |
166 if (i->second->info_bar) | |
167 infobar_helper->RemoveInfoBar(i->second->info_bar); | |
168 hung_plugins_.erase(i); | |
169 break; | |
170 } | |
171 } | |
172 } | |
173 | |
174 void HungPluginTabHelper::PluginHungStatusChanged(int plugin_child_id, | |
175 const FilePath& plugin_path, | |
176 bool is_hung) { | |
177 TabContentsWrapper* tcw = | |
178 TabContentsWrapper::GetCurrentWrapperForContents(contents_); | |
179 if (!tcw) | |
180 return; | |
181 InfoBarTabHelper* infobar_helper = tcw->infobar_tab_helper(); | |
182 | |
183 PluginStateMap::iterator found = hung_plugins_.find(plugin_child_id); | |
184 if (found != hung_plugins_.end()) { | |
185 if (!is_hung) { | |
186 // Hung plugin became un-hung, close the infobar and delete our info. | |
187 if (found->second->info_bar) | |
188 infobar_helper->RemoveInfoBar(found->second->info_bar); | |
189 hung_plugins_.erase(found); | |
190 } | |
191 return; | |
192 } | |
193 | |
194 string16 plugin_name = | |
195 content::PluginService::GetInstance()->GetPluginDisplayNameByPath( | |
196 plugin_path); | |
197 | |
198 linked_ptr<PluginState> state(new PluginState(plugin_path, plugin_name)); | |
199 hung_plugins_[plugin_child_id] = state; | |
200 ShowBar(plugin_child_id, state.get()); | |
201 } | |
202 | |
203 void HungPluginTabHelper::KillPlugin(int child_id) { | |
204 PluginStateMap::iterator found = hung_plugins_.find(child_id); | |
205 if (found == hung_plugins_.end()) { | |
206 NOTREACHED(); | |
207 return; | |
208 } | |
209 | |
210 content::BrowserThread::PostTask(content::BrowserThread::IO, | |
211 FROM_HERE, | |
212 base::Bind(&KillPluginOnIOThread, child_id)); | |
213 CloseBar(found->second.get()); | |
214 } | |
215 | |
216 void HungPluginTabHelper::BarClosed(int child_id) { | |
217 PluginStateMap::iterator found = hung_plugins_.find(child_id); | |
218 if (found == hung_plugins_.end() || !found->second->info_bar) { | |
219 NOTREACHED(); | |
220 return; | |
221 } | |
222 found->second->info_bar = NULL; | |
223 found->second->bar_closed_time = base::TimeTicks::Now(); | |
224 | |
225 // Schedule the timer to re-show the infobar if the plugin continues to be | |
226 // hung. | |
227 found->second->timer.Start(FROM_HERE, found->second->next_reshow_delay, | |
228 base::Bind(&HungPluginTabHelper::OnReshowTimer, | |
229 base::Unretained(this), | |
230 child_id)); | |
231 | |
232 // Next time we do this, delay it twice as long to avoid being annoying. | |
233 found->second->next_reshow_delay *= 2; | |
234 } | |
235 | |
236 void HungPluginTabHelper::OnReshowTimer(int child_id) { | |
237 PluginStateMap::iterator found = hung_plugins_.find(child_id); | |
238 if (found == hung_plugins_.end() || found->second->info_bar) { | |
239 // The timer should be cancelled if the record isn't in our map anymore. | |
240 NOTREACHED(); | |
241 return; | |
242 } | |
243 ShowBar(child_id, found->second.get()); | |
244 } | |
245 | |
246 void HungPluginTabHelper::ShowBar(int child_id, PluginState* state) { | |
247 TabContentsWrapper* tcw = | |
248 TabContentsWrapper::GetCurrentWrapperForContents(contents_); | |
249 if (!tcw) | |
250 return; | |
251 InfoBarTabHelper* infobar_helper = tcw->infobar_tab_helper(); | |
252 | |
253 DCHECK(!state->info_bar); | |
254 state->info_bar = new InfoBarDelegate(this, infobar_helper, | |
255 child_id, state->name); | |
256 infobar_helper->AddInfoBar(state->info_bar); | |
257 } | |
258 | |
259 void HungPluginTabHelper::CloseBar(PluginState* state) { | |
260 TabContentsWrapper* tcw = | |
261 TabContentsWrapper::GetCurrentWrapperForContents(contents_); | |
262 if (!tcw) | |
263 return; | |
264 InfoBarTabHelper* infobar_helper = tcw->infobar_tab_helper(); | |
265 | |
266 if (state->info_bar) { | |
267 infobar_helper->RemoveInfoBar(state->info_bar); | |
268 state->info_bar = NULL; | |
269 } | |
270 } | |
OLD | NEW |