summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/web-locks/resources
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/web-locks/resources
parentInitial commit. (diff)
downloadfirefox-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')
-rw-r--r--testing/web-platform/tests/web-locks/resources/helpers.js83
-rw-r--r--testing/web-platform/tests/web-locks/resources/iframe-parent.html34
-rw-r--r--testing/web-platform/tests/web-locks/resources/iframe.html52
-rw-r--r--testing/web-platform/tests/web-locks/resources/parentworker.js10
-rw-r--r--testing/web-platform/tests/web-locks/resources/partitioned-parent.html24
-rw-r--r--testing/web-platform/tests/web-locks/resources/service-worker.js7
-rw-r--r--testing/web-platform/tests/web-locks/resources/sw-controlled-iframe.html35
-rw-r--r--testing/web-platform/tests/web-locks/resources/worker.js56
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;
+});