summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/presentation-api
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /testing/web-platform/tests/presentation-api
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/presentation-api')
-rw-r--r--testing/web-platform/tests/presentation-api/META.yml4
-rw-r--r--testing/web-platform/tests/presentation-api/README.md28
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/PresentationAvailability_onchange-manual.https.html89
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnectionCloseEvent.https.html24
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnection_onclose-manual.https.html145
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnection_onconnect-manual.https.html72
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnection_onmessage-manual.https.html144
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnection_onterminate-manual.https.html157
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/PresentationConnection_send-manual.https.html125
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_error.https.html36
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_mixedcontent.https.html18
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_mixedcontent_multiple.https.html21
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_onconnectionavailable-manual.https.html85
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_sandboxing_error.https.html26
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_sandboxing_success.https.html26
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/PresentationRequest_success.https.html31
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/common.js25
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/defaultRequest.https.html24
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/defaultRequest_success-manual.https.html63
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/getAvailability.https.html54
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/getAvailability_sandboxing_success.https.html25
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/idlharness.https.html51
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/reconnectToMultiplePresentations_success-manual.https.html138
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/reconnectToPresentation_notfound_error-manual.https.html61
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/reconnectToPresentation_sandboxing_success.https.html25
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/reconnectToPresentation_success-manual.https.html185
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/startMultiplePresentations_success-manual.https.html99
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_displaynotallowed-manual.https.html45
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_displaynotfound-manual.https.html45
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_error.https.html20
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_sandboxing_success-manual.https.html31
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_success-manual.https.html95
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_unsettledpromise-manual.https.html44
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/support/iframe.html181
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/support/presentation.html98
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/support/stash.js83
-rw-r--r--testing/web-platform/tests/presentation-api/controlling-ua/support/stash.py10
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnectionList_onconnectionavailable-manual.https.html77
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnection_onclose-manual.https.html84
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnection_onmessage-manual.https.html64
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnection_send-manual.https.html97
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/PresentationConnection_terminate-manual.https.html71
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/PresentationReceiver_create-manual.https.html272
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/cache.txt1
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/common.js63
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/idlharness-manual.https.html44
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/serviceworker.js15
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnectionList_onconnectionavailable_receiving-ua.html92
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnection_onclose_receiving-ua.html89
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnection_onmessage_receiving-ua.html108
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnection_send_receiving-ua.html45
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationConnection_terminate_receiving-ua.html23
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationReceiver_create_receiving-ua.html328
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationReceiver_create_receiving-ua_child.html171
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/support/PresentationReceiver_unreached_receiving-ua.html21
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/support/idlharness_receiving-ua.html41
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/support/iframe.html22
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/support/serviceworker.js9
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/support/stash.js98
-rw-r--r--testing/web-platform/tests/presentation-api/receiving-ua/support/stash.py10
60 files changed, 4278 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..beba890621
--- /dev/null
+++ b/testing/web-platform/tests/presentation-api/META.yml
@@ -0,0 +1,4 @@
+spec: https://w3c.github.io/presentation-api/
+suggested_reviewers:
+ - tidoust
+ - zqzhang
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