Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / hung_plugin_tab_helper.cc
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/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_service.h"
16 #include "chrome/common/chrome_version_info.h"
17 #include "components/infobars/core/infobar.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"
32
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
37
38
39 namespace {
40
41 #if defined(OS_WIN)
42
43 // OwnedHandleVector ----------------------------------------------------------
44
45 class OwnedHandleVector {
46  public:
47   typedef std::vector<HANDLE> Handles;
48   OwnedHandleVector();
49   ~OwnedHandleVector();
50
51   Handles* data() { return &data_; }
52
53  private:
54   Handles data_;
55
56   DISALLOW_COPY_AND_ASSIGN(OwnedHandleVector);
57 };
58
59 OwnedHandleVector::OwnedHandleVector() {
60 }
61
62 OwnedHandleVector::~OwnedHandleVector() {
63   for (Handles::iterator iter = data_.begin(); iter != data_.end(); ++iter)
64     ::CloseHandle(*iter);
65 }
66
67
68 // Helpers --------------------------------------------------------------------
69
70 const char kDumpChildProcessesSequenceName[] = "DumpChildProcesses";
71
72 void DumpBrowserInBlockingPool() {
73   CrashDumpForHangDebugging(::GetCurrentProcess());
74 }
75
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);
81   }
82 }
83
84 void DumpAndTerminatePluginInBlockingPool(
85     base::win::ScopedHandle* plugin_handle) {
86   CrashDumpAndTerminateHungChildProcess(plugin_handle->Get());
87 }
88
89 #endif  // defined(OS_WIN)
90
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.
95 //
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;
119     }
120     ++iter;
121   }
122   // Ignore the case where we didn't find the plugin, it may have terminated
123   // before this function could run.
124 }
125
126 }  // namespace
127
128
129 // HungPluginInfoBarDelegate --------------------------------------------------
130
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 infobars::InfoBar* Create(InfoBarService* infobar_service,
136                                    HungPluginTabHelper* helper,
137                                    int plugin_child_id,
138                                    const base::string16& plugin_name);
139
140  private:
141   HungPluginInfoBarDelegate(HungPluginTabHelper* helper,
142                             int plugin_child_id,
143                             const base::string16& plugin_name);
144   virtual ~HungPluginInfoBarDelegate();
145
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;
152
153   HungPluginTabHelper* helper_;
154   int plugin_child_id_;
155
156   base::string16 message_;
157   base::string16 button_text_;
158 };
159
160 // static
161 infobars::InfoBar* HungPluginInfoBarDelegate::Create(
162     InfoBarService* infobar_service,
163     HungPluginTabHelper* helper,
164     int plugin_child_id,
165     const base::string16& plugin_name) {
166   return infobar_service->AddInfoBar(ConfirmInfoBarDelegate::CreateInfoBar(
167       scoped_ptr<ConfirmInfoBarDelegate>(new HungPluginInfoBarDelegate(
168           helper, plugin_child_id, plugin_name))));
169 }
170
171 HungPluginInfoBarDelegate::HungPluginInfoBarDelegate(
172     HungPluginTabHelper* helper,
173     int plugin_child_id,
174     const base::string16& plugin_name)
175     : ConfirmInfoBarDelegate(),
176       helper_(helper),
177       plugin_child_id_(plugin_child_id),
178       message_(l10n_util::GetStringFUTF16(
179           IDS_BROWSER_HANGMONITOR_PLUGIN_INFOBAR, plugin_name)),
180       button_text_(l10n_util::GetStringUTF16(
181           IDS_BROWSER_HANGMONITOR_PLUGIN_INFOBAR_KILLBUTTON)) {
182 }
183
184 HungPluginInfoBarDelegate::~HungPluginInfoBarDelegate() {
185 }
186
187 int HungPluginInfoBarDelegate::GetIconID() const {
188   return IDR_INFOBAR_PLUGIN_CRASHED;
189 }
190
191 base::string16 HungPluginInfoBarDelegate::GetMessageText() const {
192   return message_;
193 }
194
195 int HungPluginInfoBarDelegate::GetButtons() const {
196   return BUTTON_OK;
197 }
198
199 base::string16 HungPluginInfoBarDelegate::GetButtonLabel(
200     InfoBarButton button) const {
201   return button_text_;
202 }
203
204 bool HungPluginInfoBarDelegate::Accept() {
205   helper_->KillPlugin(plugin_child_id_);
206   return true;
207 }
208
209
210 // HungPluginTabHelper::PluginState -------------------------------------------
211
212 // Per-plugin state (since there could be more than one plugin hung).  The
213 // integer key is the child process ID of the plugin process.  This maintains
214 // the state for all plugins on this page that are currently hung, whether or
215 // not we're currently showing the infobar.
216 struct HungPluginTabHelper::PluginState {
217   // Initializes the plugin state to be a hung plugin.
218   PluginState(const base::FilePath& p, const base::string16& n);
219   ~PluginState();
220
221   base::FilePath path;
222   base::string16 name;
223
224   // Possibly-null if we're not showing an infobar right now.
225   infobars::InfoBar* infobar;
226
227   // Time to delay before re-showing the infobar for a hung plugin. This is
228   // increased each time the user cancels it.
229   base::TimeDelta next_reshow_delay;
230
231   // Handles calling the helper when the infobar should be re-shown.
232   base::Timer timer;
233
234  private:
235   // Initial delay in seconds before re-showing the hung plugin message.
236   static const int kInitialReshowDelaySec;
237
238   // Since the scope of the timer manages our callback, this struct should
239   // not be copied.
240   DISALLOW_COPY_AND_ASSIGN(PluginState);
241 };
242
243 // static
244 const int HungPluginTabHelper::PluginState::kInitialReshowDelaySec = 10;
245
246 HungPluginTabHelper::PluginState::PluginState(const base::FilePath& p,
247                                               const base::string16& n)
248     : path(p),
249       name(n),
250       infobar(NULL),
251       next_reshow_delay(base::TimeDelta::FromSeconds(kInitialReshowDelaySec)),
252       timer(false, false) {
253 }
254
255 HungPluginTabHelper::PluginState::~PluginState() {
256 }
257
258
259 // HungPluginTabHelper --------------------------------------------------------
260
261 DEFINE_WEB_CONTENTS_USER_DATA_KEY(HungPluginTabHelper);
262
263 HungPluginTabHelper::HungPluginTabHelper(content::WebContents* contents)
264     : content::WebContentsObserver(contents) {
265   registrar_.Add(this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
266                  content::NotificationService::AllSources());
267 }
268
269 HungPluginTabHelper::~HungPluginTabHelper() {
270 }
271
272 void HungPluginTabHelper::PluginCrashed(const base::FilePath& plugin_path,
273                                         base::ProcessId plugin_pid) {
274   // TODO(brettw) ideally this would take the child process ID. When we do this
275   // for NaCl plugins, we'll want to know exactly which process it was since
276   // the path won't be useful.
277   InfoBarService* infobar_service =
278       InfoBarService::FromWebContents(web_contents());
279   if (!infobar_service)
280     return;
281
282   // For now, just do a brute-force search to see if we have this plugin. Since
283   // we'll normally have 0 or 1, this is fast.
284   for (PluginStateMap::iterator i = hung_plugins_.begin();
285        i != hung_plugins_.end(); ++i) {
286     if (i->second->path == plugin_path) {
287       if (i->second->infobar)
288         infobar_service->RemoveInfoBar(i->second->infobar);
289       hung_plugins_.erase(i);
290       break;
291     }
292   }
293 }
294
295 void HungPluginTabHelper::PluginHungStatusChanged(
296     int plugin_child_id,
297     const base::FilePath& plugin_path,
298     bool is_hung) {
299   InfoBarService* infobar_service =
300       InfoBarService::FromWebContents(web_contents());
301   if (!infobar_service)
302     return;
303
304   PluginStateMap::iterator found = hung_plugins_.find(plugin_child_id);
305   if (found != hung_plugins_.end()) {
306     if (!is_hung) {
307       // Hung plugin became un-hung, close the infobar and delete our info.
308       if (found->second->infobar)
309         infobar_service->RemoveInfoBar(found->second->infobar);
310       hung_plugins_.erase(found);
311     }
312     return;
313   }
314
315   base::string16 plugin_name =
316       content::PluginService::GetInstance()->GetPluginDisplayNameByPath(
317           plugin_path);
318
319   linked_ptr<PluginState> state(new PluginState(plugin_path, plugin_name));
320   hung_plugins_[plugin_child_id] = state;
321   ShowBar(plugin_child_id, state.get());
322 }
323
324 void HungPluginTabHelper::Observe(
325     int type,
326     const content::NotificationSource& source,
327     const content::NotificationDetails& details) {
328   DCHECK_EQ(chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, type);
329   infobars::InfoBar* infobar =
330       content::Details<infobars::InfoBar::RemovedDetails>(details)->first;
331   for (PluginStateMap::iterator i = hung_plugins_.begin();
332        i != hung_plugins_.end(); ++i) {
333     PluginState* state = i->second.get();
334     if (state->infobar == infobar) {
335       state->infobar = NULL;
336
337       // Schedule the timer to re-show the infobar if the plugin continues to be
338       // hung.
339       state->timer.Start(FROM_HERE, state->next_reshow_delay,
340           base::Bind(&HungPluginTabHelper::OnReshowTimer,
341                      base::Unretained(this),
342                      i->first));
343
344       // Next time we do this, delay it twice as long to avoid being annoying.
345       state->next_reshow_delay *= 2;
346       return;
347     }
348   }
349 }
350
351 void HungPluginTabHelper::KillPlugin(int child_id) {
352 #if defined(OS_WIN)
353   // Dump renderers that are sending or receiving pepper messages, in order to
354   // diagnose inter-process deadlocks.
355   // Only do that on the Canary channel, for 20% of pepper plugin hangs.
356   if (base::RandInt(0, 100) < 20) {
357     chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
358     if (channel == chrome::VersionInfo::CHANNEL_CANARY) {
359       scoped_ptr<OwnedHandleVector> renderer_handles(new OwnedHandleVector);
360       HANDLE current_process = ::GetCurrentProcess();
361       content::RenderProcessHost::iterator renderer_iter =
362           content::RenderProcessHost::AllHostsIterator();
363       for (; !renderer_iter.IsAtEnd(); renderer_iter.Advance()) {
364         content::RenderProcessHost* host = renderer_iter.GetCurrentValue();
365         HANDLE handle = NULL;
366         ::DuplicateHandle(current_process, host->GetHandle(), current_process,
367                           &handle, 0, FALSE, DUPLICATE_SAME_ACCESS);
368         renderer_handles->data()->push_back(handle);
369       }
370       // If there are a lot of renderer processes, it is likely that we will
371       // generate too many crash dumps. They might not all be uploaded/recorded
372       // due to our crash dump uploading restrictions. So we just don't generate
373       // renderer crash dumps in that case.
374       if (renderer_handles->data()->size() > 0 &&
375           renderer_handles->data()->size() < 4) {
376         content::BrowserThread::PostBlockingPoolSequencedTask(
377             kDumpChildProcessesSequenceName, FROM_HERE,
378             base::Bind(&DumpBrowserInBlockingPool));
379         content::BrowserThread::PostBlockingPoolSequencedTask(
380             kDumpChildProcessesSequenceName, FROM_HERE,
381             base::Bind(&DumpRenderersInBlockingPool,
382                        base::Owned(renderer_handles.release())));
383       }
384     }
385   }
386 #endif
387
388   PluginStateMap::iterator found = hung_plugins_.find(child_id);
389   DCHECK(found != hung_plugins_.end());
390
391   content::BrowserThread::PostTask(content::BrowserThread::IO,
392                                    FROM_HERE,
393                                    base::Bind(&KillPluginOnIOThread, child_id));
394   CloseBar(found->second.get());
395 }
396
397 void HungPluginTabHelper::OnReshowTimer(int child_id) {
398   // The timer should have been cancelled if the record isn't in our map
399   // anymore.
400   PluginStateMap::iterator found = hung_plugins_.find(child_id);
401   DCHECK(found != hung_plugins_.end());
402   DCHECK(!found->second->infobar);
403   ShowBar(child_id, found->second.get());
404 }
405
406 void HungPluginTabHelper::ShowBar(int child_id, PluginState* state) {
407   InfoBarService* infobar_service =
408       InfoBarService::FromWebContents(web_contents());
409   if (!infobar_service)
410     return;
411
412   DCHECK(!state->infobar);
413   state->infobar = HungPluginInfoBarDelegate::Create(infobar_service, this,
414                                                      child_id, state->name);
415 }
416
417 void HungPluginTabHelper::CloseBar(PluginState* state) {
418   InfoBarService* infobar_service =
419       InfoBarService::FromWebContents(web_contents());
420   if (infobar_service && state->infobar) {
421     infobar_service->RemoveInfoBar(state->infobar);
422     state->infobar = NULL;
423   }
424 }