summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/serial
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/serial')
-rw-r--r--testing/web-platform/tests/serial/META.yml1
-rw-r--r--testing/web-platform/tests/serial/README.md10
-rw-r--r--testing/web-platform/tests/serial/getPorts/reject_opaque_origin.https.html14
-rw-r--r--testing/web-platform/tests/serial/getPorts/reject_opaque_origin.https.html.headers1
-rw-r--r--testing/web-platform/tests/serial/getPorts/sandboxed_iframe.https.window.js20
-rw-r--r--testing/web-platform/tests/serial/idlharness.https.any.js22
-rw-r--r--testing/web-platform/tests/serial/requestPort/reject_opaque_origin.https.html14
-rw-r--r--testing/web-platform/tests/serial/requestPort/reject_opaque_origin.https.html.headers1
-rw-r--r--testing/web-platform/tests/serial/requestPort/sandboxed_iframe.https.window.js24
-rw-r--r--testing/web-platform/tests/serial/resources/common.js33
-rw-r--r--testing/web-platform/tests/serial/resources/manual.js38
-rw-r--r--testing/web-platform/tests/serial/resources/open-in-iframe.html33
-rw-r--r--testing/web-platform/tests/serial/resources/serial-allowed-by-permissions-policy-worker.js14
-rw-r--r--testing/web-platform/tests/serial/resources/serial-disabled-by-permissions-policy-worker.js17
-rw-r--r--testing/web-platform/tests/serial/serial-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html44
-rw-r--r--testing/web-platform/tests/serial/serial-allowed-by-permissions-policy-attribute.https.sub.html46
-rw-r--r--testing/web-platform/tests/serial/serial-allowed-by-permissions-policy.https.sub.html49
-rw-r--r--testing/web-platform/tests/serial/serial-allowed-by-permissions-policy.https.sub.html.headers1
-rw-r--r--testing/web-platform/tests/serial/serial-default-permissions-policy.https.sub.html27
-rw-r--r--testing/web-platform/tests/serial/serial-disabled-by-permissions-policy.https.sub.html50
-rw-r--r--testing/web-platform/tests/serial/serial-disabled-by-permissions-policy.https.sub.html.headers1
-rw-r--r--testing/web-platform/tests/serial/serialPort_disconnect-manual.https.html83
-rw-r--r--testing/web-platform/tests/serial/serialPort_loopback-manual.https.html97
-rw-r--r--testing/web-platform/tests/serial/serialPort_loopback_BreakError-manual.https.html55
-rw-r--r--testing/web-platform/tests/serial/serialPort_loopback_BufferOverrunError-manual.https.html65
-rw-r--r--testing/web-platform/tests/serial/serialPort_loopback_flowControl-manual.https.html59
-rw-r--r--testing/web-platform/tests/serial/serialPort_readable-manual.https.html107
27 files changed, 926 insertions, 0 deletions
diff --git a/testing/web-platform/tests/serial/META.yml b/testing/web-platform/tests/serial/META.yml
new file mode 100644
index 0000000000..64938c9298
--- /dev/null
+++ b/testing/web-platform/tests/serial/META.yml
@@ -0,0 +1 @@
+spec: https://wicg.github.io/serial/
diff --git a/testing/web-platform/tests/serial/README.md b/testing/web-platform/tests/serial/README.md
new file mode 100644
index 0000000000..019eebd06b
--- /dev/null
+++ b/testing/web-platform/tests/serial/README.md
@@ -0,0 +1,10 @@
+# Web Serial Testing
+
+Currently Web Serial only provide permission policy and maualy tests. The goal in future is to define test API specification similar to [WebUSB] and provide test-only interface to support more comprehensive tests.
+
+Tests with the "-manual" suffix do not use the test-only interface and expect a
+real hardware device to be connected. The specific characteristics of the device
+are described in each test.
+
+[WebUSB]: webusb
+[Web Serial API]: https://wicg.github.io/serial
diff --git a/testing/web-platform/tests/serial/getPorts/reject_opaque_origin.https.html b/testing/web-platform/tests/serial/getPorts/reject_opaque_origin.https.html
new file mode 100644
index 0000000000..b2f630a319
--- /dev/null
+++ b/testing/web-platform/tests/serial/getPorts/reject_opaque_origin.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ 'use strict';
+
+ promise_test(async (t) => {
+ await promise_rejects_dom(
+ t, 'SecurityError', navigator.serial.getPorts(),
+ 'getPorts() should throw a SecurityError DOMException when called ' +
+ 'from a context where the top-level document has an opaque origin.');
+ }, 'Calls to Serial APIs from an origin with opaque top origin get blocked.');
+</script>
diff --git a/testing/web-platform/tests/serial/getPorts/reject_opaque_origin.https.html.headers b/testing/web-platform/tests/serial/getPorts/reject_opaque_origin.https.html.headers
new file mode 100644
index 0000000000..1efcf8c226
--- /dev/null
+++ b/testing/web-platform/tests/serial/getPorts/reject_opaque_origin.https.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: sandbox allow-scripts
diff --git a/testing/web-platform/tests/serial/getPorts/sandboxed_iframe.https.window.js b/testing/web-platform/tests/serial/getPorts/sandboxed_iframe.https.window.js
new file mode 100644
index 0000000000..0a99f75aae
--- /dev/null
+++ b/testing/web-platform/tests/serial/getPorts/sandboxed_iframe.https.window.js
@@ -0,0 +1,20 @@
+'use strict';
+
+promise_test(async (t) => {
+ let iframe = document.createElement('iframe');
+ await new Promise(resolve => {
+ iframe.src = '../resources/open-in-iframe.html';
+ iframe.sandbox.add('allow-scripts');
+ iframe.allow = 'serial';
+ document.body.appendChild(iframe);
+ iframe.addEventListener('load', resolve);
+ });
+
+ await new Promise(resolve => {
+ window.addEventListener('message', t.step_func(messageEvent => {
+ assert_equals(messageEvent.data, 'Success');
+ resolve();
+ }));
+ iframe.contentWindow.postMessage({type: 'GetPorts'}, '*');
+ });
+}, 'GetPorts from a sandboxed iframe is valid.');
diff --git a/testing/web-platform/tests/serial/idlharness.https.any.js b/testing/web-platform/tests/serial/idlharness.https.any.js
new file mode 100644
index 0000000000..b240da933a
--- /dev/null
+++ b/testing/web-platform/tests/serial/idlharness.https.any.js
@@ -0,0 +1,22 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+
+'use strict';
+
+idl_test(
+ ['serial'],
+ ['html', 'dom'],
+ idl_array => {
+ idl_array.add_objects({
+ Serial: ['navigator.serial'],
+ // TODO: SerialPort
+ // TODO: SerialPortInfo
+ });
+
+ if (self.GLOBAL.isWorker()) {
+ idl_array.add_objects({ WorkerNavigator: ['navigator'] });
+ } else {
+ idl_array.add_objects({ Navigator: ['navigator'] });
+ }
+ }
+);
diff --git a/testing/web-platform/tests/serial/requestPort/reject_opaque_origin.https.html b/testing/web-platform/tests/serial/requestPort/reject_opaque_origin.https.html
new file mode 100644
index 0000000000..ade8ae7392
--- /dev/null
+++ b/testing/web-platform/tests/serial/requestPort/reject_opaque_origin.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ 'use strict';
+
+ promise_test(async (t) => {
+ await promise_rejects_dom(
+ t, 'SecurityError', navigator.serial.requestPort(),
+ 'requestPort() should throw a SecurityError DOMException when called ' +
+ 'from a context where the top-level document has an opaque origin.');
+ }, 'Calls to Serial APIs from an origin with opaque top origin get blocked.');
+</script>
diff --git a/testing/web-platform/tests/serial/requestPort/reject_opaque_origin.https.html.headers b/testing/web-platform/tests/serial/requestPort/reject_opaque_origin.https.html.headers
new file mode 100644
index 0000000000..1efcf8c226
--- /dev/null
+++ b/testing/web-platform/tests/serial/requestPort/reject_opaque_origin.https.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: sandbox allow-scripts
diff --git a/testing/web-platform/tests/serial/requestPort/sandboxed_iframe.https.window.js b/testing/web-platform/tests/serial/requestPort/sandboxed_iframe.https.window.js
new file mode 100644
index 0000000000..f6d7abad10
--- /dev/null
+++ b/testing/web-platform/tests/serial/requestPort/sandboxed_iframe.https.window.js
@@ -0,0 +1,24 @@
+'use strict';
+
+promise_test(async (t) => {
+ let iframe = document.createElement('iframe');
+ await new Promise(resolve => {
+ iframe.src = '../resources/open-in-iframe.html';
+ iframe.sandbox.add('allow-scripts');
+ iframe.allow = 'serial';
+ document.body.appendChild(iframe);
+ iframe.addEventListener('load', resolve);
+ });
+
+ await new Promise(resolve => {
+ window.addEventListener('message', t.step_func(messageEvent => {
+ // The failure message of no device chosen is expected. The point here is
+ // to validate not failing because of a sandboxed iframe.
+ assert_equals(
+ 'FAIL: NotFoundError: Failed to execute \'requestPort\' on \'Serial\': No port selected by the user.',
+ messageEvent.data);
+ resolve();
+ }));
+ iframe.contentWindow.postMessage({type: 'RequestPort'}, '*');
+ });
+}, 'RequestPort from a sandboxed iframe is valid.');
diff --git a/testing/web-platform/tests/serial/resources/common.js b/testing/web-platform/tests/serial/resources/common.js
new file mode 100644
index 0000000000..5177f83a86
--- /dev/null
+++ b/testing/web-platform/tests/serial/resources/common.js
@@ -0,0 +1,33 @@
+// Compare two Uint8Arrays.
+function compareArrays(actual, expected) {
+ assert_true(actual instanceof Uint8Array, 'actual is Uint8Array');
+ assert_true(expected instanceof Uint8Array, 'expected is Uint8Array');
+ assert_equals(actual.byteLength, expected.byteLength, 'lengths equal');
+ for (let i = 0; i < expected.byteLength; ++i)
+ assert_equals(actual[i], expected[i], `Mismatch at position ${i}.`);
+}
+
+// Reads from |reader| until at least |targetLength| is read or the stream is
+// closed. The data is returned as a combined Uint8Array.
+async function readWithLength(reader, targetLength) {
+ const chunks = [];
+ let actualLength = 0;
+
+ while (true) {
+ let {value, done} = await reader.read();
+ chunks.push(value);
+ actualLength += value.byteLength;
+
+ if (actualLength >= targetLength || done) {
+ // It would be better to allocate |buffer| up front with the number of
+ // of bytes expected but this is the best that can be done without a
+ // BYOB reader to control the amount of data read.
+ const buffer = new Uint8Array(actualLength);
+ chunks.reduce((offset, chunk) => {
+ buffer.set(chunk, offset);
+ return offset + chunk.byteLength;
+ }, 0);
+ return buffer;
+ }
+ }
+}
diff --git a/testing/web-platform/tests/serial/resources/manual.js b/testing/web-platform/tests/serial/resources/manual.js
new file mode 100644
index 0000000000..ac19136b35
--- /dev/null
+++ b/testing/web-platform/tests/serial/resources/manual.js
@@ -0,0 +1,38 @@
+let manualTestPort = null;
+
+navigator.serial.addEventListener('disconnect', (e) => {
+ if (e.target === manualTestPort) {
+ manualTestPort = null;
+ }
+})
+
+async function getPortForManualTest() {
+ if (manualTestPort) {
+ return manualTestPort;
+ }
+
+ const button = document.createElement('button');
+ button.textContent = 'Click to select a device';
+ button.style.display = 'block';
+ button.style.fontSize = '20px';
+ button.style.padding = '10px';
+
+ await new Promise((resolve) => {
+ button.onclick = () => {
+ document.body.removeChild(button);
+ resolve();
+ };
+ document.body.appendChild(button);
+ });
+
+ manualTestPort = await navigator.serial.requestPort({filters: []});
+ assert_true(manualTestPort instanceof SerialPort);
+
+ return manualTestPort;
+}
+
+function manual_serial_test(func, name, properties) {
+ promise_test(async (test) => {
+ await func(test, await getPortForManualTest());
+ }, name, properties);
+}
diff --git a/testing/web-platform/tests/serial/resources/open-in-iframe.html b/testing/web-platform/tests/serial/resources/open-in-iframe.html
new file mode 100644
index 0000000000..9bf8beb66a
--- /dev/null
+++ b/testing/web-platform/tests/serial/resources/open-in-iframe.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+ <button>Fake user gesture</button>
+</body>
+
+<script>
+ 'use strict';
+
+ test_driver.set_test_context(parent);
+
+ window.onmessage = messageEvent => {
+ switch (messageEvent.data.type) {
+ case 'GetPorts':
+ navigator.serial.getPorts()
+ .then(ports => parent.postMessage('Success', '*'))
+ .catch(err => parent.postMessage(`FAIL: ${err}`, '*'));
+ break;
+ case 'RequestPort':
+ test_driver.click(document.getElementsByTagName('button')[0])
+ .then(() => navigator.serial.requestPort({filters: []}))
+ .then(port => parent.postMessage('Success', '*'))
+ .catch(err => parent.postMessage(`FAIL: ${err}`, '*'));
+ break;
+ default:
+ parent.postMessage(
+ `FAIL: Bad message type: ${messageEvent.data}`, '*');
+ };
+ };
+</script>
diff --git a/testing/web-platform/tests/serial/resources/serial-allowed-by-permissions-policy-worker.js b/testing/web-platform/tests/serial/resources/serial-allowed-by-permissions-policy-worker.js
new file mode 100644
index 0000000000..cef0aacdfc
--- /dev/null
+++ b/testing/web-platform/tests/serial/resources/serial-allowed-by-permissions-policy-worker.js
@@ -0,0 +1,14 @@
+'use strict';
+
+importScripts('/resources/testharness.js');
+
+let workerType;
+
+if (typeof postMessage === 'function') {
+ workerType = 'dedicated';
+}
+
+promise_test(() => navigator.serial.getPorts(),
+ `Inherited header permissions policy allows ${workerType} workers.`);
+
+done();
diff --git a/testing/web-platform/tests/serial/resources/serial-disabled-by-permissions-policy-worker.js b/testing/web-platform/tests/serial/resources/serial-disabled-by-permissions-policy-worker.js
new file mode 100644
index 0000000000..afac442974
--- /dev/null
+++ b/testing/web-platform/tests/serial/resources/serial-disabled-by-permissions-policy-worker.js
@@ -0,0 +1,17 @@
+'use strict';
+
+importScripts('/resources/testharness.js');
+
+const header = 'Permissions-Policy header serial=()';
+let workerType;
+
+if (typeof postMessage === 'function') {
+ workerType = 'dedicated';
+}
+
+promise_test(() => navigator.serial.getPorts().then(
+ () => assert_unreached('expected promise to reject with SecurityError'),
+ error => assert_equals(error.name, 'SecurityError')),
+ `Inherited ${header} disallows ${workerType} workers.`);
+
+done();
diff --git a/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html b/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html
new file mode 100644
index 0000000000..ac278ff0cf
--- /dev/null
+++ b/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<body>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/permissions-policy/resources/permissions-policy.js></script>
+<script>
+'use strict';
+const relative_path = '/permissions-policy/resources/permissions-policy-serial.html';
+const base_src = '/permissions-policy/resources/redirect-on-load.html#';
+const relative_worker_frame_path =
+ '/permissions-policy/resources/permissions-policy-serial-worker.html';
+const sub = 'https://{{domains[www]}}:{{ports[https][0]}}';
+const same_origin_src = base_src + relative_path;
+const cross_origin_src = base_src + sub + relative_path;
+const same_origin_worker_frame_src = base_src + relative_worker_frame_path;
+const cross_origin_worker_frame_src = base_src + sub +
+ relative_worker_frame_path;
+const header = 'Permissions-Policy allow="serial"';
+
+async_test(t => {
+ test_feature_availability(
+ 'serial.getPorts()', t, same_origin_src,
+ expect_feature_available_default, 'serial');
+}, header + ' allows same-origin relocation.');
+
+async_test(t => {
+ test_feature_availability(
+ 'serial.getPorts()', t, same_origin_worker_frame_src,
+ expect_feature_available_default, 'serial');
+}, header + ' allows workers in same-origin relocation.');
+
+async_test(t => {
+ test_feature_availability(
+ 'serial.getPorts()', t, cross_origin_src,
+ expect_feature_unavailable_default, 'serial');
+}, header + ' disallows cross-origin relocation.');
+
+async_test(t => {
+ test_feature_availability(
+ 'serial.getPorts()', t, cross_origin_worker_frame_src,
+ expect_feature_unavailable_default, 'serial');
+}, header + ' disallows workers in cross-origin relocation.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy-attribute.https.sub.html b/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy-attribute.https.sub.html
new file mode 100644
index 0000000000..02560b180f
--- /dev/null
+++ b/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy-attribute.https.sub.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<body>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/permissions-policy/resources/permissions-policy.js></script>
+<script>
+'use strict';
+const sub = 'https://{{domains[www]}}:{{ports[https][0]}}';
+const same_origin_src = '/permissions-policy/resources/permissions-policy-serial.html';
+const cross_origin_src = sub + same_origin_src;
+const same_origin_worker_frame_src =
+ '/permissions-policy/resources/permissions-policy-serial-worker.html';
+const cross_origin_worker_frame_src = sub + same_origin_worker_frame_src;
+const feature_name = 'Permissions policy "serial"';
+const header = 'allow="serial" attribute';
+
+async_test(t => {
+ test_feature_availability(
+ 'serial.getPorts()', t, same_origin_src,
+ expect_feature_available_default, 'serial');
+}, feature_name + ' can be enabled in same-origin iframe using ' + header);
+
+async_test(t => {
+ test_feature_availability(
+ 'serial.getPorts()', t, same_origin_worker_frame_src,
+ expect_feature_available_default, 'serial');
+}, feature_name + ' can be enabled in a worker in same-origin iframe using ' +
+ header);
+
+async_test(t => {
+ test_feature_availability(
+ 'serial.getPorts()', t, cross_origin_src,
+ expect_feature_available_default, 'serial');
+}, feature_name + ' can be enabled in cross-origin iframe using ' + header);
+
+async_test(t => {
+ test_feature_availability(
+ 'serial.getPorts()', t, cross_origin_worker_frame_src,
+ expect_feature_available_default, 'serial');
+}, feature_name + ' can be enabled in a worker in cross-origin iframe using ' +
+ header);
+
+fetch_tests_from_worker(new Worker(
+ 'resources/serial-allowed-by-permissions-policy-worker.js'));
+</script>
+</body>
diff --git a/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy.https.sub.html b/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy.https.sub.html
new file mode 100644
index 0000000000..1be8eb979f
--- /dev/null
+++ b/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy.https.sub.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<meta name=timeout content=long>
+<body>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/permissions-policy/resources/permissions-policy.js></script>
+<script>
+'use strict';
+const sub = 'https://{{domains[www]}}:{{ports[https][0]}}';
+const same_origin_src = '/permissions-policy/resources/permissions-policy-serial.html';
+const cross_origin_src = sub + same_origin_src;
+const same_origin_worker_frame_src =
+ '/permissions-policy/resources/permissions-policy-serial-worker.html';
+const cross_origin_worker_frame_src = sub + same_origin_worker_frame_src;
+const header = 'Permissions-Policy header serial=*';
+
+promise_test(
+ () => navigator.serial.getPorts(),
+ header + ' allows the top-level document.');
+
+async_test(t => {
+ test_feature_availability('serial.getPorts()', t, same_origin_src,
+ expect_feature_available_default);
+}, header + ' allows same-origin iframes.');
+
+async_test(t => {
+ test_feature_availability('serial.getPorts()', t, same_origin_worker_frame_src,
+ expect_feature_available_default);
+}, header + ' allows workers in same-origin iframes.');
+
+// Set allow="serial" on iframe element to delegate 'serial' to cross origin
+// subframe.
+async_test(t => {
+ test_feature_availability('serial.getPorts()', t, cross_origin_src,
+ expect_feature_available_default, 'serial');
+}, header + ' allows cross-origin iframes.');
+
+// Set allow="serial" on iframe element to delegate 'serial' to cross origin
+// subframe.
+async_test(t => {
+ test_feature_availability('serial.getPorts()', t,
+ cross_origin_worker_frame_src,
+ expect_feature_available_default, 'serial');
+}, header + ' allows workers in cross-origin iframes.');
+
+fetch_tests_from_worker(new Worker(
+ 'resources/serial-allowed-by-permissions-policy-worker.js'));
+</script>
+</body>
diff --git a/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy.https.sub.html.headers b/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy.https.sub.html.headers
new file mode 100644
index 0000000000..27deb82758
--- /dev/null
+++ b/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: serial=*
diff --git a/testing/web-platform/tests/serial/serial-default-permissions-policy.https.sub.html b/testing/web-platform/tests/serial/serial-default-permissions-policy.https.sub.html
new file mode 100644
index 0000000000..e3908d9215
--- /dev/null
+++ b/testing/web-platform/tests/serial/serial-default-permissions-policy.https.sub.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<body>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/permissions-policy/resources/permissions-policy.js></script>
+<script>
+'use strict';
+var same_origin_src = '/permissions-policy/resources/permissions-policy-serial.html';
+var cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+var header = 'Default "serial" permissions policy "self"';
+
+promise_test(
+ () => navigator.serial.getPorts(),
+ header + ' allows the top-level document.');
+
+async_test(t => {
+ test_feature_availability('serial.getPorts()', t, same_origin_src,
+ expect_feature_available_default);
+}, header + ' allows same-origin iframes.');
+
+async_test(t => {
+ test_feature_availability('serial.getPorts()', t, cross_origin_src,
+ expect_feature_unavailable_default);
+}, header + ' disallows cross-origin iframes.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/serial/serial-disabled-by-permissions-policy.https.sub.html b/testing/web-platform/tests/serial/serial-disabled-by-permissions-policy.https.sub.html
new file mode 100644
index 0000000000..d482b19cae
--- /dev/null
+++ b/testing/web-platform/tests/serial/serial-disabled-by-permissions-policy.https.sub.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/test-only-api.js"></script>
+<script src="/permissions-policy/resources/permissions-policy.js"></script>
+<script>
+'use strict';
+const sub = 'https://{{domains[www]}}:{{ports[https][0]}}';
+const same_origin_src = '/permissions-policy/resources/permissions-policy-serial.html';
+const cross_origin_src = sub + same_origin_src;
+const same_origin_worker_frame_src =
+ '/permissions-policy/resources/permissions-policy-serial-worker.html';
+const cross_origin_worker_frame_src = sub + same_origin_worker_frame_src;
+const header = 'Permissions-Policy header serial=()';
+
+promise_test(() => {
+ return navigator.serial.getPorts().then(() => {
+ assert_unreached('expected promise to reject with SecurityError');
+ }, error => {
+ assert_equals(error.name, 'SecurityError');
+ });
+}, header + ' disallows getPorts in the top-level document.');
+
+async_test(t => {
+ test_feature_availability('serial.getPorts()', t, same_origin_src,
+ expect_feature_unavailable_default);
+}, header + ' disallows same-origin iframes.');
+
+async_test(t => {
+ test_feature_availability('serial.getPorts()', t, same_origin_worker_frame_src,
+ expect_feature_unavailable_default);
+}, header + ' disallows workers in same-origin iframes.');
+
+async_test(t => {
+ test_feature_availability('serial.getPorts()', t, cross_origin_src,
+ expect_feature_unavailable_default);
+}, header + ' disallows cross-origin iframes.');
+
+async_test(t => {
+ test_feature_availability('serial.getPorts()', t,
+ cross_origin_worker_frame_src,
+ expect_feature_unavailable_default);
+}, header + ' disallows workers in cross-origin iframes.');
+
+fetch_tests_from_worker(new Worker(
+ 'resources/serial-disabled-by-permissions-policy-worker.js'));
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/serial/serial-disabled-by-permissions-policy.https.sub.html.headers b/testing/web-platform/tests/serial/serial-disabled-by-permissions-policy.https.sub.html.headers
new file mode 100644
index 0000000000..690b696751
--- /dev/null
+++ b/testing/web-platform/tests/serial/serial-disabled-by-permissions-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: serial=()
diff --git a/testing/web-platform/tests/serial/serialPort_disconnect-manual.https.html b/testing/web-platform/tests/serial/serialPort_disconnect-manual.https.html
new file mode 100644
index 0000000000..5386201e6c
--- /dev/null
+++ b/testing/web-platform/tests/serial/serialPort_disconnect-manual.https.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title></title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="resources/common.js"></script>
+ <script src="resources/manual.js"></script>
+ </head>
+ <body>
+ <p>
+ These tests require a serial device to be connected and disconnected.
+ </p>
+ <script>
+ manual_serial_test(async (t, port) => {
+ const watcher = new EventWatcher(t, navigator.serial, ['disconnect']);
+
+ await port.open({baudRate: 115200, bufferSize: 1024});
+
+ const disconnectPromise = watcher.wait_for(['disconnect'])
+ const reader = port.readable.getReader();
+
+ const disconnectMessage = document.createElement('div');
+ disconnectMessage.textContent = 'Disconnect the device now.';
+ document.body.appendChild(disconnectMessage);
+
+ try {
+ while (true) {
+ const {value, done} = await reader.read();
+ // Ignore |value| in case the device happens to produce data. It is
+ // not important for this test.
+ assert_false(done);
+ }
+ } catch (e) {
+ assert_equals(e.constructor, DOMException);
+ assert_equals(e.name, 'NetworkError');
+ }
+ reader.releaseLock();
+ assert_equals(port.readable, null);
+
+ let event = await disconnectPromise;
+ assert_equals(event.target, port);
+
+ disconnectMessage.remove();
+
+ await port.close();
+ }, 'Disconnect during read is detected.');
+
+ manual_serial_test(async (t, port) => {
+ const watcher = new EventWatcher(t, navigator.serial, ['disconnect']);
+
+ await port.open({baudRate: 115200, bufferSize: 1024});
+
+ const disconnectPromise = watcher.wait_for(['disconnect'])
+ const writer = port.writable.getWriter();
+
+ const disconnectMessage = document.createElement('div');
+ disconnectMessage.textContent = 'Disconnect the device now.';
+ document.body.appendChild(disconnectMessage);
+
+ const data = new Uint8Array(4096);
+ try {
+ while (true) {
+ await writer.write(data);
+ }
+ } catch (e) {
+ assert_equals(e.constructor, DOMException);
+ assert_equals(e.name, 'NetworkError');
+ }
+ writer.releaseLock();
+ assert_equals(port.writable, null);
+
+ let event = await disconnectPromise;
+ assert_equals(event.target, port);
+
+ disconnectMessage.remove();
+
+ await port.close();
+ }, 'Disconnect during write is detected.');
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/serial/serialPort_loopback-manual.https.html b/testing/web-platform/tests/serial/serialPort_loopback-manual.https.html
new file mode 100644
index 0000000000..bf876b6610
--- /dev/null
+++ b/testing/web-platform/tests/serial/serialPort_loopback-manual.https.html
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title></title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="resources/common.js"></script>
+ <script src="resources/manual.js"></script>
+ </head>
+ <body>
+ <p>
+ These tests require a connected serial device configured to act as a
+ "loopback" device, with the transmit and receive pins wired together.
+ </p>
+ <script>
+ manual_serial_test(async (t, port) => {
+ await port.open({baudRate: 115200, bufferSize: 1024});
+
+ // Create something much smaller than bufferSize above.
+ const data = new Uint8Array(64);
+ for (let i = 0; i < data.byteLength; ++i)
+ data[i] = i & 0xff;
+
+ const reader = port.readable.getReader();
+
+ for (let i = 0; i < 10; ++i) {
+ const writer = port.writable.getWriter();
+ writer.write(data);
+ const writePromise = writer.close();
+
+ const value = await readWithLength(reader, data.byteLength);
+ await writePromise;
+
+ compareArrays(value, data);
+ }
+
+ reader.releaseLock();
+ await port.close();
+ }, 'Can perform a series of small writes.');
+
+ manual_serial_test(async (t, port) => {
+ await port.open({baudRate: 115200, bufferSize: 1024});
+
+ // Create something much larger than bufferSize above.
+ const data = new Uint8Array(10 * 1024);
+ for (let i = 0; i < data.byteLength; ++i)
+ data[i] = (i / 1024) & 0xff;
+
+ const reader = port.readable.getReader();
+
+ for (let i = 0; i < 10; ++i) {
+ const writer = port.writable.getWriter();
+ writer.write(data);
+ const writePromise = writer.close();
+
+ const value = await readWithLength(reader, data.byteLength);
+ await writePromise;
+
+ compareArrays(value, data);
+ }
+
+ reader.releaseLock();
+ await port.close();
+ }, 'Can perform a series of large writes.');
+
+ manual_serial_test(async (t, port) => {
+ await port.open({baudRate: 115200, bufferSize: 64});
+
+ const writer = port.writable.getWriter();
+ // |data| is small enough to be completely transmitted.
+ let data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
+ await writer.write(data);
+
+ // Wait a little bit for the device to process the incoming data.
+ await new Promise((resolve) => setTimeout(resolve, 100));
+ // ...before discarding the receive buffers.
+ await port.readable.cancel();
+
+ data = new Uint8Array([9, 10, 11, 12, 13, 14, 15, 16]);
+ const reader = port.readable.getReader();
+ const readPromise = readWithLength(reader, data.byteLength);
+
+ // The next block of data should be received successfully.
+ await writer.write(data);
+ writer.releaseLock();
+
+ const value = await readPromise;
+ reader.releaseLock();
+
+ compareArrays(value, data);
+
+ await port.close();
+ }, 'Canceling the reader discards buffered data.');
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/serial/serialPort_loopback_BreakError-manual.https.html b/testing/web-platform/tests/serial/serialPort_loopback_BreakError-manual.https.html
new file mode 100644
index 0000000000..369fac6155
--- /dev/null
+++ b/testing/web-platform/tests/serial/serialPort_loopback_BreakError-manual.https.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title></title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="resources/common.js"></script>
+ <script src="resources/manual.js"></script>
+ </head>
+ <body>
+ <p>
+ These tests require a connected serial device configured to act as a
+ "loopback" device, with the transmit and receive pins wired together.
+ </p>
+ <script>
+ manual_serial_test(async (t, port) => {
+ await port.open({baudRate: 115200, bufferSize: 1024});
+
+ let reader = port.readable.getReader();
+ let readPromise = (async () => {
+ // A single zero byte will be read before the break is detected.
+ const {value, done} = await reader.read();
+ compareArrays(value, new Uint8Array([0]));
+ assert_false(done);
+
+ try {
+ const {value, done} = await reader.read();
+ assert_unreached(`Expected break, got ${value.byteLength} bytes`);
+ } catch (e) {
+ assert_equals(e.constructor, DOMException);
+ assert_equals(e.name, 'BreakError');
+ }
+ })();
+
+ await port.setSignals({break: true});
+ await readPromise;
+ await port.setSignals({break: false});
+
+ const writer = port.writable.getWriter();
+ // |data| is small enough to be completely transmitted.
+ let data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
+ await writer.write(data);
+ writer.releaseLock();
+
+ reader = port.readable.getReader();
+ const buffer = await readWithLength(reader, data.byteLength);;
+ compareArrays(buffer, data);
+ reader.releaseLock();
+
+ await port.close();
+ }, 'Break is detected.');
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/serial/serialPort_loopback_BufferOverrunError-manual.https.html b/testing/web-platform/tests/serial/serialPort_loopback_BufferOverrunError-manual.https.html
new file mode 100644
index 0000000000..06f8271ac9
--- /dev/null
+++ b/testing/web-platform/tests/serial/serialPort_loopback_BufferOverrunError-manual.https.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title></title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="resources/common.js"></script>
+ <script src="resources/manual.js"></script>
+ </head>
+ <body>
+ <p>
+ These tests require a connected serial device configured to act as a
+ "loopback" device, with the transmit and receive pins wired together.
+ </p>
+ <script>
+ manual_serial_test(async (t, port) => {
+ await port.open({baudRate: 115200, bufferSize: 1024});
+
+ // Create something much larger than bufferSize above.
+ const data = new Uint8Array(16 * 1024);
+ for (let i = 0; i < data.byteLength; ++i)
+ data[i] = (i / 1024) & 0xff;
+
+ // Completely write |data| to the port without waiting for it to be
+ // received.
+ const writer = port.writable.getWriter();
+ writer.write(data);
+ await writer.close();
+
+ const reader = port.readable.getReader();
+ const chunks = [];
+ let actualLength = 0;
+ while (true) {
+ try {
+ const {value, done} = await reader.read();
+ if (value) {
+ actualLength += value.byteLength;
+ chunks.push(value);
+ }
+ if (done) {
+ assert_unreached("Unexpected end of stream.");
+ break;
+ }
+ } catch (e) {
+ assert_equals(e.name, 'BufferOverrunError');
+ break;
+ }
+ }
+ reader.releaseLock();
+
+ const buffer = new Uint8Array(actualLength);
+ chunks.reduce((offset, chunk) => {
+ buffer.set(chunk, offset);
+ return offset + chunk.byteLength;
+ }, 0);
+
+ assert_greater_than(actualLength, 0);
+ compareArrays(buffer, data.slice(0, actualLength));
+
+ await port.close();
+ }, 'Overflowing the receive buffer triggers an error.');
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/serial/serialPort_loopback_flowControl-manual.https.html b/testing/web-platform/tests/serial/serialPort_loopback_flowControl-manual.https.html
new file mode 100644
index 0000000000..930dab74d6
--- /dev/null
+++ b/testing/web-platform/tests/serial/serialPort_loopback_flowControl-manual.https.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title></title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="resources/common.js"></script>
+ <script src="resources/manual.js"></script>
+ </head>
+ <body>
+ <p>
+ These tests require a connected serial device configured to act as a
+ "loopback" device, with the TX and RX pins and RTS and CTS pins wired
+ together.
+ </p>
+ <script>
+ manual_serial_test(async (t, port) => {
+ await port.open({
+ baudRate: 115200,
+ bufferSize: 255,
+ flowControl: 'none'
+ });
+ t.add_cleanup(async () => {
+ await port.close();
+ });
+
+ await port.setSignals({ requestToSend: true });
+ let signals = await port.getSignals();
+ assert_true(signals.clearToSend);
+
+ await port.setSignals({ requestToSend: false });
+ signals = await port.getSignals();
+ assert_false(signals.clearToSend);
+ }, 'Manual RTS control works with no flow control enabled');
+
+ manual_serial_test(async (t, port) => {
+ await port.open({
+ baudRate: 115200,
+ bufferSize: 255,
+ flowControl: 'hardware'
+ });
+
+ const writer = port.writable.getWriter();
+ t.add_cleanup(async () => {
+ writer.releaseLock();
+ await port.close();
+ });
+
+ assert_true((await port.getSignals()).clearToSend);
+
+ const buffer = new Uint8Array(1);
+ while ((await port.getSignals()).clearToSend) {
+ await writer.write(buffer);
+ }
+ }, 'Hardware flow control automatically sets RTS pin');
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/serial/serialPort_readable-manual.https.html b/testing/web-platform/tests/serial/serialPort_readable-manual.https.html
new file mode 100644
index 0000000000..4e49ef4061
--- /dev/null
+++ b/testing/web-platform/tests/serial/serialPort_readable-manual.https.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title></title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="resources/common.js"></script>
+ <script src="resources/manual.js"></script>
+ </head>
+ <body>
+ <p>
+ These tests require a device configured with the following Arduino sketch:
+
+ <pre>
+uint32_t seed = 0;
+uint32_t bytesToSend = 0;
+
+uint8_t nextByte() {
+ seed = (1103515245 * seed + 12345) % 0x8000000;
+ return (seed >> 16) & 0xFF;
+}
+
+void setup() {
+ Serial.begin(115200);
+}
+
+void loop() {
+ if (!Serial) {
+ return;
+ }
+
+ if (bytesToSend == 0) {
+ // Read the seed and number of bytes to send from the host as 32-bit
+ // little-endian values.
+ if (Serial.available() &lt 8) {
+ return;
+ }
+
+ uint8_t buf[8];
+ Serial.readBytes((char*)buf, sizeof buf);
+ seed = (uint32_t)buf[0] |
+ ((uint32_t)buf[1] &lt;&lt; 8) |
+ ((uint32_t)buf[2] &lt;&lt; 16) |
+ ((uint32_t)buf[3] &lt;&lt; 24);
+ bytesToSend = (uint32_t)buf[4] |
+ ((uint32_t)buf[5] &lt;&lt; 8) |
+ ((uint32_t)buf[6] &lt;&lt; 16) |
+ ((uint32_t)buf[7] &lt;&lt; 24);
+ } else {
+ uint8_t buf[64];
+ uint32_t count = min(sizeof buf, bytesToSend);
+ for (uint32_t i = 0; i &lt; count; ++i) {
+ buf[i] = nextByte();
+ }
+ bytesToSend -= count;
+ Serial.write((char*)buf, count);
+ }
+}
+ </pre>
+ </p>
+ <p>
+ <progress id='progress'></progress>
+ </p>
+ <script>
+ let seed = 10;
+ const length = 1024 * 1024 * 10;
+
+ function next_byte() {
+ seed = (Math.imul(1103515245, seed) + 12345) % (1 << 31);
+ return (seed >> 16) & 0xFF;
+ }
+
+ manual_serial_test(async (t, port) => {
+ await port.open({baudRate: 115200, bufferSize: 1024});
+
+ const config = new DataView(new ArrayBuffer(8));
+ config.setUint32(0, seed, /*littleEndian=*/true);
+ config.setUint32(4, length, /*littleEndian=*/true);
+
+ const writer = port.writable.getWriter();
+ writer.write(config);
+
+ const progress = document.getElementById('progress');
+ progress.max = length;
+ progress.value = 0;
+
+ const reader = port.readable.getReader();
+ let bytesRead = 0;
+ while (bytesRead < length) {
+ const { value, done } = await reader.read();
+ assert_false(done);
+ for (let i = 0; i < value.byteLength; ++i) {
+ assert_equals(value[i], next_byte(),
+ `mismatch at byte ${bytesRead + i}`);
+ }
+ bytesRead += value.byteLength;
+ progress.value = bytesRead;
+ }
+
+ writer.releaseLock();
+ reader.releaseLock();
+ await port.close();
+ }, `Reading ${length} bytes from the device succeeds.`);
+ </script>
+ </body>
+</html>