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/presentation-api/receiving-ua/support | |
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/presentation-api/receiving-ua/support')
13 files changed, 1057 insertions, 0 deletions
diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnectionList_onconnectionavailable_receiving-ua.html b/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnectionList_onconnectionavailable_receiving-ua.html new file mode 100644 index 0000000000..74d8dfa0ed --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnectionList_onconnectionavailable_receiving-ua.html @@ -0,0 +1,92 @@ +<!DOCTYPE html> + +<meta charset="utf-8"> +<title>Monitoring incoming presentation connections</title> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs"> +<link rel="help" href="https://w3c.github.io/presentation-api/#monitoring-incoming-presentation-connections"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../common.js"></script> +<script src="stash.js"></script> + +<script> + const stash = new Stash(stashIds.toReceiver, stashIds.toController); + + add_completion_callback((tests, status) => { + const log = document.getElementById('log'); + stash.send(JSON.stringify({ type: 'result', tests: tests, status: status, log: log.innerHTML })); + }); + + promise_test(t => { + const receiver = navigator.presentation.receiver; + return receiver.connectionList.then(list => { + let connections = list.connections; + let number = connections.length; + + const checkConnectionList = (connection, action) => { + assert_equals(connections.length, number, 'PresentationConnectionList.connections is a frozen array.'); + + // Note: When a presentation is terminated, a receiving user agent unloads a document + // without firing a "terminate" event. + return receiver.connectionList.then(list => { + connections = list.connections; + if (action === 'close') { + assert_true(connections.length === number - 1 && !connections.includes(connection), + 'A closed presentation connection is removed from the set of presentation controllers.'); + } else if (action === 'connect') { + assert_true(connections.length === number + 1 && connections.includes(connection), + 'A new presentation connection is added to the set of presentation controllers.'); + } + number = connections.length; + }); + }; + + const checkEvent = evt => { + assert_true(evt instanceof PresentationConnectionAvailableEvent, 'An event using PresentationConnectionAvailableEvent is fired.'); + assert_true(evt.isTrusted, 'The event is a trusted event.'); + assert_false(evt.bubbles, 'The event does not bubbles.'); + assert_false(evt.cancelable, 'The event is not cancelable.'); + assert_equals(evt.type, 'connectionavailable', 'The event name is "connectionavailable".'); + assert_equals(evt.target, list, 'event.target is the presentation connection list.'); + assert_true(evt.connection instanceof PresentationConnection, 'event.connection is a presentation connection.'); + + return checkConnectionList(evt.connection, 'connect'); + }; + + const watchEvent = (obj, watcher, type) => { + const watchHandler = new Promise(resolve => { + obj['on' + type] = evt => { resolve(evt); }; + }); + return Promise.all([ watchHandler, watcher.wait_for(type) ]).then(results => { + assert_equals(results[0], results[1], 'Both on' + type + ' and addEventListener pass the same event object.'); + return results[0]; + }); + }; + + // Step 1: check the first connection in "connected" state + let connection = list.connections[0]; + assert_equals(number, 1, 'A presentation connection list is populated with a first presentation connection.'); + assert_true(list instanceof PresentationConnectionList, 'navigator.presentation.receiver.connectionList is resolved with a presentation connection list.'); + assert_true(list.connections instanceof Array, 'A presentation connection list is an array.'); + assert_true(connection instanceof PresentationConnection, 'A presentation connection is added to a presentation connection list.'); + + // Step 2: check the connection in "closed" state + stash.send(JSON.stringify({ type: 'ok' })); + let eventWatcher = new EventWatcher(t, connection, 'close'); + return eventWatcher.wait_for('close').then(() => { + return checkConnectionList(connection, 'close'); + }) + // Step 3: check the first connection when reconnected + .then(() => { + stash.send(JSON.stringify({ type: 'ok' })); + eventWatcher = new EventWatcher(t, list, 'connectionavailable'); + return watchEvent(list, eventWatcher, 'connectionavailable') + }).then(checkEvent) + // Step 4: check the second connection (with <iframe>) in "connected" state + .then(() => { + stash.send(JSON.stringify({ type: 'ok' })); + return watchEvent(list, eventWatcher, 'connectionavailable') + }).then(checkEvent); + }); + }); +</script> diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnection_onclose_receiving-ua.html b/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnection_onclose_receiving-ua.html new file mode 100644 index 0000000000..7194c6f4d9 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnection_onclose_receiving-ua.html @@ -0,0 +1,89 @@ +<!DOCTYPE html> + +<meta charset="utf-8"> +<title>Closing a PresentationConnection</title> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs"> +<link rel="help" href="https://w3c.github.io/presentation-api/#closing-a-presentationconnection"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../common.js"></script> +<script src="stash.js"></script> + +<script> + const stash = new Stash(stashIds.toReceiver, stashIds.toController); + + add_completion_callback((tests, status) => { + const log = document.getElementById('log'); + stash.send(JSON.stringify({ type: 'result', tests: tests, status: status, log: log.innerHTML })); + }); + + const checkCloseEvent = (evt, connection, reason) => { + assert_true(evt instanceof PresentationConnectionCloseEvent, 'An event using PresentationConnectionCloseEvent is fired.'); + assert_true(evt.isTrusted, 'The event is a trusted event.'); + assert_false(evt.bubbles, 'The event does not bubbles.'); + assert_false(evt.cancelable, 'The event is not cancelable.'); + assert_equals(evt.type, 'close', 'The event name is "close".'); + assert_equals(evt.target, connection, 'event.target is the presentation connection.'); + assert_equals(connection.state, 'closed', 'State of the presentation connection is "closed".'); + assert_equals(evt.reason, reason, 'The reason for closing the presentation connection is "' + reason + '".'); + }; + + const watchEvent = (obj, watcher, type) => { + const watchHandler = new Promise(resolve => { + obj['on' + type] = evt => { resolve(evt); }; + }); + return Promise.all([ watchHandler, watcher.wait_for(type) ]).then(results => { + assert_equals(results[0], results[1], 'Both on' + type + ' and addEventListener pass the same event object.'); + return results[0]; + }); + }; + + promise_test(t => { + return navigator.presentation.receiver.connectionList.then(list => { + let connection = list.connections[0]; + assert_equals(list.connections.length, 1, 'A presentation connection list is populated with a first presentation connection.'); + + // Step 1: close the presentation connection in "connected" state + connection.close(); + let watchClose = new EventWatcher(t, connection, 'close'); + const watchNewConnection = new EventWatcher(t, list, 'connectionavailable'); + return watchEvent(connection, watchClose, 'close').then(evt => { + checkCloseEvent(evt, connection, 'closed'); + + // Step 2: check a connection closed by the controlling user agent + stash.send(JSON.stringify({ type: 'ok' })); + return watchNewConnection.wait_for('connectionavailable'); + }).then(evt => { + connection = evt.connection; + watchClose = new EventWatcher(t, connection, 'close'); + return watchEvent(connection, watchClose, 'close'); + }).then(evt => { + checkCloseEvent(evt, connection, 'closed'); + + // Step 3: try to close the presentation connection in "closed" state (nothing should happen) + connection.close(); + return Promise.race([ + new Promise(resolve => { t.step_timeout(resolve, 1000); }), + watchEvent(connection, watchClose, 'close').then(() => { + assert_unreached('Invoking PresentationConnection.close() in the "closed" state causes nothing.'); }) + ]); + }).then(() => { + + // Step 4: check a connection closed due to aborting the nested browsing context + stash.send(JSON.stringify({ type: 'ok' })); + return watchNewConnection.wait_for('connectionavailable'); + }).then(evt => { + connection = evt.connection; + stash.send(JSON.stringify({ type: 'ok' })); + watchClose = new EventWatcher(t, connection, 'close'); + return Promise.race([ + watchEvent(connection, watchClose, 'close'), + new Promise(resolve => { t.step_timeout(() => { resolve(null); }, 3000); }) + ]); + }).then(evt => { + assert_true(!!evt, 'A presentation connection is closed when its controller\'s browsing context is discarded.'); + checkCloseEvent(evt, connection, 'wentaway'); + }); + }); + }); +</script> diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnection_onmessage_receiving-ua.html b/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnection_onmessage_receiving-ua.html new file mode 100644 index 0000000000..0734a5570c --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnection_onmessage_receiving-ua.html @@ -0,0 +1,108 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Receiving a message through PresentationConnection</title> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs/"> +<link rel="help" href="http://w3c.github.io/presentation-api/#receiving-a-message-through-presentationconnection"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../common.js"></script> +<script src="stash.js"></script> + +<script> +setup({explicit_timeout: true}); + +const message1 = '1st'; +const message2 = '2nd'; +const message3 = new Uint8Array([51, 114, 100]); // "3rd" +const message4 = new Uint8Array([52, 116, 104]); // "4th" +const message5 = new Uint8Array([108, 97, 115, 116]); // "last" + +const toUint8Array = buf => { + return buf instanceof ArrayBuffer ? new Uint8Array(buf) : buf; +} + +// compare two ArrayBuffer or Uint8Array +const compare = (a, b) => { + const p = toUint8Array(a); + const q = toUint8Array(b); + return !!p && !!q && p.every((item, index) => { return item === q[index]; }); +}; + +const stash = new Stash(stashIds.toReceiver, stashIds.toController); + +add_completion_callback((tests, status) => { + const log = document.getElementById('log'); + stash.send(JSON.stringify({ type: 'result', tests: tests, status: status, log: log.innerHTML })); +}); + +navigator.presentation.receiver.connectionList.then(list => { + const checkEvent = event => { + assert_true(event.isTrusted, 'a trusted event is fired'); + assert_true(event instanceof MessageEvent, 'The event uses the MessageEvent interface'); + assert_false(event.bubbles, 'the event does not bubble'); + assert_false(event.cancelable, 'the event is not cancelable'); + }; + + const watchEvent = (obj, watcher, type) => { + const watchHandler = new Promise(resolve => { + obj['on' + type] = evt => { resolve(evt); }; + }); + return Promise.all([ watchHandler, watcher.wait_for(type) ]).then(results => { + assert_equals(results[0], results[1], 'Both on' + type + ' and addEventListener pass the same event object.'); + return results[0]; + }); + }; + + const connection = list.connections[0]; + + promise_test(t => { + t.step_timeout(() => { + t.force_timeout(); + t.done(); + }, 3000); + + assert_equals(connection.binaryType, 'arraybuffer', 'the default value of binaryType is "arraybuffer"'); + const eventWatcher = new EventWatcher(t, connection, 'message'); + return eventWatcher.wait_for('message').then(event => { + checkEvent(event); + assert_equals(event.data, message1, 'receive a string correctly'); + return watchEvent(connection, eventWatcher, 'message'); + }).then(event => { + checkEvent(event); + assert_equals(event.data, message2, 'receive a string correctly'); + return watchEvent(connection, eventWatcher, 'message'); + }).then(event => { + checkEvent(event); + assert_true(event.data instanceof ArrayBuffer, 'receive binary data as ArrayBuffer'); + assert_true(compare(event.data, message3), 'receive an ArrayBuffer correctly (originally a Blob at a controlling user agent)'); + return watchEvent(connection, eventWatcher, 'message'); + }).then(event => { + checkEvent(event); + assert_true(event.data instanceof ArrayBuffer, 'receive binary data as ArrayBuffer'); + assert_true(compare(event.data, message4), 'receive an ArrayBuffer correctly (originally an ArrayBuffer at a controlling user agent)'); + return watchEvent(connection, eventWatcher, 'message'); + }).then(event => { + checkEvent(event); + assert_true(event.data instanceof ArrayBuffer, 'receive binary data as ArrayBuffer'); + assert_true(compare(event.data, message5), 'receive an ArrayBuffer correctly (originally an ArrayBufferView at a controlling user agent)'); + + connection.binaryType = 'blob'; + return Promise.all([ + stash.send(JSON.stringify({ type: 'blob' })), + watchEvent(connection, eventWatcher, 'message') + ]).then(results => results[1]); + }).then(event => { + assert_true(event.data instanceof Blob, 'receive binary data as Blob'); + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = resolve; + reader.onerror = reject; + reader.readAsArrayBuffer(event.data); + }); + }).then(event => { + assert_true(compare(event.target.result, message5), 'receive a Blob correctly'); + connection.terminate(); + }); + }); +}); +</script> diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnection_send_receiving-ua.html b/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnection_send_receiving-ua.html new file mode 100644 index 0000000000..bc482e9dfd --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnection_send_receiving-ua.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> + +<meta charset="utf-8"> +<title>Sending a message through PresentationConnection</title> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs/"> +<link rel="help" href="http://w3c.github.io/presentation-api/#sending-a-message-through-presentationconnection"> +<script src="../common.js"></script> +<script src="stash.js"></script> + +<script> +const stash = new Stash(stashIds.toReceiver, stashIds.toController); + +navigator.presentation.receiver.connectionList.then(list => { + const connection = list.connections[0]; + + const message1 = '1st'; + const message2 = '2nd'; + const message3 = new Uint8Array([51, 114, 100]); // "3rd" + const message4 = new Uint8Array([52, 116, 104]); // "4th" + const message5 = new Uint8Array([108, 97, 115, 116]); // "last" + + // send messages + return stash.receive().then(() => { + connection.send(message1); // string + connection.send(message2); // string + connection.send(new Blob([message3])); // Blob + connection.send(message4.buffer); // ArrayBuffer + connection.send(message5); // ArrayBufferView + + return stash.receive(); + }).then(() => { + // try to send a message in the "closed" state + // Note: a receiving user agent does not fire a "terminate" event + connection.close(); + connection.onclose = () => { + try { + connection.send(message1); + stash.send(JSON.stringify({ type: 'ok' })); + } catch (e) { + stash.send(JSON.stringify({ type: 'error', name: e.name, message: e.message })); + } + }; + }); +}); +</script> diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnection_terminate_receiving-ua.html b/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnection_terminate_receiving-ua.html new file mode 100644 index 0000000000..3a234aaa83 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnection_terminate_receiving-ua.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> + +<meta charset="utf-8"> +<title>Terminating a presentation in a receiving browsing context</title> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs/"> +<link rel="help" href="https://w3c.github.io/presentation-api/#terminating-a-presentation-in-a-receiving-browsing-context"> +<script src="../common.js"></script> +<script src="stash.js"></script> + +<script> + const stash = new Stash(stashIds.toReceiver, stashIds.toController); + + navigator.presentation.receiver.connectionList.then(list => { + const connection = list.connections[0]; + + // terminate a presentation when two presentation connections become "connected" + list.onconnectionavailable = evt => { + connection.terminate(); + + stash.sendBeacon({ type: (window.closed ? 'ok' : 'error') }); + }; + }); +</script> diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationReceiver_create_receiving-ua.html b/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationReceiver_create_receiving-ua.html new file mode 100644 index 0000000000..53f7601442 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationReceiver_create_receiving-ua.html @@ -0,0 +1,328 @@ +<!DOCTYPE html> + +<meta charset="utf-8"> +<title>Creating a receiving browsing context</title> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs/"> +<link rel="help" href="https://w3c.github.io/presentation-api/#creating-a-receiving-browsing-context"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../common.js"></script> +<script src="stash.js"></script> +<style> +a { visibility: hidden; } +iframe { width: 100%; } +</style> + +<iframe id="child" src="PresentationReceiver_create_receiving-ua_child.html"></iframe> +<p id="notice">Checking <code id="modal"></code>: if you see this message, please wait for test to time out.</p> +<a href="PresentationReceiver_unreached_receiving-ua.html">do not navigate</a> +<script> +let finished = false; + +const sendResult = (test, status) => { + if(!finished) { + finished = true; + const stash = new Stash(stashIds.toReceiver, stashIds.toController); + stash.send(JSON.stringify({ test: test, status: status })); + } +}; + +add_completion_callback((tests, status) => { + // note: a single test result is supposed to appear here. + sendResult(tests[0], status); +}); + +const child = document.getElementById('child'); +child.addEventListener('load', () => { + const notice = document.getElementById('notice'); + const modal = document.getElementById('modal'); + + const dbName = { + controller: 'db-presentation-api-controlling-ua', + receiver: 'db-presentation-api-receiving-ua' + }; + + promise_test(t => { + t.add_cleanup(() => { + document.cookie = cookieName + '=False;Expires=' + new Date().toUTCString(); + document.cookie = cookieNameChild + '=False;Expires=' + new Date().toUTCString(); + sessionStorage.removeItem(storageName); + localStorage.removeItem(storageName); + sessionStorage.removeItem(storageNameChild); + localStorage.removeItem(storageNameChild); + + Object.values(dbName).forEach(name => { + indexedDB.deleteDatabase(name); + }); + + if ('serviceWorker' in navigator) { + navigator.serviceWorker.getRegistrations().then(registrations => { + return Promise.all(registrations.map(reg => reg.unregister())); + }); + } + if ('caches' in window) { + caches.keys().then(keys => { + return Promise.all(keys.map(key => caches.delete(key))); + }); + } + }); + + // Session history + assert_equals(window.history.length, 1, 'Session history consists of the current page only.'); + + // The Sandboxed auxiliary navigation browsing context flag + assert_equals(window.open('PresentationReceiver_unreached_receiving-ua.html'), null, 'Sandboxed auxiliary navigation browsing context flag is set.'); + + // The sandboxed top-level navigation browsing context flag (codes below are expected to be ignored) + window.close(); + document.querySelector('a').click(); + location.href = 'PresentationReceiver_unreached_receiving-ua.html'; + + // The sandboxed modals flag (codes below are expected to be ignored): + // If user agent prompts user, a timeout will occur and the test will eventually fail + let message = 'If you see this prompt, do not dismiss it and wait for test to time out.'; + notice.style.display = 'block'; + modal.textContent = 'alert()'; + alert(message); + modal.textContent = 'confirm()'; + confirm(message); + modal.textContent = 'print()'; + print(); + modal.textContent = 'prompt()'; + prompt(message); + notice.style.display = 'none'; + + // Permissions + const checkPermission = query => { + return navigator.permissions ? navigator.permissions.query(query).then(status => { + assert_equals(status.state, 'denied', 'The state of the "' + query.name + '" permission is set to "denied".'); + }, () => { /* skip this assertion if the specified permission is not implemented */ }) : Promise.resolve(); + } + + // Cookie + assert_equals(document.cookie, '', 'A cookie store is set to an empty store.') + + // Indexed Database + const checkIndexedDB = () => { + if ('indexedDB' in window) { + // The test would fail when the database is already created by the controlling UA + const req = indexedDB.open(dbName.controller); + const upgradeneededWatcher = new EventWatcher(t, req, 'upgradeneeded'); + const successWatcher = new EventWatcher(t, req, 'success'); + return Promise.race([ + upgradeneededWatcher.wait_for('upgradeneeded').then(evt => { + evt.target.result.close(); + const req = indexedDB.open(dbName.receiver, 2); + const eventWatcher = new EventWatcher(t, req, 'upgradeneeded'); + return eventWatcher.wait_for('upgradeneeded'); + }).then(evt => { + evt.target.result.close(); + }), + successWatcher.wait_for('success').then(evt => { + evt.target.result.close(); + // This would fail if the database created by the controlling UA is visible to the receiving UA + assert_unreached('Indexed Database is set to an empty storage.'); + }) + ]); + } + else + return Promise.resolve(); + }; + + // Web Storage + assert_equals(sessionStorage.length, 0, 'Session storage is set to an empty storage.'); + assert_equals(localStorage.length, 0, 'Local storage is set to an empty storage.'); + + // Service Workers + const checkServiceWorkers = () => { + return 'serviceWorker' in navigator ? navigator.serviceWorker.getRegistrations().then(registrations => { + assert_equals(registrations.length, 0, 'List of registered service worker registrations is empty.'); + }) : Promise.resolve(); + }; + const checkCaches = () => { + return 'caches' in window ? caches.keys().then(keys => { + assert_equals(keys.length, 0, 'Cache storage is empty.') + }) : Promise.resolve(); + }; + + // Navigation + const checkNavigation = () => { + return navigator.presentation.receiver.connectionList.then(connectionList => { + assert_equals(connectionList.connections.length, 1, 'The initial number of presentation connections is one'); + assert_equals(location.href, connectionList.connections[0].url, 'A receiving browsing context is navigated to the presentation URL.'); + }); + }; + + // Update storages and service workers shared with the top-level brosing context + const cookieName = 'PresentationApiTest'; + const cookieValue = 'Receiving-UA'; + const storageName = 'presentation_api_test'; + const storageValue = 'receiving-ua'; + document.cookie = cookieName + '=' + cookieValue; + sessionStorage.setItem(storageName, storageValue); + localStorage.setItem(storageName, storageValue); + + // Service Workers and Caches + const cacheName = 'receiving-ua'; + const getClientUrls = () => { + return new Promise(resolve => { + navigator.serviceWorker.getRegistration().then(reg => { + const channel = new MessageChannel(); + channel.port1.onmessage = event => { + resolve(event.data); + }; + reg.active.postMessage('test', [channel.port2]); + }); + }); + }; + + const registerServiceWorker = () => { + return ('serviceWorker' in navigator ? + navigator.serviceWorker.register('../serviceworker.js').then(registration => { + return new Promise((resolve, reject) => { + if (registration.installing) { + registration.installing.addEventListener('statechange', event => { + if(event.target.state === 'installed') + resolve(); + }); + } + else + resolve(); + }); + }) : Promise.resolve()).then(getClientUrls).then(urls => { + assert_true(urls.every(url => { return url !== new Request('../PresentationReceiver_create-manual.https.html').url }), + 'A window client in a controlling user agent is not accessible to a service worker on a receiving user agent.'); + }); + }; + + const openCaches = () => { + return 'caches' in window ? caches.open(cacheName).then(cache => cache.add('../cache.txt')) : Promise.resolve(); + }; + + const getChildFrameResult = () => { + return new Promise(resolve => { + window.addEventListener('message', t.step_func(event => { + const result = event.data; + if (result.type === 'presentation-api') { + // if the test in iframe failed, report the result and abort the test + if (result.test.status === 0) + resolve(); + else { + sendResult(result.test, result.status); + assert_unreached('A test for a nested browsing context failed.'); + } + } + })); + child.contentWindow.postMessage('start', location.origin); + }); + }; + + // check the results from updates by iframe + const cookieNameChild = 'NestedBrowsingContext'; + const cookieValueChild = 'True'; + const storageNameChild = 'nested_browsing_context'; + const storageValueChild = 'yes'; + + const checkUpdatedResult = () => { + // cookie + const cookies = document.cookie.split(/;\s*/).reduce((result, item) => { + const t = item.split('='); + result[t[0]] = t[1]; + return result; + }, {}); + message = 'A cookie store is shared by top-level and nested browsing contexts.' + assert_equals(Object.keys(cookies).length, 2, message); + assert_equals(cookies[cookieName], cookieValue, message); + assert_equals(cookies[cookieNameChild], cookieValueChild, message); + + // Web Storage + message = 'Session storage is shared by top-level and nested browsing contexts.'; + assert_equals(sessionStorage.length, 2, message); + assert_equals(sessionStorage.getItem(storageName), storageValue, message); + assert_equals(sessionStorage.getItem(storageNameChild), storageValueChild, message); + message = 'Local storage is shared by top-level and nested browsing contexts.'; + assert_equals(localStorage.length, 2, message); + assert_equals(localStorage.getItem(storageName), storageValue, message); + assert_equals(localStorage.getItem(storageNameChild), storageValueChild, message); + }; + + // Indexed Database + const checkUpdatedIndexedDB = () => { + if ('indexedDB' in window) { + message = 'Indexed Database is shared by top-level and nested browsing contexts.'; + const req = indexedDB.open(dbName.receiver); + const upgradeneededWatcher = new EventWatcher(t, req, 'upgradeneeded'); + const successWatcher = new EventWatcher(t, req, 'success'); + return Promise.race([ + upgradeneededWatcher.wait_for('upgradeneeded').then(evt => { + evt.target.result.close(); + // Check if the version of the database is upgraded to 3 by the nested browsing context + assert_unreached(message); + }), + successWatcher.wait_for('success').then(evt => { + const db = evt.target.result; + const version = db.version; + db.close(); + // Check if the version of the database is upgraded to 3 by the nested browsing context + assert_equals(version, 3, message); + }) + ]); + } + else + return Promise.resolve(); + }; + + // Service Workers + const checkUpdatedServiceWorkers = () => { + return 'serviceWorker' in window ? navigator.serviceWorker.getRegistrations().then(registrations => { + message = 'List of registered service worker registrations is shared by top-level and nested browsing contexts.'; + assert_equals(registrations.length, 2, message); + const scriptURLs = registrations.map(reg => { return reg.active.scriptURL; }).sort(); + assert_equals(scriptURLs[0], new Request('../serviceworker.js').url, message); + assert_equals(scriptURLs[1], new Request('serviceworker.js').url, message); + }) : Promise.resolve(); + }; + const cacheNameChild = 'nested-browsing-context'; + const checkUpdatedCaches = () => { + message = 'Cache storage is shared by top-level and nested browsing contexts.'; + return 'caches' in window ? caches.keys().then(keys => { + assert_equals(keys.length, 2, message); + const cacheKeys = keys.sort(); + assert_equals(cacheKeys[0], cacheNameChild, message); + assert_equals(cacheKeys[1], cacheName, message); + return Promise.all(keys.map( + key => caches.open(key) + .then(cache => cache.matchAll()) + .then(responses => { + assert_equals(responses.length, 1, message); + assert_equals(responses[0].url, new Request('../cache.txt').url, message); + }))); + }) : Promise.resolve(); + }; + + // Asynchronous tests + const permissionList = [ + { name: 'geolocation' }, + { name: 'notifications' }, + { name: 'push', userVisibleOnly: true }, + { name: 'midi' }, + { name: 'camera' }, + { name: 'microphone' }, + { name: 'speaker' }, + { name: 'background-sync' } + ]; + return Promise.all(permissionList.map(perm => { return checkPermission(perm); })) + .then(checkIndexedDB) + .then(checkServiceWorkers) + .then(checkCaches) + .then(checkNavigation) + .then(registerServiceWorker) + .then(openCaches) + .then(getChildFrameResult) + .then(checkUpdatedResult) + .then(checkUpdatedIndexedDB) + .then(checkUpdatedServiceWorkers) + .then(checkUpdatedCaches); + }); +}); +</script> diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationReceiver_create_receiving-ua_child.html b/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationReceiver_create_receiving-ua_child.html new file mode 100644 index 0000000000..2f67f7fac0 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationReceiver_create_receiving-ua_child.html @@ -0,0 +1,171 @@ +<!DOCTYPE html> + +<meta charset="utf-8"> +<title>Creating a receiving browsing context</title> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs/"> +<link rel="help" href="https://w3c.github.io/presentation-api/#creating-a-receiving-browsing-context"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<p id="notice">Checking <code id="modal"></code>: if you see this message, please wait for test to time out.</p> + +<script> +add_completion_callback((tests, status) => { + // remove unserializable attributes, then send the result to the parent window + // note: a single test result is supposed to appear here. + window.parent.postMessage(JSON.parse(JSON.stringify({ + type: 'presentation-api', test: tests[0], status: status + })), location.origin); +}); + +window.addEventListener('message', event => { + if(event.data !== 'start') + return; + + promise_test(t => { + const notice = document.getElementById('notice'); + const modal = document.getElementById('modal'); + + // Presentation and PresentationReceiver + assert_true(navigator.presentation instanceof Presentation, 'navigator.presentation in a nested browsing context is an instanceof Presentation.'); + assert_equals(navigator.presentation.receiver, null, 'navigator.presentation.receiver in a nested browsing context is set to null.'); + + // Session history + assert_equals(window.history.length, 1, 'Session history consists of the current page only.'); + + // The Sandboxed auxiliary navigation browsing context flag (codes below are expected to be ignored) + assert_equals(window.open('./idlharness_receiving-ua.html'), null, 'Sandboxed auxiliary navigation browsing context flag is set.'); + + // The sandboxed modals flag (codes below are expected to be ignored): + // If user agent prompts user, a timeout will occur and the test will eventually fail + let message = 'If you see this prompt, do not dismiss it and wait for test to time out.'; + notice.style.display = 'block'; + modal.textContent = 'alert()'; + alert(message); + modal.textContent = 'confirm()'; + confirm(message); + modal.textContent = 'print()'; + print(); + modal.textContent = 'prompt()'; + prompt(message); + notice.style.display = 'none'; + + // Permissions + const checkPermission = query => { + return navigator.permissions ? navigator.permissions.query(query).then(status => { + assert_equals(status.state, 'denied', 'The state of the "' + query.name + '" permission in a nested browsing context is set to "denied".'); + }, () => { /* skip this assertion if the specified permission is not implemented */ }) : Promise.resolve(); + }; + + // Cookie + assert_equals(document.cookie, 'PresentationApiTest=Receiving-UA', 'A cookie store is shared by top-level and nested browsing contexts.'); + + // Indexed Database + const dbName = 'db-presentation-api-receiving-ua'; + const checkIndexedDB = () => { + if ('indexedDB' in window) { + message = 'Indexed Database is shared by top-level and nested browsing contexts.'; + + const req = indexedDB.open(dbName); + const upgradeneededWatcher = new EventWatcher(t, req, 'upgradeneeded'); + const successWatcher = new EventWatcher(t, req, 'success'); + return Promise.race([ + upgradeneededWatcher.wait_for('upgradeneeded').then(evt => { + evt.target.result.close(); + // This would fail if the database is not created by the top-level browsing context + assert_unreached(message); + }), + successWatcher.wait_for('success').then(evt => { + evt.target.result.close(); + const db = evt.target.result; + const version = db.version; + db.close(); + // Check if the version of the database is 2 as specified by the top-level browsing context + assert_equals(version, 2, message); + + // Upgrade the version + const req = indexedDB.open(dbName, 3); + const eventWatcher = new EventWatcher(t, req, 'upgradeneeded'); + return eventWatcher.wait_for('upgradeneeded'); + }).then(evt => { + evt.target.result.close(); + }) + ]); + } + else + return Promise.resolve(); + }; + + // Web Storage + assert_equals(sessionStorage.length, 1, 'Session storage is shared by top-level and nested browsing contexts.'); + assert_equals(sessionStorage.getItem('presentation_api_test'), 'receiving-ua', 'Session storage is shared by top-level and nested browsing contexts.'); + assert_equals(localStorage.length, 1, 'Local storage is shared by top-level and nested browsing contexts.'); + assert_equals(localStorage.getItem('presentation_api_test'), 'receiving-ua', 'Local storage is shared by top-level and nested browsing contexts.'); + + // Service Workers + const checkServiceWorkers = () => { + return 'serviceWorker' in navigator ? navigator.serviceWorker.getRegistrations().then(registrations => { + message = 'List of registered service worker registrations is shared by top-level and nested browsing contexts.'; + assert_equals(registrations.length, 1, message); + assert_equals(registrations[0].active.scriptURL, new Request('../serviceworker.js').url, message); + }) : Promise.resolve(); + }; + const checkCaches = () => { + message = 'Cache storage is shared by top-level and nested browsing contexts.'; + return 'caches' in window ? caches.keys().then(keys => { + assert_equals(keys.length, 1, message); + assert_equals(keys[0], 'receiving-ua', message); + return caches.open(keys[0]); + }).then(cache => cache.matchAll()) + .then(responses => { + assert_equals(responses.length, 1, message); + assert_equals(responses[0].url, new Request('../cache.txt').url, message); + }) : Promise.resolve(); + }; + + // Update storages and service workers shared with the top-level brosing context + document.cookie = 'NestedBrowsingContext=True'; + sessionStorage.setItem('nested_browsing_context', 'yes'); + localStorage.setItem('nested_browsing_context', 'yes'); + + // register a service worker with different scope from that of the top-level browsing context + const registerServiceWorker = () => { + return 'serviceWorker' in navigator ? + navigator.serviceWorker.register('serviceworker.js').then(registration => { + return new Promise((resolve, reject) => { + if (registration.installing) { + registration.installing.addEventListener('statechange', event => { + if(event.target.state === 'installed') + resolve(); + }); + } + else + resolve(); + }); + }) : Promise.resolve(); + }; + + const openCaches = () => { + return 'caches' in window + ? caches.open('nested-browsing-context').then(cache => cache.add('../cache.txt')) : Promise.resolve(); + }; + + // Asynchronous tests + const permissionList = [ + { name: 'geolocation' }, + { name: 'notifications' }, + { name: 'push', userVisibleOnly: true }, + { name: 'midi' }, + { name: 'camera' }, + { name: 'microphone' }, + { name: 'speaker' }, + { name: 'background-sync' } + ]; + return Promise.all(permissionList.map(perm => { return checkPermission(perm); })) + .then(checkIndexedDB) + .then(checkServiceWorkers) + .then(checkCaches) + .then(registerServiceWorker) + .then(openCaches); + }); +}); +</script> diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationReceiver_unreached_receiving-ua.html b/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationReceiver_unreached_receiving-ua.html new file mode 100644 index 0000000000..2b2a8b315e --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationReceiver_unreached_receiving-ua.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> + +<meta charset="utf-8"> +<title>Creating a receiving browsing context</title> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs/"> +<link rel="help" href="https://w3c.github.io/presentation-api/#creating-a-receiving-browsing-context"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../common.js"></script> +<script src="stash.js"></script> +<script> +add_completion_callback((tests, status) => { + const stash = new Stash(stashIds.toReceiver, stashIds.toController); + const log = document.getElementById('log'); + stash.send(JSON.stringify({ test: tests[0], status: status, log: log.innerHTML })); +}); + +test(() => { + assert_unreached('Top-level navigation is disabled.'); +}); +</script> diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/support/idlharness_receiving-ua.html b/testing/web-platform/tests/presentation-api/receiving-ua/support/idlharness_receiving-ua.html new file mode 100644 index 0000000000..2d5a5b17f1 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/support/idlharness_receiving-ua.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Presentation API IDL tests for Receiving User Agent</title> +<link rel="author" title="Louay Bassbouss" href="http://www.fokus.fraunhofer.de"> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs"> +<link rel="help" href="http://w3c.github.io/presentation-api/#dfn-receiving-user-agent"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/WebIDLParser.js"></script> +<script src="/resources/idlharness.js"></script> +<script src="../common.js"></script> +<script src="stash.js"></script> + +<script> + 'use strict'; + (async () => { + const srcs = ['presentation-api', 'dom', 'html']; + const [idl, dom, html] = await Promise.all( + srcs.map(i => fetch(`/interfaces/${i}.idl`).then(r => r.text()))); + + const idl_array = new IdlArray(); + idl_array.add_idls(idl, { + except: [ + 'PresentationRequest', + 'PresentationAvailability', + ] + }); + idl_array.add_dependency_idls(dom); + idl_array.add_dependency_idls(html); + idl_array.add_objects({ + Presentation: ['navigator.presentation'], + PresentationReceiver: ['navigator.presentation.receiver'] + }); + add_completion_callback((tests, status) => { + const stash = new Stash(stashIds.toReceiver, stashIds.toController); + const log = document.getElementById('log'); + stash.send(JSON.stringify({ tests: tests, status: status, log: log.innerHTML })); + }); + idl_array.test(); + })(); +</script> diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/support/iframe.html b/testing/web-platform/tests/presentation-api/receiving-ua/support/iframe.html new file mode 100644 index 0000000000..61f18b8912 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/support/iframe.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Helper functions invoked by a nested browsing context</title> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs"> +<link rel="help" href="https://w3c.github.io/presentation-api/#monitoring-incoming-presentation-connections"> + +<script> + window.addEventListener('message', event => { + const data = event.data; + + if (!data.type) + return; + else if (data.type === 'connect') { + const request = new PresentationRequest(data.url); + request.reconnect(data.id).then(c => { + c.onterminate = () => { + window.parent.postMessage({ type: 'terminated' }, '*'); + }; + }); + } + }); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/support/serviceworker.js b/testing/web-platform/tests/presentation-api/receiving-ua/support/serviceworker.js new file mode 100644 index 0000000000..d2d4449ded --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/support/serviceworker.js @@ -0,0 +1,9 @@ +self.addEventListener('install', () => { + // activate this service worker immediately + self.skipWaiting(); +}); + +self.addEventListener('activate', event => { + // let this service worker control window clients immediately + event.waitUntil(self.clients.claim()); +}); diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/support/stash.js b/testing/web-platform/tests/presentation-api/receiving-ua/support/stash.js new file mode 100644 index 0000000000..eb63bb8623 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/support/stash.js @@ -0,0 +1,98 @@ +var Stash = function(inbound, outbound) { + this.stashPath = '/presentation-api/receiving-ua/support/stash.py?id='; + this.inbound = inbound; + this.outbound = outbound; +} + +// initialize a stash on wptserve +Stash.prototype.init = function() { + return Promise.all([ + fetch(this.stashPath + this.inbound).then(response => { + return response.text(); + }), + fetch(this.stashPath + this.outbound).then(response => { + return response.text(); + }) + ]); +} + +// upload a test result to a stash on wptserve +Stash.prototype.send = function(result) { + return fetch(this.stashPath + this.outbound, { + method: 'POST', + body: JSON.stringify({ type: 'data', data: result }) + }).then(response => { + return response.text(); + }).then(text => { + return text === 'ok' ? null : Promise.reject(); + }) +}; + +// upload a test result to a stash on wptserve via navigator.sendBeacon +Stash.prototype.sendBeacon = function(result) { + if ('sendBeacon' in navigator) { + navigator.sendBeacon(this.stashPath + this.outbound, JSON.stringify({ type: 'data', data: result })); + } + // Note: The following could be discarded, since XHR in synchronous mode is now being deprecated. + else { + return new Promise(resolve, reject => { + const xhr = new XMLHttpRequest(); + xhr.open('POST', this.stashPath + this.outbound, false); + xhr.send(JSON.stringify({ type: 'data', data: result })); + }); + } +}; + +// wait until a test result is uploaded to a stash on wptserve +Stash.prototype.receive = function() { + return new Promise((resolve, reject) => { + let intervalId; + const interval = 500; // msec + const polling = () => { + return fetch(this.stashPath + this.inbound).then(response => { + return response.text(); + }).then(text => { + if (text) { + try { + const json = JSON.parse(text); + if (json.type === 'data') + resolve(json.data); + else + reject(); + } catch(e) { + resolve(text); + } + clearInterval(intervalId); + } + }); + }; + intervalId = setInterval(polling, interval); + }); +}; + +// reset a stash on wptserve +Stash.prototype.stop = function() { + return Promise.all([ + fetch(this.stashPath + this.inbound).then(response => { + return response.text(); + }), + fetch(this.stashPath + this.outbound).then(response => { + return response.text(); + }) + ]).then(() => { + return Promise.all([ + fetch(this.stashPath + this.inbound, { + method: 'POST', + body: JSON.stringify({ type: 'stop' }) + }).then(response => { + return response.text(); + }), + fetch(this.stashPath + this.outbound, { + method: 'POST', + body: JSON.stringify({ type: 'stop' }) + }).then(response => { + return response.text(); + }) + ]); + }); +} diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/support/stash.py b/testing/web-platform/tests/presentation-api/receiving-ua/support/stash.py new file mode 100644 index 0000000000..83653d365a --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/support/stash.py @@ -0,0 +1,10 @@ +def main(request, response): + key = request.GET.first(b"id") + + if request.method == u"POST": + request.server.stash.put(key, request.body) + return b"ok" + else: + value = request.server.stash.take(key) + assert request.server.stash.take(key) is None + return value
\ No newline at end of file |