<!DOCTYPE html>
<html style="width: 100%; height: 100%;">
<head>
<meta charset="utf-8">
<title>Scheduler Test Page</title>
<style>
.correct {
border: solid green 2px;
}.incorrect {
border: dashed red 2px;
}</style>
</head>
<body>
<h1>Scheduler Fixture</h1>
<p>
This fixture is for manual testing purposes, and the patterns used inimplementing it should not be used as a model. This is mainly for anyoneworking on making changes to the `schedule` module.</p>
<h2>Tests:</h2>
<ol>
<li>
<button onClick="runTestOne()">Run Test 1</button>
<p>Calls the callback within the frame when not blocked:</p>
<div><b>Expected:</b></div>
<div id="test-1-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
<div> -------------------------------------------------</div>
<div><b>Actual:</b></div>
<div id="test-1"></div>
</li>
<li>
<p>Accepts multiple callbacks and calls within frame when not blocked</p>
<button onClick="runTestTwo()">Run Test 2</button>
<div><b>Expected:</b></div>
<div id="test-2-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
<div> -------------------------------------------------</div>
<div><b>Actual:</b></div>
<div id="test-2"></div>
</li>
<li>
<p>Schedules callbacks in correct order when they use scheduleCallback to schedule themselves</p>
<button onClick="runTestThree()">Run Test 3</button>
<div><b>Expected:</b></div>
<div id="test-3-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
<div> -------------------------------------------------</div>
<div><b>Actual:</b></div>
<div id="test-3"></div>
</li>
<li>
<p>Calls timed out callbacks and then any more pending callbacks, defers others if time runs out</p>
<button onClick="runTestFour()">Run Test 4</button>
<div><b>Expected:</b></div>
<div id="test-4-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
<div> -------------------------------------------------</div>
<div><b>Actual:</b></div>
<div id="test-4"></div>
</li>
<li>
<p>When some callbacks throw errors, still calls them all within the same frame</p>
<p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
<button onClick="runTestFive()">Run Test 5</button>
</li>
<li>
<p>When some callbacks throw errors <b> and some also time out</b>, still calls them all within the same frame</p>
<p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
<button onClick="runTestSix()">Run Test 6</button>
</li>
<li>
<p>Continues calling callbacks even when user switches away from this tab</p>
<button onClick="runTestSeven()">Run Test 7</button>
<div><b>Click the button above, observe the counter, then switch to
another tab and switch back:</b></div>
<div id="test-7">
</div>
<div> If the counter advanced while you were away from this tab, it's correct.</div>
</li>
<li>
<p>Can pause execution, dump scheduled callbacks, and continue where it left off</p>
<button onClick="runTestEight()">Run Test 8</button>
<div><b>Click the button above, press "continue" to finish the test after it pauses:</b></div>
<button onClick="continueTestEight()">continue</button>
<div><b>Expected:</b></div>
<div id="test-8-expected">
</div>
<div> -------------------------------------------------</div>
<div> If the test didn't progress until you hit "continue" and </div>
<div> you see the same above and below afterwards it's correct.
<div> -------------------------------------------------</div>
<div><b>Actual:</b></div>
<div id="test-8"></div>
</li>
<li>
<p>Can force a specific framerate</p>
<p><b>IMPORTANT:</b> This test may be flaky if other tests have been run in this js instance. To get a clean test refresh the page before running test 9</p>
<button onClick="runTestNine()">Run Test 9</button>
<div><b>Expected:</b></div>
<div id="test-9-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
<div> -------------------------------------------------</div>
<div><b>Actual:</b></div>
<div id="test-9"></div>
</div>
</li>
<li>
<p>Runs scheduled JS work for 99% of the frame time when nothing else is using the thread.</p>
<p><b>NOTE:</b> Try this test both when nothing else is running and when something is using the compositor thread in another visible tab with video or <a href="https://www.shadertoy.com/view/MtffDX">WebGL content</a> (Shift+Click).</p>
<button onClick="runTestTen()">Run Test 10</button>
<div><b>Expected:</b></div>
<div id="test-10-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
<div> -------------------------------------------------</div>
<div><b>Actual:</b></div>
<div id="test-10"></div>
</div>
</li>
<li>
<p>Runs scheduled JS work more than 95% of the frame time when inserting DOM nodes.</p>
<p><b>NOTE:</b> Try this test both when nothing else is running and when something is using the compositor thread in another visible tab with video or <a href="https://www.shadertoy.com/view/MtffDX">WebGL content</a> (Shift+Click).</p>
<button onClick="runTestEleven()">Run Test 11</button>
<div><b>Expected:</b></div>
<div id="test-11-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
<div> -------------------------------------------------</div>
<div><b>Actual:</b></div>
<div id="test-11"></div>
</div>
</li>
</ol>
<script src="../../build/oss-experimental/react/umd/react.production.min.js"></script>
<script src="../../build/oss-experimental/scheduler/umd/scheduler.production.min.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
<script type="text/babel">
const {unstable_scheduleCallback: scheduleCallback,unstable_cancelCallback: cancelCallback,unstable_now: now,unstable_getFirstCallbackNode: getFirstCallbackNode,unstable_pauseExecution: pauseExecution,unstable_continueExecution: continueExecution,unstable_forceFrameRate: forceFrameRate,unstable_shouldYield: shouldYield,unstable_NormalPriority: NormalPriority,} = Scheduler;function displayTestResult(testNumber) {const expectationNode = document.getElementById('test-' + testNumber + '-expected');const resultNode = document.getElementById('test-' + testNumber);resultNode.innerHTML = latestResults[testNumber - 1].join('<br />');
expectationNode.innerHTML = expectedResults[testNumber - 1].join('<br />');
}function clearTestResult(testNumber) {const resultNode = document.getElementById('test-' + testNumber);resultNode.innerHTML = '';latestResults[testNumber - 1] = [];}function updateTestResult(testNumber, textToAddToResult) {latestResults[testNumber - 1].push(textToAddToResult);};function checkTestResult(testNumber) {let correct = true;const expected = expectedResults[testNumber - 1]; // zero indexingconst result = latestResults[testNumber - 1]; // zero indexingif (expected.length !== result.length) {correct = false;} else {for (let i = 0, len = expected.length; i < len; i++) {if (expected[i] !== result[i]) {correct = false;break;}}}const currentClass = correct ? 'correct' : 'incorrect';const previousClass = correct ? 'incorrect' : 'correct';document.getElementById('test-' + testNumber).classList.remove(previousClass);document.getElementById('test-' + testNumber).classList.add(currentClass);}function logWhenFramesStart(testNumber, cb) {requestAnimationFrame(() => {updateTestResult(testNumber, 'frame 1 started');requestAnimationFrame(() => {updateTestResult(testNumber, 'frame 2 started');requestAnimationFrame(() => {updateTestResult(testNumber, 'frame 3 started... we stop counting now.');cb();});});});}// push in results when we run the testconst latestResults = [// test 1[],// test 2[],// test 3[],// test 4[],// test 5[],];const expectedResults = [// test 1['scheduled Cb1','frame 1 started','cb1 called with argument of false','frame 2 started','frame 3 started... we stop counting now.',],// test 2['scheduled CbA','scheduled CbB','frame 1 started','cbA called with argument of false','cbB called with argument of false','frame 2 started','frame 3 started... we stop counting now.',],// test 3['scheduled CbA','scheduled CbB','frame 1 started','scheduled CbA again','cbA0 called with argument of false','cbB called with argument of false','cbA1 called with argument of false','frame 2 started','frame 3 started... we stop counting now.',],// test 4['scheduled cbA','scheduled cbB','scheduled cbC','scheduled cbD','frame 1 started','cbC called with argument of {"didTimeout":true}','cbA called with argument of false','cbA running and taking some time','frame 2 started','cbB called with argument of false','cbD called with argument of false','frame 3 started... we stop counting now.',],// test 5[// ... TODO],[],[],// Test 8['Queue size: 0.','Pausing... press continue to resume.','Queue size: 2.','Finishing...','Done!',],// test 9['Forcing new frame times...','Using new frame time!','Using new frame time!','Finished!',],// test 10['Running work for 10 seconds...','Ran scheduled work for >99% of the time.',],// test 11['Running work for 10 seconds...','Ran scheduled work for >95% of the time.',],];function runTestOne() {// Test 1// Calls the callback with the frame when not blockedclearTestResult(1);const test1Log = [];const cb1Arguments = [];const cb1 = (x) => {updateTestResult(1, 'cb1 called with argument of ' + JSON.stringify(x));}scheduleCallback(NormalPriority, cb1);updateTestResult(1, 'scheduled Cb1');logWhenFramesStart(1, () => {displayTestResult(1);checkTestResult(1);});};function runTestTwo() {// Test 2// accepts multiple callbacks and calls within frame when not blockedclearTestResult(2);const cbA = (x) => {updateTestResult(2, 'cbA called with argument of ' + JSON.stringify(x));}const cbB = (x) => {updateTestResult(2, 'cbB called with argument of ' + JSON.stringify(x));}scheduleCallback(NormalPriority, cbA);updateTestResult(2, 'scheduled CbA');scheduleCallback(NormalPriority, cbB);updateTestResult(2, 'scheduled CbB');logWhenFramesStart(2, () => {displayTestResult(2);checkTestResult(2);});}function runTestThree() {// Test 3// Schedules callbacks in correct order when they use scheduleCallback to schedule themselvesclearTestResult(3);let callbackAIterations = 0;const cbA = (x) => {if (callbackAIterations < 1) {scheduleCallback(NormalPriority, cbA);updateTestResult(3, 'scheduled CbA again');}updateTestResult(3, 'cbA' + callbackAIterations + ' called with argument of ' + JSON.stringify(x));callbackAIterations++;}const cbB = (x) => {updateTestResult(3, 'cbB called with argument of ' + JSON.stringify(x));}scheduleCallback(NormalPriority, cbA);updateTestResult(3, 'scheduled CbA');scheduleCallback(NormalPriority, cbB);updateTestResult(3, 'scheduled CbB');logWhenFramesStart(3, () => {displayTestResult(3);checkTestResult(3);});}function waitForTimeToPass(timeInMs) {const startTime = Date.now();const endTime = startTime + timeInMs;while (Date.now() < endTime) {// wait...}}function runTestFour() {// Test 4// Calls timed out callbacks and then any more pending callbacks, defers others if time runs outclearTestResult(4);const cbA = (x) => {updateTestResult(4, 'cbA called with argument of ' + JSON.stringify(x));updateTestResult(4, 'cbA running and taking some time');waitForTimeToPass(35);}const cbB = (x) => {updateTestResult(4, 'cbB called with argument of ' + JSON.stringify(x));}const cbC = (x) => {updateTestResult(4, 'cbC called with argument of ' + JSON.stringify(x));}const cbD = (x) => {updateTestResult(4, 'cbD called with argument of ' + JSON.stringify(x));}scheduleCallback(NormalPriority, cbA); // won't time outupdateTestResult(4, 'scheduled cbA');scheduleCallback(NormalPriority, cbB, {timeout: 100}); // times out laterupdateTestResult(4, 'scheduled cbB');scheduleCallback(NormalPriority, cbC, {timeout: 1}); // will time out fastupdateTestResult(4, 'scheduled cbC');scheduleCallback(NormalPriority, cbD); // won't time outupdateTestResult(4, 'scheduled cbD');// should have run in order of C, A, B, DlogWhenFramesStart(4, () => {displayTestResult(4);checkTestResult(4);});}// Error handlingfunction runTestFive() {// Test 5// When some callbacks throw errors, still calls them all within the same frameconst cbA = (x) => {console.log('cbA called with argument of ' + JSON.stringify(x));}const cbB = (x) => {console.log('cbB called with argument of ' + JSON.stringify(x));console.log('cbB is about to throw an error!');throw new Error('error B');}const cbC = (x) => {console.log('cbC called with argument of ' + JSON.stringify(x));}const cbD = (x) => {console.log('cbD called with argument of ' + JSON.stringify(x));console.log('cbD is about to throw an error!');throw new Error('error D');}const cbE = (x) => {console.log('cbE called with argument of ' + JSON.stringify(x));console.log('This was the last callback! ------------------');}console.log('We are aiming to roughly emulate the way ' +'`requestAnimationFrame` handles errors from callbacks.');console.log('about to run the simulation of what it should look like...:');requestAnimationFrame(() => {console.log('frame 1 started');requestAnimationFrame(() => {console.log('frame 2 started');requestAnimationFrame(() => {console.log('frame 3 started... we stop counting now.');console.log('about to wait a moment and start this again but ' +'with the scheduler instead of requestAnimationFrame');setTimeout(runSchedulerCode, 1000);});});});requestAnimationFrame(cbA);console.log('scheduled cbA');requestAnimationFrame(cbB); // will throw errorconsole.log('scheduled cbB');requestAnimationFrame(cbC);console.log('scheduled cbC');requestAnimationFrame(cbD); // will throw errorconsole.log('scheduled cbD');requestAnimationFrame(cbE);console.log('scheduled cbE');function runSchedulerCode() {console.log('-------------------------------------------------------------');console.log('now lets see what it looks like using the scheduler...:');requestAnimationFrame(() => {console.log('frame 1 started');requestAnimationFrame(() => {console.log('frame 2 started');requestAnimationFrame(() => {console.log('frame 3 started... we stop counting now.');});});});scheduleCallback(NormalPriority, cbA);console.log('scheduled cbA');scheduleCallback(NormalPriority, cbB); // will throw errorconsole.log('scheduled cbB');scheduleCallback(NormalPriority, cbC);console.log('scheduled cbC');scheduleCallback(NormalPriority, cbD); // will throw errorconsole.log('scheduled cbD');scheduleCallback(NormalPriority, cbE);console.log('scheduled cbE');};}function runTestSix() {// Test 6// When some callbacks throw errors, still calls them all within the same frameconst cbA = (x) => {console.log('cbA called with argument of ' + JSON.stringify(x));console.log('cbA is about to throw an error!');throw new Error('error A');}const cbB = (x) => {console.log('cbB called with argument of ' + JSON.stringify(x));}const cbC = (x) => {console.log('cbC called with argument of ' + JSON.stringify(x));}const cbD = (x) => {console.log('cbD called with argument of ' + JSON.stringify(x));console.log('cbD is about to throw an error!');throw new Error('error D');}const cbE = (x) => {console.log('cbE called with argument of ' + JSON.stringify(x));console.log('This was the last callback! ------------------');}console.log('We are aiming to roughly emulate the way ' +'`requestAnimationFrame` handles errors from callbacks.');console.log('about to run the simulation of what it should look like...:');requestAnimationFrame(() => {console.log('frame 1 started');requestAnimationFrame(() => {console.log('frame 2 started');requestAnimationFrame(() => {console.log('frame 3 started... we stop counting now.');console.log('about to wait a moment and start this again but ' +'with the scheduler instead of requestAnimationFrame');setTimeout(runSchedulerCode, 1000);});});});requestAnimationFrame(cbC);console.log('scheduled cbC first; simulating timing out');requestAnimationFrame(cbD); // will throw errorconsole.log('scheduled cbD first; simulating timing out');requestAnimationFrame(cbE);console.log('scheduled cbE first; simulating timing out');requestAnimationFrame(cbA);console.log('scheduled cbA'); // will throw errorrequestAnimationFrame(cbB);console.log('scheduled cbB');function runSchedulerCode() {console.log('-------------------------------------------------------------');console.log('now lets see what it looks like using the scheduler...:');requestAnimationFrame(() => {console.log('frame 1 started');requestAnimationFrame(() => {console.log('frame 2 started');requestAnimationFrame(() => {console.log('frame 3 started... we stop counting now.');});});});scheduleCallback(NormalPriority, cbA);console.log('scheduled cbA');scheduleCallback(NormalPriority, cbB); // will throw errorconsole.log('scheduled cbB');scheduleCallback(NormalPriority, cbC, {timeout: 1});console.log('scheduled cbC');scheduleCallback(NormalPriority, cbD, {timeout: 1}); // will throw errorconsole.log('scheduled cbD');scheduleCallback(NormalPriority, cbE, {timeout: 1});console.log('scheduled cbE');};}function runTestSeven() {// Test 7// Calls callbacks, continues calling them even when this tab is in the// backgroundclearTestResult(7);let counter = -1;function incrementCounterAndScheduleNextCallback() {const counterNode = document.getElementById('test-7');counter++;counterNode.innerHTML = counter;waitForTimeToPass(100);scheduleCallback(NormalPriority, incrementCounterAndScheduleNextCallback);}scheduleCallback(NormalPriority, incrementCounterAndScheduleNextCallback);}function runTestEight() {// Test 8// Pauses execution, dumps the queue, and continues executionclearTestResult(8);function countNodesInStack(firstCallbackNode) {var node = firstCallbackNode;var count = 0;if (node !== null) {do {count = count + 1;node = node.next;} while (node !== firstCallbackNode);}return count;}scheduleCallback(NormalPriority, () => {// size should be 0updateTestResult(8, `Queue size: ${countNodesInStack(getFirstCallbackNode())}.`);updateTestResult(8, 'Pausing... press continue to resume.');pauseExecution();scheduleCallback(NormalPriority, function () {updateTestResult(8, 'Finishing...');displayTestResult(8);})scheduleCallback(NormalPriority, function () {updateTestResult(8, 'Done!');displayTestResult(8);checkTestResult(8);})// new size should be 2 nowupdateTestResult(8, `Queue size: ${countNodesInStack(getFirstCallbackNode())}.`);displayTestResult(8);});}function continueTestEight() {continueExecution();}function runTestNine() {clearTestResult(9);// We have this to make sure that the thing that goes right after it can get a full framevar forceFrameFinish = () => {while (!shouldYield()) {waitForTimeToPass(1);}waitForTimeToPass(100);}scheduleCallback(NormalPriority, forceFrameFinish);scheduleCallback(NormalPriority, () => {var startTime = now();while (!shouldYield()) {}var initialFrameTime = now() - startTime;var newFrameTime = (initialFrameTime * 2) > 60 ? (initialFrameTime * 2) : 60;var newFrameRate = Math.floor(1000/newFrameTime);updateTestResult(9, `Forcing new frame times...`);displayTestResult(9);forceFrameRate(newFrameRate);var toSchedule = (again) => {var startTime = now();while (!shouldYield()) {}var frameTime = now() - startTime;if (frameTime >= (newFrameTime-8)) {updateTestResult(9, `Using new frame time!`);} else {updateTestResult(9, `Failed to use new frame time. (off by ${newFrameTime - frameTime}ms)`);}displayTestResult(9);if (again) {scheduleCallback(NormalPriority, forceFrameFinish);scheduleCallback(NormalPriority, () => {toSchedule(false);});} else {updateTestResult(9, `Finished!`);forceFrameRate(0);displayTestResult(9);checkTestResult(9);}}scheduleCallback(NormalPriority, forceFrameFinish);scheduleCallback(NormalPriority, () => {toSchedule(true);});});}function runTestTen() {clearTestResult(10);updateTestResult(10, `Running work for 10 seconds...`);var testStartTime = now();var accumulatedWork = 0function loop() {var startTime = now();while (!shouldYield()) {}var endTime = now();accumulatedWork += endTime - startTime;var runTime = endTime - testStartTime;if (runTime > 10000) {updateTestResult(10, `Ran scheduled work for ${(100 * accumulatedWork / runTime).toFixed(2)}% of the time.`);displayTestResult(10);return;}scheduleCallback(NormalPriority, loop);}scheduleCallback(NormalPriority, loop);}function runTestEleven() {clearTestResult(11);updateTestResult(11, `Running work for 10 seconds...`);var testStartTime = now();var lastInsertion = 0;var accumulatedWork = 0function loop() {var startTime = now();var timeSinceLastDOMInteraction = startTime - lastInsertion;if (timeSinceLastDOMInteraction > 15) {lastInsertion = startTime;var node = document.createElement('div');node.textContent = startTime;document.body.appendChild(node);document.body.clientHeight; // force layout}while (!shouldYield()) {}var endTime = now();accumulatedWork += endTime - startTime;var runTime = endTime - testStartTime;if (runTime > 10000) {updateTestResult(11, `Ran scheduled work for ${(100 * accumulatedWork / runTime).toFixed(2)}% of the time.`);displayTestResult(11);return;}scheduleCallback(NormalPriority, loop);}scheduleCallback(NormalPriority, loop);}</script type="text/babel">
</body>
</html>