main
html 673 lines 22.2 KB
Raw
1 <!DOCTYPE html>
2 <html style="width: 100%; height: 100%;">
3
4 <head>
5 <meta charset="utf-8">
6 <title>Scheduler Test Page</title>
7 <style>
8 .correct {
9 border: solid green 2px;
10 }
11 .incorrect {
12 border: dashed red 2px;
13 }
14 </style>
15 </head>
16
17 <body>
18 <h1>Scheduler Fixture</h1>
19 <p>
20 This fixture is for manual testing purposes, and the patterns used in
21 implementing it should not be used as a model. This is mainly for anyone
22 working on making changes to the `schedule` module.
23 </p>
24 <h2>Tests:</h2>
25 <ol>
26 <li>
27 <button onClick="runTestOne()">Run Test 1</button>
28 <p>Calls the callback within the frame when not blocked:</p>
29 <div><b>Expected:</b></div>
30 <div id="test-1-expected">
31 </div>
32 <div> -------------------------------------------------</div>
33 <div> If you see the same above and below it's correct.
34 <div> -------------------------------------------------</div>
35 <div><b>Actual:</b></div>
36 <div id="test-1"></div>
37 </li>
38 <li>
39 <p>Accepts multiple callbacks and calls within frame when not blocked</p>
40 <button onClick="runTestTwo()">Run Test 2</button>
41 <div><b>Expected:</b></div>
42 <div id="test-2-expected">
43 </div>
44 <div> -------------------------------------------------</div>
45 <div> If you see the same above and below it's correct.
46 <div> -------------------------------------------------</div>
47 <div><b>Actual:</b></div>
48 <div id="test-2"></div>
49 </li>
50 <li>
51 <p>Schedules callbacks in correct order when they use scheduleCallback to schedule themselves</p>
52 <button onClick="runTestThree()">Run Test 3</button>
53 <div><b>Expected:</b></div>
54 <div id="test-3-expected">
55 </div>
56 <div> -------------------------------------------------</div>
57 <div> If you see the same above and below it's correct.
58 <div> -------------------------------------------------</div>
59 <div><b>Actual:</b></div>
60 <div id="test-3"></div>
61 </li>
62 <li>
63 <p>Calls timed out callbacks and then any more pending callbacks, defers others if time runs out</p>
64 <button onClick="runTestFour()">Run Test 4</button>
65 <div><b>Expected:</b></div>
66 <div id="test-4-expected">
67 </div>
68 <div> -------------------------------------------------</div>
69 <div> If you see the same above and below it's correct.
70 <div> -------------------------------------------------</div>
71 <div><b>Actual:</b></div>
72 <div id="test-4"></div>
73 </li>
74 <li>
75 <p>When some callbacks throw errors, still calls them all within the same frame</p>
76 <p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
77 <button onClick="runTestFive()">Run Test 5</button>
78 </li>
79 <li>
80 <p>When some callbacks throw errors <b> and some also time out</b>, still calls them all within the same frame</p>
81 <p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
82 <button onClick="runTestSix()">Run Test 6</button>
83 </li>
84 <li>
85 <p>Continues calling callbacks even when user switches away from this tab</p>
86 <button onClick="runTestSeven()">Run Test 7</button>
87 <div><b>Click the button above, observe the counter, then switch to
88 another tab and switch back:</b></div>
89 <div id="test-7">
90 </div>
91 <div> If the counter advanced while you were away from this tab, it's correct.</div>
92 </li>
93 <li>
94 <p>Test Eight Removed</p>
95 <p>Test 8 was removed because it was testing a feature that was removed from the scheduler.</p>
96 </li>
97 <li>
98 <p>Can force a specific framerate</p>
99 <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>
100 <button onClick="runTestNine()">Run Test 9</button>
101 <div><b>Expected:</b></div>
102 <div id="test-9-expected">
103 </div>
104 <div> -------------------------------------------------</div>
105 <div> If you see the same above and below it's correct.
106 <div> -------------------------------------------------</div>
107 <div><b>Actual:</b></div>
108 <div id="test-9"></div>
109 </div>
110 </li>
111 <li>
112 <p>Runs scheduled JS work for 99% of the frame time when nothing else is using the thread.</p>
113 <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>
114 <button onClick="runTestTen()">Run Test 10</button>
115 <div><b>Expected:</b></div>
116 <div id="test-10-expected">
117 </div>
118 <div> -------------------------------------------------</div>
119 <div> If you see the same above and below it's correct.
120 <div> -------------------------------------------------</div>
121 <div><b>Actual:</b></div>
122 <div id="test-10"></div>
123 </div>
124 </li>
125 <li>
126 <p>Runs scheduled JS work more than 95% of the frame time when inserting DOM nodes.</p>
127 <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>
128 <button onClick="runTestEleven()">Run Test 11</button>
129 <div><b>Expected:</b></div>
130 <div id="test-11-expected">
131 </div>
132 <div> -------------------------------------------------</div>
133 <div> If you see the same above and below it's correct.
134 <div> -------------------------------------------------</div>
135 <div><b>Actual:</b></div>
136 <div id="test-11"></div>
137 </div>
138 </li>
139 </ol>
140 <script src="../../build/oss-experimental/react/umd/react.production.min.js"></script>
141 <script src="../../build/oss-experimental/scheduler/umd/scheduler.production.min.js"></script>
142 <script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
143 <script type="text/babel">
144 const {
145 unstable_scheduleCallback: scheduleCallback,
146 unstable_cancelCallback: cancelCallback,
147 unstable_now: now,
148 unstable_forceFrameRate: forceFrameRate,
149 unstable_shouldYield: shouldYield,
150 unstable_NormalPriority: NormalPriority,
151 } = Scheduler;
152 function displayTestResult(testNumber) {
153 const expectationNode = document.getElementById('test-' + testNumber + '-expected');
154 const resultNode = document.getElementById('test-' + testNumber);
155 resultNode.innerHTML = latestResults[testNumber - 1].join('<br />');
156 expectationNode.innerHTML = expectedResults[testNumber - 1].join('<br />');
157 }
158 function clearTestResult(testNumber) {
159 const resultNode = document.getElementById('test-' + testNumber);
160 resultNode.innerHTML = '';
161 latestResults[testNumber - 1] = [];
162 }
163 function updateTestResult(testNumber, textToAddToResult) {
164 latestResults[testNumber - 1].push(textToAddToResult);
165 };
166 function checkTestResult(testNumber) {
167
168 let correct = true;
169 const expected = expectedResults[testNumber - 1]; // zero indexing
170 const result = latestResults[testNumber - 1]; // zero indexing
171 if (expected.length !== result.length) {
172 correct = false;
173 } else {
174 for (let i = 0, len = expected.length; i < len; i++) {
175 if (expected[i] !== result[i]) {
176 correct = false;
177 break;
178 }
179 }
180 }
181 const currentClass = correct ? 'correct' : 'incorrect';
182 const previousClass = correct ? 'incorrect' : 'correct';
183 document.getElementById('test-' + testNumber).classList.remove(previousClass);
184 document.getElementById('test-' + testNumber).classList.add(currentClass);
185 }
186 function logWhenFramesStart(testNumber, cb) {
187 requestAnimationFrame(() => {
188 updateTestResult(testNumber, 'frame 1 started');
189 requestAnimationFrame(() => {
190 updateTestResult(testNumber, 'frame 2 started');
191 requestAnimationFrame(() => {
192 updateTestResult(testNumber, 'frame 3 started... we stop counting now.');
193 cb();
194 });
195 });
196 });
197 }
198 // push in results when we run the test
199 const latestResults = [
200 // test 1
201 [
202 ],
203 // test 2
204 [
205 ],
206 // test 3
207 [
208 ],
209 // test 4
210 [
211 ],
212 // test 5
213 [
214 ],
215 ];
216
217 const expectedResults = [
218 // test 1
219 [
220 'scheduled Cb1',
221 'frame 1 started',
222 'cb1 called with argument of false',
223 'frame 2 started',
224 'frame 3 started... we stop counting now.',
225 ],
226 // test 2
227 [
228 'scheduled CbA',
229 'scheduled CbB',
230 'frame 1 started',
231 'cbA called with argument of false',
232 'cbB called with argument of false',
233 'frame 2 started',
234 'frame 3 started... we stop counting now.',
235 ],
236 // test 3
237 [
238 'scheduled CbA',
239 'scheduled CbB',
240 'frame 1 started',
241 'scheduled CbA again',
242 'cbA0 called with argument of false',
243 'cbB called with argument of false',
244 'cbA1 called with argument of false',
245 'frame 2 started',
246 'frame 3 started... we stop counting now.',
247 ],
248 // test 4
249 [
250 'scheduled cbA',
251 'scheduled cbB',
252 'scheduled cbC',
253 'scheduled cbD',
254 'frame 1 started',
255 'cbC called with argument of {"didTimeout":true}',
256 'cbA called with argument of false',
257 'cbA running and taking some time',
258 'frame 2 started',
259 'cbB called with argument of false',
260 'cbD called with argument of false',
261 'frame 3 started... we stop counting now.',
262 ],
263 // test 5
264 [
265 // ... TODO
266 ],
267 [],
268 [],
269 // Test 8
270 [
271 'Queue size: 0.',
272 'Pausing... press continue to resume.',
273 'Queue size: 2.',
274 'Finishing...',
275 'Done!',
276 ],
277 // test 9
278 [
279 'Forcing new frame times...',
280 'Using new frame time!',
281 'Using new frame time!',
282 'Finished!',
283 ],
284 // test 10
285 [
286 'Running work for 10 seconds...',
287 'Ran scheduled work for >99% of the time.',
288 ],
289 // test 11
290 [
291 'Running work for 10 seconds...',
292 'Ran scheduled work for >95% of the time.',
293 ],
294 ];
295 function runTestOne() {
296 // Test 1
297 // Calls the callback with the frame when not blocked
298 clearTestResult(1);
299 const test1Log = [];
300 const cb1Arguments = [];
301 const cb1 = (x) => {
302 updateTestResult(1, 'cb1 called with argument of ' + JSON.stringify(x));
303 }
304 scheduleCallback(NormalPriority, cb1);
305 updateTestResult(1, 'scheduled Cb1');
306 logWhenFramesStart(1, () => {
307 displayTestResult(1);
308 checkTestResult(1);
309 });
310 };
311
312 function runTestTwo() {
313 // Test 2
314 // accepts multiple callbacks and calls within frame when not blocked
315 clearTestResult(2);
316 const cbA = (x) => {
317 updateTestResult(2, 'cbA called with argument of ' + JSON.stringify(x));
318 }
319 const cbB = (x) => {
320 updateTestResult(2, 'cbB called with argument of ' + JSON.stringify(x));
321 }
322 scheduleCallback(NormalPriority, cbA);
323 updateTestResult(2, 'scheduled CbA');
324 scheduleCallback(NormalPriority, cbB);
325 updateTestResult(2, 'scheduled CbB');
326 logWhenFramesStart(2, () => {
327 displayTestResult(2);
328 checkTestResult(2);
329 });
330 }
331
332 function runTestThree() {
333 // Test 3
334 // Schedules callbacks in correct order when they use scheduleCallback to schedule themselves
335 clearTestResult(3);
336 let callbackAIterations = 0;
337 const cbA = (x) => {
338 if (callbackAIterations < 1) {
339 scheduleCallback(NormalPriority, cbA);
340 updateTestResult(3, 'scheduled CbA again');
341 }
342 updateTestResult(3, 'cbA' + callbackAIterations + ' called with argument of ' + JSON.stringify(x));
343 callbackAIterations++;
344 }
345 const cbB = (x) => {
346 updateTestResult(3, 'cbB called with argument of ' + JSON.stringify(x));
347 }
348 scheduleCallback(NormalPriority, cbA);
349 updateTestResult(3, 'scheduled CbA');
350 scheduleCallback(NormalPriority, cbB);
351 updateTestResult(3, 'scheduled CbB');
352 logWhenFramesStart(3, () => {
353 displayTestResult(3);
354 checkTestResult(3);
355 });
356 }
357
358 function waitForTimeToPass(timeInMs) {
359 const startTime = Date.now();
360 const endTime = startTime + timeInMs;
361 while (Date.now() < endTime) {
362 // wait...
363 }
364 }
365
366 function runTestFour() {
367 // Test 4
368 // Calls timed out callbacks and then any more pending callbacks, defers others if time runs out
369 clearTestResult(4);
370 const cbA = (x) => {
371 updateTestResult(4, 'cbA called with argument of ' + JSON.stringify(x));
372 updateTestResult(4, 'cbA running and taking some time');
373 waitForTimeToPass(35);
374 }
375 const cbB = (x) => {
376 updateTestResult(4, 'cbB called with argument of ' + JSON.stringify(x));
377 }
378 const cbC = (x) => {
379 updateTestResult(4, 'cbC called with argument of ' + JSON.stringify(x));
380 }
381 const cbD = (x) => {
382 updateTestResult(4, 'cbD called with argument of ' + JSON.stringify(x));
383 }
384 scheduleCallback(NormalPriority, cbA); // won't time out
385 updateTestResult(4, 'scheduled cbA');
386 scheduleCallback(NormalPriority, cbB, {timeout: 100}); // times out later
387 updateTestResult(4, 'scheduled cbB');
388 scheduleCallback(NormalPriority, cbC, {timeout: 1}); // will time out fast
389 updateTestResult(4, 'scheduled cbC');
390 scheduleCallback(NormalPriority, cbD); // won't time out
391 updateTestResult(4, 'scheduled cbD');
392
393 // should have run in order of C, A, B, D
394
395 logWhenFramesStart(4, () => {
396 displayTestResult(4);
397 checkTestResult(4);
398 });
399
400 }
401
402 // Error handling
403
404 function runTestFive() {
405 // Test 5
406 // When some callbacks throw errors, still calls them all within the same frame
407 const cbA = (x) => {
408 console.log('cbA called with argument of ' + JSON.stringify(x));
409 }
410 const cbB = (x) => {
411 console.log('cbB called with argument of ' + JSON.stringify(x));
412 console.log('cbB is about to throw an error!');
413 throw new Error('error B');
414 }
415 const cbC = (x) => {
416 console.log('cbC called with argument of ' + JSON.stringify(x));
417 }
418 const cbD = (x) => {
419 console.log('cbD called with argument of ' + JSON.stringify(x));
420 console.log('cbD is about to throw an error!');
421 throw new Error('error D');
422 }
423 const cbE = (x) => {
424 console.log('cbE called with argument of ' + JSON.stringify(x));
425 console.log('This was the last callback! ------------------');
426 }
427
428 console.log('We are aiming to roughly emulate the way ' +
429 '`requestAnimationFrame` handles errors from callbacks.');
430
431 console.log('about to run the simulation of what it should look like...:');
432
433 requestAnimationFrame(() => {
434 console.log('frame 1 started');
435 requestAnimationFrame(() => {
436 console.log('frame 2 started');
437 requestAnimationFrame(() => {
438 console.log('frame 3 started... we stop counting now.');
439 console.log('about to wait a moment and start this again but ' +
440 'with the scheduler instead of requestAnimationFrame');
441 setTimeout(runSchedulerCode, 1000);
442 });
443 });
444 });
445 requestAnimationFrame(cbA);
446 console.log('scheduled cbA');
447 requestAnimationFrame(cbB); // will throw error
448 console.log('scheduled cbB');
449 requestAnimationFrame(cbC);
450 console.log('scheduled cbC');
451 requestAnimationFrame(cbD); // will throw error
452 console.log('scheduled cbD');
453 requestAnimationFrame(cbE);
454 console.log('scheduled cbE');
455
456
457 function runSchedulerCode() {
458 console.log('-------------------------------------------------------------');
459 console.log('now lets see what it looks like using the scheduler...:');
460 requestAnimationFrame(() => {
461 console.log('frame 1 started');
462 requestAnimationFrame(() => {
463 console.log('frame 2 started');
464 requestAnimationFrame(() => {
465 console.log('frame 3 started... we stop counting now.');
466 });
467 });
468 });
469 scheduleCallback(NormalPriority, cbA);
470 console.log('scheduled cbA');
471 scheduleCallback(NormalPriority, cbB); // will throw error
472 console.log('scheduled cbB');
473 scheduleCallback(NormalPriority, cbC);
474 console.log('scheduled cbC');
475 scheduleCallback(NormalPriority, cbD); // will throw error
476 console.log('scheduled cbD');
477 scheduleCallback(NormalPriority, cbE);
478 console.log('scheduled cbE');
479 };
480 }
481
482 function runTestSix() {
483 // Test 6
484 // When some callbacks throw errors, still calls them all within the same frame
485 const cbA = (x) => {
486 console.log('cbA called with argument of ' + JSON.stringify(x));
487 console.log('cbA is about to throw an error!');
488 throw new Error('error A');
489 }
490 const cbB = (x) => {
491 console.log('cbB called with argument of ' + JSON.stringify(x));
492 }
493 const cbC = (x) => {
494 console.log('cbC called with argument of ' + JSON.stringify(x));
495 }
496 const cbD = (x) => {
497 console.log('cbD called with argument of ' + JSON.stringify(x));
498 console.log('cbD is about to throw an error!');
499 throw new Error('error D');
500 }
501 const cbE = (x) => {
502 console.log('cbE called with argument of ' + JSON.stringify(x));
503 console.log('This was the last callback! ------------------');
504 }
505
506 console.log('We are aiming to roughly emulate the way ' +
507 '`requestAnimationFrame` handles errors from callbacks.');
508
509 console.log('about to run the simulation of what it should look like...:');
510
511 requestAnimationFrame(() => {
512 console.log('frame 1 started');
513 requestAnimationFrame(() => {
514 console.log('frame 2 started');
515 requestAnimationFrame(() => {
516 console.log('frame 3 started... we stop counting now.');
517 console.log('about to wait a moment and start this again but ' +
518 'with the scheduler instead of requestAnimationFrame');
519 setTimeout(runSchedulerCode, 1000);
520 });
521 });
522 });
523 requestAnimationFrame(cbC);
524 console.log('scheduled cbC first; simulating timing out');
525 requestAnimationFrame(cbD); // will throw error
526 console.log('scheduled cbD first; simulating timing out');
527 requestAnimationFrame(cbE);
528 console.log('scheduled cbE first; simulating timing out');
529 requestAnimationFrame(cbA);
530 console.log('scheduled cbA'); // will throw error
531 requestAnimationFrame(cbB);
532 console.log('scheduled cbB');
533
534
535 function runSchedulerCode() {
536 console.log('-------------------------------------------------------------');
537 console.log('now lets see what it looks like using the scheduler...:');
538 requestAnimationFrame(() => {
539 console.log('frame 1 started');
540 requestAnimationFrame(() => {
541 console.log('frame 2 started');
542 requestAnimationFrame(() => {
543 console.log('frame 3 started... we stop counting now.');
544 });
545 });
546 });
547 scheduleCallback(NormalPriority, cbA);
548 console.log('scheduled cbA');
549 scheduleCallback(NormalPriority, cbB); // will throw error
550 console.log('scheduled cbB');
551 scheduleCallback(NormalPriority, cbC, {timeout: 1});
552 console.log('scheduled cbC');
553 scheduleCallback(NormalPriority, cbD, {timeout: 1}); // will throw error
554 console.log('scheduled cbD');
555 scheduleCallback(NormalPriority, cbE, {timeout: 1});
556 console.log('scheduled cbE');
557 };
558 }
559
560 function runTestSeven() {
561 // Test 7
562 // Calls callbacks, continues calling them even when this tab is in the
563 // background
564 clearTestResult(7);
565 let counter = -1;
566 function incrementCounterAndScheduleNextCallback() {
567 const counterNode = document.getElementById('test-7');
568 counter++;
569 counterNode.innerHTML = counter;
570 waitForTimeToPass(100);
571 scheduleCallback(NormalPriority, incrementCounterAndScheduleNextCallback);
572 }
573 scheduleCallback(NormalPriority, incrementCounterAndScheduleNextCallback);
574 }
575
576 function runTestNine() {
577 clearTestResult(9);
578 // We have this to make sure that the thing that goes right after it can get a full frame
579 var forceFrameFinish = () => {
580 while (!shouldYield()) {
581 waitForTimeToPass(1);
582 }
583 waitForTimeToPass(100);
584 }
585 scheduleCallback(NormalPriority, forceFrameFinish);
586 scheduleCallback(NormalPriority, () => {
587 var startTime = now();
588 while (!shouldYield()) {}
589 var initialFrameTime = now() - startTime;
590 var newFrameTime = (initialFrameTime * 2) > 60 ? (initialFrameTime * 2) : 60;
591 var newFrameRate = Math.floor(1000/newFrameTime);
592 updateTestResult(9, `Forcing new frame times...`);
593 displayTestResult(9);
594 forceFrameRate(newFrameRate);
595 var toSchedule = (again) => {
596 var startTime = now();
597 while (!shouldYield()) {}
598 var frameTime = now() - startTime;
599 if (frameTime >= (newFrameTime-8)) {
600 updateTestResult(9, `Using new frame time!`);
601 } else {
602 updateTestResult(9, `Failed to use new frame time. (off by ${newFrameTime - frameTime}ms)`);
603 }
604 displayTestResult(9);
605 if (again) {
606 scheduleCallback(NormalPriority, forceFrameFinish);
607 scheduleCallback(NormalPriority, () => {toSchedule(false);});
608 } else {
609 updateTestResult(9, `Finished!`);
610 forceFrameRate(0);
611 displayTestResult(9);
612 checkTestResult(9);
613 }
614 }
615 scheduleCallback(NormalPriority, forceFrameFinish);
616 scheduleCallback(NormalPriority, () => {toSchedule(true);});
617 });
618 }
619
620 function runTestTen() {
621 clearTestResult(10);
622 updateTestResult(10, `Running work for 10 seconds...`);
623 var testStartTime = now();
624 var accumulatedWork = 0
625 function loop() {
626 var startTime = now();
627 while (!shouldYield()) {}
628 var endTime = now();
629 accumulatedWork += endTime - startTime;
630 var runTime = endTime - testStartTime;
631 if (runTime > 10000) {
632 updateTestResult(10, `Ran scheduled work for ${(100 * accumulatedWork / runTime).toFixed(2)}% of the time.`);
633 displayTestResult(10);
634 return;
635 }
636 scheduleCallback(NormalPriority, loop);
637 }
638 scheduleCallback(NormalPriority, loop);
639 }
640
641 function runTestEleven() {
642 clearTestResult(11);
643 updateTestResult(11, `Running work for 10 seconds...`);
644 var testStartTime = now();
645 var lastInsertion = 0;
646 var accumulatedWork = 0
647 function loop() {
648 var startTime = now();
649 var timeSinceLastDOMInteraction = startTime - lastInsertion;
650 if (timeSinceLastDOMInteraction > 15) {
651 lastInsertion = startTime;
652 var node = document.createElement('div');
653 node.textContent = startTime;
654 document.body.appendChild(node);
655 document.body.clientHeight; // force layout
656 }
657 while (!shouldYield()) {}
658 var endTime = now();
659 accumulatedWork += endTime - startTime;
660 var runTime = endTime - testStartTime;
661 if (runTime > 10000) {
662 updateTestResult(11, `Ran scheduled work for ${(100 * accumulatedWork / runTime).toFixed(2)}% of the time.`);
663 displayTestResult(11);
664 return;
665 }
666 scheduleCallback(NormalPriority, loop);
667 }
668 scheduleCallback(NormalPriority, loop);
669 }
670
671 </script type="text/babel">
672 </body>
673 </html>