mirror of
https://github.com/servo/servo.git
synced 2025-09-09 22:48:21 +01:00
Update web-platform-tests to revision 60220357131c65146444da1f54624d5b54d0975d
This commit is contained in:
parent
c45192614c
commit
775b784f79
2144 changed files with 58115 additions and 29658 deletions
4
tests/wpt/web-platform-tests/web-locks/META.yml
Normal file
4
tests/wpt/web-platform-tests/web-locks/META.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
spec: https://inexorabletash.github.io/web-locks/
|
||||
suggested_reviewers:
|
||||
- inexorabletash
|
||||
- pwnall
|
5
tests/wpt/web-platform-tests/web-locks/README.md
Normal file
5
tests/wpt/web-platform-tests/web-locks/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
This directory contains a test suite for the proposed Web Locks API.
|
||||
|
||||
Explainer: https://github.com/inexorabletash/web-locks
|
||||
|
||||
Spec: https://inexorabletash.github.io/web-locks/
|
|
@ -0,0 +1,130 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: navigator.locks.request method</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="resources/helpers.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
await promise_rejects(t, new TypeError(), navigator.locks.request());
|
||||
await promise_rejects(t, new TypeError(), navigator.locks.request(res));
|
||||
}, 'navigator.locks.request requires a name and a callback');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
await promise_rejects(
|
||||
t, new TypeError(),
|
||||
navigator.locks.request(res, {mode: 'foo'}, lock => {}));
|
||||
await promise_rejects(
|
||||
t, new TypeError(),
|
||||
navigator.locks.request(res, {mode: null }, lock => {}));
|
||||
assert_equals(await navigator.locks.request(
|
||||
res, {mode: 'exclusive'}, lock => lock.mode), 'exclusive',
|
||||
'mode is exclusive');
|
||||
assert_equals(await navigator.locks.request(
|
||||
res, {mode: 'shared'}, lock => lock.mode), 'shared',
|
||||
'mode is shared');
|
||||
}, 'mode must be "shared" or "exclusive"');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
await promise_rejects(
|
||||
t, 'NotSupportedError',
|
||||
navigator.locks.request(
|
||||
res, {steal: true, ifAvailable: true}, lock => {}),
|
||||
"A NotSupportedError should be thrown if both " +
|
||||
"'steal' and 'ifAvailable' are specified.");
|
||||
}, "The 'steal' and 'ifAvailable' options are mutually exclusive");
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
await promise_rejects(
|
||||
t, 'NotSupportedError',
|
||||
navigator.locks.request(res, {mode: 'shared', steal: true}, lock => {}),
|
||||
'Request with mode=shared and steal=true should fail');
|
||||
}, "The 'steal' option must be used with exclusive locks");
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
const controller = new AbortController();
|
||||
await promise_rejects(
|
||||
t, 'NotSupportedError',
|
||||
navigator.locks.request(
|
||||
res, {signal: controller.signal, steal: true}, lock => {}),
|
||||
'Request with signal and steal=true should fail');
|
||||
}, "The 'signal' and 'steal' options are mutually exclusive");
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
const controller = new AbortController();
|
||||
await promise_rejects(
|
||||
t, 'NotSupportedError',
|
||||
navigator.locks.request(
|
||||
res, {signal: controller.signal, ifAvailable: true}, lock => {}),
|
||||
'Request with signal and ifAvailable=true should fail');
|
||||
}, "The 'signal' and 'ifAvailable' options are mutually exclusive");
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
await promise_rejects(
|
||||
t, new TypeError(), navigator.locks.request(res, undefined));
|
||||
await promise_rejects(
|
||||
t, new TypeError(), navigator.locks.request(res, null));
|
||||
await promise_rejects(
|
||||
t, new TypeError(), navigator.locks.request(res, 123));
|
||||
await promise_rejects(
|
||||
t, new TypeError(), navigator.locks.request(res, 'abc'));
|
||||
await promise_rejects(
|
||||
t, new TypeError(), navigator.locks.request(res, []));
|
||||
await promise_rejects(
|
||||
t, new TypeError(), navigator.locks.request(res, {}));
|
||||
await promise_rejects(
|
||||
t, new TypeError(), navigator.locks.request(res, new Promise(r => {})));
|
||||
}, 'callback must be a function');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
let release;
|
||||
const promise = new Promise(r => { release = r; });
|
||||
|
||||
let returned = navigator.locks.request(res, lock => { return promise; });
|
||||
|
||||
const order = [];
|
||||
|
||||
returned.then(() => { order.push('returned'); });
|
||||
promise.then(() => { order.push('holding'); });
|
||||
|
||||
release();
|
||||
|
||||
await Promise.all([returned, promise]);
|
||||
|
||||
assert_array_equals(order, ['holding', 'returned']);
|
||||
|
||||
}, 'navigator.locks.request\'s returned promise resolves after' +
|
||||
' lock is released');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
const test_error = {name: 'test'};
|
||||
const p = navigator.locks.request(res, lock => {
|
||||
throw test_error;
|
||||
});
|
||||
assert_equals(Promise.resolve(p), p, 'request() result is a Promise');
|
||||
await promise_rejects(t, test_error, p, 'result should reject');
|
||||
}, 'Returned Promise rejects if callback throws synchronously');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
const test_error = {name: 'test'};
|
||||
const p = navigator.locks.request(res, async lock => {
|
||||
throw test_error;
|
||||
});
|
||||
assert_equals(Promise.resolve(p), p, 'request() result is a Promise');
|
||||
await promise_rejects(t, test_error, p, 'result should reject');
|
||||
}, 'Returned Promise rejects if callback throws asynchronously');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: Client IDs in query() vs. Service Worker</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
|
||||
<script>
|
||||
|
||||
// Returns a promise resolved by the next message event.
|
||||
function nextMessage() {
|
||||
return new Promise(resolve => {
|
||||
window.addEventListener('message', event => {
|
||||
resolve(event.data);
|
||||
}, {once: true});
|
||||
});
|
||||
}
|
||||
|
||||
promise_test(async t => {
|
||||
const iframe_url = 'resources/sw-controlled-iframe.html';
|
||||
|
||||
// Register a service worker that will control an iframe.
|
||||
const registration = await service_worker_unregister_and_register(
|
||||
t, 'resources/service-worker.js', iframe_url);
|
||||
await wait_for_state(t, registration.installing, 'activated');
|
||||
|
||||
const iframe = await with_iframe(iframe_url);
|
||||
|
||||
iframe.contentWindow.postMessage('get_sw_client_id', '*');
|
||||
const sw_client_id = await nextMessage();
|
||||
|
||||
iframe.contentWindow.postMessage('get_lock_client_id', '*');
|
||||
const lock_client_id = await nextMessage();
|
||||
|
||||
// NOTE: Not assert_equals(), as we don't want log the randomly generated
|
||||
// clientIds, since they would not match any failure expectation files.
|
||||
assert_true(lock_client_id === sw_client_id,
|
||||
'clientIds should match, but are different');
|
||||
|
||||
await registration.unregister();
|
||||
|
||||
}, 'Client IDs match between Locks API and Service Workers');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,239 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: Frames</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="resources/helpers.js"></script>
|
||||
<style>iframe { display: none; }</style>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
|
||||
const frame = await iframe('resources/iframe.html');
|
||||
t.add_cleanup(() => { frame.remove(); });
|
||||
|
||||
const lock_id = (await postToFrameAndWait(
|
||||
frame, {op: 'request', name: res, mode: 'shared'})).lock_id;
|
||||
|
||||
await navigator.locks.request(res, {mode: 'shared'}, async lock => {
|
||||
await postToFrameAndWait(frame, {op: 'release', lock_id});
|
||||
});
|
||||
|
||||
}, 'Window and Frame - shared mode');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
|
||||
const frame = await iframe('resources/iframe.html');
|
||||
t.add_cleanup(() => { frame.remove(); });
|
||||
|
||||
// frame acquires the lock.
|
||||
const lock_id = (await postToFrameAndWait(
|
||||
frame, {op: 'request', name: res})).lock_id;
|
||||
|
||||
// This request should be blocked.
|
||||
let lock_granted = false;
|
||||
const blocked = navigator.locks.request(res, lock => { lock_granted = true; });
|
||||
|
||||
// Verify that we can't get it.
|
||||
let available = undefined;
|
||||
await navigator.locks.request(
|
||||
res, {ifAvailable: true}, lock => { available = lock !== null; });
|
||||
assert_false(available);
|
||||
assert_false(lock_granted);
|
||||
|
||||
// Ask the frame to release it.
|
||||
await postToFrameAndWait(frame, {op: 'release', lock_id});
|
||||
|
||||
await blocked;
|
||||
// Now we've got it.
|
||||
assert_true(lock_granted);
|
||||
}, 'Window and Frame - exclusive mode');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
|
||||
const frame1 = await iframe('resources/iframe.html');
|
||||
const frame2 = await iframe('resources/iframe.html');
|
||||
|
||||
// frame1 acquires the lock.
|
||||
const lock_id = (await postToFrameAndWait(
|
||||
frame1, {op: 'request', name: res})).lock_id;
|
||||
|
||||
// frame2's request should be blocked.
|
||||
let lock_granted = false;
|
||||
const blocked = postToFrameAndWait(
|
||||
frame2, {op: 'request', name: res});
|
||||
blocked.then(f => { lock_granted = true; });
|
||||
|
||||
// Verify that frame2 can't get it.
|
||||
assert_true((await postToFrameAndWait(frame2, {
|
||||
op: 'request', name: res, ifAvailable: true
|
||||
})).failed, 'Lock request should have failed');
|
||||
assert_false(lock_granted);
|
||||
|
||||
// Ask frame1 to release it.
|
||||
await postToFrameAndWait(frame1, {op: 'release', lock_id});
|
||||
|
||||
await blocked;
|
||||
// Now frame2 can get it.
|
||||
assert_true(lock_granted);
|
||||
frame1.parentElement.removeChild(frame1);
|
||||
frame2.parentElement.removeChild(frame2);
|
||||
}, 'Frame and Frame - exclusive mode');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
|
||||
const frame = await iframe('resources/iframe.html');
|
||||
|
||||
// Frame acquires the lock.
|
||||
await postToFrameAndWait(frame, {op: 'request', name: res});
|
||||
|
||||
// This request should be blocked.
|
||||
let lock_granted = false;
|
||||
const blocked = navigator.locks.request(
|
||||
res, lock => { lock_granted = true; });
|
||||
|
||||
// Verify that we can't get it.
|
||||
let available = undefined;
|
||||
await navigator.locks.request(
|
||||
res, {ifAvailable: true}, lock => { available = lock !== null; });
|
||||
assert_false(available);
|
||||
assert_false(lock_granted);
|
||||
|
||||
// Implicitly release it by terminating the frame.
|
||||
frame.remove();
|
||||
await blocked;
|
||||
// Now we've got it.
|
||||
assert_true(lock_granted);
|
||||
|
||||
}, 'Terminated Frame with held lock');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
|
||||
const frame = await iframe('resources/iframe.html');
|
||||
|
||||
// Frame acquires the lock.
|
||||
await postToFrameAndWait(frame, {op: 'request', name: res});
|
||||
|
||||
// This request should be blocked.
|
||||
let lock_granted = false;
|
||||
const blocked = navigator.locks.request(
|
||||
res, lock => { lock_granted = true; });
|
||||
|
||||
// Verify that we can't get it.
|
||||
let available = undefined;
|
||||
await navigator.locks.request(
|
||||
res, {ifAvailable: true}, lock => { available = lock !== null; });
|
||||
assert_false(available);
|
||||
assert_false(lock_granted);
|
||||
|
||||
// Implicitly release it by navigating the frame.
|
||||
frame.src = 'about:blank';
|
||||
await blocked;
|
||||
// Now we've got it.
|
||||
assert_true(lock_granted);
|
||||
|
||||
}, 'Navigated Frame with held lock');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
|
||||
// frame1 requests and holds res - should be granted immediately.
|
||||
// frame2 requests res - should be blocked.
|
||||
// frame3 requests res - should be blocked.
|
||||
// frame2 is navigated.
|
||||
// frame1 releases res.
|
||||
// frame3's request should be granted.
|
||||
|
||||
const frame1 = await iframe('resources/iframe.html');
|
||||
const frame2 = await iframe('resources/iframe.html');
|
||||
const frame3 = await iframe('resources/iframe.html');
|
||||
t.add_cleanup(() => { frame1.remove(); });
|
||||
t.add_cleanup(() => { frame2.remove(); });
|
||||
t.add_cleanup(() => { frame3.remove(); });
|
||||
|
||||
// frame1 requests and holds res - should be granted immediately.
|
||||
const lock_id = (await postToFrameAndWait(
|
||||
frame1, {op: 'request', name: res})).lock_id;
|
||||
|
||||
// frame2 requests res - should be blocked.
|
||||
// (don't attach listeners as they will keep the frame alive)
|
||||
frame2.contentWindow.postMessage({op: 'request', name: res}, '*');
|
||||
|
||||
// frame3 requests res - should be blocked.
|
||||
let lock_granted = false;
|
||||
const blocked = postToFrameAndWait(frame3, {op: 'request', name: res});
|
||||
blocked.then(f => { lock_granted = true; });
|
||||
|
||||
// Verify that frame3 can't get it.
|
||||
assert_true((await postToFrameAndWait(frame3, {
|
||||
op: 'request', name: res, ifAvailable: true
|
||||
})).failed, 'Lock request should have failed');
|
||||
assert_false(lock_granted);
|
||||
|
||||
// Navigate frame2.
|
||||
frame2.src = 'about:blank';
|
||||
|
||||
// frame1 releases lock
|
||||
await postToFrameAndWait(frame1, {op: 'release', lock_id});
|
||||
|
||||
// frame3's request should be granted.
|
||||
await blocked;
|
||||
assert_true(lock_granted);
|
||||
|
||||
}, 'Navigated Frame with pending request');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
|
||||
// frame1 requests and holds res - should be granted immediately.
|
||||
// frame2 requests res - should be blocked.
|
||||
// frame3 requests res - should be blocked.
|
||||
// frame2 is removed.
|
||||
// frame1 drops lock.
|
||||
// frame3's request should be granted.
|
||||
|
||||
const frame1 = await iframe('resources/iframe.html');
|
||||
const frame2 = await iframe('resources/iframe.html');
|
||||
const frame3 = await iframe('resources/iframe.html');
|
||||
t.add_cleanup(() => { frame1.remove(); });
|
||||
t.add_cleanup(() => { frame3.remove(); });
|
||||
|
||||
// frame1 requests and holds res - should be granted immediately.
|
||||
const lock_id = (await postToFrameAndWait(
|
||||
frame1, {op: 'request', name: res})).lock_id;
|
||||
|
||||
// frame2 requests res - should be blocked.
|
||||
// (don't attach listeners as they will keep the frame alive)
|
||||
frame2.contentWindow.postMessage({op: 'request', name: res}, '*');
|
||||
|
||||
// frame3 requests res - should be blocked.
|
||||
let lock_granted = false;
|
||||
const blocked = postToFrameAndWait(frame3, {op: 'request', name: res});
|
||||
blocked.then(f => { lock_granted = true; });
|
||||
|
||||
// So frame3 can't get it
|
||||
assert_true((await postToFrameAndWait(frame3, {
|
||||
op: 'request', name: res, ifAvailable: true
|
||||
})).failed, 'Lock request should have failed');
|
||||
assert_false(lock_granted);
|
||||
|
||||
// Remove frame2.
|
||||
frame2.remove();
|
||||
|
||||
// frame1 releases lock
|
||||
await postToFrameAndWait(frame1, {op: 'release', lock_id});
|
||||
|
||||
// frame3's request should be granted.
|
||||
await blocked;
|
||||
assert_true(lock_granted);
|
||||
|
||||
}, 'Removed Frame with pending request');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,97 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: Lock held until callback result resolves</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="resources/helpers.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// For uncaught rejections.
|
||||
setup({allow_uncaught_exception: true});
|
||||
|
||||
function snooze(t, ms) { return new Promise(r => t.step_timeout(r, ms)); }
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
const p = navigator.locks.request(res, lock => 123);
|
||||
assert_equals(Promise.resolve(p), p, 'request() result is a Promise');
|
||||
assert_equals(await p, 123, 'promise resolves to the returned value');
|
||||
}, 'callback\'s result is promisified if not async');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
// Resolved when the lock is granted.
|
||||
let granted;
|
||||
const lock_granted_promise = new Promise(r => { granted = r; });
|
||||
|
||||
// Lock is held until this is resolved.
|
||||
let resolve;
|
||||
const lock_release_promise = new Promise(r => { resolve = r; });
|
||||
|
||||
const order = [];
|
||||
|
||||
navigator.locks.request(res, lock => {
|
||||
granted(lock);
|
||||
return lock_release_promise;
|
||||
});
|
||||
await lock_granted_promise;
|
||||
|
||||
await Promise.all([
|
||||
snooze(t, 50).then(() => {
|
||||
order.push('1st lock released');
|
||||
resolve();
|
||||
}),
|
||||
navigator.locks.request(res, () => {
|
||||
order.push('2nd lock granted');
|
||||
})
|
||||
]);
|
||||
|
||||
assert_array_equals(order, ['1st lock released', '2nd lock granted']);
|
||||
}, 'lock is held until callback\'s returned promise resolves');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
// Resolved when the lock is granted.
|
||||
let granted;
|
||||
const lock_granted_promise = new Promise(r => { granted = r; });
|
||||
|
||||
// Lock is held until this is rejected.
|
||||
let reject;
|
||||
const lock_release_promise = new Promise((_, r) => { reject = r; });
|
||||
|
||||
const order = [];
|
||||
|
||||
navigator.locks.request(res, lock => {
|
||||
granted(lock);
|
||||
return lock_release_promise;
|
||||
});
|
||||
await lock_granted_promise;
|
||||
|
||||
await Promise.all([
|
||||
snooze(t, 50).then(() => {
|
||||
order.push('reject');
|
||||
reject(new Error('this uncaught rejection is expected'));
|
||||
}),
|
||||
navigator.locks.request(res, () => {
|
||||
order.push('2nd lock granted');
|
||||
})
|
||||
]);
|
||||
|
||||
assert_array_equals(order, ['reject', '2nd lock granted']);
|
||||
}, 'lock is held until callback\'s returned promise rejects');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
let callback_called = false;
|
||||
await navigator.locks.request(res, async lock => {
|
||||
await navigator.locks.request(res, {ifAvailable: true}, lock => {
|
||||
callback_called = true;
|
||||
assert_equals(lock, null, 'lock request should fail if held');
|
||||
});
|
||||
});
|
||||
assert_true(callback_called, 'callback should have executed');
|
||||
}, 'held lock prevents the same client from acquiring it');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,169 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: ifAvailable option</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="resources/helpers.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
promise_test(async t => {
|
||||
const res = self.uniqueName(t);
|
||||
let callback_called = false;
|
||||
await navigator.locks.request(res, {ifAvailable: true}, async lock => {
|
||||
callback_called = true;
|
||||
assert_not_equals(lock, null, 'lock should be granted');
|
||||
});
|
||||
assert_true(callback_called, 'callback should be called');
|
||||
}, 'Lock request with ifAvailable - lock available');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = self.uniqueName(t);
|
||||
let callback_called = false;
|
||||
await navigator.locks.request(res, async lock => {
|
||||
// Request would time out if |ifAvailable| was not specified.
|
||||
const result = await navigator.locks.request(
|
||||
res, {ifAvailable: true}, async lock => {
|
||||
callback_called = true;
|
||||
assert_equals(lock, null, 'lock should not be granted');
|
||||
return 123;
|
||||
});
|
||||
assert_equals(result, 123, 'result should be value returned by callback');
|
||||
});
|
||||
assert_true(callback_called, 'callback should be called');
|
||||
}, 'Lock request with ifAvailable - lock not available');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = self.uniqueName(t);
|
||||
let callback_called = false;
|
||||
await navigator.locks.request(res, async lock => {
|
||||
try {
|
||||
// Request would time out if |ifAvailable| was not specified.
|
||||
await navigator.locks.request(res, {ifAvailable: true}, async lock => {
|
||||
callback_called = true;
|
||||
assert_equals(lock, null, 'lock should not be granted');
|
||||
throw 123;
|
||||
});
|
||||
assert_unreached('call should throw');
|
||||
} catch (ex) {
|
||||
assert_equals(ex, 123, 'ex should be value thrown by callback');
|
||||
}
|
||||
});
|
||||
assert_true(callback_called, 'callback should be called');
|
||||
}, 'Lock request with ifAvailable - lock not available, callback throws');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = self.uniqueName(t);
|
||||
let callback_called = false;
|
||||
await navigator.locks.request(res, async lock => {
|
||||
// Request with a different name - should be grantable.
|
||||
await navigator.locks.request('different', {ifAvailable: true}, async lock => {
|
||||
callback_called = true;
|
||||
assert_not_equals(lock, null, 'lock should be granted');
|
||||
});
|
||||
});
|
||||
assert_true(callback_called, 'callback should be called');
|
||||
}, 'Lock request with ifAvailable - unrelated lock held');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = self.uniqueName(t);
|
||||
let callback_called = false;
|
||||
await navigator.locks.request(res, {mode: 'shared'}, async lock => {
|
||||
await navigator.locks.request(
|
||||
res, {mode: 'shared', ifAvailable: true}, async lock => {
|
||||
callback_called = true;
|
||||
assert_not_equals(lock, null, 'lock should be granted');
|
||||
});
|
||||
});
|
||||
assert_true(callback_called, 'callback should be called');
|
||||
}, 'Shared lock request with ifAvailable - shared lock held');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = self.uniqueName(t);
|
||||
let callback_called = false;
|
||||
await navigator.locks.request(res, {mode: 'shared'}, async lock => {
|
||||
// Request would time out if |ifAvailable| was not specified.
|
||||
await navigator.locks.request(res, {ifAvailable: true}, async lock => {
|
||||
callback_called = true;
|
||||
assert_equals(lock, null, 'lock should not be granted');
|
||||
});
|
||||
});
|
||||
assert_true(callback_called, 'callback should be called');
|
||||
}, 'Exclusive lock request with ifAvailable - shared lock held');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = self.uniqueName(t);
|
||||
let callback_called = false;
|
||||
await navigator.locks.request(res, async lock => {
|
||||
// Request would time out if |ifAvailable| was not specified.
|
||||
await navigator.locks.request(
|
||||
res, {mode: 'shared', ifAvailable: true}, async lock => {
|
||||
callback_called = true;
|
||||
assert_equals(lock, null, 'lock should not be granted');
|
||||
});
|
||||
});
|
||||
assert_true(callback_called, 'callback should be called');
|
||||
}, 'Shared lock request with ifAvailable - exclusive lock held');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = self.uniqueName(t);
|
||||
let callback_called = false;
|
||||
await navigator.locks.request(res, async lock => {
|
||||
callback_called = true;
|
||||
const test_error = {name: 'test'};
|
||||
const p = navigator.locks.request(
|
||||
res, {ifAvailable: true}, lock => {
|
||||
assert_equals(lock, null, 'lock should not be available');
|
||||
throw test_error;
|
||||
});
|
||||
assert_equals(Promise.resolve(p), p, 'request() result is a Promise');
|
||||
await promise_rejects(t, test_error, p, 'result should reject');
|
||||
});
|
||||
assert_true(callback_called, 'callback should be called');
|
||||
}, 'Returned Promise rejects if callback throws synchronously');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = self.uniqueName(t);
|
||||
let callback_called = false;
|
||||
await navigator.locks.request(res, async lock => {
|
||||
callback_called = true;
|
||||
const test_error = {name: 'test'};
|
||||
const p = navigator.locks.request(
|
||||
res, {ifAvailable: true}, async lock => {
|
||||
assert_equals(lock, null, 'lock should not be available');
|
||||
throw test_error;
|
||||
});
|
||||
assert_equals(Promise.resolve(p), p, 'request() result is a Promise');
|
||||
await promise_rejects(t, test_error, p, 'result should reject');
|
||||
});
|
||||
assert_true(callback_called, 'callback should be called');
|
||||
}, 'Returned Promise rejects if async callback yields rejected promise');
|
||||
|
||||
// Regression test for: https://crbug.com/840994
|
||||
promise_test(async t => {
|
||||
const res1 = self.uniqueName(t);
|
||||
const res2 = self.uniqueName(t);
|
||||
let callback1_called = false;
|
||||
await navigator.locks.request(res1, async lock => {
|
||||
callback1_called = true;
|
||||
let callback2_called = false;
|
||||
await navigator.locks.request(res2, async lock => {
|
||||
callback2_called = true;
|
||||
});
|
||||
assert_true(callback2_called, 'callback2 should be called');
|
||||
|
||||
let callback3_called = false;
|
||||
await navigator.locks.request(res2, {ifAvailable: true}, async lock => {
|
||||
callback3_called = true;
|
||||
// This request would fail if the "is this grantable?" test
|
||||
// failed, e.g. due to the release without a pending request
|
||||
// skipping steps.
|
||||
assert_not_equals(lock, null, 'Lock should be available');
|
||||
});
|
||||
assert_true(callback3_called, 'callback2 should be called');
|
||||
});
|
||||
assert_true(callback1_called, 'callback1 should be called');
|
||||
}, 'Locks are available once previous release is processed');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: WebIDL tests in service worker</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
(async () => {
|
||||
const scope = 'resources/does/not/exist';
|
||||
|
||||
let registration = await navigator.serviceWorker.getRegistration(scope);
|
||||
if (registration)
|
||||
await registration.unregister();
|
||||
registration = await navigator.serviceWorker.register(
|
||||
'resources/interfaces-serviceworker.js', {scope});
|
||||
|
||||
fetch_tests_from_worker(registration.installing);
|
||||
})();
|
||||
</script>
|
46
tests/wpt/web-platform-tests/web-locks/interfaces.idl
Normal file
46
tests/wpt/web-platform-tests/web-locks/interfaces.idl
Normal file
|
@ -0,0 +1,46 @@
|
|||
|
||||
enum LockMode { "shared", "exclusive" };
|
||||
|
||||
dictionary LockOptions {
|
||||
LockMode mode = "exclusive";
|
||||
boolean ifAvailable = false;
|
||||
};
|
||||
|
||||
callback LockGrantedCallback = any (Lock lock);
|
||||
|
||||
[Exposed=Window]
|
||||
partial interface Navigator {
|
||||
[SecureContext] readonly attribute LockManager locks;
|
||||
};
|
||||
|
||||
[Exposed=Worker]
|
||||
partial interface WorkerNavigator {
|
||||
[SecureContext] readonly attribute LockManager locks;
|
||||
};
|
||||
|
||||
[Exposed=(Window,Worker), SecureContext]
|
||||
interface LockManager {
|
||||
Promise<any> request(DOMString name,
|
||||
LockGrantedCallback callback);
|
||||
Promise<any> request(DOMString name,
|
||||
LockOptions options,
|
||||
LockGrantedCallback callback);
|
||||
|
||||
Promise<LockManagerSnapshot> query();
|
||||
};
|
||||
|
||||
[Exposed=(Window,Worker), SecureContext]
|
||||
interface Lock {
|
||||
readonly attribute DOMString name;
|
||||
readonly attribute LockMode mode;
|
||||
};
|
||||
|
||||
dictionary LockManagerSnapshot {
|
||||
sequence<LockInfo> pending;
|
||||
sequence<LockInfo> held;
|
||||
};
|
||||
|
||||
dictionary LockInfo {
|
||||
DOMString name;
|
||||
LockMode mode;
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
// META: script=/resources/WebIDLParser.js
|
||||
// META: script=/resources/idlharness.js
|
||||
|
||||
'use strict';
|
||||
|
||||
promise_test(async t => {
|
||||
const response = await fetch('interfaces.idl');
|
||||
const idls = await response.text();
|
||||
|
||||
const idl_array = new IdlArray();
|
||||
|
||||
idl_array.add_untested_idls('[Exposed=Window] interface Navigator {};');
|
||||
idl_array.add_untested_idls('[Exposed=Worker] interface WorkerNavigator {};');
|
||||
|
||||
idl_array.add_idls(idls);
|
||||
|
||||
let lock;
|
||||
await navigator.locks.request('name', l => { lock = l; });
|
||||
|
||||
idl_array.add_objects({
|
||||
LockManager: [navigator.locks],
|
||||
Lock: [lock],
|
||||
});
|
||||
|
||||
idl_array.test();
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: Lock Attributes</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
promise_test(async t => {
|
||||
await navigator.locks.request('resource', lock => {
|
||||
assert_equals(lock.name, 'resource');
|
||||
assert_equals(lock.mode, 'exclusive');
|
||||
});
|
||||
}, 'Lock attributes reflect requested properties (exclusive)');
|
||||
|
||||
promise_test(async t => {
|
||||
await navigator.locks.request('resource', {mode: 'shared'}, lock => {
|
||||
assert_equals(lock.name, 'resource');
|
||||
assert_equals(lock.mode, 'shared');
|
||||
});
|
||||
}, 'Lock attributes reflect requested properties (shared)');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,40 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: Exclusive Mode</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
promise_test(async t => {
|
||||
const granted = [];
|
||||
function log_grant(n) { return () => { granted.push(n); }; }
|
||||
|
||||
await Promise.all([
|
||||
navigator.locks.request('a', log_grant(1)),
|
||||
navigator.locks.request('a', log_grant(2)),
|
||||
navigator.locks.request('a', log_grant(3))
|
||||
]);
|
||||
assert_array_equals(granted, [1, 2, 3]);
|
||||
}, 'Lock requests are granted in order');
|
||||
|
||||
promise_test(async t => {
|
||||
const granted = [];
|
||||
function log_grant(n) { return () => { granted.push(n); }; }
|
||||
|
||||
let inner_promise;
|
||||
await navigator.locks.request('a', async lock => {
|
||||
inner_promise = Promise.all([
|
||||
// This will be blocked.
|
||||
navigator.locks.request('a', log_grant(1)),
|
||||
// But this should be grantable immediately.
|
||||
navigator.locks.request('b', log_grant(2))
|
||||
]);
|
||||
});
|
||||
|
||||
await inner_promise;
|
||||
assert_array_equals(granted, [2, 1]);
|
||||
}, 'Requests for distinct resources can be granted');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,51 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: Mixed Modes</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
promise_test(async t => {
|
||||
let unblock;
|
||||
const blocked = new Promise(r => { unblock = r; });
|
||||
|
||||
const granted = [];
|
||||
|
||||
// These should be granted immediately, and held until unblocked.
|
||||
navigator.locks.request('a', {mode: 'shared'}, async lock => {
|
||||
granted.push('a-shared-1'); await blocked; });
|
||||
navigator.locks.request('a', {mode: 'shared'}, async lock => {
|
||||
granted.push('a-shared-2'); await blocked; });
|
||||
navigator.locks.request('a', {mode: 'shared'}, async lock => {
|
||||
granted.push('a-shared-3'); await blocked; });
|
||||
|
||||
// This should be blocked.
|
||||
let exclusive_lock;
|
||||
const exclusive_request = navigator.locks.request('a', async lock => {
|
||||
granted.push('a-exclusive');
|
||||
exclusive_lock = lock;
|
||||
});
|
||||
|
||||
// This should be granted immediately (different name).
|
||||
await navigator.locks.request('b', {mode: 'exclusive'}, lock => {
|
||||
granted.push('b-exclusive'); });
|
||||
|
||||
assert_array_equals(
|
||||
granted, ['a-shared-1', 'a-shared-2', 'a-shared-3', 'b-exclusive']);
|
||||
|
||||
// Release the shared locks granted above.
|
||||
unblock();
|
||||
|
||||
// Now the blocked request can be granted.
|
||||
await exclusive_request;
|
||||
assert_equals(exclusive_lock.mode, 'exclusive');
|
||||
|
||||
assert_array_equals(
|
||||
granted,
|
||||
['a-shared-1', 'a-shared-2', 'a-shared-3', 'b-exclusive', 'a-exclusive']);
|
||||
|
||||
}, 'Lock requests are granted in order');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: Shared Mode</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
promise_test(async t => {
|
||||
const granted = [];
|
||||
function log_grant(n) { return () => { granted.push(n); }; }
|
||||
|
||||
await Promise.all([
|
||||
navigator.locks.request('a', {mode: 'shared'}, log_grant(1)),
|
||||
navigator.locks.request('b', {mode: 'shared'}, log_grant(2)),
|
||||
navigator.locks.request('c', {mode: 'shared'}, log_grant(3)),
|
||||
navigator.locks.request('a', {mode: 'shared'}, log_grant(4)),
|
||||
navigator.locks.request('b', {mode: 'shared'}, log_grant(5)),
|
||||
navigator.locks.request('c', {mode: 'shared'}, log_grant(6)),
|
||||
]);
|
||||
|
||||
assert_array_equals(granted, [1, 2, 3, 4, 5, 6]);
|
||||
}, 'Lock requests are granted in order');
|
||||
|
||||
promise_test(async t => {
|
||||
let a_acquired = false, a_acquired_again = false;
|
||||
|
||||
await navigator.locks.request('a', {mode: 'shared'}, async lock => {
|
||||
a_acquired = true;
|
||||
|
||||
// Since lock is held, this request would be blocked if the
|
||||
// lock was not 'shared', causing this test to time out.
|
||||
|
||||
await navigator.locks.request('a', {mode: 'shared'}, lock => {
|
||||
a_acquired_again = true;
|
||||
});
|
||||
});
|
||||
|
||||
assert_true(a_acquired, 'first lock acquired');
|
||||
assert_true(a_acquired_again, 'second lock acquired');
|
||||
}, 'Shared locks are not exclusive');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: API not available in non-secure context</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
test(t => {
|
||||
assert_false(window.isSecureContext);
|
||||
assert_false('locks' in navigator,
|
||||
'navigator.locks is only present in secure contexts');
|
||||
assert_false('LockManager' in self,
|
||||
'LockManager is only present in secure contexts');
|
||||
assert_false('Lock' in self,
|
||||
'Lock interface is only present in secure contexts');
|
||||
}, 'API presence in non-secure contexts');
|
||||
</script>
|
|
@ -0,0 +1,61 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: Opaque origins</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
|
||||
function load_iframe(src, sandbox) {
|
||||
return new Promise(resolve => {
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.onload = () => { resolve(iframe); };
|
||||
if (sandbox)
|
||||
iframe.sandbox = sandbox;
|
||||
iframe.srcdoc = src;
|
||||
iframe.style.display = 'none';
|
||||
document.documentElement.appendChild(iframe);
|
||||
});
|
||||
}
|
||||
|
||||
function wait_for_message(iframe) {
|
||||
return new Promise(resolve => {
|
||||
self.addEventListener('message', function listener(e) {
|
||||
if (e.source === iframe.contentWindow) {
|
||||
resolve(e.data);
|
||||
self.removeEventListener('message', listener);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const script = `
|
||||
<script>
|
||||
"use strict";
|
||||
window.onmessage = async () => {
|
||||
try {
|
||||
await navigator.locks.request('name', lock => {});
|
||||
window.parent.postMessage({result: "no exception"}, "*");
|
||||
} catch (ex) {
|
||||
window.parent.postMessage({result: ex.name}, "*");
|
||||
};
|
||||
};
|
||||
<\/script>
|
||||
`;
|
||||
|
||||
promise_test(async t => {
|
||||
const iframe = await load_iframe(script);
|
||||
iframe.contentWindow.postMessage({}, '*');
|
||||
const message = await wait_for_message(iframe);
|
||||
assert_equals(message.result, 'no exception',
|
||||
'navigator.locks.request() should not throw');
|
||||
}, 'navigator.locks.request() in non-sandboxed iframe should not throw');
|
||||
|
||||
promise_test(async t => {
|
||||
const iframe = await load_iframe(script, 'allow-scripts');
|
||||
iframe.contentWindow.postMessage({}, '*');
|
||||
const message = await wait_for_message(iframe);
|
||||
assert_equals(message.result, 'SecurityError',
|
||||
'Exception should be SecurityError');
|
||||
}, 'navigator.locks.request() in sandboxed iframe should throw SecurityError');
|
||||
</script>
|
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: navigator.locks.query method - no locks held</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="resources/helpers.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
promise_test(async t => {
|
||||
const state = await navigator.locks.query();
|
||||
|
||||
assert_own_property(state, 'pending', 'State has `pending` property');
|
||||
assert_true(Array.isArray(state.pending),
|
||||
'State `pending` property is an array');
|
||||
assert_array_equals(state.pending, [], 'Pending array is empty');
|
||||
|
||||
assert_own_property(state, 'held', 'State has `held` property');
|
||||
assert_true(Array.isArray(state.held), 'State `held` property is an array');
|
||||
assert_array_equals(state.held, [], 'Held array is empty');
|
||||
}, 'query() returns dictionary with empty arrays when no locks are held');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,116 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: navigator.locks.query ordering</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="resources/helpers.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Grab a lock and hold until a release function is called. Resolves
|
||||
// to a release function.
|
||||
function getLockAndHoldUntilReleased(name, options) {
|
||||
let release;
|
||||
const promise = new Promise(resolve => { release = resolve; });
|
||||
return new Promise(resolve => {
|
||||
navigator.locks.request(name, options || {}, lock => {
|
||||
resolve(release);
|
||||
return promise;
|
||||
}).catch(_ => {});
|
||||
});
|
||||
}
|
||||
|
||||
promise_test(async t => {
|
||||
const res1 = uniqueName(t);
|
||||
const res2 = uniqueName(t);
|
||||
const res3 = uniqueName(t);
|
||||
|
||||
// These will never be released.
|
||||
await Promise.all([
|
||||
getLockAndHoldUntilReleased(res1),
|
||||
getLockAndHoldUntilReleased(res2),
|
||||
getLockAndHoldUntilReleased(res3)
|
||||
]);
|
||||
|
||||
// These requests should be blocked.
|
||||
navigator.locks.request(res3, {mode: 'shared'}, lock => {});
|
||||
navigator.locks.request(res2, {mode: 'shared'}, lock => {});
|
||||
navigator.locks.request(res1, {mode: 'shared'}, lock => {});
|
||||
|
||||
const state = await navigator.locks.query();
|
||||
|
||||
const relevant_pending_names = state.pending.map(lock => lock.name)
|
||||
.filter(name => [res1, res2, res3].includes(name));
|
||||
|
||||
assert_array_equals(relevant_pending_names, [res3, res2, res1],
|
||||
'Pending locks should appear in order.');
|
||||
}, 'Requests appear in state in order made');
|
||||
|
||||
promise_test(async t => {
|
||||
const res1 = uniqueName(t);
|
||||
const res2 = uniqueName(t);
|
||||
const res3 = uniqueName(t);
|
||||
|
||||
// These should be granted, and will be held until released.
|
||||
const [release1, release2, release3] = await Promise.all([
|
||||
getLockAndHoldUntilReleased(res1),
|
||||
getLockAndHoldUntilReleased(res2),
|
||||
getLockAndHoldUntilReleased(res3)
|
||||
]);
|
||||
|
||||
// These requests should be blocked.
|
||||
const requests = [
|
||||
getLockAndHoldUntilReleased(res1),
|
||||
getLockAndHoldUntilReleased(res2),
|
||||
getLockAndHoldUntilReleased(res3)
|
||||
];
|
||||
|
||||
// Ensure the requests have had a chance to get queued by
|
||||
// waiting for something else to make it through the queue.
|
||||
await navigator.locks.request(uniqueName(t), lock => {});
|
||||
|
||||
// Now release the previous holders.
|
||||
release2();
|
||||
release3();
|
||||
release1();
|
||||
|
||||
// Wait until the subsequent requests make it through.
|
||||
await Promise.all(requests);
|
||||
|
||||
const state = await navigator.locks.query();
|
||||
const relevant_held_names = state.held.map(lock => lock.name)
|
||||
.filter(name => [res1, res2, res3].includes(name));
|
||||
|
||||
assert_array_equals(relevant_held_names, [res2, res3, res1],
|
||||
'Held locks should appear in granted order.');
|
||||
}, 'Held locks appear in state in order granted');
|
||||
|
||||
promise_test(async t => {
|
||||
const res1 = uniqueName(t);
|
||||
const res2 = uniqueName(t);
|
||||
const res3 = uniqueName(t);
|
||||
|
||||
// These should be granted, and will be held until stolen.
|
||||
await Promise.all([
|
||||
getLockAndHoldUntilReleased(res1),
|
||||
getLockAndHoldUntilReleased(res2),
|
||||
getLockAndHoldUntilReleased(res3)
|
||||
]);
|
||||
|
||||
// Steal in a different order.
|
||||
await Promise.all([
|
||||
getLockAndHoldUntilReleased(res3, {steal: true}),
|
||||
getLockAndHoldUntilReleased(res1, {steal: true}),
|
||||
getLockAndHoldUntilReleased(res2, {steal: true})
|
||||
]);
|
||||
|
||||
const state = await navigator.locks.query();
|
||||
const relevant_held_names = state.held.map(lock => lock.name)
|
||||
.filter(name => [res1, res2, res3].includes(name));
|
||||
|
||||
assert_array_equals(relevant_held_names, [res3, res1, res2],
|
||||
'Held locks should appear in granted order.');
|
||||
}, 'Held locks appear in state in order granted, including when stolen');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,234 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: navigator.locks.query method</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="resources/helpers.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Returns an array of the modes for the locks with matching name.
|
||||
function modes(list, name) {
|
||||
return list.filter(item => item.name === name).map(item => item.mode);
|
||||
}
|
||||
// Returns an array of the clientIds for the locks with matching name.
|
||||
function clients(list, name) {
|
||||
return list.filter(item => item.name === name).map(item => item.clientId);
|
||||
}
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
|
||||
await navigator.locks.request(res, async lock1 => {
|
||||
// Attempt to request this again - should be blocked.
|
||||
let lock2_acquired = false;
|
||||
navigator.locks.request(res, lock2 => { lock2_acquired = true; });
|
||||
|
||||
// Verify that it was blocked.
|
||||
await navigator.locks.request(res, {ifAvailable: true}, async lock3 => {
|
||||
assert_false(lock2_acquired, 'second request should be blocked');
|
||||
assert_equals(lock3, null, 'third request should have failed');
|
||||
|
||||
const state = await navigator.locks.query();
|
||||
|
||||
assert_own_property(state, 'pending', 'State has `pending` property');
|
||||
assert_true(Array.isArray(state.pending),
|
||||
'State `pending` property is an array');
|
||||
const pending_info = state.pending[0];
|
||||
assert_own_property(pending_info, 'name',
|
||||
'Pending info dictionary has `name` property');
|
||||
assert_own_property(pending_info, 'mode',
|
||||
'Pending info dictionary has `mode` property');
|
||||
assert_own_property(pending_info, 'clientId',
|
||||
'Pending info dictionary has `clientId` property');
|
||||
|
||||
assert_own_property(state, 'held', 'State has `held` property');
|
||||
assert_true(Array.isArray(state.held),
|
||||
'State `held` property is an array');
|
||||
const held_info = state.held[0];
|
||||
assert_own_property(held_info, 'name',
|
||||
'Held info dictionary has `name` property');
|
||||
assert_own_property(held_info, 'mode',
|
||||
'Held info dictionary has `mode` property');
|
||||
assert_own_property(held_info, 'clientId',
|
||||
'Held info dictionary has `clientId` property');
|
||||
});
|
||||
});
|
||||
}, 'query() returns dictionaries with expected properties');
|
||||
|
||||
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
|
||||
await navigator.locks.request(res, async lock1 => {
|
||||
const state = await navigator.locks.query();
|
||||
assert_array_equals(modes(state.held, res), ['exclusive'],
|
||||
'Held lock should appear once');
|
||||
});
|
||||
|
||||
await navigator.locks.request(res, {mode: 'shared'}, async lock1 => {
|
||||
const state = await navigator.locks.query();
|
||||
assert_array_equals(modes(state.held, res), ['shared'],
|
||||
'Held lock should appear once');
|
||||
});
|
||||
}, 'query() reports individual held locks');
|
||||
|
||||
promise_test(async t => {
|
||||
const res1 = uniqueName(t);
|
||||
const res2 = uniqueName(t);
|
||||
|
||||
await navigator.locks.request(res1, async lock1 => {
|
||||
await navigator.locks.request(res2, {mode: 'shared'}, async lock2 => {
|
||||
const state = await navigator.locks.query();
|
||||
assert_array_equals(modes(state.held, res1), ['exclusive'],
|
||||
'Held lock should appear once');
|
||||
assert_array_equals(modes(state.held, res2), ['shared'],
|
||||
'Held lock should appear once');
|
||||
});
|
||||
});
|
||||
}, 'query() reports multiple held locks');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
|
||||
await navigator.locks.request(res, async lock1 => {
|
||||
// Attempt to request this again - should be blocked.
|
||||
let lock2_acquired = false;
|
||||
navigator.locks.request(res, lock2 => { lock2_acquired = true; });
|
||||
|
||||
// Verify that it was blocked.
|
||||
await navigator.locks.request(res, {ifAvailable: true}, async lock3 => {
|
||||
assert_false(lock2_acquired, 'second request should be blocked');
|
||||
assert_equals(lock3, null, 'third request should have failed');
|
||||
|
||||
const state = await navigator.locks.query();
|
||||
assert_array_equals(modes(state.pending, res), ['exclusive'],
|
||||
'Pending lock should appear once');
|
||||
assert_array_equals(modes(state.held, res), ['exclusive'],
|
||||
'Held lock should appear once');
|
||||
});
|
||||
});
|
||||
}, 'query() reports pending and held locks');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
|
||||
await navigator.locks.request(res, {mode: 'shared'}, async lock1 => {
|
||||
await navigator.locks.request(res, {mode: 'shared'}, async lock2 => {
|
||||
const state = await navigator.locks.query();
|
||||
assert_array_equals(modes(state.held, res), ['shared', 'shared'],
|
||||
'Held lock should appear twice');
|
||||
});
|
||||
});
|
||||
}, 'query() reports held shared locks with appropriate count');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
|
||||
await navigator.locks.request(res, async lock1 => {
|
||||
let lock2_acquired = false, lock3_acquired = false;
|
||||
navigator.locks.request(res, {mode: 'shared'},
|
||||
lock2 => { lock2_acquired = true; });
|
||||
navigator.locks.request(res, {mode: 'shared'},
|
||||
lock3 => { lock3_acquired = true; });
|
||||
|
||||
await navigator.locks.request(res, {ifAvailable: true}, async lock4 => {
|
||||
assert_equals(lock4, null, 'lock should not be available');
|
||||
assert_false(lock2_acquired, 'second attempt should be blocked');
|
||||
assert_false(lock3_acquired, 'third attempt should be blocked');
|
||||
|
||||
const state = await navigator.locks.query();
|
||||
assert_array_equals(modes(state.held, res), ['exclusive'],
|
||||
'Held lock should appear once');
|
||||
|
||||
assert_array_equals(modes(state.pending, res), ['shared', 'shared'],
|
||||
'Pending lock should appear twice');
|
||||
});
|
||||
});
|
||||
}, 'query() reports pending shared locks with appropriate count');
|
||||
|
||||
promise_test(async t => {
|
||||
const res1 = uniqueName(t);
|
||||
const res2 = uniqueName(t);
|
||||
|
||||
await navigator.locks.request(res1, async lock1 => {
|
||||
await navigator.locks.request(res2, async lock2 => {
|
||||
const state = await navigator.locks.query();
|
||||
|
||||
const res1_clients = clients(state.held, res1);
|
||||
const res2_clients = clients(state.held, res2);
|
||||
|
||||
assert_equals(res1_clients.length, 1, 'Each lock should have one holder');
|
||||
assert_equals(res2_clients.length, 1, 'Each lock should have one holder');
|
||||
|
||||
assert_array_equals(res1_clients, res2_clients,
|
||||
'Both locks should have same clientId');
|
||||
});
|
||||
});
|
||||
}, 'query() reports the same clientId for held locks from the same context');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
|
||||
const worker = new Worker('resources/worker.js');
|
||||
t.add_cleanup(() => { worker.terminate(); });
|
||||
|
||||
await postToWorkerAndWait(
|
||||
worker, {op: 'request', name: res, mode: 'shared'});
|
||||
|
||||
await navigator.locks.request(res, {mode: 'shared'}, async lock => {
|
||||
const state = await navigator.locks.query();
|
||||
const res_clients = clients(state.held, res);
|
||||
assert_equals(res_clients.length, 2, 'Clients should have same resource');
|
||||
assert_not_equals(res_clients[0], res_clients[1],
|
||||
'Clients should have different ids');
|
||||
});
|
||||
}, 'query() reports different ids for held locks from different contexts');
|
||||
|
||||
promise_test(async t => {
|
||||
const res1 = uniqueName(t);
|
||||
const res2 = uniqueName(t);
|
||||
|
||||
const worker = new Worker('resources/worker.js');
|
||||
t.add_cleanup(() => { worker.terminate(); });
|
||||
|
||||
// Acquire 1 in the worker.
|
||||
await postToWorkerAndWait(worker, {op: 'request', name: res1})
|
||||
|
||||
// Acquire 2 here.
|
||||
await new Promise(resolve => {
|
||||
navigator.locks.request(res2, lock => {
|
||||
resolve();
|
||||
return new Promise(() => {}); // Never released.
|
||||
});
|
||||
});
|
||||
|
||||
// Request 2 in the worker.
|
||||
postToWorkerAndWait(worker, {op: 'request', name: res2});
|
||||
assert_true((await postToWorkerAndWait(worker, {
|
||||
op: 'request', name: res2, ifAvailable: true
|
||||
})).failed, 'Lock request should have failed');
|
||||
|
||||
// Request 1 here.
|
||||
navigator.locks.request(
|
||||
res1, t.unreached_func('Lock should not be acquired'));
|
||||
|
||||
// Verify that we're seeing a deadlock.
|
||||
const state = await navigator.locks.query();
|
||||
const res1_held_clients = clients(state.held, res1);
|
||||
const res2_held_clients = clients(state.held, res2);
|
||||
const res1_pending_clients = clients(state.pending, res1);
|
||||
const res2_pending_clients = clients(state.pending, res2);
|
||||
|
||||
assert_equals(res1_held_clients.length, 1);
|
||||
assert_equals(res2_held_clients.length, 1);
|
||||
assert_equals(res1_pending_clients.length, 1);
|
||||
assert_equals(res2_pending_clients.length, 1);
|
||||
|
||||
assert_equals(res1_held_clients[0], res2_pending_clients[0]);
|
||||
assert_equals(res2_held_clients[0], res1_pending_clients[0]);
|
||||
}, 'query() can observe a deadlock');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,62 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: Resources DOMString edge cases</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
function code_points(s) {
|
||||
return [...s]
|
||||
.map(c => '0x' + c.charCodeAt(0).toString(16).toUpperCase())
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
[
|
||||
'', // Empty strings
|
||||
'abc\x00def', // Embedded NUL
|
||||
'\uD800', // Unpaired low surrogage
|
||||
'\uDC00', // Unpaired high surrogage
|
||||
'\uDC00\uD800', // Swapped surrogate pair
|
||||
'\uFFFF' // Non-character
|
||||
].forEach(string => {
|
||||
promise_test(async t => {
|
||||
await navigator.locks.request(string, lock => {
|
||||
assert_equals(lock.name, string,
|
||||
'Requested name matches granted name');
|
||||
});
|
||||
}, 'DOMString: ' + code_points(string));
|
||||
});
|
||||
|
||||
promise_test(async t => {
|
||||
// '\uD800' treated as a USVString would become '\uFFFD'.
|
||||
await navigator.locks.request('\uD800', async lock => {
|
||||
assert_equals(lock.name, '\uD800');
|
||||
|
||||
// |lock| is held for the duration of this name. It
|
||||
// Should not block acquiring |lock2| with a distinct
|
||||
// DOMString.
|
||||
await navigator.locks.request('\uFFFD', lock2 => {
|
||||
assert_equals(lock2.name, '\uFFFD');
|
||||
});
|
||||
|
||||
// If we did not time out, this passed.
|
||||
});
|
||||
}, 'Resource names that are not valid UTF-16 are not mangled');
|
||||
|
||||
promise_test(async t => {
|
||||
for (const name of ['-', '-foo']) {
|
||||
await promise_rejects(
|
||||
t, 'NotSupportedError',
|
||||
navigator.locks.request(name, lock => {}),
|
||||
'Names starting with "-" should be rejected');
|
||||
}
|
||||
let got_lock = false;
|
||||
await navigator.locks.request('x-anything', lock => {
|
||||
got_lock = true;
|
||||
});
|
||||
assert_true(got_lock, 'Names with embedded "-" should be accepted');
|
||||
}, 'Names cannot start with "-"');
|
||||
|
||||
</script>
|
60
tests/wpt/web-platform-tests/web-locks/resources/helpers.js
Normal file
60
tests/wpt/web-platform-tests/web-locks/resources/helpers.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
// Test helpers used by multiple Web Locks API tests.
|
||||
(() => {
|
||||
|
||||
// Generate a unique resource identifier, using the script path and
|
||||
// test case name. This is useful to avoid lock interference between
|
||||
// test cases.
|
||||
let res_num = 0;
|
||||
self.uniqueName = testCase => {
|
||||
return `${self.location.pathname}-${testCase.name}-${++res_num}`;
|
||||
};
|
||||
|
||||
// Inject an iframe showing the given url into the page, and resolve
|
||||
// the returned promise when the frame is loaded.
|
||||
self.iframe = url => new Promise(resolve => {
|
||||
const element = document.createElement('iframe');
|
||||
element.addEventListener(
|
||||
'load', () => { resolve(element); }, { once: true });
|
||||
element.src = url;
|
||||
document.documentElement.appendChild(element);
|
||||
});
|
||||
|
||||
// Post a message to the target frame, and resolve the returned
|
||||
// promise when a response comes back. The posted data is annotated
|
||||
// with unique id to track the response. This assumes the use of
|
||||
// 'iframe.html' as the frame, which implements this protocol.
|
||||
let next_request_id = 0;
|
||||
self.postToFrameAndWait = (frame, data) => {
|
||||
const iframe_window = frame.contentWindow;
|
||||
data.rqid = next_request_id++;
|
||||
iframe_window.postMessage(data, '*');
|
||||
return new Promise(resolve => {
|
||||
const listener = event => {
|
||||
if (event.source !== iframe_window || event.data.rqid !== data.rqid)
|
||||
return;
|
||||
self.removeEventListener('message', listener);
|
||||
resolve(event.data);
|
||||
};
|
||||
self.addEventListener('message', listener);
|
||||
});
|
||||
};
|
||||
|
||||
// Post a message to the target worker, and resolve the returned
|
||||
// promise when a response comes back. The posted data is annotated
|
||||
// with unique id to track the response. This assumes the use of
|
||||
// 'worker.js' as the worker, which implements this protocol.
|
||||
self.postToWorkerAndWait = (worker, data) => {
|
||||
return new Promise(resolve => {
|
||||
data.rqid = next_request_id++;
|
||||
worker.postMessage(data);
|
||||
const listener = event => {
|
||||
if (event.data.rqid !== data.rqid)
|
||||
return;
|
||||
worker.removeEventListener('message', listener);
|
||||
resolve(event.data);
|
||||
};
|
||||
worker.addEventListener('message', listener);
|
||||
});
|
||||
};
|
||||
|
||||
})();
|
43
tests/wpt/web-platform-tests/web-locks/resources/iframe.html
Normal file
43
tests/wpt/web-platform-tests/web-locks/resources/iframe.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<title>Helper IFrame</title>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Map of lock_id => function that releases a lock.
|
||||
|
||||
const held = new Map();
|
||||
let next_lock_id = 1;
|
||||
|
||||
self.addEventListener('message', e => {
|
||||
function respond(data) {
|
||||
parent.postMessage(Object.assign(data, {rqid: e.data.rqid}), '*');
|
||||
}
|
||||
|
||||
switch (e.data.op) {
|
||||
case 'request':
|
||||
navigator.locks.request(
|
||||
e.data.name, {
|
||||
mode: e.data.mode || 'exclusive',
|
||||
ifAvailable: e.data.ifAvailable || false
|
||||
}, lock => {
|
||||
if (lock === null) {
|
||||
respond({ack: 'request', failed: true});
|
||||
return;
|
||||
}
|
||||
let lock_id = next_lock_id++;
|
||||
let release;
|
||||
const promise = new Promise(r => { release = r; });
|
||||
held.set(lock_id, release);
|
||||
respond({ack: 'request', lock_id: lock_id});
|
||||
return promise
|
||||
});
|
||||
break;
|
||||
|
||||
case 'release':
|
||||
held.get(e.data.lock_id)();
|
||||
held.delete(e.data.lock_id);
|
||||
respond({ack: 'release', lock_id: e.data.lock_id});
|
||||
break;
|
||||
}
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,29 @@
|
|||
'use strict';
|
||||
|
||||
importScripts('/resources/testharness.js',
|
||||
'/resources/WebIDLParser.js',
|
||||
'/resources/idlharness.js');
|
||||
|
||||
promise_test(async t => {
|
||||
const response = await fetch('../interfaces.idl');
|
||||
const idls = await response.text();
|
||||
|
||||
const idl_array = new IdlArray();
|
||||
|
||||
idl_array.add_untested_idls('[Exposed=Window] interface Navigator {};');
|
||||
idl_array.add_untested_idls('[Exposed=Worker] interface WorkerNavigator {};');
|
||||
|
||||
idl_array.add_idls(idls);
|
||||
|
||||
let lock;
|
||||
await navigator.locks.request('name', l => { lock = l; });
|
||||
|
||||
idl_array.add_objects({
|
||||
LockManager: [navigator.locks],
|
||||
Lock: [lock],
|
||||
});
|
||||
|
||||
idl_array.test();
|
||||
}, 'Interface test');
|
||||
|
||||
done();
|
|
@ -0,0 +1,7 @@
|
|||
// Responds to '/clientId' with the request's clientId.
|
||||
self.addEventListener('fetch', e => {
|
||||
if (new URL(e.request.url).pathname === '/clientId') {
|
||||
e.respondWith(new Response(JSON.stringify({clientId: e.clientId})));
|
||||
return;
|
||||
}
|
||||
});
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>iframe used in clientId test</title>
|
||||
<script>
|
||||
|
||||
self.onmessage = async event => {
|
||||
try {
|
||||
if (event.data === 'get_sw_client_id') {
|
||||
// Use the controlling service worker to determine
|
||||
// this client's id according to the Service Worker.
|
||||
const response = await fetch('/clientId');
|
||||
const data = await response.json();
|
||||
window.parent.postMessage(data.clientId, '*');
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data === 'get_lock_client_id') {
|
||||
// Grab a lock, then query the lock manager for state to
|
||||
// determine this client's id according to the lock manager.
|
||||
await navigator.locks.request('lock-name', async lock => {
|
||||
const lock_state = await navigator.locks.query();
|
||||
const held_lock = lock_state.held.filter(l => l.name === lock.name)[0];
|
||||
window.parent.postMessage(held_lock.clientId, '*');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
window.parent.postMessage(`unknown request: ${event.data}`, '*');
|
||||
} catch (ex) {
|
||||
// In case of test failure, don't leave parent window hanging.
|
||||
window.parent.postMessage(`${ex.name}: ${ex.message}`, '*');
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
39
tests/wpt/web-platform-tests/web-locks/resources/worker.js
Normal file
39
tests/wpt/web-platform-tests/web-locks/resources/worker.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
'use strict';
|
||||
|
||||
// Map of id => function that releases a lock.
|
||||
|
||||
const held = new Map();
|
||||
let next_lock_id = 1;
|
||||
|
||||
self.addEventListener('message', e => {
|
||||
function respond(data) {
|
||||
self.postMessage(Object.assign(data, {rqid: e.data.rqid}));
|
||||
}
|
||||
|
||||
switch (e.data.op) {
|
||||
case 'request':
|
||||
navigator.locks.request(
|
||||
e.data.name, {
|
||||
mode: e.data.mode || 'exclusive',
|
||||
ifAvailable: e.data.ifAvailable || false
|
||||
}, lock => {
|
||||
if (lock === null) {
|
||||
respond({ack: 'request', failed: true});
|
||||
return;
|
||||
}
|
||||
let lock_id = next_lock_id++;
|
||||
let release;
|
||||
const promise = new Promise(r => { release = r; });
|
||||
held.set(lock_id, release);
|
||||
respond({ack: 'request', lock_id: lock_id});
|
||||
return promise;
|
||||
});
|
||||
break;
|
||||
|
||||
case 'release':
|
||||
held.get(e.data.lock_id)();
|
||||
held.delete(e.data.lock_id);
|
||||
respond({ack: 'release', lock_id: e.data.lock_id});
|
||||
break;
|
||||
}
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: API requires secure context</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
test(t => {
|
||||
assert_true(window.isSecureContext);
|
||||
assert_idl_attribute(navigator, 'locks',
|
||||
'navigator.locks exists in secure context');
|
||||
assert_true('LockManager' in self,
|
||||
'LockManager is present in secure contexts');
|
||||
assert_true('Lock' in self,
|
||||
'Lock interface is present in secure contexts');
|
||||
}, 'API presence in secure contexts');
|
||||
</script>
|
|
@ -0,0 +1,202 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: AbortSignal integration</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="resources/helpers.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
function makePromiseAndResolveFunc() {
|
||||
let resolve;
|
||||
const promise = new Promise(r => { resolve = r; });
|
||||
return [promise, resolve];
|
||||
}
|
||||
|
||||
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(
|
||||
t, new 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(
|
||||
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);
|
||||
|
||||
// Grab a lock and hold it forever.
|
||||
const never_settled = new Promise(resolve => { /* never */ });
|
||||
navigator.locks.request(res, lock => never_settled);
|
||||
|
||||
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);
|
||||
assert_equals(state.pending.filter(lock => lock.name === res).length, 1);
|
||||
|
||||
const rejected = promise_rejects(
|
||||
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 forever.
|
||||
const never_settled = new Promise(resolve => { /* never */ });
|
||||
navigator.locks.request(res, lock => never_settled);
|
||||
|
||||
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);
|
||||
assert_equals(state.pending.filter(lock => lock.name === res).length, 1);
|
||||
|
||||
const rejected = promise_rejects(
|
||||
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(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');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,97 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: steal option</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="resources/helpers.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
const never_settled = new Promise(resolve => { /* never */ });
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
let callback_called = false;
|
||||
await navigator.locks.request(res, {steal: true}, lock => {
|
||||
callback_called = true;
|
||||
assert_not_equals(lock, null, 'Lock should be granted');
|
||||
});
|
||||
assert_true(callback_called, 'Callback should be called');
|
||||
}, 'Lock available');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
let callback_called = false;
|
||||
|
||||
// Grab and hold the lock.
|
||||
navigator.locks.request(res, lock => never_settled).catch(_ => {});
|
||||
|
||||
// Steal it.
|
||||
await navigator.locks.request(res, {steal: true}, lock => {
|
||||
callback_called = true;
|
||||
assert_not_equals(lock, null, 'Lock should be granted');
|
||||
});
|
||||
|
||||
assert_true(callback_called, 'Callback should be called');
|
||||
}, 'Lock not available');
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
|
||||
// Grab and hold the lock.
|
||||
const promise = navigator.locks.request(res, lock => never_settled);
|
||||
const assertion = promise_rejects(
|
||||
t, 'AbortError', promise, `Initial request's promise should reject`);
|
||||
|
||||
// Steal it.
|
||||
await navigator.locks.request(res, {steal: true}, lock => {});
|
||||
|
||||
await assertion;
|
||||
|
||||
}, `Broken lock's release promise rejects`);
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
|
||||
// Grab and hold the lock.
|
||||
navigator.locks.request(res, lock => never_settled).catch(_ => {});
|
||||
|
||||
// Make a request for it.
|
||||
let request_granted = false;
|
||||
const promise = navigator.locks.request(res, lock => {
|
||||
request_granted = true;
|
||||
});
|
||||
|
||||
// Steal it.
|
||||
await navigator.locks.request(res, {steal: true}, lock => {
|
||||
assert_false(request_granted, 'Steal should override request');
|
||||
});
|
||||
|
||||
await promise;
|
||||
assert_true(request_granted, 'Request should eventually be granted');
|
||||
|
||||
}, `Requested lock's release promise is deferred`);
|
||||
|
||||
promise_test(async t => {
|
||||
const res = uniqueName(t);
|
||||
|
||||
// Grab and hold the lock.
|
||||
navigator.locks.request(res, lock => never_settled).catch(_ => {});
|
||||
|
||||
// Steal it.
|
||||
let saw_abort = false;
|
||||
const first_steal = navigator.locks.request(
|
||||
res, {steal: true}, lock => never_settled).catch(error => {
|
||||
saw_abort = true;
|
||||
});
|
||||
|
||||
// Steal it again.
|
||||
await navigator.locks.request(res, {steal: true}, lock => {});
|
||||
|
||||
await first_steal;
|
||||
assert_true(saw_abort, 'First steal should have aborted');
|
||||
|
||||
}, 'Last caller wins');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,118 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Web Locks API: Workers</title>
|
||||
<link rel=help href="https://github.com/inexorabletash/web-locks">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="resources/helpers.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
promise_test(async t => {
|
||||
const worker = new Worker('resources/worker.js');
|
||||
t.add_cleanup(() => { worker.terminate(); });
|
||||
|
||||
const res = 'shared resource 1';
|
||||
|
||||
const lock_id = (await postToWorkerAndWait(
|
||||
worker, {op: 'request', name: res, mode: 'shared'})).lock_id;
|
||||
|
||||
await navigator.locks.request(res, {mode: 'shared'}, async lock => {
|
||||
await postToWorkerAndWait(worker, {op: 'release', lock_id});
|
||||
});
|
||||
|
||||
}, 'Window and Worker - shared mode');
|
||||
|
||||
promise_test(async t => {
|
||||
const worker = new Worker('resources/worker.js');
|
||||
t.add_cleanup(() => { worker.terminate(); });
|
||||
|
||||
const res = 'exclusive resource 1';
|
||||
|
||||
// worker acquires the lock.
|
||||
const lock_id = (await postToWorkerAndWait(
|
||||
worker, {op: 'request', name: res})).lock_id;
|
||||
|
||||
// This request should be blocked.
|
||||
let lock_granted = false;
|
||||
const blocked = navigator.locks.request(
|
||||
res, lock => { lock_granted = true; });
|
||||
|
||||
// Verify we can't get it.
|
||||
let available = undefined;
|
||||
await navigator.locks.request(
|
||||
res, {ifAvailable: true}, lock => { available = lock !== null; });
|
||||
assert_false(available);
|
||||
assert_false(lock_granted);
|
||||
|
||||
// Ask the worker to release it.
|
||||
await postToWorkerAndWait(worker, {op: 'release', lock_id});
|
||||
|
||||
// Now we've got it.
|
||||
const lock2 = await blocked;
|
||||
assert_true(lock_granted);
|
||||
|
||||
}, 'Window and Worker - exclusive mode');
|
||||
|
||||
promise_test(async t => {
|
||||
const worker1 = new Worker('resources/worker.js');
|
||||
const worker2 = new Worker('resources/worker.js');
|
||||
t.add_cleanup(() => { worker1.terminate(); worker2.terminate(); });
|
||||
|
||||
const res = 'exclusive resource 2';
|
||||
|
||||
// worker1 acquires the lock.
|
||||
const lock_id = (await postToWorkerAndWait(
|
||||
worker1, {op: 'request', name: res})).lock_id;
|
||||
|
||||
// This request should be blocked.
|
||||
let lock_granted = false;
|
||||
const blocked = postToWorkerAndWait(
|
||||
worker2, {op: 'request', name: res});
|
||||
blocked.then(f => { lock_granted = true; });
|
||||
|
||||
// Verify worker2 can't get it.
|
||||
assert_true((await postToWorkerAndWait(worker2, {
|
||||
op: 'request', name: res, ifAvailable: true
|
||||
})).failed, 'Lock request should have failed');
|
||||
assert_false(lock_granted);
|
||||
|
||||
// Ask worker1 to release it.
|
||||
await postToWorkerAndWait(worker1, {op: 'release', lock_id});
|
||||
|
||||
// Now worker2 can get it.
|
||||
const lock = await blocked;
|
||||
assert_true(lock_granted);
|
||||
|
||||
}, 'Worker and Worker - exclusive mode');
|
||||
|
||||
promise_test(async t => {
|
||||
const worker = new Worker('resources/worker.js');
|
||||
|
||||
const res = 'exclusive resource 3';
|
||||
|
||||
// Worker acquires the lock.
|
||||
await postToWorkerAndWait(worker, {op: 'request', name: res});
|
||||
|
||||
// This request should be blocked.
|
||||
let lock_granted = false;
|
||||
const blocked = navigator.locks.request(
|
||||
res, lock => { lock_granted = true; });
|
||||
|
||||
// Verify we can't get it.
|
||||
let available = undefined;
|
||||
await navigator.locks.request(
|
||||
res, {ifAvailable: true}, lock => { available = lock !== null; });
|
||||
assert_false(available);
|
||||
assert_false(lock_granted);
|
||||
|
||||
// Implicitly release it by terminating the worker.
|
||||
worker.terminate();
|
||||
|
||||
// Now we've got it.
|
||||
const lock = await blocked;
|
||||
assert_true(lock_granted);
|
||||
|
||||
}, 'Terminated Worker - exclusive mode');
|
||||
|
||||
</script>
|
Loading…
Add table
Add a link
Reference in a new issue