Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / ui / hung_plugin_tab_helper.cc
blobafcd1e8d8590f6aef2e901b3bc85b3fa5d98ff82
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/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"
32 #if defined(OS_WIN)
33 #include "base/win/scoped_handle.h"
34 #include "chrome/browser/hang_monitor/hang_crash_dump_win.h"
35 #endif
38 namespace {
40 #if defined(OS_WIN)
42 // OwnedHandleVector ----------------------------------------------------------
44 class OwnedHandleVector {
45 public:
46 typedef std::vector<HANDLE> Handles;
47 OwnedHandleVector();
48 ~OwnedHandleVector();
50 Handles* data() { return &data_; }
52 private:
53 Handles data_;
55 DISALLOW_COPY_AND_ASSIGN(OwnedHandleVector);
58 OwnedHandleVector::OwnedHandleVector() {
61 OwnedHandleVector::~OwnedHandleVector() {
62 for (Handles::iterator iter = data_.begin(); iter != data_.end(); ++iter)
63 ::CloseHandle(*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
93 // tab first.
95 // Be careful with the child_id. It's supplied by the renderer which might be
96 // hacked.
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) {
103 #if defined(OS_WIN)
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))));
114 #else
115 base::Process process =
116 base::Process::DeprecatedGetProcessFromHandle(data.handle);
117 process.Terminate(content::RESULT_CODE_HUNG, false);
118 #endif
119 break;
121 ++iter;
123 // Ignore the case where we didn't find the plugin, it may have terminated
124 // before this function could run.
127 } // namespace
130 // HungPluginInfoBarDelegate --------------------------------------------------
132 class HungPluginInfoBarDelegate : public ConfirmInfoBarDelegate {
133 public:
134 // Creates a hung plugin infobar and delegate and adds the infobar to
135 // |infobar_service|. Returns the infobar if it was successfully added.
136 static infobars::InfoBar* Create(InfoBarService* infobar_service,
137 HungPluginTabHelper* helper,
138 int plugin_child_id,
139 const base::string16& plugin_name);
141 private:
142 HungPluginInfoBarDelegate(HungPluginTabHelper* helper,
143 int plugin_child_id,
144 const base::string16& plugin_name);
145 ~HungPluginInfoBarDelegate() override;
147 // ConfirmInfoBarDelegate:
148 int GetIconID() const override;
149 base::string16 GetMessageText() const override;
150 int GetButtons() const override;
151 base::string16 GetButtonLabel(InfoBarButton button) const override;
152 bool Accept() override;
154 HungPluginTabHelper* helper_;
155 int plugin_child_id_;
157 base::string16 message_;
158 base::string16 button_text_;
161 // static
162 infobars::InfoBar* HungPluginInfoBarDelegate::Create(
163 InfoBarService* infobar_service,
164 HungPluginTabHelper* helper,
165 int plugin_child_id,
166 const base::string16& plugin_name) {
167 return infobar_service->AddInfoBar(infobar_service->CreateConfirmInfoBar(
168 scoped_ptr<ConfirmInfoBarDelegate>(new HungPluginInfoBarDelegate(
169 helper, plugin_child_id, plugin_name))));
172 HungPluginInfoBarDelegate::HungPluginInfoBarDelegate(
173 HungPluginTabHelper* helper,
174 int plugin_child_id,
175 const base::string16& plugin_name)
176 : ConfirmInfoBarDelegate(),
177 helper_(helper),
178 plugin_child_id_(plugin_child_id),
179 message_(l10n_util::GetStringFUTF16(
180 IDS_BROWSER_HANGMONITOR_PLUGIN_INFOBAR, plugin_name)),
181 button_text_(l10n_util::GetStringUTF16(
182 IDS_BROWSER_HANGMONITOR_PLUGIN_INFOBAR_KILLBUTTON)) {
185 HungPluginInfoBarDelegate::~HungPluginInfoBarDelegate() {
188 int HungPluginInfoBarDelegate::GetIconID() const {
189 return IDR_INFOBAR_PLUGIN_CRASHED;
192 base::string16 HungPluginInfoBarDelegate::GetMessageText() const {
193 return message_;
196 int HungPluginInfoBarDelegate::GetButtons() const {
197 return BUTTON_OK;
200 base::string16 HungPluginInfoBarDelegate::GetButtonLabel(
201 InfoBarButton button) const {
202 return button_text_;
205 bool HungPluginInfoBarDelegate::Accept() {
206 helper_->KillPlugin(plugin_child_id_);
207 return true;
211 // HungPluginTabHelper::PluginState -------------------------------------------
213 // Per-plugin state (since there could be more than one plugin hung). The
214 // integer key is the child process ID of the plugin process. This maintains
215 // the state for all plugins on this page that are currently hung, whether or
216 // not we're currently showing the infobar.
217 struct HungPluginTabHelper::PluginState {
218 // Initializes the plugin state to be a hung plugin.
219 PluginState(const base::FilePath& p, const base::string16& n);
220 ~PluginState();
222 base::FilePath path;
223 base::string16 name;
225 // Possibly-null if we're not showing an infobar right now.
226 infobars::InfoBar* infobar;
228 // Time to delay before re-showing the infobar for a hung plugin. This is
229 // increased each time the user cancels it.
230 base::TimeDelta next_reshow_delay;
232 // Handles calling the helper when the infobar should be re-shown.
233 base::Timer timer;
235 private:
236 // Initial delay in seconds before re-showing the hung plugin message.
237 static const int kInitialReshowDelaySec;
239 // Since the scope of the timer manages our callback, this struct should
240 // not be copied.
241 DISALLOW_COPY_AND_ASSIGN(PluginState);
244 // static
245 const int HungPluginTabHelper::PluginState::kInitialReshowDelaySec = 10;
247 HungPluginTabHelper::PluginState::PluginState(const base::FilePath& p,
248 const base::string16& n)
249 : path(p),
250 name(n),
251 infobar(NULL),
252 next_reshow_delay(base::TimeDelta::FromSeconds(kInitialReshowDelaySec)),
253 timer(false, false) {
256 HungPluginTabHelper::PluginState::~PluginState() {
260 // HungPluginTabHelper --------------------------------------------------------
262 DEFINE_WEB_CONTENTS_USER_DATA_KEY(HungPluginTabHelper);
264 HungPluginTabHelper::HungPluginTabHelper(content::WebContents* contents)
265 : content::WebContentsObserver(contents) {
266 registrar_.Add(this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
267 content::NotificationService::AllSources());
270 HungPluginTabHelper::~HungPluginTabHelper() {
273 void HungPluginTabHelper::PluginCrashed(const base::FilePath& plugin_path,
274 base::ProcessId plugin_pid) {
275 // TODO(brettw) ideally this would take the child process ID. When we do this
276 // for NaCl plugins, we'll want to know exactly which process it was since
277 // the path won't be useful.
278 InfoBarService* infobar_service =
279 InfoBarService::FromWebContents(web_contents());
280 if (!infobar_service)
281 return;
283 // For now, just do a brute-force search to see if we have this plugin. Since
284 // we'll normally have 0 or 1, this is fast.
285 for (PluginStateMap::iterator i = hung_plugins_.begin();
286 i != hung_plugins_.end(); ++i) {
287 if (i->second->path == plugin_path) {
288 if (i->second->infobar)
289 infobar_service->RemoveInfoBar(i->second->infobar);
290 hung_plugins_.erase(i);
291 break;
296 void HungPluginTabHelper::PluginHungStatusChanged(
297 int plugin_child_id,
298 const base::FilePath& plugin_path,
299 bool is_hung) {
300 InfoBarService* infobar_service =
301 InfoBarService::FromWebContents(web_contents());
302 if (!infobar_service)
303 return;
305 PluginStateMap::iterator found = hung_plugins_.find(plugin_child_id);
306 if (found != hung_plugins_.end()) {
307 if (!is_hung) {
308 // Hung plugin became un-hung, close the infobar and delete our info.
309 if (found->second->infobar)
310 infobar_service->RemoveInfoBar(found->second->infobar);
311 hung_plugins_.erase(found);
313 return;
316 base::string16 plugin_name =
317 content::PluginService::GetInstance()->GetPluginDisplayNameByPath(
318 plugin_path);
320 linked_ptr<PluginState> state(new PluginState(plugin_path, plugin_name));
321 hung_plugins_[plugin_child_id] = state;
322 ShowBar(plugin_child_id, state.get());
325 void HungPluginTabHelper::Observe(
326 int type,
327 const content::NotificationSource& source,
328 const content::NotificationDetails& details) {
329 DCHECK_EQ(chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, type);
330 infobars::InfoBar* infobar =
331 content::Details<infobars::InfoBar::RemovedDetails>(details)->first;
332 for (PluginStateMap::iterator i = hung_plugins_.begin();
333 i != hung_plugins_.end(); ++i) {
334 PluginState* state = i->second.get();
335 if (state->infobar == infobar) {
336 state->infobar = NULL;
338 // Schedule the timer to re-show the infobar if the plugin continues to be
339 // hung.
340 state->timer.Start(FROM_HERE, state->next_reshow_delay,
341 base::Bind(&HungPluginTabHelper::OnReshowTimer,
342 base::Unretained(this),
343 i->first));
345 // Next time we do this, delay it twice as long to avoid being annoying.
346 state->next_reshow_delay *= 2;
347 return;
352 void HungPluginTabHelper::KillPlugin(int child_id) {
353 #if defined(OS_WIN)
354 // Dump renderers that are sending or receiving pepper messages, in order to
355 // diagnose inter-process deadlocks.
356 // Only do that on the Canary channel, for 20% of pepper plugin hangs.
357 if (base::RandInt(0, 100) < 20) {
358 chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
359 if (channel == chrome::VersionInfo::CHANNEL_CANARY) {
360 scoped_ptr<OwnedHandleVector> renderer_handles(new OwnedHandleVector);
361 HANDLE current_process = ::GetCurrentProcess();
362 content::RenderProcessHost::iterator renderer_iter =
363 content::RenderProcessHost::AllHostsIterator();
364 for (; !renderer_iter.IsAtEnd(); renderer_iter.Advance()) {
365 content::RenderProcessHost* host = renderer_iter.GetCurrentValue();
366 HANDLE handle = NULL;
367 ::DuplicateHandle(current_process, host->GetHandle(), current_process,
368 &handle, 0, FALSE, DUPLICATE_SAME_ACCESS);
369 renderer_handles->data()->push_back(handle);
371 // If there are a lot of renderer processes, it is likely that we will
372 // generate too many crash dumps. They might not all be uploaded/recorded
373 // due to our crash dump uploading restrictions. So we just don't generate
374 // renderer crash dumps in that case.
375 if (renderer_handles->data()->size() > 0 &&
376 renderer_handles->data()->size() < 4) {
377 content::BrowserThread::PostBlockingPoolSequencedTask(
378 kDumpChildProcessesSequenceName, FROM_HERE,
379 base::Bind(&DumpBrowserInBlockingPool));
380 content::BrowserThread::PostBlockingPoolSequencedTask(
381 kDumpChildProcessesSequenceName, FROM_HERE,
382 base::Bind(&DumpRenderersInBlockingPool,
383 base::Owned(renderer_handles.release())));
387 #endif
389 PluginStateMap::iterator found = hung_plugins_.find(child_id);
390 DCHECK(found != hung_plugins_.end());
392 content::BrowserThread::PostTask(content::BrowserThread::IO,
393 FROM_HERE,
394 base::Bind(&KillPluginOnIOThread, child_id));
395 CloseBar(found->second.get());
398 void HungPluginTabHelper::OnReshowTimer(int child_id) {
399 // The timer should have been cancelled if the record isn't in our map
400 // anymore.
401 PluginStateMap::iterator found = hung_plugins_.find(child_id);
402 DCHECK(found != hung_plugins_.end());
403 DCHECK(!found->second->infobar);
404 ShowBar(child_id, found->second.get());
407 void HungPluginTabHelper::ShowBar(int child_id, PluginState* state) {
408 InfoBarService* infobar_service =
409 InfoBarService::FromWebContents(web_contents());
410 if (!infobar_service)
411 return;
413 DCHECK(!state->infobar);
414 state->infobar = HungPluginInfoBarDelegate::Create(infobar_service, this,
415 child_id, state->name);
418 void HungPluginTabHelper::CloseBar(PluginState* state) {
419 InfoBarService* infobar_service =
420 InfoBarService::FromWebContents(web_contents());
421 if (infobar_service && state->infobar) {
422 infobar_service->RemoveInfoBar(state->infobar);
423 state->infobar = NULL;