// Copyright 2014 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 "components/scheduler/renderer/renderer_scheduler_impl.h"

#include "base/bind.h"
#include "base/debug/stack_trace.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_event_argument.h"
#include "cc/output/begin_frame_args.h"
#include "components/scheduler/base/task_queue_impl.h"
#include "components/scheduler/base/task_queue_selector.h"
#include "components/scheduler/base/virtual_time_domain.h"
#include "components/scheduler/child/scheduler_tqm_delegate.h"
#include "components/scheduler/renderer/web_view_scheduler_impl.h"
#include "components/scheduler/renderer/webthread_impl_for_renderer_scheduler.h"

namespace scheduler {
namespace {
// The run time of loading tasks is strongly bimodal.  The vast majority are
// very cheap, but there are usually a handful of very expensive tasks (e.g ~1
// second on a mobile device) so we take a very pesimistic view when estimating
// the cost of loading tasks.
const int kLoadingTaskEstimationSampleCount = 1000;
const double kLoadingTaskEstimationPercentile = 99;
const int kTimerTaskEstimationSampleCount = 1000;
const double kTimerTaskEstimationPercentile = 99;
const int kShortIdlePeriodDurationSampleCount = 10;
const double kShortIdlePeriodDurationPercentile = 50;
// Amount of idle time left in a frame (as a ratio of the vsync interval) above
// which main thread compositing can be considered fast.
const double kFastCompositingIdleTimeThreshold = .2;
}  // namespace

RendererSchedulerImpl::RendererSchedulerImpl(
    scoped_refptr<SchedulerTqmDelegate> main_task_runner)
    : helper_(main_task_runner,
              "renderer.scheduler",
              TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
              TRACE_DISABLED_BY_DEFAULT("renderer.scheduler.debug")),
      idle_helper_(&helper_,
                   this,
                   "renderer.scheduler",
                   TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
                   "RendererSchedulerIdlePeriod",
                   base::TimeDelta()),
      render_widget_scheduler_signals_(this),
      control_task_runner_(helper_.ControlTaskRunner()),
      compositor_task_runner_(
          helper_.NewTaskQueue(TaskQueue::Spec("compositor_tq")
                                   .SetShouldMonitorQuiescence(true))),
      delayed_update_policy_runner_(
          base::Bind(&RendererSchedulerImpl::UpdatePolicy,
                     base::Unretained(this)),
          helper_.ControlTaskRunner()),
      main_thread_only_(compositor_task_runner_,
                        helper_.scheduler_tqm_delegate().get()),
      policy_may_need_update_(&any_thread_lock_),
      weak_factory_(this) {
  throttling_helper_.reset(new ThrottlingHelper(this, "renderer.scheduler"));
  update_policy_closure_ = base::Bind(&RendererSchedulerImpl::UpdatePolicy,
                                      weak_factory_.GetWeakPtr());
  end_renderer_hidden_idle_period_closure_.Reset(base::Bind(
      &RendererSchedulerImpl::EndIdlePeriod, weak_factory_.GetWeakPtr()));

  suspend_timers_when_backgrounded_closure_.Reset(
      base::Bind(&RendererSchedulerImpl::SuspendTimerQueueWhenBackgrounded,
                 weak_factory_.GetWeakPtr()));

  default_loading_task_runner_ = NewLoadingTaskRunner("default_loading_tq");
  default_timer_task_runner_ = NewTimerTaskRunner("default_timer_tq");

  TRACE_EVENT_OBJECT_CREATED_WITH_ID(
      TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererScheduler",
      this);

  helper_.SetObserver(this);
}

RendererSchedulerImpl::~RendererSchedulerImpl() {
  TRACE_EVENT_OBJECT_DELETED_WITH_ID(
      TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererScheduler",
      this);

  for (const scoped_refptr<TaskQueue>& loading_queue : loading_task_runners_) {
    loading_queue->RemoveTaskObserver(
        &MainThreadOnly().loading_task_cost_estimator);
  }
  for (const scoped_refptr<TaskQueue>& timer_queue : timer_task_runners_) {
    timer_queue->RemoveTaskObserver(
        &MainThreadOnly().timer_task_cost_estimator);
  }

  // Ensure the renderer scheduler was shut down explicitly, because otherwise
  // we could end up having stale pointers to the Blink heap which has been
  // terminated by this point.
  DCHECK(MainThreadOnly().was_shutdown);
}

RendererSchedulerImpl::MainThreadOnly::MainThreadOnly(
    const scoped_refptr<TaskQueue>& compositor_task_runner,
    base::TickClock* time_source)
    : loading_task_cost_estimator(time_source,
                                  kLoadingTaskEstimationSampleCount,
                                  kLoadingTaskEstimationPercentile),
      timer_task_cost_estimator(time_source,
                                kTimerTaskEstimationSampleCount,
                                kTimerTaskEstimationPercentile),
      idle_time_estimator(compositor_task_runner,
                          time_source,
                          kShortIdlePeriodDurationSampleCount,
                          kShortIdlePeriodDurationPercentile),
      current_use_case(UseCase::NONE),
      timer_queue_suspend_count(0),
      navigation_task_expected_count(0),
      expensive_task_policy(ExpensiveTaskPolicy::RUN),
      renderer_hidden(false),
      renderer_backgrounded(false),
      renderer_suspended(false),
      timer_queue_suspension_when_backgrounded_enabled(false),
      timer_queue_suspended_when_backgrounded(false),
      was_shutdown(false),
      loading_tasks_seem_expensive(false),
      timer_tasks_seem_expensive(false),
      touchstart_expected_soon(false),
      have_seen_a_begin_main_frame(false),
      have_reported_blocking_intervention_in_current_policy(false),
      have_reported_blocking_intervention_since_navigation(false),
      has_visible_render_widget_with_touch_handler(false),
      begin_frame_not_expected_soon(false),
      expensive_task_blocking_allowed(true),
      in_idle_period_for_testing(false),
      rail_mode_observer(nullptr) {}

RendererSchedulerImpl::MainThreadOnly::~MainThreadOnly() {}

RendererSchedulerImpl::AnyThread::AnyThread()
    : awaiting_touch_start_response(false),
      in_idle_period(false),
      begin_main_frame_on_critical_path(false),
      last_gesture_was_compositor_driven(false),
      default_gesture_prevented(true),
      have_seen_touchstart(false) {}

RendererSchedulerImpl::AnyThread::~AnyThread() {}

RendererSchedulerImpl::CompositorThreadOnly::CompositorThreadOnly()
    : last_input_type(blink::WebInputEvent::Undefined) {}

RendererSchedulerImpl::CompositorThreadOnly::~CompositorThreadOnly() {}

void RendererSchedulerImpl::Shutdown() {
  throttling_helper_.reset();
  helper_.Shutdown();
  MainThreadOnly().was_shutdown = true;
  MainThreadOnly().rail_mode_observer = nullptr;
}

std::unique_ptr<blink::WebThread> RendererSchedulerImpl::CreateMainThread() {
  return base::WrapUnique(new WebThreadImplForRendererScheduler(this));
}

scoped_refptr<TaskQueue> RendererSchedulerImpl::DefaultTaskRunner() {
  return helper_.DefaultTaskRunner();
}

scoped_refptr<TaskQueue> RendererSchedulerImpl::CompositorTaskRunner() {
  helper_.CheckOnValidThread();
  return compositor_task_runner_;
}

scoped_refptr<SingleThreadIdleTaskRunner>
RendererSchedulerImpl::IdleTaskRunner() {
  return idle_helper_.IdleTaskRunner();
}

scoped_refptr<TaskQueue> RendererSchedulerImpl::LoadingTaskRunner() {
  helper_.CheckOnValidThread();
  return default_loading_task_runner_;
}

scoped_refptr<TaskQueue> RendererSchedulerImpl::TimerTaskRunner() {
  helper_.CheckOnValidThread();
  return default_timer_task_runner_;
}

scoped_refptr<TaskQueue> RendererSchedulerImpl::ControlTaskRunner() {
  helper_.CheckOnValidThread();
  return helper_.ControlTaskRunner();
}

scoped_refptr<TaskQueue> RendererSchedulerImpl::NewLoadingTaskRunner(
    const char* name) {
  helper_.CheckOnValidThread();
  scoped_refptr<TaskQueue> loading_task_queue(helper_.NewTaskQueue(
      TaskQueue::Spec(name).SetShouldMonitorQuiescence(true)));
  loading_task_runners_.insert(loading_task_queue);
  loading_task_queue->SetQueueEnabled(
      MainThreadOnly().current_policy.loading_queue_policy.is_enabled);
  loading_task_queue->SetQueuePriority(
      MainThreadOnly().current_policy.loading_queue_policy.priority);
  if (MainThreadOnly().current_policy.loading_queue_policy.time_domain_type ==
      TimeDomainType::THROTTLED) {
    throttling_helper_->IncreaseThrottleRefCount(loading_task_queue.get());
  }
  loading_task_queue->AddTaskObserver(
      &MainThreadOnly().loading_task_cost_estimator);
  return loading_task_queue;
}

scoped_refptr<TaskQueue> RendererSchedulerImpl::NewTimerTaskRunner(
    const char* name) {
  helper_.CheckOnValidThread();
  scoped_refptr<TaskQueue> timer_task_queue(
      helper_.NewTaskQueue(TaskQueue::Spec(name)
                               .SetShouldMonitorQuiescence(true)
                               .SetShouldReportWhenExecutionBlocked(true)));
  timer_task_runners_.insert(timer_task_queue);
  timer_task_queue->SetQueueEnabled(
      MainThreadOnly().current_policy.timer_queue_policy.is_enabled);
  timer_task_queue->SetQueuePriority(
      MainThreadOnly().current_policy.timer_queue_policy.priority);
  if (MainThreadOnly().current_policy.timer_queue_policy.time_domain_type ==
      TimeDomainType::THROTTLED) {
    throttling_helper_->IncreaseThrottleRefCount(timer_task_queue.get());
  }
  timer_task_queue->AddTaskObserver(
      &MainThreadOnly().timer_task_cost_estimator);
  return timer_task_queue;
}

std::unique_ptr<RenderWidgetSchedulingState>
RendererSchedulerImpl::NewRenderWidgetSchedulingState() {
  return render_widget_scheduler_signals_.NewRenderWidgetSchedulingState();
}

void RendererSchedulerImpl::OnUnregisterTaskQueue(
    const scoped_refptr<TaskQueue>& task_queue) {
  if (throttling_helper_.get())
    throttling_helper_->UnregisterTaskQueue(task_queue.get());

  if (loading_task_runners_.find(task_queue) != loading_task_runners_.end()) {
    task_queue->RemoveTaskObserver(
        &MainThreadOnly().loading_task_cost_estimator);
    loading_task_runners_.erase(task_queue);
  } else if (timer_task_runners_.find(task_queue) !=
             timer_task_runners_.end()) {
    task_queue->RemoveTaskObserver(&MainThreadOnly().timer_task_cost_estimator);
    timer_task_runners_.erase(task_queue);
  }
}

bool RendererSchedulerImpl::CanExceedIdleDeadlineIfRequired() const {
  return idle_helper_.CanExceedIdleDeadlineIfRequired();
}

void RendererSchedulerImpl::AddTaskObserver(
    base::MessageLoop::TaskObserver* task_observer) {
  helper_.AddTaskObserver(task_observer);
}

void RendererSchedulerImpl::RemoveTaskObserver(
    base::MessageLoop::TaskObserver* task_observer) {
  helper_.RemoveTaskObserver(task_observer);
}

void RendererSchedulerImpl::WillBeginFrame(const cc::BeginFrameArgs& args) {
  TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
               "RendererSchedulerImpl::WillBeginFrame", "args", args.AsValue());
  helper_.CheckOnValidThread();
  if (helper_.IsShutdown())
    return;

