<!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>