| // META: title=Web Locks API: AbortSignal integration | |
| // META: script=resources/helpers.js | |
| // META: global=window,dedicatedworker,sharedworker,serviceworker | |
| 'use strict'; | |
| promise_test(async t => { | |
| const res = uniqueName(t); | |
| // These cases should not work: | |
| for (const signal of ['string', 12.34, false, {}, Symbol(), () => {}, self]) { | |
| await promise_rejects_js( | |
| t, TypeError, | |
| navigator.locks.request( | |
| res, {signal}, t.unreached_func('callback should not run')), | |
| 'Bindings should throw if the signal option is a not an AbortSignal'); | |
| } | |
| }, 'The signal option must be an AbortSignal'); | |
| promise_test(async t => { | |
| const res = uniqueName(t); | |
| const controller = new AbortController(); | |
| controller.abort(); | |
| await promise_rejects_dom( | |
| t, 'AbortError', | |
| navigator.locks.request(res, {signal: controller.signal}, | |
| t.unreached_func('callback should not run')), | |
| 'Request should reject with AbortError'); | |
| }, 'Passing an already aborted signal aborts'); | |
| promise_test(async t => { | |
| const res = uniqueName(t); | |
| const controller = new AbortController(); | |
| const reason = 'My dog ate it.'; | |
| controller.abort(reason); | |
| const promise = | |
| navigator.locks.request(res, {signal: controller.signal}, | |
| t.unreached_func('callback should not run')); | |
| await promise_rejects_exactly( | |
| t, reason, promise, "Rejection should give the abort reason"); | |
| }, 'Passing an already aborted signal rejects with the custom abort reason.'); | |
| promise_test(async t => { | |
| const res = uniqueName(t); | |
| const controller = new AbortController(); | |
| controller.abort(); | |
| const promise = | |
| navigator.locks.request(res, {signal: controller.signal}, | |
| t.unreached_func('callback should not run')); | |
| await promise_rejects_exactly( | |
| t, controller.signal.reason, promise, | |
| "Rejection should give the abort reason"); | |
| }, 'Passing an already aborted signal rejects with the default abort reason.'); | |
| promise_test(async t => { | |
| const res = uniqueName(t); | |
| // Grab a lock and hold it until this subtest completes. | |
| requestLockAndHold(t, res); | |
| const controller = new AbortController(); | |
| const promise = | |
| navigator.locks.request(res, {signal: controller.signal}, | |
| t.unreached_func('callback should not run')); | |
| // Verify the request is enqueued: | |
| const state = await navigator.locks.query(); | |
| assert_equals(state.held.filter(lock => lock.name === res).length, 1, | |
| 'Number of held locks'); | |
| assert_equals(state.pending.filter(lock => lock.name === res).length, 1, | |
| 'Number of pending locks'); | |
| const rejected = promise_rejects_dom( | |
| t, 'AbortError', promise, 'Request should reject with AbortError'); | |
| controller.abort(); | |
| await rejected; | |
| }, 'An aborted request results in AbortError'); | |
| promise_test(async t => { | |
| const res = uniqueName(t); | |
| // Grab a lock and hold it until this subtest completes. | |
| requestLockAndHold(t, res); | |
| const controller = new AbortController(); | |
| const promise = | |
| navigator.locks.request(res, {signal: controller.signal}, lock => {}); | |
| // Verify the request is enqueued: | |
| const state = await navigator.locks.query(); | |
| assert_equals(state.held.filter(lock => lock.name === res).length, 1, | |
| 'Number of held locks'); | |
| assert_equals(state.pending.filter(lock => lock.name === res).length, 1, | |
| 'Number of pending locks'); | |
| const rejected = promise_rejects_dom( | |
| t, 'AbortError', promise, 'Request should reject with AbortError'); | |
| let callback_called = false; | |
| t.step_timeout(() => { | |
| callback_called = true; | |
| controller.abort(); | |
| }, 10); | |
| await rejected; | |
| assert_true(callback_called, 'timeout should have caused the abort'); | |
| }, 'Abort after a timeout'); | |
| promise_test(async t => { | |
| const res = uniqueName(t); | |
| const controller = new AbortController(); | |
| let got_lock = false; | |
| await navigator.locks.request( | |
| res, {signal: controller.signal}, async lock => { got_lock = true; }); | |
| assert_true(got_lock, 'Lock should be acquired if abort is not signaled.'); | |
| }, 'Signal that is not aborted'); | |
| promise_test(async t => { | |
| const res = uniqueName(t); | |
| const controller = new AbortController(); | |
| let got_lock = false; | |
| const p = navigator.locks.request( | |
| res, {signal: controller.signal}, lock => { got_lock = true; }); | |
| // Even though lock is grantable, this abort should be processed synchronously. | |
| controller.abort(); | |
| await promise_rejects_dom(t, 'AbortError', p, 'Request should abort'); | |
| assert_false(got_lock, 'Request should be aborted if signal is synchronous'); | |
| await navigator.locks.request(res, lock => { got_lock = true; }); | |
| assert_true(got_lock, 'Subsequent request should not be blocked'); | |
| }, 'Synchronously signaled abort'); | |
| promise_test(async t => { | |
| const res = uniqueName(t); | |
| const controller = new AbortController(); | |
| // Make a promise that resolves when the lock is acquired. | |
| const [acquired_promise, acquired_func] = makePromiseAndResolveFunc(); | |
| // Request the lock. | |
| let release_func; | |
| const released_promise = navigator.locks.request( | |
| res, {signal: controller.signal}, lock => { | |
| acquired_func(); | |
| // Hold lock until release_func is called. | |
| const [waiting_promise, waiting_func] = makePromiseAndResolveFunc(); | |
| release_func = waiting_func; | |
| return waiting_promise; | |
| }); | |
| // Wait for the lock to be acquired. | |
| await acquired_promise; | |
| // Signal an abort. | |
| controller.abort(); | |
| // Release the lock. | |
| release_func('resolved ok'); | |
| assert_equals(await released_promise, 'resolved ok', | |
| 'Lock released promise should not reject'); | |
| }, 'Abort signaled after lock granted'); | |
| promise_test(async t => { | |
| const res = uniqueName(t); | |
| const controller = new AbortController(); | |
| // Make a promise that resolves when the lock is acquired. | |
| const [acquired_promise, acquired_func] = makePromiseAndResolveFunc(); | |
| // Request the lock. | |
| let release_func; | |
| const released_promise = navigator.locks.request( | |
| res, {signal: controller.signal}, lock => { | |
| acquired_func(); | |
| // Hold lock until release_func is called. | |
| const [waiting_promise, waiting_func] = makePromiseAndResolveFunc(); | |
| release_func = waiting_func; | |
| return waiting_promise; | |
| }); | |
| // Wait for the lock to be acquired. | |
| await acquired_promise; | |
| // Release the lock. | |
| release_func('resolved ok'); | |
| // Signal an abort. | |
| controller.abort(); | |
| assert_equals(await released_promise, 'resolved ok', | |
| 'Lock released promise should not reject'); | |
| }, 'Abort signaled after lock released'); | |
| promise_test(async t => { | |
| const res = uniqueName(t); | |
| const controller = new AbortController(); | |
| const first = requestLockAndHold(t, res, { signal: controller.signal }); | |
| const next = navigator.locks.request(res, () => "resolved"); | |
| controller.abort(); | |
| await promise_rejects_dom(t, "AbortError", first, "Request should abort"); | |
| assert_equals( | |
| await next, | |
| "resolved", | |
| "The next request is processed after abort" | |
| ); | |
| }, "Abort should process the next pending lock request"); | |
| promise_test(async t => { | |
| const res = uniqueName(t); | |
| const controller = new AbortController(); | |
| const promise = requestLockAndHold(t, res, { signal: controller.signal }); | |
| const reason = "My cat handled it"; | |
| controller.abort(reason); | |
| await promise_rejects_exactly(t, reason, promise, "Rejection should give the abort reason"); | |
| }, "Aborted promise should reject with the custom abort reason"); | |
| promise_test(async t => { | |
| const res = uniqueName(t); | |
| const controller = new AbortController(); | |
| const promise = requestLockAndHold(t, res, { signal: controller.signal }); | |
| controller.abort(); | |
| await promise_rejects_exactly(t, controller.signal.reason, promise, "Should be the same reason"); | |
| }, "Aborted promise should reject with the default abort reason"); |