blob: 461b6bf17a138304c2fa545acc6d4955fe5f83f1 [file] [log] [blame]
// Copyright 2017 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 "third_party/blink/renderer/core/dom/scripted_idle_task_controller.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/frame/lifecycle.mojom-blink.h"
#include "third_party/blink/public/platform/scheduler/web_agent_group_scheduler.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_idle_request_callback.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_idle_request_options.h"
#include "third_party/blink/renderer/core/testing/null_execution_context.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
#include "third_party/blink/renderer/platform/scheduler/test/fake_task_runner.h"
#include "third_party/blink/renderer/platform/testing/scoped_scheduler_overrider.h"
namespace blink {
namespace {
enum class ShouldYield { YIELD, DONT_YIELD };
class MockScriptedIdleTaskControllerScheduler final : public ThreadScheduler {
public:
explicit MockScriptedIdleTaskControllerScheduler(ShouldYield should_yield)
: should_yield_(should_yield == ShouldYield::YIELD) {}
MockScriptedIdleTaskControllerScheduler(
const MockScriptedIdleTaskControllerScheduler&) = delete;
MockScriptedIdleTaskControllerScheduler& operator=(
const MockScriptedIdleTaskControllerScheduler&) = delete;
~MockScriptedIdleTaskControllerScheduler() override = default;
// ThreadScheduler implementation:
scoped_refptr<base::SingleThreadTaskRunner> CompositorTaskRunner() override {
return nullptr;
}
scoped_refptr<base::SingleThreadTaskRunner> V8TaskRunner() override {
return nullptr;
}
scoped_refptr<base::SingleThreadTaskRunner> NonWakingTaskRunner() override {
return nullptr;
}
scoped_refptr<base::SingleThreadTaskRunner> DeprecatedDefaultTaskRunner()
override {
return task_runner_;
}
void Shutdown() override {}
bool ShouldYieldForHighPriorityWork() override { return should_yield_; }
bool CanExceedIdleDeadlineIfRequired() const override { return false; }
void PostIdleTask(const base::Location&,
Thread::IdleTask idle_task) override {
idle_task_ = std::move(idle_task);
}
void PostDelayedIdleTask(const base::Location&,
base::TimeDelta,
Thread::IdleTask) override {
NOTIMPLEMENTED();
}
void PostNonNestableIdleTask(const base::Location&,
Thread::IdleTask) override {}
std::unique_ptr<scheduler::WebAgentGroupScheduler> CreateAgentGroupScheduler()
override {
NOTREACHED();
return nullptr;
}
std::unique_ptr<RendererPauseHandle> PauseScheduler() override {
return nullptr;
}
scheduler::WebAgentGroupScheduler* GetCurrentAgentGroupScheduler() override {
NOTREACHED();
return nullptr;
}
base::TimeTicks MonotonicallyIncreasingVirtualTime() override {
return base::TimeTicks();
}
void AddTaskObserver(Thread::TaskObserver* task_observer) override {}
void RemoveTaskObserver(Thread::TaskObserver* task_observer) override {}
void AddRAILModeObserver(RAILModeObserver*) override {}
void RemoveRAILModeObserver(RAILModeObserver const*) override {}
scheduler::NonMainThreadSchedulerImpl* AsNonMainThreadScheduler() override {
return nullptr;
}
void SetV8Isolate(v8::Isolate* isolate) override {}
void RunIdleTask() { std::move(idle_task_).Run(base::TimeTicks()); }
bool HasIdleTask() const { return !!idle_task_; }
void AdvanceTimeAndRun(base::TimeDelta delta) {
task_runner_->AdvanceTimeAndRun(delta);
}
private:
bool should_yield_;
Thread::IdleTask idle_task_;
scoped_refptr<scheduler::FakeTaskRunner> task_runner_ =
base::MakeRefCounted<scheduler::FakeTaskRunner>();
};
class MockIdleTask : public IdleTask {
public:
MOCK_METHOD1(invoke, void(IdleDeadline*));
};
} // namespace
class ScriptedIdleTaskControllerTest : public testing::Test {
public:
~ScriptedIdleTaskControllerTest() override {
execution_context_->NotifyContextDestroyed();
}
void SetUp() override {
execution_context_ = MakeGarbageCollected<NullExecutionContext>();
}
protected:
Persistent<ExecutionContext> execution_context_;
};
TEST_F(ScriptedIdleTaskControllerTest, RunCallback) {
MockScriptedIdleTaskControllerScheduler scheduler(ShouldYield::DONT_YIELD);
ScopedSchedulerOverrider scheduler_overrider(&scheduler);
ScriptedIdleTaskController* controller =
ScriptedIdleTaskController::Create(execution_context_);
Persistent<MockIdleTask> idle_task(MakeGarbageCollected<MockIdleTask>());
IdleRequestOptions* options = IdleRequestOptions::Create();
EXPECT_FALSE(scheduler.HasIdleTask());
int id = controller->RegisterCallback(idle_task, options);
EXPECT_TRUE(scheduler.HasIdleTask());
EXPECT_NE(0, id);
EXPECT_CALL(*idle_task, invoke(testing::_));
scheduler.RunIdleTask();
testing::Mock::VerifyAndClearExpectations(idle_task);
EXPECT_FALSE(scheduler.HasIdleTask());
}
TEST_F(ScriptedIdleTaskControllerTest, DontRunCallbackWhenAskedToYield) {
MockScriptedIdleTaskControllerScheduler scheduler(ShouldYield::YIELD);
ScopedSchedulerOverrider scheduler_overrider(&scheduler);
ScriptedIdleTaskController* controller =
ScriptedIdleTaskController::Create(execution_context_);
Persistent<MockIdleTask> idle_task(MakeGarbageCollected<MockIdleTask>());
IdleRequestOptions* options = IdleRequestOptions::Create();
int id = controller->RegisterCallback(idle_task, options);
EXPECT_NE(0, id);
EXPECT_CALL(*idle_task, invoke(testing::_)).Times(0);
scheduler.RunIdleTask();
testing::Mock::VerifyAndClearExpectations(idle_task);
// The idle task should have been reposted.
EXPECT_TRUE(scheduler.HasIdleTask());
}
TEST_F(ScriptedIdleTaskControllerTest, RunCallbacksAsyncWhenUnpaused) {
MockScriptedIdleTaskControllerScheduler scheduler(ShouldYield::YIELD);
ScopedSchedulerOverrider scheduler_overrider(&scheduler);
ScriptedIdleTaskController* controller =
ScriptedIdleTaskController::Create(execution_context_);
// Register an idle task with a deadline.
Persistent<MockIdleTask> idle_task(MakeGarbageCollected<MockIdleTask>());
IdleRequestOptions* options = IdleRequestOptions::Create();
options->setTimeout(1);
int id = controller->RegisterCallback(idle_task, options);
EXPECT_NE(0, id);
// Hitting the deadline while the frame is paused shouldn't cause any tasks to
// run.
controller->ContextLifecycleStateChanged(mojom::FrameLifecycleState::kPaused);
EXPECT_CALL(*idle_task, invoke(testing::_)).Times(0);
scheduler.AdvanceTimeAndRun(base::TimeDelta::FromMilliseconds(1));
testing::Mock::VerifyAndClearExpectations(idle_task);
// Even if we unpause, no tasks should run immediately.
EXPECT_CALL(*idle_task, invoke(testing::_)).Times(0);
controller->ContextLifecycleStateChanged(
mojom::FrameLifecycleState::kRunning);
testing::Mock::VerifyAndClearExpectations(idle_task);
// Idle callback should have been scheduled as an asynchronous task.
EXPECT_CALL(*idle_task, invoke(testing::_)).Times(1);
scheduler.AdvanceTimeAndRun(base::TimeDelta::FromMilliseconds(0));
testing::Mock::VerifyAndClearExpectations(idle_task);
}
} // namespace blink