summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/app/generated_resources.grd6
-rw-r--r--chrome/browser/memory_details.cc2
-rw-r--r--chrome/browser/ui/browser.cc31
-rw-r--r--chrome/browser/ui/browser.h4
-rw-r--r--chrome/browser/ui/hung_plugin_tab_helper.cc268
-rw-r--r--chrome/browser/ui/hung_plugin_tab_helper.h108
-rw-r--r--chrome/browser/ui/tab_contents/tab_contents_wrapper.cc2
-rw-r--r--chrome/browser/ui/tab_contents/tab_contents_wrapper.h5
-rw-r--r--chrome/chrome_browser.gypi2
-rw-r--r--content/browser/plugin_data_remover_impl.cc3
-rw-r--r--content/browser/plugin_service_impl.cc21
-rw-r--r--content/browser/plugin_service_impl.h1
-rw-r--r--content/browser/ppapi_plugin_process_host.cc15
-rw-r--r--content/browser/ppapi_plugin_process_host.h9
-rw-r--r--content/browser/renderer_host/render_message_filter.cc8
-rw-r--r--content/browser/web_contents/web_contents_impl.cc8
-rw-r--r--content/browser/web_contents/web_contents_impl.h3
-rw-r--r--content/common/view_messages.h36
-rw-r--r--content/content_renderer.gypi2
-rw-r--r--content/public/browser/plugin_service.h5
-rw-r--r--content/public/browser/render_process_host.h2
-rw-r--r--content/public/browser/web_contents_delegate.h11
-rw-r--r--content/renderer/pepper/pepper_hung_plugin_filter.cc153
-rw-r--r--content/renderer/pepper/pepper_hung_plugin_filter.h109
-rw-r--r--content/renderer/pepper/pepper_plugin_delegate_impl.cc16
-rw-r--r--ppapi/proxy/dispatcher.cc3
-rw-r--r--ppapi/proxy/dispatcher.h3
-rw-r--r--ppapi/proxy/host_dispatcher.cc13
-rw-r--r--ppapi/proxy/host_dispatcher.h27
-rw-r--r--ppapi/proxy/ppapi_proxy_test.cc17
-rw-r--r--ppapi/proxy/ppapi_proxy_test.h4
31 files changed, 838 insertions, 59 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 55e9bf9..a365f8c 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -6098,6 +6098,12 @@ The following plug-in is unresponsive: <ph name="PLUGIN_NAME">$1
<message name="IDS_BROWSER_HANGMONITOR_RENDERER_END" desc="The label of the 'kill' button">
Kill pages
</message>
+ <message name="IDS_BROWSER_HANGMONITOR_PLUGIN_INFOBAR" desc="The text of the infobar notifying the user that a plugin has hung">
+ A plug-in (<ph name="PLUGIN_NAME">$1<ex>Flash</ex></ph>) isn't responding.
+ </message>
+ <message name="IDS_BROWSER_HANGMONITOR_PLUGIN_INFOBAR_KILLBUTTON" desc="The button on the hung plugin infobar (...PLUGIN_INFOBAR above) to terminate the plugin">
+ Stop plug-in
+ </message>
<!-- Passwords and Exceptions Dialog -->
<message name="IDS_PASSWORDS_EXCEPTIONS_WINDOW_TITLE" desc="Title for 'Passwords and exceptions dialog'">
diff --git a/chrome/browser/memory_details.cc b/chrome/browser/memory_details.cc
index 04b248a..74e99f2 100644
--- a/chrome/browser/memory_details.cc
+++ b/chrome/browser/memory_details.cc
@@ -107,7 +107,7 @@ ProcessData& ProcessData::operator=(const ProcessData& rhs) {
//
// This operation will hit no fewer than 3 threads.
//
-// The ChildProcessInfo::Iterator can only be accessed from the IO thread.
+// The BrowserChildProcessHostIterator can only be accessed from the IO thread.
//
// The RenderProcessHostIterator can only be accessed from the UI thread.
//
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index 686cd20..0d1b529 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -118,6 +118,7 @@
#include "chrome/browser/ui/global_error.h"
#include "chrome/browser/ui/global_error_service.h"
#include "chrome/browser/ui/global_error_service_factory.h"
+#include "chrome/browser/ui/hung_plugin_tab_helper.h"
#include "chrome/browser/ui/intents/web_intent_picker_controller.h"
#include "chrome/browser/ui/omnibox/location_bar.h"
#include "chrome/browser/ui/panels/panel.h"
@@ -2918,22 +2919,14 @@ void Browser::CrashedPluginHelper(WebContents* tab,
if (!tcw)
return;
+ // Tell the hung plugin infobars about this crash so they can close any
+ // related ones.
+ tcw->hung_plugin_tab_helper()->PluginCrashed(plugin_path);
+
DCHECK(!plugin_path.value().empty());
- string16 plugin_name = plugin_path.LossyDisplayName();
- webkit::WebPluginInfo plugin_info;
- if (PluginService::GetInstance()->GetPluginInfoByPath(
- plugin_path, &plugin_info) &&
- !plugin_info.name.empty()) {
- plugin_name = plugin_info.name;
-#if defined(OS_MACOSX)
- // Many plugins on the Mac have .plugin in the actual name, which looks
- // terrible, so look for that and strip it off if present.
- const std::string kPluginExtension = ".plugin";
- if (EndsWith(plugin_name, ASCIIToUTF16(kPluginExtension), true))
- plugin_name.erase(plugin_name.length() - kPluginExtension.length());
-#endif // OS_MACOSX
- }
+ string16 plugin_name =
+ PluginService::GetInstance()->GetPluginDisplayNameByPath(plugin_path);
gfx::Image* icon = &ResourceBundle::GetSharedInstance().GetNativeImageNamed(
IDR_INFOBAR_PLUGIN_CRASHED);
InfoBarTabHelper* infobar_helper = tcw->infobar_tab_helper();
@@ -4284,6 +4277,16 @@ void Browser::CrashedPlugin(WebContents* tab, const FilePath& plugin_path) {
CrashedPluginHelper(tab, plugin_path);
}
+void Browser::PluginHungStatusChanged(WebContents* tab,
+ int plugin_child_id,
+ const FilePath& plugin_path,
+ bool is_hung) {
+ TabContentsWrapper* tcw =
+ TabContentsWrapper::GetCurrentWrapperForContents(tab);
+ tcw->hung_plugin_tab_helper()->PluginHungStatusChanged(
+ plugin_child_id, plugin_path, is_hung);
+}
+
void Browser::UpdatePreferredSize(WebContents* source,
const gfx::Size& pref_size) {
window_->UpdatePreferredSize(source, pref_size);
diff --git a/chrome/browser/ui/browser.h b/chrome/browser/ui/browser.h
index 54b9e86..e6e3911 100644
--- a/chrome/browser/ui/browser.h
+++ b/chrome/browser/ui/browser.h
@@ -1085,6 +1085,10 @@ class Browser : public TabHandlerDelegate,
virtual void CrashedPlugin(content::WebContents* tab,
const FilePath& plugin_path) OVERRIDE;
+ virtual void PluginHungStatusChanged(content::WebContents* tab,
+ int plugin_child_id,
+ const FilePath& plugin_path,
+ bool is_hung) OVERRIDE;
virtual void RequestToLockMouse(content::WebContents* tab) OVERRIDE;
virtual void LostMouseLock() OVERRIDE;
diff --git a/chrome/browser/ui/hung_plugin_tab_helper.cc b/chrome/browser/ui/hung_plugin_tab_helper.cc
new file mode 100644
index 0000000..df2cf8c
--- /dev/null
+++ b/chrome/browser/ui/hung_plugin_tab_helper.cc
@@ -0,0 +1,268 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/hung_plugin_tab_helper.h"
+
+#include "base/bind.h"
+#include "base/process_util.h"
+#include "chrome/browser/infobars/infobar_tab_helper.h"
+#include "chrome/browser/tab_contents/confirm_infobar_delegate.h"
+#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/browser_child_process_host_iterator.h"
+#include "content/public/browser/child_process_data.h"
+#include "content/public/browser/plugin_service.h"
+#include "content/public/common/result_codes.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "grit/locale_settings.h"
+#include "grit/theme_resources_standard.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace {
+
+// Delay in seconds before re-showing the hung plugin message. This will be
+// increased each time.
+const int kInitialReshowDelaySec = 10;
+
+// Called on the I/O thread to actually kill the plugin with the given child
+// ID. We specifically don't want this to be a member function since if the
+// user chooses to kill the plugin, we want to kill it even if they close the
+// tab first.
+//
+// Be careful with the child_id. It's supplied by the renderer which might be
+// hacked.
+void KillPluginOnIOThread(int child_id) {
+ content::BrowserChildProcessHostIterator iter(
+ content::PROCESS_TYPE_PPAPI_PLUGIN);
+ while (!iter.Done()) {
+ const content::ChildProcessData& data = iter.GetData();
+ if (data.id == child_id) {
+ // TODO(brettw) bug 123021: it might be nice to do some stuff to capture
+ // a stack. The NPAPI Windows hang monitor does some cool stuff in
+ // hung_window_detector.cc.
+ base::KillProcess(data.handle, content::RESULT_CODE_HUNG, false);
+ break;
+ }
+ ++iter;
+ }
+ // Ignore the case where we didn't find the plugin, it may have terminated
+ // before this function could run.
+}
+
+} // namespace
+
+class HungPluginTabHelper::InfoBarDelegate : public ConfirmInfoBarDelegate {
+ public:
+ InfoBarDelegate(HungPluginTabHelper* helper,
+ InfoBarTabHelper* infobar_helper,
+ int plugin_child_id,
+ const string16& plugin_name);
+ virtual ~InfoBarDelegate();
+
+ // ConfirmInfoBarDelegate:
+ virtual gfx::Image* GetIcon() const OVERRIDE;
+ virtual string16 GetMessageText() const OVERRIDE;
+ virtual int GetButtons() const OVERRIDE;
+ virtual string16 GetButtonLabel(InfoBarButton button) const OVERRIDE;
+ virtual bool Accept() OVERRIDE;
+ virtual void InfoBarDismissed() OVERRIDE;
+
+ private:
+ HungPluginTabHelper* helper_;
+ int plugin_child_id_;
+
+ string16 message_;
+ string16 button_text_;
+ gfx::Image* icon_;
+};
+
+HungPluginTabHelper::InfoBarDelegate::InfoBarDelegate(
+ HungPluginTabHelper* helper,
+ InfoBarTabHelper* infobar_helper,
+ int plugin_child_id,
+ const string16& plugin_name)
+ : ConfirmInfoBarDelegate(infobar_helper),
+ helper_(helper),
+ plugin_child_id_(plugin_child_id) {
+ message_ = l10n_util::GetStringFUTF16(IDS_BROWSER_HANGMONITOR_PLUGIN_INFOBAR,
+ plugin_name);
+ button_text_ = l10n_util::GetStringUTF16(
+ IDS_BROWSER_HANGMONITOR_PLUGIN_INFOBAR_KILLBUTTON);
+ icon_ = &ResourceBundle::GetSharedInstance().GetNativeImageNamed(
+ IDR_INFOBAR_PLUGIN_CRASHED);
+}
+
+HungPluginTabHelper::InfoBarDelegate::~InfoBarDelegate() {
+}
+
+gfx::Image* HungPluginTabHelper::InfoBarDelegate::GetIcon() const {
+ return icon_;
+}
+
+string16 HungPluginTabHelper::InfoBarDelegate::GetMessageText() const {
+ return message_;
+}
+
+int HungPluginTabHelper::InfoBarDelegate::GetButtons() const {
+ return BUTTON_OK;
+}
+
+string16 HungPluginTabHelper::InfoBarDelegate::GetButtonLabel(
+ InfoBarButton button) const {
+ return button_text_;
+}
+
+bool HungPluginTabHelper::InfoBarDelegate::Accept() {
+ helper_->KillPlugin(plugin_child_id_);
+ return true;
+}
+
+void HungPluginTabHelper::InfoBarDelegate::InfoBarDismissed() {
+ helper_->BarClosed(plugin_child_id_);
+}
+
+// -----------------------------------------------------------------------------
+
+HungPluginTabHelper::PluginState::PluginState(const FilePath& p,
+ const string16& n)
+ : path(p),
+ name(n),
+ info_bar(NULL),
+ next_reshow_delay(base::TimeDelta::FromSeconds(kInitialReshowDelaySec)),
+ timer(false, false) {
+}
+
+HungPluginTabHelper::PluginState::~PluginState() {
+}
+
+// -----------------------------------------------------------------------------
+
+HungPluginTabHelper::HungPluginTabHelper(content::WebContents* contents)
+ : contents_(contents) {
+}
+
+HungPluginTabHelper::~HungPluginTabHelper() {
+}
+
+void HungPluginTabHelper::PluginCrashed(const FilePath& plugin_path) {
+ // TODO(brettw) ideally this would take the child process ID. When we do this
+ // for NaCl plugins, we'll want to know exactly which process it was since
+ // the path won't be useful.
+ InfoBarTabHelper* infobar_helper = GetInfoBarHelper();
+ if (!infobar_helper)
+ return;
+
+ // For now, just do a brute-force search to see if we have this plugin. Since
+ // we'll normally have 0 or 1, this is fast.
+ for (PluginStateMap::iterator i = hung_plugins_.begin();
+ i != hung_plugins_.end(); ++i) {
+ if (i->second->path == plugin_path) {
+ if (i->second->info_bar)
+ infobar_helper->RemoveInfoBar(i->second->info_bar);
+ hung_plugins_.erase(i);
+ break;
+ }
+ }
+}
+
+void HungPluginTabHelper::PluginHungStatusChanged(int plugin_child_id,
+ const FilePath& plugin_path,
+ bool is_hung) {
+ InfoBarTabHelper* infobar_helper = GetInfoBarHelper();
+ if (!infobar_helper)
+ return;
+
+ PluginStateMap::iterator found = hung_plugins_.find(plugin_child_id);
+ if (found != hung_plugins_.end()) {
+ if (!is_hung) {
+ // Hung plugin became un-hung, close the infobar and delete our info.
+ if (found->second->info_bar)
+ infobar_helper->RemoveInfoBar(found->second->info_bar);
+ hung_plugins_.erase(found);
+ }
+ return;
+ }
+
+ string16 plugin_name =
+ content::PluginService::GetInstance()->GetPluginDisplayNameByPath(
+ plugin_path);
+
+ linked_ptr<PluginState> state(new PluginState(plugin_path, plugin_name));
+ hung_plugins_[plugin_child_id] = state;
+ ShowBar(plugin_child_id, state.get());
+}
+
+void HungPluginTabHelper::KillPlugin(int child_id) {
+ PluginStateMap::iterator found = hung_plugins_.find(child_id);
+ if (found == hung_plugins_.end()) {
+ NOTREACHED();
+ return;
+ }
+
+ content::BrowserThread::PostTask(content::BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&KillPluginOnIOThread, child_id));
+ CloseBar(found->second.get());
+}
+
+void HungPluginTabHelper::BarClosed(int child_id) {
+ PluginStateMap::iterator found = hung_plugins_.find(child_id);
+ if (found == hung_plugins_.end() || !found->second->info_bar) {
+ NOTREACHED();
+ return;
+ }
+ found->second->info_bar = NULL;
+
+ // Schedule the timer to re-show the infobar if the plugin continues to be
+ // hung.
+ found->second->timer.Start(FROM_HERE, found->second->next_reshow_delay,
+ base::Bind(&HungPluginTabHelper::OnReshowTimer,
+ base::Unretained(this),
+ child_id));
+
+ // Next time we do this, delay it twice as long to avoid being annoying.
+ found->second->next_reshow_delay *= 2;
+}
+
+void HungPluginTabHelper::OnReshowTimer(int child_id) {
+ PluginStateMap::iterator found = hung_plugins_.find(child_id);
+ if (found == hung_plugins_.end() || found->second->info_bar) {
+ // The timer should be cancelled if the record isn't in our map anymore.
+ NOTREACHED();
+ return;
+ }
+ ShowBar(child_id, found->second.get());
+}
+
+void HungPluginTabHelper::ShowBar(int child_id, PluginState* state) {
+ InfoBarTabHelper* infobar_helper = GetInfoBarHelper();
+ if (!infobar_helper)
+ return;
+
+ DCHECK(!state->info_bar);
+ state->info_bar = new InfoBarDelegate(this, infobar_helper,
+ child_id, state->name);
+ infobar_helper->AddInfoBar(state->info_bar);
+}
+
+void HungPluginTabHelper::CloseBar(PluginState* state) {
+ InfoBarTabHelper* infobar_helper = GetInfoBarHelper();
+ if (!infobar_helper)
+ return;
+
+ if (state->info_bar) {
+ infobar_helper->RemoveInfoBar(state->info_bar);
+ state->info_bar = NULL;
+ }
+}
+
+InfoBarTabHelper* HungPluginTabHelper::GetInfoBarHelper() {
+ TabContentsWrapper* tcw =
+ TabContentsWrapper::GetCurrentWrapperForContents(contents_);
+ if (!tcw)
+ return NULL;
+ return tcw->infobar_tab_helper();
+}
diff --git a/chrome/browser/ui/hung_plugin_tab_helper.h b/chrome/browser/ui/hung_plugin_tab_helper.h
new file mode 100644
index 0000000..bea4e38
--- /dev/null
+++ b/chrome/browser/ui/hung_plugin_tab_helper.h
@@ -0,0 +1,108 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_HUNG_PLUGIN_TAB_HELPER_H_
+#define CHROME_BROWSER_UI_HUNG_PLUGIN_TAB_HELPER_H_
+
+#include <map>
+
+#include "base/file_path.h"
+#include "base/memory/linked_ptr.h"
+#include "base/string16.h"
+#include "base/time.h"
+#include "base/timer.h"
+
+class FilePath;
+class InfoBarTabHelper;
+
+namespace content {
+class WebContents;
+}
+
+// Manages per-tab state with regard to hung plugins. This only handles
+// Pepper plugins which we know are windowless. Hung NPAPI plugins (which
+// may have native windows) can not be handled with infobars and have a
+// separate OS-specific hang monitoring.
+//
+// Our job is:
+// - Pop up an infobar when a plugin is hung.
+// - Terminate the plugin process if the user so chooses.
+// - Periodically re-show the hung plugin infobar if the user closes it without
+// terminating the plugin.
+// - Hide the infobar if the plugin starts responding again.
+// - Keep track of all of this for any number of plugins.
+class HungPluginTabHelper {
+ public:
+ explicit HungPluginTabHelper(content::WebContents* contents);
+ ~HungPluginTabHelper();
+
+ void PluginCrashed(const FilePath& plugin_path);
+
+ void PluginHungStatusChanged(int plugin_child_id,
+ const FilePath& plugin_path,
+ bool is_hung);
+
+ private:
+ class InfoBarDelegate;
+ friend class InfoBarDelegate;
+
+ // Per-plugin state (since there could be more than one plugin hung). The
+ // integer key is the child process ID of the plugin process. This maintains
+ // the state for all plugins on this page that are currently hung, whether or
+ // not we're currently showing the infobar.
+ struct PluginState {
+ // Initializes the plugin state to be a hung plugin.
+ PluginState(const FilePath& p, const string16& n);
+ ~PluginState();
+
+ FilePath path;
+ string16 name;
+
+ // Possibly-null if we're not showing an infobar right now.
+ InfoBarDelegate* info_bar;
+
+ // Time to delay before re-showing the infobar for a hung plugin. This is
+ // increased each time the user cancels it.
+ base::TimeDelta next_reshow_delay;
+
+ // Handles calling the helper when the infobar should be re-shown.
+ base::Timer timer;
+
+ private:
+ // Since the scope of the timer manages our callback, this struct should
+ // not be copied.
+ DISALLOW_COPY_AND_ASSIGN(PluginState);
+ };
+ typedef std::map<int, linked_ptr<PluginState> > PluginStateMap;
+
+ // Called by an infobar when the user selects to kill the plugin.
+ void KillPlugin(int child_id);
+
+ // Called by an infobar when the user presses the close button to dismiss the
+ // bar without doing anything.
+ void BarClosed(int child_id);
+
+ // Called on a timer for a hung plugin to re-show the bar.
+ void OnReshowTimer(int child_id);
+
+ // Shows the bar for the plugin identified by the given state, updating the
+ // state accordingly. The plugin must not have an infobar already.
+ void ShowBar(int child_id, PluginState* state);
+
+ // Closes the infobar associated with the given state. Note that this can
+ // be called even if the bar is not opened, in which case it will do nothing.
+ void CloseBar(PluginState* state);
+
+ // Possibly returns null.
+ InfoBarTabHelper* GetInfoBarHelper();
+
+ content::WebContents* contents_;
+
+ // All currently hung plugins.
+ PluginStateMap hung_plugins_;
+
+ DISALLOW_COPY_AND_ASSIGN(HungPluginTabHelper);
+};
+
+#endif // CHROME_BROWSER_UI_HUNG_PLUGIN_TAB_HELPER_H_
diff --git a/chrome/browser/ui/tab_contents/tab_contents_wrapper.cc b/chrome/browser/ui/tab_contents/tab_contents_wrapper.cc
index 43e49d3..3d8aaa7 100644
--- a/chrome/browser/ui/tab_contents/tab_contents_wrapper.cc
+++ b/chrome/browser/ui/tab_contents/tab_contents_wrapper.cc
@@ -35,6 +35,7 @@
#include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
#include "chrome/browser/ui/constrained_window_tab_helper.h"
#include "chrome/browser/ui/find_bar/find_tab_helper.h"
+#include "chrome/browser/ui/hung_plugin_tab_helper.h"
#include "chrome/browser/ui/intents/web_intent_picker_controller.h"
#include "chrome/browser/ui/pdf/pdf_tab_observer.h"
#include "chrome/browser/ui/prefs/prefs_tab_helper.h"
@@ -92,6 +93,7 @@ TabContentsWrapper::TabContentsWrapper(WebContents* contents)
favicon_tab_helper_.reset(new FaviconTabHelper(contents));
find_tab_helper_.reset(new FindTabHelper(contents));
history_tab_helper_.reset(new HistoryTabHelper(contents));
+ hung_plugin_tab_helper_.reset(new HungPluginTabHelper(contents));
infobar_tab_helper_.reset(new InfoBarTabHelper(contents));
password_manager_delegate_.reset(new PasswordManagerDelegateImpl(this));
password_manager_.reset(
diff --git a/chrome/browser/ui/tab_contents/tab_contents_wrapper.h b/chrome/browser/ui/tab_contents/tab_contents_wrapper.h
index bb48bf9..d573d62 100644
--- a/chrome/browser/ui/tab_contents/tab_contents_wrapper.h
+++ b/chrome/browser/ui/tab_contents/tab_contents_wrapper.h
@@ -31,6 +31,7 @@ class ExternalProtocolObserver;
class FaviconTabHelper;
class FindTabHelper;
class HistoryTabHelper;
+class HungPluginTabHelper;
class InfoBarTabHelper;
class OmniboxSearchHint;
class PasswordManager;
@@ -148,6 +149,9 @@ class TabContentsWrapper : public content::WebContentsObserver {
FaviconTabHelper* favicon_tab_helper() { return favicon_tab_helper_.get(); }
FindTabHelper* find_tab_helper() { return find_tab_helper_.get(); }
HistoryTabHelper* history_tab_helper() { return history_tab_helper_.get(); }
+ HungPluginTabHelper* hung_plugin_tab_helper() {
+ return hung_plugin_tab_helper_.get();
+ }
InfoBarTabHelper* infobar_tab_helper() { return infobar_tab_helper_.get(); }
#if defined(ENABLE_ONE_CLICK_SIGNIN)
@@ -229,6 +233,7 @@ class TabContentsWrapper : public content::WebContentsObserver {
scoped_ptr<FaviconTabHelper> favicon_tab_helper_;
scoped_ptr<FindTabHelper> find_tab_helper_;
scoped_ptr<HistoryTabHelper> history_tab_helper_;
+ scoped_ptr<HungPluginTabHelper> hung_plugin_tab_helper_;
scoped_ptr<InfoBarTabHelper> infobar_tab_helper_;
// PasswordManager and its delegate. The delegate must outlive the manager,
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 1b8f255..7a8ee76 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -3038,6 +3038,8 @@
'browser/ui/gtk/web_intent_picker_gtk.h',
'browser/ui/gtk/website_settings_popup_gtk.cc',
'browser/ui/gtk/website_settings_popup_gtk.h',
+ 'browser/ui/hung_plugin_tab_helper.cc',
+ 'browser/ui/hung_plugin_tab_helper.h',
'browser/ui/intents/web_intent_inline_disposition_delegate.cc',
'browser/ui/intents/web_intent_inline_disposition_delegate.h',
'browser/ui/intents/web_intent_picker.cc',
diff --git a/content/browser/plugin_data_remover_impl.cc b/content/browser/plugin_data_remover_impl.cc
index 0d83135..fd62447 100644
--- a/content/browser/plugin_data_remover_impl.cc
+++ b/content/browser/plugin_data_remover_impl.cc
@@ -168,7 +168,8 @@ class PluginDataRemoverImpl::Context
virtual void OnPpapiChannelOpened(
base::ProcessHandle plugin_process_handle,
- const IPC::ChannelHandle& channel_handle) OVERRIDE {
+ const IPC::ChannelHandle& channel_handle,
+ int /* child_id */) OVERRIDE {
if (plugin_process_handle != base::kNullProcessHandle)
ConnectToChannel(channel_handle, true);
diff --git a/content/browser/plugin_service_impl.cc b/content/browser/plugin_service_impl.cc
index 99924b1..29909c2 100644
--- a/content/browser/plugin_service_impl.cc
+++ b/content/browser/plugin_service_impl.cc
@@ -342,7 +342,7 @@ void PluginServiceImpl::OpenChannelToPpapiPlugin(
} else {
// Send error.
client->OnPpapiChannelOpened(base::kNullProcessHandle,
- IPC::ChannelHandle());
+ IPC::ChannelHandle(), 0);
}
}
@@ -355,7 +355,7 @@ void PluginServiceImpl::OpenChannelToPpapiBroker(
} else {
// Send error.
client->OnPpapiChannelOpened(base::kNullProcessHandle,
- IPC::ChannelHandle());
+ IPC::ChannelHandle(), 0);
}
}
@@ -483,6 +483,23 @@ bool PluginServiceImpl::GetPluginInfoByPath(const FilePath& plugin_path,
return false;
}
+string16 PluginServiceImpl::GetPluginDisplayNameByPath(const FilePath& path) {
+ string16 plugin_name = path.LossyDisplayName();
+ webkit::WebPluginInfo info;
+ if (PluginService::GetInstance()->GetPluginInfoByPath(path, &info) &&
+ !info.name.empty()) {
+ plugin_name = info.name;
+#if defined(OS_MACOSX)
+ // Many plugins on the Mac have .plugin in the actual name, which looks
+ // terrible, so look for that and strip it off if present.
+ const std::string kPluginExtension = ".plugin";
+ if (EndsWith(plugin_name, ASCIIToUTF16(kPluginExtension), true))
+ plugin_name.erase(plugin_name.length() - kPluginExtension.length());
+#endif // OS_MACOSX
+ }
+ return plugin_name;
+}
+
void PluginServiceImpl::GetPlugins(const GetPluginsCallback& callback) {
scoped_refptr<base::MessageLoopProxy> target_loop(
MessageLoop::current()->message_loop_proxy());
diff --git a/content/browser/plugin_service_impl.h b/content/browser/plugin_service_impl.h
index be36075..bc8f112 100644
--- a/content/browser/plugin_service_impl.h
+++ b/content/browser/plugin_service_impl.h
@@ -97,6 +97,7 @@ class CONTENT_EXPORT PluginServiceImpl
std::string* actual_mime_type) OVERRIDE;
virtual bool GetPluginInfoByPath(const FilePath& plugin_path,
webkit::WebPluginInfo* info) OVERRIDE;
+ virtual string16 GetPluginDisplayNameByPath(const FilePath& path) OVERRIDE;
virtual void GetPlugins(const GetPluginsCallback& callback) OVERRIDE;
virtual void GetPluginGroups(
const GetPluginGroupsCallback& callback) OVERRIDE;
diff --git a/content/browser/ppapi_plugin_process_host.cc b/content/browser/ppapi_plugin_process_host.cc
index ceee322..98218ad 100644
--- a/content/browser/ppapi_plugin_process_host.cc
+++ b/content/browser/ppapi_plugin_process_host.cc
@@ -111,16 +111,14 @@ PpapiPluginProcessHost::PpapiPluginProcessHost(net::HostResolver* host_resolver)
: filter_(new PepperMessageFilter(PepperMessageFilter::PLUGIN,
host_resolver)),
network_observer_(new PluginNetworkObserver(this)),
- is_broker_(false),
- process_id_(ChildProcessHostImpl::GenerateChildProcessUniqueId()) {
+ is_broker_(false) {
process_.reset(new BrowserChildProcessHostImpl(
content::PROCESS_TYPE_PPAPI_PLUGIN, this));
process_->GetHost()->AddFilter(filter_.get());
}
PpapiPluginProcessHost::PpapiPluginProcessHost()
- : is_broker_(true),
- process_id_(ChildProcessHostImpl::GenerateChildProcessUniqueId()) {
+ : is_broker_(true) {
process_.reset(new BrowserChildProcessHostImpl(
content::PROCESS_TYPE_PPAPI_BROKER, this));
}
@@ -211,7 +209,7 @@ void PpapiPluginProcessHost::RequestPluginChannel(Client* client) {
sent_requests_.push(client);
} else {
client->OnPpapiChannelOpened(base::kNullProcessHandle,
- IPC::ChannelHandle());
+ IPC::ChannelHandle(), 0);
}
}
@@ -259,13 +257,13 @@ void PpapiPluginProcessHost::CancelRequests() {
<< "CancelRequests()";
for (size_t i = 0; i < pending_requests_.size(); i++) {
pending_requests_[i]->OnPpapiChannelOpened(base::kNullProcessHandle,
- IPC::ChannelHandle());
+ IPC::ChannelHandle(), 0);
}
pending_requests_.clear();
while (!sent_requests_.empty()) {
sent_requests_.front()->OnPpapiChannelOpened(base::kNullProcessHandle,
- IPC::ChannelHandle());
+ IPC::ChannelHandle(), 0);
sent_requests_.pop();
}
}
@@ -297,5 +295,6 @@ void PpapiPluginProcessHost::OnRendererPluginChannelCreated(
base::ProcessHandle renderers_plugin_handle = plugin_process;
#endif
- client->OnPpapiChannelOpened(renderers_plugin_handle, channel_handle);
+ client->OnPpapiChannelOpened(renderers_plugin_handle, channel_handle,
+ process_->GetData().id);
}
diff --git a/content/browser/ppapi_plugin_process_host.h b/content/browser/ppapi_plugin_process_host.h
index 02e1f83..3d7436e 100644
--- a/content/browser/ppapi_plugin_process_host.h
+++ b/content/browser/ppapi_plugin_process_host.h
@@ -42,10 +42,12 @@ class PpapiPluginProcessHost : public content::BrowserChildProcessHostDelegate,
// Called when the channel is asynchronously opened to the plugin or on
// error. On error, the parameters should be:
// base::kNullProcessHandle
- // IPC::ChannelHandle()
+ // IPC::ChannelHandle(),
+ // 0
virtual void OnPpapiChannelOpened(
base::ProcessHandle plugin_process_handle,
- const IPC::ChannelHandle& channel_handle) = 0;
+ const IPC::ChannelHandle& channel_handle,
+ int plugin_child_id) = 0;
};
class PluginClient : public Client {
@@ -120,9 +122,6 @@ class PpapiPluginProcessHost : public content::BrowserChildProcessHostDelegate,
const bool is_broker_;
- // The unique id created for the process.
- int process_id_;
-
scoped_ptr<BrowserChildProcessHostImpl> process_;
DISALLOW_COPY_AND_ASSIGN(PpapiPluginProcessHost);
diff --git a/content/browser/renderer_host/render_message_filter.cc b/content/browser/renderer_host/render_message_filter.cc
index 98904ba..8d0eb4b 100644
--- a/content/browser/renderer_host/render_message_filter.cc
+++ b/content/browser/renderer_host/render_message_filter.cc
@@ -131,9 +131,10 @@ class OpenChannelToPpapiPluginCallback
}
virtual void OnPpapiChannelOpened(base::ProcessHandle plugin_process_handle,
- const IPC::ChannelHandle& channel_handle) {
+ const IPC::ChannelHandle& channel_handle,
+ int plugin_child_id) {
ViewHostMsg_OpenChannelToPepperPlugin::WriteReplyParams(
- reply_msg(), plugin_process_handle, channel_handle);
+ reply_msg(), plugin_process_handle, channel_handle, plugin_child_id);
SendReplyAndDeleteThis();
}
@@ -165,7 +166,8 @@ class OpenChannelToPpapiBrokerCallback
}
virtual void OnPpapiChannelOpened(base::ProcessHandle broker_process_handle,
- const IPC::ChannelHandle& channel_handle) {
+ const IPC::ChannelHandle& channel_handle,
+ int /* plugin_child_id */) {
filter_->Send(new ViewMsg_PpapiBrokerChannelCreated(routing_id_,
request_id_,
broker_process_handle,
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 48ffa6c..fa1907c 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -574,6 +574,7 @@ bool WebContentsImpl::OnMessageReceived(const IPC::Message& message) {
IPC_MESSAGE_HANDLER(ViewHostMsg_EndColorChooser, OnEndColorChooser)
IPC_MESSAGE_HANDLER(ViewHostMsg_SetSelectedColorInColorChooser,
OnSetSelectedColorInColorChooser)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_PepperPluginHung, OnPepperPluginHung)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP_EX()
@@ -1769,6 +1770,13 @@ void WebContentsImpl::OnSetSelectedColorInColorChooser(int color_chooser_id,
color_chooser_->SetSelectedColor(color);
}
+void TabContents::OnPepperPluginHung(int plugin_child_id,
+ const FilePath& path,
+ bool is_hung) {
+ if (delegate_)
+ delegate_->PluginHungStatusChanged(this, plugin_child_id, path, is_hung);
+}
+
// Notifies the RenderWidgetHost instance about the fact that the page is
// loading, or done loading and calls the base implementation.
void WebContentsImpl::SetIsLoading(bool is_loading,
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 1c0287b..d37dc44 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -461,6 +461,9 @@ class CONTENT_EXPORT WebContentsImpl
void OnEndColorChooser(int color_chooser_id);
void OnSetSelectedColorInColorChooser(int color_chooser_id,
const SkColor& color);
+ void OnPepperPluginHung(int plugin_child_id,
+ const FilePath& path,
+ bool is_hung);
// Changes the IsLoading state and notifies delegate as needed
// |details| is used to provide details on the load that just finished
diff --git a/content/common/view_messages.h b/content/common/view_messages.h
index ea01f54..8d75190 100644
--- a/content/common/view_messages.h
+++ b/content/common/view_messages.h
@@ -1175,7 +1175,12 @@ IPC_MESSAGE_ROUTED2(ViewMsg_SavePageAsMHTML,
IPC_MESSAGE_CONTROL1(ViewMsg_TempCrashWithData,
GURL /* data */)
+// Enable or disable inverting of web content pixels, for users who prefer
+// white-on-black.
+IPC_MESSAGE_ROUTED1(ViewMsg_InvertWebContent,
+ bool /* invert */)
+// -----------------------------------------------------------------------------
// Messages sent from the renderer to the browser.
// Sent by the renderer when it is creating a new window. The browser creates
@@ -1642,14 +1647,21 @@ IPC_MESSAGE_ROUTED3(ViewHostMsg_WebUISend,
std::string /* message */,
base::ListValue /* args */)
-// A renderer sends this to the browser process when it wants to
-// create a ppapi plugin. The browser will create the plugin process if
-// necessary, and will return a handle to the channel on success.
-// On error an empty string is returned.
-IPC_SYNC_MESSAGE_CONTROL1_2(ViewHostMsg_OpenChannelToPepperPlugin,
+// A renderer sends this to the browser process when it wants to create a ppapi
+// plugin. The browser will create the plugin process if necessary, and will
+// return a handle to the channel on success.
+//
+// The plugin_child_id is the ChildProcessHost ID assigned in the browser
+// process. This ID is valid only in the context of the browser process and is
+// used to identify the proper process when the renderer notifies it that the
+// plugin is hung.
+//
+// On error an empty string and null handles are returned.
+IPC_SYNC_MESSAGE_CONTROL1_3(ViewHostMsg_OpenChannelToPepperPlugin,
FilePath /* path */,
base::ProcessHandle /* plugin_process_handle */,
- IPC::ChannelHandle /* handle to channel */)
+ IPC::ChannelHandle /* handle to channel */,
+ int /* plugin_child_id */)
// A renderer sends this to the browser process when it wants to
// create a ppapi broker. The browser will create the broker process
@@ -1992,7 +2004,11 @@ IPC_MESSAGE_ROUTED2(ViewHostMsg_DomOperationResponse,
std::string /* json_string */,
int /* automation_id */)
-// Enable or disable inverting of web content pixels, for users who prefer
-// white-on-black.
-IPC_MESSAGE_ROUTED1(ViewMsg_InvertWebContent,
- bool /* invert */)
+// Sent to the browser when the renderer detects it is blocked on a pepper
+// plugin message for too long. This is also sent when it becomes unhung
+// (according to the value of is_hung). The browser can give the user the
+// option of killing the plugin.
+IPC_MESSAGE_ROUTED3(ViewHostMsg_PepperPluginHung,
+ int /* plugin_child_id */,
+ FilePath /* path */,
+ bool /* is_hung */)
diff --git a/content/content_renderer.gypi b/content/content_renderer.gypi
index d64fe40..b0a2037 100644
--- a/content/content_renderer.gypi
+++ b/content/content_renderer.gypi
@@ -123,6 +123,8 @@
'renderer/pepper/pepper_broker_impl.h',
'renderer/pepper/pepper_device_enumeration_event_handler.cc',
'renderer/pepper/pepper_device_enumeration_event_handler.h',
+ 'renderer/pepper/pepper_hung_plugin_filter.cc',
+ 'renderer/pepper/pepper_hung_plugin_filter.h',
'renderer/pepper/pepper_parent_context_provider.cc',
'renderer/pepper/pepper_parent_context_provider.h',
'renderer/pepper/pepper_platform_audio_input_impl.cc',
diff --git a/content/public/browser/plugin_service.h b/content/public/browser/plugin_service.h
index 5be3d08..862aca4 100644
--- a/content/public/browser/plugin_service.h
+++ b/content/public/browser/plugin_service.h
@@ -93,6 +93,11 @@ class PluginService {
virtual bool GetPluginInfoByPath(const FilePath& plugin_path,
webkit::WebPluginInfo* info) = 0;
+ // Returns the display name for the plugin identified by the given path. If
+ // the path doesn't identify a plugin, or the plugin has no display name,
+ // this will attempt to generate a display name from the path.
+ virtual string16 GetPluginDisplayNameByPath(const FilePath& plugin_path) = 0;
+
// Asynchronously loads plugins if necessary and then calls back to the
// provided function on the calling MessageLoop on completion.
virtual void GetPlugins(const GetPluginsCallback& callback) = 0;
diff --git a/content/public/browser/render_process_host.h b/content/public/browser/render_process_host.h
index dc64905..eec6a6b 100644
--- a/content/public/browser/render_process_host.h
+++ b/content/public/browser/render_process_host.h
@@ -131,7 +131,7 @@ class CONTENT_EXPORT RenderProcessHost : public IPC::Message::Sender,
// sending non-threadsafe pointers to other threads).
//
// This ID will be unique for all child processes, including workers, plugins,
- // etc. It is generated by ChildProcessInfo.
+ // etc.
virtual int GetID() const = 0;
// Returns the render widget host for the routing id passed in.
diff --git a/content/public/browser/web_contents_delegate.h b/content/public/browser/web_contents_delegate.h
index 3972c2d..682b38438 100644
--- a/content/public/browser/web_contents_delegate.h
+++ b/content/public/browser/web_contents_delegate.h
@@ -372,6 +372,17 @@ class CONTENT_EXPORT WebContentsDelegate {
// Notification that a plugin has crashed.
virtual void CrashedPlugin(WebContents* tab, const FilePath& plugin_path) {}
+ // Notication that the given plugin has hung or become unhung. This
+ // notification is only for Pepper plugins.
+ //
+ // The plugin_child_id is the unique child process ID from the plugin. Note
+ // that this ID is supplied by the renderer, so should be validated before
+ // it's used for anything in case there's an exploited renderer.
+ virtual void PluginHungStatusChanged(WebContents* tab,
+ int plugin_child_id,
+ const FilePath& plugin_path,
+ bool is_hung) {}
+
// Invoked when the preferred size of the contents has been changed.
virtual void UpdatePreferredSize(WebContents* tab,
const gfx::Size& pref_size) {}
diff --git a/content/renderer/pepper/pepper_hung_plugin_filter.cc b/content/renderer/pepper/pepper_hung_plugin_filter.cc
new file mode 100644
index 0000000..4da00fc
--- /dev/null
+++ b/content/renderer/pepper/pepper_hung_plugin_filter.cc
@@ -0,0 +1,153 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/renderer/pepper/pepper_hung_plugin_filter.h"
+
+#include "base/bind.h"
+#include "content/common/view_messages.h"
+#include "content/renderer/render_thread_impl.h"
+
+namespace content {
+
+namespace {
+
+// We'll consider the plugin hung after not hearing anything for this long.
+const int kHungThresholdSec = 10;
+
+// If we ever are blocked for this long, we'll consider the plugin hung, even
+// if we continue to get messages (which is why the above hung threshold never
+// kicked in). Maybe the plugin is spamming us with events and never unblocking
+// and never processing our sync message.
+const int kBlockedHardThresholdSec = kHungThresholdSec * 1.5;
+
+} // namespace
+
+PepperHungPluginFilter::PepperHungPluginFilter(const FilePath& plugin_path,
+ int view_routing_id,
+ int plugin_child_id)
+ : plugin_path_(plugin_path),
+ view_routing_id_(view_routing_id),
+ plugin_child_id_(plugin_child_id),
+ filter_(RenderThread::Get()->GetSyncMessageFilter()),
+ io_loop_(RenderThreadImpl::current()->GetIOLoopProxy()),
+ pending_sync_message_count_(0),
+ hung_plugin_showing_(false),
+ timer_task_pending_(false) {
+}
+
+PepperHungPluginFilter::~PepperHungPluginFilter() {
+}
+
+void PepperHungPluginFilter::BeginBlockOnSyncMessage() {
+ base::AutoLock lock(lock_);
+ if (pending_sync_message_count_ == 0)
+ began_blocking_time_ = base::TimeTicks::Now();
+ pending_sync_message_count_++;
+
+ EnsureTimerScheduled();
+}
+
+void PepperHungPluginFilter::EndBlockOnSyncMessage() {
+ base::AutoLock lock(lock_);
+ pending_sync_message_count_--;
+ DCHECK(pending_sync_message_count_ >= 0);
+
+ MayHaveBecomeUnhung();
+}
+
+void PepperHungPluginFilter::OnFilterAdded(IPC::Channel* channel) {
+}
+
+void PepperHungPluginFilter::OnFilterRemoved() {
+ base::AutoLock lock(lock_);
+ MayHaveBecomeUnhung();
+}
+
+void PepperHungPluginFilter::OnChannelError() {
+ base::AutoLock lock(lock_);
+ MayHaveBecomeUnhung();
+}
+
+bool PepperHungPluginFilter::OnMessageReceived(const IPC::Message& message) {
+ // Just track incoming message times but don't handle any messages.
+ base::AutoLock lock(lock_);
+ last_message_received_ = base::TimeTicks::Now();
+ MayHaveBecomeUnhung();
+ return false;
+}
+
+void PepperHungPluginFilter::EnsureTimerScheduled() {
+ lock_.AssertAcquired();
+ if (timer_task_pending_)
+ return;
+
+ timer_task_pending_ = true;
+ io_loop_->PostDelayedTask(FROM_HERE,
+ base::Bind(&PepperHungPluginFilter::OnHangTimer, this),
+ base::TimeDelta::FromSeconds(kHungThresholdSec));
+}
+
+void PepperHungPluginFilter::MayHaveBecomeUnhung() {
+ if (!hung_plugin_showing_ || IsHung())
+ return;
+
+ SendHungMessage(false);
+ hung_plugin_showing_ = false;
+}
+
+bool PepperHungPluginFilter::IsHung() const {
+ lock_.AssertAcquired();
+
+ if (!pending_sync_message_count_)
+ return false; // Not blocked on a sync message.
+
+ base::TimeTicks now = base::TimeTicks::Now();
+
+ DCHECK(!began_blocking_time_.is_null());
+ if (now - began_blocking_time_ >=
+ base::TimeDelta::FromSeconds(kBlockedHardThresholdSec))
+ return true; // Been blocked too long regardless of what the plugin is
+ // sending us.
+
+ base::TimeDelta hung_threshold =
+ base::TimeDelta::FromSeconds(kHungThresholdSec);
+ if (now - began_blocking_time_ >= hung_threshold &&
+ (last_message_received_.is_null() ||
+ now - last_message_received_ >= hung_threshold))
+ return true; // Blocked and haven't received a message in too long.
+
+ return false;
+}
+
+void PepperHungPluginFilter::OnHangTimer() {
+ base::AutoLock lock(lock_);
+ timer_task_pending_ = false;
+
+ if (!IsHung()) {
+ if (pending_sync_message_count_ > 0) {
+ // Got a timer message while we're waiting on a sync message. We need
+ // to schedule another timer message because the latest sync message
+ // would not have scheduled one (we only have one out-standing timer at
+ // a time).
+ DCHECK(!began_blocking_time_.is_null());
+ base::TimeDelta delay =
+ base::TimeDelta::FromSeconds(kHungThresholdSec) -
+ (base::TimeTicks::Now() - began_blocking_time_);
+ io_loop_->PostDelayedTask(FROM_HERE,
+ base::Bind(&PepperHungPluginFilter::OnHangTimer, this),
+ delay);
+ }
+ return;
+ }
+
+ hung_plugin_showing_ = true;
+ SendHungMessage(true);
+}
+
+void PepperHungPluginFilter::SendHungMessage(bool is_hung) {
+ filter_->Send(new ViewHostMsg_PepperPluginHung(
+ view_routing_id_, plugin_child_id_, plugin_path_, is_hung));
+}
+
+} // namespace content
diff --git a/content/renderer/pepper/pepper_hung_plugin_filter.h b/content/renderer/pepper/pepper_hung_plugin_filter.h
new file mode 100644
index 0000000..5fb63ef
--- /dev/null
+++ b/content/renderer/pepper/pepper_hung_plugin_filter.h
@@ -0,0 +1,109 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_RENDERER_PEPPER_PEPPER_HUNG_PLUGIN_FILTER_H_
+#define CONTENT_RENDERER_PEPPER_PEPPER_HUNG_PLUGIN_FILTER_H_
+
+#include "base/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop_proxy.h"
+#include "base/synchronization/lock.h"
+#include "ipc/ipc_channel_proxy.h"
+#include "ipc/ipc_sync_message_filter.h"
+#include "ppapi/proxy/host_dispatcher.h"
+
+namespace content {
+
+// This class monitors a renderer <-> pepper plugin channel on the I/O thread
+// of the renderer for a hung plugin.
+//
+// If the plugin is not responding to sync messages, it will notify the browser
+// process and give the user the option to kill the hung plugin.
+//
+// Note that this class must be threadsafe since it will get the begin/end
+// block notifications on the main thread, but the filter is run on the I/O
+// thread. This is important since when we're blocked on a sync message to a
+// hung plugin, the main thread is frozen.
+//
+// NOTE: This class is refcounted (via SyncMessageStatusReceiver).
+class PepperHungPluginFilter
+ : public ppapi::proxy::HostDispatcher::SyncMessageStatusReceiver {
+ public:
+ // The |view_routing_id| is the ID of the render_view so that this class can
+ // send messages to the browser via that view's route. The |plugin_child_id|
+ // is the ID in the browser process of the pepper plugin process host. We use
+ // this to identify the proper plugin process to terminate.
+ PepperHungPluginFilter(const FilePath& plugin_path,
+ int view_routing_id,
+ int plugin_child_id);
+ virtual ~PepperHungPluginFilter();
+
+ // SyncMessageStatusReceiver implementation.
+ virtual void BeginBlockOnSyncMessage() OVERRIDE;
+ virtual void EndBlockOnSyncMessage() OVERRIDE;
+
+ // MessageFilter implementation.
+ virtual void OnFilterAdded(IPC::Channel* channel) OVERRIDE;
+ virtual void OnFilterRemoved() OVERRIDE;
+ virtual void OnChannelError() OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+
+ private:
+ // Makes sure that the hung timer is scheduled.
+ void EnsureTimerScheduled();
+
+ // Checks whether the plugin could have transitioned from hung to unhung and
+ // notifies the browser if so.
+ void MayHaveBecomeUnhung();
+
+ // Checks if the plugin is considered hung based on whether it has been
+ // blocked for long enough.
+ bool IsHung() const;
+
+ // Timer handler that checks for a hang after a timeout.
+ void OnHangTimer();
+
+ // Sends the hung/unhung message to the browser process.
+ void SendHungMessage(bool is_hung);
+
+ base::Lock lock_;
+
+ FilePath plugin_path_;
+ int view_routing_id_;
+ int plugin_child_id_;
+
+ // Used to post messages to the renderer <-> browser message channel from
+ // other threads without having to worry about the lifetime of that object.
+ // We don't actually use the "sync" feature of this filter.
+ scoped_refptr<IPC::SyncMessageFilter> filter_;
+
+ scoped_refptr<base::MessageLoopProxy> io_loop_;
+
+ // The time when we start being blocked on a sync message. If there are
+ // nested ones, this is the time of the outermost one.
+ //
+ // This will be is_null() if we've never blocked.
+ base::TimeTicks began_blocking_time_;
+
+ // Time that the last message was received from the plugin.
+ //
+ // This will be is_null() if we've never received any messages.
+ base::TimeTicks last_message_received_;
+
+ // Number of nested sync messages that we're blocked on.
+ int pending_sync_message_count_;
+
+ // True when we've sent the "plugin is hung" message to the browser. We track
+ // this so we know to look for it becoming unhung and send the corresponding
+ // message to the browser.
+ bool hung_plugin_showing_;
+
+ bool timer_task_pending_;
+
+ DISALLOW_COPY_AND_ASSIGN(PepperHungPluginFilter);
+};
+
+} // namespace content
+
+#endif // CONTENT_RENDERER_PEPPER_PEPPER_HUNG_PLUGIN_FILTER_H_
diff --git a/content/renderer/pepper/pepper_plugin_delegate_impl.cc b/content/renderer/pepper/pepper_plugin_delegate_impl.cc
index bb023e8..30c4806 100644
--- a/content/renderer/pepper/pepper_plugin_delegate_impl.cc
+++ b/content/renderer/pepper/pepper_plugin_delegate_impl.cc
@@ -41,6 +41,7 @@
#include "content/renderer/p2p/socket_dispatcher.h"
#include "content/renderer/pepper/pepper_broker_impl.h"
#include "content/renderer/pepper/pepper_device_enumeration_event_handler.h"
+#include "content/renderer/pepper/pepper_hung_plugin_filter.h"
#include "content/renderer/pepper/pepper_platform_audio_input_impl.h"
#include "content/renderer/pepper/pepper_platform_audio_output_impl.h"
#include "content/renderer/pepper/pepper_platform_context_3d_impl.h"
@@ -105,7 +106,8 @@ class HostDispatcherWrapper
const IPC::ChannelHandle& channel_handle,
PP_Module pp_module,
ppapi::proxy::Dispatcher::GetInterfaceFunc local_get_interface,
- const ppapi::Preferences& preferences) {
+ const ppapi::Preferences& preferences,
+ PepperHungPluginFilter* filter) {
if (channel_handle.name.empty())
return false;
@@ -117,7 +119,7 @@ class HostDispatcherWrapper
dispatcher_delegate_.reset(new PepperProxyChannelDelegateImpl);
dispatcher_.reset(new ppapi::proxy::HostDispatcher(
- plugin_process_handle, pp_module, local_get_interface));
+ plugin_process_handle, pp_module, local_get_interface, filter));
if (!dispatcher_->InitHostWithChannel(dispatcher_delegate_.get(),
channel_handle,
@@ -234,13 +236,18 @@ PepperPluginDelegateImpl::CreatePepperPluginModule(
// Out of process: have the browser start the plugin process for us.
base::ProcessHandle plugin_process_handle = base::kNullProcessHandle;
IPC::ChannelHandle channel_handle;
+ int plugin_child_id = 0;
render_view_->Send(new ViewHostMsg_OpenChannelToPepperPlugin(
- path, &plugin_process_handle, &channel_handle));
+ path, &plugin_process_handle, &channel_handle, &plugin_child_id));
if (channel_handle.name.empty()) {
// Couldn't be initialized.
return scoped_refptr<webkit::ppapi::PluginModule>();
}
+ scoped_refptr<PepperHungPluginFilter> hung_filter(
+ new PepperHungPluginFilter(path, render_view_->routing_id(),
+ plugin_child_id));
+
// Create a new HostDispatcher for the proxying, and hook it to a new
// PluginModule. Note that AddLiveModule must be called before any early
// returns since the module's destructor will remove itself.
@@ -253,7 +260,8 @@ PepperPluginDelegateImpl::CreatePepperPluginModule(
channel_handle,
module->pp_module(),
webkit::ppapi::PluginModule::GetLocalGetInterfaceFunc(),
- GetPreferences()))
+ GetPreferences(),
+ hung_filter.get()))
return scoped_refptr<webkit::ppapi::PluginModule>();
module->InitAsProxied(dispatcher.release());
return module;
diff --git a/ppapi/proxy/dispatcher.cc b/ppapi/proxy/dispatcher.cc
index a2a1d71..39b1a06 100644
--- a/ppapi/proxy/dispatcher.cc
+++ b/ppapi/proxy/dispatcher.cc
@@ -50,6 +50,9 @@ base::MessageLoopProxy* Dispatcher::GetIPCMessageLoop() {
void Dispatcher::AddIOThreadMessageFilter(
IPC::ChannelProxy::MessageFilter* filter) {
+ // Our filter is refcounted. The channel will call the destruct method on the
+ // filter when the channel is done with it, so the corresponding Release()
+ // happens there.
channel()->AddFilter(filter);
}
diff --git a/ppapi/proxy/dispatcher.h b/ppapi/proxy/dispatcher.h
index 35a82ab..df0f484 100644
--- a/ppapi/proxy/dispatcher.h
+++ b/ppapi/proxy/dispatcher.h
@@ -68,9 +68,6 @@ class PPAPI_PROXY_EXPORT Dispatcher : public ProxyChannel {
base::MessageLoopProxy* GetIPCMessageLoop();
// Adds the given filter to the IO thread. Takes ownership of the pointer.
- // TODO(brettw) remove this. It's a hack to support the Flash
- // ModuleLocalThreadAdapter. When the thread stuff is sorted out, this
- // implementation detail should be hidden.
void AddIOThreadMessageFilter(IPC::ChannelProxy::MessageFilter* filter);
// TODO(brettw): What is this comment referring to?
diff --git a/ppapi/proxy/host_dispatcher.cc b/ppapi/proxy/host_dispatcher.cc
index d1c28f7..9256fe9 100644
--- a/ppapi/proxy/host_dispatcher.cc
+++ b/ppapi/proxy/host_dispatcher.cc
@@ -63,8 +63,10 @@ class BoolRestorer {
HostDispatcher::HostDispatcher(base::ProcessHandle remote_process_handle,
PP_Module module,
- GetInterfaceFunc local_get_interface)
+ GetInterfaceFunc local_get_interface,
+ SyncMessageStatusReceiver* sync_status)
: Dispatcher(remote_process_handle, local_get_interface),
+ sync_status_(sync_status),
pp_module_(module),
ppb_proxy_(NULL),
allow_plugin_reentrancy_(false) {
@@ -92,6 +94,8 @@ bool HostDispatcher::InitHostWithChannel(
const ppapi::Preferences& preferences) {
if (!Dispatcher::InitWithChannel(delegate, channel_handle, is_client))
return false;
+ AddIOThreadMessageFilter(sync_status_.get());
+
Send(new PpapiMsg_SetPreferences(preferences));
return true;
}
@@ -152,7 +156,12 @@ bool HostDispatcher::Send(IPC::Message* msg) {
// waiting for the reply, dispatches an incoming ExecuteScript call which
// destroys the plugin module and in turn the dispatcher.
ScopedModuleReference scoped_ref(this);
- return Dispatcher::Send(msg);
+
+ sync_status_->BeginBlockOnSyncMessage();
+ bool result = Dispatcher::Send(msg);
+ sync_status_->EndBlockOnSyncMessage();
+
+ return result;
} else {
// We don't want to have a scoped ref for async message cases since since
// async messages are sent during module desruction. In this case, the
diff --git a/ppapi/proxy/host_dispatcher.h b/ppapi/proxy/host_dispatcher.h
index 1bacdfa..2d44979 100644
--- a/ppapi/proxy/host_dispatcher.h
+++ b/ppapi/proxy/host_dispatcher.h
@@ -9,8 +9,10 @@
#include <string>
#include <vector>
+#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/process.h"
+#include "ipc/ipc_channel_proxy.h"
#include "ppapi/c/pp_instance.h"
#include "ppapi/proxy/dispatcher.h"
#include "ppapi/shared_impl/function_group_base.h"
@@ -25,12 +27,31 @@ namespace proxy {
class PPAPI_PROXY_EXPORT HostDispatcher : public Dispatcher {
public:
- // Constructor for the renderer side.
+ // This interface receives notifications about sync messages being sent by
+ // the dispatcher to the plugin process. It is used to detect a hung plugin.
+ //
+ // Note that there can be nested sync messages, so the begin/end status
+ // actually represents a stack of blocking messages.
+ class SyncMessageStatusReceiver : public IPC::ChannelProxy::MessageFilter {
+ public:
+ virtual ~SyncMessageStatusReceiver() {}
+
+ // Notification that a sync message is about to be sent out.
+ virtual void BeginBlockOnSyncMessage() = 0;
+
+ // Notification that a sync message reply was received and the dispatcher
+ // is no longer blocked on a sync message.
+ virtual void EndBlockOnSyncMessage() = 0;
+ };
+
+ // Constructor for the renderer side. This will take a reference to the
+ // SyncMessageStatusReceiver.
//
// You must call InitHostWithChannel after the constructor.
HostDispatcher(base::ProcessHandle host_process_handle,
PP_Module module,
- GetInterfaceFunc local_get_interface);
+ GetInterfaceFunc local_get_interface,
+ SyncMessageStatusReceiver* sync_status);
~HostDispatcher();
// You must call this function before anything else. Returns true on success.
@@ -90,6 +111,8 @@ class PPAPI_PROXY_EXPORT HostDispatcher : public Dispatcher {
const std::string& source,
const std::string& value);
+ scoped_refptr<SyncMessageStatusReceiver> sync_status_;
+
PP_Module pp_module_;
// Maps interface name to whether that interface is supported. If an interface
diff --git a/ppapi/proxy/ppapi_proxy_test.cc b/ppapi/proxy/ppapi_proxy_test.cc
index 7bfacc5..98baafd 100644
--- a/ppapi/proxy/ppapi_proxy_test.cc
+++ b/ppapi/proxy/ppapi_proxy_test.cc
@@ -5,6 +5,7 @@
#include "ppapi/proxy/ppapi_proxy_test.h"
#include "base/bind.h"
+#include "base/compiler_specific.h"
#include "base/message_loop_proxy.h"
#include "base/observer_list.h"
#include "ipc/ipc_sync_channel.h"
@@ -240,8 +241,16 @@ void PluginProxyTest::TearDown() {
// HostProxyTestHarness --------------------------------------------------------
+class HostProxyTestHarness::MockSyncMessageStatusReceiver
+ : public HostDispatcher::SyncMessageStatusReceiver {
+ public:
+ virtual void BeginBlockOnSyncMessage() OVERRIDE {}
+ virtual void EndBlockOnSyncMessage() OVERRIDE {}
+};
+
HostProxyTestHarness::HostProxyTestHarness()
- : host_globals_(PpapiGlobals::ForTest()) {
+ : host_globals_(PpapiGlobals::ForTest()),
+ status_receiver_(new MockSyncMessageStatusReceiver) {
}
HostProxyTestHarness::~HostProxyTestHarness() {
@@ -257,7 +266,8 @@ void HostProxyTestHarness::SetUpHarness() {
host_dispatcher_.reset(new HostDispatcher(
base::Process::Current().handle(),
pp_module(),
- &MockGetInterface));
+ &MockGetInterface,
+ status_receiver_.get()));
host_dispatcher_->InitWithTestSink(&sink());
HostDispatcher::SetForInstance(pp_instance(), host_dispatcher_.get());
}
@@ -274,7 +284,8 @@ void HostProxyTestHarness::SetUpHarnessWithChannel(
host_dispatcher_.reset(new HostDispatcher(
base::Process::Current().handle(),
pp_module(),
- &MockGetInterface));
+ &MockGetInterface,
+ status_receiver_.get()));
ppapi::Preferences preferences;
host_dispatcher_->InitHostWithChannel(&delegate_mock_, channel_handle,
is_client, preferences);
diff --git a/ppapi/proxy/ppapi_proxy_test.h b/ppapi/proxy/ppapi_proxy_test.h
index 6278f71..b2a3f36 100644
--- a/ppapi/proxy/ppapi_proxy_test.h
+++ b/ppapi/proxy/ppapi_proxy_test.h
@@ -198,9 +198,13 @@ class HostProxyTestHarness : public ProxyTestHarnessBase {
};
private:
+ class MockSyncMessageStatusReceiver;
+
ppapi::TestGlobals host_globals_;
scoped_ptr<HostDispatcher> host_dispatcher_;
DelegateMock delegate_mock_;
+
+ scoped_ptr<MockSyncMessageStatusReceiver> status_receiver_;
};
class HostProxyTest : public HostProxyTestHarness, public testing::Test {