root/chrome/browser/ui/hung_plugin_tab_helper.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. data
  2. DumpBrowserInBlockingPool
  3. DumpRenderersInBlockingPool
  4. DumpAndTerminatePluginInBlockingPool
  5. KillPluginOnIOThread
  6. Create
  7. button_text_
  8. GetIconID
  9. GetMessageText
  10. GetButtons
  11. GetButtonLabel
  12. Accept
  13. timer
  14. PluginCrashed
  15. PluginHungStatusChanged
  16. Observe
  17. KillPlugin
  18. OnReshowTimer
  19. ShowBar
  20. CloseBar

// 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/files/file_path.h"
#include "base/memory/scoped_ptr.h"
#include "base/process/process.h"
#include "base/rand_util.h"
#include "build/build_config.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/infobars/confirm_infobar_delegate.h"
#include "chrome/browser/infobars/infobar.h"
#include "chrome/browser/infobars/infobar_manager.h"
#include "chrome/browser/infobars/infobar_service.h"
#include "chrome/common/chrome_version_info.h"
#include "content/public/browser/browser_child_process_host_iterator.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_data.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/plugin_service.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/process_type.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.h"
#include "ui/base/l10n/l10n_util.h"

#if defined(OS_WIN)
#include "base/win/scoped_handle.h"
#include "chrome/browser/hang_monitor/hang_crash_dump_win.h"
#endif


namespace {

#if defined(OS_WIN)

// OwnedHandleVector ----------------------------------------------------------

class OwnedHandleVector {
 public:
  typedef std::vector<HANDLE> Handles;
  OwnedHandleVector();
  ~OwnedHandleVector();

  Handles* data() { return &data_; }

 private:
  Handles data_;

  DISALLOW_COPY_AND_ASSIGN(OwnedHandleVector);
};

OwnedHandleVector::OwnedHandleVector() {
}

OwnedHandleVector::~OwnedHandleVector() {
  for (Handles::iterator iter = data_.begin(); iter != data_.end(); ++iter)
    ::CloseHandle(*iter);
}


// Helpers --------------------------------------------------------------------

const char kDumpChildProcessesSequenceName[] = "DumpChildProcesses";

void DumpBrowserInBlockingPool() {
  CrashDumpForHangDebugging(::GetCurrentProcess());
}

void DumpRenderersInBlockingPool(OwnedHandleVector* renderer_handles) {
  for (OwnedHandleVector::Handles::const_iterator iter =
           renderer_handles->data()->begin();
       iter != renderer_handles->data()->end(); ++iter) {
    CrashDumpForHangDebugging(*iter);
  }
}

void DumpAndTerminatePluginInBlockingPool(
    base::win::ScopedHandle* plugin_handle) {
  CrashDumpAndTerminateHungChildProcess(plugin_handle->Get());
}

#endif  // defined(OS_WIN)

// 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) {
#if defined(OS_WIN)
      HANDLE handle = NULL;
      HANDLE current_process = ::GetCurrentProcess();
      ::DuplicateHandle(current_process, data.handle, current_process, &handle,
                        0, FALSE, DUPLICATE_SAME_ACCESS);
      // Run it in blocking pool so that it won't block the I/O thread. Besides,
      // we would like to make sure that it happens after dumping renderers.
      content::BrowserThread::PostBlockingPoolSequencedTask(
          kDumpChildProcessesSequenceName, FROM_HERE,
          base::Bind(&DumpAndTerminatePluginInBlockingPool,
                     base::Owned(new base::win::ScopedHandle(handle))));
#else
      base::KillProcess(data.handle, content::RESULT_CODE_HUNG, false);
#endif
      break;
    }
    ++iter;
  }
  // Ignore the case where we didn't find the plugin, it may have terminated
  // before this function could run.
}

}  // namespace


// HungPluginInfoBarDelegate --------------------------------------------------

class HungPluginInfoBarDelegate : public ConfirmInfoBarDelegate {
 public:
  // Creates a hung plugin infobar and delegate and adds the infobar to
  // |infobar_service|.  Returns the infobar if it was successfully added.
  static InfoBar* Create(InfoBarService* infobar_service,
                         HungPluginTabHelper* helper,
                         int plugin_child_id,
                         const base::string16& plugin_name);

 private:
  HungPluginInfoBarDelegate(HungPluginTabHelper* helper,
                            int plugin_child_id,
                            const base::string16& plugin_name);
  virtual ~HungPluginInfoBarDelegate();