  EndIdlePeriod();
  MainThreadOnly().estimated_next_frame_begin = args.frame_time + args.interval;
  MainThreadOnly().have_seen_a_begin_main_frame = true;
  MainThreadOnly().begin_frame_not_expected_soon = false;
  MainThreadOnly().compositor_frame_interval = args.interval;
  {
    base::AutoLock lock(any_thread_lock_);
    AnyThread().begin_main_frame_on_critical_path = args.on_critical_path;
  }
}

void RendererSchedulerImpl::DidCommitFrameToCompositor() {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
               "RendererSchedulerImpl::DidCommitFrameToCompositor");
  helper_.CheckOnValidThread();
  if (helper_.IsShutdown())
    return;

  base::TimeTicks now(helper_.scheduler_tqm_delegate()->NowTicks());
  if (now < MainThreadOnly().estimated_next_frame_begin) {
    // TODO(rmcilroy): Consider reducing the idle period based on the runtime of
    // the next pending delayed tasks (as currently done in for long idle times)
    idle_helper_.StartIdlePeriod(
        IdleHelper::IdlePeriodState::IN_SHORT_IDLE_PERIOD, now,
        MainThreadOnly().estimated_next_frame_begin);
  }

  MainThreadOnly().idle_time_estimator.DidCommitFrameToCompositor();
}

void RendererSchedulerImpl::BeginFrameNotExpectedSoon() {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
               "RendererSchedulerImpl::BeginFrameNotExpectedSoon");
  helper_.CheckOnValidThread();
  if (helper_.IsShutdown())
    return;

  MainThreadOnly().begin_frame_not_expected_soon = true;
  idle_helper_.EnableLongIdlePeriod();
  {
    base::AutoLock lock(any_thread_lock_);
    AnyThread().begin_main_frame_on_critical_path = false;
  }
}

void RendererSchedulerImpl::SetAllRenderWidgetsHidden(bool hidden) {
  TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
               "RendererSchedulerImpl::SetAllRenderWidgetsHidden", "hidden",
               hidden);

  helper_.CheckOnValidThread();

  if (helper_.IsShutdown() || MainThreadOnly().renderer_hidden == hidden)
    return;

  end_renderer_hidden_idle_period_closure_.Cancel();

  if (hidden) {
    idle_helper_.EnableLongIdlePeriod();

    // Ensure that we stop running idle tasks after a few seconds of being
    // hidden.
    base::TimeDelta end_idle_when_hidden_delay =
        base::TimeDelta::FromMilliseconds(kEndIdleWhenHiddenDelayMillis);
    control_task_runner_->PostDelayedTask(
        FROM_HERE, end_renderer_hidden_idle_period_closure_.callback(),
        end_idle_when_hidden_delay);
    MainThreadOnly().renderer_hidden = true;
  } else {
    MainThreadOnly().renderer_hidden = false;
    EndIdlePeriod();
  }

  // TODO(alexclarke): Should we update policy here?
  TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
      TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererScheduler",
      this, AsValue(helper_.scheduler_tqm_delegate()->NowTicks()));
}

