| <!DOCTYPE html> | |
| <meta charset=utf-8> | |
| <title>Web Locks API: navigator.locks.query ordering</title> | |
| <link rel=help href="https://w3c.github.io/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'; | |
| // 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(_ => {}); | |
| }); | |
| } | |
| // Returns a promise resolved by the next message event. | |
| function nextMessage() { | |
| return new Promise(resolve => { | |
| window.addEventListener('message', event => { | |
| resolve(event.data); | |
| }, {once: true}); | |
| }); | |
| } | |
| // Tests the ordering constraints on the requested lock state returned by | |
| // navigator.locks.query(). Three separate iframes are instantiated to make | |
| // lock requests on the same resource, first in one order and then in another, | |
| // different order. For each set of requests, it is verified that the requests | |
| // appear in the result of navigator.locks.query() in the same order in which | |
| // they were made. | |
| // | |
| // It is necessary to use separate iframes here so that the lock requests have | |
| // distinguishable client_ids (otherwise it would not be possible to | |
| // distinguish the requests and thus impossible to verify ordering). | |
| promise_test(async testCase => { | |
| assert_implements(navigator.locks); | |
| const resourceName = uniqueName(testCase); | |
| // Set up clients. | |
| const frame1 = await iframe('resources/iframe.html'); | |
| const frame2 = await iframe('resources/iframe.html'); | |
| const frame3 = await iframe('resources/iframe.html'); | |
| testCase.add_cleanup(() => { frame1.remove(); }); | |
| testCase.add_cleanup(() => { frame2.remove(); }); | |
| testCase.add_cleanup(() => { frame3.remove(); }); | |
| // Collect the client ids. | |
| const clientId1 = | |
| (await postToFrameAndWait(frame1, {op: 'client_id', | |
| name: resourceName})).client_id; | |
| const clientId2 = | |
| (await postToFrameAndWait(frame2, {op: 'client_id', | |
| name: resourceName})).client_id; | |
| const clientId3 = | |
| (await postToFrameAndWait(frame3, {op: 'client_id', | |
| name: resourceName})).client_id; | |
| // Preemptively take the lock. | |
| const firstRequestGroupReleaseFunction = | |
| await getLockAndHoldUntilReleased(resourceName); | |
| // Queue the first group of lock requests from the different clients. These | |
| // will be blocked until firstRequestGroupReleaseFunction() is called. | |
| let lockId1; | |
| let lockId2; | |
| const lockPromise1 = | |
| postToFrameAndWait(frame1, {op: 'request', name: resourceName}) | |
| .then(val => {lockId1 = val.lock_id;}); | |
| const lockPromise2 = | |
| postToFrameAndWait(frame2, {op: 'request', name: resourceName}) | |
| .then(val => {lockId2 = val.lock_id;}); | |
| // This third request will later be granted and held in order to block a | |
| // second group of requests to test a different client ordering. It is not | |
| // meant to be released. | |
| postToFrameAndWait(frame3, {op: 'request', name: resourceName}); | |
| // Request and wait for the release of a separate lock to ensure all previous | |
| // requests are processed. | |
| const checkpointName = uniqueName(testCase, 'checkpoint'); | |
| const checkpointId = (await postToFrameAndWait( | |
| frame3, | |
| {op: 'request', name: checkpointName})).lock_id; | |
| await postToFrameAndWait(frame3, {op: 'release', lock_id: checkpointId}); | |
| // Query the state and test the ordering of requested locks. | |
| const state = await navigator.locks.query(); | |
| const relevant_pending_ids = state.pending | |
| .filter(lock => [clientId1, clientId2, clientId3].includes(lock.clientId)) | |
| .map(lock => lock.clientId); | |
| assert_array_equals( | |
| [clientId1, clientId2, clientId3], | |
| relevant_pending_ids, | |
| 'Querying the state should return requested locks in the order they were ' | |
| + 'requested.'); | |
| // Add the second group of requests from the clients in a new order. | |
| postToFrameAndWait(frame3, {op: 'request', name: resourceName}); | |
| postToFrameAndWait(frame1, {op: 'request', name: resourceName}); | |
| postToFrameAndWait(frame2, {op: 'request', name: resourceName}); | |
| // Release locks such that only the newly added locks are requested. This | |
| // acts like a checkpoint for the newly queued requests. | |
| firstRequestGroupReleaseFunction(); | |
| await lockPromise1; | |
| await postToFrameAndWait(frame1, {op: 'release', lock_id: lockId1}); | |
| await lockPromise2; | |
| await postToFrameAndWait(frame2, {op: 'release', lock_id: lockId2}); | |
| // Query the state and test the new ordering. | |
| const state2 = await navigator.locks.query(); | |
| const relevant_pending_ids2 = state2.pending | |
| .filter(lock => [clientId1, clientId2, clientId3].includes(lock.clientId)) | |
| .map(lock => lock.clientId); | |
| assert_array_equals( | |
| [clientId3, clientId1, clientId2], | |
| relevant_pending_ids2, | |
| 'Querying the state should return requested locks in the order they were ' | |
| + 'requested.'); | |
| }, 'Requests appear in state in order made.'); | |
| </script> |