  // ConfirmInfoBarDelegate:
  virtual int GetIconID() const OVERRIDE;
  virtual base::string16 GetMessageText() const OVERRIDE;
  virtual int GetButtons() const OVERRIDE;
  virtual base::string16 GetButtonLabel(InfoBarButton button) const OVERRIDE;
  virtual bool Accept() OVERRIDE;

  HungPluginTabHelper* helper_;
  int plugin_child_id_;

  base::string16 message_;
  base::string16 button_text_;
};

// static
InfoBar* HungPluginInfoBarDelegate::Create(InfoBarService* infobar_service,
                                           HungPluginTabHelper* helper,
                                           int plugin_child_id,
                                           const base::string16& plugin_name) {
  return infobar_service->AddInfoBar(ConfirmInfoBarDelegate::CreateInfoBar(
      scoped_ptr<ConfirmInfoBarDelegate>(new HungPluginInfoBarDelegate(
          helper, plugin_child_id, plugin_name))));
}

HungPluginInfoBarDelegate::HungPluginInfoBarDelegate(
    HungPluginTabHelper* helper,
    int plugin_child_id,
    const base::string16& plugin_name)
    : ConfirmInfoBarDelegate(),
      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)) {
}

HungPluginInfoBarDelegate::~HungPluginInfoBarDelegate() {
}

int HungPluginInfoBarDelegate::GetIconID() const {
  return IDR_INFOBAR_PLUGIN_CRASHED;
}

base::string16 HungPluginInfoBarDelegate::GetMessageText() const {
  return message_;
}

int HungPluginInfoBarDelegate::GetButtons() const {
  return BUTTON_OK;
}

base::string16 HungPluginInfoBarDelegate::GetButtonLabel(
    InfoBarButton button) const {
  return button_text_;
}

bool HungPluginInfoBarDelegate::Accept() {
  helper_->KillPlugin(plugin_child_id_);
  return true;
}


// HungPluginTabHelper::PluginState -------------------------------------------

// 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 HungPluginTabHelper::PluginState {
  // Initializes the plugin state to be a hung plugin.
  PluginState(const base::FilePath& p, const base::string16& n);
  ~PluginState();

  base::FilePath path;
  base::string16 name;

  // Possibly-null if we're not showing an infobar right now.
  InfoBar* infobar;

  // 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:
  // Initial delay in seconds before re-showing the hung plugin message.
  static const int kInitialReshowDelaySec;

  // Since the scope of the timer manages our callback, this struct should
  // not be copied.
  DISALLOW_COPY_AND_ASSIGN(PluginState);
};

// static
const int HungPluginTabHelper::PluginState::kInitialReshowDelaySec = 10;

HungPluginTabHelper::PluginState::PluginState(const base::FilePath& p,
                                              const base::string16& n)
    : path(p),
      name(n),
      infobar(NULL),
      next_reshow_delay(base::TimeDelta::FromSeconds(kInitialReshowDelaySec)),
      timer(false, false) {
}

HungPluginTabHelper::PluginState::~PluginState() {
}


// HungPluginTabHelper --------------------------------------------------------

DEFINE_WEB_CONTENTS_USER_DATA_KEY(HungPluginTabHelper);

HungPluginTabHelper::HungPluginTabHelper(content::WebContents* contents)
    : content::WebContentsObserver(contents) {
  registrar_.Add(this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
                 content::NotificationService::AllSources());
}

HungPluginTabHelper::~HungPluginTabHelper() {
}

void HungPluginTabHelper::PluginCrashed(const base::FilePath& plugin_path,
                                        base::ProcessId plugin_pid) {
  // 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.
  InfoBarService* infobar_service =
      InfoBarService::FromWebContents(web_contents());
  if (!infobar_service)
    return;

  InfoBarManager* infobar_manager = infobar_service->infobar_manager();
  // 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->infobar)
        infobar_manager->RemoveInfoBar(i->second->infobar);
      hung_plugins_.erase(i);
      break;
    }
  }
}