void RendererSchedulerImpl::SetHasVisibleRenderWidgetWithTouchHandler(
    bool has_visible_render_widget_with_touch_handler) {
  helper_.CheckOnValidThread();
  if (has_visible_render_widget_with_touch_handler ==
      MainThreadOnly().has_visible_render_widget_with_touch_handler)
    return;

  MainThreadOnly().has_visible_render_widget_with_touch_handler =
      has_visible_render_widget_with_touch_handler;

  base::AutoLock lock(any_thread_lock_);
  UpdatePolicyLocked(UpdateType::FORCE_UPDATE);
}

void RendererSchedulerImpl::OnRendererBackgrounded() {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
               "RendererSchedulerImpl::OnRendererBackgrounded");
  helper_.CheckOnValidThread();
  if (helper_.IsShutdown() || MainThreadOnly().renderer_backgrounded)
    return;

  MainThreadOnly().renderer_backgrounded = true;
  if (!MainThreadOnly().timer_queue_suspension_when_backgrounded_enabled)
    return;

  suspend_timers_when_backgrounded_closure_.Cancel();
  base::TimeDelta suspend_timers_when_backgrounded_delay =
      base::TimeDelta::FromMilliseconds(
          kSuspendTimersWhenBackgroundedDelayMillis);
  control_task_runner_->PostDelayedTask(
      FROM_HERE, suspend_timers_when_backgrounded_closure_.callback(),
      suspend_timers_when_backgrounded_delay);
}

void RendererSchedulerImpl::OnRendererForegrounded() {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
               "RendererSchedulerImpl::OnRendererForegrounded");
  helper_.CheckOnValidThread();
  if (helper_.IsShutdown() || !MainThreadOnly().renderer_backgrounded)
    return;

  MainThreadOnly().renderer_backgrounded = false;
  MainThreadOnly().renderer_suspended = false;
  suspend_timers_when_backgrounded_closure_.Cancel();
  ResumeTimerQueueWhenForegrounded();
}

void RendererSchedulerImpl::SuspendRenderer() {
  helper_.CheckOnValidThread();
  DCHECK(MainThreadOnly().renderer_backgrounded);
  if (helper_.IsShutdown())
    return;
  suspend_timers_when_backgrounded_closure_.Cancel();
  // TODO(hajimehoshi): We might need to suspend not only timer queue but also
  // e.g. loading tasks or postMessage.
  MainThreadOnly().renderer_suspended = true;
  SuspendTimerQueueWhenBackgrounded();
}

void RendererSchedulerImpl::EndIdlePeriod() {
  if (MainThreadOnly().in_idle_period_for_testing)
    return;
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
               "RendererSchedulerImpl::EndIdlePeriod");
  helper_.CheckOnValidThread();
  idle_helper_.EndIdlePeriod();
}

void RendererSchedulerImpl::EndIdlePeriodForTesting(
    const base::Closure& callback,
    base::TimeTicks time_remaining) {
  MainThreadOnly().in_idle_period_for_testing = false;
  EndIdlePeriod();
  callback.Run();
}

bool RendererSchedulerImpl::PolicyNeedsUpdateForTesting() {
  return policy_may_need_update_.IsSet();
}

// static
bool RendererSchedulerImpl::ShouldPrioritizeInputEvent(
    const blink::WebInputEvent& web_input_event) {
  // We regard MouseMove events with the left mouse button down as a signal
  // that the user is doing something requiring a smooth frame rate.
  if (web_input_event.type == blink::WebInputEvent::MouseMove &&
      (web_input_event.modifiers & blink::WebInputEvent::LeftButtonDown)) {
    return true;
  }
  // Ignore all other mouse events because they probably don't signal user
  // interaction needing a smooth framerate. NOTE isMouseEventType returns false
  // for mouse wheel events, hence we regard them as user input.
  // Ignore keyboard events because it doesn't really make sense to enter
  // compositor priority for them.
  if (blink::WebInputEvent::isMouseEventType(web_input_event.type) ||
      blink::WebInputEvent::isKeyboardEventType(web_input_event.type)) {
    return false;
  }
  return true;
}

void RendererSchedulerImpl::DidHandleInputEventOnCompositorThread(
    const blink::WebInputEvent& web_input_event,
    InputEventState event_state) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
               "RendererSchedulerImpl::DidHandleInputEventOnCompositorThread");
  if (!ShouldPrioritizeInputEvent(web_input_event))
    return;

  UpdateForInputEventOnCompositorThread(web_input_event.type, event_state);
}

void RendererSchedulerImpl::DidAnimateForInputOnCompositorThread() {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
               "RendererSchedulerImpl::DidAnimateForInputOnCompositorThread");
  base::AutoLock lock(any_thread_lock_);
  AnyThread().fling_compositor_escalation_deadline =
      helper_.scheduler_tqm_delegate()->NowTicks() +
      base::TimeDelta::FromMilliseconds(kFlingEscalationLimitMillis);
}

void RendererSchedulerImpl::UpdateForInputEventOnCompositorThread(
    blink::WebInputEvent::Type type,
    InputEventState input_event_state) {
  base::AutoLock lock(any_thread_lock_);
  base::TimeTicks now = helper_.scheduler_tqm_delegate()->NowTicks();

  // TODO(alexclarke): Move WebInputEventTraits where we can access it from here
  // and record the name rather than the integer representation.
  TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
               "RendererSchedulerImpl::UpdateForInputEventOnCompositorThread",
               "type", static_cast<int>(type), "input_event_state",
               InputEventStateToString(input_event_state));

  base::TimeDelta unused_policy_duration;
  UseCase previous_use_case =
      ComputeCurrentUseCase(now, &unused_policy_duration);
  bool was_awaiting_touch_start_response =
      AnyThread().awaiting_touch_start_response;

  AnyThread().user_model.DidStartProcessingInputEvent(type, now);

  if (input_event_state == InputEventState::EVENT_CONSUMED_BY_COMPOSITOR)
    AnyThread().user_model.DidFinishProcessingInputEvent(now);

  if (type) {
    switch (type) {
      case blink::WebInputEvent::TouchStart:
        AnyThread().awaiting_touch_start_response = true;
        // This is just a fail-safe to reset the state of
        // |last_gesture_was_compositor_driven| to the default. We don't know
        // yet where the gesture will run.
        AnyThread().last_gesture_was_compositor_driven = false;
        AnyThread().have_seen_touchstart = true;
        // Assume the default gesture is prevented until we see evidence
        // otherwise.
        AnyThread().default_gesture_prevented = true;
        break;

      case blink::WebInputEvent::TouchMove:
        // Observation of consecutive touchmoves is a strong signal that the
        // page is consuming the touch sequence, in which case touchstart
        // response prioritization is no longer necessary. Otherwise, the
        // initial touchmove should preserve the touchstart response pending
        // state.
        if (AnyThread().awaiting_touch_start_response &&
            CompositorThreadOnly().last_input_type ==
                blink::WebInputEvent::TouchMove) {
          AnyThread().awaiting_touch_start_response = false;
        }
        break;

      case blink::WebInputEvent::GesturePinchUpdate:
      case blink::WebInputEvent::GestureScrollUpdate:
        // If we see events for an established gesture, we can lock it to the
        // appropriate thread as the gesture can no longer be cancelled.
        AnyThread().last_gesture_was_compositor_driven =
            input_event_state == InputEventState::EVENT_CONSUMED_BY_COMPOSITOR;
        AnyThread().awaiting_touch_start_response = false;
        AnyThread().default_gesture_prevented = false;
        break;

      case blink::WebInputEvent::GestureFlingCancel:
        AnyThread().fling_compositor_escalation_deadline = base::TimeTicks();
        break;

      case blink::WebInputEvent::GestureTapDown:
      case blink::WebInputEvent::GestureShowPress:
      case blink::WebInputEvent::GestureScrollEnd:
        // With no observable effect, these meta events do not indicate a
        // meaningful touchstart response and should not impact task priority.
        break;

      default:
        AnyThread().awaiting_touch_start_response = false;
        break;
    }
  }

  // Avoid unnecessary policy updates if the use case did not change.
  UseCase use_case = ComputeCurrentUseCase(now, &unused_policy_duration);

  if (use_case != previous_use_case ||
      was_awaiting_touch_start_response !=
          AnyThread().awaiting_touch_start_response) {
    EnsureUrgentPolicyUpdatePostedOnMainThread(FROM_HERE);
  }
  CompositorThreadOnly().last_input_type = type;
}

