1<!DOCTYPE html> 2<!-- 3Copyright (c) 2014 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7<link rel="import" href="/tracing/base/utils.html"> 8<script> 9'use strict'; 10 11tr.exportTo('tr.b', function() { 12 var ESTIMATED_IDLE_PERIOD_LENGTH_MILLISECONDS = 10; 13 // The maximum amount of time that we allow for a task to get scheduled 14 // in idle time before forcing the task to run. 15 var REQUEST_IDLE_CALLBACK_TIMEOUT_MILLISECONDS = 100; 16 17 // Setting this to true will cause stack traces to get dumped into the 18 // tasks. When an exception happens the original stack will be printed. 19 // 20 // NOTE: This should never be set committed as true. 21 var recordRAFStacks = false; 22 23 var pendingPreAFs = []; 24 var pendingRAFs = []; 25 var pendingIdleCallbacks = []; 26 var currentRAFDispatchList = undefined; 27 28 var rafScheduled = false; 29 var idleWorkScheduled = false; 30 31 function scheduleRAF() { 32 if (rafScheduled) 33 return; 34 rafScheduled = true; 35 if (tr.isHeadless) { 36 Promise.resolve().then(function() { 37 processRequests(false, 0); 38 }, function(e) { 39 console.log(e.stack); 40 throw e; 41 }); 42 } else { 43 if (window.requestAnimationFrame) { 44 window.requestAnimationFrame(processRequests.bind(this, false)); 45 } else { 46 var delta = Date.now() - window.performance.now(); 47 window.webkitRequestAnimationFrame(function(domTimeStamp) { 48 processRequests(false, domTimeStamp - delta); 49 }); 50 } 51 } 52 } 53 54 function nativeRequestIdleCallbackSupported() { 55 return !tr.isHeadless && window.requestIdleCallback; 56 } 57 58 function scheduleIdleWork() { 59 if (idleWorkScheduled) 60 return; 61 if (!nativeRequestIdleCallbackSupported()) { 62 scheduleRAF(); 63 return; 64 } 65 idleWorkScheduled = true; 66 window.requestIdleCallback(function(deadline, didTimeout) { 67 processIdleWork(false /* forceAllTasksToRun */, deadline); 68 }, { timeout: REQUEST_IDLE_CALLBACK_TIMEOUT_MILLISECONDS }); 69 } 70 71 function onAnimationFrameError(e, opt_stack) { 72 console.log(e.stack); 73 if (tr.isHeadless) 74 throw e; 75 76 if (opt_stack) 77 console.log(opt_stack); 78 79 if (e.message) 80 console.error(e.message, e.stack); 81 else 82 console.error(e); 83 } 84 85 function runTask(task, frameBeginTime) { 86 try { 87 task.callback.call(task.context, frameBeginTime); 88 } catch (e) { 89 tr.b.onAnimationFrameError(e, task.stack); 90 } 91 } 92 93 function processRequests(forceAllTasksToRun, frameBeginTime) { 94 rafScheduled = false; 95 96 var currentPreAFs = pendingPreAFs; 97 currentRAFDispatchList = pendingRAFs; 98 pendingPreAFs = []; 99 pendingRAFs = []; 100 var hasRAFTasks = currentPreAFs.length || currentRAFDispatchList.length; 101 102 for (var i = 0; i < currentPreAFs.length; i++) 103 runTask(currentPreAFs[i], frameBeginTime); 104 105 while (currentRAFDispatchList.length > 0) 106 runTask(currentRAFDispatchList.shift(), frameBeginTime); 107 currentRAFDispatchList = undefined; 108 109 if ((!hasRAFTasks && !nativeRequestIdleCallbackSupported()) || 110 forceAllTasksToRun) { 111 // We assume that we want to do a fixed maximum amount of optional work 112 // per frame. Hopefully rAF will eventually pass this in for us. 113 var rafCompletionDeadline = 114 frameBeginTime + ESTIMATED_IDLE_PERIOD_LENGTH_MILLISECONDS; 115 processIdleWork( 116 forceAllTasksToRun, { 117 timeRemaining: function() { 118 return rafCompletionDeadline - window.performance.now(); 119 } 120 } 121 ); 122 } 123 124 if (pendingIdleCallbacks.length > 0) 125 scheduleIdleWork(); 126 } 127 128 function processIdleWork(forceAllTasksToRun, deadline) { 129 idleWorkScheduled = false; 130 while (pendingIdleCallbacks.length > 0) { 131 runTask(pendingIdleCallbacks.shift()); 132 // Check timer after running at least one idle task to avoid buggy 133 // window.performance.now() on some platforms from blocking the idle 134 // task queue. 135 if (!forceAllTasksToRun && 136 (tr.isHeadless || deadline.timeRemaining() <= 0)) { 137 break; 138 } 139 } 140 141 if (pendingIdleCallbacks.length > 0) 142 scheduleIdleWork(); 143 } 144 145 function getStack_() { 146 if (!recordRAFStacks) 147 return ''; 148 149 var stackLines = tr.b.stackTrace(); 150 // Strip off getStack_. 151 stackLines.shift(); 152 return stackLines.join('\n'); 153 } 154 155 function requestPreAnimationFrame(callback, opt_this) { 156 pendingPreAFs.push({ 157 callback: callback, 158 context: opt_this || global, 159 stack: getStack_()}); 160 scheduleRAF(); 161 } 162 163 function requestAnimationFrameInThisFrameIfPossible(callback, opt_this) { 164 if (!currentRAFDispatchList) { 165 requestAnimationFrame(callback, opt_this); 166 return; 167 } 168 currentRAFDispatchList.push({ 169 callback: callback, 170 context: opt_this || global, 171 stack: getStack_()}); 172 return; 173 } 174 175 function requestAnimationFrame(callback, opt_this) { 176 pendingRAFs.push({ 177 callback: callback, 178 context: opt_this || global, 179 stack: getStack_()}); 180 scheduleRAF(); 181 } 182 183 function requestIdleCallback(callback, opt_this) { 184 pendingIdleCallbacks.push({ 185 callback: callback, 186 context: opt_this || global, 187 stack: getStack_()}); 188 scheduleIdleWork(); 189 } 190 191 function forcePendingRAFTasksToRun(frameBeginTime) { 192 if (!rafScheduled) 193 return; 194 processRequests(false, frameBeginTime); 195 } 196 197 function forceAllPendingTasksToRunForTest() { 198 if (!rafScheduled && !idleWorkScheduled) 199 return; 200 processRequests(true, 0); 201 } 202 203 return { 204 onAnimationFrameError: onAnimationFrameError, 205 requestPreAnimationFrame: requestPreAnimationFrame, 206 requestAnimationFrame: requestAnimationFrame, 207 requestAnimationFrameInThisFrameIfPossible: 208 requestAnimationFrameInThisFrameIfPossible, 209 requestIdleCallback: requestIdleCallback, 210 forcePendingRAFTasksToRun: forcePendingRAFTasksToRun, 211 forceAllPendingTasksToRunForTest: forceAllPendingTasksToRunForTest 212 }; 213}); 214</script> 215