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 @@ + + + + + + + + + + + + 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