void HungPluginTabHelper::PluginHungStatusChanged(
    int plugin_child_id,
    const base::FilePath& plugin_path,
    bool is_hung) {
  InfoBarService* infobar_service =
      InfoBarService::FromWebContents(web_contents());
  if (!infobar_service)
    return;

  InfoBarManager* infobar_manager = infobar_service->infobar_manager();
  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->infobar)
        infobar_manager->RemoveInfoBar(found->second->infobar);
      hung_plugins_.erase(found);
    }
    return;
  }

  base::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::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  DCHECK_EQ(chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, type);
  InfoBar* infobar = content::Details<InfoBar::RemovedDetails>(details)->first;
  for (PluginStateMap::iterator i = hung_plugins_.begin();
       i != hung_plugins_.end(); ++i) {
    PluginState* state = i->second.get();
    if (state->infobar == infobar) {
      state->infobar = NULL;

      // Schedule the timer to re-show the infobar if the plugin continues to be
      // hung.
      state->timer.Start(FROM_HERE, state->next_reshow_delay,
          base::Bind(&HungPluginTabHelper::OnReshowTimer,
                     base::Unretained(this),
                     i->first));

      // Next time we do this, delay it twice as long to avoid being annoying.
      state->next_reshow_delay *= 2;
      return;
    }
  }
}

void HungPluginTabHelper::KillPlugin(int child_id) {
#if defined(OS_WIN)
  // Dump renderers that are sending or receiving pepper messages, in order to
  // diagnose inter-process deadlocks.
  // Only do that on the Canary channel, for 20% of pepper plugin hangs.
  if (base::RandInt(0, 100) < 20) {
    chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
    if (channel == chrome::VersionInfo::CHANNEL_CANARY) {
      scoped_ptr<OwnedHandleVector> renderer_handles(new OwnedHandleVector);
      HANDLE current_process = ::GetCurrentProcess();
      content::RenderProcessHost::iterator renderer_iter =
          content::RenderProcessHost::AllHostsIterator();
      for (; !renderer_iter.IsAtEnd(); renderer_iter.Advance()) {
        content::RenderProcessHost* host = renderer_iter.GetCurrentValue();
        HANDLE handle = NULL;
        ::DuplicateHandle(current_process, host->GetHandle(), current_process,
                          &handle, 0, FALSE, DUPLICATE_SAME_ACCESS);
        renderer_handles->data()->push_back(handle);
      }
      // If there are a lot of renderer processes, it is likely that we will
      // generate too many crash dumps. They might not all be uploaded/recorded
      // due to our crash dump uploading restrictions. So we just don't generate
      // renderer crash dumps in that case.
      if (renderer_handles->data()->size() > 0 &&
          renderer_handles->data()->size() < 4) {
        content::BrowserThread::PostBlockingPoolSequencedTask(
            kDumpChildProcessesSequenceName, FROM_HERE,
            base::Bind(&DumpBrowserInBlockingPool));
        content::BrowserThread::PostBlockingPoolSequencedTask(
            kDumpChildProcessesSequenceName, FROM_HERE,
            base::Bind(&DumpRenderersInBlockingPool,
                       base::Owned(renderer_handles.release())));
      }
    }
  }
#endif

  PluginStateMap::iterator found = hung_plugins_.find(child_id);
  DCHECK(found != hung_plugins_.end());

  content::BrowserThread::PostTask(content::BrowserThread::IO,
                                   FROM_HERE,
                                   base::Bind(&KillPluginOnIOThread, child_id));
  CloseBar(found->second.get());
}

void HungPluginTabHelper::OnReshowTimer(int child_id) {
  // The timer should have been cancelled if the record isn't in our map
  // anymore.
  PluginStateMap::iterator found = hung_plugins_.find(child_id);
  DCHECK(found != hung_plugins_.end());
  DCHECK(!found->second->infobar);
  ShowBar(child_id, found->second.get());
}

void HungPluginTabHelper::ShowBar(int child_id, PluginState* state) {
  InfoBarService* infobar_service =
      InfoBarService::FromWebContents(web_contents());
  if (!infobar_service)
    return;

  DCHECK(!state->infobar);
  state->infobar = HungPluginInfoBarDelegate::Create(infobar_service, this,
                                                     child_id, state->name);
}

void HungPluginTabHelper::CloseBar(PluginState* state) {
  InfoBarService* infobar_service =
      InfoBarService::FromWebContents(web_contents());
  if (infobar_service && state->infobar) {
    infobar_service->infobar_manager()->RemoveInfoBar(state->infobar);
    state->infobar = NULL;
  }
}

/* [<][>][^][v][top][bottom][index][help] */