From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001
From: Daniel Baumann
Date: Sun, 7 Apr 2024 19:32:43 +0200
Subject: Adding upstream version 1:115.7.0.
Signed-off-by: Daniel Baumann
---
testing/web-platform/tests/webusb/META.yml | 3 +
testing/web-platform/tests/webusb/README.md | 17 +
.../getDevices/reject_opaque_origin.https.html | 14 +
.../reject_opaque_origin.https.html.headers | 1 +
.../getDevices/sandboxed_iframe.https.window.js | 19 +
.../tests/webusb/idlharness.https.any.js | 47 +
.../tests/webusb/insecure-context.any.js | 21 +
.../protected-interface-classes.https.any.js | 89 ++
.../requestDevice/reject_opaque_origin.https.html | 15 +
.../reject_opaque_origin.https.html.headers | 1 +
.../requestDevice/sandboxed_iframe.https.window.js | 24 +
.../tests/webusb/resources/fake-devices.js | 175 +++
.../web-platform/tests/webusb/resources/manual.js | 110 ++
.../tests/webusb/resources/open-in-iframe.html | 42 +
.../tests/webusb/resources/open-in-worker.js | 16 +
.../usb-allowed-by-permissions-policy-worker.js | 14 +
.../usb-disabled-by-permissions-policy-worker.js | 17 +
.../tests/webusb/resources/usb-helpers.js | 104 ++
...olicy-attribute-redirect-on-load.https.sub.html | 44 +
...-by-permissions-policy-attribute.https.sub.html | 46 +
...sb-allowed-by-permissions-policy.https.sub.html | 46 +
...ed-by-permissions-policy.https.sub.html.headers | 1 +
.../usb-default-permissions-policy.https.sub.html | 27 +
...b-disabled-by-permissions-policy.https.sub.html | 57 +
...ed-by-permissions-policy.https.sub.html.headers | 1 +
.../webusb/usb-garbage-collection.https.window.js | 15 +
.../usb-supported-by-permissions-policy.html | 11 +
testing/web-platform/tests/webusb/usb.https.any.js | 50 +
.../web-platform/tests/webusb/usb.https.window.js | 129 ++
.../tests/webusb/usb.serviceworker.https.html | 12 +
.../web-platform/tests/webusb/usb.serviceworker.js | 9 +
.../webusb/usbAlternateInterface.https.any.js | 34 +
.../tests/webusb/usbConfiguration.https.any.js | 24 +
.../tests/webusb/usbConnectionEvent.https.any.js | 22 +
.../tests/webusb/usbDevice-iframe.https.html | 86 ++
.../webusb/usbDevice-same-objecct.https.any.js | 26 +
.../tests/webusb/usbDevice-worker.https.html | 36 +
.../tests/webusb/usbDevice.https.any.js | 1249 ++++++++++++++++++++
.../usbDevice_claimInterface-manual.https.html | 46 +
.../usbDevice_controlTransferIn-manual.https.html | 348 ++++++
.../webusb/usbDevice_forget-manual.https.html | 27 +
.../tests/webusb/usbDevice_reset-manual.https.html | 46 +
.../webusb/usbDevice_transferIn-manual.https.html | 148 +++
.../tests/webusb/usbEndpoint.https.any.js | 46 +
.../tests/webusb/usbInTransferResult.https.any.js | 29 +
.../tests/webusb/usbInterface.https.any.js | 55 +
.../usbIsochronousInTransferPacket.https.any.js | 28 +
.../usbIsochronousInTransferResult.https.any.js | 36 +
.../usbIsochronousOutTransferPacket.https.any.js | 21 +
.../usbIsochronousOutTransferResult.https.any.js | 19 +
.../tests/webusb/usbOutTransferResult.https.any.js | 19 +
51 files changed, 3522 insertions(+)
create mode 100644 testing/web-platform/tests/webusb/META.yml
create mode 100644 testing/web-platform/tests/webusb/README.md
create mode 100644 testing/web-platform/tests/webusb/getDevices/reject_opaque_origin.https.html
create mode 100644 testing/web-platform/tests/webusb/getDevices/reject_opaque_origin.https.html.headers
create mode 100644 testing/web-platform/tests/webusb/getDevices/sandboxed_iframe.https.window.js
create mode 100644 testing/web-platform/tests/webusb/idlharness.https.any.js
create mode 100644 testing/web-platform/tests/webusb/insecure-context.any.js
create mode 100644 testing/web-platform/tests/webusb/protected-interface-classes.https.any.js
create mode 100644 testing/web-platform/tests/webusb/requestDevice/reject_opaque_origin.https.html
create mode 100644 testing/web-platform/tests/webusb/requestDevice/reject_opaque_origin.https.html.headers
create mode 100644 testing/web-platform/tests/webusb/requestDevice/sandboxed_iframe.https.window.js
create mode 100644 testing/web-platform/tests/webusb/resources/fake-devices.js
create mode 100644 testing/web-platform/tests/webusb/resources/manual.js
create mode 100644 testing/web-platform/tests/webusb/resources/open-in-iframe.html
create mode 100644 testing/web-platform/tests/webusb/resources/open-in-worker.js
create mode 100644 testing/web-platform/tests/webusb/resources/usb-allowed-by-permissions-policy-worker.js
create mode 100644 testing/web-platform/tests/webusb/resources/usb-disabled-by-permissions-policy-worker.js
create mode 100644 testing/web-platform/tests/webusb/resources/usb-helpers.js
create mode 100644 testing/web-platform/tests/webusb/usb-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html
create mode 100644 testing/web-platform/tests/webusb/usb-allowed-by-permissions-policy-attribute.https.sub.html
create mode 100644 testing/web-platform/tests/webusb/usb-allowed-by-permissions-policy.https.sub.html
create mode 100644 testing/web-platform/tests/webusb/usb-allowed-by-permissions-policy.https.sub.html.headers
create mode 100644 testing/web-platform/tests/webusb/usb-default-permissions-policy.https.sub.html
create mode 100644 testing/web-platform/tests/webusb/usb-disabled-by-permissions-policy.https.sub.html
create mode 100644 testing/web-platform/tests/webusb/usb-disabled-by-permissions-policy.https.sub.html.headers
create mode 100644 testing/web-platform/tests/webusb/usb-garbage-collection.https.window.js
create mode 100644 testing/web-platform/tests/webusb/usb-supported-by-permissions-policy.html
create mode 100644 testing/web-platform/tests/webusb/usb.https.any.js
create mode 100644 testing/web-platform/tests/webusb/usb.https.window.js
create mode 100644 testing/web-platform/tests/webusb/usb.serviceworker.https.html
create mode 100644 testing/web-platform/tests/webusb/usb.serviceworker.js
create mode 100644 testing/web-platform/tests/webusb/usbAlternateInterface.https.any.js
create mode 100644 testing/web-platform/tests/webusb/usbConfiguration.https.any.js
create mode 100644 testing/web-platform/tests/webusb/usbConnectionEvent.https.any.js
create mode 100644 testing/web-platform/tests/webusb/usbDevice-iframe.https.html
create mode 100644 testing/web-platform/tests/webusb/usbDevice-same-objecct.https.any.js
create mode 100644 testing/web-platform/tests/webusb/usbDevice-worker.https.html
create mode 100644 testing/web-platform/tests/webusb/usbDevice.https.any.js
create mode 100644 testing/web-platform/tests/webusb/usbDevice_claimInterface-manual.https.html
create mode 100644 testing/web-platform/tests/webusb/usbDevice_controlTransferIn-manual.https.html
create mode 100644 testing/web-platform/tests/webusb/usbDevice_forget-manual.https.html
create mode 100644 testing/web-platform/tests/webusb/usbDevice_reset-manual.https.html
create mode 100644 testing/web-platform/tests/webusb/usbDevice_transferIn-manual.https.html
create mode 100644 testing/web-platform/tests/webusb/usbEndpoint.https.any.js
create mode 100644 testing/web-platform/tests/webusb/usbInTransferResult.https.any.js
create mode 100644 testing/web-platform/tests/webusb/usbInterface.https.any.js
create mode 100644 testing/web-platform/tests/webusb/usbIsochronousInTransferPacket.https.any.js
create mode 100644 testing/web-platform/tests/webusb/usbIsochronousInTransferResult.https.any.js
create mode 100644 testing/web-platform/tests/webusb/usbIsochronousOutTransferPacket.https.any.js
create mode 100644 testing/web-platform/tests/webusb/usbIsochronousOutTransferResult.https.any.js
create mode 100644 testing/web-platform/tests/webusb/usbOutTransferResult.https.any.js
(limited to 'testing/web-platform/tests/webusb')
diff --git a/testing/web-platform/tests/webusb/META.yml b/testing/web-platform/tests/webusb/META.yml
new file mode 100644
index 0000000000..546094855e
--- /dev/null
+++ b/testing/web-platform/tests/webusb/META.yml
@@ -0,0 +1,3 @@
+spec: https://wicg.github.io/webusb/
+suggested_reviewers:
+ - reillyeon
diff --git a/testing/web-platform/tests/webusb/README.md b/testing/web-platform/tests/webusb/README.md
new file mode 100644
index 0000000000..c19e8fa347
--- /dev/null
+++ b/testing/web-platform/tests/webusb/README.md
@@ -0,0 +1,17 @@
+# WebUSB Testing
+
+WebUSB testing relies on the [WebUSB Testing API] which must be
+provided by browsers under test.
+
+In this test suite `resources/usb-helpers.js` detects and triggers
+the API to be loaded as needed.
+
+The Chromium implementation is provided by
+`../resources/chromium/webusb-test.js` using [MojoJS].
+
+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.
+
+[MojoJS]: https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/testing/web_platform_tests.md#mojojs
+[WebUSB Testing API]: https://wicg.github.io/webusb/test/
diff --git a/testing/web-platform/tests/webusb/getDevices/reject_opaque_origin.https.html b/testing/web-platform/tests/webusb/getDevices/reject_opaque_origin.https.html
new file mode 100644
index 0000000000..7cb503ce3c
--- /dev/null
+++ b/testing/web-platform/tests/webusb/getDevices/reject_opaque_origin.https.html
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/testing/web-platform/tests/webusb/getDevices/reject_opaque_origin.https.html.headers b/testing/web-platform/tests/webusb/getDevices/reject_opaque_origin.https.html.headers
new file mode 100644
index 0000000000..1efcf8c226
--- /dev/null
+++ b/testing/web-platform/tests/webusb/getDevices/reject_opaque_origin.https.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: sandbox allow-scripts
diff --git a/testing/web-platform/tests/webusb/getDevices/sandboxed_iframe.https.window.js b/testing/web-platform/tests/webusb/getDevices/sandboxed_iframe.https.window.js
new file mode 100644
index 0000000000..60bdf30587
--- /dev/null
+++ b/testing/web-platform/tests/webusb/getDevices/sandboxed_iframe.https.window.js
@@ -0,0 +1,19 @@
+'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 = 'usb';
+ 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('GetDevices', '*');
+ });
+}, 'GetDevices from a sandboxed iframe is valid.');
diff --git a/testing/web-platform/tests/webusb/idlharness.https.any.js b/testing/web-platform/tests/webusb/idlharness.https.any.js
new file mode 100644
index 0000000000..0c8cb322a0
--- /dev/null
+++ b/testing/web-platform/tests/webusb/idlharness.https.any.js
@@ -0,0 +1,47 @@
+// META: timeout=long
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+// META: script=/resources/test-only-api.js
+// META: script=/webusb/resources/fake-devices.js
+// META: script=/webusb/resources/usb-helpers.js
+
+'use strict';
+
+idl_test(
+ ['webusb'],
+ ['permissions', 'html', 'dom'],
+ async idl_array => {
+ if (self.GLOBAL.isWindow()) {
+ idl_array.add_objects({ Navigator: ['navigator'] });
+ } else if (self.GLOBAL.isWorker()) {
+ idl_array.add_objects({ WorkerNavigator: ['navigator'] });
+ }
+
+ idl_array.add_objects({
+ USB: ['navigator.usb'],
+ USBAlternateInterface: ['usbAlternateInterface'],
+ USBConfiguration: ['usbConfiguration'],
+ USBConnectionEvent: ['usbConnectionEvent'],
+ USBDevice: ['usbDevice'],
+ USBEndpoint: ['usbEndpoint'],
+ USBInterface: ['usbInterface'],
+ USBInTransferResult: ['new USBInTransferResult("ok")'],
+ USBOutTransferResult: ['new USBOutTransferResult("ok")'],
+ USBIsochronousInTransferResult: ['new USBIsochronousInTransferResult([])'],
+ USBIsochronousOutTransferResult: ['new USBIsochronousOutTransferResult([])'],
+ USBIsochronousInTransferPacket: ['new USBIsochronousInTransferPacket("ok")'],
+ USBIsochronousOutTransferPacket: ['new USBIsochronousOutTransferPacket("ok")'],
+ });
+
+ return usb_test(async () => {
+ // Ignored errors are surfaced in idlharness.js's test_object below.
+ self.usbDevice = await getFakeDevice().device;
+ self.usbConfiguration = usbDevice.configurations[0];
+ self.usbInterface = usbConfiguration.interfaces[0];
+ self.usbAlternateInterface = usbInterface.alternates[0];
+ self.usbEndpoint = usbAlternateInterface.endpoints[0];
+ self.usbConnectionEvent =
+ new USBConnectionEvent('connect', { device: usbDevice });
+ }, 'USB device setup');
+ }
+);
diff --git a/testing/web-platform/tests/webusb/insecure-context.any.js b/testing/web-platform/tests/webusb/insecure-context.any.js
new file mode 100644
index 0000000000..962738987b
--- /dev/null
+++ b/testing/web-platform/tests/webusb/insecure-context.any.js
@@ -0,0 +1,21 @@
+'use strict';
+
+test(() => {
+ assert_false(isSecureContext);
+ assert_false('usb' in navigator);
+}, '"usb" should not be present on navigator in an insecure context.');
+
+[
+ 'USB', 'USBAlternateInterface', 'USBConfiguration', 'USBConnectionEvent',
+ 'USBDevice', 'USBEndpoint', 'USBInterface', 'USBInTransferResult',
+ 'USBOutTransferResult', 'USBIsochronousInTransferResult',
+ 'USBIsochronousOutTransferResult', 'USBIsochronousInTransferPacket',
+ 'USBIsochronousOutTransferPacket',
+].forEach((symbol) => {
+ test(() => {
+ assert_false(isSecureContext);
+ assert_false(symbol in this)
+ }, '"' + symbol + '" should not be visible in an insecure context.');
+});
+
+done();
diff --git a/testing/web-platform/tests/webusb/protected-interface-classes.https.any.js b/testing/web-platform/tests/webusb/protected-interface-classes.https.any.js
new file mode 100644
index 0000000000..027c2c418c
--- /dev/null
+++ b/testing/web-platform/tests/webusb/protected-interface-classes.https.any.js
@@ -0,0 +1,89 @@
+// META: script=/resources/test-only-api.js
+// META: script=/webusb/resources/usb-helpers.js
+'use strict';
+
+async function runTestForInterfaceClass(t, interfaceClass) {
+ await navigator.usb.test.initialize();
+
+ const fakeDeviceTemplate = {
+ usbVersionMajor: 2,
+ usbVersionMinor: 0,
+ usbVersionSubminor: 0,
+ deviceClass: 7,
+ deviceSubclass: 1,
+ deviceProtocol: 2,
+ vendorId: 0x18d1,
+ productId: 0xf00d,
+ deviceVersionMajor: 1,
+ deviceVersionMinor: 2,
+ deviceVersionSubminor: 3,
+ manufacturerName: 'Google, Inc.',
+ productName: 'Test Device',
+ serialNumber: '4 (chosen randomly)',
+ activeConfigurationValue: 0,
+ configurations: [{
+ configurationValue: 1,
+ configurationName: 'Default configuration',
+ interfaces: [{
+ interfaceNumber: 0,
+ alternates: [{
+ alternateSetting: 0,
+ interfaceClass: interfaceClass,
+ interfaceSubclass: 0x01,
+ interfaceProtocol: 0x01,
+ interfaceName: 'Protected interface',
+ endpoints: []
+ }]
+ }, {
+ interfaceNumber: 1,
+ alternates: [{
+ alternateSetting: 0,
+ interfaceClass: 0xff,
+ interfaceSubclass: 0x01,
+ interfaceProtocol: 0x01,
+ interfaceName: 'Unprotected interface',
+ endpoints: []
+ }]
+ }]
+ }]
+ };
+
+ let fakeDevice;
+ let device = await new Promise((resolve) => {
+ navigator.usb.addEventListener('connect', (e) => {
+ resolve(e.device);
+ }, { once: true });
+ fakeDevice = navigator.usb.test.addFakeDevice(fakeDeviceTemplate);
+ });
+
+ await device.open();
+ await device.selectConfiguration(1);
+
+ await promise_rejects_dom(t, 'SecurityError', device.claimInterface(0));
+ await device.claimInterface(1);
+
+ await device.close();
+ fakeDevice.disconnect();
+}
+
+usb_test(
+ (t) => runTestForInterfaceClass(t, 0x01),
+ 'Protected audio interface cannot be claimed');
+usb_test(
+ (t) => runTestForInterfaceClass(t, 0x03),
+ 'Protected HID interface cannot be claimed');
+usb_test(
+ (t) => runTestForInterfaceClass(t, 0x08),
+ 'Protected mass storage interface cannot be claimed');
+usb_test(
+ (t) => runTestForInterfaceClass(t, 0x0B),
+ 'Protected smart card interface cannot be claimed');
+usb_test(
+ (t) => runTestForInterfaceClass(t, 0x0E),
+ 'Protected video interface cannot be claimed');
+usb_test(
+ (t) => runTestForInterfaceClass(t, 0x10),
+ 'Protected audio/video interface cannot be claimed');
+usb_test(
+ (t) => runTestForInterfaceClass(t, 0xE0),
+ 'Protected wireless controller interface cannot be claimed');
diff --git a/testing/web-platform/tests/webusb/requestDevice/reject_opaque_origin.https.html b/testing/web-platform/tests/webusb/requestDevice/reject_opaque_origin.https.html
new file mode 100644
index 0000000000..34798ce2b1
--- /dev/null
+++ b/testing/web-platform/tests/webusb/requestDevice/reject_opaque_origin.https.html
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/testing/web-platform/tests/webusb/requestDevice/reject_opaque_origin.https.html.headers b/testing/web-platform/tests/webusb/requestDevice/reject_opaque_origin.https.html.headers
new file mode 100644
index 0000000000..1efcf8c226
--- /dev/null
+++ b/testing/web-platform/tests/webusb/requestDevice/reject_opaque_origin.https.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: sandbox allow-scripts
diff --git a/testing/web-platform/tests/webusb/requestDevice/sandboxed_iframe.https.window.js b/testing/web-platform/tests/webusb/requestDevice/sandboxed_iframe.https.window.js
new file mode 100644
index 0000000000..b63f409480
--- /dev/null
+++ b/testing/web-platform/tests/webusb/requestDevice/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 = 'usb';
+ 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 \'requestDevice\' on ' +
+ '\'USB\': No device selected.',
+ messageEvent.data);
+ resolve();
+ }));
+ iframe.contentWindow.postMessage('RequestDevice', '*');
+ });
+}, 'RequestDevice from a sandboxed iframe is valid.');
diff --git a/testing/web-platform/tests/webusb/resources/fake-devices.js b/testing/web-platform/tests/webusb/resources/fake-devices.js
new file mode 100644
index 0000000000..c5c5cadaa6
--- /dev/null
+++ b/testing/web-platform/tests/webusb/resources/fake-devices.js
@@ -0,0 +1,175 @@
+'use strict';
+
+let fakeDeviceInit = {
+ usbVersionMajor: 2,
+ usbVersionMinor: 0,
+ usbVersionSubminor: 0,
+ deviceClass: 7,
+ deviceSubclass: 1,
+ deviceProtocol: 2,
+ vendorId: 0x18d1,
+ productId: 0xf00d,
+ deviceVersionMajor: 1,
+ deviceVersionMinor: 2,
+ deviceVersionSubminor: 3,
+ manufacturerName: 'Google, Inc.',
+ productName: 'The amazing imaginary printer',
+ serialNumber: '4',
+ activeConfigurationValue: 0,
+ configurations: [
+ {
+ configurationValue: 1,
+ configurationName: 'Printer Mode',
+ interfaces: [
+ {
+ interfaceNumber: 0,
+ alternates: [{
+ alternateSetting: 0,
+ interfaceClass: 0xff,
+ interfaceSubclass: 0x01,
+ interfaceProtocol: 0x01,
+ interfaceName: 'Control',
+ endpoints: [{
+ endpointNumber: 1,
+ direction: 'in',
+ type: 'interrupt',
+ packetSize: 8
+ }]
+ }]
+ },
+ {
+ interfaceNumber: 1,
+ alternates: [{
+ alternateSetting: 0,
+ interfaceClass: 0xff,
+ interfaceSubclass: 0x02,
+ interfaceProtocol: 0x01,
+ interfaceName: 'Data',
+ endpoints: [
+ {
+ endpointNumber: 2,
+ direction: 'in',
+ type: 'bulk',
+ packetSize: 1024
+ },
+ {
+ endpointNumber: 2,
+ direction: 'out',
+ type: 'bulk',
+ packetSize: 1024
+ }
+ ]
+ }]
+ }
+ ]
+ },
+ {
+ configurationValue: 2,
+ configurationName: 'Fighting Robot Mode',
+ interfaces: [{
+ interfaceNumber: 0,
+ alternates: [
+ {
+ alternateSetting: 0,
+ interfaceClass: 0xff,
+ interfaceSubclass: 0x42,
+ interfaceProtocol: 0x01,
+ interfaceName: 'Disabled',
+ endpoints: []
+ },
+ {
+ alternateSetting: 1,
+ interfaceClass: 0xff,
+ interfaceSubclass: 0x42,
+ interfaceProtocol: 0x01,
+ interfaceName: 'Activate!',
+ endpoints: [
+ {
+ endpointNumber: 1,
+ direction: 'in',
+ type: 'isochronous',
+ packetSize: 1024
+ },
+ {
+ endpointNumber: 1,
+ direction: 'out',
+ type: 'isochronous',
+ packetSize: 1024
+ }
+ ]
+ }
+ ]
+ }]
+ },
+ {
+ configurationValue: 3,
+ configurationName: 'Non-sequential interface number and alternate ' +
+ 'setting Mode',
+ interfaces: [
+ {
+ interfaceNumber: 0,
+ alternates: [
+ {
+ alternateSetting: 0,
+ interfaceClass: 0xff,
+ interfaceSubclass: 0x01,
+ interfaceProtocol: 0x01,
+ interfaceName: 'Control',
+ endpoints: [{
+ endpointNumber: 1,
+ direction: 'in',
+ type: 'interrupt',
+ packetSize: 8
+ }]
+ },
+ {
+ alternateSetting: 2,
+ interfaceClass: 0xff,
+ interfaceSubclass: 0x02,
+ interfaceProtocol: 0x01,
+ interfaceName: 'Data',
+ endpoints: [
+ {
+ endpointNumber: 2,
+ direction: 'in',
+ type: 'bulk',
+ packetSize: 1024
+ },
+ {
+ endpointNumber: 2,
+ direction: 'out',
+ type: 'bulk',
+ packetSize: 1024
+ }
+ ]
+ }
+ ]
+ },
+ {
+ interfaceNumber: 2,
+ alternates: [{
+ alternateSetting: 0,
+ interfaceClass: 0xff,
+ interfaceSubclass: 0x02,
+ interfaceProtocol: 0x01,
+ interfaceName: 'Data',
+ endpoints: [
+ {
+ endpointNumber: 2,
+ direction: 'in',
+ type: 'bulk',
+ packetSize: 1024
+ },
+ {
+ endpointNumber: 2,
+ direction: 'out',
+ type: 'bulk',
+ packetSize: 1024
+ }
+ ]
+ }]
+ }
+ ]
+ }
+ ]
+};
diff --git a/testing/web-platform/tests/webusb/resources/manual.js b/testing/web-platform/tests/webusb/resources/manual.js
new file mode 100644
index 0000000000..e8dc08a8bd
--- /dev/null
+++ b/testing/web-platform/tests/webusb/resources/manual.js
@@ -0,0 +1,110 @@
+let manualTestDevice = null;
+
+navigator.usb.addEventListener('disconnect', (e) => {
+ if (e.device === manualTestDevice) {
+ manualTestDevice = null;
+ }
+})
+
+async function getDeviceForManualTest() {
+ if (manualTestDevice) {
+ return manualTestDevice;
+ }
+
+ 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);
+ });
+
+ manualTestDevice = await navigator.usb.requestDevice({filters: []});
+ assert_true(manualTestDevice instanceof USBDevice);
+
+ return manualTestDevice;
+}
+
+function manual_usb_test(func, name, properties) {
+ promise_test(async (test) => {
+ await func(test, await getDeviceForManualTest());
+ }, name, properties);
+}
+
+function manual_usb_serial_test(func, name, properties) {
+ promise_test(async (test) => {
+ const device = await getDeviceForManualTest();
+ await device.open();
+ test.add_cleanup(async () => {
+ if (device.opened) {
+ await device.close();
+ }
+ });
+
+ await device.selectConfiguration(1);
+
+ let controlInterface = undefined;
+ for (const iface of device.configuration.interfaces) {
+ const alternate = iface.alternates[0];
+ if (alternate.interfaceClass == 2 &&
+ alternate.interfaceSubclass == 2 &&
+ alternate.interfaceProtocol == 0) {
+ controlInterface = iface;
+ break;
+ }
+ }
+ assert_not_equals(controlInterface, undefined,
+ 'No control interface found.');
+
+ let dataInterface = undefined;
+ for (const iface of device.configuration.interfaces) {
+ const alternate = iface.alternates[0];
+ if (alternate.interfaceClass == 10 &&
+ alternate.interfaceSubclass == 0 &&
+ alternate.interfaceProtocol == 0) {
+ dataInterface = iface;
+ break;
+ }
+ }
+ assert_not_equals(dataInterface, undefined, 'No data interface found.');
+
+ await device.claimInterface(controlInterface.interfaceNumber);
+ await device.claimInterface(dataInterface.interfaceNumber);
+
+ let inEndpoint = undefined;
+ for (const endpoint of dataInterface.alternate.endpoints) {
+ if (endpoint.type == 'bulk' && endpoint.direction == 'in') {
+ inEndpoint = endpoint;
+ break;
+ }
+ }
+ assert_not_equals(inEndpoint, undefined, 'No IN endpoint found.');
+
+ let outEndpoint = undefined;
+ for (const endpoint of dataInterface.alternate.endpoints) {
+ if (endpoint.type == 'bulk' && endpoint.direction == 'out') {
+ outEndpoint = endpoint;
+ break;
+ }
+ }
+ assert_not_equals(outEndpoint, undefined, 'No OUT endpoint found.');
+
+ // Execute a SET_CONTROL_LINE_STATE command to let the device know the
+ // host is ready to transmit and receive data.
+ await device.controlTransferOut({
+ requestType: 'class',
+ recipient: 'interface',
+ request: 0x22,
+ value: 0x01,
+ index: controlInterface.interfaceNumber,
+ });
+
+ await func(test, device, inEndpoint, outEndpoint);
+ }, name, properties);
+}
\ No newline at end of file
diff --git a/testing/web-platform/tests/webusb/resources/open-in-iframe.html b/testing/web-platform/tests/webusb/resources/open-in-iframe.html
new file mode 100644
index 0000000000..ad0e12d371
--- /dev/null
+++ b/testing/web-platform/tests/webusb/resources/open-in-iframe.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+ Fake user gesture
+
+
+
diff --git a/testing/web-platform/tests/webusb/resources/open-in-worker.js b/testing/web-platform/tests/webusb/resources/open-in-worker.js
new file mode 100644
index 0000000000..2175cfd397
--- /dev/null
+++ b/testing/web-platform/tests/webusb/resources/open-in-worker.js
@@ -0,0 +1,16 @@
+importScripts('/resources/test-only-api.js');
+importScripts('/webusb/resources/usb-helpers.js');
+'use strict';
+
+onmessage = messageEvent => {
+ if (messageEvent.data.type === 'ConnectEvent') {
+ navigator.usb.addEventListener('connect', connectEvent => {
+ connectEvent.device.open().then(() => {
+ postMessage({ type: 'Success' });
+ }).catch(error => {
+ postMessage({ type: `FAIL: open rejected ${error}` });
+ });
+ });
+ postMessage({ type: 'Ready' });
+ }
+};
diff --git a/testing/web-platform/tests/webusb/resources/usb-allowed-by-permissions-policy-worker.js b/testing/web-platform/tests/webusb/resources/usb-allowed-by-permissions-policy-worker.js
new file mode 100644
index 0000000000..d06a586474
--- /dev/null
+++ b/testing/web-platform/tests/webusb/resources/usb-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.usb.getDevices(),
+ `Inherited header permissions policy allows ${workerType} workers.`);
+
+done();
diff --git a/testing/web-platform/tests/webusb/resources/usb-disabled-by-permissions-policy-worker.js b/testing/web-platform/tests/webusb/resources/usb-disabled-by-permissions-policy-worker.js
new file mode 100644
index 0000000000..caf2727cd1
--- /dev/null
+++ b/testing/web-platform/tests/webusb/resources/usb-disabled-by-permissions-policy-worker.js
@@ -0,0 +1,17 @@
+'use strict';
+
+importScripts('/resources/testharness.js');
+
+const header = 'Permissions-Policy header usb=()';
+let workerType;
+
+if (typeof postMessage === 'function') {
+ workerType = 'dedicated';
+}
+
+promise_test(() => navigator.usb.getDevices().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/webusb/resources/usb-helpers.js b/testing/web-platform/tests/webusb/resources/usb-helpers.js
new file mode 100644
index 0000000000..cb6aaadf98
--- /dev/null
+++ b/testing/web-platform/tests/webusb/resources/usb-helpers.js
@@ -0,0 +1,104 @@
+'use strict';
+
+// These tests rely on the User Agent providing an implementation of the
+// WebUSB Testing API (https://wicg.github.io/webusb/test/).
+//
+// In Chromium-based browsers this implementation is provided by a polyfill
+// in order to reduce the amount of test-only code shipped to users. To enable
+// these tests the browser must be run with these options:
+//
+// --enable-blink-features=MojoJS,MojoJSTest
+
+(() => {
+ // Load scripts needed by the test API on context creation.
+ if (isChromiumBased) {
+ loadScript('/resources/chromium/webusb-child-test.js');
+ }
+})();
+
+function usb_test(func, name, properties) {
+ promise_test(async (t) => {
+ assert_implements(navigator.usb, 'missing navigator.usb');
+ if (navigator.usb.test === undefined) {
+ // Try loading a polyfill for the WebUSB Testing API.
+ if (isChromiumBased) {
+ await loadScript('/resources/chromium/webusb-test.js');
+ }
+ }
+ assert_implements(navigator.usb.test, 'missing navigator.usb.test after initialization');
+
+ await navigator.usb.test.initialize();
+ try {
+ await func(t);
+ } finally {
+ await navigator.usb.test.reset();
+ }
+ }, name, properties);
+}
+
+// Returns a promise that is resolved when the next USBConnectionEvent of the
+// given type is received.
+function connectionEventPromise(eventType) {
+ return new Promise(resolve => {
+ let eventHandler = e => {
+ assert_true(e instanceof USBConnectionEvent);
+ navigator.usb.removeEventListener(eventType, eventHandler);
+ resolve(e.device);
+ };
+ navigator.usb.addEventListener(eventType, eventHandler);
+ });
+}
+
+// Creates a fake device and returns a promise that resolves once the
+// 'connect' event is fired for the fake device. The promise is resolved with
+// an object containing the fake USB device and the corresponding USBDevice.
+function getFakeDevice() {
+ let promise = connectionEventPromise('connect');
+ let fakeDevice = navigator.usb.test.addFakeDevice(fakeDeviceInit);
+ return promise.then(device => {
+ return { device: device, fakeDevice: fakeDevice };
+ });
+}
+
+// Disconnects the given device and returns a promise that is resolved when it
+// is done.
+function waitForDisconnect(fakeDevice) {
+ let promise = connectionEventPromise('disconnect');
+ fakeDevice.disconnect();
+ return promise;
+}
+
+function assertDeviceInfoEquals(usbDevice, deviceInit) {
+ for (var property in deviceInit) {
+ if (property == 'activeConfigurationValue') {
+ if (deviceInit.activeConfigurationValue == 0) {
+ assert_equals(usbDevice.configuration, null);
+ } else {
+ assert_equals(usbDevice.configuration.configurationValue,
+ deviceInit.activeConfigurationValue);
+ }
+ } else if (Array.isArray(deviceInit[property])) {
+ assert_equals(usbDevice[property].length, deviceInit[property].length);
+ for (var i = 0; i < usbDevice[property].length; ++i)
+ assertDeviceInfoEquals(usbDevice[property][i], deviceInit[property][i]);
+ } else {
+ assert_equals(usbDevice[property], deviceInit[property], property);
+ }
+ }
+}
+
+function callWithTrustedClick(callback) {
+ return new Promise(resolve => {
+ let button = document.createElement('button');
+ button.textContent = 'click to continue test';
+ button.style.display = 'block';
+ button.style.fontSize = '20px';
+ button.style.padding = '10px';
+ button.onclick = () => {
+ resolve(callback());
+ document.body.removeChild(button);
+ };
+ document.body.appendChild(button);
+ test_driver.click(button);
+ });
+}
diff --git a/testing/web-platform/tests/webusb/usb-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html b/testing/web-platform/tests/webusb/usb-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html
new file mode 100644
index 0000000000..013efd9b4d
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usb-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
diff --git a/testing/web-platform/tests/webusb/usb-allowed-by-permissions-policy-attribute.https.sub.html b/testing/web-platform/tests/webusb/usb-allowed-by-permissions-policy-attribute.https.sub.html
new file mode 100644
index 0000000000..54af693da0
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usb-allowed-by-permissions-policy-attribute.https.sub.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
diff --git a/testing/web-platform/tests/webusb/usb-allowed-by-permissions-policy.https.sub.html b/testing/web-platform/tests/webusb/usb-allowed-by-permissions-policy.https.sub.html
new file mode 100644
index 0000000000..e1461fe8e6
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usb-allowed-by-permissions-policy.https.sub.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
diff --git a/testing/web-platform/tests/webusb/usb-allowed-by-permissions-policy.https.sub.html.headers b/testing/web-platform/tests/webusb/usb-allowed-by-permissions-policy.https.sub.html.headers
new file mode 100644
index 0000000000..022b027812
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usb-allowed-by-permissions-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: usb=*
diff --git a/testing/web-platform/tests/webusb/usb-default-permissions-policy.https.sub.html b/testing/web-platform/tests/webusb/usb-default-permissions-policy.https.sub.html
new file mode 100644
index 0000000000..5a9ddcb4fe
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usb-default-permissions-policy.https.sub.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/testing/web-platform/tests/webusb/usb-disabled-by-permissions-policy.https.sub.html b/testing/web-platform/tests/webusb/usb-disabled-by-permissions-policy.https.sub.html
new file mode 100644
index 0000000000..3217d326f7
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usb-disabled-by-permissions-policy.https.sub.html
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
diff --git a/testing/web-platform/tests/webusb/usb-disabled-by-permissions-policy.https.sub.html.headers b/testing/web-platform/tests/webusb/usb-disabled-by-permissions-policy.https.sub.html.headers
new file mode 100644
index 0000000000..ff22d62f10
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usb-disabled-by-permissions-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: usb=()
diff --git a/testing/web-platform/tests/webusb/usb-garbage-collection.https.window.js b/testing/web-platform/tests/webusb/usb-garbage-collection.https.window.js
new file mode 100644
index 0000000000..5c153eb0a9
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usb-garbage-collection.https.window.js
@@ -0,0 +1,15 @@
+// META: script=/resources/test-only-api.js
+// META: script=/webusb/resources/fake-devices.js
+// META: script=/webusb/resources/usb-helpers.js
+// META: script=/common/gc.js
+'use strict';
+
+usb_test(async () => {
+ {
+ let {device} = await getFakeDevice();
+ await device.open();
+ await device.selectConfiguration(2);
+ await device.claimInterface(0);
+ }
+ await garbageCollect();
+}, 'Run garbage collection when the device reference is out of scope');
diff --git a/testing/web-platform/tests/webusb/usb-supported-by-permissions-policy.html b/testing/web-platform/tests/webusb/usb-supported-by-permissions-policy.html
new file mode 100644
index 0000000000..8e6352116d
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usb-supported-by-permissions-policy.html
@@ -0,0 +1,11 @@
+
+Test that usb is advertised in the feature list
+
+
+
+
+
diff --git a/testing/web-platform/tests/webusb/usb.https.any.js b/testing/web-platform/tests/webusb/usb.https.any.js
new file mode 100644
index 0000000000..c9a95b10ad
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usb.https.any.js
@@ -0,0 +1,50 @@
+// META: script=/resources/test-only-api.js
+// META: script=/webusb/resources/fake-devices.js
+// META: script=/webusb/resources/usb-helpers.js
+'use strict';
+
+let usbDevice, devicesFirstTime, fakeDevice, removedDevice;
+
+usb_test(() => getFakeDevice()
+ .then(_ => usbDevice = _.device)
+ .then(() => navigator.usb.getDevices())
+ .then(devices => {
+ assert_equals(devices.length, 1);
+ assert_equals(usbDevice, devices[0]);
+ assertDeviceInfoEquals(devices[0], fakeDeviceInit);
+ }), 'getDevices returns devices that are connected');
+
+usb_test(() => getFakeDevice()
+ .then(() => navigator.usb.getDevices())
+ .then(_ => devicesFirstTime = _)
+ .then(() => assert_equals(devicesFirstTime.length, 1))
+ .then(() => navigator.usb.getDevices())
+ .then(devicesSecondTime => assert_array_equals(devicesSecondTime,
+ devicesFirstTime)),
+ 'getDevices returns the same objects for each USB device');
+
+usb_test(() => getFakeDevice()
+ .then(_ => usbDevice = _.device)
+ .then(() => assertDeviceInfoEquals(usbDevice, fakeDeviceInit))
+ .then(() => usbDevice.open())
+ .then(() => usbDevice.close()),
+ 'onconnect event is trigged by adding a device');
+
+usb_test(() => getFakeDevice()
+ .then(_ => {
+ usbDevice = _.device;
+ fakeDevice = _.fakeDevice;
+ })
+ .then(() => waitForDisconnect(fakeDevice))
+ .then(_ => removedDevice = _)
+ .then(() => {
+ assertDeviceInfoEquals(removedDevice, fakeDeviceInit);
+ assert_equals(removedDevice, usbDevice);
+ })
+ .then(() => removedDevice.open())
+ .then(() =>
+ assert_unreachable('should not be able to open a disconnected device'),
+ error => assert_equals(error.code, DOMException.NOT_FOUND_ERR)),
+ 'ondisconnect event is triggered by removing a device');
+
+done();
diff --git a/testing/web-platform/tests/webusb/usb.https.window.js b/testing/web-platform/tests/webusb/usb.https.window.js
new file mode 100644
index 0000000000..690faf3e92
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usb.https.window.js
@@ -0,0 +1,129 @@
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=/resources/test-only-api.js
+// META: script=/webusb/resources/fake-devices.js
+// META: script=/webusb/resources/usb-helpers.js
+'use strict';
+
+usb_test(() => {
+ return navigator.usb.requestDevice({ filters: [] })
+ .then(device => {
+ assert_unreachable('requestDevice should reject without a user gesture');
+ })
+ .catch(error => {
+ assert_equals(error.code, DOMException.SECURITY_ERR);
+ });
+}, 'requestDevice rejects when called without a user gesture');
+
+usb_test(() => {
+ return callWithTrustedClick(() => navigator.usb.requestDevice({ filters: [] })
+ .then(device => {
+ assert_unreachable('requestDevice should reject when no device selected');
+ })
+ .catch(error => {
+ assert_equals(error.code, DOMException.NOT_FOUND_ERR);
+ })
+ );
+}, 'requestDevice rejects when no device is chosen');
+
+usb_test(() => {
+ return getFakeDevice().then(({ device, fakeDevice }) => {
+ navigator.usb.test.onrequestdevice = event => {
+ navigator.usb.test.onrequestdevice = undefined;
+ event.respondWith(fakeDevice);
+ };
+ return callWithTrustedClick(() => {
+ return navigator.usb.requestDevice({ filters: [] }).then(chosenDevice => {
+ assert_equals(chosenDevice, device);
+ });
+ });
+ });
+}, 'requestDevice returns the device chosen by the user');
+
+usb_test(() => {
+ return getFakeDevice().then(({ device, fakeDevice }) => {
+ navigator.usb.test.onrequestdevice = event => {
+ navigator.usb.test.onrequestdevice = undefined;
+ event.respondWith(fakeDevice);
+ };
+ return callWithTrustedClick(() => {
+ return navigator.usb.requestDevice({ filters: [] }).then(chosenDevice => {
+ assert_equals(chosenDevice, device);
+ return navigator.usb.getDevices().then(devices => {
+ assert_equals(devices.length, 1);
+ assert_equals(devices[0], chosenDevice);
+ });
+ });
+ });
+ });
+}, 'getDevices returns the same object as requestDevice');
+
+usb_test(() => {
+ const expectedFilters = [
+ { vendorId: 1234, classCode: 0xFF, serialNumber: "123ABC" },
+ { vendorId: 5678, productId: 0xF00F },
+ { vendorId: 9012, classCode: 0xFF, subclassCode: 0xEE, protocolCode: 0xDD },
+ ];
+
+ navigator.usb.test.onrequestdevice = event => {
+ navigator.usb.test.onrequestdevice = undefined;
+
+ assert_equals(event.filters.length, expectedFilters.length);
+ for (var i = 0; i < event.filters.length; ++i) {
+ assert_object_equals(event.filters[i], expectedFilters[i]);
+ }
+
+ event.respondWith(null);
+ };
+
+ return callWithTrustedClick(() => {
+ return navigator.usb.requestDevice({ filters: expectedFilters })
+ .then(device => {
+ assert_unreached(
+ 'requestDevice should reject because no device selected');
+ })
+ .catch(error => {
+ assert_equals(error.code, DOMException.NOT_FOUND_ERR);
+ });
+ });
+}, 'filters are sent correctly');
+
+usb_test(async () => {
+ const badFilters = [
+ { productId: 1234 }, // productId requires vendorId
+ { subclassCode: 5678 }, // subclassCode requires classCode
+ { protocolCode: 9012 }, // protocolCode requires subclassCode
+ ];
+
+ for (const filter of badFilters) {
+ await callWithTrustedClick(async () => {
+ try {
+ await navigator.usb.requestDevice({ filters: [filter] });
+ assert_unreached(
+ 'requestDevice should reject because of invalid filters');
+ } catch (error) {
+ assert_equals(error.name, 'TypeError');
+ }
+ });
+ }
+}, 'requestDevice rejects on invalid filters');
+
+usb_test(() => {
+ return getFakeDevice().then(({ device, fakeDevice }) => {
+ navigator.usb.test.onrequestdevice = event => {
+ event.respondWith(fakeDevice);
+ };
+ return callWithTrustedClick(() => {
+ let first = navigator.usb.requestDevice({ filters: [] });
+ let second = navigator.usb.requestDevice({ filters: [] });
+ return Promise.all([
+ first.then(chosenDevice => {
+ assert_equals(chosenDevice, device);
+ }),
+ second.then(chosenDevice => {
+ assert_equals(chosenDevice, device);
+ })
+ ]);
+ });
+ });
+}, 'multiple requestDevice calls are allowed per user activation');
diff --git a/testing/web-platform/tests/webusb/usb.serviceworker.https.html b/testing/web-platform/tests/webusb/usb.serviceworker.https.html
new file mode 100644
index 0000000000..9a0b653a2a
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usb.serviceworker.https.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/testing/web-platform/tests/webusb/usb.serviceworker.js b/testing/web-platform/tests/webusb/usb.serviceworker.js
new file mode 100644
index 0000000000..c509adfef0
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usb.serviceworker.js
@@ -0,0 +1,9 @@
+'use strict';
+importScripts('/resources/testharness.js');
+
+test(() => {
+ assert_equals(typeof navigator.usb, 'undefined',
+ 'navigator.usb should not be a USB object');
+}, 'Service workers should not have access to the WebUSB API.');
+
+done();
\ No newline at end of file
diff --git a/testing/web-platform/tests/webusb/usbAlternateInterface.https.any.js b/testing/web-platform/tests/webusb/usbAlternateInterface.https.any.js
new file mode 100644
index 0000000000..e97d53c16d
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbAlternateInterface.https.any.js
@@ -0,0 +1,34 @@
+// META: script=/resources/test-only-api.js
+// META: script=/webusb/resources/fake-devices.js
+// META: script=/webusb/resources/usb-helpers.js
+'use strict';
+
+usb_test(async () => {
+ let { device } = await getFakeDevice();
+ let configuration = new USBConfiguration(
+ device, device.configurations[1].configurationValue);
+ let usbInterface = new USBInterface(
+ configuration, configuration.interfaces[0].interfaceNumber);
+ let alternateInterface = new USBAlternateInterface(
+ usbInterface, usbInterface.alternates[1].alternateSetting);
+ assertDeviceInfoEquals(
+ alternateInterface,
+ fakeDeviceInit.configurations[1].interfaces[0].alternates[1]);
+}, 'Can construct a USBAlternateInterface.');
+
+usb_test(async () => {
+ let { device } = await getFakeDevice();
+ let configuration = new USBConfiguration(
+ device, device.configurations[1].configurationValue);
+ let usbInterface = new USBInterface(
+ configuration, configuration.interfaces[0].interfaceNumber);
+ try {
+ let alternateInterface = new USBAlternateInterface(
+ usbInterface, usbInterface.alternates.length);
+ assert_unreached(
+ 'USBAlternateInterface should reject an invalid alternate setting');
+ } catch (error) {
+ assert_equals(error.name, 'RangeError');
+ }
+}, 'Constructing a USBAlternateInterface with an invalid alternate setting ' +
+ 'throws a range error.');
diff --git a/testing/web-platform/tests/webusb/usbConfiguration.https.any.js b/testing/web-platform/tests/webusb/usbConfiguration.https.any.js
new file mode 100644
index 0000000000..96aaee273d
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbConfiguration.https.any.js
@@ -0,0 +1,24 @@
+// META: script=/resources/test-only-api.js
+// META: script=/webusb/resources/fake-devices.js
+// META: script=/webusb/resources/usb-helpers.js
+'use strict';
+
+usb_test(async () => {
+ let { device } = await getFakeDevice();
+ let configuration = new USBConfiguration(
+ device, device.configurations[1].configurationValue);
+ assertDeviceInfoEquals(configuration, fakeDeviceInit.configurations[1]);
+}, 'Can construct a USBConfiguration.');
+
+usb_test(async () => {
+ let { device } = await getFakeDevice();
+ try {
+ let configuration =
+ new USBConfiguration(device, device.configurations.length + 1);
+ assert_unreached(
+ 'USBConfiguration should reject an invalid configuration value');
+ } catch (error) {
+ assert_equals(error.name, 'RangeError');
+ }
+}, 'Constructing a USBConfiguration with an invalid configuration value ' +
+ 'throws a range error.');
diff --git a/testing/web-platform/tests/webusb/usbConnectionEvent.https.any.js b/testing/web-platform/tests/webusb/usbConnectionEvent.https.any.js
new file mode 100644
index 0000000000..12ede9e6c6
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbConnectionEvent.https.any.js
@@ -0,0 +1,22 @@
+// META: script=/resources/test-only-api.js
+// META: script=/webusb/resources/fake-devices.js
+// META: script=/webusb/resources/usb-helpers.js
+
+'use strict';
+
+usb_test(() => getFakeDevice()
+ .then(({ device }) => {
+ let evt = new USBConnectionEvent('connect', { device: device });
+ assert_equals(evt.type, 'connect');
+ assert_equals(evt.device, device);
+ }),
+ 'Can construct a USBConnectionEvent with a device');
+
+test(t => {
+ assert_throws_js(TypeError,
+ () => new USBConnectionEvent('connect', { device: null }));
+ assert_throws_js(TypeError,
+ () => new USBConnectionEvent('connect', {}));
+}, 'Cannot construct a USBConnectionEvent without a device');
+
+done();
diff --git a/testing/web-platform/tests/webusb/usbDevice-iframe.https.html b/testing/web-platform/tests/webusb/usbDevice-iframe.https.html
new file mode 100644
index 0000000000..9e90adedc7
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbDevice-iframe.https.html
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
diff --git a/testing/web-platform/tests/webusb/usbDevice-same-objecct.https.any.js b/testing/web-platform/tests/webusb/usbDevice-same-objecct.https.any.js
new file mode 100644
index 0000000000..088b4a258a
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbDevice-same-objecct.https.any.js
@@ -0,0 +1,26 @@
+// META: script=/resources/test-only-api.js
+// META: script=/webusb/resources/fake-devices.js
+// META: script=/webusb/resources/usb-helpers.js
+'use strict';
+
+usb_test(async () => {
+ const {device} = await getFakeDevice();
+ await device.open();
+
+ for (const configuration of device.configurations) {
+ await device.selectConfiguration(configuration.configurationValue);
+ assert_equals(device.configuration, configuration);
+
+ for (const interfaceObj of configuration.interfaces) {
+ await device.claimInterface(interfaceObj.interfaceNumber);
+
+ for (const alternate of interfaceObj.alternates) {
+ await device.selectAlternateInterface(
+ interfaceObj.interfaceNumber, alternate.alternateSetting);
+ assert_equals(interfaceObj.alternate, alternate);
+ }
+ await device.releaseInterface(interfaceObj.interfaceNumber);
+ }
+ }
+ await device.close();
+}, '[SameObject] test for instances within USBDevice.');
diff --git a/testing/web-platform/tests/webusb/usbDevice-worker.https.html b/testing/web-platform/tests/webusb/usbDevice-worker.https.html
new file mode 100644
index 0000000000..940120495b
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbDevice-worker.https.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
diff --git a/testing/web-platform/tests/webusb/usbDevice.https.any.js b/testing/web-platform/tests/webusb/usbDevice.https.any.js
new file mode 100644
index 0000000000..b1b0c133ce
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbDevice.https.any.js
@@ -0,0 +1,1249 @@
+// META: timeout=long
+// META: script=/resources/test-only-api.js
+// META: script=/webusb/resources/fake-devices.js
+// META: script=/webusb/resources/usb-helpers.js
+'use strict';
+
+function detachBuffer(buffer) {
+ if (self.GLOBAL.isWindow())
+ window.postMessage('', '*', [buffer]);
+ else
+ self.postMessage('', [buffer]);
+}
+
+usb_test((t) => {
+ return getFakeDevice().then(({device, fakeDevice}) => {
+ return waitForDisconnect(fakeDevice)
+ .then(() => promise_rejects_dom(t, 'NotFoundError', device.open()));
+ });
+}, 'open rejects when called on a disconnected device');
+
+usb_test(() => {
+ return getFakeDevice().then(({ device, fakeDevice }) => {
+ return device.open()
+ .then(() => waitForDisconnect(fakeDevice))
+ .then(() => {
+ assert_false(device.opened);
+ });
+ });
+}, 'disconnection closes the device');
+
+usb_test(() => {
+ return getFakeDevice().then(({ device }) => {
+ assert_false(device.opened);
+ return device.open().then(() => {
+ assert_true(device.opened);
+ return device.close().then(() => {
+ assert_false(device.opened);
+ });
+ });
+ });
+}, 'a device can be opened and closed');
+
+usb_test(() => {
+ return getFakeDevice().then(({ device }) => {
+ return device.open()
+ .then(() => device.open())
+ .then(() => device.open())
+ .then(() => device.open())
+ .then(() => device.close())
+ .then(() => device.close())
+ .then(() => device.close())
+ .then(() => device.close());
+ });
+}, 'open and close can be called multiple times');
+
+usb_test(async (t) => {
+ let { device } = await getFakeDevice();
+ await Promise.all([
+ device.open(),
+ promise_rejects_dom(t, 'InvalidStateError', device.open()),
+ promise_rejects_dom(t, 'InvalidStateError', device.close()),
+ ]);
+ await Promise.all([
+ device.close(),
+ promise_rejects_dom(t, 'InvalidStateError', device.open()),
+ promise_rejects_dom(t, 'InvalidStateError', device.close()),
+ ]);
+}, 'open and close cannot be called again while open or close are in progress');
+
+usb_test(async (t) => {
+ let { device } = await getFakeDevice();
+ await device.open();
+ return Promise.all([
+ device.selectConfiguration(1),
+ promise_rejects_dom(t, 'InvalidStateError', device.claimInterface(0)),
+ promise_rejects_dom(t, 'InvalidStateError', device.releaseInterface(0)),
+ promise_rejects_dom(t, 'InvalidStateError', device.open()),
+ promise_rejects_dom(t, 'InvalidStateError', device.selectConfiguration(1)),
+ promise_rejects_dom(t, 'InvalidStateError', device.reset()),
+ promise_rejects_dom(
+ t, 'InvalidStateError', device.selectAlternateInterface(0, 0)),
+ promise_rejects_dom(t, 'InvalidStateError', device.controlTransferOut({
+ requestType: 'standard',
+ recipient: 'interface',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x0000,
+ })),
+ promise_rejects_dom(
+ t, 'InvalidStateError',
+ device.controlTransferOut(
+ {
+ requestType: 'standard',
+ recipient: 'interface',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x0000,
+ },
+ new Uint8Array([1, 2, 3]))),
+ promise_rejects_dom(
+ t, 'InvalidStateError',
+ device.controlTransferIn(
+ {
+ requestType: 'standard',
+ recipient: 'interface',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x0000
+ },
+ 0)),
+ promise_rejects_dom(t, 'InvalidStateError', device.close()),
+ ]);
+}, 'device operations reject if an device state change is in progress');
+
+usb_test((t) => {
+ return getFakeDevice().then(({device, fakeDevice}) => {
+ return device.open()
+ .then(() => waitForDisconnect(fakeDevice))
+ .then(() => promise_rejects_dom(t, 'NotFoundError', device.close()));
+ });
+}, 'close rejects when called on a disconnected device');
+
+usb_test((t) => {
+ return getFakeDevice().then(({device, fakeDevice}) => {
+ return device.open()
+ .then(() => waitForDisconnect(fakeDevice))
+ .then(
+ () => promise_rejects_dom(
+ t, 'NotFoundError', device.selectConfiguration(1)));
+ });
+}, 'selectConfiguration rejects when called on a disconnected device');
+
+usb_test((t) => {
+ return getFakeDevice().then(({device}) => Promise.all([
+ promise_rejects_dom(t, 'InvalidStateError', device.selectConfiguration(1)),
+ promise_rejects_dom(t, 'InvalidStateError', device.claimInterface(0)),
+ promise_rejects_dom(t, 'InvalidStateError', device.releaseInterface(0)),
+ promise_rejects_dom(
+ t, 'InvalidStateError', device.selectAlternateInterface(0, 1)),
+ promise_rejects_dom(
+ t, 'InvalidStateError',
+ device.controlTransferIn(
+ {
+ requestType: 'vendor',
+ recipient: 'device',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x5678
+ },
+ 7)),
+ promise_rejects_dom(
+ t, 'InvalidStateError',
+ device.controlTransferOut(
+ {
+ requestType: 'vendor',
+ recipient: 'device',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x5678
+ },
+ new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]))),
+ promise_rejects_dom(t, 'InvalidStateError', device.clearHalt('in', 1)),
+ promise_rejects_dom(t, 'InvalidStateError', device.transferIn(1, 8)),
+ promise_rejects_dom(
+ t, 'InvalidStateError', device.transferOut(1, new ArrayBuffer(8))),
+ promise_rejects_dom(
+ t, 'InvalidStateError', device.isochronousTransferIn(1, [8])),
+ promise_rejects_dom(
+ t, 'InvalidStateError',
+ device.isochronousTransferOut(1, new ArrayBuffer(8), [8])),
+ promise_rejects_dom(t, 'InvalidStateError', device.reset())
+ ]));
+}, 'methods requiring it reject when the device is not open');
+
+usb_test(() => {
+ return getFakeDevice().then(({ device }) => {
+ assert_equals(device.configuration, null);
+ return device.open()
+ .then(() => {
+ assert_equals(device.configuration, null);
+ return device.selectConfiguration(1);
+ })
+ .then(() => {
+ assertDeviceInfoEquals(
+ device.configuration, fakeDeviceInit.configurations[0]);
+ })
+ .then(() => device.close());
+ });
+}, 'device configuration can be set and queried');
+
+usb_test(async () => {
+ let { device } = await getFakeDevice();
+ assert_equals(device.configuration, null);
+ await device.open();
+ assert_equals(device.configuration, null);
+ await device.selectConfiguration(1);
+ await device.selectConfiguration(1);
+ assertDeviceInfoEquals(
+ device.configuration, fakeDeviceInit.configurations[0]);
+ await device.selectConfiguration(2);
+ assertDeviceInfoEquals(
+ device.configuration, fakeDeviceInit.configurations[1]);
+ await device.close();
+}, 'a device configuration value can be set again');
+
+usb_test((t) => {
+ return getFakeDevice().then(({ device }) => {
+ assert_equals(device.configuration, null);
+ return device.open()
+ .then(
+ () => promise_rejects_dom(
+ t, 'NotFoundError', device.selectConfiguration(10)))
+ .then(() => device.close());
+ });
+}, 'selectConfiguration rejects on invalid configurations');
+
+usb_test((t) => {
+ return getFakeDevice().then(({ device }) => {
+ assert_equals(device.configuration, null);
+ return device.open()
+ .then(() => Promise.all([
+ promise_rejects_dom(t, 'InvalidStateError', device.claimInterface(0)),
+ promise_rejects_dom(
+ t, 'InvalidStateError', device.releaseInterface(0)),
+ promise_rejects_dom(
+ t, 'InvalidStateError', device.selectAlternateInterface(0, 1)),
+ promise_rejects_dom(
+ t, 'InvalidStateError', device.clearHalt('in', 1)),
+ promise_rejects_dom(t, 'InvalidStateError', device.transferIn(1, 8)),
+ promise_rejects_dom(
+ t, 'InvalidStateError',
+ device.transferOut(1, new ArrayBuffer(8))),
+ promise_rejects_dom(
+ t, 'InvalidStateError', device.isochronousTransferIn(1, [8])),
+ promise_rejects_dom(
+ t, 'InvalidStateError',
+ device.isochronousTransferOut(1, new ArrayBuffer(8), [8])),
+ ]))
+ .then(() => device.close());
+ });
+}, 'methods requiring it reject when the device is unconfigured');
+
+usb_test(async () => {
+ let { device } = await getFakeDevice();
+ await device.open();
+ await device.selectConfiguration(1);
+ assert_false(device.configuration.interfaces[0].claimed);
+ assert_false(device.configuration.interfaces[1].claimed);
+
+ await device.claimInterface(0);
+ assert_true(device.configuration.interfaces[0].claimed);
+ assert_false(device.configuration.interfaces[1].claimed);
+
+ await device.claimInterface(1);
+ assert_true(device.configuration.interfaces[0].claimed);
+ assert_true(device.configuration.interfaces[1].claimed);
+
+ await device.releaseInterface(0);
+ assert_false(device.configuration.interfaces[0].claimed);
+ assert_true(device.configuration.interfaces[1].claimed);
+
+ await device.releaseInterface(1);
+ assert_false(device.configuration.interfaces[0].claimed);
+ assert_false(device.configuration.interfaces[1].claimed);
+
+ await device.close();
+}, 'interfaces can be claimed and released');
+
+usb_test(async () => {
+ let { device } = await getFakeDevice();
+ await device.open();
+ await device.selectConfiguration(1);
+ assert_false(device.configuration.interfaces[0].claimed);
+ assert_false(device.configuration.interfaces[1].claimed);
+
+ await Promise.all([device.claimInterface(0),
+ device.claimInterface(1)]);
+ assert_true(device.configuration.interfaces[0].claimed);
+ assert_true(device.configuration.interfaces[1].claimed);
+
+ await Promise.all([device.releaseInterface(0),
+ device.releaseInterface(1)]);
+ assert_false(device.configuration.interfaces[0].claimed);
+ assert_false(device.configuration.interfaces[1].claimed);
+
+ await device.close();
+}, 'interfaces can be claimed and released in parallel');
+
+usb_test(async () => {
+ let { device } = await getFakeDevice()
+ await device.open();
+ await device.selectConfiguration(1);
+ await device.claimInterface(0);
+ assert_true(device.configuration.interfaces[0].claimed);
+ await device.claimInterface(0);
+ assert_true(device.configuration.interfaces[0].claimed);
+ await device.close();
+}, 'an interface can be claimed multiple times');
+
+usb_test(async () => {
+ let { device } = await getFakeDevice();
+ await device.open();
+ await device.selectConfiguration(1);
+ await device.claimInterface(0);
+ assert_true(device.configuration.interfaces[0].claimed);
+ await device.releaseInterface(0);
+ assert_false(device.configuration.interfaces[0].claimed);
+ await device.releaseInterface(0);
+ assert_false(device.configuration.interfaces[0].claimed);
+ await device.close();
+}, 'an interface can be released multiple times');
+
+usb_test(async (t) => {
+ let { device } = await getFakeDevice();
+ await device.open();
+ await device.selectConfiguration(1);
+ return Promise.all([
+ device.claimInterface(0),
+ promise_rejects_dom(t, 'InvalidStateError', device.claimInterface(0)),
+ promise_rejects_dom(t, 'InvalidStateError', device.releaseInterface(0)),
+ promise_rejects_dom(t, 'InvalidStateError', device.open()),
+ promise_rejects_dom(t, 'InvalidStateError', device.selectConfiguration(1)),
+ promise_rejects_dom(t, 'InvalidStateError', device.reset()),
+ promise_rejects_dom(
+ t, 'InvalidStateError', device.selectAlternateInterface(0, 0)),
+ promise_rejects_dom(t, 'InvalidStateError', device.controlTransferOut({
+ requestType: 'standard',
+ recipient: 'interface',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x0000,
+ })),
+ promise_rejects_dom(
+ t, 'InvalidStateError',
+ device.controlTransferOut(
+ {
+ requestType: 'standard',
+ recipient: 'interface',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x0000,
+ },
+ new Uint8Array([1, 2, 3]))),
+ promise_rejects_dom(
+ t, 'InvalidStateError',
+ device.controlTransferIn(
+ {
+ requestType: 'standard',
+ recipient: 'interface',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x0000
+ },
+ 0)),
+ promise_rejects_dom(t, 'InvalidStateError', device.close()),
+ ]);
+}, 'device operations reject if an interface state change is in progress');
+
+usb_test(async () => {
+ let { device } = await getFakeDevice();
+ await device.open();
+ await device.selectConfiguration(1);
+ await device.claimInterface(0);
+ assert_true(device.configuration.interfaces[0].claimed);
+ await device.close(0);
+ assert_false(device.configuration.interfaces[0].claimed);
+}, 'interfaces are released on close');
+
+usb_test((t) => {
+ return getFakeDevice().then(({device}) => {
+ return device.open()
+ .then(() => device.selectConfiguration(1))
+ .then(() => Promise.all([
+ promise_rejects_dom(t, 'NotFoundError', device.claimInterface(2)),
+ promise_rejects_dom(t, 'NotFoundError', device.releaseInterface(2)),
+ ]))
+ .then(() => device.close());
+ });
+}, 'a non-existent interface cannot be claimed or released');
+
+usb_test((t) => {
+ return getFakeDevice().then(({device, fakeDevice}) => {
+ return device.open()
+ .then(() => device.selectConfiguration(1))
+ .then(() => waitForDisconnect(fakeDevice))
+ .then(
+ () => promise_rejects_dom(
+ t, 'NotFoundError', device.claimInterface(0)));
+ });
+}, 'claimInterface rejects when called on a disconnected device');
+
+usb_test((t) => {
+ return getFakeDevice().then(({device, fakeDevice}) => {
+ return device.open()
+ .then(() => device.selectConfiguration(1))
+ .then(() => device.claimInterface(0))
+ .then(() => waitForDisconnect(fakeDevice))
+ .then(
+ () => promise_rejects_dom(
+ t, 'NotFoundError', device.releaseInterface(0)));
+ });
+}, 'releaseInterface rejects when called on a disconnected device');
+
+usb_test(() => {
+ return getFakeDevice().then(({ device }) => {
+ return device.open()
+ .then(() => device.selectConfiguration(2))
+ .then(() => device.claimInterface(0))
+ .then(() => device.selectAlternateInterface(0, 1))
+ .then(() => device.close());
+ });
+}, 'can select an alternate interface');
+
+usb_test(
+ async () => {
+ const {device} = await getFakeDevice();
+ await device.open();
+ await device.selectConfiguration(3);
+ await device.claimInterface(2);
+ await device.selectAlternateInterface(2, 0);
+ await device.close();
+ },
+ 'can select an alternate interface on a setting with non-sequential ' +
+ 'interface number');
+
+usb_test(
+ async () => {
+ const {device} = await getFakeDevice();
+ await device.open();
+ await device.selectConfiguration(3);
+ await device.claimInterface(0);
+ await device.selectAlternateInterface(0, 2);
+ await device.close();
+ },
+ 'can select an alternate interface on a setting with non-sequential ' +
+ 'alternative setting value');
+
+usb_test((t) => {
+ return getFakeDevice().then(({device}) => {
+ return device.open()
+ .then(() => device.selectConfiguration(2))
+ .then(() => device.claimInterface(0))
+ .then(
+ () => promise_rejects_dom(
+ t, 'NotFoundError', device.selectAlternateInterface(0, 2)))
+ .then(() => device.close());
+ });
+}, 'cannot select a non-existent alternate interface');
+
+usb_test((t) => {
+ return getFakeDevice().then(({device, fakeDevice}) => {
+ return device.open()
+ .then(() => device.selectConfiguration(2))
+ .then(() => device.claimInterface(0))
+ .then(() => waitForDisconnect(fakeDevice))
+ .then(
+ () => promise_rejects_dom(
+ t, 'NotFoundError', device.selectAlternateInterface(0, 1)));
+ });
+}, 'selectAlternateInterface rejects when called on a disconnected device');
+
+usb_test(async () => {
+ let { device } = await getFakeDevice();
+ let usbRequestTypes = ['standard', 'class', 'vendor'];
+ let usbRecipients = ['device', 'interface', 'endpoint', 'other'];
+ await device.open();
+ await device.selectConfiguration(1);
+ await device.claimInterface(0);
+ await device.selectAlternateInterface(0, 0);
+ for (const requestType of usbRequestTypes) {
+ for (const recipient of usbRecipients) {
+ let index = recipient === 'interface' ? 0x5600 : 0x5681;
+ let result = await device.controlTransferIn({
+ requestType: requestType,
+ recipient: recipient,
+ request: 0x42,
+ value: 0x1234,
+ index: index
+ }, 7);
+ assert_true(result instanceof USBInTransferResult);
+ assert_equals(result.status, 'ok');
+ assert_equals(result.data.byteLength, 7);
+ assert_equals(result.data.getUint16(0), 0x07);
+ assert_equals(result.data.getUint8(2), 0x42);
+ assert_equals(result.data.getUint16(3), 0x1234);
+ assert_equals(result.data.getUint16(5), index);
+ }
+ }
+ await device.close();
+}, 'can issue all types of IN control transfers');
+
+usb_test(async () => {
+ let { device } = await getFakeDevice();
+ let usbRequestTypes = ['standard', 'class', 'vendor'];
+ let usbRecipients = ['device', 'other'];
+ await device.open();
+ await Promise.all(usbRequestTypes.flatMap(requestType => {
+ return usbRecipients.map(async recipient => {
+ let result = await device.controlTransferIn({
+ requestType: requestType,
+ recipient: recipient,
+ request: 0x42,
+ value: 0x1234,
+ index: 0x5678
+ }, 7);
+ assert_true(result instanceof USBInTransferResult);
+ assert_equals(result.status, 'ok');
+ assert_equals(result.data.byteLength, 7);
+ assert_equals(result.data.getUint16(0), 0x07);
+ assert_equals(result.data.getUint8(2), 0x42);
+ assert_equals(result.data.getUint16(3), 0x1234);
+ assert_equals(result.data.getUint16(5), 0x5678);
+ });
+ }));
+ await device.close();
+}, 'device-scope IN control transfers don\'t require configuration');
+
+usb_test(async (t) => {
+ let { device } = await getFakeDevice();
+ let usbRequestTypes = ['standard', 'class', 'vendor'];
+ let usbRecipients = ['interface', 'endpoint'];
+ await device.open();
+ await Promise.all(usbRequestTypes.flatMap(requestType => {
+ return usbRecipients.map(recipient => {
+ let index = recipient === 'interface' ? 0x5600 : 0x5681;
+ return promise_rejects_dom(
+ t, 'InvalidStateError',
+ device.controlTransferIn(
+ {
+ requestType: requestType,
+ recipient: recipient,
+ request: 0x42,
+ value: 0x1234,
+ index: index
+ },
+ 7));
+ });
+ }));
+ await device.close();
+}, 'interface-scope IN control transfers require configuration');
+
+usb_test(async (t) => {
+ let { device } = await getFakeDevice();
+ let usbRequestTypes = ['standard', 'class', 'vendor'];
+ let usbRecipients = ['interface', 'endpoint'];
+ await device.open();
+ await device.selectConfiguration(1);
+ await Promise.all(usbRequestTypes.flatMap(requestType => {
+ return [
+ promise_rejects_dom(
+ t, 'InvalidStateError',
+ device.controlTransferIn(
+ {
+ requestType: requestType,
+ recipient: 'interface',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x5600
+ },
+ 7)),
+ promise_rejects_dom(
+ t, 'NotFoundError',
+ device.controlTransferIn(
+ {
+ requestType: requestType,
+ recipient: 'endpoint',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x5681
+ },
+ 7))
+ ];
+ }));
+ await device.close();
+}, 'interface-scope IN control transfers require claiming the interface');
+
+usb_test((t) => {
+ return getFakeDevice().then(({device, fakeDevice}) => {
+ return device.open()
+ .then(() => device.selectConfiguration(1))
+ .then(() => waitForDisconnect(fakeDevice))
+ .then(
+ () => promise_rejects_dom(
+ t, 'NotFoundError',
+ device.controlTransferIn(
+ {
+ requestType: 'vendor',
+ recipient: 'device',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x5678
+ },
+ 7)));
+ });
+}, 'controlTransferIn rejects when called on a disconnected device');
+
+usb_test(async () => {
+ let { device } = await getFakeDevice();
+ let usbRequestTypes = ['standard', 'class', 'vendor'];
+ let usbRecipients = ['device', 'interface', 'endpoint', 'other'];
+ let dataArray = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
+ let dataTypes = [dataArray, dataArray.buffer];
+ await device.open();
+ await device.selectConfiguration(1);
+ await device.claimInterface(0);
+ await device.selectAlternateInterface(0, 0);
+ for (const requestType of usbRequestTypes) {
+ for (const recipient of usbRecipients) {
+ let index = recipient === 'interface' ? 0x5600 : 0x5681;
+ let transferParams = {
+ requestType: requestType,
+ recipient: recipient,
+ request: 0x42,
+ value: 0x1234,
+ index: index
+ };
+ for (const data of dataTypes) {
+ let result = await device.controlTransferOut(transferParams, data);
+ assert_true(result instanceof USBOutTransferResult);
+ assert_equals(result.status, 'ok');
+ assert_equals(result.bytesWritten, 8);
+ }
+ let result = await device.controlTransferOut(transferParams);
+ assert_true(result instanceof USBOutTransferResult);
+ assert_equals(result.status, 'ok');
+ }
+ }
+ await device.close();
+}, 'can issue all types of OUT control transfers');
+
+usb_test(async () => {
+ let { device } = await getFakeDevice();
+ let usbRequestTypes = ['standard', 'class', 'vendor'];
+ let usbRecipients = ['device', 'other'];
+ let dataArray = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
+ let dataTypes = [dataArray, dataArray.buffer];
+ await device.open();
+ await Promise.all(usbRequestTypes.flatMap(requestType => {
+ return usbRecipients.flatMap(recipient => {
+ let transferParams = {
+ requestType: requestType,
+ recipient: recipient,
+ request: 0x42,
+ value: 0x1234,
+ index: 0x5678
+ };
+ return dataTypes.map(async data => {
+ let result = await device.controlTransferOut(transferParams, data);
+ assert_true(result instanceof USBOutTransferResult);
+ assert_equals(result.status, 'ok');
+ assert_equals(result.bytesWritten, 8);
+ }).push((async () => {
+ let result = await device.controlTransferOut(transferParams);
+ assert_true(result instanceof USBOutTransferResult);
+ assert_equals(result.status, 'ok');
+ })());
+ });
+ }));
+ await device.close();
+}, 'device-scope OUT control transfers don\'t require configuration');
+
+usb_test(async (t) => {
+ let { device } = await getFakeDevice();
+ let usbRequestTypes = ['standard', 'class', 'vendor'];
+ let usbRecipients = ['interface', 'endpoint'];
+ let dataArray = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
+ let dataTypes = [dataArray, dataArray.buffer];
+ await device.open();
+ await Promise.all(usbRequestTypes.flatMap(requestType => {
+ return usbRecipients.flatMap(recipient => {
+ let index = recipient === 'interface' ? 0x5600 : 0x5681;
+ let transferParams = {
+ requestType: requestType,
+ recipient: recipient,
+ request: 0x42,
+ value: 0x1234,
+ index: index
+ };
+ return dataTypes
+ .map(data => {
+ return promise_rejects_dom(
+ t, 'InvalidStateError',
+ device.controlTransferOut(transferParams, data));
+ })
+ .push(promise_rejects_dom(
+ t, 'InvalidStateError',
+ device.controlTransferOut(transferParams)));
+ });
+ }));
+ await device.close();
+}, 'interface-scope OUT control transfers require configuration');
+
+usb_test(async (t) => {
+ let { device } = await getFakeDevice();
+ let usbRequestTypes = ['standard', 'class', 'vendor'];
+ let usbRecipients = ['interface', 'endpoint'];
+ let dataArray = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
+ let dataTypes = [dataArray, dataArray.buffer];
+ await device.open();
+ await device.selectConfiguration(1);
+ await Promise.all(usbRequestTypes.flatMap(requestType => {
+ return usbRecipients.flatMap(recipient => {
+ let index = recipient === 'interface' ? 0x5600 : 0x5681;
+ let error =
+ recipient === 'interface' ? 'InvalidStateError' : 'NotFoundError';
+ let transferParams = {
+ requestType: requestType,
+ recipient: recipient,
+ request: 0x42,
+ value: 0x1234,
+ index: index
+ };
+ return dataTypes
+ .map(data => {
+ return promise_rejects_dom(
+ t, error, device.controlTransferOut(transferParams, data));
+ })
+ .push(promise_rejects_dom(
+ t, error, device.controlTransferOut(transferParams)));
+ });
+ }));
+ await device.close();
+}, 'interface-scope OUT control transfers an interface claim');
+
+usb_test((t) => {
+ return getFakeDevice().then(({device, fakeDevice}) => {
+ return device.open()
+ .then(() => device.selectConfiguration(1))
+ .then(() => waitForDisconnect(fakeDevice))
+ .then(
+ () => promise_rejects_dom(
+ t, 'NotFoundError',
+ device.controlTransferOut(
+ {
+ requestType: 'vendor',
+ recipient: 'device',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x5678
+ },
+ new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]))));
+ });
+}, 'controlTransferOut rejects when called on a disconnected device');
+
+usb_test(async (t) => {
+ let { device } = await getFakeDevice();
+ await device.open();
+ await device.selectConfiguration(1);
+ await device.claimInterface(0);
+ await Promise.all([
+ promise_rejects_js(
+ t, TypeError,
+ device.controlTransferOut(
+ {
+ requestType: 'invalid',
+ recipient: 'device',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x5678
+ },
+ new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]))),
+ promise_rejects_js(
+ t, TypeError,
+ device.controlTransferIn(
+ {
+ requestType: 'invalid',
+ recipient: 'device',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x5678
+ },
+ 0)),
+ ]);
+ await device.close();
+}, 'control transfers with a invalid request type reject');
+
+usb_test(async (t) => {
+ let { device } = await getFakeDevice();
+ await device.open();
+ await device.selectConfiguration(1);
+ await device.claimInterface(0);
+ await Promise.all([
+ promise_rejects_js(
+ t, TypeError,
+ device.controlTransferOut(
+ {
+ requestType: 'vendor',
+ recipient: 'invalid',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x5678
+ },
+ new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]))),
+ promise_rejects_js(
+ t, TypeError,
+ device.controlTransferIn(
+ {
+ requestType: 'vendor',
+ recipient: 'invalid',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x5678
+ },
+ 0)),
+ ]);
+}, 'control transfers with a invalid recipient type reject');
+
+usb_test(async (t) => {
+ let { device } = await getFakeDevice();
+ await device.open();
+ await device.selectConfiguration(1);
+ await device.claimInterface(0);
+ await Promise.all([
+ promise_rejects_dom(
+ t, 'NotFoundError',
+ device.controlTransferOut(
+ {
+ requestType: 'vendor',
+ recipient: 'interface',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x0002 // Last byte of index is interface number.
+ },
+ new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]))),
+ promise_rejects_dom(
+ t, 'NotFoundError',
+ device.controlTransferIn(
+ {
+ requestType: 'vendor',
+ recipient: 'interface',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x0002 // Last byte of index is interface number.
+ },
+ 0)),
+ ]);
+}, 'control transfers to a non-existant interface reject');
+
+usb_test((t) => {
+ return getFakeDevice().then(({ device }) => {
+ let interfaceRequest = {
+ requestType: 'vendor',
+ recipient: 'interface',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x5600 // Last byte of index is interface number.
+ };
+ let endpointRequest = {
+ requestType: 'vendor',
+ recipient: 'endpoint',
+ request: 0x42,
+ value: 0x1234,
+ index: 0x5681 // Last byte of index is endpoint address.
+ };
+ let data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
+ return device.open()
+ .then(() => device.selectConfiguration(1))
+ .then(() => Promise.all([
+ promise_rejects_dom(
+ t, 'InvalidStateError',
+ device.controlTransferIn(interfaceRequest, 7)),
+ promise_rejects_dom(
+ t, 'NotFoundError', device.controlTransferIn(endpointRequest, 7)),
+ promise_rejects_dom(
+ t, 'InvalidStateError',
+ device.controlTransferOut(interfaceRequest, data)),
+ promise_rejects_dom(
+ t, 'NotFoundError',
+ device.controlTransferOut(endpointRequest, data)),
+ ]))
+ .then(() => device.claimInterface(0))
+ .then(() => Promise.all([
+ device.controlTransferIn(interfaceRequest, 7).then(result => {
+ assert_true(result instanceof USBInTransferResult);
+ assert_equals(result.status, 'ok');
+ assert_equals(result.data.byteLength, 7);
+ assert_equals(result.data.getUint16(0), 0x07);
+ assert_equals(result.data.getUint8(2), 0x42);
+ assert_equals(result.data.getUint16(3), 0x1234);
+ assert_equals(result.data.getUint16(5), 0x5600);
+ }),
+ device.controlTransferIn(endpointRequest, 7).then(result => {
+ assert_true(result instanceof USBInTransferResult);
+ assert_equals(result.status, 'ok');
+ assert_equals(result.data.byteLength, 7);
+ assert_equals(result.data.getUint16(0), 0x07);
+ assert_equals(result.data.getUint8(2), 0x42);
+ assert_equals(result.data.getUint16(3), 0x1234);
+ assert_equals(result.data.getUint16(5), 0x5681);
+ }),
+ device.controlTransferOut(interfaceRequest, data),
+ device.controlTransferOut(endpointRequest, data),
+ ]))
+ .then(() => device.close());
+ });
+}, 'requests to interfaces and endpoint require an interface claim');
+
+usb_test(async () => {
+ const { device } = await getFakeDevice();
+ await device.open();
+ await device.selectConfiguration(1);
+ await device.claimInterface(0);
+
+ const transfer_params = {
+ requestType: 'vendor',
+ recipient: 'device',
+ request: 0,
+ value: 0,
+ index: 0
+ };
+
+ try {
+ const array_buffer = new ArrayBuffer(64 * 8);
+ const result =
+ await device.controlTransferOut(transfer_params, array_buffer);
+ assert_equals(result.status, 'ok');
+
+ detachBuffer(array_buffer);
+ await device.controlTransferOut(transfer_params, array_buffer);
+ assert_unreached();
+ } catch (e) {
+ assert_equals(e.code, DOMException.INVALID_STATE_ERR);
+ }
+
+ try {
+ const typed_array = new Uint8Array(64 * 8);
+ const result =
+ await device.controlTransferOut(transfer_params, typed_array);
+ assert_equals(result.status, 'ok');
+
+ detachBuffer(typed_array.buffer);
+ await device.controlTransferOut(transfer_params, typed_array);
+ assert_unreached();
+ } catch (e) {
+ assert_equals(e.code, DOMException.INVALID_STATE_ERR);
+ }
+}, 'controlTransferOut rejects if called with a detached buffer');
+
+usb_test(() => {
+ return getFakeDevice().then(({ device }) => {
+ return device.open()
+ .then(() => device.selectConfiguration(1))
+ .then(() => device.claimInterface(0))
+ .then(() => device.clearHalt('in', 1))
+ .then(() => device.close());
+ });
+}, 'can clear a halt condition');
+
+usb_test((t) => {
+ return getFakeDevice(t).then(({device, fakeDevice}) => {
+ return device.open()
+ .then(() => device.selectConfiguration(1))
+ .then(() => device.claimInterface(0))
+ .then(() => waitForDisconnect(fakeDevice))
+ .then(
+ () => promise_rejects_dom(
+ t, 'NotFoundError', device.clearHalt('in', 1)));
+ });
+}, 'clearHalt rejects when called on a disconnected device');
+
+usb_test((t) => {
+ return getFakeDevice().then(({ device }) => {
+ let data = new DataView(new ArrayBuffer(1024));
+ for (let i = 0; i < 1024; ++i)
+ data.setUint8(i, i & 0xff);
+ return device.open()
+ .then(() => device.selectConfiguration(1))
+ .then(() => device.claimInterface(0))
+ .then(() => Promise.all([
+ promise_rejects_dom(
+ t, 'NotFoundError', device.transferIn(2, 8)), // Unclaimed
+ promise_rejects_dom(
+ t, 'NotFoundError', device.transferIn(3, 8)), // Non-existent
+ promise_rejects_dom(t, 'IndexSizeError', device.transferIn(16, 8)),
+ promise_rejects_dom(
+ t, 'NotFoundError', device.transferOut(2, data)), // Unclaimed
+ promise_rejects_dom(
+ t, 'NotFoundError', device.transferOut(3, data)), // Non-existent
+ promise_rejects_dom(
+ t, 'IndexSizeError', device.transferOut(16, data)),
+ ]));
+ });
+}, 'transfers to unavailable endpoints are rejected');
+
+usb_test(() => {
+ return getFakeDevice().then(({ device }) => {
+ return device.open()
+ .then(() => device.selectConfiguration(1))
+ .then(() => device.claimInterface(0))
+ .then(() => device.transferIn(1, 8))
+ .then(result => {
+ assert_true(result instanceof USBInTransferResult);
+ assert_equals(result.status, 'ok');
+ assert_equals(result.data.byteLength, 8);
+ for (let i = 0; i < 8; ++i)
+ assert_equals(result.data.getUint8(i), i, 'mismatch at byte ' + i);
+ return device.close();
+ });
+ });
+}, 'can issue IN interrupt transfer');
+
+usb_test(() => {
+ return getFakeDevice().then(({ device }) => {
+ return device.open()
+ .then(() => device.selectConfiguration(1))
+ .then(() => device.claimInterface(1))
+ .then(() => device.transferIn(2, 1024))
+ .then(result => {
+ assert_true(result instanceof USBInTransferResult);
+ assert_equals(result.status, 'ok');
+ assert_equals(result.data.byteLength, 1024);
+ for (let i = 0; i < 1024; ++i)
+ assert_equals(result.data.getUint8(i), i & 0xff,
+ 'mismatch at byte ' + i);
+ return device.close();
+ });
+ });
+}, 'can issue IN bulk transfer');
+
+usb_test((t) => {
+ return getFakeDevice().then(({device, fakeDevice}) => {
+ return device.open()
+ .then(() => device.selectConfiguration(1))
+ .then(() => device.claimInterface(1))
+ .then(() => waitForDisconnect(fakeDevice))
+ .then(
+ () => promise_rejects_dom(
+ t, 'NotFoundError', device.transferIn(2, 1024)));
+ });
+}, 'transferIn rejects if called on a disconnected device');
+
+usb_test(() => {
+ return getFakeDevice().then(({ device }) => {
+ return device.open()
+ .then(() => device.selectConfiguration(1))
+ .then(() => device.claimInterface(1))
+ .then(() => {
+ let data = new DataView(new ArrayBuffer(1024));
+ for (let i = 0; i < 1024; ++i)
+ data.setUint8(i, i & 0xff);
+ return device.transferOut(2, data);
+ })
+ .then(result => {
+ assert_true(result instanceof USBOutTransferResult);
+ assert_equals(result.status, 'ok');
+ assert_equals(result.bytesWritten, 1024);
+ return device.close();
+ });
+ });
+}, 'can issue OUT bulk transfer');
+
+usb_test((t) => {
+ return getFakeDevice().then(({ device, fakeDevice }) => {
+ return device.open()
+ .then(() => device.selectConfiguration(1))
+ .then(() => device.claimInterface(1))
+ .then(() => {
+ let data = new DataView(new ArrayBuffer(1024));
+ for (let i = 0; i < 1024; ++i)
+ data.setUint8(i, i & 0xff);
+ return waitForDisconnect(fakeDevice)
+ .then(
+ () => promise_rejects_dom(
+ t, 'NotFoundError', device.transferOut(2, data)));
+ });
+ });
+}, 'transferOut rejects if called on a disconnected device');
+
+usb_test(async () => {
+ const { device } = await getFakeDevice();
+ await device.open();
+ await device.selectConfiguration(1);
+ await device.claimInterface(1);
+
+
+ try {
+ const array_buffer = new ArrayBuffer(64 * 8);
+ const result = await device.transferOut(2, array_buffer);
+ assert_equals(result.status, 'ok');
+
+ detachBuffer(array_buffer);
+ await device.transferOut(2, array_buffer);
+ assert_unreached();
+ } catch (e) {
+ assert_equals(e.code, DOMException.INVALID_STATE_ERR);
+ }
+
+ try {
+ const typed_array = new Uint8Array(64 * 8);
+ const result = await device.transferOut(2, typed_array);
+ assert_equals(result.status, 'ok');
+
+ detachBuffer(typed_array.buffer);
+ await device.transferOut(2, typed_array);
+ assert_unreached();
+ } catch (e) {
+ assert_equals(e.code, DOMException.INVALID_STATE_ERR);
+ }
+}, 'transferOut rejects if called with a detached buffer');
+
+usb_test(() => {
+ return getFakeDevice().then(({ device }) => {
+ return device.open()
+ .then(() => device.selectConfiguration(2))
+ .then(() => device.claimInterface(0))
+ .then(() => device.selectAlternateInterface(0, 1))
+ .then(() => device.isochronousTransferIn(
+ 1, [64, 64, 64, 64, 64, 64, 64, 64]))
+ .then(result => {
+ assert_true(result instanceof USBIsochronousInTransferResult);
+ assert_equals(result.data.byteLength, 64 * 8, 'buffer size');
+ assert_equals(result.packets.length, 8, 'number of packets');
+ let byteOffset = 0;
+ for (let i = 0; i < result.packets.length; ++i) {
+ assert_true(
+ result.packets[i] instanceof USBIsochronousInTransferPacket);
+ assert_equals(result.packets[i].status, 'ok');
+ assert_equals(result.packets[i].data.byteLength, 64);
+ assert_equals(result.packets[i].data.buffer, result.data.buffer);
+ assert_equals(result.packets[i].data.byteOffset, byteOffset);
+ for (let j = 0; j < 64; ++j)
+ assert_equals(result.packets[i].data.getUint8(j), j & 0xff,
+ 'mismatch at byte ' + j + ' of packet ' + i);
+ byteOffset += result.packets[i].data.byteLength;
+ }
+ return device.close();
+ });
+ });
+}, 'can issue IN isochronous transfer');
+
+usb_test((t) => {
+ return getFakeDevice().then(({device, fakeDevice}) => {
+ return device.open()
+ .then(() => device.selectConfiguration(2))
+ .then(() => device.claimInterface(0))
+ .then(() => device.selectAlternateInterface(0, 1))
+ .then(() => waitForDisconnect(fakeDevice))
+ .then(
+ () => promise_rejects_dom(
+ t, 'NotFoundError',
+ device.isochronousTransferIn(
+ 1, [64, 64, 64, 64, 64, 64, 64, 64])));
+ });
+}, 'isochronousTransferIn rejects when called on a disconnected device');
+
+usb_test(() => {
+ return getFakeDevice().then(({ device }) => {
+ return device.open()
+ .then(() => device.selectConfiguration(2))
+ .then(() => device.claimInterface(0))
+ .then(() => device.selectAlternateInterface(0, 1))
+ .then(() => {
+ let data = new DataView(new ArrayBuffer(64 * 8));
+ for (let i = 0; i < 8; ++i) {
+ for (let j = 0; j < 64; ++j)
+ data.setUint8(i * j, j & 0xff);
+ }
+ return device.isochronousTransferOut(
+ 1, data, [64, 64, 64, 64, 64, 64, 64, 64]);
+ })
+ .then(result => {
+ assert_true(result instanceof USBIsochronousOutTransferResult);
+ assert_equals(result.packets.length, 8, 'number of packets');
+ let byteOffset = 0;
+ for (let i = 0; i < result.packets.length; ++i) {
+ assert_true(
+ result.packets[i] instanceof USBIsochronousOutTransferPacket);
+ assert_equals(result.packets[i].status, 'ok');
+ assert_equals(result.packets[i].bytesWritten, 64);
+ }
+ return device.close();
+ });
+ });
+}, 'can issue OUT isochronous transfer');
+
+usb_test((t) => {
+ return getFakeDevice().then(({ device, fakeDevice }) => {
+ return device.open()
+ .then(() => device.selectConfiguration(2))
+ .then(() => device.claimInterface(0))
+ .then(() => device.selectAlternateInterface(0, 1))
+ .then(() => {
+ let data = new DataView(new ArrayBuffer(64 * 8));
+ for (let i = 0; i < 8; ++i) {
+ for (let j = 0; j < 64; ++j)
+ data.setUint8(i * j, j & 0xff);
+ }
+ return waitForDisconnect(fakeDevice)
+ .then(
+ () => promise_rejects_dom(
+ t, 'NotFoundError',
+ device.isochronousTransferOut(
+ 1, data, [64, 64, 64, 64, 64, 64, 64, 64])));
+ });
+ });
+}, 'isochronousTransferOut rejects when called on a disconnected device');
+
+usb_test(async () => {
+ const { device } = await getFakeDevice();
+ await device.open();
+ await device.selectConfiguration(2);
+ await device.claimInterface(0);
+ await device.selectAlternateInterface(0, 1);
+
+
+ try {
+ const array_buffer = new ArrayBuffer(64 * 8);
+ const result = await device.isochronousTransferOut(
+ 1, array_buffer, [64, 64, 64, 64, 64, 64, 64, 64]);
+ for (let i = 0; i < result.packets.length; ++i)
+ assert_equals(result.packets[i].status, 'ok');
+
+ detachBuffer(array_buffer);
+ await device.isochronousTransferOut(
+ 1, array_buffer, [64, 64, 64, 64, 64, 64, 64, 64]);
+ assert_unreached();
+ } catch (e) {
+ assert_equals(e.code, DOMException.INVALID_STATE_ERR);
+ }
+
+ try {
+ const typed_array = new Uint8Array(64 * 8);
+ const result = await device.isochronousTransferOut(
+ 1, typed_array, [64, 64, 64, 64, 64, 64, 64, 64]);
+ for (let i = 0; i < result.packets.length; ++i)
+ assert_equals(result.packets[i].status, 'ok');
+
+ detachBuffer(typed_array.buffer);
+ await device.isochronousTransferOut(
+ 1, typed_array, [64, 64, 64, 64, 64, 64, 64, 64]);
+ assert_unreached();
+ } catch (e) {
+ assert_equals(e.code, DOMException.INVALID_STATE_ERR);
+ }
+}, 'isochronousTransferOut rejects when called with a detached buffer');
+
+usb_test(() => {
+ return getFakeDevice().then(({ device }) => {
+ return device.open().then(() => device.reset()).then(() => device.close());
+ });
+}, 'can reset the device');
+
+usb_test((t) => {
+ return getFakeDevice().then(({device, fakeDevice}) => {
+ return device.open()
+ .then(() => waitForDisconnect(fakeDevice))
+ .then(() => promise_rejects_dom(t, 'NotFoundError', device.reset()));
+ });
+}, 'resetDevice rejects when called on a disconnected device');
diff --git a/testing/web-platform/tests/webusb/usbDevice_claimInterface-manual.https.html b/testing/web-platform/tests/webusb/usbDevice_claimInterface-manual.https.html
new file mode 100644
index 0000000000..991c1a9f31
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbDevice_claimInterface-manual.https.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+ These tests require a USB device to be connected.
+
+
+
+
\ No newline at end of file
diff --git a/testing/web-platform/tests/webusb/usbDevice_controlTransferIn-manual.https.html b/testing/web-platform/tests/webusb/usbDevice_controlTransferIn-manual.https.html
new file mode 100644
index 0000000000..c39e255e2b
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbDevice_controlTransferIn-manual.https.html
@@ -0,0 +1,348 @@
+
+
+
+
+
+
+
+
+
+
+
+ These tests require a USB device to be connected.
+
+
+
+
diff --git a/testing/web-platform/tests/webusb/usbDevice_forget-manual.https.html b/testing/web-platform/tests/webusb/usbDevice_forget-manual.https.html
new file mode 100644
index 0000000000..9b50852454
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbDevice_forget-manual.https.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+ These tests require a USB device to be connected.
+
+
+
+
\ No newline at end of file
diff --git a/testing/web-platform/tests/webusb/usbDevice_reset-manual.https.html b/testing/web-platform/tests/webusb/usbDevice_reset-manual.https.html
new file mode 100644
index 0000000000..63a0c356ee
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbDevice_reset-manual.https.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+ These tests require a USB device to be connected.
+
+
+
+
\ No newline at end of file
diff --git a/testing/web-platform/tests/webusb/usbDevice_transferIn-manual.https.html b/testing/web-platform/tests/webusb/usbDevice_transferIn-manual.https.html
new file mode 100644
index 0000000000..c0fad37e20
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbDevice_transferIn-manual.https.html
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
+
+
+
+ This test requires a USB device implementing the USB CDC-ACM protocol
+ configured to loop back TX to RX. For example, this Arduino sketch could
+ be used:
+
+
+void setup() {
+ Serial.begin(115200);
+ Serial.setTimeout(0);
+ while (!Serial) {
+ ;
+ }
+}
+
+void loop() {
+ if (Serial.available()) {
+ char buf[1024]; // Greater than the endpoint packet size.
+ int count = Serial.readBytes(buf, sizeof buf);
+ Serial.write(buf, count);
+ }
+}
+
+
+
+
+
diff --git a/testing/web-platform/tests/webusb/usbEndpoint.https.any.js b/testing/web-platform/tests/webusb/usbEndpoint.https.any.js
new file mode 100644
index 0000000000..c987e4c333
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbEndpoint.https.any.js
@@ -0,0 +1,46 @@
+// META: script=/resources/test-only-api.js
+// META: script=/webusb/resources/fake-devices.js
+// META: script=/webusb/resources/usb-helpers.js
+'use strict';
+
+usb_test(async () => {
+ let { device } = await getFakeDevice();
+ let configuration = new USBConfiguration(
+ device, device.configurations[1].configurationValue);
+ let usbInterface = new USBInterface(
+ configuration, configuration.interfaces[0].interfaceNumber);
+ let alternateInterface = new USBAlternateInterface(
+ usbInterface, usbInterface.alternates[1].alternateSetting);
+ let inEndpoint = new USBEndpoint(
+ alternateInterface, alternateInterface.endpoints[0].endpointNumber, 'in');
+ let outEndpoint = new USBEndpoint(
+ alternateInterface,
+ alternateInterface.endpoints[1].endpointNumber,
+ 'out');
+ assertDeviceInfoEquals(
+ inEndpoint,
+ fakeDeviceInit.configurations[1].interfaces[0].alternates[1]
+ .endpoints[0]);
+ assertDeviceInfoEquals(
+ outEndpoint,
+ fakeDeviceInit.configurations[1].interfaces[0].alternates[1]
+ .endpoints[1]);
+}, 'Can construct a USBEndpoint.');
+
+usb_test(async () => {
+ let { device } = await getFakeDevice();
+ let configuration = new USBConfiguration(
+ device, device.configurations[1].configurationValue);
+ let usbInterface = new USBInterface(
+ configuration, configuration.interfaces[0].interfaceNumber);
+ let alternateInterface = new USBAlternateInterface(
+ usbInterface, usbInterface.alternates[1].alternateSetting);
+ try {
+ let endpoint = new USBEndpoint(
+ alternateInterface, alternateInterface.endpoints.length, 'in');
+ assert_unreached('USBEndpoint should reject an invalid endpoint number');
+ } catch (error) {
+ assert_equals(error.name, 'RangeError');
+ }
+}, 'Constructing a USBEndpoint with an invalid endpoint number throws a ' +
+ 'range error.');
diff --git a/testing/web-platform/tests/webusb/usbInTransferResult.https.any.js b/testing/web-platform/tests/webusb/usbInTransferResult.https.any.js
new file mode 100644
index 0000000000..dcfa38124a
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbInTransferResult.https.any.js
@@ -0,0 +1,29 @@
+// META: script=/resources/test-only-api.js
+// META: script=/webusb/resources/fake-devices.js
+// META: script=/webusb/resources/usb-helpers.js
+'use strict';
+
+test(t => {
+ let data_view = new DataView(Uint8Array.from([1, 2, 3, 4]).buffer);
+ let result = new USBInTransferResult('ok', data_view);
+ assert_equals(result.status, 'ok');
+ assert_equals(result.data.getInt32(0), 16909060);
+}, 'Can construct a USBInTransferResult');
+
+test(t => {
+ let result = new USBInTransferResult('stall');
+ assert_equals(result.status, 'stall');
+ assert_equals(result.data, null);
+
+ result = new USBInTransferResult('babble', null);
+ assert_equals(result.status, 'babble');
+ assert_equals(result.data, null);
+}, 'Can construct a USBInTransferResult without a DataView');
+
+test(t => {
+ assert_throws_js(TypeError, () => new USBInTransferResult('invalid_status'));
+}, 'Cannot construct USBInTransferResult with an invalid status');
+
+test(t => {
+ assert_throws_js(TypeError, () => new USBInTransferResult());
+}, 'Cannot construct USBInTransferResult without a status');
diff --git a/testing/web-platform/tests/webusb/usbInterface.https.any.js b/testing/web-platform/tests/webusb/usbInterface.https.any.js
new file mode 100644
index 0000000000..22692a7d94
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbInterface.https.any.js
@@ -0,0 +1,55 @@
+// META: script=/resources/test-only-api.js
+// META: script=/webusb/resources/fake-devices.js
+// META: script=/webusb/resources/usb-helpers.js
+'use strict';
+
+usb_test(async () => {
+ let { device } = await getFakeDevice();
+ let configuration = new USBConfiguration(
+ device, device.configurations[1].configurationValue);
+ let usbInterface = new USBInterface(
+ configuration, configuration.interfaces[0].interfaceNumber);
+ assertDeviceInfoEquals(
+ usbInterface, fakeDeviceInit.configurations[1].interfaces[0]);
+}, 'Can construct a USBInterface.');
+
+usb_test(async () => {
+ let { device } = await getFakeDevice();
+ let configuration = new USBConfiguration(
+ device, device.configurations[1].configurationValue);
+ try {
+ let usbInterface = new USBInterface(
+ configuration, configuration.interfaces.length);
+ assert_unreached('USBInterface should reject an invalid interface number');
+ } catch (error) {
+ assert_equals(error.name, 'RangeError');
+ }
+}, 'Constructing a USBInterface with an invalid interface number ' +
+ 'throws a range error.');
+
+usb_test(async () => {
+ let { device } = await getFakeDevice();
+ await device.open();
+ await device.selectConfiguration(2);
+ let configuration = new USBConfiguration(
+ device, device.configurations[1].configurationValue);
+ let usbInterface = new USBInterface(
+ configuration, configuration.interfaces[0].interfaceNumber);
+ assert_equals(usbInterface.alternate.alternateSetting, 0);
+}, 'The alternate attribute of USBInterface returns the one with ' +
+ 'bAlternateSetting 0 if the interface has not been claimed.');
+
+usb_test(async () => {
+ let { device } = await getFakeDevice();
+ await device.open();
+ await device.selectConfiguration(2);
+ await device.claimInterface(0);
+ let configuration = new USBConfiguration(
+ device, device.configurations[1].configurationValue);
+ let usbInterface = new USBInterface(
+ configuration, configuration.interfaces[0].interfaceNumber);
+ assert_equals(usbInterface.alternate.alternateSetting, 0);
+ await device.selectAlternateInterface(0, 1);
+ assert_equals(usbInterface.alternate.alternateSetting, 1);
+}, 'The alternate attribute of USBInterface returns the active alternate ' +
+ 'interface.');
diff --git a/testing/web-platform/tests/webusb/usbIsochronousInTransferPacket.https.any.js b/testing/web-platform/tests/webusb/usbIsochronousInTransferPacket.https.any.js
new file mode 100644
index 0000000000..55543d11f8
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbIsochronousInTransferPacket.https.any.js
@@ -0,0 +1,28 @@
+'use strict';
+
+test(t => {
+ let data_view = new DataView(Uint8Array.from([1, 2, 3, 4]).buffer);
+ let packet = new USBIsochronousInTransferPacket('ok', data_view);
+ assert_equals(packet.status, 'ok');
+ assert_equals(packet.data.getInt32(0), 16909060);
+}, 'Can construct a USBIsochronousInTransferPacket');
+
+test(t => {
+ let packet = new USBIsochronousInTransferPacket('stall');
+ assert_equals(packet.status, 'stall');
+ assert_equals(packet.data, null);
+
+ packet = new USBIsochronousInTransferPacket('stall', null);
+ assert_equals(packet.status, 'stall');
+ assert_equals(packet.data, null);
+}, 'Can construct a USBIsochronousInTransferPacket without a DataView');
+
+test(t => {
+ assert_throws_js(TypeError, () => {
+ new USBIsochronousInTransferPacket('invalid_status');
+ });
+}, 'Cannot construct USBIsochronousInTransferPacket with an invalid status');
+
+test(t => {
+ assert_throws_js(TypeError, () => new USBIsochronousInTransferPacket());
+}, 'Cannot construct USBIsochronousInTransferPacket without a status');
diff --git a/testing/web-platform/tests/webusb/usbIsochronousInTransferResult.https.any.js b/testing/web-platform/tests/webusb/usbIsochronousInTransferResult.https.any.js
new file mode 100644
index 0000000000..0aa57d00e6
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbIsochronousInTransferResult.https.any.js
@@ -0,0 +1,36 @@
+'use strict';
+
+test(t => {
+ let data_view = new DataView(Uint8Array.from([1, 2, 3, 4]).buffer);
+ let packet_data_view = new DataView(data_view.buffer);
+ let packets = [
+ new USBIsochronousInTransferPacket('ok', packet_data_view),
+ new USBIsochronousInTransferPacket('stall')
+ ];
+
+ let result = new USBIsochronousInTransferResult(packets, data_view);
+ assert_equals(result.data.getInt32(0), 16909060);
+ assert_equals(result.packets.length, 2);
+ assert_equals(result.packets[0].status, 'ok');
+ assert_equals(result.packets[0].data.getInt32(0), 16909060);
+ assert_equals(result.packets[1].status, 'stall');
+ assert_equals(result.packets[1].data, null);
+}, 'Can construct a USBIsochronousInTransferResult');
+
+test(t => {
+ let packets = [
+ new USBIsochronousInTransferPacket('stall'),
+ new USBIsochronousInTransferPacket('stall')
+ ];
+ let result = new USBIsochronousInTransferResult(packets);
+ assert_equals(result.data, null);
+ assert_equals(result.packets.length, 2);
+ assert_equals(result.packets[0].status, 'stall');
+ assert_equals(result.packets[0].data, null);
+ assert_equals(result.packets[1].status, 'stall');
+ assert_equals(result.packets[1].data, null);
+}, 'Can construct a USBIsochronousInTransferResult without a DataView');
+
+test(t => {
+ assert_throws_js(TypeError, () => new USBIsochronousInTransferResult());
+}, 'Cannot construct a USBIsochronousInTransferResult without packets');
diff --git a/testing/web-platform/tests/webusb/usbIsochronousOutTransferPacket.https.any.js b/testing/web-platform/tests/webusb/usbIsochronousOutTransferPacket.https.any.js
new file mode 100644
index 0000000000..2747a6fa0a
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbIsochronousOutTransferPacket.https.any.js
@@ -0,0 +1,21 @@
+'use strict';
+
+test(t => {
+ let packet = new USBIsochronousOutTransferPacket('ok', 42);
+ assert_equals(packet.status, 'ok');
+ assert_equals(packet.bytesWritten, 42);
+
+ packet = new USBIsochronousOutTransferPacket('stall');
+ assert_equals(packet.status, 'stall');
+ assert_equals(packet.bytesWritten, 0);
+}, 'Can construct USBIsochronousOutTransferPacket');
+
+test(t => {
+ assert_throws_js(TypeError, () => {
+ new USBIsochronousOutTransferPacket('invalid_status');
+ });
+}, 'Cannot construct USBIsochronousOutTransferPacket with an invalid status');
+
+test(t => {
+ assert_throws_js(TypeError, () => new USBIsochronousOutTransferPacket());
+}, 'Cannot construct USBIsochronousOutTransferPacket without a status');
diff --git a/testing/web-platform/tests/webusb/usbIsochronousOutTransferResult.https.any.js b/testing/web-platform/tests/webusb/usbIsochronousOutTransferResult.https.any.js
new file mode 100644
index 0000000000..692420d948
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbIsochronousOutTransferResult.https.any.js
@@ -0,0 +1,19 @@
+'use strict';
+
+test(t => {
+ let packets = [
+ new USBIsochronousOutTransferPacket('ok', 42),
+ new USBIsochronousOutTransferPacket('stall')
+ ];
+
+ let result = new USBIsochronousOutTransferResult(packets);
+ assert_equals(result.packets.length, 2);
+ assert_equals(result.packets[0].status, 'ok');
+ assert_equals(result.packets[0].bytesWritten, 42);
+ assert_equals(result.packets[1].status, 'stall');
+ assert_equals(result.packets[1].bytesWritten, 0);
+}, 'Can construct a USBIsochronousOutTransferResult');
+
+test(t => {
+ assert_throws_js(TypeError, () => new USBIsochronousOutTransferResult());
+}, 'Cannot construct a USBIsochronousOutTransferResult without packets');
diff --git a/testing/web-platform/tests/webusb/usbOutTransferResult.https.any.js b/testing/web-platform/tests/webusb/usbOutTransferResult.https.any.js
new file mode 100644
index 0000000000..200c0716eb
--- /dev/null
+++ b/testing/web-platform/tests/webusb/usbOutTransferResult.https.any.js
@@ -0,0 +1,19 @@
+'use strict';
+
+test(t => {
+ let result = new USBOutTransferResult('ok', 42);
+ assert_equals(result.status, 'ok');
+ assert_equals(result.bytesWritten, 42);
+
+ result = new USBOutTransferResult('stall');
+ assert_equals(result.status, 'stall');
+ assert_equals(result.bytesWritten, 0);
+}, 'Can construct USBOutTransferResult');
+
+test(t => {
+ assert_throws_js(TypeError, () => new USBOutTransferResult('invalid_status'));
+}, 'Cannot construct USBOutTransferResult with an invalid status');
+
+test(t => {
+ assert_throws_js(TypeError, () => new USBOutTransferResult());
+}, 'Cannot construct USBOutTransferResult without a status');
--
cgit v1.2.3