Update To 11.40.268.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/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"
31
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
36
37
38 namespace {
39
40 #if defined(OS_WIN)
41
42 // OwnedHandleVector ----------------------------------------------------------
43
44 class OwnedHandleVector {
45  public:
46   typedef std::vector<HANDLE> Handles;
47   OwnedHandleVector();
48   ~OwnedHandleVector();
49
50   Handles* data() { return &data_; }
51
52  private:
53   Handles data_;
54
55   DISALLOW_COPY_AND_ASSIGN(OwnedHandleVector);
56 };
57
58 OwnedHandleVector::OwnedHandleVector() {
59 }
60
61 OwnedHandleVector::~OwnedHandleVector() {
62   for (Handles::iterator iter = data_.begin(); iter != data_.end(); ++iter)
63     ::CloseHandle(*iter);
64 }
65
66
67 // Helpers --------------------------------------------------------------------
68
69 const char kDumpChildProcessesSequenceName[] = "DumpChildProcesses";
70
71 void DumpBrowserInBlockingPool() {
72   CrashDumpForHangDebugging(::GetCurrentProcess());
73 }
74
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);
80   }
81 }
82
83 void DumpAndTerminatePluginInBlockingPool(
84     base::win::ScopedHandle* plugin_handle) {
85   CrashDumpAndTerminateHungChildProcess(plugin_handle->Get());
86 }
87
88 #endif  // defined(OS_WIN)
89
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.
94 //
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::KillProcess(data.handle, content::RESULT_CODE_HUNG, false);
116 #endif
117       break;
118     }
119     ++iter;
120   }
121   // Ignore the case where we didn't find the plugin, it may have terminated
122   // before this function could run.
123 }
124
125 }  // namespace
126
127
128 // HungPluginInfoBarDelegate --------------------------------------------------
129
130 class HungPluginInfoBarDelegate : public ConfirmInfoBarDelegate {
131  public:
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,
136                                    int plugin_child_id,
137                                    const base::string16& plugin_name);
138
139  private:
140   HungPluginInfoBarDelegate(HungPluginTabHelper* helper,
141                             int plugin_child_id,
142                             const base::string16& plugin_name);
143   ~HungPluginInfoBarDelegate() override;
144
145   // ConfirmInfoBarDelegate:
146   int GetIconID() const override;
147   base::string16 GetMessageText() const override;
148   int GetButtons() const override;
149   base::string16 GetButtonLabel(InfoBarButton button) const override;
150   bool Accept() override;
151
152   HungPluginTabHelper* helper_;
153   int plugin_child_id_;
154
155   base::string16 message_;
156   base::string16 button_text_;
157 };
158
159 // static
160 infobars::InfoBar* HungPluginInfoBarDelegate::Create(
161     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))));
168 }
169
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)) {
181 }
182
183 HungPluginInfoBarDelegate::~HungPluginInfoBarDelegate() {
184 }
185
186 int HungPluginInfoBarDelegate::GetIconID() const {
187   return IDR_INFOBAR_PLUGIN_CRASHED;
188 }
189
190 base::string16 HungPluginInfoBarDelegate::GetMessageText() const {
191   return message_;
192 }
193
194 int HungPluginInfoBarDelegate::GetButtons() const {
195   return BUTTON_OK;
196 }
197
198 base::string16 HungPluginInfoBarDelegate::GetButtonLabel(
199     InfoBarButton button) const {
200   return button_text_;
201 }
202
203 bool HungPluginInfoBarDelegate::Accept() {
204   helper_->KillPlugin(plugin_child_id_);
205   return true;
206 }
207
208
209 // HungPluginTabHelper::PluginState -------------------------------------------
210
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();
219
220   base::FilePath path;
221   base::string16 name;
222
223   // Possibly-null if we're not showing an infobar right now.
224   infobars::InfoBar* infobar;
225
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;
229
230   // Handles calling the helper when the infobar should be re-shown.
231   base::Timer timer;
232
233  private:
234   // Initial delay in seconds before re-showing the hung plugin message.
235   static const int kInitialReshowDelaySec;
236
237   // Since the scope of the timer manages our callback, this struct should
238   // not be copied.
239   DISALLOW_COPY_AND_ASSIGN(PluginState);
240 };
241
242 // static
243 const int HungPluginTabHelper::PluginState::kInitialReshowDelaySec = 10;
244
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) {
252 }
253
254 HungPluginTabHelper::PluginState::~PluginState() {
255 }
256
257
258 // HungPluginTabHelper --------------------------------------------------------
259
260 DEFINE_WEB_CONTENTS_USER_DATA_KEY(HungPluginTabHelper);
261
262 HungPluginTabHelper::HungPluginTabHelper(content::WebContents* contents)
263     : content::WebContentsObserver(contents) {
264   registrar_.Add(this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
265                  content::NotificationService::AllSources());
266 }
267
268 HungPluginTabHelper::~HungPluginTabHelper() {
269 }
270
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;
280
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;
290     }
291   }
292 }
293
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;
302
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);
310     }
311     return;
312   }
313
314   base::string16 plugin_name =
315       content::PluginService::GetInstance()->GetPluginDisplayNameByPath(
316           plugin_path);
317
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());
321 }
322
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   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;
335
336       // Schedule the timer to re-show the infobar if the plugin continues to be
337       // hung.
338       state->timer.Start(FROM_HERE, state->next_reshow_delay,
339           base::Bind(&HungPluginTabHelper::OnReshowTimer,
340                      base::Unretained(this),
341                      i->first));
342
343       // Next time we do this, delay it twice as long to avoid being annoying.
344       state->next_reshow_delay *= 2;
345       return;
346     }
347   }
348 }
349
350 void HungPluginTabHelper::KillPlugin(int child_id) {
351 #if defined(OS_WIN)
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);
368       }
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())));
382       }
383     }
384   }
385 #endif
386
387   PluginStateMap::iterator found = hung_plugins_.find(child_id);
388   DCHECK(found != hung_plugins_.end());
389
390   content::BrowserThread::PostTask(content::BrowserThread::IO,
391                                    FROM_HERE,
392                                    base::Bind(&KillPluginOnIOThread, child_id));
393   CloseBar(found->second.get());
394 }
395
396 void HungPluginTabHelper::OnReshowTimer(int child_id) {
397   // The timer should have been cancelled if the record isn't in our map
398   // anymore.
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());
403 }
404
405 void HungPluginTabHelper::ShowBar(int child_id, PluginState* state) {
406   InfoBarService* infobar_service =
407       InfoBarService::FromWebContents(web_contents());
408   if (!infobar_service)
409     return;
410
411   DCHECK(!state->infobar);
412   state->infobar = HungPluginInfoBarDelegate::Create(infobar_service, this,
413                                                      child_id, state->name);
414 }
415
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;
422   }
423 }