void RendererSchedulerImpl::DidHandleInputEventOnMainThread(
    const blink::WebInputEvent& web_input_event) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
               "RendererSchedulerImpl::DidHandleInputEventOnMainThread");
  helper_.CheckOnValidThread();
  if (ShouldPrioritizeInputEvent(web_input_event)) {
    base::AutoLock lock(any_thread_lock_);
    AnyThread().user_model.DidFinishProcessingInputEvent(
        helper_.scheduler_tqm_delegate()->NowTicks());
  }
}

bool RendererSchedulerImpl::IsHighPriorityWorkAnticipated() {
  helper_.CheckOnValidThread();
  if (helper_.IsShutdown())
    return false;

  MaybeUpdatePolicy();
  // The touchstart, synchronized gesture and main-thread gesture use cases
  // indicate a strong likelihood of high-priority work in the near future.
  UseCase use_case = MainThreadOnly().current_use_case;
  return MainThreadOnly().touchstart_expected_soon ||
         use_case == UseCase::TOUCHSTART ||
         use_case == UseCase::MAIN_THREAD_GESTURE ||
         use_case == UseCase::MAIN_THREAD_CUSTOM_INPUT_HANDLING ||
         use_case == UseCase::SYNCHRONIZED_GESTURE;
}

bool RendererSchedulerImpl::ShouldYieldForHighPriorityWork() {
  helper_.CheckOnValidThread();
  if (helper_.IsShutdown())
    return false;

  MaybeUpdatePolicy();
  // We only yield if there's a urgent task to be run now, or we are expecting
  // one soon (touch start).
  // Note: even though the control queue has the highest priority we don't yield
  // for it since these tasks are not user-provided work and they are only
  // intended to run before the next task, not interrupt the tasks.
  switch (MainThreadOnly().current_use_case) {
    case UseCase::COMPOSITOR_GESTURE:
    case UseCase::NONE:
      return MainThreadOnly().touchstart_expected_soon;

    case UseCase::MAIN_THREAD_GESTURE:
    case UseCase::MAIN_THREAD_CUSTOM_INPUT_HANDLING:
    case UseCase::SYNCHRONIZED_GESTURE:
      return compositor_task_runner_->HasPendingImmediateWork() ||
             MainThreadOnly().touchstart_expected_soon;

    case UseCase::TOUCHSTART:
      return true;

    case UseCase::LOADING:
      return false;

    default:
      NOTREACHED();
      return false;
  }
}

base::TimeTicks RendererSchedulerImpl::CurrentIdleTaskDeadlineForTesting()
    const {
  return idle_helper_.CurrentIdleTaskDeadline();
}

void RendererSchedulerImpl::RunIdleTasksForTesting(
    const base::Closure& callback) {
  MainThreadOnly().in_idle_period_for_testing = true;
  IdleTaskRunner()->PostIdleTask(
      FROM_HERE,
      base::Bind(&RendererSchedulerImpl::EndIdlePeriodForTesting,
                 weak_factory_.GetWeakPtr(), callback));
  idle_helper_.EnableLongIdlePeriod();
}

void RendererSchedulerImpl::MaybeUpdatePolicy() {
  helper_.CheckOnValidThread();
  if (policy_may_need_update_.IsSet()) {
    UpdatePolicy();
  }
}

void RendererSchedulerImpl::EnsureUrgentPolicyUpdatePostedOnMainThread(
    const tracked_objects::Location& from_here) {
  // TODO(scheduler-dev): Check that this method isn't called from the main
  // thread.
  any_thread_lock_.AssertAcquired();
  if (!policy_may_need_update_.IsSet()) {
    policy_may_need_update_.SetWhileLocked(true);
    control_task_runner_->PostTask(from_here, update_policy_closure_);
  }
}

void RendererSchedulerImpl::UpdatePolicy() {
  base::AutoLock lock(any_thread_lock_);
  UpdatePolicyLocked(UpdateType::MAY_EARLY_OUT_IF_POLICY_UNCHANGED);
}

void RendererSchedulerImpl::ForceUpdatePolicy() {
  base::AutoLock lock(any_thread_lock_);
  UpdatePolicyLocked(UpdateType::FORCE_UPDATE);
}

