Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / hung_plugin_tab_helper.cc
blob229f0442b0784c36f933076a0a12c1087214b7d9
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"
7 #include "base/bind.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/confirm_infobar_delegate.h"
15 #include "chrome/browser/infobars/infobar.h"
16 #include "chrome/browser/infobars/infobar_service.h"
17 #include "chrome/common/chrome_version_info.h"
18 #include "content/public/browser/browser_child_process_host_iterator.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "content/public/browser/child_process_data.h"
21 #include "content/public/browser/notification_details.h"
22 #include "content/public/browser/notification_service.h"
23 #include "content/public/browser/plugin_service.h"
24 #include "content/public/browser/render_process_host.h"
25 #include "content/public/common/process_type.h"
26 #include "content/public/common/result_codes.h"
27 #include "grit/chromium_strings.h"
28 #include "grit/generated_resources.h"
29 #include "grit/locale_settings.h"
30 #include "grit/theme_resources.h"
31 #include "ui/base/l10n/l10n_util.h"
33 #if defined(OS_WIN)
34 #include "base/win/scoped_handle.h"
35 #include "chrome/browser/hang_monitor/hang_crash_dump_win.h"
36 #endif
39 namespace {
41 #if defined(OS_WIN)
43 // OwnedHandleVector ----------------------------------------------------------
45 class OwnedHandleVector {
46 public:
47 typedef std::vector<HANDLE> Handles;
48 OwnedHandleVector();
49 ~OwnedHandleVector();
51 Handles* data() { return &data_; }
53 private:
54 Handles data_;
56 DISALLOW_COPY_AND_ASSIGN(OwnedHandleVector);
59 OwnedHandleVector::OwnedHandleVector() {
62 OwnedHandleVector::~OwnedHandleVector() {
63 for (Handles::iterator iter = data_.begin(); iter != data_.end(); ++iter)
64 ::CloseHandle(*iter);
68 // Helpers --------------------------------------------------------------------
70 const char kDumpChildProcessesSequenceName[] = "DumpChildProcesses";
72 void DumpBrowserInBlockingPool() {
73 CrashDumpForHangDebugging(::GetCurrentProcess());
76 void DumpRenderersInBlockingPool(OwnedHandleVector* renderer_handles) {
77 for (OwnedHandleVector::Handles::const_iterator iter =
78 renderer_handles->data()->begin();
79 iter != renderer_handles->data()->end(); ++iter) {
80 CrashDumpForHangDebugging(*iter);
84 void DumpAndTerminatePluginInBlockingPool(
85 base::win::ScopedHandle* plugin_handle) {
86 CrashDumpAndTerminateHungChildProcess(plugin_handle->Get());
89 #endif // defined(OS_WIN)
91 // Called on the I/O thread to actually kill the plugin with the given child
92 // ID. We specifically don't want this to be a member function since if the
93 // user chooses to kill the plugin, we want to kill it even if they close the
94 // tab first.
96 // Be careful with the child_id. It's supplied by the renderer which might be
97 // hacked.
98 void KillPluginOnIOThread(int child_id) {
99 content::BrowserChildProcessHostIterator iter(
100 content::PROCESS_TYPE_PPAPI_PLUGIN);
101 while (!iter.Done()) {
102 const content::ChildProcessData& data = iter.GetData();
103 if (data.id == child_id) {
104 #if defined(OS_WIN)
105 HANDLE handle = NULL;
106 HANDLE current_process = ::GetCurrentProcess();
107 ::DuplicateHandle(current_process, data.handle, current_process, &handle,
108 0, FALSE, DUPLICATE_SAME_ACCESS);
109 // Run it in blocking pool so that it won't block the I/O thread. Besides,
110 // we would like to make sure that it happens after dumping renderers.
111 content::BrowserThread::PostBlockingPoolSequencedTask(
112 kDumpChildProcessesSequenceName, FROM_HERE,
113 base::Bind(&DumpAndTerminatePluginInBlockingPool,
114 base::Owned(new base::win::ScopedHandle(handle))));
115 #else
116 base::KillProcess(data.handle, content::RESULT_CODE_HUNG, false);
117 #endif
118 break;
120 ++iter;
122 // Ignore the case where we didn't find the plugin, it may have terminated
123 // before this function could run.
126 } // namespace
129 // HungPluginInfoBarDelegate --------------------------------------------------
131 class HungPluginInfoBarDelegate : public ConfirmInfoBarDelegate {
132 public:
133 // Creates a hung plugin infobar and delegate and adds the infobar to
134 // |infobar_service|. Returns the infobar if it was successfully added.
135 static InfoBar* Create(InfoBarService* infobar_service,
136 HungPluginTabHelper* helper,
137 int plugin_child_id,
138 const base::string16& plugin_name);
140 private:
141 HungPluginInfoBarDelegate(HungPluginTabHelper* helper,
142 int plugin_child_id,
143 const base::string16& plugin_name);
144 virtual ~HungPluginInfoBarDelegate();
146 // ConfirmInfoBarDelegate:
147 virtual int GetIconID() const OVERRIDE;
148 virtual base::string16 GetMessageText() const OVERRIDE;
149 virtual int GetButtons() const OVERRIDE;
150 virtual base::string16 GetButtonLabel(InfoBarButton button) const OVERRIDE;
151 virtual bool Accept() OVERRIDE;
153 HungPluginTabHelper* helper_;
154 int plugin_child_id_;
156 base::string16 message_;
157 base::string16 button_text_;
160 // static
161 InfoBar* HungPluginInfoBarDelegate::Create(InfoBarService* infobar_service,
162 HungPluginTabHelper* helper,
163 int plugin_child_id,
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,
172 int plugin_child_id,
173 const base::string16& plugin_name)
174 : ConfirmInfoBarDelegate(),
175 helper_(helper),
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 {
191 return message_;
194 int HungPluginInfoBarDelegate::GetButtons() const {
195 return BUTTON_OK;
198 base::string16 HungPluginInfoBarDelegate::GetButtonLabel(
199 InfoBarButton button) const {
200 return button_text_;
203 bool HungPluginInfoBarDelegate::Accept() {
204 helper_->KillPlugin(plugin_child_id_);
205 return true;
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);
218 ~PluginState();
220 base::FilePath path;
221 base::string16 name;
223 // Possibly-null if we're not showing an infobar right now.
224 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.
231 base::Timer timer;
233 private:
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
238 // not be copied.
239 DISALLOW_COPY_AND_ASSIGN(PluginState);
242 // static
243 const int HungPluginTabHelper::PluginState::kInitialReshowDelaySec = 10;
245 HungPluginTabHelper::PluginState::PluginState(const base::FilePath& p,
246 const base::string16& n)
247 : path(p),
248 name(n),
249 infobar(NULL),
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)
279 return;
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);
289 break;
294 void HungPluginTabHelper::PluginHungStatusChanged(
295 int plugin_child_id,
296 const base::FilePath& plugin_path,
297 bool is_hung) {
298 InfoBarService* infobar_service =
299 InfoBarService::FromWebContents(web_contents());
300 if (!infobar_service)
301 return;
303 PluginStateMap::iterator found = hung_plugins_.find(plugin_child_id);
304 if (found != hung_plugins_.end()) {
305 if (!is_hung) {
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);
311 return;
314 base::string16 plugin_name =
315 content::PluginService::GetInstance()->GetPluginDisplayNameByPath(
316 plugin_path);
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(
324 int type,
325 const content::NotificationSource& source,
326 const content::NotificationDetails& details) {
327 DCHECK_EQ(chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, type);
328 InfoBar* infobar = content::Details<InfoBar::RemovedDetails>(details)->first;
329 for (PluginStateMap::iterator i = hung_plugins_.begin();
330 i != hung_plugins_.end(); ++i) {
331 PluginState* state = i->second.get();
332 if (state->infobar == infobar) {
333 state->infobar = NULL;
335 // Schedule the timer to re-show the infobar if the plugin continues to be
336 // hung.
337 state->timer.Start(FROM_HERE, state->next_reshow_delay,
338 base::Bind(&HungPluginTabHelper::OnReshowTimer,
339 base::Unretained(this),
340 i->first));
342 // Next time we do this, delay it twice as long to avoid being annoying.
343 state->next_reshow_delay *= 2;
344 return;
349 void HungPluginTabHelper::KillPlugin(int child_id) {
350 #if defined(OS_WIN)
351 // Dump renderers that are sending or receiving pepper messages, in order to
352 // diagnose inter-process deadlocks.
353 // Only do that on the Canary channel, for 20% of pepper plugin hangs.
354 if (base::RandInt(0, 100) < 20) {
355 chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
356 if (channel == chrome::VersionInfo::CHANNEL_CANARY) {
357 scoped_ptr<OwnedHandleVector> renderer_handles(new OwnedHandleVector);
358 HANDLE current_process = ::GetCurrentProcess();
359 content::RenderProcessHost::iterator renderer_iter =
360 content::RenderProcessHost::AllHostsIterator();
361 for (; !renderer_iter.IsAtEnd(); renderer_iter.Advance()) {
362 content::RenderProcessHost* host = renderer_iter.GetCurrentValue();
363 HANDLE handle = NULL;
364 ::DuplicateHandle(current_process, host->GetHandle(), current_process,
365 &handle, 0, FALSE, DUPLICATE_SAME_ACCESS);
366 renderer_handles->data()->push_back(handle);
368 // If there are a lot of renderer processes, it is likely that we will
369 // generate too many crash dumps. They might not all be uploaded/recorded
370 // due to our crash dump uploading restrictions. So we just don't generate
371 // renderer crash dumps in that case.
372 if (renderer_handles->data()->size() > 0 &&
373 renderer_handles->data()->size() < 4) {
374 content::BrowserThread::PostBlockingPoolSequencedTask(
375 kDumpChildProcessesSequenceName, FROM_HERE,
376 base::Bind(&DumpBrowserInBlockingPool));
377 content::BrowserThread::PostBlockingPoolSequencedTask(
378 kDumpChildProcessesSequenceName, FROM_HERE,
379 base::Bind(&DumpRenderersInBlockingPool,
380 base::Owned(renderer_handles.release())));
384 #endif
386 PluginStateMap::iterator found = hung_plugins_.find(child_id);
387 DCHECK(found != hung_plugins_.end());
389 content::BrowserThread::PostTask(content::BrowserThread::IO,
390 FROM_HERE,
391 base::Bind(&KillPluginOnIOThread, child_id));
392 CloseBar(found->second.get());
395 void HungPluginTabHelper::OnReshowTimer(int child_id) {
396 // The timer should have been cancelled if the record isn't in our map
397 // anymore.
398 PluginStateMap::iterator found = hung_plugins_.find(child_id);
399 DCHECK(found != hung_plugins_.end());
400 DCHECK(!found->second->infobar);
401 ShowBar(child_id, found->second.get());
404 void HungPluginTabHelper::ShowBar(int child_id, PluginState* state) {
405 InfoBarService* infobar_service =
406 InfoBarService::FromWebContents(web_contents());
407 if (!infobar_service)
408 return;
410 DCHECK(!state->infobar);
411 state->infobar = HungPluginInfoBarDelegate::Create(infobar_service, this,
412 child_id, state->name);
415 void HungPluginTabHelper::CloseBar(PluginState* state) {
416 InfoBarService* infobar_service =
417 InfoBarService::FromWebContents(web_contents());
418 if (infobar_service && state->infobar) {
419 infobar_service->RemoveInfoBar(state->infobar);
420 state->infobar = NULL;