diff options
Diffstat (limited to 'testing/web-platform/tests/presentation-api')
60 files changed, 4277 insertions, 0 deletions
diff --git a/testing/web-platform/tests/presentation-api/META.yml b/testing/web-platform/tests/presentation-api/META.yml new file mode 100644 index 0000000000..64802e5cb4 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/META.yml @@ -0,0 +1,3 @@ +spec: https://w3c.github.io/presentation-api/ +suggested_reviewers: + - tidoust diff --git a/testing/web-platform/tests/presentation-api/README.md b/testing/web-platform/tests/presentation-api/README.md new file mode 100644 index 0000000000..417769a498 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/README.md @@ -0,0 +1,28 @@ +# Presentation API Tests + +This test suite is currently tracking the [Editor's Draft][editor-draft] of the Presentation API. The Presentation API describes the [conformance criteria for two classes of user agents][conformance-classes] ([controlling user agent][dfn-controlling-user-agent] and [receiving user agent][dfn-receiving-user-agent]). Each of the two subfolders [controlling-ua](./controlling-ua) and [receiving-ua](./receiving-ua) contains the Presentation API tests for each class of user agents. + +## IDL Tests + +The [controlling-ua](./controlling-ua) and [receiving-ua](./receiving-ua) subfolders contain files `idlharness.https.html` and `idlharness-manual.https.html` that define IDL tests of the Presentation API for controlling and receiving user agents, respectively. The WebIDL of the Presentation API spec is extracted from the [Editor's Draft][editor-draft] by running the following JavaScript code in the Dev. console of the Browser. + +```javascript +(function(){ + var s = ""; + [].forEach.call(document.getElementsByClassName("idl"), function(idl) { + if (!idl.classList.contains("extract")) + s += idl.textContent + "\n\n"; + }); + document.body.innerHTML = '<pre></pre>'; + document.body.firstChild.textContent = s; + })(); +``` + +## Receiving User Agent Tests + +The [receiving-ua](./receiving-ua) subfolder contains receiving user agent tests to be initiated by _a controlling user agent_. When the controlling user agent starts the test, it will ask a user to click a button and choose a presentation display. Once the presentation display is selected, the controlling user agent will request the receiving user agent to load and run the corresponding test placed in the [receiving-ua/support](./receiving-ua/support) subfolder. When the test ends, all results will appear on the controlling user agent's window. + +[editor-draft]: http://w3c.github.io/presentation-api/ +[conformance-classes]: http://w3c.github.io/presentation-api/#conformance-classes +[dfn-controlling-user-agent]: http://w3c.github.io/presentation-api/#dfn-controlling-user-agent +[dfn-receiving-user-agent]: http://w3c.github.io/presentation-api/#dfn-receiving-user-agent
\ No newline at end of file diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/PresentationAvailability_onchange-manual.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationAvailability_onchange-manual.https.html new file mode 100644 index 0000000000..48ecb610c6 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationAvailability_onchange-manual.https.html @@ -0,0 +1,89 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Monitoring the list of available presentation displays.</title> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs"> +<link rel="help" href="http://w3c.github.io/presentation-api/#monitoring-the-list-of-available-presentation-displays"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="common.js"></script> + +<p id="notice">Please wait for a moment...</p> +<p>The test passes if a "PASS" result appears.<br></p> + +<script> + // prevent the default timeout + setup({explicit_timeout: true}); + + const notice = document.getElementById('notice'); + + promise_test(t => { + // clean up the instruction notice when the test ends + t.add_cleanup(() => { + notice.parentNode.removeChild(notice); + }); + + // initialize a presentation request + const request = new PresentationRequest(presentationUrls); + + let availability, previousState, timeout; + + const wait = () => { + notice.textContent = 'Please wait for a moment... (It might take long time)'; + + // set timeout to observe the presentation availability + timeout = t.step_timeout(function() { + t.force_timeout(); + t.done(); + }, 90000); + }; + + const setup = () => { + // save the current value of the presentation availability + previousState = availability.value; + + // show an instruction notice + notice.textContent = 'Please make your presentation displays ' + + (previousState ? 'unavailable' : 'available') + + ' and click this button: '; + const button = document.createElement('button'); + button.textContent = 'Start Monitoring'; + button.onclick = wait; + notice.appendChild(button); + }; + + // check the event and its attributes + const checkEvent = evt => { + clearTimeout(timeout); + timeout = undefined; + + assert_true(evt.isTrusted && !evt.bubbles && !evt.cancelable && evt instanceof Event, 'A simple event is fired.'); + assert_equals(evt.type, 'change', 'The event name is "change".'); + assert_equals(evt.target, availability, 'event.target is the presentation availability.'); + assert_not_equals(previousState, availability.value, 'Value of the presentation availability is changed.'); + setup(); + }; + + 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]; + }); + }; + + // check the change of PresentationAvailability.value twice; "true to false" and "false to true" + return request.getAvailability().then(a => { + availability = a; + setup(); + + // wait until a "change" event is fired twice + var eventWatcher = new EventWatcher(t, availability, 'change'); + return watchEvent(availability, eventWatcher, 'change') + .then(checkEvent) + .then(() => { return eventWatcher.wait_for('change'); }) + .then(checkEvent); + }); + }); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnectionCloseEvent.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnectionCloseEvent.https.html new file mode 100644 index 0000000000..34c935a603 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnectionCloseEvent.https.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Constructing a PresentationConnectionCloseEvent</title> +<link rel="author" title="mark a. foltz" href="https://github.com/mfoltzgoogle"> +<link rel="help" href="http://w3c.github.io/presentation-api/#controlling-user-agent"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> + test(() => { + let eventWithMessage, eventWithoutMessage; + for (let reason of ["error", "closed", "wentaway"]) { + eventWithMessage = new PresentationConnectionCloseEvent("close", {reason: reason, message: "A message" }); + assert_equals(eventWithMessage.type, "close"); + assert_equals(eventWithMessage.reason, reason); + assert_equals(eventWithMessage.message, "A message"); + + eventWithoutMessage = new PresentationConnectionCloseEvent("close", {reason: reason}); + assert_equals(eventWithoutMessage.type, "close"); + assert_equals(eventWithoutMessage.reason, reason); + assert_equals(eventWithoutMessage.message, ""); + } + }); +</script> diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnection_onclose-manual.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnection_onclose-manual.https.html new file mode 100644 index 0000000000..f63806f82b --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnection_onclose-manual.https.html @@ -0,0 +1,145 @@ +<!DOCTYPE html> + +<meta charset="utf-8"> +<title>Closing a PresentationConnection</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="author" title="He Yue" href="mailto:yue.he@intel.com"> +<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="support/stash.js"></script> +<h2>Description</h2> +<p> + This test validates that after connection close,<br/> + the connection state is set closed,<br/> + the onclose EventHandler is triggered. +</p> +<br/> +<p>Click the button below to start the test.</p> +<button id="presentBtn" >Start Presentation Test</button> + +<script> + setup({explicit_timeout: true}); + + const presentBtn = document.getElementById('presentBtn'); + + promise_test(t => { + const clickWatcher = new EventWatcher(t, presentBtn, 'click'); + const request = new PresentationRequest(presentationUrls); + const stash = new Stash(stashIds.toController, stashIds.toReceiver); + let connection, eventWatcher; + + t.add_cleanup(() => { + if (connection) { + connection.onconnect = () => { connection.terminate(); }; + if (connection.state === 'closed') + request.reconnect(connection.id); + else + connection.terminate(); + } + stash.stop(); + }); + + const checkCloseEvent = evt => { + 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, 'closed', 'The reason for closing the presentation connection is "closed".'); + }; + + 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 waitForEvent = (obj, watcher, type) => { + const watchHandler = new Promise(resolve => { + obj['on' + type] = evt => { resolve(evt); }; + }); + return Promise.race([ watchHandler, watcher.wait_for(type) ]); + }; + + return Promise.all([ + clickWatcher.wait_for('click'), + stash.init() + ]).then(() => { + presentBtn.disabled = true; + return request.start(); + }).then(c => { + // Enable timeout again, cause no user action is needed from here. + t.step_timeout(() => { + t.force_timeout(); + t.done(); + }, 10000); + + connection = c; + eventWatcher = new EventWatcher(t, connection, ['connect', 'close', 'terminate']); + + // Step 1: close the presentation connection in "connecting" state + connection.close(); + return Promise.race([ + new Promise((_, reject) => { + t.step_timeout(() => { reject('The presentation connection in "connecting" state was not closed successfully.'); }, 3000); + }), + watchEvent(connection, eventWatcher, 'close') + ]); + }).then(evt => { + checkCloseEvent(evt); + + // Step 2: close the presentation connection in "connected" state + return request.reconnect(connection.id); + }).then(() => { + return eventWatcher.wait_for('connect'); + }).then(() => { + connection.close(); + return watchEvent(connection, eventWatcher, 'close'); + }).then(evt => { + checkCloseEvent(evt); + + // Step 3: check a connection closed by the receiving user agent + return request.reconnect(connection.id); + }).then(() => { + return eventWatcher.wait_for('connect'); + }).then(() => { + return Promise.all([ stash.send('close'), watchEvent(connection, eventWatcher, 'close') ]); + }).then(results => { + checkCloseEvent(results[1]); + + // Step 4: close the presentation connection in "closed" state (nothing should happen) + const closeWatcher = new EventWatcher(t, connection, 'close'); + connection.close(); + return Promise.race([ + new Promise(resolve => { t.step_timeout(resolve, 1000); }), + waitForEvent(connection, closeWatcher, 'close').then(() => { + assert_unreached('Invoking PresentationConnection.close() in the "closed" state causes nothing.'); }) + ]); + }).then(() => { + // Step 5: close the presentation connection in "terminated" state (nothing should happen) + return request.reconnect(connection.id); + }).then(() => { + return eventWatcher.wait_for('connect'); + }).then(() => { + connection.terminate(); + return eventWatcher.wait_for('terminate'); + }).then(() => { + const closeWatcher = new EventWatcher(t, connection, 'close'); + connection.close(); + return Promise.race([ + new Promise(resolve => { t.step_timeout(resolve, 1000); }), + waitForEvent(connection, closeWatcher, 'close').then(() => { + assert_unreached('Invoking PresentationConnection.close() in the "terminated" state causes nothing.'); }) + ]); + }); + }); +</script> diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnection_onconnect-manual.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnection_onconnect-manual.https.html new file mode 100644 index 0000000000..0f4a4ac369 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnection_onconnect-manual.https.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> + +<meta charset="utf-8"> +<title>Establishing a presentation connection</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="author" title="He Yue" href="mailto:yue.he@intel.com"> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs/"> +<link rel="help" href="https://w3c.github.io/presentation-api/#establishing-a-presentation-connection"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="common.js"></script> +<h2>Description</h2> +<p> + This test validates that after connection starts,<br/> + the onconnect EventHandler is triggered and connection state is connected. +</p> +<br/> +<p>Click the button below to start the test.</p> +<button id="presentBtn">Start Presentation Test</button> + +<script> + setup({explicit_timeout: true}); + + const presentBtn = document.getElementById('presentBtn'); + + promise_test(t => { + const clickWatcher = new EventWatcher(t, presentBtn, 'click'); + const request = new PresentationRequest(presentationUrls); + let connection; + + t.add_cleanup(() => { + if (connection) { + connection.onconnect = () => { connection.terminate(); }; + if (connection.state === 'closed') + request.reconnect(connection.id); + else + connection.terminate(); + } + }); + + 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]; + }); + }; + + return clickWatcher.wait_for('click').then(() => { + presentBtn.disabled = true; + + return request.start(); + }).then(c => { + // Enable timeout again, cause no user action is needed from here. + t.step_timeout(() => { + t.force_timeout(); + t.done(); + }, 5000); + + connection = c; + const eventWatcher = new EventWatcher(t, connection, 'connect'); + return watchEvent(connection, eventWatcher, 'connect'); + }).then(evt => { + assert_true(evt.isTrusted && !evt.bubbles && !evt.cancelable && evt instanceof Event, 'A simple event is fired.'); + assert_equals(evt.type, 'connect', 'The event name is "connect".'); + assert_equals(evt.target, connection, 'event.target is the presentation connection.'); + assert_equals(connection.state, 'connected', 'The presentation connection state is set to "connected".'); + }); + }); +</script> diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnection_onmessage-manual.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnection_onmessage-manual.https.html new file mode 100644 index 0000000000..59d7e8c0e0 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnection_onmessage-manual.https.html @@ -0,0 +1,144 @@ +<!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="support/stash.js"></script> + +<p id="notice"> + Click the button below and select the available presentation display, to start the manual test. The test passes if a "PASS" result appears.<br> + <button id="presentBtn">Start Presentation Test</button> +</p> + +<script> + setup({explicit_timeout: true}); + + const presentBtn = document.getElementById('presentBtn'); + + 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]; }); + }; + + promise_test(t => { + const clickWatcher = new EventWatcher(t, presentBtn, 'click'); + const request = new PresentationRequest(presentationUrls); + const stash = new Stash(stashIds.toController, stashIds.toReceiver); + let connection, watcher, eventWatcher; + + 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]; + }); + }; + + t.add_cleanup(() => { + if (connection) { + connection.onconnect = () => { connection.terminate(); }; + if (connection.state === 'closed') + request.reconnect(connection.id); + else + connection.terminate(); + } + const notice = document.getElementById('notice'); + notice.parentNode.removeChild(notice); + stash.stop(); + }); + + return Promise.all([ + clickWatcher.wait_for('click'), + stash.init() + ]).then(() => { + presentBtn.disabled = true; + return request.start(); + }).then(c => { + connection = c; + assert_equals(connection.state, 'connecting', 'the initial state of the presentation connection is "connecting"'); + assert_equals(connection.binaryType, 'arraybuffer', 'the default value of binaryType is "arraybuffer"'); + + // enable timeout again, cause no user action is needed from here. + t.step_timeout(() => { + t.force_timeout(); + t.done(); + }, 5000); + + watcher = new EventWatcher(t, connection, 'connect'); + return watcher.wait_for('connect'); + }).then(() => { + return stash.init(); + }).then(() => { + eventWatcher = new EventWatcher(t, connection, 'message'); + // Tell receiving page to start sending messages, and wait for first message + return Promise.all([ + stash.send('onmessage'), + watchEvent(connection, eventWatcher, 'message') + ]).then(results => results[1]); + }).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 receiving 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 receiving 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 receiving user agent)'); + + connection.binaryType = 'blob'; + return Promise.all([ + stash.send('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/controlling-ua/PresentationConnection_onterminate-manual.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnection_onterminate-manual.https.html new file mode 100644 index 0000000000..7fdc2dbdcd --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnection_onterminate-manual.https.html @@ -0,0 +1,157 @@ +<!DOCTYPE html> + +<meta charset="utf-8"> +<title>Terminating a presentation in a controlling browsing context</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="author" title="Chunyan Wang" href="mailto:chunyanx.wang@intel.com"> +<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-controlling-browsing-context"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="common.js"></script> +<style>iframe { display: none; }</style> +<p> + Click the button below and select the available presentation display, to start the manual test. The test passes if a "PASS" result appears.<br> + This test asks you to click the button twice, unless the test fails.<br> +</p> +<button id="presentBtn">Start Presentation Test</button> +<iframe id="childFrame" src="support/iframe.html"></iframe> + +<script> + setup({explicit_timeout: true}); + const presentBtn = document.getElementById('presentBtn'); + const childFrame = document.getElementById('childFrame'); + + promise_test(t => { + const clickWatcher = new EventWatcher(t, presentBtn, 'click'); + const messageWatcher = new EventWatcher(t, window, 'message'); + const request = new PresentationRequest(presentationUrls); + let connection, eventWatcher, timeout; + + t.add_cleanup(() => { + if (connection) { + connection.onconnect = () => { connection.terminate(); }; + if (connection.state === 'closed') + request.reconnect(connection.id); + else + connection.terminate(); + } + }); + + const startTimeout = () => { + timeout = t.step_timeout(() => { + t.force_timeout(); + t.done(); + }, 10000); + }; + + const checkTerminateEvent = evt => { + assert_true(evt.isTrusted && !evt.bubbles && !evt.cancelable && evt instanceof Event, 'A simple event is fired.'); + assert_equals(evt.type, 'terminate', 'The event name is "terminate".'); + assert_equals(evt.target, connection, 'event.target is the presentation connection.'); + assert_equals(connection.state, 'terminated', 'State of the presentation connection is "terminated".'); + }; + + 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 waitForEvent = (obj, watcher, type) => { + const watchHandler = new Promise(resolve => { + obj['on' + type] = evt => { resolve(evt); }; + }); + return Promise.race([ watchHandler, watcher.wait_for(type) ]); + }; + + return Promise.all([ + clickWatcher.wait_for('click'), + messageWatcher.wait_for('message') + ]).then(() => { + presentBtn.disabled = true; + + return request.start(); + }).then(c => { + startTimeout(); + + connection = c; + eventWatcher = new EventWatcher(t, connection, 'terminate'); + + // Step 1: terminate the presentation when the presentation connection is in "connecting" state + connection.terminate(); + return Promise.race([ + new Promise((_, reject) => { + t.step_timeout(() => { reject('The presentation is not terminated successfully when the presentation connection in "connecting" state.'); }, 3000); + }), + watchEvent(connection, eventWatcher, 'terminate') + ]); + }).then(evt => { + checkTerminateEvent(evt); + + // Step 2: terminate the presentation when the presentation connection is in "closed" state (nothing should happen) + presentBtn.textContent = 'Continue Presentation Test'; + presentBtn.disabled = false; + clearTimeout(timeout); + return clickWatcher.wait_for('click'); + }).then(() => { + return request.start(); + }).then(c => { + startTimeout(); + connection = c; + eventWatcher = new EventWatcher(t, connection, ['connect', 'close', 'terminate']); + return eventWatcher.wait_for('connect'); + }).then(() => { + connection.close(); + return eventWatcher.wait_for('close'); + }).then(() => { + const terminateWatcher = new EventWatcher(t, connection, 'terminate'); + connection.terminate(); + return Promise.race([ + new Promise(resolve => { t.step_timeout(resolve, 1000); }), + waitForEvent(connection, terminateWatcher, 'terminate').then(() => { + assert_unreached('Invoking PresentationConnection.terminate() in the "closed" state causes nothing.'); }) + ]); + }).then(() => { + // Step 3: terminate the presentation when the presentation connection is in "connected" state; + // this step also checks an event fired at another presentation connection in a nested browsing context + return request.reconnect(connection.id); + }).then(() => { + return eventWatcher.wait_for('connect'); + }).then(() => { + childFrame.contentWindow.postMessage('terminate?id=' + connection.id, '*'); + return messageWatcher.wait_for('message') + }).then(() => { + connection.terminate(); + return Promise.race([ + new Promise((_, reject) => { + t.step_timeout(() => { reject('The presentation is not terminated successfully when the presentation connection in "connected" state.'); }, 3000); + }), + Promise.all([ + watchEvent(connection, eventWatcher, 'terminate'), + messageWatcher.wait_for('message') + ]) + ]); + }).then(results => { + checkTerminateEvent(results[0]); + const evt = results[1].data; + assert_true(evt.isSimpleEvent, 'A simple event is fired in a nested browsing context.'); + assert_equals(evt.type, 'terminate', 'The event name is "terminate".'); + assert_true(evt.checkConnection, 'event.target is the presentation connection.'); + assert_equals(evt.state, 'terminated', 'State of the presentation connection is "terminated".'); + + // Step 4: terminate the presentation when the presentation connection is in "terminated" state (nothing should happen) + const terminateWatcher = new EventWatcher(t, connection, 'terminate'); + connection.terminate(); + return Promise.race([ + new Promise(resolve => { t.step_timeout(resolve, 1000); }), + waitForEvent(connection, terminateWatcher, 'terminate').then(() => { + assert_unreached('Invoking PresentationConnection.terminate() in the "terminated" state causes nothing.'); }) + ]); + }); + }); +</script> diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnection_send-manual.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnection_send-manual.https.html new file mode 100644 index 0000000000..fcc91212e0 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnection_send-manual.https.html @@ -0,0 +1,125 @@ +<!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="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="common.js"></script> +<script src="support/stash.js"></script> + +<p id="notice"> + Click the button below and select the available presentation display, to start the manual test. The test passes if a "PASS" result appears.<br> + <button id="presentBtn">Start Presentation Test</button> +</p> + +<script> + setup({explicit_timeout: true}); + + const presentBtn = document.getElementById('presentBtn'); + + 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; + } + + // convert ArrayBuffer or Uint8Array into string + const toText = buf => { + const arr = toUint8Array(buf); + return !buf ? null : arr.reduce((result, item) => { + return result + String.fromCharCode(item); + }, ''); + } + + promise_test(t => { + const clickWatcher = new EventWatcher(t, presentBtn, 'click'); + const stash = new Stash(stashIds.toController, stashIds.toReceiver); + const request = new PresentationRequest(presentationUrls); + let connection, watcher; + + t.add_cleanup(() => { + if (connection) { + connection.onconnect = () => { connection.terminate(); }; + if (connection.state === 'closed') + request.reconnect(connection.id); + else + connection.terminate(); + } + const notice = document.getElementById('notice'); + notice.parentNode.removeChild(notice); + stash.stop(); + }); + + return clickWatcher.wait_for('click').then(() => { + presentBtn.disabled = true; + + return request.start(); + }).then(c => { + connection = c; + + // send data in "connecting" state (throws an exception) + assert_equals(connection.state, 'connecting', 'the initial state of the presentation connection is "connecting"'); + assert_throws_dom('InvalidStateError', () => { + connection.send(''); + }, 'an InvalidStateError is thrown if the state is "connecting"'); + + // enable timeout again, cause no user action is needed from here. + t.step_timeout(() => { + t.force_timeout(); + t.done(); + }, 10000); + + watcher = new EventWatcher(t, connection, ['connect', 'close', 'terminate']); + return watcher.wait_for('connect'); + }).then(() => { + return stash.init(); + }).then(() => { + return Promise.all([ stash.send('send'), stash.receive() ]); + }).then(results => { + // send messages + 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(stash => { + // verify messages + const results = JSON.parse(stash); + assert_true(!!results[0] && results[0].type === 'text' && results[0].data === message1, 'send a string correctly'); + assert_true(!!results[1] && results[1].type === 'text' && results[1].data === message2, 'send a string correctly'); + assert_true(!!results[2] && results[2].type === 'binary' && results[2].data === toText(message3), 'send a Blob correctly'); + assert_true(!!results[3] && results[3].type === 'binary' && results[3].data === toText(message4), 'send a ArrayBuffer correctly'); + assert_true(!!results[4] && results[4].type === 'binary' && results[4].data === toText(message5), 'send a ArrayBufferView correctly'); + + // send data in "closed" state (throws an exception) + connection.close(); + return watcher.wait_for('close'); + }).then(() => { + assert_equals(connection.state, 'closed', 'the state is set to "closed" when the presentation connection is closed'); + assert_throws_dom('InvalidStateError', () => { + connection.send(''); + }, 'an InvalidStateError is thrown if the state is "closed"'); + + // reconnect and terminate the connection + return request.reconnect(connection.id); + }).then(() => { + return watcher.wait_for('connect'); + }).then(() => { + // send data in "terminated" state (throws an exception) + connection.terminate(); + return watcher.wait_for('terminate'); + }).then(() => { + assert_equals(connection.state, 'terminated', 'the state is set to "terminated" when the presentation connection is terminated'); + assert_throws_dom('InvalidStateError', () => { + connection.send(''); + }, 'an InvalidStateError is thrown if the state is "terminated"'); + }); + }); +</script> diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_error.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_error.https.html new file mode 100644 index 0000000000..68e0fbcd22 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_error.https.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Constructing a PresentationRequest (Error)</title> +<link rel="author" title="Franck William Taffo" 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/#constructing-a-presentationrequest"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + + test(() => { + assert_throws_js(TypeError, () => { + new PresentationRequest(); + }, 'Call PresentationRequest() constructor without presentation URL. TypeError Exception expected.'); + + assert_throws_dom('NotSupportedError', () => { + new PresentationRequest([]); + }, 'Call PresentationRequest constructor with an empty sequence. NotSupportedError Exception expected.'); + + assert_throws_dom('SyntaxError', () => { + new PresentationRequest('https://@'); + }, 'Call PresentationRequest constructor with an invalid URL. SyntaxError Exception expected.'); + + assert_throws_dom('NotSupportedError', () => { + new PresentationRequest('unsupported://example.com'); + }, 'Call PresentationRequest constructor with an unsupported URL. NotSupportedError expected.'); + + assert_throws_dom('SyntaxError', function() { + new PresentationRequest(['presentation.html', 'https://@']); + }, 'Call PresentationRequest constructor with a sequence of URLs, one of them invalid. SyntaxError Exception expected.'); + + assert_throws_dom('NotSupportedError', function() { + new PresentationRequest(['unsupported://example.com', 'invalid://example.com']); + }, 'Call PresentationRequest constructor only with a sequence of unsupported URLs. NotSupportedError Exception expected.'); + }); +</script> diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_mixedcontent.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_mixedcontent.https.html new file mode 100644 index 0000000000..3b19b9c9bd --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_mixedcontent.https.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Creating a PresentationRequest with an a priori unauthenticated URL in an HTTPS context throws a SecurityError exception.</title> +<link rel="author" title="Francois Daoust" href="https://www.w3.org/People/#fd"> +<link rel="help" href="http://w3c.github.io/presentation-api/#constructing-a-presentationrequest"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> + function createPresentation() { + var request = new PresentationRequest('http://example.org/presentation.html'); + }; + + test(function () { + assert_throws_dom('SecurityError', createPresentation); + }); +</script> + diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_mixedcontent_multiple.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_mixedcontent_multiple.https.html new file mode 100644 index 0000000000..e9571224d6 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_mixedcontent_multiple.https.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Creating a PresentationRequest with a set of URLs containing an a priori unauthenticated URL in an HTTPS context throws a SecurityError exception.</title> +<link rel="author" title="Francois Daoust" href="https://www.w3.org/People/#fd"> +<link rel="help" href="http://w3c.github.io/presentation-api/#constructing-a-presentationrequest"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> + function createPresentation() { + var request = new PresentationRequest([ + 'presentation.html', + 'http://example.org/presentation.html' + ]); + }; + + test(function () { + assert_throws_dom('SecurityError', createPresentation); + }); +</script> + diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_onconnectionavailable-manual.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_onconnectionavailable-manual.https.html new file mode 100644 index 0000000000..d06daae11c --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_onconnectionavailable-manual.https.html @@ -0,0 +1,85 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Firing a connectionavailable event at a controlling user agent</title> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs"> +<link rel="help" href="https://w3c.github.io/presentation-api/#starting-a-presentation"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="common.js"></script> +<p>Click the button below and select the available presentation display, to start the manual test.</p> +<button id="presentBtn">Start Presentation Test</button> + + +<script> + // disable the timeout function for the tests + setup({explicit_timeout: true}); + + // ---------- + // DOM Object + // ---------- + const presentBtn = document.getElementById('presentBtn'); + + // -------------------------------------------------------------------------- + // Start New PresentationRequest.onconnectionavailable Test (success) - begin + // -------------------------------------------------------------------------- + promise_test(t => { + const clickWatcher = new EventWatcher(t, presentBtn, 'click'); + const request = new PresentationRequest(presentationUrls); + let connection; + + t.add_cleanup(() => { + if (connection) { + connection.onconnect = () => { connection.terminate(); }; + if (connection.state === 'closed') + request.reconnect(connection.id); + else + connection.terminate(); + } + }); + + 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]; + }); + }; + + return clickWatcher.wait_for('click').then(() => { + presentBtn.disabled = true; + + // Note: During starting a presentation, the connectionavailable event is fired (step 9) + // after the promise P is resolved (step 8). + return request.start(); + }).then(c => { + connection = c; + assert_equals(connection.state, 'connecting', 'The initial state of the presentation connection is "connecting".'); + assert_true(!!connection.id, 'The connection ID is set.'); + assert_equals(typeof connection.id, 'string', 'The connection ID is a string.'); + assert_true(connection instanceof PresentationConnection, 'The connection is an instance of PresentationConnection.'); + + const eventWatcher = new EventWatcher(t, request, 'connectionavailable'); + const timeout = new Promise((_, reject) => { + // This test fails if request.onconnectionavailable is not invoked although the presentation is started successfully + // or the presentation fails to be started. + t.step_timeout(() => { reject('The connectionavailable event was not fired (timeout).'); }, 5000);} + ); + return Promise.race([ watchEvent(request, eventWatcher, 'connectionavailable'), timeout ]); + }).then(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, request, 'event.target is the presentation request.'); + assert_true(evt.connection instanceof PresentationConnection, 'event.connection is a presentation connection.'); + assert_equals(evt.connection, connection, 'event.connection is set to the presentation which the promise is resolved with.'); + }); + }); + // ------------------------------------------------------------------------ + // Start New PresentationRequest.onconnectionavailable Test (success) - end + // ------------------------------------------------------------------------ +</script> + diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_sandboxing_error.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_sandboxing_error.https.html new file mode 100644 index 0000000000..ad5e32a45a --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_sandboxing_error.https.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Sandboxing: Creating a PresentationRequest from a nested context fails when allow-presentation is not set</title> +<link rel="author" title="Francois Daoust" href="https://www.w3.org/People/#fd"> +<link rel="help" href="http://w3c.github.io/presentation-api/#constructing-a-presentationrequest"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + async_test(function (t) { + var startWhenReady = t.step_func(function (ev) { + var childFrame = document.getElementById('childFrame'); + if (ev.data === 'ready') { + window.removeEventListener('message', startWhenReady); + window.addEventListener('message', checkFinalMessage); + childFrame.contentWindow.postMessage('create', '*'); + } + }); + + var checkFinalMessage = t.step_func_done(function (ev) { + assert_equals(ev.data, 'SecurityError', 'Presentation sandboxing did not work as expected.'); + }); + + window.addEventListener('message', startWhenReady); + }); +</script> +<iframe id="childFrame" sandbox="allow-scripts" style="display:none" src="support/iframe.html"></iframe> diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_sandboxing_success.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_sandboxing_success.https.html new file mode 100644 index 0000000000..7ede80d595 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_sandboxing_success.https.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Sandboxing: Creating a PresentationRequest from a nested context succeeds when allow-presentation is set</title> +<link rel="author" title="Francois Daoust" href="https://www.w3.org/People/#fd"> +<link rel="help" href="http://w3c.github.io/presentation-api/#constructing-a-presentationrequest"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + async_test(function (t) { + var startWhenReady = t.step_func(function (ev) { + var childFrame = document.getElementById('childFrame'); + if (ev.data === 'ready') { + window.removeEventListener('message', startWhenReady); + window.addEventListener('message', checkFinalMessage); + childFrame.contentWindow.postMessage('create', '*'); + } + }); + + var checkFinalMessage = t.step_func_done(function (ev) { + assert_equals(ev.data, 'success', 'Presentation sandboxing did not work as expected.'); + }); + + window.addEventListener('message', startWhenReady); + }); +</script> +<iframe id="childFrame" sandbox="allow-scripts allow-presentation" style="display:none" src="support/iframe.html"></iframe> diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_success.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_success.https.html new file mode 100644 index 0000000000..890e0ed624 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_success.https.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Constructing a PresentationRequest</title> +<link rel="author" title="Franck William Taffo" 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/#constructing-a-presentationrequest"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> + test(() => { + let request = new PresentationRequest('presentation.html'); + assert_true(request instanceof PresentationRequest, 'An instance of PresentationRequest with a relative presentation URL is constructed successfully.'); + + request = new PresentationRequest('https://example.org/'); + assert_true(request instanceof PresentationRequest, 'An instance of PresentationRequest with an absolute presentation URL is constructed successfully.'); + + request = new PresentationRequest([ + 'presentation.html', + 'https://example.org/presentation/' + ]); + assert_true(request instanceof PresentationRequest, 'An instance of PresentationRequest with an array of presentation URLs is constructed successfully.'); + + request = new PresentationRequest([ + 'unsupported://example.com', + 'presentation.html', + 'https://example.org/presentation/' + ]); + assert_true(request instanceof PresentationRequest, 'An unsupported URL in an array of presentation URLs is ignored successfully.'); + }); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/common.js b/testing/web-platform/tests/presentation-api/controlling-ua/common.js new file mode 100644 index 0000000000..4a73acd1de --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/common.js @@ -0,0 +1,25 @@ +(function (window) { + // Cast ID of the main custom receiver application linked with the test suite + // That application ID, maintained by W3C team, points at: + // https://[W3C test server]/presentation-api/controlling-ua/support/presentation.html + // + // NB: this mechanism should be improved later on as tests should not depend + // on something that directly or indirectly maps to a resource on the W3C test + // server. + var castAppId = '915D2A2C'; + var castUrl = 'cast:' + castAppId; + + window.presentationUrls = [ + 'support/presentation.html', + castUrl + ]; + + // Both a controlling side and a receiving one must share the same Stash ID to + // transmit data from one to the other. On the other hand, due to polling mechanism + // which cleans up a stash, stashes in both controller-to-receiver direction + // and one for receiver-to-controller are necessary. + window.stashIds = { + toController: '9bf08fea-a71a-42f9-b3c4-fa19499e4d12', + toReceiver: 'f1fdfd10-b606-4748-a644-0a8e9df3bdd6' + } +})(window); diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/defaultRequest.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/defaultRequest.https.html new file mode 100644 index 0000000000..713cea7f9a --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/defaultRequest.https.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Setting a default presentation request</title> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs"> +<link rel="help" href="http://w3c.github.io/presentation-api/#controlling-user-agent"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> + test(() => { + assert_equals(navigator.presentation.defaultRequest, null, 'The initial value of the default presentation request is null.'); + + const request = new PresentationRequest('https://example.org/'); + navigator.presentation.defaultRequest = request; + assert_equals(navigator.presentation.defaultRequest, request, 'The default presentation request is set to an instance of PresentationRequest.'); + + assert_throws_js(TypeError, () => { + navigator.presentation.defaultRequest = {}; + }, 'The default presentation request cannot be set to any value but an instance of PresentationRequest or null.'); + + navigator.presentation.defaultRequest = null; + assert_equals(navigator.presentation.defaultRequest, null, 'The default presentation request is set to null.'); + }); +</script> diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/defaultRequest_success-manual.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/defaultRequest_success-manual.https.html new file mode 100644 index 0000000000..840b72b849 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/defaultRequest_success-manual.https.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>[Optional] Starting a presentation from the browser using a default presentation request.</title> +<link rel="author" title="Marius Wessel" 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/#dom-presentation-defaultrequest"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="common.js"></script> + +<p> + Click the button or the menu item for presentation on your browser (for example, "Cast"), <br> + to start the manual test, and select a presentation display when prompted to do so.<br> + The test passes if a "PASS" result appears. +</p> +<p id="notice"> + If your browser does not support <code>defaultRequest</code>, please click this button: <button id="notsupported">Not Supported</button> +</p> + +<script> + // disable the timeout function for the tests + // and call 'done()' when the tests cases are finished. + setup({explicit_timeout: true}); + + // ----------- + // DOM Element + // ----------- + var button = document.getElementById('notsupported'), + notice = document.getElementById('notice'); + + // ------------------------------ + // Start New Presentation with + // 'default request' Test - BEGIN + // ------------------------------ + async_test(function(t) { + // clean up the presentation and the instruction notice when the test ends + var connection; + t.add_cleanup(function() { + notice.parentNode.removeChild(notice); + if(connection) + connection.terminate(); + }); + // set an event handler to make the test fail when the button is clicked + button.onclick = t.step_func_done(function() { + assert_unreached('This browser does not support defaultRequest.'); + }); + // set up a default presentation request + var request = new PresentationRequest(presentationUrls); + navigator.presentation.defaultRequest = request; + request.onconnectionavailable = t.step_func_done(function (evt) { + connection = evt.connection; + // check the presentation connection and its attributes + assert_equals(connection.state, 'connecting', 'The initial state of the presentation connection is "connecting".'); + assert_true(!!connection.id, 'The connection ID is set.'); + assert_equals(typeof connection.id, 'string', 'The connection ID is a string.'); + assert_true(connection instanceof PresentationConnection, 'The connection is an instance of PresentationConnection.'); + }); + }); + // ---------------------------- + // Start New Presentation with + // 'default request' Test - END + // ---------------------------- +</script> diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/getAvailability.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/getAvailability.https.html new file mode 100644 index 0000000000..71b19e6a4e --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/getAvailability.https.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Getting the presentation displays availability information.</title> +<meta name="timeout" content="long"> +<link rel="author" title="Marius Wessel" 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-presentation-display-availability"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="common.js"></script> + +<p>The test passes if a "PASS" result appears.</p> + +<script> + + // --------------------------------------- + // Presentation Availability Tests - begin + // --------------------------------------- + + const catchNotSupported = err => { + assert_equals(err.name, 'NotSupportedError', 'getAvailability() rejects a Promise with a NotSupportedError exception, if the browser can find presentation displays only when starting a connection.') + }; + + promise_test(t => { + let availability; + + const request = new PresentationRequest(presentationUrls); + assert_true(request instanceof PresentationRequest, 'The request is an instance of PresentationRequest.'); + + const promise = request.getAvailability(); + assert_true(promise instanceof Promise, 'PresentationRequest.getAvailability() returns a Promise.'); + const samePromise = request.getAvailability(); + assert_true(samePromise instanceof Promise, 'PresentationRequest.getAvailabilty() returns a Promise.'); + assert_equals(promise, samePromise, 'If the PresentationRequest object has an unsettled Promise, getAvailability returns that Promise.'); + + return promise.then(a => { + availability = a; + assert_true(availability instanceof PresentationAvailability, 'The promise is resolved with an instance of PresentationAvailability.'); + assert_equals(typeof availability.value, 'boolean', 'The availability has an boolean value.'); + + const request2 = new PresentationRequest('https://example.com'); + return request2.getAvailability(); + }).then(a => { + assert_not_equals(availability, a, 'A presentation availability object is newly created if the presentation request has a newly added presentation URLs.'); + + const newPromise = request.getAvailability(); + assert_not_equals(promise, newPromise, 'If the Promise from a previous call to getAvailability has already been settled, getAvailability returns a new Promise.'); + + return newPromise.then(newAvailability => { + assert_equals(availability, newAvailability, 'Promises from a PresentationRequest\'s getAvailability are resolved with the same PresentationAvailability object.'); + }, catchNotSupported); + }, catchNotSupported); + }); +</script> diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/getAvailability_sandboxing_success.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/getAvailability_sandboxing_success.https.html new file mode 100644 index 0000000000..daa0a0ff14 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/getAvailability_sandboxing_success.https.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Sandboxing: Retrieving display availability from a nested context succeeds when allow-presentation is set</title> +<link rel="author" title="Francois Daoust" href="https://www.w3.org/People/#fd"> +<link rel="help" href="http://w3c.github.io/presentation-api/#dom-presentationrequest-getavailability"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + async_test(function (t) { + function startWhenReady(ev) { + var childFrame = document.getElementById('childFrame'); + if (ev.data === 'ready') { + window.removeEventListener('message', startWhenReady); + childFrame.contentWindow.postMessage('getAvailability', '*'); + window.addEventListener('message', t.step_func(function (ev) { + assert_equals(ev.data, 'success', + 'Presentation sandboxing did not work as expected.'); + t.done(); + })); + } + } + window.addEventListener('message', startWhenReady); + }); +</script> +<iframe id="childFrame" sandbox="allow-scripts allow-presentation" style="display:none" src="support/iframe.html"></iframe> diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/idlharness.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/idlharness.https.html new file mode 100644 index 0000000000..1cb8830f89 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/idlharness.https.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Presentation API IDL tests for Controlling User Agent</title> +<meta name="timeout" content="long"> +<link rel="author" title="Louay Bassbouss" href="http://www.fokus.fraunhofer.de"> +<link rel="help" href="http://w3c.github.io/presentation-api/#dfn-controlling-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> + "use strict"; + + promise_test(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: [ + 'PresentationReceiver', + 'PresentationConnectionList' + ] + }); + idl_array.add_dependency_idls(dom); + idl_array.add_dependency_idls(html); + + try { + window.presentation_request = new PresentationRequest("/presentation-api/receiving-ua/idlharness.html"); + window.presentation_request_urls = new PresentationRequest([ + "/presentation-api/receiving-ua/idlharness.html", + "https://www.example.com/presentation.html" + ]); + navigator.presentation.defaultRequest = window.presentation_request; + } catch (e) { + // Will be surfaced in idlharness.js's test_object below. + } + + idl_array.add_objects({ + Presentation: ['navigator.presentation'], + PresentationRequest: [ + 'navigator.presentation.defaultRequest', + 'presentation_request', + 'presentation_request_urls' + ], + }); + idl_array.test(); + }, "Test IDL implementation of Presentation API"); +</script> diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/reconnectToMultiplePresentations_success-manual.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/reconnectToMultiplePresentations_success-manual.https.html new file mode 100644 index 0000000000..ab1bd8089d --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/reconnectToMultiplePresentations_success-manual.https.html @@ -0,0 +1,138 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Reconnecting presentations on two distinct displays</title> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs"> +<link rel="help" href="http://w3c.github.io/presentation-api/#dom-presentationrequest-reconnect"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="common.js"></script> +<style> +#second-step { + display: none; +} +</style> + +<p id="first-step">Click the button below and select the available presentation display, to start the manual test.</p> +<p id="second-step">Click the button below and select the other available presentation display, to continue the manual test.</p> +<p>This test asks you to click the button twice, unless the test fails.<br> +<em>This test requires two or more available displays.<em></p> +<button id="presentBtn">Start Presentation Test</button> + + +<script> + promise_test(async t => { + const presentBtn = document.getElementById("presentBtn"); + + const request1 = new PresentationRequest(presentationUrls); + const request2 = new PresentationRequest(presentationUrls); + const clickWatcher = new EventWatcher(t, presentBtn, 'click'); + let connection1, connection2, eventWatcher1, eventWatcher2, timer; + + t.add_cleanup(() => { + [ + { connection: connection1, request: request1 }, + { connection: connection2, request: request2 } + ].forEach(p => { + if (p.connection) { + p.connection.onconnect = () => { p.connection.terminate(); }; + if (p.connection.state == 'closed') + p.request.reconnect(p.connection.id); + else + p.connection.terminate(); + } + }); + }); + + const disableTimeout = () => { + setup({explicit_timeout: true}); + if (timer) { + clearTimeout(timer); + timer = null; + } + }; + + const enableTimeout = () => { + timer = t.step_timeout(() => { + t.force_timeout(); + t.done(); + }, 5000); + } + + disableTimeout(); + + await clickWatcher.wait_for('click'); + presentBtn.disabled = true; + connection1 = await request1.start(); + + presentBtn.disabled = false; + document.getElementById('first-step').style.display = 'none'; + document.getElementById('second-step').style.display = 'block'; + presentBtn.innerText = 'Continue Presentation Test'; + eventWatcher1 = new EventWatcher(t, connection1, ['connect', 'close', 'terminate']); + + await Promise.all([ + eventWatcher1.wait_for('connect'), + (async () => { + await clickWatcher.wait_for('click'); + presentBtn.disabled = true; + + connection2 = await request2.start(); + enableTimeout(); + eventWatcher2 = new EventWatcher(t, connection2, ['connect', 'close', 'terminate']); + await eventWatcher2.wait_for('connect'); + })() + ]); + + connection1.close(); + assert_equals(connection2.state, 'connected', + 'Closing one presentation connection does not affect the state of the other.'); + + await eventWatcher1.wait_for('close'); + assert_equals(connection1.state, 'closed', 'The presentation connection is successfully closed.'); + + connection2.close(); + await eventWatcher2.wait_for('close'); + assert_equals(connection2.state, 'closed', 'The presentation connection is successfully closed.'); + + const c11 = await request1.reconnect(connection1.id); + assert_equals(c11, connection1, 'The promise is resolved with the existing presentation connection.'); + + const c22 = await request2.reconnect(connection2.id); + assert_equals(c22, connection2, 'The promise is resolved with the existing presentation connection.'); + + await Promise.all([ + eventWatcher1.wait_for('connect'), + eventWatcher2.wait_for('connect') + ]); + + assert_equals(connection1.state, 'connected', 'The presentation connection is successfully reconnected.'); + assert_equals(connection2.state, 'connected', 'The presentation connection is successfully reconnected.'); + + // Reconnecting a presentation via a different presentation request with the same presentation + // URLs will succeed + connection2.close(); + await eventWatcher2.wait_for('close'); + const c12 = await request1.reconnect(connection2.id); + assert_equals(c12, connection2, 'The promise is resolved with the existing presentation connection.'); + + connection1.close(); + await eventWatcher1.wait_for('close'); + const c21 = await request2.reconnect(connection1.id); + assert_equals(c21, connection1, 'The promise is resolved with the existing presentation connection.'); + + await Promise.all([ + eventWatcher1.wait_for('connect'), + eventWatcher2.wait_for('connect') + ]); + + assert_equals(connection1.state, 'connected', 'The presentation connection is successfully reconnected.'); + assert_equals(connection2.state, 'connected', 'The presentation connection is successfully reconnected.'); + connection1.terminate(); + connection2.terminate(); + + await Promise.all([ + eventWatcher1.wait_for('terminate'), + eventWatcher2.wait_for('terminate') + ]); + }); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/reconnectToPresentation_notfound_error-manual.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/reconnectToPresentation_notfound_error-manual.https.html new file mode 100644 index 0000000000..f92ab80f73 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/reconnectToPresentation_notfound_error-manual.https.html @@ -0,0 +1,61 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Calling "reconnect" with a wrong presentation ID fails with a NotFoundError exception</title> +<link rel="author" title="Franck William Taffo" href="http://www.fokus.fraunhofer.de"> +<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/#dom-presentationrequest-reconnect"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="common.js"></script> + +<p>Click the button below to start the manual test. Select a presentation device after the selection dialog is prompted. + The test assumes that at least one presentation device is available. The test passes if a "PASS" result appears.</p> +<button id="startBtn">Start Test</button> + +<script> + promise_test(async t => { + const startBtn = document.getElementById('startBtn'); + const wrongPresentationId = "wrongPresentationId"; + const request1 = new PresentationRequest(presentationUrls); + const request2 = new PresentationRequest('https://www.w3.org'); + let connection1, eventWatcher1; + + t.add_cleanup(() => { + if (connection1) { + connection1.onconnect = () => { connection1.terminate(); } + if (connection1.state === 'closed') + request1.reconnect(connection1.id); + else + connection1.terminate(); + } + }); + + await promise_rejects_dom(t, 'NotFoundError', request1.reconnect(wrongPresentationId), + 'Reconnecting with an unknown presentation ID fails with a NotFoundError exception.'); + + setup({explicit_timeout: true}); + const clickWatcher = new EventWatcher(t, startBtn, 'click'); + await clickWatcher.wait_for('click'); + connection1 = await request1.start(); + + t.step_timeout(() => { + t.force_timeout(); + t.done(); + }, 5000); + + startBtn.disabled = true; + eventWatcher1 = new EventWatcher(t, connection1, ['connect', 'close', 'terminate']); + await eventWatcher1.wait_for('connect'); + connection1.close(); + await eventWatcher1.wait_for('close'); + + await promise_rejects_dom(t, 'NotFoundError', request2.reconnect(connection1.id), + 'Reconnecting with a presentation ID on a presentation request with a different URL fails with a NotFoundError exception.'); + + await request1.reconnect(connection1.id); + await eventWatcher1.wait_for('connect'); + connection1.terminate(); + await eventWatcher1.wait_for('terminate'); + }); +</script> diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/reconnectToPresentation_sandboxing_success.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/reconnectToPresentation_sandboxing_success.https.html new file mode 100644 index 0000000000..96505aca05 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/reconnectToPresentation_sandboxing_success.https.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Sandboxing: Reconnecting a presentation from a nested context succeeds when allow-presentation is set</title> +<link rel="author" title="Francois Daoust" href="https://www.w3.org/People/#fd"> +<link rel="help" href="http://w3c.github.io/presentation-api/#dom-presentationrequest-reconnect"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + async_test(function (t) { + function startWhenReady(ev) { + var childFrame = document.getElementById('childFrame'); + if (ev.data === 'ready') { + window.removeEventListener('message', startWhenReady); + childFrame.contentWindow.postMessage('reconnect', '*'); + window.addEventListener('message', t.step_func(function (ev) { + assert_equals(ev.data, 'NotFoundError', + 'Presentation sandboxing did not work as expected.'); + t.done(); + })); + } + } + window.addEventListener('message', startWhenReady); + }); +</script> +<iframe id="childFrame" sandbox="allow-scripts allow-presentation" style="display:none" src="support/iframe.html"></iframe> diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/reconnectToPresentation_success-manual.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/reconnectToPresentation_success-manual.https.html new file mode 100644 index 0000000000..a2619042f8 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/reconnectToPresentation_success-manual.https.html @@ -0,0 +1,185 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Reconnect to presentation success manual test</title> +<link rel="author" title="Marius Wessel" href="http://www.fokus.fraunhofer.de"> +<link rel="author" title="Louay Bassbouss" href="http://www.fokus.fraunhofer.de"> +<link rel="help" href="http://w3c.github.io/presentation-api/#dom-presentationrequest-reconnect"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="common.js"></script> +<style>iframe { display: none; }</style> + +<p>Click the button below to start the manual test. Select a presentation device after the selection dialog is prompted. + The test assumes that at least one presentation device is available. The test passes if a "PASS" result appears.</p> +<button id="startBtn">Start Test</button> +<iframe id="childFrame" src="support/iframe.html"></iframe> + +<script> + let receiverStack; + add_completion_callback(() => { + // overwrite a stack written in the test result + if (receiverStack) { + document.querySelector('#log pre').textContent = receiverStack; + } + }); + + // disable timeout for manual tests + setup({explicit_timeout: true}); + const startBtn = document.getElementById('startBtn'); + const childFrame = document.getElementById('childFrame'); + + promise_test(t => { + const startWatcher = new EventWatcher(t, startBtn, 'click'); + const messageWatcher = new EventWatcher(t, window, 'message'); + const request = new PresentationRequest(presentationUrls); + let connection, eventWatcher; + + t.add_cleanup(() => { + if (connection) { + connection.onconnect = () => { connection.terminate(); } + if (connection.state === 'closed') + request.reconnect(connection.id); + else + connection.terminate(); + } + }); + + const waitForMessage = () => { + return messageWatcher.wait_for('message').then(evt => { + return evt.data.type === 'presentation-api' ? evt : waitForMessage(); + }); + }; + + // handle a test result received from a nested browsing context + const parseValue = value => { + let r; + + // String + if (r = value.match(/^(\(string\)\s+)?"(.*)"$/)) + return r[2]; + // Object + else if (r = value.match(/^(\(object\)\s+)?object\s+"\[object\s+(.*)\]"$/)) + return window[r[2]].prototype; + // undefined + else if (value === "undefined") + return undefined; + // Number, boolean, null + else { + if (r = value.match(/^(\(\S+\)\s+)?(\S+)$/)) { + try { + return JSON.parse(r[2]); + } catch(e) { + return value; + } + } + else + return value; + } + }; + + const parseResult = t.step_func(evt => { + const result = evt.data; + if (result.test.status === 0) + return evt; + + receiverStack = result.test.stack; + const message = result.test.message; + let r = message.match(/^(assert_.*):\s+(.*)$/); + if (r) { + const assertion = r[1]; + const body = r[2]; + let args; + if (assertion === 'assert_equals') { + if (r = body.match(/^((.*)\s+)?expected\s+((\(\S*\)\s+)?(\S+|(\S+\s+)?\".*\"))\s+but\s+got\s+((\(\S*\)\s+)?(\S+|(\S+\s+)?\".*\"))$/)) + args = [parseValue(r[7]), parseValue(r[3]), r[2]]; + } + else if (assertion === 'assert_true') { + if (r = body.match(/^((.*)\s+)?expected\s+(true|false)\s+got\s+(\S+|(\S+\s+)?\".*\")$/)) { + args = [parseValue(r[4]), r[2]]; + } + } + else if (assertion === 'assert_unreached') { + if (r = body.match(/^((.*)\s+)?Reached\s+unreachable\s+code$/)) + args = [r[2]]; + } + if (args) { + window[assertion](args[0], args[1], args[2]); + return; + } + } + // default + assert_unreached('Test result received from a receiving user agent: ' + message + ': '); + }); + + return Promise.all([ + startWatcher.wait_for('click'), + messageWatcher.wait_for('message') + ]).then(() => { + startBtn.disabled = true; + let presentationId = null; + return request.start(); + }).then(c => { + connection = c; + presentationId = connection.id; + + // No more user input needed, re-enable test timeout + t.step_timeout(() => { + t.force_timeout(); + t.done(); + }, 5000); + + eventWatcher = new EventWatcher(t, connection, ['connect', 'close', 'terminate']); + + return Promise.all([ + // Wait for "connect" event + eventWatcher.wait_for('connect'), + // Try to reconnect when the connection state is "connecting" + request.reconnect(presentationId).then(c => { + assert_equals(c, connection, 'The promise is resolved with the existing presentation connection.'); + assert_equals(c.state, "connecting", "The connection state remains 'connecting'."); + assert_equals(c.id, presentationId, "The presentation ID is not changed."); + }) + ]); + }).then(() => { + // Try to reconnect when the connection state is "connected" + return request.reconnect(presentationId); + }).then(c => { + assert_equals(c, connection, 'The promise is resolved with the existing presentation connection.'); + assert_equals(c.state, "connected", "The connection state remains 'connected'."); + assert_equals(c.id, presentationId, "The presentation ID is not changed."); + + // Close connection and wait for "close" event + connection.close(); + return eventWatcher.wait_for('close'); + }).then(() => { + // Connection now closed, let's reconnect to it + return request.reconnect(presentationId); + }).then(c => { + // Check the presentation connection in "connecting" state + assert_equals(c, connection, 'The promise is resolved with the existing presentation connection.'); + connection = c; + assert_equals(connection.state, "connecting", "The connection state is set to 'connecting'."); + assert_equals(connection.id, presentationId, "Ids of old and new connections must be equal."); + + return eventWatcher.wait_for('connect'); + }).then(evt => { + // Check the established presentation connection and its associated "connect" event + assert_true(evt.isTrusted && !evt.bubbles && !evt.cancelable && evt instanceof Event, 'A simple event is fired.'); + assert_equals(evt.type, 'connect', 'The event name is "connect".'); + assert_equals(evt.target, connection, 'event.target is the presentation connection.'); + assert_equals(connection.state, 'connected', 'The presentation connection state is set to "connected".'); + + // Request an iframe to reconnect the presentation with the current presentation ID + childFrame.contentWindow.postMessage('reconnect?id=' + presentationId, '*'); + return waitForMessage().then(parseResult); + }).then(() => { + // Wait until state of each presentation connection is set to "terminated" + connection.terminate(); + return Promise.all([ eventWatcher.wait_for('terminate'), waitForMessage().then(parseResult) ]); + }).then(() => { + // Try to reconnect to the presentation, while all presentation connection have already been terminated + return promise_rejects_dom(t, 'NotFoundError', request.reconnect(presentationId), + 'Reconnecting to a terminated presentation rejects a promise with a "NotFoundError" exception.'); + }); + }); +</script> diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/startMultiplePresentations_success-manual.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/startMultiplePresentations_success-manual.https.html new file mode 100644 index 0000000000..0268bd87e8 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/startMultiplePresentations_success-manual.https.html @@ -0,0 +1,99 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Starting presentations on two distinct displays</title> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs"> +<link rel="help" href="http://w3c.github.io/presentation-api/#dfn-controlling-user-agent"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="common.js"></script> +<style> +#second-step { + display: none; +} +</style> + +<p id="first-step">Click the button below and select the available presentation display, to start the manual test.</p> +<p id="second-step">Click the button below and select the other available presentation display, to continue the manual test.</p> +<p>This test asks you to click the button twice, unless the test fails.<br> +<em>This test requires two or more available displays.<em></p> +<button id="presentBtn">Start Presentation Test</button> + + +<script> + promise_test(async t => { + const presentBtn = document.getElementById("presentBtn"); + + const request = new PresentationRequest(presentationUrls); + const clickWatcher = new EventWatcher(t, presentBtn, 'click'); + let connection1, connection2, eventWatcher1, eventWatcher2, timer; + + t.add_cleanup(() => { + [connection1, connection2].forEach(connection => { + if (connection) { + connection.onconnect = () => { connection.terminate(); }; + if (connection.state == 'closed') + request.reconnect(connection.id); + else + connection.terminate(); + } + }); + }); + + const disableTimeout = () => { + setup({explicit_timeout: true}); + if (timer) { + clearTimeout(timer); + timer = null; + } + }; + + const enableTimeout = () => { + timer = t.step_timeout(() => { + t.force_timeout(); + t.done(); + }, 5000); + } + + disableTimeout(); + + await clickWatcher.wait_for('click'); + presentBtn.disabled = true; + connection1 = await request.start(); + + presentBtn.disabled = false; + document.getElementById('first-step').style.display = 'none'; + document.getElementById('second-step').style.display = 'block'; + presentBtn.innerText = 'Continue Presentation Test'; + eventWatcher1 = new EventWatcher(t, connection1, ['connect', 'close', 'terminate']); + + await Promise.all([ + eventWatcher1.wait_for('connect'), + (async () => { + await clickWatcher.wait_for('click'); + presentBtn.disabled = true; + + connection2 = await request.start(); + enableTimeout(); + eventWatcher2 = new EventWatcher(t, connection2, ['connect', 'terminate']); + await eventWatcher2.wait_for('connect'); + })() + ]); + + assert_not_equals(connection1.id, connection2.id, + 'Presentation connections on distinct presentations must have different presentation IDs.'); + + assert_equals(connection1.state, 'connected', 'The presentation connection is successfully reconnected.'); + connection1.terminate(); + assert_equals(connection2.state, 'connected', + 'Terminating one presentation connection does not affect the state of the other.'); + connection2.terminate(); + + await Promise.all([ + eventWatcher1.wait_for('terminate'), + eventWatcher2.wait_for('terminate') + ]); + + assert_equals(connection1.state, 'terminated', 'One presentation connection is successfully terminated.'); + assert_equals(connection2.state, 'terminated', 'Both presentation connections are successfully terminated.'); + }); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_displaynotallowed-manual.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_displaynotallowed-manual.https.html new file mode 100644 index 0000000000..877c17a35d --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_displaynotallowed-manual.https.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Calling "start" when the user denied permission to use the display returns a Promise rejected with a NotAllowedError exception.</title> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs/"> +<link rel="help" href="http://w3c.github.io/presentation-api/#dfn-controlling-user-agent"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="common.js"></script> + +<p>Before starting this test, confirm that there are one or more available presentation display on your local network.</p> +<p>Click the button below to start the manual test. If prompted to select a device, please dismiss the dialog box. The test passes if a "PASS" result appears. +</p> +<button id="presentBtn">Start Presentation Test</button> + +<script> + // disable the timeout function for the tests + setup({explicit_timeout: true}); + + // ---------- + // DOM Object + // ---------- + var presentBtn = document.getElementById("presentBtn"); + + // ------------------------------------------- + // Start New Presentation Test (error) - begin + // ------------------------------------------- + presentBtn.onclick = function () { + presentBtn.disabled = true; + promise_test(function (t) { + var request = new PresentationRequest(presentationUrls); + + // terminate the presentation connection when the presentation is started by accident + var connection; + t.add_cleanup(function() { + if(connection) + connection.terminate(); + }); + + return promise_rejects_dom(t, 'NotAllowedError', request.start().then(function(c) { connection = c; })); + }); + }; + // ----------------------------------------- + // Start New Presentation Test (error) - end + // ----------------------------------------- +</script> diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_displaynotfound-manual.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_displaynotfound-manual.https.html new file mode 100644 index 0000000000..edfea3a4fd --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_displaynotfound-manual.https.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Calling "start" when there is no available presentation display returns a Promise rejected with a NotFoundError exception.</title> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs/"> +<link rel="help" href="http://w3c.github.io/presentation-api/#dfn-controlling-user-agent"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="common.js"></script> + +<p>Before starting this test, confirm that there is no available presentation display on your local network.</p> +<p>Click the button below to start the manual test. If prompted to select a device, please dismiss the dialog box. The test passes if a "PASS" result appears. +</p> +<button id="presentBtn">Start Presentation Test</button> + +<script> + // disable the timeout function for the tests + setup({explicit_timeout: true}); + + // ---------- + // DOM Object + // ---------- + var presentBtn = document.getElementById("presentBtn"); + + // ------------------------------------------- + // Start New Presentation Test (error) - begin + // ------------------------------------------- + presentBtn.onclick = function () { + presentBtn.disabled = true; + promise_test(function (t) { + var request = new PresentationRequest(presentationUrls); + + // terminate the presentation connection when the presentation is started by accident + var connection; + t.add_cleanup(function() { + if(connection) + connection.terminate(); + }); + + return promise_rejects_dom(t, 'NotFoundError', request.start().then(function(c) { connection = c; })); + }); + }; + // ----------------------------------------- + // Start New Presentation Test (error) - end + // ----------------------------------------- +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_error.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_error.https.html new file mode 100644 index 0000000000..3171c6c104 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_error.https.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Presentation API, start new presentation tests for Controlling User Agent (error)</title> +<link rel="author" title="Marius Wessel" href="http://www.fokus.fraunhofer.de"> +<link rel="help" href="http://w3c.github.io/presentation-api/#dfn-controlling-user-agent"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + // ----------------------------------- + // Start New Presentation Test - begin + // ----------------------------------- + promise_test(function (t) { + var request = new PresentationRequest('presentation.html'); + return promise_rejects_dom(t, 'InvalidAccessError', request.start()); + }, "The presentation could not start, because a user gesture is required."); + // ---------------------------------- + // Launch New Presentation Test - end + // ---------------------------------- +</script> + diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_sandboxing_success-manual.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_sandboxing_success-manual.https.html new file mode 100644 index 0000000000..68e037a5c0 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_sandboxing_success-manual.https.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Sandboxing: starting a presentation from a nested context succeeds when allow-presentation is set</title> +<link rel="author" title="Francois Daoust" href="https://www.w3.org/People/#fd"> +<link rel="help" href="http://w3c.github.io/presentation-api/#dom-presentationrequest-start"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe id="childFrame" sandbox="allow-scripts allow-presentation" style="display:none" src="support/iframe.html"></iframe> +<p>Click the button below to start the manual test. If prompted to select a device, please dismiss the dialog box. The test passes if a "PASS" result appears.</p> +<button id="presentBtn" onclick="startPresentationTest()">Start Presentation Test</button> + +<script> + setup({explicit_timeout: true}); + + var presentBtn = document.getElementById('presentBtn'); + var childFrame = document.getElementById('childFrame'); + + function startPresentationTest() { + presentBtn.disabled = true; + async_test(function (t) { + childFrame.contentWindow.postMessage('start', '*'); + window.addEventListener('message', t.step_func(function (ev) { + assert_equals(ev.data, 'success', + 'Presentation could not be started from nested frame.'); + t.done(); + })); + }); + } +</script> + diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_success-manual.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_success-manual.https.html new file mode 100644 index 0000000000..c9378c0e68 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_success-manual.https.html @@ -0,0 +1,95 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Checking the chain of events when starting a new presentation</title> +<link rel="author" title="Marius Wessel" 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-controlling-user-agent"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="common.js"></script> + +<p>Click the button below and select the available presentation display, to start the manual test.</p> +<button id="presentBtn">Start Presentation Test</button> + + +<script> + // description of event order + var description = [ + "Phase #1: Promise is resolved", + "Phase #2: 'connectionavailable' event fired", + "Phase #3: 'connect' event fired" + ]; + var step = 0; + + // presentation connection + var connection; + + // disable the timeout function for the tests + setup({explicit_timeout: true}); + + // ---------- + // DOM Object + // ---------- + var presentBtn = document.getElementById("presentBtn"); + + // --------------------------------------------- + // Start New Presentation Test (success) - begin + // --------------------------------------------- + presentBtn.onclick = function () { + presentBtn.disabled = true; + promise_test(function (t) { + var phase = -1, actual = -1; + + // increment the count in the actual event order + var count = function(evt) { actual++; return evt; }; + // increment the count in the expected event order and compare it with the actual event order + var checkPhase = function(evt) { phase++; assert_equals(description[actual], description[phase], 'Event order is incorrect.'); return evt; }; + + var request = new PresentationRequest(presentationUrls); + var eventWatcher = new EventWatcher(t, request, 'connectionavailable'); + var waitConnectionavailable = eventWatcher.wait_for('connectionavailable').then(count).then(function(evt) { connection = connection || evt.connection; return evt; }); + var waitConnect; + + t.add_cleanup(function() { + if(connection) + connection.terminate(); + }); + + return request.start().then(count) + .then(checkPhase).then(function (c) { + // Phase #1: Promise is resolved + connection = c; + + // No more user input needed, re-enable timeout + t.step_timeout(function() { + t.force_timeout(); + t.done(); + }, 5000); + + // Check the initial state of the presentation connection + assert_equals(connection.state, 'connecting', 'Check the initial state of the presentation connection.'); + assert_true(!!connection.id, 'The connection ID is set.'); + assert_equals(typeof connection.id, 'string', 'The connection ID is a string.'); + assert_true(connection instanceof PresentationConnection, 'The connection is an instance of PresentationConnection.'); + + var eventWatcher = new EventWatcher(t, connection, 'connect'); + waitConnect = eventWatcher.wait_for('connect').then(count); + + return waitConnectionavailable; + }) + .then(checkPhase).then(function (evt) { + // Phase #2: "connectionavailable" event fired + assert_equals(connection, evt.connection, 'Both Promise from PresentationRequest() and a "connectionavailable" event handler receive the same presentation connection.'); + + return waitConnect; + }) + .then(checkPhase).then(function () { + // Phase #3: "connect" event fired + assert_equals(connection.state, 'connected', 'The state of the presentation connection is "connected" when a "connect" event fires.'); + }); + }); + } + // ------------------------------------------- + // Start New Presentation Test (success) - end + // ------------------------------------------- +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_unsettledpromise-manual.https.html b/testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_unsettledpromise-manual.https.html new file mode 100644 index 0000000000..fb747eb0a4 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_unsettledpromise-manual.https.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Calling "start" when there is already an unsettled Promise returns a Promise rejected with an OperationError exception.</title> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs/"> +<link rel="help" href="http://w3c.github.io/presentation-api/#dfn-controlling-user-agent"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="common.js"></script> + +<p>Click the button below to start the manual test. If prompted to select a device, please dismiss the dialog box. The test passes if a "PASS" result appears.</p> +<button id="presentBtn">Start Presentation Test</button> + +<script> + // disable the timeout function for the tests + setup({explicit_timeout: true}); + + // ---------- + // DOM Object + // ---------- + var presentBtn = document.getElementById("presentBtn"); + + // ----------------------------------------------- + // Terminate a presentation if started by accident + // ----------------------------------------------- + function terminate(connection) { + connection.terminate(); + } + + // ------------------------------------------- + // Start New Presentation Test (error) - begin + // ------------------------------------------- + presentBtn.onclick = function () { + presentBtn.disabled = true; + promise_test(function (t) { + var request1 = new PresentationRequest(presentationUrls), + request2 = new PresentationRequest(presentationUrls); + request1.start().then(terminate, function(){}); + return promise_rejects_dom(t, 'OperationError', request2.start().then(terminate)); + }); + }; + // ----------------------------------------- + // Start New Presentation Test (error) - end + // ----------------------------------------- +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/support/iframe.html b/testing/web-platform/tests/presentation-api/controlling-ua/support/iframe.html new file mode 100644 index 0000000000..e2171deaa8 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/support/iframe.html @@ -0,0 +1,181 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Presentation API - controlling ua - sandboxing</title> +<link rel="author" title="Francois Daoust" href="https://www.w3.org/People/#fd"> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs/"> +<link rel="help" href="http://w3c.github.io/presentation-api/#dom-presentationrequest-start"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../common.js"></script> +<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. + parent.window.postMessage(JSON.parse(JSON.stringify({ + type: 'presentation-api', test: tests[0], status: status + })), '*'); + }); + + // disable timeout for manual tests + setup({explicit_timeout: true}); + + window.onmessage = function (ev) { + try { + // Presentation URLs are relative to the "controlling-ua" folder, + // update relative URLs for this folder + var urls = presentationUrls.map(function (url) { + if (/:\/\//.test(url)) { + return url; + } + else { + return '../' + url; + } + }); + var request = null; + if (ev.data === 'create') { + try { + request = new PresentationRequest(urls); + parent.window.postMessage('success', '*'); + } + catch (err) { + parent.window.postMessage(err.name, '*'); + } + } + else if (ev.data === 'start') { + request = new PresentationRequest(urls); + request.start() + .then(function () { + parent.window.postMessage('success', '*'); + }) + .catch(function (err) { + if ((err.name === 'NotFoundError') || + (err.name === 'NotAllowedError')) { + // These errors either mean that the user dismissed the dialog + // box or that the UA could not find any available or suitable + // screen. This is equivalent of succeeding for the purpose of + // iframe tests. + parent.window.postMessage('success', '*'); + } + else { + parent.window.postMessage(err.name, '*'); + } + }); + } + else if (ev.data === 'reconnect') { + request = new PresentationRequest(urls); + request.reconnect('someid') + .then(function () { + parent.window.postMessage('success', '*'); + }) + .catch(function (err) { + parent.window.postMessage(err.name, '*'); + }); + } + else if (ev.data.match(/^reconnect\?id=(.*)$/)) { + promise_test(function (t) { + var presentationId = RegExp.$1; + var phase = -1, actual = -1, connection, waitConnection; + var description = [ + "Phase #1: Promise is resolved", + "Phase #2: 'connectionavailable' event fired", + "Phase #3: 'connect' event fired" + ].map(d => { return '(Reconnecting in a nested browsing context) ' + d; }); + + var count = function(evt) { actual++; return evt; }; + var checkPhase = function(evt) { + phase++; + assert_equals(description[actual], description[phase], 'Event order is incorrect.'); + return evt; + }; + + request = new PresentationRequest(urls); + + var eventWatcher = new EventWatcher(t, request, 'connectionavailable'); + var waitConnectionavailable = eventWatcher.wait_for('connectionavailable').then(count).then(function (evt) { + connection = connection || evt.connection; return evt; + }); + + return request.reconnect(presentationId).then(count).then(checkPhase).then(function (c) { + // Reconnecting Phase #1: Promise is resolved + connection = c; + assert_equals(connection.state, 'connecting', 'Check the initial state of the presentation connection.'); + assert_equals(connection.id, presentationId, "The same presentation ID is set to the newly created presentation connection."); + assert_true(connection instanceof PresentationConnection, 'The connection is an instance of PresentationConnection.'); + + var eventWatcher = new EventWatcher(t, connection, 'connect'); + waitConnect = eventWatcher.wait_for('connect').then(count); + + // Reconnecting Phase #2: "connectionavailable" event is fired + return waitConnectionavailable; + }).then(checkPhase).then(function (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, request, 'event.target is the presentation request.'); + assert_true(evt.connection instanceof PresentationConnection, 'event.connection is a presentation connection.'); + assert_equals(evt.connection, connection, 'event.connection is set to the presentation which the promise is resolved with.'); + + // Reconnecting Phase #3: "connect" event is fired + return waitConnect; + }).then(checkPhase).then(function (evt) { + assert_true(evt.isTrusted && !evt.bubbles && !evt.cancelable && evt instanceof Event, 'A simple event is fired.'); + assert_equals(evt.type, 'connect', 'The event name is "connect".'); + assert_equals(evt.target, connection, 'event.target is the presentation connection.'); + assert_equals(connection.state, 'connected', 'The presentation connection state is set to "connected".'); + parent.window.postMessage({ type: 'presentation-api', test: { status: 0 } }, '*'); + var terminateWatcher = new EventWatcher(t, connection, 'terminate'); + + // "terminate" event is fired + return terminateWatcher.wait_for('terminate'); + }).then(function (evt) { + assert_true(evt.isTrusted && !evt.bubbles && !evt.cancelable && evt instanceof Event, 'A simple event is fired.'); + assert_equals(evt.type, 'terminate', 'The event name is "terminate".'); + assert_equals(evt.target, connection, 'event.target is the presentation connection.'); + assert_equals(connection.state, 'terminated', 'The presentation connection state is set to "terminated".'); + }); + }); + } + else if (ev.data.match(/^terminate\?id=(.*)$/)) { + var presentationId = RegExp.$1; + request = new PresentationRequest(urls); + request.reconnect(presentationId) + .then(function (c) { + parent.window.postMessage('reconnected', '*'); + c.onterminate = function(evt) { + parent.window.postMessage({ + isSimpleEvent: evt.isTrusted && !evt.bubbles && !evt.cancelable && evt instanceof Event, + type: evt.type, + checkConnection: evt.target === c, + state: c.state + }, '*'); + }; + }) + .catch(function (err) { + parent.window.postMessage(err.name, '*'); + }); + } + else if (ev.data === 'getAvailability') { + request = new PresentationRequest(urls); + request.getAvailability() + .then(function () { + parent.window.postMessage('success', '*'); + }) + .catch(function (err) { + if (err.name === 'NotSupportedError') { + parent.window.postMessage('success', '*'); + } + else { + parent.window.postMessage(err.name, '*'); + } + }); + } + } + catch (err) { + parent.window.postMessage('Could not create PresentationRequest', '*'); + } + } + parent.window.postMessage('ready', '*'); +</script> + diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/support/presentation.html b/testing/web-platform/tests/presentation-api/controlling-ua/support/presentation.html new file mode 100644 index 0000000000..8ad838062f --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/support/presentation.html @@ -0,0 +1,98 @@ +<!DOCTYPE html> + +<meta charset="utf-8"> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="author" title="He Yue" href="mailto:yue.he@intel.com"> +<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs/"> +<link rel="help" href="http://w3c.github.io/presentation-api/#interface-presentationconnectionlist"> +<script src="../common.js"></script> +<script src="stash.js"></script> +<script> + 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; + } + + // convert ArrayBuffer or Uint8Array into string + const toText = buf => { + const arr = toUint8Array(buf); + return !buf ? null : arr.reduce((result, item) => { + return result + String.fromCharCode(item); + }, ''); + } + + // 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); + + let connection, count = 0; + + const addConnection = c => { + let result = [], testCase; + connection = c; + count++; + + connection.onmessage = event => { + // PresentationConnection_send-manual.https.html + if (testCase === 'send') { + if (typeof event.data === 'string') { + result.push({ type: 'text', data: event.data }); + } + // default value of connection.binaryType is "arraybuffer" + else if(event.data instanceof ArrayBuffer) { + result.push({ type: 'binary', data: toText(event.data) }); + if (compare(event.data, message5)) { + stash.send(JSON.stringify(result)); + } + } + else { + result.push({ type: 'error' }); + } + } + }; + + stash.receive().then(data => { + testCase = data; + + // PresentationConnection_send-manual.https.html + if (testCase === 'send') { + stash.send('ok'); + } + // PresentationConnection_onmessage-manual.https.html + else if (testCase === 'onmessage') { + connection.send(message1); // string + connection.send(message2); // string + connection.send(new Blob([message3])); // Blob + connection.send(message4.buffer); // ArrayBuffer + connection.send(message5); // ArrayBufferView + stash.receive().then(data => { + connection.send(message5); + }); + } + // PresentationConnection_onclose-manual.https.html + else if (testCase === 'close') { + connection.close(); + } + }); + }; + + navigator.presentation.receiver.connectionList + .then(list => { + list.onconnectionavailable = evt => { + addConnection(evt.connection); + }; + list.connections.map(connection => { + addConnection(connection); + }); + }); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/presentation-api/controlling-ua/support/stash.js b/testing/web-platform/tests/presentation-api/controlling-ua/support/stash.js new file mode 100644 index 0000000000..616907d4f2 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-ua/support/stash.js @@ -0,0 +1,83 @@ +var Stash = function(inbound, outbound) { + this.stashPath = '/presentation-api/controlling-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(); + }) +}; + +// 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/controlling-ua/support/stash.py b/testing/web-platform/tests/presentation-api/controlling-ua/support/stash.py new file mode 100644 index 0000000000..83653d365a --- /dev/null +++ b/testing/web-platform/tests/presentation-api/controlling-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 diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnectionList_onconnectionavailable-manual.https.html b/testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnectionList_onconnectionavailable-manual.https.html new file mode 100644 index 0000000000..9b75a85e9f --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnectionList_onconnectionavailable-manual.https.html @@ -0,0 +1,77 @@ +<!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="common.js"></script> +<script src="support/stash.js"></script> +<style> +iframe { display: none; } +</style> + +<p>Click the button below and select the available presentation display, to start the manual test.</p> +<button id="presentBtn" disabled>Start Presentation Test</button> +<iframe id="childFrame" sandbox="allow-scripts allow-presentation" src="support/iframe.html"></iframe> + +<script> + let connection; + const presentBtn = document.getElementById('presentBtn'); + const child = document.getElementById('childFrame'); + + child.onload = () => { presentBtn.disabled = false; }; + + presentBtn.onclick = () => { + presentBtn.disabled = true; + const stash = new Stash(stashIds.toController, stashIds.toReceiver); + const request = new PresentationRequest('support/PresentationConnectionList_onconnectionavailable_receiving-ua.html'); + + return request.start().then(c => { + connection = c; + return stash.init(); + }).then(() => { + return stash.receive(); + }).then(data => { + const result = JSON.parse(data); + if (result.type === 'ok') { + connection.close(); + return stash.receive(); + } + else + return data; + }).then(data => { + const result = JSON.parse(data); + if (result.type === 'ok') { + request.reconnect(connection.id); + return stash.receive(); + } + else + return data; + }).then(data => { + const result = JSON.parse(data); + if (result.type === 'ok') { + child.contentWindow.postMessage({ type: 'connect', id: connection.id, url: connection.url }, '*'); + return stash.receive(); + } + else + return data; + }).then(result => { + const json = JSON.parse(result); + + // notify receiver's results of a parent window (e.g. test runner) + if (window.opener && 'completion_callback' in window.opener) { + window.opener.completion_callback(json.tests, json.status); + } + // display receiver's results as HTML + const log = document.createElement('div'); + log.id = 'log'; + log.innerHTML = json.log; + document.body.appendChild(log); + + connection.onconnect = () => { connection.terminate(); }; + if (connection.state === 'closed') + request.reconnect(connection.id); + else + connection.terminate(); + }); + }; +</script> diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnection_onclose-manual.https.html b/testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnection_onclose-manual.https.html new file mode 100644 index 0000000000..50585f7813 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnection_onclose-manual.https.html @@ -0,0 +1,84 @@ +<!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="common.js"></script> +<script src="support/stash.js"></script> +<style> +iframe { display: none; } +</style> + +<p>Click the button below and select the available presentation display, to start the manual test.</p> +<button id="presentBtn" disabled>Start Presentation Test</button> +<iframe id="childFrame" sandbox="allow-scripts allow-presentation" src="support/iframe.html"></iframe> + +<script> + let connection; + const presentBtn = document.getElementById('presentBtn'); + const child = document.getElementById('childFrame'); + + child.onload = () => { presentBtn.disabled = false; }; + + presentBtn.onclick = () => { + presentBtn.disabled = true; + const stash = new Stash(stashIds.toController, stashIds.toReceiver); + const request = new PresentationRequest('support/PresentationConnection_onclose_receiving-ua.html'); + + return request.start().then(c => { + connection = c; + return stash.init(); + }).then(() => { + return stash.receive(); + }).then(data => { + const result = JSON.parse(data); + if (result.type === 'ok') { + return request.reconnect(connection.id).then(() => data); + } + else + return data; + }).then(data => { + const result = JSON.parse(data); + if (result.type === 'ok') { + connection.onclose = null; + connection.close(); + return stash.receive(); + } + else + return data; + }).then(data => { + const result = JSON.parse(data); + if (result.type === 'ok') { + child.contentWindow.postMessage({ type: 'connect', id: connection.id, url: connection.url }, '*'); + return stash.receive(); + } + else + return data; + }).then(data => { + const result = JSON.parse(data); + if (result.type === 'ok') { + child.parentNode.removeChild(child); + return stash.receive(); + } + else + return data; + }).then(result => { + const json = JSON.parse(result); + // notify receiver's results of a parent window (e.g. test runner) + if (window.opener && 'completion_callback' in window.opener) { + window.opener.completion_callback(json.tests, json.status); + } + // display receiver's results as HTML + const log = document.createElement('div'); + log.id = 'log'; + log.innerHTML = json.log; + document.body.appendChild(log); + + connection.onconnect = () => { connection.terminate(); }; + if (connection.state === 'closed') + request.reconnect(connection.id); + else + connection.terminate(); + }); + }; +</script> diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnection_onmessage-manual.https.html b/testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnection_onmessage-manual.https.html new file mode 100644 index 0000000000..53451a6cef --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnection_onmessage-manual.https.html @@ -0,0 +1,64 @@ +<!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="common.js"></script> +<script src="support/stash.js"></script> + +<p>Click the button below and select the available presentation display, to start the manual test.</p> +<button id="presentBtn">Start Presentation Test</button> + +<script> + 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" + + let connection; + const presentBtn = document.getElementById('presentBtn'); + presentBtn.onclick = () => { + presentBtn.disabled = true; + const stash = new Stash(stashIds.toController, stashIds.toReceiver); + const request = new PresentationRequest('support/PresentationConnection_onmessage_receiving-ua.html'); + + request.start().then(c => { + c.onconnect = () => { + connection = c; + 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(data => { + const result = JSON.parse(data); + if (result.type === 'blob') { + connection.send(message5); + return stash.receive(); + } + else + return data; + }).then(result => { + const json = JSON.parse(result); + + // notify receiver's results of a parent window (e.g. test runner) + if (window.opener && 'completion_callback' in window.opener) { + window.opener.completion_callback(json.tests, json.status); + } + // display receiver's results as HTML + const log = document.createElement('div'); + log.id = 'log'; + log.innerHTML = json.log; + document.body.appendChild(log); + + connection.onconnect = () => { connection.terminate(); }; + if (connection.state === 'closed') + request.reconnect(connection.id); + else + connection.terminate(); + }); + }; + }); + }; +</script> diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnection_send-manual.https.html b/testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnection_send-manual.https.html new file mode 100644 index 0000000000..188f7e3fc1 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnection_send-manual.https.html @@ -0,0 +1,97 @@ +<!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="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="common.js"></script> +<script src="support/stash.js"></script> + +<p>Click the button below and select the available presentation display, to start the manual test.</p> +<button id="presentBtn">Start Presentation Test</button> + +<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]; }); + }; + + let connection; + + const presentBtn = document.getElementById('presentBtn'); + presentBtn.onclick = () => { + presentBtn.disabled = true; + const stash = new Stash(stashIds.toController, stashIds.toReceiver); + const request = new PresentationRequest('support/PresentationConnection_send_receiving-ua.html'); + + let eventWatcher; + + promise_test(t => { + t.add_cleanup(() => { + connection.onconnect = () => { connection.terminate(); }; + if (connection.state === 'closed') + request.reconnect(connection.id); + else + connection.terminate(); + }); + + return request.start().then(c => { + // enable timeout again, cause no user action is needed from here. + t.step_timeout(() => { + t.force_timeout(); + t.done(); + }, 3000); + + connection = c; + eventWatcher = new EventWatcher(t, connection, 'connect'); + return eventWatcher.wait_for('connect'); + }).then(() => { + return stash.init(); + }).then(() => { + stash.send({ type: 'ok' }); + eventWatcher = new EventWatcher(t, connection, 'message'); + return eventWatcher.wait_for('message'); + }).then(evt => { + assert_true(typeof evt.data === 'string' && evt.data === message1, 'send a string correctly'); + return eventWatcher.wait_for('message'); + }).then(evt => { + assert_true(typeof evt.data === 'string' && evt.data === message2, 'send a string correctly'); + return eventWatcher.wait_for('message'); + }).then(evt => { + assert_true(evt.data instanceof ArrayBuffer && compare(evt.data, message3), 'send a Blob correctly'); + return eventWatcher.wait_for('message'); + }).then(evt => { + assert_true(evt.data instanceof ArrayBuffer && compare(evt.data, message4), 'send an ArrayBuffer correctly'); + return eventWatcher.wait_for('message'); + }).then(evt => { + assert_true(evt.data instanceof ArrayBuffer && compare(evt.data, message5), 'send an ArrayBufferView correctly'); + return stash.send({ type: 'ok' }); + }).then(() => { + return stash.receive(); + }).then(data => { + const result = JSON.parse(data); + if (!result.type || result.type !== 'error') + assert_unreached('an InvalidStateError is thrown if the state is "closed"'); + else + assert_throws_dom('InvalidStateError', () => { + throw new DOMException(result.message, result.name); + }, 'an InvalidStateError is thrown if the state is "closed"'); + }); + }); + }; +</script> diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnection_terminate-manual.https.html b/testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnection_terminate-manual.https.html new file mode 100644 index 0000000000..6484e97c5a --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnection_terminate-manual.https.html @@ -0,0 +1,71 @@ +<!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="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="common.js"></script> +<script src="support/stash.js"></script> +<style> +iframe { display: none; } +</style> + +<p>Click the button below and select the available presentation display, to start the manual test.</p> +<button id="presentBtn" disabled>Start Presentation Test</button> +<iframe id="childFrame" sandbox="allow-scripts allow-presentation" src="support/iframe.html"></iframe> + +<script> + setup({explicit_timeout: true}); + + let connection; + + const presentBtn = document.getElementById('presentBtn'); + const child = document.getElementById('childFrame'); + + child.onload = () => { presentBtn.disabled = false; }; + + presentBtn.onclick = () => { + presentBtn.disabled = true; + const stash = new Stash(stashIds.toController, stashIds.toReceiver); + const request = new PresentationRequest('support/PresentationConnection_terminate_receiving-ua.html'); + + promise_test(t => { + t.add_cleanup(() => { + connection.onconnect = () => { connection.terminate(); }; + if (connection.state === 'closed') + request.reconnect(connection.id); + else + connection.terminate(); + stash.stop(); + }); + + return request.start().then(c => { + // enable timeout again, cause no user action is needed from here. + t.step_timeout(() => { + t.force_timeout(); + t.done(); + }, 5000); + + connection = c; + const eventWatcher = new EventWatcher(t, connection, 'connect'); + return eventWatcher.wait_for('connect'); + }).then(() => { + return stash.init(); + }).then(() => { + child.contentWindow.postMessage({ type: 'connect', id: connection.id, url: connection.url }, '*'); + const eventWatcher1 = new EventWatcher(t, connection, 'terminate'); + const eventWatcher2 = new EventWatcher(t, window, 'message'); + return Promise.all([ eventWatcher1.wait_for('terminate'), eventWatcher2.wait_for('message') ]); + }).then(() => { + return Promise.race([ + stash.receive().then(data => { + if (data.type === 'error') + assert_unreached('The presentation is not terminated successfully.'); + }), + new Promise(resolve => { t.step_timeout(resolve, 2000); }) + ]); + }); + }); + }; +</script> diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/PresentationReceiver_create-manual.https.html b/testing/web-platform/tests/presentation-api/receiving-ua/PresentationReceiver_create-manual.https.html new file mode 100644 index 0000000000..96fc6f345d --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/PresentationReceiver_create-manual.https.html @@ -0,0 +1,272 @@ +<!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="support/stash.js"></script> + +<p id="notice"> + Click the button below and select the available presentation display, to start the manual test. The test passes if a "PASS" result appears.<br> + This test asks you to click the button twice, unless the test fails.<br> + <button id="presentBtn">Start Presentation Test</button> +</p> + +<script> + setup({explicit_timeout: true}); + + let receiverStack; + add_completion_callback(() => { + // overwrite a stack written in the test result + if (receiverStack) { + document.querySelector('#log pre').textContent = receiverStack; + } + }); + + let connection; + const presentBtn = document.getElementById('presentBtn'); + + const dbName = { + controller: 'db-presentation-api-controlling-ua', + receiver: 'db-presentation-api-receiving-ua' + }; + + const main = () => { + promise_test(t => { + presentBtn.disabled = true; + const stash = new Stash(stashIds.toController, stashIds.toReceiver); + const request = new PresentationRequest('support/PresentationReceiver_create_receiving-ua.html'); + + t.add_cleanup(() => { + const notice = document.getElementById('notice'); + notice.parentNode.removeChild(notice); + stash.stop(); + + // history.back(); + document.cookie = 'PresentationApiTest=true; Expires=' + new Date().toUTCString(); + sessionStorage.removeItem('presentation_api_test'); + localStorage.removeItem('presentation_api_test'); + + Object.values(dbName).forEach(name => { + indexedDB.deleteDatabase(name); + }); + + if (connection) { + connection.onconnect = () => { connection.terminate(); }; + if (connection.state === 'closed') + request.reconnect(connection.id); + else + connection.terminate(); + } + + 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))); + }); + } + }); + + history.pushState(null, 'test', 'PresentationReceiver_create-manual.https.html'); + document.cookie = 'PresentationApiTest=Controlling-UA'; + + const storageName = 'presentation_api_test'; + const storageValue = 'receiving-ua'; + sessionStorage.setItem(storageName, storageValue); + localStorage.setItem(storageName, storageValue); + + const openIndexedDB = () => { + if ('indexedDB' in window) { + const req = indexedDB.open(dbName.controller, 1); + const eventWatcher = new EventWatcher(t, req, 'upgradeneeded'); + return eventWatcher.wait_for('upgradeneeded').then(evt => { + evt.target.result.close(); + }); + } + else + return Promise.resolve(); + }; + + const cacheName = 'controlling-ua'; + let clientUrls; + const getClientUrls = () => { + return new Promise(resolve => { + navigator.serviceWorker.getRegistration().then(reg => { + if (reg) { + const channel = new MessageChannel(); + channel.port1.onmessage = event => { + resolve(event.data); + }; + reg.active.postMessage('', [channel.port2]); + } + else + resolve([]); + }); + }); + }; + 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 => { + clientUrls = urls; + }); + }; + const openCaches = () => { + return 'caches' in window ? caches.open(cacheName).then(cache => cache.add('cache.txt')) : Promise.resolve(); + }; + + const checkUpdates = () => { + // Cookie + assert_equals(document.cookie, 'PresentationApiTest=Controlling-UA', 'A cookie store is not shared with a receiving user agent.'); + + // Web Storage + assert_equals(sessionStorage.length, 1, 'Session storage is not shared with a receiving user agent.'); + assert_equals(sessionStorage.getItem(storageName), storageValue, 'Session storage is not shared with a receiving user agent.'); + assert_equals(localStorage.length, 1, 'Local storage is not shared with a receiving user agent.'); + assert_equals(localStorage.getItem(storageName), storageValue, 'Local storage is not shared with a receiving user agent.'); + }; + + // Indexed Database + const checkIndexedDB = t => { + if ('indexedDB' in window) { + 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(); + }), + successWatcher.wait_for('success').then(evt => { + evt.target.result.close(); + // This would fail if the database created by the receiving UA is visible to the controlling UA + assert_unreached('Indexed Database is not shared with a receiving user agent.'); + }) + ]); + } + else + return Promise.resolve(); + }; + + // Service Workers + const checkServiceWorkers = () => { + return 'serviceWorker' in navigator ? navigator.serviceWorker.getRegistrations().then(registrations => { + const message = 'List of registered service worker registrations is not shared with a receiving user agent.'; + assert_equals(registrations.length, 1, message); + assert_equals(registrations[0].active.scriptURL, new Request('serviceworker.js').url, message); + }) : Promise.resolve(); + }; + const checkCaches = () => { + const message = 'Cache storage is not shared with a receiving user agent.'; + return 'caches' in window ? caches.keys().then(keys => { + assert_equals(keys.length, 1, message); + assert_equals(keys[0], cacheName, 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(); + }; + + let timeout; + const enableTimeout = () => { + timeout = t.step_timeout(() => { + t.force_timeout(); + t.done(); + }, 5000); + }; + const disableTimeout = () => { + clearTimeout(timeout); + }; + const cancelWait = () => { + connection.removeEventListener('close', onTerminate); + connection.removeEventListener('terminate', onTerminate); + }; + const onTerminate = (reject, event) => { + cancelWait(); + reject('A receiving user agent unexpectedly ' + event.type + 'd a presentation. '); + }; + const waitForTerminate = () => { + return new Promise((resolve, reject) => { + connection.addEventListener('close', onTerminate.bind(this, reject)); + connection.addEventListener('terminate', onTerminate.bind(this, reject)); + }); + }; + + // Start a presentation for receiving user agent tests + return request.start().then(c => { + connection = c; + enableTimeout(); + + // This Promise.race will be rejected if a receiving side terminates/closes the connection when window.close() is invoked + return Promise.race([ + openIndexedDB() + .then(registerServiceWorker) + .then(openCaches) + .then(() => { return stash.init(); }) + .then(() => { return stash.receive(); }), + waitForTerminate()]); + }).then(result => { + // terminate and connect again if the result is PASS + cancelWait(); + const json = JSON.parse(result); + if (json.test.status !== 0) + return json; + + // Check accessibility to window clients before terminating a presentation + return getClientUrls().then(urls => { + assert_true(urls.length === clientUrls.length && urls.every((value, index) => { return clientUrls[index] === value}), + 'A window client in a receiving user agent is not accessible to a service worker on a controlling user agent.'); + const eventWatcher = new EventWatcher(t, connection, 'terminate'); + connection.terminate(); + return eventWatcher.wait_for('terminate'); + }).then(() => { + connection = null; + disableTimeout(); + presentBtn.removeEventListener('click', main); + presentBtn.textContent = 'Continue Presentation Test'; + presentBtn.disabled = false; + const eventWatcher = new EventWatcher(t, presentBtn, 'click'); + return eventWatcher.wait_for('click'); + }).then(() => { + presentBtn.disabled = true; + return request.start(); + }).then(c => { + connection = c; + enableTimeout(); + return stash.receive(); + }).then(result => { + return JSON.parse(result); + }); + }).then(json => { + if (json.test.status === 0) { + checkUpdates(); + return checkIndexedDB(t) + .then(checkServiceWorkers) + .then(checkCaches); + } + else { + receiverStack = json.test.stack; + parseResult(json.test.message); + } + }); + }); + }; + presentBtn.addEventListener('click', main); +</script> diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/cache.txt b/testing/web-platform/tests/presentation-api/receiving-ua/cache.txt new file mode 100644 index 0000000000..551e8ff210 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/cache.txt @@ -0,0 +1 @@ +Hello, Presentation API!
\ No newline at end of file diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/common.js b/testing/web-platform/tests/presentation-api/receiving-ua/common.js new file mode 100644 index 0000000000..cbee5b4702 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/common.js @@ -0,0 +1,63 @@ +(window => { + // Both a controlling side and a receiving one must share the same Stash ID to + // transmit data from one to the other. On the other hand, due to polling mechanism + // which cleans up a stash, stashes in both controller-to-receiver direction + // and one for receiver-to-controller are necessary. + window.stashIds = { + toController: '0c382524-5738-4df0-837d-4f53ea8addc2', + toReceiver: 'a9618cd1-ca2b-4155-b7f6-630dce953c44' + } + + // handle a test result received from a receiving page + const parseValue = value => { + let r; + + // String + if (r = value.match(/^(\(string\)\s+)?"(.*)"$/)) + return r[2]; + // Object + else if (r = value.match(/^(\(object\)\s+)?object\s+"\[object\s+(.*)\]"$/)) + return window[r[2]].prototype; + // Number, boolean, null, undefined + else { + if (r = value.match(/^(\(\S+\)\s+)?(\S+)$/)) { + try { + return JSON.parse(r[2]); + } catch(e) { + return value; + } + } + else + return value; + } + }; + + window.parseResult = message => { + let r = message.match(/^(assert_.*):\s+(.*)$/); + if (r) { + const assertion = r[1]; + const body = r[2]; + let args; + switch (assertion) { + case 'assert_equals': + if (r = body.match(/^((.*)\s+)?expected\s+((\(\S*\)\s+)?(\S+|(\S+\s+)?\".*\"))\s+but\s+got\s+((\(\S*\)\s+)?(\S+|(\S+\s+)?\".*\"))$/)) + args = [parseValue(r[7]), parseValue(r[3]), r[2]]; + break; + case 'assert_true': + if (r = body.match(/^((.*)\s+)?expected\s+(true|false)\s+got\s+(\S+|(\S+\s+)?\".*\")$/)) + args = [parseValue(r[4]), r[2]]; + break; + case 'assert_unreached': + if (r = body.match(/^((.*)\s+)?Reached\s+unreachable\s+code$/)) + args = [r[2]]; + break; + } + if (args) { + window[assertion](args[0], args[1], args[2]); + return; + } + } + // default + assert_unreached('Test result received from a receiving user agent: ' + message + ': '); + }; +})(window);
\ No newline at end of file diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/idlharness-manual.https.html b/testing/web-platform/tests/presentation-api/receiving-ua/idlharness-manual.https.html new file mode 100644 index 0000000000..c7bd7344d5 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/idlharness-manual.https.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Presentation API IDL tests for Receiving User Agent</title> +<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="common.js"></script> +<script src="support/stash.js"></script> + +<p>Click the button below and select the available presentation display, to start the manual test.</p> +<button id="presentBtn">Start Presentation Test</button> + +<script> + let connection; + const presentBtn = document.getElementById('presentBtn'); + presentBtn.onclick = () => { + presentBtn.disabled = true; + const stash = new Stash(stashIds.toController, stashIds.toReceiver); + const request = new PresentationRequest('support/idlharness_receiving-ua.html'); + + return request.start().then(c => { + connection = c; + return stash.init(); + }).then(() => { + return stash.receive(); + }).then(result => { + const json = JSON.parse(result); + // notify receiver's results of a parent window (e.g. test runner) + if (window.opener && 'completion_callback' in window.opener) { + window.opener.completion_callback(json.tests, json.status); + } + // display receiver's results as HTML + const log = document.createElement('div'); + log.id = 'log'; + log.innerHTML = json.log; + document.body.appendChild(log); + + connection.onconnect = () => { connection.terminate(); }; + if (connection.state === 'closed') + request.reconnect(connection.id); + else + connection.terminate(); + }); + }; +</script> diff --git a/testing/web-platform/tests/presentation-api/receiving-ua/serviceworker.js b/testing/web-platform/tests/presentation-api/receiving-ua/serviceworker.js new file mode 100644 index 0000000000..ae50ad6498 --- /dev/null +++ b/testing/web-platform/tests/presentation-api/receiving-ua/serviceworker.js @@ -0,0 +1,15 @@ +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()); +}); + +self.addEventListener('message', event => { + event.waitUntil(clients.matchAll().then(windows => { + event.ports[0].postMessage(windows.map(w => { return w.url; }).sort()); + })); +}); 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 |