void RendererSchedulerImpl::UpdatePolicyLocked(UpdateType update_type) {
  helper_.CheckOnValidThread();
  any_thread_lock_.AssertAcquired();
  if (helper_.IsShutdown())
    return;

  base::TimeTicks now = helper_.scheduler_tqm_delegate()->NowTicks();
  policy_may_need_update_.SetWhileLocked(false);

  base::TimeDelta expected_use_case_duration;
  UseCase use_case = ComputeCurrentUseCase(now, &expected_use_case_duration);
  MainThreadOnly().current_use_case = use_case;

  base::TimeDelta touchstart_expected_flag_valid_for_duration;
  bool touchstart_expected_soon = false;
  if (MainThreadOnly().has_visible_render_widget_with_touch_handler) {
    touchstart_expected_soon = AnyThread().user_model.IsGestureExpectedSoon(
        now, &touchstart_expected_flag_valid_for_duration);
  }
  MainThreadOnly().touchstart_expected_soon = touchstart_expected_soon;

  base::TimeDelta longest_jank_free_task_duration =
      EstimateLongestJankFreeTaskDuration();
  MainThreadOnly().longest_jank_free_task_duration =
      longest_jank_free_task_duration;

  bool loading_tasks_seem_expensive = false;
  bool timer_tasks_seem_expensive = false;
  loading_tasks_seem_expensive =
      MainThreadOnly().loading_task_cost_estimator.expected_task_duration() >
      longest_jank_free_task_duration;
  timer_tasks_seem_expensive =
      MainThreadOnly().timer_task_cost_estimator.expected_task_duration() >
      longest_jank_free_task_duration;
  MainThreadOnly().timer_tasks_seem_expensive = timer_tasks_seem_expensive;
  MainThreadOnly().loading_tasks_seem_expensive = loading_tasks_seem_expensive;

  // The |new_policy_duration| is the minimum of |expected_use_case_duration|
  // and |touchstart_expected_flag_valid_for_duration| unless one is zero in
  // which case we choose the other.
  base::TimeDelta new_policy_duration = expected_use_case_duration;
  if (new_policy_duration.is_zero() ||
      (touchstart_expected_flag_valid_for_duration > base::TimeDelta() &&
       new_policy_duration > touchstart_expected_flag_valid_for_duration)) {
    new_policy_duration = touchstart_expected_flag_valid_for_duration;
  }

  if (new_policy_duration > base::TimeDelta()) {
    MainThreadOnly().current_policy_expiration_time = now + new_policy_duration;
    delayed_update_policy_runner_.SetDeadline(FROM_HERE, new_policy_duration,
                                              now);
  } else {
    MainThreadOnly().current_policy_expiration_time = base::TimeTicks();
  }

  // Avoid prioritizing main thread compositing (e.g., rAF) if it is extremely
  // slow, because that can cause starvation in other task sources.
  bool main_thread_compositing_is_fast =
      MainThreadOnly().idle_time_estimator.GetExpectedIdleDuration(
          MainThreadOnly().compositor_frame_interval) >
      MainThreadOnly().compositor_frame_interval *
          kFastCompositingIdleTimeThreshold;

  Policy new_policy;
  ExpensiveTaskPolicy expensive_task_policy = ExpensiveTaskPolicy::RUN;
  new_policy.rail_mode = v8::PERFORMANCE_ANIMATION;

  switch (use_case) {
    case UseCase::COMPOSITOR_GESTURE:
      if (touchstart_expected_soon) {
        new_policy.rail_mode = v8::PERFORMANCE_RESPONSE;
        expensive_task_policy = ExpensiveTaskPolicy::BLOCK;
        new_policy.compositor_queue_policy.priority = TaskQueue::HIGH_PRIORITY;
      } else {
        // What we really want to do is priorize loading tasks, but that doesn't
        // seem to be safe. Instead we do that by proxy by deprioritizing
        // compositor tasks. This should be safe since we've already gone to the
        // pain of fixing ordering issues with them.
        new_policy.compositor_queue_policy.priority =
            TaskQueue::BEST_EFFORT_PRIORITY;
      }
      break;

    case UseCase::SYNCHRONIZED_GESTURE:
      new_policy.compositor_queue_policy.priority =
          main_thread_compositing_is_fast ? TaskQueue::HIGH_PRIORITY
                                          : TaskQueue::NORMAL_PRIORITY;
      if (touchstart_expected_soon) {
        new_policy.rail_mode = v8::PERFORMANCE_RESPONSE;
        expensive_task_policy = ExpensiveTaskPolicy::BLOCK;
      } else {
        expensive_task_policy = ExpensiveTaskPolicy::THROTTLE;
      }
      break;

    case UseCase::MAIN_THREAD_CUSTOM_INPUT_HANDLING:
      // In main thread input handling scenarios we don't have perfect knowledge
      // about which things we should be prioritizing, so we don't attempt to
      // block expensive tasks because we don't know whether they were integral
      // to the page's functionality or not.
      new_policy.compositor_queue_policy.priority =
          main_thread_compositing_is_fast ? TaskQueue::HIGH_PRIORITY
                                          : TaskQueue::NORMAL_PRIORITY;
      break;

    case UseCase::MAIN_THREAD_GESTURE:
      // A main thread gesture is for example a scroll gesture which is handled
      // by the main thread. Since we know the established gesture type, we can
      // be a little more aggressive about prioritizing compositing and input
      // handling over other tasks.
      new_policy.compositor_queue_policy.priority = TaskQueue::HIGH_PRIORITY;
      if (touchstart_expected_soon) {
        new_policy.rail_mode = v8::PERFORMANCE_RESPONSE;
        expensive_task_policy = ExpensiveTaskPolicy::BLOCK;
      } else {
        expensive_task_policy = ExpensiveTaskPolicy::THROTTLE;
      }
      break;

    case UseCase::TOUCHSTART:
      new_policy.rail_mode = v8::PERFORMANCE_RESPONSE;
      new_policy.compositor_queue_policy.priority = TaskQueue::HIGH_PRIORITY;
      new_policy.loading_queue_policy.is_enabled = false;
      new_policy.timer_queue_policy.is_enabled = false;
      // NOTE this is a nop due to the above.
      expensive_task_policy = ExpensiveTaskPolicy::BLOCK;
      break;

    case UseCase::NONE:
      // It's only safe to block tasks that if we are expecting a compositor
      // driven gesture.
      if (touchstart_expected_soon &&
          AnyThread().last_gesture_was_compositor_driven) {
        new_policy.rail_mode = v8::PERFORMANCE_RESPONSE;
        expensive_task_policy = ExpensiveTaskPolicy::BLOCK;
      }
      break;

    case UseCase::LOADING:
      new_policy.rail_mode = v8::PERFORMANCE_LOAD;
      new_policy.loading_queue_policy.priority = TaskQueue::HIGH_PRIORITY;
      new_policy.default_queue_policy.priority = TaskQueue::HIGH_PRIORITY;
      break;

    default:
      NOTREACHED();
  }

  // TODO(skyostil): Add an idle state for foreground tabs too.
  if (MainThreadOnly().renderer_hidden)
    new_policy.rail_mode = v8::PERFORMANCE_IDLE;

  if (expensive_task_policy == ExpensiveTaskPolicy::BLOCK &&
      (!MainThreadOnly().expensive_task_blocking_allowed ||
       !MainThreadOnly().have_seen_a_begin_main_frame ||
       MainThreadOnly().navigation_task_expected_count > 0)) {
    expensive_task_policy = ExpensiveTaskPolicy::RUN;
  }

  switch (expensive_task_policy) {
    case ExpensiveTaskPolicy::RUN:
      break;

    case ExpensiveTaskPolicy::BLOCK:
      if (loading_tasks_seem_expensive)
        new_policy.loading_queue_policy.is_enabled = false;
      if (timer_tasks_seem_expensive)
        new_policy.timer_queue_policy.is_enabled = false;
      break;

    case ExpensiveTaskPolicy::THROTTLE:
      if (loading_tasks_seem_expensive) {
        new_policy.loading_queue_policy.time_domain_type =
            TimeDomainType::THROTTLED;
      }
      if (timer_tasks_seem_expensive) {
        new_policy.timer_queue_policy.time_domain_type =
            TimeDomainType::THROTTLED;
      }
      break;
  }

  MainThreadOnly().expensive_task_policy = expensive_task_policy;

  if (MainThreadOnly().timer_queue_suspend_count != 0 ||
      MainThreadOnly().timer_queue_suspended_when_backgrounded) {
    new_policy.timer_queue_policy.is_enabled = false;
    new_policy.timer_queue_policy.time_domain_type = TimeDomainType::REAL;
  }

  if (MainThreadOnly().renderer_suspended) {
    new_policy.loading_queue_policy.is_enabled = false;
    DCHECK(!new_policy.timer_queue_policy.is_enabled);
  }

  // Tracing is done before the early out check, because it's quite possible we
  // will otherwise miss this information in traces.
  TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
      TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererScheduler",
      this, AsValueLocked(now));
  TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "use_case",
                 use_case);
  TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "rail_mode",
                 new_policy.rail_mode);
  TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
                 "touchstart_expected_soon",
                 MainThreadOnly().touchstart_expected_soon);
  TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
                 "expensive_task_policy", expensive_task_policy);
  TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
                 "RendererScheduler.loading_tasks_seem_expensive",
                 MainThreadOnly().loading_tasks_seem_expensive);
  TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
                 "RendererScheduler.timer_tasks_seem_expensive",
                 MainThreadOnly().timer_tasks_seem_expensive);

  // TODO(alexclarke): Can we get rid of force update now?
  if (update_type == UpdateType::MAY_EARLY_OUT_IF_POLICY_UNCHANGED &&
      new_policy == MainThreadOnly().current_policy) {
    return;
  }

  ApplyTaskQueuePolicy(compositor_task_runner_.get(),
                       MainThreadOnly().current_policy.compositor_queue_policy,
                       new_policy.compositor_queue_policy);

  for (const scoped_refptr<TaskQueue>& loading_queue : loading_task_runners_) {
    ApplyTaskQueuePolicy(loading_queue.get(),
                         MainThreadOnly().current_policy.loading_queue_policy,
                         new_policy.loading_queue_policy);
  }

  for (const scoped_refptr<TaskQueue>& timer_queue : timer_task_runners_) {
    ApplyTaskQueuePolicy(timer_queue.get(),
                         MainThreadOnly().current_policy.timer_queue_policy,
                         new_policy.timer_queue_policy);
  }
  MainThreadOnly().have_reported_blocking_intervention_in_current_policy =
      false;

  // TODO(alexclarke): We shouldn't have to prioritize the default queue, but it
  // appears to be necessary since the order of loading tasks and IPCs (which
  // are mostly dispatched on the default queue) need to be preserved.
  ApplyTaskQueuePolicy(helper_.DefaultTaskRunner().get(),
                       MainThreadOnly().current_policy.default_queue_policy,
                       new_policy.default_queue_policy);
  if (MainThreadOnly().rail_mode_observer &&
      new_policy.rail_mode != MainThreadOnly().current_policy.rail_mode) {
    MainThreadOnly().rail_mode_observer->OnRAILModeChanged(
        new_policy.rail_mode);
  }

  DCHECK(compositor_task_runner_->IsQueueEnabled());
  MainThreadOnly().current_policy = new_policy;
}

