// META: title=Web Locks API: navigator.locks.query method // META: script=resources/helpers.js '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');