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.
5 #include "chrome/browser/ui/hung_plugin_tab_helper.h"
8 #include "base/files/file_path.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/process/process.h"
11 #include "base/rand_util.h"
12 #include "build/build_config.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/infobars/infobar_service.h"
15 #include "chrome/common/chrome_version_info.h"
16 #include "chrome/grit/generated_resources.h"
17 #include "chrome/grit/locale_settings.h"
18 #include "components/infobars/core/confirm_infobar_delegate.h"
19 #include "components/infobars/core/infobar.h"
20 #include "content/public/browser/browser_child_process_host_iterator.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/child_process_data.h"
23 #include "content/public/browser/notification_details.h"
24 #include "content/public/browser/notification_service.h"
25 #include "content/public/browser/plugin_service.h"
26 #include "content/public/browser/render_process_host.h"
27 #include "content/public/common/process_type.h"
28 #include "content/public/common/result_codes.h"
29 #include "grit/theme_resources.h"
30 #include "ui/base/l10n/l10n_util.h"
33 #include "base/win/scoped_handle.h"
34 #include "chrome/browser/hang_monitor/hang_crash_dump_win.h"
42 // OwnedHandleVector ----------------------------------------------------------
44 class OwnedHandleVector
{
46 typedef std::vector
<HANDLE
> Handles
;
50 Handles
* data() { return &data_
; }
55 DISALLOW_COPY_AND_ASSIGN(OwnedHandleVector
);
58 OwnedHandleVector::OwnedHandleVector() {
61 OwnedHandleVector::~OwnedHandleVector() {
62 for (Handles::iterator iter
= data_
.begin(); iter
!= data_
.end(); ++iter
)
67 // Helpers --------------------------------------------------------------------
69 const char kDumpChildProcessesSequenceName
[] = "DumpChildProcesses";
71 void DumpBrowserInBlockingPool() {
72 CrashDumpForHangDebugging(::GetCurrentProcess());
75 void DumpRenderersInBlockingPool(OwnedHandleVector
* renderer_handles
) {
76 for (OwnedHandleVector::Handles::const_iterator iter
=
77 renderer_handles
->data()->begin();
78 iter
!= renderer_handles
->data()->end(); ++iter
) {
79 CrashDumpForHangDebugging(*iter
);
83 void DumpAndTerminatePluginInBlockingPool(
84 base::win::ScopedHandle
* plugin_handle
) {
85 CrashDumpAndTerminateHungChildProcess(plugin_handle
->Get());
88 #endif // defined(OS_WIN)
90 // Called on the I/O thread to actually kill the plugin with the given child
91 // ID. We specifically don't want this to be a member function since if the
92 // user chooses to kill the plugin, we want to kill it even if they close the
95 // Be careful with the child_id. It's supplied by the renderer which might be
97 void KillPluginOnIOThread(int child_id
) {
98 content::BrowserChildProcessHostIterator
iter(
99 content::PROCESS_TYPE_PPAPI_PLUGIN
);
100 while (!iter
.Done()) {
101 const content::ChildProcessData
& data
= iter
.GetData();
102 if (data
.id
== child_id
) {
104 HANDLE handle
= NULL
;
105 HANDLE current_process
= ::GetCurrentProcess();
106 ::DuplicateHandle(current_process
, data
.handle
, current_process
, &handle
,
107 0, FALSE
, DUPLICATE_SAME_ACCESS
);
108 // Run it in blocking pool so that it won't block the I/O thread. Besides,
109 // we would like to make sure that it happens after dumping renderers.
110 content::BrowserThread::PostBlockingPoolSequencedTask(
111 kDumpChildProcessesSequenceName
, FROM_HERE
,
112 base::Bind(&DumpAndTerminatePluginInBlockingPool
,
113 base::Owned(new base::win::ScopedHandle(handle
))));
115 base::KillProcess(data
.handle
, content::RESULT_CODE_HUNG
, false);
121 // Ignore the case where we didn't find the plugin, it may have terminated
122 // before this function could run.
128 // HungPluginInfoBarDelegate --------------------------------------------------
130 class HungPluginInfoBarDelegate
: public ConfirmInfoBarDelegate
{
132 // Creates a hung plugin infobar and delegate and adds the infobar to
133 // |infobar_service|. Returns the infobar if it was successfully added.
134 static infobars::InfoBar
* Create(InfoBarService
* infobar_service
,
135 HungPluginTabHelper
* helper
,
137 const base::string16
& plugin_name
);
140 HungPluginInfoBarDelegate(HungPluginTabHelper
* helper
,
142 const base::string16
& plugin_name
);
143 virtual ~HungPluginInfoBarDelegate();
145 // ConfirmInfoBarDelegate:
146 virtual int GetIconID() const override
;
147 virtual base::string16
GetMessageText() const override
;
148 virtual int GetButtons() const override
;
149 virtual base::string16
GetButtonLabel(InfoBarButton button
) const override
;
150 virtual bool Accept() override
;
152 HungPluginTabHelper
* helper_
;
153 int plugin_child_id_
;
155 base::string16 message_
;
156 base::string16 button_text_
;
160 infobars::InfoBar
* HungPluginInfoBarDelegate::Create(
161 InfoBarService
* infobar_service
,
162 HungPluginTabHelper
* helper
,
164 const base::string16
& plugin_name
) {
165 return infobar_service
->AddInfoBar(ConfirmInfoBarDelegate::CreateInfoBar(
166 scoped_ptr
<ConfirmInfoBarDelegate
>(new HungPluginInfoBarDelegate(
167 helper
, plugin_child_id
, plugin_name
))));
170 HungPluginInfoBarDelegate::HungPluginInfoBarDelegate(
171 HungPluginTabHelper
* helper
,
173 const base::string16
& plugin_name
)
174 : ConfirmInfoBarDelegate(),
176 plugin_child_id_(plugin_child_id
),
177 message_(l10n_util::GetStringFUTF16(
178 IDS_BROWSER_HANGMONITOR_PLUGIN_INFOBAR
, plugin_name
)),
179 button_text_(l10n_util::GetStringUTF16(
180 IDS_BROWSER_HANGMONITOR_PLUGIN_INFOBAR_KILLBUTTON
)) {
183 HungPluginInfoBarDelegate::~HungPluginInfoBarDelegate() {
186 int HungPluginInfoBarDelegate::GetIconID() const {
187 return IDR_INFOBAR_PLUGIN_CRASHED
;
190 base::string16
HungPluginInfoBarDelegate::GetMessageText() const {
194 int HungPluginInfoBarDelegate::GetButtons() const {
198 base::string16
HungPluginInfoBarDelegate::GetButtonLabel(
199 InfoBarButton button
) const {
203 bool HungPluginInfoBarDelegate::Accept() {
204 helper_
->KillPlugin(plugin_child_id_
);
209 // HungPluginTabHelper::PluginState -------------------------------------------
211 // Per-plugin state (since there could be more than one plugin hung). The
212 // integer key is the child process ID of the plugin process. This maintains
213 // the state for all plugins on this page that are currently hung, whether or
214 // not we're currently showing the infobar.
215 struct HungPluginTabHelper::PluginState
{
216 // Initializes the plugin state to be a hung plugin.
217 PluginState(const base::FilePath
& p
, const base::string16
& n
);
223 // Possibly-null if we're not showing an infobar right now.
224 infobars::InfoBar
* infobar
;
226 // Time to delay before re-showing the infobar for a hung plugin. This is
227 // increased each time the user cancels it.
228 base::TimeDelta next_reshow_delay
;
230 // Handles calling the helper when the infobar should be re-shown.
234 // Initial delay in seconds before re-showing the hung plugin message.
235 static const int kInitialReshowDelaySec
;
237 // Since the scope of the timer manages our callback, this struct should
239 DISALLOW_COPY_AND_ASSIGN(PluginState
);
243 const int HungPluginTabHelper::PluginState::kInitialReshowDelaySec
= 10;
245 HungPluginTabHelper::PluginState::PluginState(const base::FilePath
& p
,
246 const base::string16
& n
)
250 next_reshow_delay(base::TimeDelta::FromSeconds(kInitialReshowDelaySec
)),
251 timer(false, false) {
254 HungPluginTabHelper::PluginState::~PluginState() {
258 // HungPluginTabHelper --------------------------------------------------------
260 DEFINE_WEB_CONTENTS_USER_DATA_KEY(HungPluginTabHelper
);
262 HungPluginTabHelper::HungPluginTabHelper(content::WebContents
* contents
)
263 : content::WebContentsObserver(contents
) {
264 registrar_
.Add(this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
,
265 content::NotificationService::AllSources());
268 HungPluginTabHelper::~HungPluginTabHelper() {
271 void HungPluginTabHelper::PluginCrashed(const base::FilePath
& plugin_path
,
272 base::ProcessId plugin_pid
) {
273 // TODO(brettw) ideally this would take the child process ID. When we do this
274 // for NaCl plugins, we'll want to know exactly which process it was since
275 // the path won't be useful.
276 InfoBarService
* infobar_service
=
277 InfoBarService::FromWebContents(web_contents());
278 if (!infobar_service
)
281 // For now, just do a brute-force search to see if we have this plugin. Since
282 // we'll normally have 0 or 1, this is fast.
283 for (PluginStateMap::iterator i
= hung_plugins_
.begin();
284 i
!= hung_plugins_
.end(); ++i
) {
285 if (i
->second
->path
== plugin_path
) {
286 if (i
->second
->infobar
)
287 infobar_service
->RemoveInfoBar(i
->second
->infobar
);
288 hung_plugins_
.erase(i
);
294 void HungPluginTabHelper::PluginHungStatusChanged(
296 const base::FilePath
& plugin_path
,
298 InfoBarService
* infobar_service
=
299 InfoBarService::FromWebContents(web_contents());
300 if (!infobar_service
)
303 PluginStateMap::iterator found
= hung_plugins_
.find(plugin_child_id
);
304 if (found
!= hung_plugins_
.end()) {
306 // Hung plugin became un-hung, close the infobar and delete our info.
307 if (found
->second
->infobar
)
308 infobar_service
->RemoveInfoBar(found
->second
->infobar
);
309 hung_plugins_
.erase(found
);
314 base::string16 plugin_name
=
315 content::PluginService::GetInstance()->GetPluginDisplayNameByPath(
318 linked_ptr
<PluginState
> state(new PluginState(plugin_path
, plugin_name
));
319 hung_plugins_
[plugin_child_id
] = state
;
320 ShowBar(plugin_child_id
, state
.get());
323 void HungPluginTabHelper::Observe(
325 const content::NotificationSource
& source
,
326 const content::NotificationDetails
& details
) {
327 DCHECK_EQ(chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
, type
);
328 infobars::InfoBar
* infobar
=
329 content::Details
<infobars::InfoBar::RemovedDetails
>(details
)->first
;
330 for (PluginStateMap::iterator i
= hung_plugins_
.begin();
331 i
!= hung_plugins_
.end(); ++i
) {
332 PluginState
* state
= i
->second
.get();
333 if (state
->infobar
== infobar
) {
334 state
->infobar
= NULL
;
336 // Schedule the timer to re-show the infobar if the plugin continues to be
338 state
->timer
.Start(FROM_HERE
, state
->next_reshow_delay
,
339 base::Bind(&HungPluginTabHelper::OnReshowTimer
,
340 base::Unretained(this),
343 // Next time we do this, delay it twice as long to avoid being annoying.
344 state
->next_reshow_delay
*= 2;
350 void HungPluginTabHelper::KillPlugin(int child_id
) {
352 // Dump renderers that are sending or receiving pepper messages, in order to
353 // diagnose inter-process deadlocks.
354 // Only do that on the Canary channel, for 20% of pepper plugin hangs.
355 if (base::RandInt(0, 100) < 20) {
356 chrome::VersionInfo::Channel channel
= chrome::VersionInfo::GetChannel();
357 if (channel
== chrome::VersionInfo::CHANNEL_CANARY
) {
358 scoped_ptr
<OwnedHandleVector
> renderer_handles(new OwnedHandleVector
);
359 HANDLE current_process
= ::GetCurrentProcess();
360 content::RenderProcessHost::iterator renderer_iter
=
361 content::RenderProcessHost::AllHostsIterator();
362 for (; !renderer_iter
.IsAtEnd(); renderer_iter
.Advance()) {
363 content::RenderProcessHost
* host
= renderer_iter
.GetCurrentValue();
364 HANDLE handle
= NULL
;
365 ::DuplicateHandle(current_process
, host
->GetHandle(), current_process
,
366 &handle
, 0, FALSE
, DUPLICATE_SAME_ACCESS
);
367 renderer_handles
->data()->push_back(handle
);
369 // If there are a lot of renderer processes, it is likely that we will
370 // generate too many crash dumps. They might not all be uploaded/recorded
371 // due to our crash dump uploading restrictions. So we just don't generate
372 // renderer crash dumps in that case.
373 if (renderer_handles
->data()->size() > 0 &&
374 renderer_handles
->data()->size() < 4) {
375 content::BrowserThread::PostBlockingPoolSequencedTask(
376 kDumpChildProcessesSequenceName
, FROM_HERE
,
377 base::Bind(&DumpBrowserInBlockingPool
));
378 content::BrowserThread::PostBlockingPoolSequencedTask(
379 kDumpChildProcessesSequenceName
, FROM_HERE
,
380 base::Bind(&DumpRenderersInBlockingPool
,
381 base::Owned(renderer_handles
.release())));
387 PluginStateMap::iterator found
= hung_plugins_
.find(child_id
);
388 DCHECK(found
!= hung_plugins_
.end());
390 content::BrowserThread::PostTask(content::BrowserThread::IO
,
392 base::Bind(&KillPluginOnIOThread
, child_id
));
393 CloseBar(found
->second
.get());
396 void HungPluginTabHelper::OnReshowTimer(int child_id
) {
397 // The timer should have been cancelled if the record isn't in our map
399 PluginStateMap::iterator found
= hung_plugins_
.find(child_id
);
400 DCHECK(found
!= hung_plugins_
.end());
401 DCHECK(!found
->second
->infobar
);
402 ShowBar(child_id
, found
->second
.get());
405 void HungPluginTabHelper::ShowBar(int child_id
, PluginState
* state
) {
406 InfoBarService
* infobar_service
=
407 InfoBarService::FromWebContents(web_contents());
408 if (!infobar_service
)
411 DCHECK(!state
->infobar
);
412 state
->infobar
= HungPluginInfoBarDelegate::Create(infobar_service
, this,
413 child_id
, state
->name
);
416 void HungPluginTabHelper::CloseBar(PluginState
* state
) {
417 InfoBarService
* infobar_service
=
418 InfoBarService::FromWebContents(web_contents());
419 if (infobar_service
&& state
->infobar
) {
420 infobar_service
->RemoveInfoBar(state
->infobar
);
421 state
->infobar
= NULL
;