Commit fd7d0cc6 authored by Peter Kvitek's avatar Peter Kvitek Committed by Commit Bot

Implemented virtual time controller for browser tests

Change-Id: Id112e69693cf40f3e1dc2980a690ac4e5153d8d4
Reviewed-on: https://chromium-review.googlesource.com/1135765
Commit-Queue: Peter Kvitek <kvitekp@chromium.org>
Reviewed-by: default avatarPavel Feldman <pfeldman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#576264}
parent 143de4ea
<html>
<script>
window.rafCount = 0;
function animationCallback() {
++window.rafCount;
console.log(`Animation frame callback #${window.rafCount}`);
window.requestAnimationFrame(animationCallback);
}
function startRAF() {
console.log('Requesting first animation frame');
window.requestAnimationFrame(animationCallback);
}
</script>
<body></body>
</Page loaded
Tests virtual time controller operation.
onInstalled:
onExpired: 100
Requesting first animation frame
Animation frame callback #1
Animation frame callback #2
onExpired: 150
Animation frame callback #3
Animation frame callback #4
onExpired: 200
\ No newline at end of file
// Copyright 2018 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.
(async function(testRunner) {
let {page, session, dp} = await testRunner.startBlank(
`Tests virtual time controller operation.`);
await dp.Target.enable();
let VirtualTimeController =
await testRunner.loadScript('virtual-time-controller.js');
// Open the test page in a new tab with BeginFrameControl enabled.
await testTargetPage(
await session.createTargetInNewContext(800, 600, 'about:blank', true));
async function testTargetPage(session) {
const dp = session.protocol;
await dp.Runtime.enable();
await dp.HeadlessExperimental.enable();
dp.Runtime.onConsoleAPICalled(data => {
const text = data.params.args[0].value;
testRunner.log(text);
});
let expirationCount = 0;
const vtc = new VirtualTimeController(testRunner, dp, 25);
await vtc.grantInitialTime(100, 1000, onInstalled, onExpired);
async function onInstalled(virtualTimeBase){
testRunner.log(`onInstalled:`);
}
async function onExpired(totalElapsedTime) {
testRunner.log(`onExpired: ${totalElapsedTime}`);
if (expirationCount === 0)
await session.evaluate('startRAF()');
if (++expirationCount < 3) {
await vtc.grantTime(50, onExpired);
} else {
testRunner.completeTest();
}
}
dp.Page.navigate({url: testRunner.url(
'resources/virtual-time-controller-test.html')});
}
})
// Copyright 2018 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.
/**
* A helper class to manage virtual time and automatically generate animation
* frames within the granted virtual time interval.
*/
(class VirtualTimeController {
/**
* @param {!TestRunner} testRunner Host TestRunner instance.
* @param {!Proxy} dp DevTools session protocol instance.
* @param {?number} animationFrameInterval in milliseconds, integer.
* @param {?number} maxTaskStarvationCount Specifies the maximum number of
* tasks that can be run before virtual time is forced forward to prevent
* deadlock.
*/
constructor(testRunner, dp, animationFrameInterval, maxTaskStarvationCount) {
this.testRunner_ = testRunner;
this.dp_ = dp;
this.animationFrameInterval_ = animationFrameInterval || 16;
this.maxTaskStarvationCount_ = maxTaskStarvationCount || 100 * 1000;
this.virtualTimeBase_ = 0;
this.remainingBudget_ = 0;
this.lastGrantedChunk_ = 0;
this.totalElapsedTime_ = 0;
this.onInstalled_ = null;
this.onExpired_ = null;
this.dp_.Emulation.onVirtualTimeBudgetExpired(async data => {
this.totalElapsedTime_ += this.lastGrantedChunk_;
this.remainingBudget_ -= this.lastGrantedChunk_;
if (this.remainingBudget_ === 0) {
if (this.onExpired_) {
this.onExpired_(this.totalElapsedTime_);
}
} else {
await this.issueAnimationFrameAndScheduleNextChunk_();
}
});
}
/**
* Grants initial portion of virtual time.
* @param {number} budget Virtual time budget in milliseconds.
* @param {number} initialVirtualTime Initial virtual time in milliseconds.
* @param {?function()} onInstalled Called when initial virtual time is
* granted, parameter specifies virtual time base.
* @param {?function()} onExpired Called when granted virtual time is expired,
* parameter specifies total elapsed virtual time.
*/
async grantInitialTime(budget, initialVirtualTime, onInstalled, onExpired) {
// Pause for the first time and remember base virtual time.
this.virtualTimeBase_ = (await this.dp_.Emulation.setVirtualTimePolicy(
{initialVirtualTime, policy: 'pause'}))
.result.virtualTimeTicksBase;
// Renderer wants the very first frame to be fully updated.
await this.dp_.HeadlessExperimental.beginFrame({noDisplayUpdates: false});
this.onInstalled_ = onInstalled;
await this.grantTime(budget, onExpired);
}
/**
* Grants additional virtual time.
* @param {number} budget Virtual time budget in milliseconds.
* @param {?function()} onExpired Called when granted virtual time is expired,
* parameter specifies total elapsed virtual time.
*/
async grantTime(budget, onExpired) {
this.remainingBudget_ = budget;
this.onExpired_ = onExpired;
await this.issueAnimationFrameAndScheduleNextChunk_();
}
/**
* Revokes any granted virtual time, resulting in no more animatino frames
* being issued and final OnExpired call being made.
*/
stopVirtualTimeGracefully() {
if (this.remainingBudget_) {
this.remainingBudget_ = 0;
}
}
async issueAnimationFrameAndScheduleNextChunk_() {
if (this.totalElapsedTime_ > 0 && this.remainingBudget_ > 0) {
const remainder = this.totalElapsedTime_ % this.animationFrameInterval_;
if (remainder === 0) { // at the frame boundary?
const frameTimeTicks = this.virtualTimeBase_ + this.totalElapsedTime_;
await this.dp_.HeadlessExperimental.beginFrame(
{frameTimeTicks, noDisplayUpdates: true});
}
}
await this.scheduleNextChunk_();
}
async scheduleNextChunk_() {
const lastFrame = this.totalElapsedTime_ % this.animationFrameInterval_;
const nextAnimationFrame = this.animationFrameInterval_ - lastFrame;
const chunk = Math.min(nextAnimationFrame, this.remainingBudget_);
await this.dp_.Emulation.setVirtualTimePolicy(
{policy: 'pauseIfNetworkFetchesPending', budget: chunk,
maxVirtualTimeTaskStarvationCount: this.maxTaskStarvationCount_,
waitForNavigation: this.totalElapsedTime_ === 0});
this.lastGrantedChunk_ = chunk;
if (this.onInstalled_) {
this.onInstalled_(this.virtualTimeBase_);
this.onInstalled_ = null;
}
}
});
......@@ -297,10 +297,12 @@ class HeadlessProtocolCompositorBrowserTest
#define MAYBE_CompositorBasicRaf DISABLED_CompositorBasicRaf
#define MAYBE_CompositorImageAnimation DISABLED_CompositorImageAnimation
#define MAYBE_CompositorCssAnimation DISABLED_CompositorCssAnimation
#define MAYBE_VirtualTimeControllerTest DISABLED_VirtualTimeControllerTest
#else
#define MAYBE_CompositorBasicRaf CompositorBasicRaf
#define MAYBE_CompositorImageAnimation CompositorImageAnimation
#define MAYBE_CompositorCssAnimation CompositorCssAnimation
#define MAYBE_VirtualTimeControllerTest VirtualTimeControllerTest
#endif
HEADLESS_PROTOCOL_COMPOSITOR_TEST(MAYBE_CompositorBasicRaf,
"emulation/compositor-basic-raf.js");
......@@ -309,8 +311,11 @@ HEADLESS_PROTOCOL_COMPOSITOR_TEST(
"emulation/compositor-image-animation-test.js");
HEADLESS_PROTOCOL_COMPOSITOR_TEST(MAYBE_CompositorCssAnimation,
"emulation/compositor-css-animation-test.js");
HEADLESS_PROTOCOL_TEST(MAYBE_VirtualTimeControllerTest,
"helpers/virtual-time-controller-test.js");
#undef MAYBE_CompositorBasicRaf
#undef MAYBE_CompositorImageAnimation
#undef MAYBE_CompositorCssAnimation
#undef MAYBE_VirtualTimeControllerTest
} // namespace headless
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment