chromium/content/renderer/pepper/pepper_hung_plugin_filter.cc

// Copyright 2012 The Chromium Authors
// 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/functional/bind.h"
#include "content/child/child_process.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()
    : io_task_runner_(ChildProcess::current()->io_task_runner()) {}

void PepperHungPluginFilter::BindHungDetectorHost(
    mojo::PendingRemote<mojom::PepperHungDetectorHost> hung_host) {
  io_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&PepperHungPluginFilter::BindHungDetectorHostOnIOThread,
                     this, std::move(hung_host)));
}

void PepperHungPluginFilter::UnbindHungDetectorHostOnIOThread() {
  hung_host_.reset();
}

void PepperHungPluginFilter::BindHungDetectorHostOnIOThread(
    mojo::PendingRemote<mojom::PepperHungDetectorHost> hung_host) {
  DCHECK(io_task_runner_->BelongsToCurrentThread());
  hung_host_.Bind(std::move(hung_host));
}

void PepperHungPluginFilter::BeginBlockOnSyncMessage() {
  base::AutoLock lock(lock_);
  last_message_received_ = base::TimeTicks::Now();
  if (pending_sync_message_count_ == 0)
    began_blocking_time_ = last_message_received_;
  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::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;
}

PepperHungPluginFilter::~PepperHungPluginFilter() {}

void PepperHungPluginFilter::HostDispatcherDestroyed() {
  io_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&PepperHungPluginFilter::UnbindHungDetectorHostOnIOThread,
                     this));
}

void PepperHungPluginFilter::EnsureTimerScheduled() {
  lock_.AssertAcquired();
  if (timer_task_pending_)
    return;

  timer_task_pending_ = true;
  io_task_runner_->PostDelayedTask(
      FROM_HERE, base::BindOnce(&PepperHungPluginFilter::OnHangTimer, this),
      base::Seconds(kHungThresholdSec));
}

void PepperHungPluginFilter::MayHaveBecomeUnhung() {
  lock_.AssertAcquired();
  if (!hung_plugin_showing_ || IsHung())
    return;

  SendHungMessage(false);
  hung_plugin_showing_ = false;
}

base::TimeTicks PepperHungPluginFilter::GetHungTime() const {
  lock_.AssertAcquired();

  DCHECK(pending_sync_message_count_);
  DCHECK(!began_blocking_time_.is_null());
  DCHECK(!last_message_received_.is_null());

  // Always considered hung at the hard threshold.
  base::TimeTicks hard_time =
      began_blocking_time_ + base::Seconds(kBlockedHardThresholdSec);

  // Hung after a soft threshold from last message of any sort.
  base::TimeTicks soft_time =
      last_message_received_ + base::Seconds(kHungThresholdSec);

  return std::min(soft_time, hard_time);
}

bool PepperHungPluginFilter::IsHung() const {
  lock_.AssertAcquired();

  if (!pending_sync_message_count_)
    return false;  // Not blocked on a sync message.

  return base::TimeTicks::Now() > GetHungTime();
}

void PepperHungPluginFilter::OnHangTimer() {
  base::AutoLock lock(lock_);
  timer_task_pending_ = false;

  if (!pending_sync_message_count_)
    return;  // Not blocked any longer.

  base::TimeDelta delay = GetHungTime() - base::TimeTicks::Now();
  if (delay.is_positive()) {
    // 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).
    timer_task_pending_ = true;
    io_task_runner_->PostDelayedTask(
        FROM_HERE, base::BindOnce(&PepperHungPluginFilter::OnHangTimer, this),
        delay);
    return;
  }

  hung_plugin_showing_ = true;
  SendHungMessage(true);
}

void PepperHungPluginFilter::SendHungMessage(bool is_hung) {
  if (!io_task_runner_->BelongsToCurrentThread()) {
    io_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(&PepperHungPluginFilter::SendHungMessage,
                                  this, is_hung));
    return;
  }

  hung_host_->PluginHung(is_hung);
}

}  // namespace content