void RendererSchedulerImpl::ApplyTaskQueuePolicy(
    TaskQueue* task_queue,
    const TaskQueuePolicy& old_task_queue_policy,
    const TaskQueuePolicy& new_task_queue_policy) const {
  if (old_task_queue_policy.is_enabled != new_task_queue_policy.is_enabled) {
    throttling_helper_->SetQueueEnabled(task_queue,
                                        new_task_queue_policy.is_enabled);
  }

  if (old_task_queue_policy.priority != new_task_queue_policy.priority)
    task_queue->SetQueuePriority(new_task_queue_policy.priority);

  if (old_task_queue_policy.time_domain_type !=
      new_task_queue_policy.time_domain_type) {
    if (new_task_queue_policy.time_domain_type == TimeDomainType::THROTTLED) {
      throttling_helper_->IncreaseThrottleRefCount(task_queue);
    } else if (old_task_queue_policy.time_domain_type ==
               TimeDomainType::THROTTLED) {
      throttling_helper_->DecreaseThrottleRefCount(task_queue);
    }
  }
}

RendererSchedulerImpl::UseCase RendererSchedulerImpl::ComputeCurrentUseCase(
    base::TimeTicks now,
    base::TimeDelta* expected_use_case_duration) const {
  any_thread_lock_.AssertAcquired();
  // Special case for flings. This is needed because we don't get notification
  // of a fling ending (although we do for cancellation).
  if (AnyThread().fling_compositor_escalation_deadline > now &&
      !AnyThread().awaiting_touch_start_response) {
    *expected_use_case_duration =
        AnyThread().fling_compositor_escalation_deadline - now;
    return UseCase::COMPOSITOR_GESTURE;
  }
  // Above all else we want to be responsive to user input.
  *expected_use_case_duration =
      AnyThread().user_model.TimeLeftInUserGesture(now);
  if (*expected_use_case_duration > base::TimeDelta()) {
    // Has a gesture been fully established?
    if (AnyThread().awaiting_touch_start_response) {
      // No, so arrange for compositor tasks to be run at the highest priority.
      return UseCase::TOUCHSTART;
    }

    // Yes a gesture has been established.  Based on how the gesture is handled
    // we need to choose between one of four use cases:
    // 1. COMPOSITOR_GESTURE where the gesture is processed only on the
    //    compositor thread.
    // 2. MAIN_THREAD_GESTURE where the gesture is processed only on the main
    //    thread.
    // 3. MAIN_THREAD_CUSTOM_INPUT_HANDLING where the main thread processes a
    //    stream of input events and has prevented a default gesture from being
    //    started.
    // 4. SYNCHRONIZED_GESTURE where the gesture is processed on both threads.
    // TODO(skyostil): Consider removing in_idle_period_ and
    // HadAnIdlePeriodRecently() unless we need them here.
    if (AnyThread().last_gesture_was_compositor_driven) {
      if (AnyThread().begin_main_frame_on_critical_path) {
        return UseCase::SYNCHRONIZED_GESTURE;
      } else {
        return UseCase::COMPOSITOR_GESTURE;
      }
    }
    if (AnyThread().default_gesture_prevented) {
      return UseCase::MAIN_THREAD_CUSTOM_INPUT_HANDLING;
    } else {
      return UseCase::MAIN_THREAD_GESTURE;
    }
  }

  // TODO(alexclarke): return UseCase::LOADING if signals suggest the system is
  // in the initial 1s of RAIL loading.
  return UseCase::NONE;
}

base::TimeDelta RendererSchedulerImpl::EstimateLongestJankFreeTaskDuration()
    const {
  switch (MainThreadOnly().current_use_case) {
    case UseCase::TOUCHSTART:
    case UseCase::COMPOSITOR_GESTURE:
    case UseCase::LOADING:
    case UseCase::NONE:
      return base::TimeDelta::FromMilliseconds(kRailsResponseTimeMillis);

    case UseCase::MAIN_THREAD_CUSTOM_INPUT_HANDLING:
    case UseCase::MAIN_THREAD_GESTURE:
    case UseCase::SYNCHRONIZED_GESTURE:
      return MainThreadOnly().idle_time_estimator.GetExpectedIdleDuration(
          MainThreadOnly().compositor_frame_interval);

    default:
      NOTREACHED();
      return base::TimeDelta::FromMilliseconds(kRailsResponseTimeMillis);
  }
}

bool RendererSchedulerImpl::CanEnterLongIdlePeriod(
    base::TimeTicks now,
    base::TimeDelta* next_long_idle_period_delay_out) {
  helper_.CheckOnValidThread();

  MaybeUpdatePolicy();
  if (MainThreadOnly().current_use_case == UseCase::TOUCHSTART) {
    // Don't start a long idle task in touch start priority, try again when
    // the policy is scheduled to end.
    *next_long_idle_period_delay_out =
        std::max(base::TimeDelta(),
                 MainThreadOnly().current_policy_expiration_time - now);
    return false;
  }
  return true;
}

SchedulerHelper* RendererSchedulerImpl::GetSchedulerHelperForTesting() {
  return &helper_;
}

TaskCostEstimator*
RendererSchedulerImpl::GetLoadingTaskCostEstimatorForTesting() {
  return &MainThreadOnly().loading_task_cost_estimator;
}

TaskCostEstimator*
RendererSchedulerImpl::GetTimerTaskCostEstimatorForTesting() {
  return &MainThreadOnly().timer_task_cost_estimator;
}

IdleTimeEstimator* RendererSchedulerImpl::GetIdleTimeEstimatorForTesting() {
  return &MainThreadOnly().idle_time_estimator;
}

void RendererSchedulerImpl::SuspendTimerQueue() {
  MainThreadOnly().timer_queue_suspend_count++;
  ForceUpdatePolicy();
#ifndef NDEBUG
  DCHECK(!default_timer_task_runner_->IsQueueEnabled());
  for (const auto& runner : timer_task_runners_) {
     DCHECK(!runner->IsQueueEnabled());
  }
#endif
}

void RendererSchedulerImpl::ResumeTimerQueue() {
  MainThreadOnly().timer_queue_suspend_count--;
  DCHECK_GE(MainThreadOnly().timer_queue_suspend_count, 0);
  ForceUpdatePolicy();
}

