diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/scheduler | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/scheduler')
34 files changed, 1282 insertions, 0 deletions
diff --git a/testing/web-platform/tests/scheduler/META.yml b/testing/web-platform/tests/scheduler/META.yml new file mode 100644 index 0000000000..ef9511daa4 --- /dev/null +++ b/testing/web-platform/tests/scheduler/META.yml @@ -0,0 +1,4 @@ +spec: https://wicg.github.io/scheduling-apis/ +suggested_reviewers: + - shaseley + - natechapin diff --git a/testing/web-platform/tests/scheduler/WEB_FEATURES.yml b/testing/web-platform/tests/scheduler/WEB_FEATURES.yml new file mode 100644 index 0000000000..4b5218b8b6 --- /dev/null +++ b/testing/web-platform/tests/scheduler/WEB_FEATURES.yml @@ -0,0 +1,3 @@ +features: +- name: scheduler + files: "**" diff --git a/testing/web-platform/tests/scheduler/post-task-abort-reason.any.js b/testing/web-platform/tests/scheduler/post-task-abort-reason.any.js new file mode 100644 index 0000000000..27eff9f080 --- /dev/null +++ b/testing/web-platform/tests/scheduler/post-task-abort-reason.any.js @@ -0,0 +1,37 @@ +// META: title=Scheduler: postTask uses abort reason +// META: global=window,worker +'use strict'; + +promise_test(t => { + const controller = new TaskController(); + const signal = controller.signal; + const reason = new Error("Custom Abort Error"); + controller.abort(reason); + return promise_rejects_exactly(t, reason, scheduler.postTask(() => {}, {signal})); +}, 'Calling postTask with an aborted TaskSignal rejects the promise with the abort reason'); + +promise_test(t => { + const controller = new AbortController(); + const signal = controller.signal; + const reason = new Error("Custom Abort Error"); + controller.abort(reason); + return promise_rejects_exactly(t, reason, scheduler.postTask(() => {}, {signal})); +}, 'Calling postTask with an aborted AbortSignal rejects the promise with the abort reason'); + +promise_test(t => { + const controller = new TaskController(); + const signal = controller.signal; + const reason = new Error("Custom Abort Error"); + const result = scheduler.postTask(() => {}, {signal}); + controller.abort(reason); + return promise_rejects_exactly(t, reason, result); +}, 'Aborting a TaskSignal rejects the promise of a scheduled task with the abort reason'); + +promise_test(t => { + const reason = new Error("Custom Abort Error"); + const controller = new AbortController(); + const signal = controller.signal; + const result = scheduler.postTask(() => {}, {signal}); + controller.abort(reason); + return promise_rejects_exactly(t, reason, result); +}, 'Aborting an AbortSignal rejects the promise of a scheduled task with the abort reason'); diff --git a/testing/web-platform/tests/scheduler/post-task-delay.any.js b/testing/web-platform/tests/scheduler/post-task-delay.any.js new file mode 100644 index 0000000000..cf96f6703b --- /dev/null +++ b/testing/web-platform/tests/scheduler/post-task-delay.any.js @@ -0,0 +1,11 @@ +// META: title=Scheduler: postTask Delayed Tasks +// META: global=window,worker +'use strict'; + +promise_test(async t => { + const start = performance.now(); + return scheduler.postTask(() => { + const elapsed = performance.now() - start; + assert_greater_than_equal(elapsed, 10); + }, {priority: 'user-blocking', delay: 10}); +}, 'Tests basic scheduler.postTask with a delay'); diff --git a/testing/web-platform/tests/scheduler/post-task-result-success.any.js b/testing/web-platform/tests/scheduler/post-task-result-success.any.js new file mode 100644 index 0000000000..dd73c148e9 --- /dev/null +++ b/testing/web-platform/tests/scheduler/post-task-result-success.any.js @@ -0,0 +1,8 @@ +// META: title=Scheduler: postTask Promise Value +// META: global=window,worker +'use strict'; + +promise_test(async t => { + const result = await scheduler.postTask(() => 1234); + assert_equals(result, 1234); +}, 'Test the task promise is resolved with the callback return value'); diff --git a/testing/web-platform/tests/scheduler/post-task-result-throws.any.js b/testing/web-platform/tests/scheduler/post-task-result-throws.any.js new file mode 100644 index 0000000000..7155c94eac --- /dev/null +++ b/testing/web-platform/tests/scheduler/post-task-result-throws.any.js @@ -0,0 +1,9 @@ +// META: title=Scheduler: postTask Error Propagation +// META: global=window,worker +'use strict'; + +promise_test(t => { + const testError = new Error('Failed'); + const task = scheduler.postTask(() => { throw testError; }); + return promise_rejects_exactly(t, testError, task, 'postTask should propagate the error'); +}, 'Test postTask rejects the associated promise with the callback error'); diff --git a/testing/web-platform/tests/scheduler/post-task-run-order.any.js b/testing/web-platform/tests/scheduler/post-task-run-order.any.js new file mode 100644 index 0000000000..acbe86744c --- /dev/null +++ b/testing/web-platform/tests/scheduler/post-task-run-order.any.js @@ -0,0 +1,21 @@ +// META: title=Scheduler: Tasks Run in Priority Order +// META: global=window,worker + +promise_test(async t => { + const runOrder = []; + const schedule = (id, priority) => scheduler.postTask(() => { runOrder.push(id); }, {priority}); + + // Post tasks in reverse priority order and expect they are run from highest + // to lowest priority. + const tasks = []; + tasks.push(schedule('B1', 'background')); + tasks.push(schedule('B2', 'background')); + tasks.push(schedule('UV1', 'user-visible')); + tasks.push(schedule('UV2', 'user-visible')); + tasks.push(schedule('UB1', 'user-blocking')); + tasks.push(schedule('UB2', 'user-blocking')); + + await Promise.all(tasks); + + assert_equals(runOrder.toString(),'UB1,UB2,UV1,UV2,B1,B2'); +}, 'Test scheduler.postTask task run in priority order'); diff --git a/testing/web-platform/tests/scheduler/post-task-then-detach.html b/testing/web-platform/tests/scheduler/post-task-then-detach.html new file mode 100644 index 0000000000..402f34dc0c --- /dev/null +++ b/testing/web-platform/tests/scheduler/post-task-then-detach.html @@ -0,0 +1,28 @@ +<!doctype html> +<title>Scheduler: postTask in Detached Scheduler</title> +<link rel="author" title="Nate Chapin" href="mailto:japhet@chromium.org"> +<link rel="help" href="https://github.com/WICG/scheduling-apis"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +'use strict'; + +async_test(t => { + onload = () => { + let didRun = false; + const frame = document.body.appendChild(document.createElement('iframe')); + frame.contentWindow.scheduler.postTask(() => { didRun = true; }); + document.body.removeChild(frame); + + // We cannot assume anything about ordering between postTask tasks and + // non-postTask or postTask tasks from different schedulers, so the best we + // can do is give the task time to run. + t.step_timeout(() => { + assert_false(didRun, 'The task should not have run.'); + t.done(); + }, 10); + } +}, 'Test scheduler.postTask() from an iframe that is removed before the task runs'); + +</script> diff --git a/testing/web-platform/tests/scheduler/post-task-with-abort-signal-in-handler.any.js b/testing/web-platform/tests/scheduler/post-task-with-abort-signal-in-handler.any.js new file mode 100644 index 0000000000..1fd416c775 --- /dev/null +++ b/testing/web-platform/tests/scheduler/post-task-with-abort-signal-in-handler.any.js @@ -0,0 +1,20 @@ +// META: title=Scheduler: postTask with a signal and abort the signal when running the callback +// META: global=window,worker +'use strict'; + +promise_test(t => { + const controller = new TaskController(); + const signal = controller.signal; + return promise_rejects_dom(t, 'AbortError', scheduler.postTask(() => { + controller.abort(); + }, { signal })); +}, 'Posting a task with a signal and abort the signal when running the sync callback'); + +promise_test(t => { + const controller = new TaskController(); + const signal = controller.signal; + return scheduler.postTask(async () => { + await new Promise(resolve => t.step_timeout(resolve, 0)); + controller.abort(); + }, { signal }); +}, 'Posting a task with a signal and abort the signal when running the async callback'); diff --git a/testing/web-platform/tests/scheduler/post-task-with-abort-signal.any.js b/testing/web-platform/tests/scheduler/post-task-with-abort-signal.any.js new file mode 100644 index 0000000000..41cbafba90 --- /dev/null +++ b/testing/web-platform/tests/scheduler/post-task-with-abort-signal.any.js @@ -0,0 +1,11 @@ +// META: title=Scheduler: postTask and AbortSignal +// META: global=window,worker +'use strict'; + +promise_test(t => { + const controller = new AbortController(); + const signal = controller.signal; + const taskResult = scheduler.postTask(() => {}, {signal}); + controller.abort(); + return promise_rejects_dom(t, 'AbortError', taskResult); +}, 'Test that scheduler.postTask() accepts an AbortSignal that is not also a TaskSignal'); diff --git a/testing/web-platform/tests/scheduler/post-task-with-aborted-signal.any.js b/testing/web-platform/tests/scheduler/post-task-with-aborted-signal.any.js new file mode 100644 index 0000000000..4e5d42c048 --- /dev/null +++ b/testing/web-platform/tests/scheduler/post-task-with-aborted-signal.any.js @@ -0,0 +1,10 @@ +// META: title=Scheduler: postTask with an Aborted Signal +// META: global=window,worker +'use strict'; + +promise_test(t => { + const controller = new TaskController(); + const signal = controller.signal; + controller.abort(); + return promise_rejects_dom(t, 'AbortError', scheduler.postTask(() => {}, {signal})); +}, 'Posting a task with an aborted signal rejects with an AbortError'); diff --git a/testing/web-platform/tests/scheduler/post-task-with-signal-and-priority.any.js b/testing/web-platform/tests/scheduler/post-task-with-signal-and-priority.any.js new file mode 100644 index 0000000000..ba40d7cf53 --- /dev/null +++ b/testing/web-platform/tests/scheduler/post-task-with-signal-and-priority.any.js @@ -0,0 +1,14 @@ +// META: title=Scheduler: Signal and Priority Combination +// META: global=window,worker +'use strict'; + +promise_test(async t => { + const task1Result = scheduler.postTask(() => 'task1', {priority: 'user-visible'}); + + const controller = new TaskController({priority: 'background'}); + const signal = controller.signal + const task2Result = scheduler.postTask(() => 'task2', {priority: 'user-blocking', signal}); + + const result = await Promise.race([task1Result, task2Result]); + assert_equals('task2', result); +}, 'Test when scheduler.postTask() is passed both a signal and a priority'); diff --git a/testing/web-platform/tests/scheduler/post-task-with-signal-from-detached-iframe.html b/testing/web-platform/tests/scheduler/post-task-with-signal-from-detached-iframe.html new file mode 100644 index 0000000000..acd974cbc7 --- /dev/null +++ b/testing/web-platform/tests/scheduler/post-task-with-signal-from-detached-iframe.html @@ -0,0 +1,32 @@ +<!doctype html> +<title>Scheduler: postTask with Detached Frame's Signal</title> +<link rel="author" title="Nate Chapin" href="mailto:japhet@chromium.org"> +<link rel="help" href="https://github.com/WICG/scheduling-apis"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +'use strict'; + +promise_test(async t => { + await new Promise((resolve) => { + window.addEventListener('load', resolve); + }); + + const frame = document.createElement('iframe'); + frame.srcdoc = ` + <script> + const controller = new TaskController(); + window.childFrameSignal = controller.signal; + <\/script>` + await new Promise((resolve) => { + frame.addEventListener('load', resolve) + document.body.appendChild(frame); + }); + + const signal = frame.contentWindow.childFrameSignal; + document.body.removeChild(frame); + return scheduler.postTask(() => {}, {signal}); +}, 'Test scheduler.postTask() with a signal from a detached iframe'); + +</script> diff --git a/testing/web-platform/tests/scheduler/post-task-without-signals.any.js b/testing/web-platform/tests/scheduler/post-task-without-signals.any.js new file mode 100644 index 0000000000..f5fe3e11a6 --- /dev/null +++ b/testing/web-platform/tests/scheduler/post-task-without-signals.any.js @@ -0,0 +1,10 @@ +// META: title=Scheduler: Basic Functionality without Signals +// META: global=window,worker +'use strict'; + +promise_test(async t => { + for (const priority of ['user-blocking', 'user-visible', 'background']) { + const result = await scheduler.postTask(() => priority, {priority}); + assert_equals(result, priority); + } +}, 'Basic functionality for scheduler.postTask() without using TaskSignals'); diff --git a/testing/web-platform/tests/scheduler/scheduler-replaceable.any.js b/testing/web-platform/tests/scheduler/scheduler-replaceable.any.js new file mode 100644 index 0000000000..12bf1116dd --- /dev/null +++ b/testing/web-platform/tests/scheduler/scheduler-replaceable.any.js @@ -0,0 +1,12 @@ +// META: title=Scheduler: scheduler should be replaceable +// META: global=window,worker +'use strict'; + +test(() => { + class Scheduler { + constructor() { + scheduler = this; + } + } + new Scheduler(); +}, 'Tests replacing window.scheduler with a different object'); diff --git a/testing/web-platform/tests/scheduler/task-controller-abort-completed-tasks.any.js b/testing/web-platform/tests/scheduler/task-controller-abort-completed-tasks.any.js new file mode 100644 index 0000000000..fc96038e64 --- /dev/null +++ b/testing/web-platform/tests/scheduler/task-controller-abort-completed-tasks.any.js @@ -0,0 +1,19 @@ +// META: title=Scheduler: Aborting Completed Tasks is a No-op +// META: global=window,worker +'use strict'; + +promise_test(async t => { + const controller1 = new TaskController(); + const controller2 = new TaskController(); + + await scheduler.postTask(() => {}, {signal: controller1.signal}); + + const task = scheduler.postTask(() => {}, {signal: controller2.signal}); + controller2.abort(); + await promise_rejects_dom(t, 'AbortError', task); + + // The tasks associated with these controllers have completed, so this should + // not lead to any unhandled rejections. + controller1.abort(); + controller2.abort(); +}, 'Aborting completed tasks should be a no-op.'); diff --git a/testing/web-platform/tests/scheduler/task-controller-abort-signal-and-priority.any.js b/testing/web-platform/tests/scheduler/task-controller-abort-signal-and-priority.any.js new file mode 100644 index 0000000000..168fe92f54 --- /dev/null +++ b/testing/web-platform/tests/scheduler/task-controller-abort-signal-and-priority.any.js @@ -0,0 +1,16 @@ +// META: title=Scheduler: TaskController.abort() with Signal and Priority +// META: global=window,worker +'use strict'; + +promise_test(async t => { + const controller = new TaskController(); + const signal = controller.signal; + + const task1 = scheduler.postTask(() => {}, {signal}); + const task2 = scheduler.postTask(() => {}, {priority: 'background', signal}); + + controller.abort(); + + await promise_rejects_dom(t, 'AbortError', task1); + return promise_rejects_dom(t, 'AbortError', task2); +}, 'Test that when scheduler.postTask() is given both a signal and priority, the signal abort is honored'); diff --git a/testing/web-platform/tests/scheduler/task-controller-abort1.any.js b/testing/web-platform/tests/scheduler/task-controller-abort1.any.js new file mode 100644 index 0000000000..fc7e02ce37 --- /dev/null +++ b/testing/web-platform/tests/scheduler/task-controller-abort1.any.js @@ -0,0 +1,16 @@ +// META: title=Scheduler: TaskController.abort() Basic Functionality +// META: global=window,worker +'use strict'; + +promise_test(async t => { + const controller = new TaskController(); + const signal = controller.signal; + + let didRun = false; + const taskResult = scheduler.postTask(() => { didRun = true; }, {signal}); + + controller.abort(); + + await promise_rejects_dom(t, 'AbortError', taskResult); + assert_false(didRun); +}, 'Test that TaskController.abort() prevents a task from running and rejects the promise'); diff --git a/testing/web-platform/tests/scheduler/task-controller-abort2.any.js b/testing/web-platform/tests/scheduler/task-controller-abort2.any.js new file mode 100644 index 0000000000..075715b565 --- /dev/null +++ b/testing/web-platform/tests/scheduler/task-controller-abort2.any.js @@ -0,0 +1,23 @@ +// META: title=Scheduler: TaskController.abort() Aborts Correct Task +// META: global=window,worker +'use strict'; + +promise_test(async t => { + const taskControllers = []; + const taskResults = []; + + for (let i = 0; i < 5; i++) { + const controller = new TaskController(); + taskControllers.push(controller); + + const signal = controller.signal; + taskResults.push(scheduler.postTask(() => i, {signal})); + } + + const abortedTask = taskResults.splice(2, 1)[0]; + taskControllers[2].abort(); + await promise_rejects_dom(t, 'AbortError', abortedTask); + + const result = await Promise.all(taskResults); + assert_equals(result.toString(), '0,1,3,4'); +}, 'Test aborting a task aborts the appropriate task'); diff --git a/testing/web-platform/tests/scheduler/task-controller-setPriority-delayed-task.any.js b/testing/web-platform/tests/scheduler/task-controller-setPriority-delayed-task.any.js new file mode 100644 index 0000000000..43d24c8f92 --- /dev/null +++ b/testing/web-platform/tests/scheduler/task-controller-setPriority-delayed-task.any.js @@ -0,0 +1,31 @@ +// META: title=Scheduler: Change Delayed Task Priority +// META: global=window,worker +'use strict'; + +promise_test(t => { + let taskCount = 0; + const start = performance.now(); + const controller = new TaskController({priority: 'background'}); + + const task1 = scheduler.postTask(() => { + assert_equals(++taskCount, 1); + controller.setPriority('user-blocking'); + }, {priority: 'user-blocking', delay: 10}); + + const task2 = scheduler.postTask(() => { + assert_equals(++taskCount, 2); + + const elapsed = performance.now() - start; + + if(navigator.userAgent.toLowerCase().indexOf('firefox') > -1){ + // Firefox returns the timings with different precision, + // so we put 19 here. + assert_greater_than_equal(elapsed, 19); + } else { + assert_greater_than_equal(elapsed, 20); + } + }, {signal: controller.signal, delay: 20}); + + return Promise.all([task1, task2]); + +}, "Tests delay when changing a delayed task's priority"); diff --git a/testing/web-platform/tests/scheduler/task-controller-setPriority-recursive.any.js b/testing/web-platform/tests/scheduler/task-controller-setPriority-recursive.any.js new file mode 100644 index 0000000000..ebc4ccd950 --- /dev/null +++ b/testing/web-platform/tests/scheduler/task-controller-setPriority-recursive.any.js @@ -0,0 +1,12 @@ +// META: title=Scheduler: Recursive TaskController.setPriority() +// META: global=window,worker +'use strict'; + +async_test(t => { + const controller = new TaskController(); + controller.signal.onprioritychange = t.step_func_done(() => { + assert_equals(controller.signal.priority, 'background'); + assert_throws_dom('NotAllowedError', () => { controller.setPriority('user-blocking'); }); + }); + controller.setPriority('background'); +}, 'Test that TaskController.setPriority() throws an error if called recursively'); diff --git a/testing/web-platform/tests/scheduler/task-controller-setPriority-repeated.any.js b/testing/web-platform/tests/scheduler/task-controller-setPriority-repeated.any.js new file mode 100644 index 0000000000..fae3ec6c48 --- /dev/null +++ b/testing/web-platform/tests/scheduler/task-controller-setPriority-repeated.any.js @@ -0,0 +1,60 @@ +// META: title=Scheduler: TaskController.setPriority() repeated calls +// META: global=window,worker +'use strict'; + +promise_test(async t => { + const controller = new TaskController(); + const signal = controller.signal; + + const tasks = []; + const runOrder = []; + const callback = id => { runOrder.push(id); }; + + tasks.push(scheduler.postTask(() => callback(0), {signal})); + tasks.push(scheduler.postTask(() => callback(1), {priority: 'user-blocking'})); + tasks.push(scheduler.postTask(() => callback(2), {priority: 'user-visible' })); + + controller.setPriority('background'); + assert_equals(signal.priority, 'background'); + + await Promise.all(tasks); + assert_equals(runOrder.toString(), '1,2,0'); + + while (tasks.length) { tasks.pop(); } + while (runOrder.length) { runOrder.pop(); } + + tasks.push(scheduler.postTask(() => callback(3), {signal})); + tasks.push(scheduler.postTask(() => callback(4), {priority: 'user-blocking'})); + tasks.push(scheduler.postTask(() => callback(5), {priority: 'user-visible' })); + + controller.setPriority('user-blocking'); + assert_equals(signal.priority, 'user-blocking'); + + await Promise.all(tasks); + assert_equals(runOrder.toString(), '3,4,5'); +}, 'TaskController.setPriority() changes the priority of all associated tasks when called repeatedly'); + +promise_test(async t => { + const controller = new TaskController(); + const signal = controller.signal; + + const tasks = []; + const runOrder = []; + const callback = id => { runOrder.push(id); }; + + tasks.push(scheduler.postTask(() => callback(0), {signal})); + tasks.push(scheduler.postTask(() => callback(1), {priority: 'user-blocking'})); + tasks.push(scheduler.postTask(() => callback(2), {priority: 'user-visible' })); + + controller.setPriority('background'); + assert_equals(signal.priority, 'background'); + + controller.setPriority('user-visible'); + assert_equals(signal.priority, 'user-visible'); + + controller.setPriority('user-blocking'); + assert_equals(signal.priority, 'user-blocking'); + + await Promise.all(tasks); + assert_equals(runOrder.toString(), '0,1,2'); +}, 'TaskController.setPriority() changes the priority of all associated tasks when called repeatedly before tasks run'); diff --git a/testing/web-platform/tests/scheduler/task-controller-setPriority1.any.js b/testing/web-platform/tests/scheduler/task-controller-setPriority1.any.js new file mode 100644 index 0000000000..a59c20cacc --- /dev/null +++ b/testing/web-platform/tests/scheduler/task-controller-setPriority1.any.js @@ -0,0 +1,24 @@ +// META: title=Scheduler: TaskController.setPriority() +// META: global=window,worker +'use strict'; + +promise_test(async t => { + const controller = new TaskController(); + const signal = controller.signal; + + const tasks = []; + const runOrder = []; + const callback = id => { runOrder.push(id); }; + + for (let i = 0; i < 5; i++) + tasks.push(scheduler.postTask(() => callback(i), {signal})); + tasks.push(scheduler.postTask(() => callback(5), {priority: 'user-blocking'})); + tasks.push(scheduler.postTask(() => callback(6), {priority: 'user-visible' })); + + controller.setPriority('background'); + assert_equals(signal.priority, 'background'); + + await Promise.all(tasks); + + assert_equals(runOrder.toString(), '5,6,0,1,2,3,4'); +}, 'Test that TaskController.setPriority() changes the priority of all associated tasks'); diff --git a/testing/web-platform/tests/scheduler/task-controller-setPriority2.any.js b/testing/web-platform/tests/scheduler/task-controller-setPriority2.any.js new file mode 100644 index 0000000000..716b3af33e --- /dev/null +++ b/testing/web-platform/tests/scheduler/task-controller-setPriority2.any.js @@ -0,0 +1,22 @@ +// META: title=Scheduler: TaskController.setPriority and Task Order +// META: global=window,worker +'use strict'; + +promise_test(async t => { + const tasks = []; + const runOrder = []; + const taskControllers = []; + + for (let i = 0; i < 5; i++) { + taskControllers.push(new TaskController({priority: 'background'})); + const signal = taskControllers[i].signal; + tasks.push(scheduler.postTask(() => { runOrder.push(i); }, {signal})); + } + + taskControllers[2].setPriority('user-blocking'); + assert_equals(taskControllers[2].signal.priority, 'user-blocking'); + + await Promise.all(tasks); + + assert_equals(runOrder.toString(), '2,0,1,3,4'); +}, 'Test TaskController.setPriority() affects task order.'); diff --git a/testing/web-platform/tests/scheduler/task-signal-any-abort.tentative.any.js b/testing/web-platform/tests/scheduler/task-signal-any-abort.tentative.any.js new file mode 100644 index 0000000000..4afcde901d --- /dev/null +++ b/testing/web-platform/tests/scheduler/task-signal-any-abort.tentative.any.js @@ -0,0 +1,6 @@ +// META: global=window,worker +// META: script=../dom/abort/resources/abort-signal-any-tests.js + +abortSignalAnySignalOnlyTests(TaskSignal); +abortSignalAnyTests(TaskSignal, AbortController); +abortSignalAnyTests(TaskSignal, TaskController); diff --git a/testing/web-platform/tests/scheduler/task-signal-any-post-task-run-order.tentative.any.js b/testing/web-platform/tests/scheduler/task-signal-any-post-task-run-order.tentative.any.js new file mode 100644 index 0000000000..889217b081 --- /dev/null +++ b/testing/web-platform/tests/scheduler/task-signal-any-post-task-run-order.tentative.any.js @@ -0,0 +1,73 @@ +// META: title=Scheduler: Tasks Run in Priority Order +// META: global=window,worker + +promise_test(async t => { + const runOrder = []; + const schedule = (id, signal) => scheduler.postTask(() => { runOrder.push(id); }, {signal}); + + const tasks = []; + tasks.push(schedule('B1', TaskSignal.any([], {priority: 'background'}))); + tasks.push(schedule('B2', TaskSignal.any([], {priority: 'background'}))); + tasks.push(schedule('UV1', TaskSignal.any([], {priority: 'user-visible'}))); + tasks.push(schedule('UV2', TaskSignal.any([], {priority: 'user-visible'}))); + tasks.push(schedule('UB1', TaskSignal.any([], {priority: 'user-blocking'}))); + tasks.push(schedule('UB2', TaskSignal.any([], {priority: 'user-blocking'}))); + + await Promise.all(tasks); + + assert_equals(runOrder.toString(),'UB1,UB2,UV1,UV2,B1,B2'); +}, 'scheduler.postTask() tasks run in priority order with a fixed priority composite signal'); + +promise_test(async t => { + const runOrder = []; + const schedule = (id, priorityOrSignal) => { + if (priorityOrSignal instanceof TaskSignal) { + return scheduler.postTask(() => { runOrder.push(id); }, {signal: priorityOrSignal}); + } else { + return scheduler.postTask(() => { runOrder.push(id); }, {priority: priorityOrSignal}); + } + }; + + const controller = new TaskController({priority: 'user-blocking'}); + const signal = TaskSignal.any([], {priority: controller.signal}); + + const tasks = []; + tasks.push(schedule('B1', signal)); + tasks.push(schedule('B2', signal)); + tasks.push(schedule('UV1', 'user-visible')); + tasks.push(schedule('UV2', 'user-visible')); + tasks.push(schedule('UB1', 'user-blocking')); + tasks.push(schedule('UB2', 'user-blocking')); + + controller.setPriority('background'); + + await Promise.all(tasks); + + assert_equals(runOrder.toString(),'UB1,UB2,UV1,UV2,B1,B2'); +}, 'scheduler.postTask() tasks run in priority order with a dynamic priority composite signal'); + +promise_test(async t => { + const runOrder = []; + const schedule = (id, priorityOrSignal) => { + if (priorityOrSignal instanceof TaskSignal) { + return scheduler.postTask(() => { runOrder.push(id); }, {signal: priorityOrSignal}); + } else { + return scheduler.postTask(() => { runOrder.push(id); }, {priority: priorityOrSignal}); + } + }; + + const parentSignal = TaskSignal.any([], {priority: 'background'}); + const signal = TaskSignal.any([], {priority: parentSignal}); + + const tasks = []; + tasks.push(schedule('B1', signal)); + tasks.push(schedule('B2', signal)); + tasks.push(schedule('UV1', 'user-visible')); + tasks.push(schedule('UV2', 'user-visible')); + tasks.push(schedule('UB1', 'user-blocking')); + tasks.push(schedule('UB2', 'user-blocking')); + + await Promise.all(tasks); + + assert_equals(runOrder.toString(),'UB1,UB2,UV1,UV2,B1,B2'); +}, 'scheduler.postTask() tasks run in priority order with a composite signal whose source has fixed priority'); diff --git a/testing/web-platform/tests/scheduler/task-signal-any-priority.tentative.any.js b/testing/web-platform/tests/scheduler/task-signal-any-priority.tentative.any.js new file mode 100644 index 0000000000..46af80b8b1 --- /dev/null +++ b/testing/web-platform/tests/scheduler/task-signal-any-priority.tentative.any.js @@ -0,0 +1,213 @@ +// META: global=window,worker + +test((t) => { + const signal = TaskSignal.any([]); + assert_true(signal instanceof TaskSignal); + assert_equals(signal.priority, 'user-visible'); +}, "TaskSignal.any() returns a user-visible TaskSignal when no priority is specified"); + +test((t) => { + let signal = TaskSignal.any([], {priority: 'user-blocking'}); + assert_equals(signal.priority, 'user-blocking'); + + signal = TaskSignal.any([], {priority: 'user-visible'}); + assert_equals(signal.priority, 'user-visible'); + + signal = TaskSignal.any([], {priority: 'background'}); + assert_equals(signal.priority, 'background'); +}, "TaskSignal.any() returns a signal with the correct priority when intialized with a string"); + +test((t) => { + let controller = new TaskController({priority: 'user-blocking'}); + let signal = TaskSignal.any([], {priority: controller.signal}); + assert_equals(signal.priority, 'user-blocking'); + + controller = new TaskController({priority: 'user-visible'}); + signal = TaskSignal.any([], {priority: controller.signal}); + assert_equals(signal.priority, 'user-visible'); + + controller = new TaskController({priority: 'background'}); + signal = TaskSignal.any([], {priority: controller.signal}); + assert_equals(signal.priority, 'background'); +}, "TaskSignal.any() returns a signal with the correct priority when intialized with a TaskSignal"); + +test((t) => { + let controller = new TaskController({priority: 'user-blocking'}); + let signal = TaskSignal.any([], {priority: controller.signal}); + assert_equals(signal.priority, 'user-blocking'); + + controller.setPriority('user-visible'); + assert_equals(signal.priority, 'user-visible'); + + controller.setPriority('background'); + assert_equals(signal.priority, 'background'); + + controller.setPriority('user-blocking'); + assert_equals(signal.priority, 'user-blocking'); +}, "TaskSignal.any() returns a signal with dynamic priority"); + +test((t) => { + const controller = new TaskController(); + const signal = TaskSignal.any([], {priority: controller.signal}); + + let eventFiredCount = 0; + signal.onprioritychange = t.step_func((e) => { + assert_equals(e.target, signal, + `The event target is the signal returned by TaskSignal.any()`); + ++eventFiredCount; + }); + + controller.setPriority('background'); + assert_equals(eventFiredCount, 1); + + controller.setPriority('user-visible'); + assert_equals(eventFiredCount, 2); + + controller.setPriority('user-blocking'); + assert_equals(eventFiredCount, 3); +}, "Priority change events fire for composite signals"); + + +test((t) => { + const controller = new TaskController(); + let signal = TaskSignal.any([], {priority: controller.signal}); + signal = TaskSignal.any([], {priority: signal}); + signal = TaskSignal.any([], {priority: signal}); + signal = TaskSignal.any([], {priority: signal}); + signal = TaskSignal.any([], {priority: signal}); + + assert_equals(signal.priority, 'user-visible'); + + let eventFiredCount = 0; + signal.onprioritychange = t.step_func((e) => { + assert_equals(e.target, signal, + "The event target is the signal returned by TaskSignal.any()"); + ++eventFiredCount; + }); + + controller.setPriority('background'); + assert_equals(eventFiredCount, 1); + assert_equals(signal.priority, 'background'); + + controller.setPriority('user-visible'); + assert_equals(eventFiredCount, 2); + assert_equals(signal.priority, 'user-visible'); + + controller.setPriority('user-blocking'); + assert_equals(eventFiredCount, 3); + assert_equals(signal.priority, 'user-blocking'); +}, "Priority change events fire for composite signals with intermediate sources"); + +test((t) => { + const controller = new TaskController(); + const signals = []; + const results = []; + + let id = 0; + for (let i = 0; i < 3; i++) { + const signal = TaskSignal.any([], {priority: controller.signal}); + const eventId = id++; + signal.addEventListener('prioritychange', () => { + results.push(eventId); + }); + signals.push(signal); + } + for (let i = 0; i < 3; i++) { + const signal = TaskSignal.any([], {priority: signals[i]}); + const eventId = id++; + signal.addEventListener('prioritychange', () => { + results.push(eventId); + }); + } + + controller.setPriority('background'); + assert_equals(results.toString(), '0,1,2,3,4,5') + + controller.setPriority('user-blocking'); + assert_equals(results.toString(), '0,1,2,3,4,5,0,1,2,3,4,5') +}, "Priority change propagates to multiple dependent signals in the right order"); + +test((t) => { + const controller = new TaskController(); + const signal = TaskSignal.any([], {priority: controller.signal}); + + let fired = false; + signal.onabort = t.step_func(() => { + assert_unreached("The signal should not abort"); + fired = true; + }); + + controller.abort(); + assert_false(fired); +}, "TaskSignal.any() does not propagate abort when not given dependent abort signals"); + +test((t) => { + const taskController = new TaskController(); + const abortController = new AbortController(); + const signal = TaskSignal.any([abortController.signal], {priority: taskController.signal}); + + let priorityFireCount = 0; + signal.onprioritychange = t.step_func(() => { + ++priorityFireCount; + }); + + let abortFired = false; + signal.onabort = t.step_func(() => { + abortFired = true; + }); + + taskController.setPriority('background'); + assert_equals(signal.priority, 'background'); + assert_equals(priorityFireCount, 1); + + taskController.abort(); + assert_false(abortFired, "The signal should use abortController for abort"); + + abortController.abort(); + assert_true(abortFired); + + taskController.setPriority('user-visible'); + assert_equals(signal.priority, 'user-visible'); + assert_equals(priorityFireCount, 2); +}, "TaskSignal.any() propagates abort and priority"); + + +test((t) => { + const controller = new TaskController(); + const signal = TaskSignal.any([AbortSignal.abort()], {priority: controller.signal}); + + let fired = false; + signal.onprioritychange = t.step_func(() => { + fired = true; + }); + + controller.setPriority('background'); + assert_true(fired); +}, "TaskSignal.any() propagates priority after returning an aborted signal"); + +test((t) => { + // Add a dependent in the initial event dispatch stage. + let controller = new TaskController(); + let fired = false; + controller.signal.onprioritychange = t.step_func(() => { + fired = true; + const newSignal = TaskSignal.any([], {priority: controller.signal}); + assert_equals(newSignal.priority, 'background'); + newSignal.onprioritychange = t.unreached_func('onprioritychange called'); + }); + controller.setPriority('background'); + assert_true(fired); + + // Add a dependent while signaling prioritychange on dependents. + fired = false; + controller = new TaskController(); + const signal = TaskSignal.any([], {priority: controller.signal}); + signal.onprioritychange = t.step_func(() => { + fired = true; + const newSignal = TaskSignal.any([], {priority: signal}); + assert_equals(newSignal.priority, 'background'); + newSignal.onprioritychange = t.unreached_func('onprioritychange called'); + }); + controller.setPriority('background'); + assert_true(fired); +}, "TaskSignal.any() does not fire prioritychange for dependents added during prioritychange"); diff --git a/testing/web-platform/tests/scheduler/task-signal-onprioritychange.any.js b/testing/web-platform/tests/scheduler/task-signal-onprioritychange.any.js new file mode 100644 index 0000000000..7f59e1f1f8 --- /dev/null +++ b/testing/web-platform/tests/scheduler/task-signal-onprioritychange.any.js @@ -0,0 +1,14 @@ +// META: title=Scheduler: TaskSignal onprioritychange +// META: global=window,worker +'use strict'; + +async_test(t => { + const controller = new TaskController({priority: 'user-visible'}); + controller.signal.onprioritychange = t.step_func_done((event) => { + assert_equals(controller.signal.priority, 'background'); + assert_equals(event.type, 'prioritychange'); + assert_equals(event.target.priority, 'background'); + assert_equals(event.previousPriority, 'user-visible'); + }); + controller.setPriority('background'); +}, 'Test that TaskSignal.onprioritychange listens for prioritychange events'); diff --git a/testing/web-platform/tests/scheduler/tentative/yield/yield-abort.any.js b/testing/web-platform/tests/scheduler/tentative/yield/yield-abort.any.js new file mode 100644 index 0000000000..e96694f5ab --- /dev/null +++ b/testing/web-platform/tests/scheduler/tentative/yield/yield-abort.any.js @@ -0,0 +1,85 @@ +'use strict'; + +promise_test(t => { + const signal = AbortSignal.abort(); + return scheduler.postTask(async () => { + const p = scheduler.yield({signal}); + await promise_rejects_dom(t, 'AbortError', p); + }); +}, 'yield() with an aborted signal'); + +promise_test(t => { + const controller = new TaskController(); + const signal = controller.signal; + const task = scheduler.postTask(async () => { + controller.abort(); + const p = scheduler.yield({signal: 'inherit'}); + await promise_rejects_dom(t, 'AbortError', p); + }, {signal}); + return promise_rejects_dom(t, 'AbortError', task); +}, 'yield() with an aborted signal (inherit signal)'); + +promise_test(t => { + const controller = new TaskController(); + const signal = controller.signal; + const task = scheduler.postTask(async () => { + controller.abort(); + const p = scheduler.yield({signal: 'inherit', priority: 'background'}); + await promise_rejects_dom(t, 'AbortError', p); + }, {signal}); + return promise_rejects_dom(t, 'AbortError', task); +}, 'yield() with an aborted signal (inherit signal priority override)'); + +promise_test(t => { + const controller = new TaskController(); + const signal = controller.signal; + const task = scheduler.postTask(async () => { + controller.abort(); + await scheduler.yield({priority: 'inherit'}); + }, {signal}); + return promise_rejects_dom(t, 'AbortError', task); +}, 'yield() with an aborted signal (inherit priority only)'); + +promise_test(t => { + const controller = new TaskController(); + const signal = controller.signal; + return scheduler.postTask(async () => { + scheduler.postTask(async () => {controller.abort();}, {priority: 'user-blocking'}); + t.step(() => assert_false(signal.aborted)); + const p = scheduler.yield({signal}); + await promise_rejects_dom(t, 'AbortError', p); + }); +}, 'yield() aborted in a separate task'); + +promise_test(t => { + const controller = new TaskController(); + const signal = controller.signal; + return scheduler.postTask(async () => { + scheduler.postTask(async () => {controller.abort();}, {priority: 'user-blocking'}); + t.step(() => assert_false(signal.aborted)); + const p = scheduler.yield({signal: 'inherit'}); + await promise_rejects_dom(t, 'AbortError', p); + }, {signal}); +}, 'yield() aborted in a separate task (inherit)'); + +promise_test(t => { + const controller = new TaskController(); + const signal = controller.signal; + return scheduler.postTask(async () => { + scheduler.postTask(async () => {controller.abort();}, {priority: 'user-blocking'}); + t.step(() => assert_false(signal.aborted)); + const p = scheduler.yield({signal: 'inherit', priority: 'background'}); + await promise_rejects_dom(t, 'AbortError', p); + }, {signal}); +}, 'yield() aborted in a separate task (inherit signal priority override)'); + +promise_test(t => { + const controller = new TaskController(); + const signal = controller.signal; + return scheduler.postTask(async () => { + scheduler.postTask(async () => {controller.abort();}, {priority: 'user-blocking'}); + t.step(() => assert_false(signal.aborted)); + await scheduler.yield({priority: 'inherit'}); + t.step(() => assert_true(signal.aborted)); + }, {signal}); +}, 'yield() aborted in a separate task (inherit priority only)'); diff --git a/testing/web-platform/tests/scheduler/tentative/yield/yield-inherit-across-promises.any.js b/testing/web-platform/tests/scheduler/tentative/yield/yield-inherit-across-promises.any.js new file mode 100644 index 0000000000..eaa0125a78 --- /dev/null +++ b/testing/web-platform/tests/scheduler/tentative/yield/yield-inherit-across-promises.any.js @@ -0,0 +1,65 @@ +'use strict'; + +function postInheritPriorityTestTask(config) { + const ids = []; + const task = scheduler.postTask(async () => { + await new Promise(resolve => setTimeout(resolve)); + await fetch('/common/blank.html'); + await new Promise(resolve => setTimeout(resolve)); + const subtask = scheduler.postTask(() => { ids.push('subtask'); }, {priority: config.subTaskPriority}); + await scheduler.yield(config.yieldOptions); + ids.push('yield'); + await subtask; + }, config.taskOptions); + return {task, ids} +} + +for (let priority of ['user-blocking', 'background']) { + const expected = priority == 'user-blocking' ? 'yield,subtask' : 'subtask,yield'; + promise_test(async t => { + const config = { + taskOptions: {priority}, + subTaskPriority: 'user-blocking', + yieldOptions: {priority: 'inherit'}, + }; + const {task, ids} = postInheritPriorityTestTask(config); + await task; + assert_equals(ids.join(), expected); + }, `yield() inherits priority (string) across promises (${priority})`); + + promise_test(async t => { + const signal = (new TaskController({priority})).signal; + const config = { + taskOptions: {signal}, + subTaskPriority: 'user-blocking', + yieldOptions: {signal: 'inherit'}, + }; + const {task, ids} = postInheritPriorityTestTask(config); + await task; + assert_equals(ids.join(), expected); + }, `yield() inherits priority (signal) across promises (${priority})`); + + promise_test(async t => { + const config = { + taskOptions: {priority}, + subTaskPriority: 'user-blocking', + yieldOptions: {signal: 'inherit'}, + }; + const {task, ids} = postInheritPriorityTestTask(config); + await task; + assert_equals(ids.join(), expected); + }, `yield() inherits priority (priority string with signal inherit) across promises (${priority})`); +} + +promise_test(async t => { + const controller = new TaskController(); + const signal = controller.signal; + return scheduler.postTask(async () => { + await new Promise(resolve => setTimeout(resolve)); + await fetch('/common/blank.html'); + await new Promise(resolve => setTimeout(resolve)); + controller.abort(); + const p = scheduler.yield({signal: 'inherit'}); + await promise_rejects_dom(t, 'AbortError', p); + }, {signal}); +}, `yield() inherits abort across promises`); diff --git a/testing/web-platform/tests/scheduler/tentative/yield/yield-priority-idle-callbacks.html b/testing/web-platform/tests/scheduler/tentative/yield/yield-priority-idle-callbacks.html new file mode 100644 index 0000000000..d47e8c5eba --- /dev/null +++ b/testing/web-platform/tests/scheduler/tentative/yield/yield-priority-idle-callbacks.html @@ -0,0 +1,60 @@ +<!doctype html> +<title>Scheduler: yield inheritance in requestIdleCallback</title> +<link rel="help" href="https://github.com/WICG/scheduling-apis"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +'use strict'; + +// Queues a requestIdleCallback that schedules 2 user-visible tasks, 2 +// background tasks, another requestIdleCallback, and then yields 3 times using +// `yieldParams`. +// +// Returns {tasks, ids} where `tasks` is an array of promises associated with +// the tasks and `ids` is an array of task ids appended to by the scheduled +// tasks. +function postTestTasks(yieldParams) { + const ids = []; + const task = new Promise(resolve => { + requestIdleCallback(async () => { + ids.push('i1'); + + const subtasks = []; + subtasks.push(scheduler.postTask(() => { ids.push('uv1'); })); + subtasks.push(scheduler.postTask(() => { ids.push('uv2'); })); + subtasks.push(scheduler.postTask(() => { ids.push('bg1'); }, {priority: 'background'})); + subtasks.push(scheduler.postTask(() => { ids.push('bg2'); }, {priority: 'background'})); + subtasks.push(new Promise(resolve => { + requestIdleCallback(() => { + ids.push('i2'); + resolve(); + }); + })); + + for (let i = 1; i <= 3; i++) { + await scheduler.yield(yieldParams); + ids.push('y' + i); + } + await Promise.all(subtasks); + resolve(); + }); + }); + return {task, ids}; +} + +const expected_inherited_task_order = 'i1,uv1,uv2,y1,y2,y3,bg1,bg2,i2'; + +promise_test(async t => { + const {task, ids} = postTestTasks({priority: "inherit"}); + await task; + assert_equals(ids.join(), expected_inherited_task_order); +}, 'requestIdleCallback() yields at background priority when inheriting priority'); + +promise_test(async t => { + const {task, ids} = postTestTasks({signal: "inherit"}); + await task; + assert_equals(ids.join(), expected_inherited_task_order); +}, 'requestIdleCallback() yields at background priority when inheriting signal'); + +</script> diff --git a/testing/web-platform/tests/scheduler/tentative/yield/yield-priority-posttask.any.js b/testing/web-platform/tests/scheduler/tentative/yield/yield-priority-posttask.any.js new file mode 100644 index 0000000000..0700094dcf --- /dev/null +++ b/testing/web-platform/tests/scheduler/tentative/yield/yield-priority-posttask.any.js @@ -0,0 +1,193 @@ +'use strict'; + +// Posts a postTask task with `yieldyTaskParams` that yields 3 times using +// `yieldParams`, then posts 2 tasks of each priority, in descending order. +// +// Returns {tasks, ids} where `tasks` is an array of promises returned by +// postTask and `ids` is an array of task ids appended to by the scheduled +// tasks. +function postTestTasks(yieldyTaskParams, yieldParams) { + const tasks = []; + const ids = []; + + tasks.push(scheduler.postTask(async () => { + ids.push('y0'); + for (let i = 1; i < 4; i++) { + await scheduler.yield(yieldParams); + ids.push('y' + i); + } + }, yieldyTaskParams)); + + tasks.push( + scheduler.postTask(() => {ids.push('ub1')}, {priority: 'user-blocking'})); + tasks.push( + scheduler.postTask(() => {ids.push('ub2')}, {priority: 'user-blocking'})); + tasks.push( + scheduler.postTask(() => {ids.push('uv1')}, {priority: 'user-visible'})); + tasks.push( + scheduler.postTask(() => {ids.push('uv2')}, {priority: 'user-visible'})); + tasks.push( + scheduler.postTask(() => {ids.push('bg1')}, {priority: 'background'})); + tasks.push( + scheduler.postTask(() => {ids.push('bg2')}, {priority: 'background'})); + return {tasks, ids}; +} + +// Expected task orders for `postTestTasks` tasks. +const taskOrders = { + 'user-blocking': 'y0,y1,y2,y3,ub1,ub2,uv1,uv2,bg1,bg2', + 'user-visible': 'ub1,ub2,y0,y1,y2,y3,uv1,uv2,bg1,bg2', + 'background': 'ub1,ub2,uv1,uv2,y0,y1,y2,y3,bg1,bg2', +}; + +const priorityConfigs = [ + {options: {}, expected: taskOrders['user-visible']}, + {options: {priority: 'user-visible'}, expected: taskOrders['user-visible']}, + {options: {priority: 'user-blocking'}, expected: taskOrders['user-blocking']}, + {options: {priority: 'background'}, expected: taskOrders['background']}, +]; + +const fixedPrioritySignals = { + 'user-blocking': (new TaskController({priority: 'user-blocking'})).signal, + 'user-visible': (new TaskController({priority: 'user-visible'})).signal, + 'background': (new TaskController({priority: 'background'})).signal, +}; + +const signalConfigs = [ + { + options: {signal: fixedPrioritySignals['user-visible']}, + expected: taskOrders['user-visible'] + }, + { + options: {signal: fixedPrioritySignals['user-blocking']}, + expected: taskOrders['user-blocking'] + }, + { + options: {signal: fixedPrioritySignals['background']}, + expected: taskOrders['background'] + }, +]; + +promise_test(async t => { + for (const config of priorityConfigs) { + const {tasks, ids} = postTestTasks(config.options, config.options); + await Promise.all(tasks); + assert_equals(ids.join(), config.expected); + } +}, 'yield() with postTask tasks (priority option)'); + +promise_test(async t => { + for (const config of signalConfigs) { + const {tasks, ids} = postTestTasks(config.options, config.options); + await Promise.all(tasks); + assert_equals(ids.join(), config.expected); + } +}, 'yield() with postTask tasks (signal option)'); + +promise_test(async t => { + for (const config of priorityConfigs) { + const {tasks, ids} = + postTestTasks(config.options, {priority: 'inherit'}); + await Promise.all(tasks); + assert_equals(ids.join(), config.expected); + } +}, 'yield() with postTask tasks (inherit priority)'); + +promise_test(async t => { + for (const config of signalConfigs) { + const {tasks, ids} = + postTestTasks(config.options, {signal: 'inherit'}); + await Promise.all(tasks); + assert_equals(ids.join(), config.expected); + } +}, 'yield() with postTask tasks (inherit signal)'); + +promise_test(async t => { + const expected = 'y0,ub1,ub2,uv1,uv2,y1,y2,y3,bg1,bg2'; + const {tasks, ids} = postTestTasks( + {priority: 'user-blocking'}, {priority: 'background'}); + await Promise.all(tasks); + assert_equals(ids.join(), expected); +}, 'yield() with different priority from task (priority)'); + +promise_test(async t => { + const expected = 'y0,ub1,ub2,uv1,uv2,y1,y2,y3,bg1,bg2'; + const bgSignal = (new TaskController({priority: 'background'})).signal; + const {tasks, ids} = + postTestTasks({priority: 'user-blocking'}, {signal: bgSignal}); + await Promise.all(tasks); + assert_equals(ids.join(), expected); +}, 'yield() with different priority from task (signal)'); + +promise_test(async t => { + const bgSignal = (new TaskController({priority: 'background'})).signal; + const {tasks, ids} = postTestTasks( + {priority: 'user-blocking'}, + {signal: bgSignal, priority: 'user-blocking'}); + await Promise.all(tasks); + assert_equals(ids.join(), taskOrders['user-blocking']); +}, 'yield() priority overrides signal'); + +promise_test(async t => { + const ids = []; + + const controller = new TaskController(); + const signal = controller.signal; + + await scheduler.postTask(async () => { + ids.push('y0'); + + const subtasks = []; + subtasks.push(scheduler.postTask(() => { ids.push('uv1'); })); + subtasks.push(scheduler.postTask(() => { ids.push('uv2'); })); + + // 'user-visible' continuations. + await scheduler.yield({signal: 'inherit'}); + ids.push('y1'); + await scheduler.yield({signal: 'inherit'}); + ids.push('y2'); + + controller.setPriority('background'); + + // 'background' continuations. + await scheduler.yield({signal: 'inherit'}); + ids.push('y3'); + await scheduler.yield({signal: 'inherit'}); + ids.push('y4'); + + await Promise.all(subtasks); + }, {signal}); + + assert_equals(ids.join(), 'y0,y1,y2,uv1,uv2,y3,y4'); +}, 'yield() with TaskSignal has dynamic priority') + +promise_test(async t => { + const ids = []; + + await scheduler.postTask(async () => { + ids.push('y0'); + + const subtasks = []; + subtasks.push(scheduler.postTask(() => { ids.push('ub1'); }, {priority: 'user-blocking'})); + subtasks.push(scheduler.postTask(() => { ids.push('uv1'); })); + + // Ignore inherited signal (user-visible continuations). + await scheduler.yield(); + ids.push('y1'); + await scheduler.yield(); + ids.push('y2'); + + subtasks.push(scheduler.postTask(() => { ids.push('ub2'); }, {priority: 'user-blocking'})); + subtasks.push(scheduler.postTask(() => { ids.push('uv2'); })); + + // Now use inherited signal (user-blocking continuations). + await scheduler.yield({signal: 'inherit'}); + ids.push('y3'); + await scheduler.yield({signal: 'inherit'}); + ids.push('y4'); + + await Promise.all(subtasks); + }, {priority: 'user-blocking'}); + + assert_equals(ids.join(), 'y0,ub1,y1,y2,y3,y4,ub2,uv1,uv2'); +}, 'yield() mixed inheritance and default') diff --git a/testing/web-platform/tests/scheduler/tentative/yield/yield-priority-timers.any.js b/testing/web-platform/tests/scheduler/tentative/yield/yield-priority-timers.any.js new file mode 100644 index 0000000000..ff5a3d4b33 --- /dev/null +++ b/testing/web-platform/tests/scheduler/tentative/yield/yield-priority-timers.any.js @@ -0,0 +1,94 @@ +'use strict'; + +// Queues a zero ms timer that yields 3 times using `yieldParams`, then posts 2 +// more 0 ms timers. +// +// Returns {tasks, ids} where `tasks` is an array of promises associated with +// the timers and `ids` is an array of task ids appended to by the scheduled +// tasks. +function postTestTasks(yieldParams) { + const tasks = []; + const ids = []; + + tasks.push(new Promise(resolve => { + setTimeout(async () => { + ids.push('t1'); + for (let i = 1; i < 4; i++) { + await scheduler.yield(yieldParams); + ids.push('y' + i); + } + resolve(); + }); + })); + + tasks.push(new Promise(resolve => { + setTimeout(() => { ids.push('t2'); resolve(); }); + })); + tasks.push(new Promise(resolve => { + setTimeout(() => { ids.push('t3'); resolve(); }); + })); + return {tasks, ids}; +} + +// Expected task orders for `postTestTasks` tasks. +const taskOrders = { + 'user-blocking': 't1,y1,y2,y3,t2,t3', + 'user-visible': 't1,y1,y2,y3,t2,t3', + 'background': 't1,t2,t3,y1,y2,y3', +}; + +const priorityConfigs = [ + {options: {}, expected: taskOrders['user-visible']}, + {options: {priority: 'user-visible'}, expected: taskOrders['user-visible']}, + {options: {priority: 'user-blocking'}, expected: taskOrders['user-blocking']}, + {options: {priority: 'background'}, expected: taskOrders['background']}, +]; + +const fixedPrioritySignals = { + 'user-blocking': (new TaskController({priority: 'user-blocking'})).signal, + 'user-visible': (new TaskController({priority: 'user-visible'})).signal, + 'background': (new TaskController({priority: 'background'})).signal, +}; + +const signalConfigs = [ + { + options: {signal: fixedPrioritySignals['user-visible']}, + expected: taskOrders['user-visible'] + }, + { + options: {signal: fixedPrioritySignals['user-blocking']}, + expected: taskOrders['user-blocking'] + }, + { + options: {signal: fixedPrioritySignals['background']}, + expected: taskOrders['background'] + }, +]; + +promise_test(async t => { + for (const config of priorityConfigs) { + const {tasks, ids} = postTestTasks(config.options); + await Promise.all(tasks); + assert_equals(ids.join(), config.expected); + } +}, 'yield() with timer tasks (priority option)'); + +promise_test(async t => { + for (const config of signalConfigs) { + const {tasks, ids} = postTestTasks(config.options); + await Promise.all(tasks); + assert_equals(ids.join(), config.expected); + } +}, 'yield() with timer tasks (signal option)'); + +promise_test(async t => { + const {tasks, ids} = postTestTasks({priority: 'inherit'}); + await Promise.all(tasks); + assert_equals(ids.join(), taskOrders['user-visible']); +}, 'yield() with timer tasks (inherit priority)'); + +promise_test(async t => { + const {tasks, ids} = postTestTasks({signal: 'inherit'}); + await Promise.all(tasks); + assert_equals(ids.join(), taskOrders['user-visible']); +}, 'yield() with timer tasks (inherit signal)'); diff --git a/testing/web-platform/tests/scheduler/tentative/yield/yield-then-detach.html b/testing/web-platform/tests/scheduler/tentative/yield/yield-then-detach.html new file mode 100644 index 0000000000..835f9e7a62 --- /dev/null +++ b/testing/web-platform/tests/scheduler/tentative/yield/yield-then-detach.html @@ -0,0 +1,26 @@ +<!doctype html> +<title>Scheduler: yield in Detached Scheduler</title> +<link rel="help" href="https://github.com/WICG/scheduling-apis"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +'use strict'; + +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + const frame = document.body.appendChild(document.createElement('iframe')); + const iframeDOMException = frame.contentWindow.DOMException; + const iframeScheduler = frame.contentWindow.scheduler; + + let didRun = false; + iframeScheduler.yield().then(() => { didRun = true; }); + + document.body.removeChild(frame); + await promise_rejects_dom(t, 'NotSupportedError', iframeDOMException, iframeScheduler.yield()); + await new Promise(resolve => t.step_timeout(resolve, 10)); + assert_false(didRun, 'The continuation should not have run.'); +}, 'Test scheduler.yield() from an iframe that is removed'); + +</script> |