// 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/child/child_process.h"
#include "content/common/frame_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 base::FilePath& plugin_path,
    int frame_routing_id,
    int plugin_child_id)
    : plugin_path_(plugin_path),
      frame_routing_id_(frame_routing_id),
      plugin_child_id_(plugin_child_id),
      filter_(RenderThread::Get()->GetSyncMessageFilter()),
      io_loop_(ChildProcess::current()->io_message_loop_proxy()),
      pending_sync_message_count_(0),
      hung_plugin_showing_(false),
      timer_task_pending_(false) {}

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::OnFilterAdded(IPC::Sender* sender) {}

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::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() {
  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::TimeDelta::FromSeconds(kBlockedHardThresholdSec);

  // Hung after a soft threshold from last message of any sort.
  base::TimeTicks soft_time =
      last_message_received_ + base::TimeDelta::FromSeconds(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 > base::TimeDelta()) {
    // 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_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 FrameHostMsg_PepperPluginHung(
      frame_routing_id_, plugin_child_id_, plugin_path_, is_hung));
}

}  // namespace content