void RendererSchedulerImpl::SetTimerQueueSuspensionWhenBackgroundedEnabled(
    bool enabled) {
  // Note that this will only take effect for the next backgrounded signal.
  MainThreadOnly().timer_queue_suspension_when_backgrounded_enabled = enabled;
}

std::unique_ptr<base::trace_event::ConvertableToTraceFormat>
RendererSchedulerImpl::AsValue(base::TimeTicks optional_now) const {
  base::AutoLock lock(any_thread_lock_);
  return AsValueLocked(optional_now);
}

// static
const char* RendererSchedulerImpl::ExpensiveTaskPolicyToString(
    ExpensiveTaskPolicy expensive_task_policy) {
  switch (expensive_task_policy) {
    case ExpensiveTaskPolicy::RUN:
      return "RUN";
    case ExpensiveTaskPolicy::BLOCK:
      return "BLOCK";
    case ExpensiveTaskPolicy::THROTTLE:
      return "THROTTLE";
    default:
      NOTREACHED();
      return nullptr;
  }
}

std::unique_ptr<base::trace_event::ConvertableToTraceFormat>
RendererSchedulerImpl::AsValueLocked(base::TimeTicks optional_now) const {
  helper_.CheckOnValidThread();
  any_thread_lock_.AssertAcquired();

  if (optional_now.is_null())
    optional_now = helper_.scheduler_tqm_delegate()->NowTicks();
  std::unique_ptr<base::trace_event::TracedValue> state(
      new base::trace_event::TracedValue());
  state->SetBoolean(
      "has_visible_render_widget_with_touch_handler",
      MainThreadOnly().has_visible_render_widget_with_touch_handler);
  state->SetString("current_use_case",
                   UseCaseToString(MainThreadOnly().current_use_case));
  state->SetString("rail_mode",
                   RAILModeToString(MainThreadOnly().current_policy.rail_mode));
  state->SetBoolean("expensive_task_blocking_allowed",
                    MainThreadOnly().expensive_task_blocking_allowed);
  state->SetBoolean("loading_tasks_seem_expensive",
                    MainThreadOnly().loading_tasks_seem_expensive);
  state->SetBoolean("timer_tasks_seem_expensive",
                    MainThreadOnly().timer_tasks_seem_expensive);
  state->SetBoolean("begin_frame_not_expected_soon",
                    MainThreadOnly().begin_frame_not_expected_soon);
  state->SetBoolean("touchstart_expected_soon",
                    MainThreadOnly().touchstart_expected_soon);
  state->SetString("idle_period_state",
                   IdleHelper::IdlePeriodStateToString(
                       idle_helper_.SchedulerIdlePeriodState()));
  state->SetBoolean("renderer_hidden", MainThreadOnly().renderer_hidden);
  state->SetBoolean("have_seen_a_begin_main_frame",
                    MainThreadOnly().have_seen_a_begin_main_frame);
  state->SetBoolean(
      "have_reported_blocking_intervention_in_current_policy",
      MainThreadOnly().have_reported_blocking_intervention_in_current_policy);
  state->SetBoolean(
      "have_reported_blocking_intervention_since_navigation",
      MainThreadOnly().have_reported_blocking_intervention_since_navigation);
  state->SetBoolean("renderer_backgrounded",
                    MainThreadOnly().renderer_backgrounded);
  state->SetBoolean("timer_queue_suspended_when_backgrounded",
                    MainThreadOnly().timer_queue_suspended_when_backgrounded);
  state->SetInteger("timer_queue_suspend_count",
                    MainThreadOnly().timer_queue_suspend_count);
  state->SetDouble("now", (optional_now - base::TimeTicks()).InMillisecondsF());
  state->SetDouble(
      "rails_loading_priority_deadline",
      (AnyThread().rails_loading_priority_deadline - base::TimeTicks())
          .InMillisecondsF());
  state->SetDouble(
      "fling_compositor_escalation_deadline",
      (AnyThread().fling_compositor_escalation_deadline - base::TimeTicks())
          .InMillisecondsF());
  state->SetInteger("navigation_task_expected_count",
                    MainThreadOnly().navigation_task_expected_count);
  state->SetDouble("last_idle_period_end_time",
                   (AnyThread().last_idle_period_end_time - base::TimeTicks())
                       .InMillisecondsF());
  state->SetBoolean("awaiting_touch_start_response",
                    AnyThread().awaiting_touch_start_response);
  state->SetBoolean("begin_main_frame_on_critical_path",
                    AnyThread().begin_main_frame_on_critical_path);
  state->SetBoolean("last_gesture_was_compositor_driven",
                    AnyThread().last_gesture_was_compositor_driven);
  state->SetBoolean("default_gesture_prevented",
                    AnyThread().default_gesture_prevented);
  state->SetDouble("expected_loading_task_duration",
                   MainThreadOnly()
                       .loading_task_cost_estimator.expected_task_duration()
                       .InMillisecondsF());
  state->SetDouble("expected_timer_task_duration",
                   MainThreadOnly()
                       .timer_task_cost_estimator.expected_task_duration()
                       .InMillisecondsF());
  // TODO(skyostil): Can we somehow trace how accurate these estimates were?
  state->SetDouble(
      "longest_jank_free_task_duration",
      MainThreadOnly().longest_jank_free_task_duration.InMillisecondsF());
  state->SetDouble(
      "compositor_frame_interval",
      MainThreadOnly().compositor_frame_interval.InMillisecondsF());
  state->SetDouble(
      "estimated_next_frame_begin",
      (MainThreadOnly().estimated_next_frame_begin - base::TimeTicks())
          .InMillisecondsF());
  state->SetBoolean("in_idle_period", AnyThread().in_idle_period);

  state->SetString(
      "expensive_task_policy",
      ExpensiveTaskPolicyToString(MainThreadOnly().expensive_task_policy));

  AnyThread().user_model.AsValueInto(state.get());
  render_widget_scheduler_signals_.AsValueInto(state.get());

  return std::move(state);
}

void RendererSchedulerImpl::OnIdlePeriodStarted() {
  base::AutoLock lock(any_thread_lock_);
  AnyThread().in_idle_period = true;
  UpdatePolicyLocked(UpdateType::MAY_EARLY_OUT_IF_POLICY_UNCHANGED);
}

void RendererSchedulerImpl::OnIdlePeriodEnded() {
  base::AutoLock lock(any_thread_lock_);
  AnyThread().last_idle_period_end_time =
      helper_.scheduler_tqm_delegate()->NowTicks();
  AnyThread().in_idle_period = false;
  UpdatePolicyLocked(UpdateType::MAY_EARLY_OUT_IF_POLICY_UNCHANGED);
}

void RendererSchedulerImpl::AddPendingNavigation(
      blink::WebScheduler::NavigatingFrameType type) {
  helper_.CheckOnValidThread();
  if (type == blink::WebScheduler::NavigatingFrameType::kMainFrame) {
    MainThreadOnly().navigation_task_expected_count++;
    UpdatePolicy();
  }
}

void RendererSchedulerImpl::RemovePendingNavigation(
      blink::WebScheduler::NavigatingFrameType type) {
  helper_.CheckOnValidThread();
  DCHECK_GT(MainThreadOnly().navigation_task_expected_count, 0);
  if (type == blink::WebScheduler::NavigatingFrameType::kMainFrame &&
      MainThreadOnly().navigation_task_expected_count > 0) {
    MainThreadOnly().navigation_task_expected_count--;
    UpdatePolicy();
  }
}

