diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/web-locks/resources | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/web-locks/resources')
8 files changed, 301 insertions, 0 deletions
diff --git a/testing/web-platform/tests/web-locks/resources/helpers.js b/testing/web-platform/tests/web-locks/resources/helpers.js new file mode 100644 index 0000000000..4b3311eee6 --- /dev/null +++ b/testing/web-platform/tests/web-locks/resources/helpers.js @@ -0,0 +1,83 @@ +// 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, prefix) => { + return `${self.location.pathname}-${prefix}-${testCase.name}-${++res_num}`; + }; + self.uniqueNameByQuery = () => { + const prefix = new URL(location.href).searchParams.get('prefix'); + return `${prefix}-${++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); + }); + }; + + /** + * Request a lock and hold it until the subtest ends. + * @param {*} t test runner object + * @param {string} name lock name + * @param {LockOptions=} options lock options + * @returns + */ + self.requestLockAndHold = (t, name, options = {}) => { + return navigator.locks.request(name, options, () => { + return new Promise(resolve => t.add_cleanup(resolve)); + }); + }; + + self.makePromiseAndResolveFunc = () => { + let resolve; + const promise = new Promise(r => { resolve = r; }); + return [promise, resolve]; + }; + +})(); diff --git a/testing/web-platform/tests/web-locks/resources/iframe-parent.html b/testing/web-platform/tests/web-locks/resources/iframe-parent.html new file mode 100644 index 0000000000..021fffab68 --- /dev/null +++ b/testing/web-platform/tests/web-locks/resources/iframe-parent.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<title>Helper IFrame</title> +<script> +'use strict'; + +async function onLoad() { + // Load the innermost child iframe and its content. + const params = new URLSearchParams(self.location.search); + const frame = document.createElement('iframe'); + frame.src = params.get('target'); + document.body.appendChild(frame); + + self.addEventListener('message', evt => { + // Pass any operations request messages to the + // innermost child iframe. + if (evt.data.op){ + // Ensure that the iframe has loaded before passing + // on the message. + frame.addEventListener('load', function(){ + frame.contentWindow.postMessage(evt.data, '*'); + }); + } + else { + // All other messages, should be sent back to the + // top-level site. + if (self.opener) + self.opener.postMessage(evt.data, '*'); + else + self.top.postMessage(evt.data, '*'); + } + }); +} +self.addEventListener('load', onLoad); +</script> diff --git a/testing/web-platform/tests/web-locks/resources/iframe.html b/testing/web-platform/tests/web-locks/resources/iframe.html new file mode 100644 index 0000000000..ba63c77bae --- /dev/null +++ b/testing/web-platform/tests/web-locks/resources/iframe.html @@ -0,0 +1,52 @@ +<!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; + + case 'client_id': + navigator.locks.request(e.data.name, async lock => { + const lock_state = await navigator.locks.query(); + const held_lock = + lock_state.held.filter(l => l.name === lock.name)[0]; + respond({ack: 'client_id', client_id: held_lock.clientId}); + }); + break; + } +}); +</script> diff --git a/testing/web-platform/tests/web-locks/resources/parentworker.js b/testing/web-platform/tests/web-locks/resources/parentworker.js new file mode 100644 index 0000000000..2b2b2c2028 --- /dev/null +++ b/testing/web-platform/tests/web-locks/resources/parentworker.js @@ -0,0 +1,10 @@ +// Just transparently forwards things to the child worker + +importScripts("/web-locks/resources/helpers.js"); +const worker = new Worker("/web-locks/resources/worker.js"); + +self.addEventListener("message", async ev => { + const data = await postToWorkerAndWait(worker, ev.data); + data.rqid = ev.data.rqid; + postMessage(data); +}); diff --git a/testing/web-platform/tests/web-locks/resources/partitioned-parent.html b/testing/web-platform/tests/web-locks/resources/partitioned-parent.html new file mode 100644 index 0000000000..5dafce4965 --- /dev/null +++ b/testing/web-platform/tests/web-locks/resources/partitioned-parent.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<meta name="referrer" content="origin"> +<script> +async function onLoad() { + self.addEventListener('message', evt => { + if (self.opener) + self.opener.postMessage(evt.data, '*'); + else + self.top.postMessage(evt.data, '*'); + }, { once: true }); + + const params = new URLSearchParams(self.location.search); + const frame = document.createElement('iframe'); + frame.src = params.get('target'); + document.body.appendChild(frame); + + frame.addEventListener('load', function(){ + frame.contentWindow.postMessage({op: 'request', + name: 'testLock', ifAvailable: true}, '*'); + }); +} +self.addEventListener('load', onLoad); +</script> diff --git a/testing/web-platform/tests/web-locks/resources/service-worker.js b/testing/web-platform/tests/web-locks/resources/service-worker.js new file mode 100644 index 0000000000..027863e33e --- /dev/null +++ b/testing/web-platform/tests/web-locks/resources/service-worker.js @@ -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; + } +}); diff --git a/testing/web-platform/tests/web-locks/resources/sw-controlled-iframe.html b/testing/web-platform/tests/web-locks/resources/sw-controlled-iframe.html new file mode 100644 index 0000000000..bc5c9bdb83 --- /dev/null +++ b/testing/web-platform/tests/web-locks/resources/sw-controlled-iframe.html @@ -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> diff --git a/testing/web-platform/tests/web-locks/resources/worker.js b/testing/web-platform/tests/web-locks/resources/worker.js new file mode 100644 index 0000000000..cc71631ba6 --- /dev/null +++ b/testing/web-platform/tests/web-locks/resources/worker.js @@ -0,0 +1,56 @@ +'use strict'; + +// Map of id => function that releases a lock. + +const held = new Map(); +let next_lock_id = 1; + +function processMessage(e) { + const target = this; + + function respond(data) { + target.postMessage(Object.assign(data, {rqid: e.data.rqid})); + } + + switch (e.data.op) { + case 'request': { + const controller = new AbortController(); + navigator.locks.request( + e.data.name, { + mode: e.data.mode || 'exclusive', + ifAvailable: e.data.ifAvailable || false, + signal: e.data.abortImmediately ? controller.signal : undefined, + }, 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; + }).catch(e => { + respond({ack: 'request', error: e.name}); + }); + if (e.data.abortImmediately) { + controller.abort(); + } + 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; + } +} + +self.addEventListener('message', processMessage); + +self.addEventListener('connect', ev => { + // Shared worker case + ev.ports[0].onmessage = processMessage; +}); |