From 43a97878ce14b72f0981164f87f2e35e14151312 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:22:09 +0200 Subject: Adding upstream version 110.0.1. Signed-off-by: Daniel Baumann --- .../tests/webusb/resources/fake-devices.js | 175 +++++++++++++++++++++ .../web-platform/tests/webusb/resources/manual.js | 110 +++++++++++++ .../tests/webusb/resources/open-in-iframe.html | 19 +++ .../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 ++++++++++++ 7 files changed, 455 insertions(+) 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 (limited to 'testing/web-platform/tests/webusb/resources') 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..730db24902 --- /dev/null +++ b/testing/web-platform/tests/webusb/resources/open-in-iframe.html @@ -0,0 +1,19 @@ + + + + + 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..e16621057a --- /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 === 'Ready') { + 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); + }); +} -- cgit v1.2.3