void RendererSchedulerImpl::OnNavigationStarted() {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
               "RendererSchedulerImpl::OnNavigationStarted");
  base::AutoLock lock(any_thread_lock_);
  AnyThread().rails_loading_priority_deadline =
      helper_.scheduler_tqm_delegate()->NowTicks() +
      base::TimeDelta::FromMilliseconds(
          kRailsInitialLoadingPrioritizationMillis);
  ResetForNavigationLocked();
}

bool RendererSchedulerImpl::HadAnIdlePeriodRecently(base::TimeTicks now) const {
  return (now - AnyThread().last_idle_period_end_time) <=
         base::TimeDelta::FromMilliseconds(
             kIdlePeriodStarvationThresholdMillis);
}

void RendererSchedulerImpl::SuspendTimerQueueWhenBackgrounded() {
  DCHECK(MainThreadOnly().renderer_backgrounded);
  if (MainThreadOnly().timer_queue_suspended_when_backgrounded)
    return;

  MainThreadOnly().timer_queue_suspended_when_backgrounded = true;
  ForceUpdatePolicy();
}

void RendererSchedulerImpl::ResumeTimerQueueWhenForegrounded() {
  DCHECK(!MainThreadOnly().renderer_backgrounded);
  if (!MainThreadOnly().timer_queue_suspended_when_backgrounded)
    return;

  MainThreadOnly().timer_queue_suspended_when_backgrounded = false;
  ForceUpdatePolicy();
}

void RendererSchedulerImpl::ResetForNavigationLocked() {
  helper_.CheckOnValidThread();
  any_thread_lock_.AssertAcquired();
  AnyThread().user_model.Reset(helper_.scheduler_tqm_delegate()->NowTicks());
  AnyThread().have_seen_touchstart = false;
  MainThreadOnly().loading_task_cost_estimator.Clear();
  MainThreadOnly().timer_task_cost_estimator.Clear();
  MainThreadOnly().idle_time_estimator.Clear();
  MainThreadOnly().have_seen_a_begin_main_frame = false;
  MainThreadOnly().have_reported_blocking_intervention_since_navigation = false;
  UpdatePolicyLocked(UpdateType::MAY_EARLY_OUT_IF_POLICY_UNCHANGED);
}

void RendererSchedulerImpl::SetTopLevelBlameContext(
    base::trace_event::BlameContext* blame_context) {
  // Any task that runs in the default task runners belongs to the context of
  // all frames (as opposed to a particular frame). Note that the task itself
  // may still enter a more specific blame context if necessary.
  //
  // Per-frame task runners (loading, timers, etc.) are configured with a more
  // specific blame context by WebFrameSchedulerImpl.
  control_task_runner_->SetBlameContext(blame_context);
  DefaultTaskRunner()->SetBlameContext(blame_context);
  default_loading_task_runner_->SetBlameContext(blame_context);
  default_timer_task_runner_->SetBlameContext(blame_context);
  compositor_task_runner_->SetBlameContext(blame_context);
  idle_helper_.IdleTaskRunner()->SetBlameContext(blame_context);
}

void RendererSchedulerImpl::SetRAILModeObserver(RAILModeObserver* observer) {
  MainThreadOnly().rail_mode_observer = observer;
}

void RendererSchedulerImpl::RegisterTimeDomain(TimeDomain* time_domain) {
  helper_.RegisterTimeDomain(time_domain);
}

void RendererSchedulerImpl::UnregisterTimeDomain(TimeDomain* time_domain) {
  helper_.UnregisterTimeDomain(time_domain);
}

void RendererSchedulerImpl::SetExpensiveTaskBlockingAllowed(bool allowed) {
  MainThreadOnly().expensive_task_blocking_allowed = allowed;
}

base::TickClock* RendererSchedulerImpl::tick_clock() const {
  return helper_.scheduler_tqm_delegate().get();
}

void RendererSchedulerImpl::AddWebViewScheduler(
    WebViewSchedulerImpl* web_view_scheduler) {
  MainThreadOnly().web_view_schedulers.insert(web_view_scheduler);
}

void RendererSchedulerImpl::RemoveWebViewScheduler(
    WebViewSchedulerImpl* web_view_scheduler) {
  DCHECK(MainThreadOnly().web_view_schedulers.find(web_view_scheduler) !=
         MainThreadOnly().web_view_schedulers.end());
  MainThreadOnly().web_view_schedulers.erase(web_view_scheduler);
}

void RendererSchedulerImpl::BroadcastConsoleWarning(
    const std::string& message) {
  helper_.CheckOnValidThread();
  for (auto& web_view_scheduler : MainThreadOnly().web_view_schedulers)
    web_view_scheduler->AddConsoleWarning(message);
}

void RendererSchedulerImpl::OnTriedToExecuteBlockedTask(
    const TaskQueue& queue,
    const base::PendingTask& task) {
  if (!MainThreadOnly().expensive_task_blocking_allowed ||
      MainThreadOnly().current_use_case == UseCase::TOUCHSTART ||
      MainThreadOnly().longest_jank_free_task_duration <
          base::TimeDelta::FromMilliseconds(kRailsResponseTimeMillis) ||
      MainThreadOnly().timer_queue_suspend_count ||
      MainThreadOnly().timer_queue_suspended_when_backgrounded) {
    return;
  }
  if (!MainThreadOnly().timer_tasks_seem_expensive &&
      !MainThreadOnly().loading_tasks_seem_expensive) {
    return;
  }
  if (!MainThreadOnly().have_reported_blocking_intervention_in_current_policy) {
    MainThreadOnly().have_reported_blocking_intervention_in_current_policy =
        true;
    TRACE_EVENT_INSTANT0("renderer.scheduler",
                         "RendererSchedulerImpl::TaskBlocked",
                         TRACE_EVENT_SCOPE_THREAD);
  }

  if (!MainThreadOnly().have_reported_blocking_intervention_since_navigation) {
    {
      base::AutoLock lock(any_thread_lock_);
      if (!AnyThread().have_seen_touchstart)
        return;
    }
    MainThreadOnly().have_reported_blocking_intervention_since_navigation =
        true;
    BroadcastConsoleWarning(
        "Blink deferred a task in order to make scrolling smoother. "
        "Your timer and network tasks should take less than 50ms to run "
        "to avoid this. Please see "
        "https://developers.google.com/web/tools/chrome-devtools/profile/evaluate-performance/rail"
        " and https://crbug.com/574343#c40 for more information.");
  }
}

// static
const char* RendererSchedulerImpl::UseCaseToString(UseCase use_case) {
  switch (use_case) {
    case UseCase::NONE:
      return "none";
    case UseCase::COMPOSITOR_GESTURE:
      return "compositor_gesture";
    case UseCase::MAIN_THREAD_CUSTOM_INPUT_HANDLING:
      return "main_thread_custom_input_handling";
    case UseCase::SYNCHRONIZED_GESTURE:
      return "synchronized_gesture";
    case UseCase::TOUCHSTART:
      return "touchstart";
    case UseCase::LOADING:
      return "loading";
    case UseCase::MAIN_THREAD_GESTURE:
      return "main_thread_gesture";
    default:
      NOTREACHED();
      return nullptr;
  }
}

// static
const char* RendererSchedulerImpl::RAILModeToString(v8::RAILMode rail_mode) {
  switch (rail_mode) {
    case v8::PERFORMANCE_RESPONSE:
      return "response";
    case v8::PERFORMANCE_ANIMATION:
      return "animation";
    case v8::PERFORMANCE_IDLE:
      return "idle";
    case v8::PERFORMANCE_LOAD:
      return "load";
    default:
      NOTREACHED();
      return nullptr;
  }
}

}  // namespace scheduler
