blob: f93740fe47794215a5c3097da146cbb736e093c2 [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 "headless/public/util/compositor_controller.h"
#include <memory>
#include "base/base64.h"
#include "base/bind.h"
#include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/test_simple_task_runner.h"
#include "headless/public/internal/headless_devtools_client_impl.h"
#include "headless/public/util/testing/mock_devtools_agent_host.h"
#include "headless/public/util/virtual_time_controller.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace headless {
namespace {
static constexpr base::TimeDelta kAnimationFrameInterval =
base::TimeDelta::FromMilliseconds(16);
static constexpr base::TimeDelta kWaitForCompositorReadyFrameDelay =
base::TimeDelta::FromMilliseconds(20);
} // namespace
using testing::Return;
using testing::_;
class TestVirtualTimeController : public VirtualTimeController {
public:
TestVirtualTimeController(HeadlessDevToolsClient* devtools_client)
: VirtualTimeController(devtools_client) {}
~TestVirtualTimeController() override = default;
MOCK_METHOD0(StartVirtualTime, void());
MOCK_METHOD2(ScheduleRepeatingTask,
void(RepeatingTask* task, base::TimeDelta interval));
MOCK_METHOD1(CancelRepeatingTask, void(RepeatingTask* task));
MOCK_METHOD1(AddObserver, void(Observer* observer));
MOCK_METHOD1(RemoveObserver, void(Observer* observer));
MOCK_METHOD1(SetStartDeferrer, void(StartDeferrer* deferrer));
MOCK_CONST_METHOD0(GetVirtualTimeBase, base::Time());
MOCK_CONST_METHOD0(GetCurrentVirtualTimeOffset, base::TimeDelta());
};
class CompositorControllerTest : public ::testing::Test {
protected:
CompositorControllerTest(bool update_display_for_animations = true) {
task_runner_ = base::MakeRefCounted<base::TestSimpleTaskRunner>();
client_.SetTaskRunnerForTests(task_runner_);
mock_host_ = base::MakeRefCounted<MockDevToolsAgentHost>();
EXPECT_CALL(*mock_host_, IsAttached()).WillOnce(Return(false));
EXPECT_CALL(*mock_host_, AttachClient(&client_));
client_.AttachToHost(mock_host_.get());
virtual_time_controller_ =
std::make_unique<TestVirtualTimeController>(&client_);
EXPECT_CALL(*virtual_time_controller_,
ScheduleRepeatingTask(_, kAnimationFrameInterval))
.WillOnce(testing::SaveArg<0>(&task_));
EXPECT_CALL(*virtual_time_controller_, AddObserver(_))
.WillOnce(testing::SaveArg<0>(&observer_));
EXPECT_CALL(*virtual_time_controller_, SetStartDeferrer(_))
.WillOnce(testing::SaveArg<0>(&deferer_));
ExpectHeadlessExperimentalEnable();
controller_ = std::make_unique<CompositorController>(
task_runner_, &client_, virtual_time_controller_.get(),
kAnimationFrameInterval, kWaitForCompositorReadyFrameDelay,
update_display_for_animations);
EXPECT_NE(nullptr, task_);
}
~CompositorControllerTest() override {
EXPECT_CALL(*virtual_time_controller_, RemoveObserver(_));
EXPECT_CALL(*virtual_time_controller_, CancelRepeatingTask(_));
EXPECT_CALL(*virtual_time_controller_, SetStartDeferrer(_));
}
void ExpectHeadlessExperimentalEnable() {
last_command_id_ += 2;
EXPECT_CALL(*mock_host_,
DispatchProtocolMessage(
&client_,
base::StringPrintf(
"{\"id\":%d,\"method\":\"HeadlessExperimental.enable\","
"\"params\":{}}",
last_command_id_)))
.WillOnce(Return(true));
}
void ExpectVirtualTime(double base, double offset) {
auto base_time = base::Time::FromJsTime(base);
auto offset_delta = base::TimeDelta::FromMilliseconds(offset);
EXPECT_CALL(*virtual_time_controller_, GetVirtualTimeBase())
.WillOnce(Return(base_time));
EXPECT_CALL(*virtual_time_controller_, GetCurrentVirtualTimeOffset())
.WillOnce(Return(offset_delta));
// Next BeginFrame's time should be the virtual time provided it has
// progressed.
base::Time virtual_time = base_time + offset_delta;
if (virtual_time > next_begin_frame_time_)
next_begin_frame_time_ = virtual_time;
}
void ExpectBeginFrame(bool no_display_updates = false,
std::unique_ptr<headless_experimental::ScreenshotParams>
screenshot_params = nullptr) {
last_command_id_ += 2;
base::DictionaryValue params;
auto builder =
std::move(headless_experimental::BeginFrameParams::Builder()
.SetFrameTime(next_begin_frame_time_.ToJsTime())
.SetInterval(kAnimationFrameInterval.InMillisecondsF())
.SetNoDisplayUpdates(no_display_updates));
if (screenshot_params)
builder.SetScreenshot(std::move(screenshot_params));
// Subsequent BeginFrames should have a later timestamp.
next_begin_frame_time_ += base::TimeDelta::FromMicroseconds(1);
std::string params_json;
auto params_value = builder.Build()->Serialize();
base::JSONWriter::Write(*params_value, &params_json);
EXPECT_CALL(
*mock_host_,
DispatchProtocolMessage(
&client_,
base::StringPrintf(
"{\"id\":%d,\"method\":\"HeadlessExperimental.beginFrame\","
"\"params\":%s}",
last_command_id_, params_json.c_str())))
.WillOnce(Return(true));
}
void SendBeginFrameReply(bool has_damage,
bool main_frame_content_updated,
const std::string& screenshot_data) {
auto result = headless_experimental::BeginFrameResult::Builder()
.SetHasDamage(has_damage)
.SetMainFrameContentUpdated(main_frame_content_updated)
.Build();
if (screenshot_data.length())
result->SetScreenshotData(screenshot_data);
std::string result_json;
auto result_value = result->Serialize();
base::JSONWriter::Write(*result_value, &result_json);
client_.DispatchProtocolMessage(
mock_host_.get(),
base::StringPrintf("{\"id\":%d,\"result\":%s}", last_command_id_,
result_json.c_str()));
}
void SendNeedsBeginFramesEvent(bool needs_begin_frames) {
client_.DispatchProtocolMessage(
mock_host_.get(),
base::StringPrintf("{\"method\":\"HeadlessExperimental."
"needsBeginFramesChanged\",\"params\":{"
"\"needsBeginFrames\":%s}}",
needs_begin_frames ? "true" : "false"));
// Events are dispatched asynchronously.
task_runner_->RunPendingTasks();
}
void SendMainFrameReadyForScreenshotsEvent() {
client_.DispatchProtocolMessage(mock_host_.get(),
"{\"method\":\"HeadlessExperimental."
"mainFrameReadyForScreenshots\",\"params\":"
"{}}");
// Events are dispatched asynchronously.
task_runner_->RunPendingTasks();
}
scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
scoped_refptr<MockDevToolsAgentHost> mock_host_;
HeadlessDevToolsClientImpl client_;
std::unique_ptr<TestVirtualTimeController> virtual_time_controller_;
std::unique_ptr<CompositorController> controller_;
int last_command_id_ = -2;
TestVirtualTimeController::RepeatingTask* task_ = nullptr;
TestVirtualTimeController::Observer* observer_ = nullptr;
TestVirtualTimeController::StartDeferrer* deferer_ = nullptr;
base::Time next_begin_frame_time_ =
base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(1);
};
TEST_F(CompositorControllerTest, WaitForCompositorReady) {
// Shouldn't send any commands yet as no needsBeginFrames event was sent yet.
bool ready = false;
controller_->WaitForCompositorReady(
base::BindRepeating([](bool* ready) { *ready = true; }, &ready));
EXPECT_FALSE(ready);
// Sends BeginFrames with delay while they are needed.
SendNeedsBeginFramesEvent(true);
EXPECT_TRUE(task_runner_->HasPendingTask());
ExpectVirtualTime(0, 0);
ExpectBeginFrame();
task_runner_->RunPendingTasks();
SendBeginFrameReply(true, false, std::string());
EXPECT_TRUE(task_runner_->HasPendingTask());
EXPECT_EQ(kWaitForCompositorReadyFrameDelay,
task_runner_->NextPendingTaskDelay());
ExpectVirtualTime(0, 0);
ExpectBeginFrame();
task_runner_->RunPendingTasks();
EXPECT_FALSE(task_runner_->HasPendingTask());
SendBeginFrameReply(false, false, std::string());
EXPECT_TRUE(task_runner_->HasPendingTask());
EXPECT_EQ(kWaitForCompositorReadyFrameDelay,
task_runner_->NextPendingTaskDelay());
ExpectVirtualTime(0, 0);
ExpectBeginFrame();
task_runner_->RunPendingTasks();
// No new BeginFrames are scheduled when BeginFrames are not needed.
SendNeedsBeginFramesEvent(false);
EXPECT_FALSE(task_runner_->HasPendingTask());
SendBeginFrameReply(false, false, std::string());
EXPECT_FALSE(task_runner_->HasPendingTask());
// Restarts sending BeginFrames when they are needed again.
SendNeedsBeginFramesEvent(true);
EXPECT_TRUE(task_runner_->HasPendingTask());
ExpectVirtualTime(0, 0);
ExpectBeginFrame();
task_runner_->RunPendingTasks();
// Stops sending BeginFrames when main frame becomes ready.
SendMainFrameReadyForScreenshotsEvent();
EXPECT_FALSE(task_runner_->HasPendingTask());
EXPECT_FALSE(ready);
SendBeginFrameReply(true, true, std::string());
EXPECT_FALSE(task_runner_->HasPendingTask());
EXPECT_TRUE(ready);
}
TEST_F(CompositorControllerTest, CaptureScreenshot) {
SendMainFrameReadyForScreenshotsEvent();
EXPECT_FALSE(task_runner_->HasPendingTask());
bool done = false;
controller_->CaptureScreenshot(
headless_experimental::ScreenshotParamsFormat::PNG, 100,
base::BindRepeating(
[](bool* done, const std::string& screenshot_data) {
*done = true;
EXPECT_EQ("test", screenshot_data);
},
&done));
EXPECT_TRUE(task_runner_->HasPendingTask());
ExpectVirtualTime(0, 0);
ExpectBeginFrame(
false, headless_experimental::ScreenshotParams::Builder()
.SetFormat(headless_experimental::ScreenshotParamsFormat::PNG)
.SetQuality(100)
.Build());
task_runner_->RunPendingTasks();
std::string base64;
base::Base64Encode("test", &base64);
SendBeginFrameReply(true, true, base64);
EXPECT_FALSE(task_runner_->HasPendingTask());
EXPECT_TRUE(done);
}
TEST_F(CompositorControllerTest, WaitForMainFrameContentUpdate) {
bool updated = false;
controller_->WaitForMainFrameContentUpdate(
base::BindRepeating([](bool* updated) { *updated = true; }, &updated));
EXPECT_FALSE(updated);
// Sends BeginFrames while they are needed.
SendNeedsBeginFramesEvent(true);
EXPECT_TRUE(task_runner_->HasPendingTask());
ExpectVirtualTime(0, 0);
ExpectBeginFrame();
task_runner_->RunPendingTasks();
SendBeginFrameReply(true, false, std::string());
EXPECT_TRUE(task_runner_->HasPendingTask());
EXPECT_EQ(base::TimeDelta(), task_runner_->NextPendingTaskDelay());
ExpectVirtualTime(0, 0);
ExpectBeginFrame();
task_runner_->RunPendingTasks();
EXPECT_FALSE(task_runner_->HasPendingTask());
SendBeginFrameReply(false, false, std::string());
EXPECT_TRUE(task_runner_->HasPendingTask());
EXPECT_EQ(base::TimeDelta(), task_runner_->NextPendingTaskDelay());
ExpectVirtualTime(0, 0);
ExpectBeginFrame();
task_runner_->RunPendingTasks();
// No new BeginFrames are scheduled when BeginFrames are not needed.
SendNeedsBeginFramesEvent(false);
EXPECT_FALSE(task_runner_->HasPendingTask());
SendBeginFrameReply(false, false, std::string());
EXPECT_FALSE(task_runner_->HasPendingTask());
// Restarts sending BeginFrames when they are needed again.
SendNeedsBeginFramesEvent(true);
EXPECT_TRUE(task_runner_->HasPendingTask());
ExpectVirtualTime(0, 0);
ExpectBeginFrame();
task_runner_->RunPendingTasks();
// Stops sending BeginFrames when an main frame update is included.
SendMainFrameReadyForScreenshotsEvent();
EXPECT_FALSE(task_runner_->HasPendingTask());
EXPECT_FALSE(updated);
SendBeginFrameReply(true, true, std::string());
EXPECT_FALSE(task_runner_->HasPendingTask());
EXPECT_TRUE(updated);
}
TEST_F(CompositorControllerTest, SendsAnimationFrames) {
base::Optional<VirtualTimeController::RepeatingTask::ContinuePolicy>
continue_policy;
auto continue_callback = base::BindRepeating(
[](base::Optional<VirtualTimeController::RepeatingTask::ContinuePolicy>*
continue_policy,
VirtualTimeController::RepeatingTask::ContinuePolicy policy) {
*continue_policy = policy;
},
&continue_policy);
// Doesn't send BeginFrames before virtual time started.
SendNeedsBeginFramesEvent(true);
EXPECT_FALSE(task_runner_->HasPendingTask());
// Sends a BeginFrame after interval elapsed.
task_->IntervalElapsed(kAnimationFrameInterval, continue_callback);
EXPECT_FALSE(continue_policy);
continue_policy = base::nullopt;
EXPECT_TRUE(task_runner_->HasPendingTask());
ExpectVirtualTime(1000, kAnimationFrameInterval.InMillisecondsF());
ExpectBeginFrame();
task_runner_->RunPendingTasks();
// Lets virtual time continue after BeginFrame was completed.
SendBeginFrameReply(false, false, std::string());
EXPECT_FALSE(task_runner_->HasPendingTask());
EXPECT_EQ(VirtualTimeController::RepeatingTask::ContinuePolicy::NOT_REQUIRED,
*continue_policy);
continue_policy = base::nullopt;
// Sends another BeginFrame after next interval elapsed.
task_->IntervalElapsed(kAnimationFrameInterval, continue_callback);
EXPECT_FALSE(continue_policy);
EXPECT_TRUE(task_runner_->HasPendingTask());
ExpectVirtualTime(1000, kAnimationFrameInterval.InMillisecondsF() * 2);
ExpectBeginFrame();
task_runner_->RunPendingTasks();
// Lets virtual time continue after BeginFrame was completed.
SendBeginFrameReply(false, false, std::string());
EXPECT_FALSE(task_runner_->HasPendingTask());
EXPECT_EQ(VirtualTimeController::RepeatingTask::ContinuePolicy::NOT_REQUIRED,
*continue_policy);
}
TEST_F(CompositorControllerTest, SkipsAnimationFrameForScreenshots) {
base::Optional<VirtualTimeController::RepeatingTask::ContinuePolicy>
continue_policy;
auto continue_callback = base::BindRepeating(
[](base::Optional<VirtualTimeController::RepeatingTask::ContinuePolicy>*
continue_policy,
VirtualTimeController::RepeatingTask::ContinuePolicy policy) {
*continue_policy = policy;
},
&continue_policy);
SendMainFrameReadyForScreenshotsEvent();
EXPECT_FALSE(task_runner_->HasPendingTask());
SendNeedsBeginFramesEvent(true);
EXPECT_FALSE(task_runner_->HasPendingTask());
// Doesn't send a BeginFrame after interval elapsed if a screenshot is taken
// instead.
task_->IntervalElapsed(kAnimationFrameInterval, continue_callback);
EXPECT_FALSE(continue_policy);
controller_->CaptureScreenshot(
headless_experimental::ScreenshotParamsFormat::PNG, 100,
base::BindRepeating([](const std::string&) {}));
EXPECT_TRUE(task_runner_->HasPendingTask());
ExpectVirtualTime(0, 0);
ExpectBeginFrame(
false, headless_experimental::ScreenshotParams::Builder()
.SetFormat(headless_experimental::ScreenshotParamsFormat::PNG)
.SetQuality(100)
.Build());
task_runner_->RunPendingTasks();
EXPECT_EQ(VirtualTimeController::RepeatingTask::ContinuePolicy::NOT_REQUIRED,
*continue_policy);
}
TEST_F(CompositorControllerTest,
PostponesAnimationFrameWhenVirtualTimeStopped) {
base::Optional<VirtualTimeController::RepeatingTask::ContinuePolicy>
continue_policy;
auto continue_callback = base::BindRepeating(
[](base::Optional<VirtualTimeController::RepeatingTask::ContinuePolicy>*
continue_policy,
VirtualTimeController::RepeatingTask::ContinuePolicy policy) {
*continue_policy = policy;
},
&continue_policy);
SendNeedsBeginFramesEvent(true);
EXPECT_FALSE(task_runner_->HasPendingTask());
// Doesn't send a BeginFrame after interval elapsed if the budget also
// expired.
task_->IntervalElapsed(kAnimationFrameInterval, continue_callback);
EXPECT_FALSE(continue_policy);
observer_->VirtualTimeStopped(kAnimationFrameInterval);
EXPECT_EQ(VirtualTimeController::RepeatingTask::ContinuePolicy::NOT_REQUIRED,
*continue_policy);
continue_policy = base::nullopt;
// Flush cancelled task.
task_runner_->RunPendingTasks();
bool can_continue = false;
auto defer_callback = base::BindRepeating(
[](bool* can_continue) { *can_continue = true; }, &can_continue);
// Sends a BeginFrame when more virtual time budget is requested.
ExpectVirtualTime(0, 0);
ExpectBeginFrame();
deferer_->DeferStart(defer_callback);
EXPECT_FALSE(can_continue);
SendBeginFrameReply(false, false, std::string());
EXPECT_FALSE(task_runner_->HasPendingTask());
EXPECT_TRUE(can_continue);
}
TEST_F(CompositorControllerTest,
SkipsAnimationFrameWhenVirtualTimeStoppedAndScreenshotWasTaken) {
base::Optional<VirtualTimeController::RepeatingTask::ContinuePolicy>
continue_policy;
auto continue_callback = base::BindRepeating(
[](base::Optional<VirtualTimeController::RepeatingTask::ContinuePolicy>*
continue_policy,
VirtualTimeController::RepeatingTask::ContinuePolicy policy) {
*continue_policy = policy;
},
&continue_policy);
SendMainFrameReadyForScreenshotsEvent();
EXPECT_FALSE(task_runner_->HasPendingTask());
SendNeedsBeginFramesEvent(true);
EXPECT_FALSE(task_runner_->HasPendingTask());
// Doesn't send a BeginFrame after interval elapsed if the budget also
// expired.
task_->IntervalElapsed(kAnimationFrameInterval, continue_callback);
EXPECT_FALSE(continue_policy);
observer_->VirtualTimeStopped(kAnimationFrameInterval);
EXPECT_EQ(VirtualTimeController::RepeatingTask::ContinuePolicy::NOT_REQUIRED,
*continue_policy);
continue_policy = base::nullopt;
// Flush cancelled task.
task_runner_->RunPendingTasks();
controller_->CaptureScreenshot(
headless_experimental::ScreenshotParamsFormat::PNG, 100,
base::BindRepeating([](const std::string&) {}));
EXPECT_TRUE(task_runner_->HasPendingTask());
ExpectVirtualTime(0, 0);
ExpectBeginFrame(
false, headless_experimental::ScreenshotParams::Builder()
.SetFormat(headless_experimental::ScreenshotParamsFormat::PNG)
.SetQuality(100)
.Build());
task_runner_->RunPendingTasks();
bool can_continue = false;
auto defer_callback = base::BindRepeating(
[](bool* can_continue) { *can_continue = true; }, &can_continue);
// Sends a BeginFrame when more virtual time budget is requested.
deferer_->DeferStart(defer_callback);
EXPECT_TRUE(can_continue);
}
TEST_F(CompositorControllerTest, WaitUntilIdle) {
bool idle = false;
auto idle_callback =
base::BindRepeating([](bool* idle) { *idle = true; }, &idle);
SendNeedsBeginFramesEvent(true);
EXPECT_FALSE(task_runner_->HasPendingTask());
// WaitUntilIdle executes callback immediately if no BeginFrame is active.
controller_->WaitUntilIdle(idle_callback);
EXPECT_TRUE(idle);
idle = false;
// Send a BeginFrame.
task_->IntervalElapsed(
kAnimationFrameInterval,
base::BindRepeating(
[](VirtualTimeController::RepeatingTask::ContinuePolicy) {}));
EXPECT_TRUE(task_runner_->HasPendingTask());
ExpectVirtualTime(0, 0);
ExpectBeginFrame();
task_runner_->RunPendingTasks();
// WaitUntilIdle only executes callback after BeginFrame was completed.
controller_->WaitUntilIdle(idle_callback);
EXPECT_FALSE(idle);
SendBeginFrameReply(false, false, std::string());
EXPECT_FALSE(task_runner_->HasPendingTask());
EXPECT_TRUE(idle);
idle = false;
}
class CompositorControllerNoDisplayUpdateTest
: public CompositorControllerTest {
protected:
CompositorControllerNoDisplayUpdateTest() : CompositorControllerTest(false) {}
};
TEST_F(CompositorControllerNoDisplayUpdateTest,
SkipsDisplayUpdateOnlyForAnimationFrames) {
base::Optional<VirtualTimeController::RepeatingTask::ContinuePolicy>
continue_policy;
auto continue_callback = base::BindRepeating(
[](base::Optional<VirtualTimeController::RepeatingTask::ContinuePolicy>*
continue_policy,
VirtualTimeController::RepeatingTask::ContinuePolicy policy) {
*continue_policy = policy;
},
&continue_policy);
// Wait for update BeginFrames update display.
SendNeedsBeginFramesEvent(true);
controller_->WaitForMainFrameContentUpdate(base::BindRepeating([]() {}));
EXPECT_TRUE(task_runner_->HasPendingTask());
ExpectVirtualTime(1000, 0);
ExpectBeginFrame();
task_runner_->RunPendingTasks();
SendMainFrameReadyForScreenshotsEvent();
EXPECT_FALSE(task_runner_->HasPendingTask());
SendBeginFrameReply(true, true, std::string());
EXPECT_FALSE(task_runner_->HasPendingTask());
// Sends an animation BeginFrame without display update after interval
// elapsed.
task_->IntervalElapsed(kAnimationFrameInterval, continue_callback);
EXPECT_FALSE(continue_policy);
EXPECT_TRUE(task_runner_->HasPendingTask());
ExpectVirtualTime(1000, kAnimationFrameInterval.InMillisecondsF());
ExpectBeginFrame(true);
task_runner_->RunPendingTasks();
// Lets virtual time continue after BeginFrame was completed.
SendBeginFrameReply(false, false, std::string());
EXPECT_FALSE(task_runner_->HasPendingTask());
EXPECT_EQ(VirtualTimeController::RepeatingTask::ContinuePolicy::NOT_REQUIRED,
*continue_policy);
continue_policy = base::nullopt;
// Screenshots update display.
task_->IntervalElapsed(kAnimationFrameInterval, continue_callback);
EXPECT_FALSE(continue_policy);
controller_->CaptureScreenshot(
headless_experimental::ScreenshotParamsFormat::PNG, 100,
base::BindRepeating([](const std::string&) {}));
EXPECT_TRUE(task_runner_->HasPendingTask());
ExpectVirtualTime(1000, kAnimationFrameInterval.InMillisecondsF() * 2);
ExpectBeginFrame(
false, headless_experimental::ScreenshotParams::Builder()
.SetFormat(headless_experimental::ScreenshotParamsFormat::PNG)
.SetQuality(100)
.Build());
task_runner_->RunPendingTasks();
EXPECT_EQ(VirtualTimeController::RepeatingTask::ContinuePolicy::NOT_REQUIRED,
*continue_policy);
}
} // namespace headless