blob: 9082e972f1b82cc61998ae96cee779f4f09808a7 [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright (c) 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.
-->
<link rel="import" href="/tracing/base/utils.html">
<script>
'use strict';
tr.exportTo('tr.b', function() {
var ESTIMATED_IDLE_PERIOD_LENGTH_MILLISECONDS = 10;
// The maximum amount of time that we allow for a task to get scheduled
// in idle time before forcing the task to run.
var REQUEST_IDLE_CALLBACK_TIMEOUT_MILLISECONDS = 100;
// Setting this to true will cause stack traces to get dumped into the
// tasks. When an exception happens the original stack will be printed.
//
// NOTE: This should never be set committed as true.
var recordRAFStacks = false;
var pendingPreAFs = [];
var pendingRAFs = [];
var pendingIdleCallbacks = [];
var currentRAFDispatchList = undefined;
var rafScheduled = false;
var idleWorkScheduled = false;
function scheduleRAF() {
if (rafScheduled)
return;
rafScheduled = true;
if (tr.isHeadless) {
Promise.resolve().then(function() {
processRequests(false, 0);
}, function(e) {
console.log(e.stack);
throw e;
});
} else {
if (window.requestAnimationFrame) {
window.requestAnimationFrame(processRequests.bind(this, false));
} else {
var delta = Date.now() - window.performance.now();
window.webkitRequestAnimationFrame(function(domTimeStamp) {
processRequests(false, domTimeStamp - delta);
});
}
}
}
function nativeRequestIdleCallbackSupported() {
return !tr.isHeadless && window.requestIdleCallback;
}
function scheduleIdleWork() {
if (idleWorkScheduled)
return;
if (!nativeRequestIdleCallbackSupported()) {
scheduleRAF();
return;
}
idleWorkScheduled = true;
window.requestIdleCallback(function(deadline, didTimeout) {
processIdleWork(false /* forceAllTasksToRun */, deadline);
}, { timeout: REQUEST_IDLE_CALLBACK_TIMEOUT_MILLISECONDS });
}
function onAnimationFrameError(e, opt_stack) {
console.log(e.stack);
if (tr.isHeadless)
throw e;
if (opt_stack)
console.log(opt_stack);
if (e.message)
console.error(e.message, e.stack);
else
console.error(e);
}
function runTask(task, frameBeginTime) {
try {
task.callback.call(task.context, frameBeginTime);
} catch (e) {
tr.b.onAnimationFrameError(e, task.stack);
}
}
function processRequests(forceAllTasksToRun, frameBeginTime) {
rafScheduled = false;
var currentPreAFs = pendingPreAFs;
currentRAFDispatchList = pendingRAFs;
pendingPreAFs = [];
pendingRAFs = [];
var hasRAFTasks = currentPreAFs.length || currentRAFDispatchList.length;
for (var i = 0; i < currentPreAFs.length; i++)
runTask(currentPreAFs[i], frameBeginTime);
while (currentRAFDispatchList.length > 0)
runTask(currentRAFDispatchList.shift(), frameBeginTime);
currentRAFDispatchList = undefined;
if ((!hasRAFTasks && !nativeRequestIdleCallbackSupported()) ||
forceAllTasksToRun) {
// We assume that we want to do a fixed maximum amount of optional work
// per frame. Hopefully rAF will eventually pass this in for us.
var rafCompletionDeadline =
frameBeginTime + ESTIMATED_IDLE_PERIOD_LENGTH_MILLISECONDS;
processIdleWork(
forceAllTasksToRun, {
timeRemaining: function() {
return rafCompletionDeadline - window.performance.now();
}
}
);
}
if (pendingIdleCallbacks.length > 0)
scheduleIdleWork();
}
function processIdleWork(forceAllTasksToRun, deadline) {
idleWorkScheduled = false;
while (pendingIdleCallbacks.length > 0) {
runTask(pendingIdleCallbacks.shift());
// Check timer after running at least one idle task to avoid buggy
// window.performance.now() on some platforms from blocking the idle
// task queue.
if (!forceAllTasksToRun &&
(tr.isHeadless || deadline.timeRemaining() <= 0)) {
break;
}
}
if (pendingIdleCallbacks.length > 0)
scheduleIdleWork();
}
function getStack_() {
if (!recordRAFStacks)
return '';
var stackLines = tr.b.stackTrace();
// Strip off getStack_.
stackLines.shift();
return stackLines.join('\n');
}
function requestPreAnimationFrame(callback, opt_this) {
pendingPreAFs.push({
callback: callback,
context: opt_this || global,
stack: getStack_()});
scheduleRAF();
}
function requestAnimationFrameInThisFrameIfPossible(callback, opt_this) {
if (!currentRAFDispatchList) {
requestAnimationFrame(callback, opt_this);
return;
}
currentRAFDispatchList.push({
callback: callback,
context: opt_this || global,
stack: getStack_()});
return;
}
function requestAnimationFrame(callback, opt_this) {
pendingRAFs.push({
callback: callback,
context: opt_this || global,
stack: getStack_()});
scheduleRAF();
}
function requestIdleCallback(callback, opt_this) {
pendingIdleCallbacks.push({
callback: callback,
context: opt_this || global,
stack: getStack_()});
scheduleIdleWork();
}
function forcePendingRAFTasksToRun(frameBeginTime) {
if (!rafScheduled)
return;
processRequests(false, frameBeginTime);
}
function forceAllPendingTasksToRunForTest() {
if (!rafScheduled && !idleWorkScheduled)
return;
processRequests(true, 0);
}
return {
onAnimationFrameError: onAnimationFrameError,
requestPreAnimationFrame: requestPreAnimationFrame,
requestAnimationFrame: requestAnimationFrame,
requestAnimationFrameInThisFrameIfPossible:
requestAnimationFrameInThisFrameIfPossible,
requestIdleCallback: requestIdleCallback,
forcePendingRAFTasksToRun: forcePendingRAFTasksToRun,
forceAllPendingTasksToRunForTest: forceAllPendingTasksToRunForTest
};
});
</script>