Index: third_party/WebKit/LayoutTests/usb/resources/webusb-test.js |
diff --git a/third_party/WebKit/LayoutTests/usb/resources/webusb-test.js b/third_party/WebKit/LayoutTests/usb/resources/webusb-test.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..94184fe92ad0a55b77658100339bba2b6661df89 |
--- /dev/null |
+++ b/third_party/WebKit/LayoutTests/usb/resources/webusb-test.js |
@@ -0,0 +1,565 @@ |
+'use strict'; |
+ |
+// This polyfil library implements the following WebIDL: |
+// |
+// partial interface USB { |
+// [SameObject] readonly attribute USBTest test; |
+// } |
+// |
+// interface USBTest { |
+// attribute EventHandler ondeviceclose; |
+// attribute DOMString? chosenDevice; |
+// attribute FrozenArray<USBDeviceFilter>? lastFilters; |
+// |
+// Promise<void> initialize(); |
+// Promise<void> attachToWindow(Window window); |
+// DOMString addFakeDevice(FakeUSBDeviceInit deviceInit); |
ortuno
2017/03/29 00:12:30
We made our whole API promised based so that we co
Reilly Grant (use Gerrit)
2017/03/29 00:29:53
I looked over the Web Bluetooth Test API and the g
|
+// void removeFakeDevice(DOMString); |
+// void reset(); |
+// }; |
+// |
+// dictionary FakeUSBDeviceInit { |
+// octet usbVersionMajor; |
+// octet usbVersionMinor; |
+// octet usbVersionSubminor; |
+// octet deviceClass; |
+// octet deviceSubclass; |
+// octet deviceProtocol; |
+// unsigned short vendorId; |
+// unsigned short productId; |
+// octet deviceVersionMajor; |
+// octet deviceVersionMinor; |
+// octet deviceVersionSubminor; |
+// DOMString? manufacturerName; |
+// DOMString? productName; |
+// DOMString? serialNumber; |
+// octet activeConfigurationValue = 0; |
+// sequence<FakeUSBConfigurationInit> configurations; |
+// }; |
+// |
+// dictionary FakeUSBConfigurationInit { |
+// octet configurationValue; |
+// DOMString? configurationName; |
+// sequence<FakeUSBInterfaceInit> interfaces; |
+// }; |
+// |
+// dictionary FakeUSBInterfaceInit { |
+// octet interfaceNumber; |
+// sequence<FakeUSBAlternateInterfaceInit> alternates; |
+// }; |
+// |
+// dictionary FakeUSBAlternateInterfaceInit { |
+// octet alternateSetting; |
+// octet interfaceClass; |
+// octet interfaceSubclass; |
+// octet interfaceProtocol; |
+// DOMString? interfaceName; |
+// sequence<FakeUSBEndpointInit> endpoints; |
+// }; |
+// |
+// dictionary FakeUSBEndpointInit { |
+// octet endpointNumber; |
+// USBDirection direction; |
+// USBEndpointType type; |
+// unsigned long packetSize; |
+// }; |
+ |
+(() => { |
+ |
+// The global mojo object contains the Mojo JS binding modules loaded during |
+// initialization. |
+let mojo = null; |
+ |
+// These variables are logically members of the USBTest class but are defined |
+// here to hide them from being visible as fields of navigator.usb.test. |
+let g_initializePromise = null; |
+let g_chooserService = null; |
+let g_deviceManager = null; |
+let g_closeListener = null; |
+ |
+function fakeDeviceInitToDeviceInfo(guid, init) { |
+ let deviceInfo = { |
+ guid: guid + "", |
+ usb_version_major: init.usbVersionMajor, |
+ usb_version_minor: init.usbVersionMinor, |
+ usb_version_subminor: init.usbVersionSubminor, |
+ class_code: init.deviceClass, |
+ subclass_code: init.deviceSubclass, |
+ protocol_code: init.deviceProtocol, |
+ vendor_id: init.vendorId, |
+ product_id: init.productId, |
+ device_version_major: init.deviceVersionMajor, |
+ device_version_minor: init.deviceVersionMinor, |
+ device_version_subminor: init.deviceVersionSubminor, |
+ manufacturer_name: init.manufacturerName, |
+ product_name: init.productName, |
+ serial_number: init.serialNumber, |
+ active_configuration: init.activeConfigurationValue, |
+ configurations: [] |
+ }; |
+ init.configurations.forEach(config => { |
+ var configInfo = { |
+ configuration_value: config.configurationValue, |
+ configuration_name: config.configurationName, |
+ interfaces: [] |
+ }; |
+ config.interfaces.forEach(iface => { |
+ var interfaceInfo = { |
+ interface_number: iface.interfaceNumber, |
+ alternates: [] |
+ }; |
+ iface.alternates.forEach(alternate => { |
+ var alternateInfo = { |
+ alternate_setting: alternate.alternateSetting, |
+ class_code: alternate.interfaceClass, |
+ subclass_code: alternate.interfaceSubclass, |
+ protocol_code: alternate.interfaceProtocol, |
+ interface_name: alternate.interfaceName, |
+ endpoints: [] |
+ }; |
+ alternate.endpoints.forEach(endpoint => { |
+ var endpointInfo = { |
+ endpoint_number: endpoint.endpointNumber, |
+ packet_size: endpoint.packetSize, |
+ }; |
+ switch (endpoint.direction) { |
+ case "in": |
+ endpointInfo.direction = mojo.device.TransferDirection.INBOUND; |
+ break; |
+ case "out": |
+ endpointInfo.direction = mojo.device.TransferDirection.OUTBOUND; |
+ break; |
+ } |
+ switch (endpoint.type) { |
+ case "bulk": |
+ endpointInfo.type = mojo.device.EndpointType.BULK; |
+ break; |
+ case "interrupt": |
+ endpointInfo.type = mojo.device.EndpointType.INTERRUPT; |
+ break; |
+ case "isochronous": |
+ endpointInfo.type = mojo.device.EndpointType.ISOCHRONOUS; |
+ break; |
+ } |
+ alternateInfo.endpoints.push(endpointInfo); |
+ }); |
+ interfaceInfo.alternates.push(alternateInfo); |
+ }); |
+ configInfo.interfaces.push(interfaceInfo); |
+ }); |
+ deviceInfo.configurations.push(configInfo); |
+ }); |
+ return deviceInfo; |
+} |
+ |
+function convertMojoDeviceFilters(input) { |
+ let output = []; |
+ input.forEach(filter => { |
+ output.push(convertMojoDeviceFilter(filter)); |
+ }); |
+ return output; |
+} |
+ |
+function convertMojoDeviceFilter(input) { |
+ let output = {}; |
+ if (input.has_vendor_id) |
+ output.vendorId = input.vendor_id; |
+ if (input.has_product_id) |
+ output.productId = input.product_id; |
+ if (input.has_class_code) |
+ output.classCode = input.class_code; |
+ if (input.has_subclass_code) |
+ output.subclassCode = input.subclass_code; |
+ if (input.has_protocol_code) |
+ output.protocolCode = input.protocol_code; |
+ if (input.serial_number) |
+ output.serialNumber = input.serial_number; |
+ return output; |
+} |
+ |
+class FakeDevice { |
+ constructor(deviceInit) { |
+ this.info_ = deviceInit; |
+ this.opened_ = false; |
+ this.currentConfiguration_ = null; |
+ this.claimedInterfaces_ = new Map(); |
+ } |
+ |
+ getDeviceInfo() { |
+ return Promise.resolve({ info: this.info_ }); |
+ } |
+ |
+ getConfiguration() { |
+ if (this.currentConfiguration_) { |
+ return Promise.resolve({ |
+ value: this.currentConfiguration_.configuration_value }); |
+ } else { |
+ return Promise.resolve({ value: 0 }); |
+ } |
+ } |
+ |
+ open() { |
+ assert_false(this.opened_); |
+ this.opened_ = true; |
+ return Promise.resolve({ error: mojo.device.OpenDeviceError.OK }); |
+ } |
+ |
+ close() { |
+ assert_true(this.opened_); |
+ this.opened_ = false; |
+ return Promise.resolve(); |
+ } |
+ |
+ setConfiguration(value) { |
+ assert_true(this.opened_); |
+ |
+ let selected_configuration = this.info_.configurations.find( |
+ configuration => configuration.configurationValue == value); |
+ // Blink should never request an invalid configuration. |
+ assert_false(selected_configuration == undefined); |
+ this.currentConfiguration_ = selected_configuration; |
+ return Promise.resolve({ success: true }); |
+ } |
+ |
+ claimInterface(interfaceNumber) { |
+ assert_true(this.opened_); |
+ assert_false(this.currentConfiguration_ == null, 'device configured'); |
+ assert_false(this.claimedInterfaces_.has(interfaceNumber), |
+ 'interface already claimed'); |
+ |
+ // Blink should never request an invalid interface. |
+ assert_true(this.currentConfiguration_.interfaces.some( |
+ iface => iface.interfaceNumber == interfaceNumber)); |
+ this.claimedInterfaces_.set(interfaceNumber, 0); |
+ return Promise.resolve({ success: true }); |
+ } |
+ |
+ releaseInterface(interfaceNumber) { |
+ assert_true(this.opened_); |
+ assert_false(this.currentConfiguration_ == null, 'device configured'); |
+ assert_true(this.claimedInterfaces_.has(interfaceNumber)); |
+ this.claimedInterfaces_.delete(interfaceNumber); |
+ return Promise.resolve({ success: true }); |
+ } |
+ |
+ setInterfaceAlternateSetting(interfaceNumber, alternateSetting) { |
+ assert_true(this.opened_); |
+ assert_false(this.currentConfiguration_ == null, 'device configured'); |
+ assert_true(this.claimedInterfaces_.has(interfaceNumber)); |
+ |
+ let iface = this.currentConfiguration_.interfaces.find( |
+ iface => iface.interfaceNumber == interfaceNumber); |
+ // Blink should never request an invalid interface or alternate. |
+ assert_false(iface == undefined); |
+ assert_true(iface.alternates.some( |
+ x => x.alternateSetting == alternateSetting)); |
+ this.claimedInterfaces_.set(interfaceNumber, alternateSetting); |
+ return Promise.resolve({ success: true }); |
+ } |
+ |
+ reset() { |
+ assert_true(this.opened_); |
+ return Promise.resolve({ success: true }); |
+ } |
+ |
+ clearHalt(endpoint) { |
+ assert_true(this.opened_); |
+ assert_false(this.currentConfiguration_ == null, 'device configured'); |
+ // TODO(reillyg): Assert that endpoint is valid. |
+ return Promise.resolve({ success: true }); |
+ } |
+ |
+ controlTransferIn(params, length, timeout) { |
+ assert_true(this.opened_); |
+ assert_false(this.currentConfiguration_ == null, 'device configured'); |
+ return Promise.resolve({ |
+ status: mojo.device.TransferStatus.OK, |
+ data: [length >> 8, length & 0xff, params.request, params.value >> 8, |
+ params.value & 0xff, params.index >> 8, params.index & 0xff] |
+ }); |
+ } |
+ |
+ controlTransferOut(params, data, timeout) { |
+ assert_true(this.opened_); |
+ assert_false(this.currentConfiguration_ == null, 'device configured'); |
+ return Promise.resolve({ |
+ status: mojo.device.TransferStatus.OK, |
+ bytesWritten: data.byteLength |
+ }); |
+ } |
+ |
+ genericTransferIn(endpointNumber, length, timeout) { |
+ assert_true(this.opened_); |
+ assert_false(this.currentConfiguration_ == null, 'device configured'); |
+ // TODO(reillyg): Assert that endpoint is valid. |
+ let data = new Array(length); |
+ for (let i = 0; i < length; ++i) |
+ data[i] = i & 0xff; |
+ return Promise.resolve({ |
+ status: mojo.device.TransferStatus.OK, |
+ data: data |
+ }); |
+ } |
+ |
+ genericTransferOut(endpointNumber, data, timeout) { |
+ assert_true(this.opened_); |
+ assert_false(this.currentConfiguration_ == null, 'device configured'); |
+ // TODO(reillyg): Assert that endpoint is valid. |
+ return Promise.resolve({ |
+ status: mojo.device.TransferStatus.OK, |
+ bytesWritten: data.byteLength |
+ }); |
+ } |
+ |
+ isochronousTransferIn(endpointNumber, packetLengths, timeout) { |
+ assert_true(this.opened_); |
+ assert_false(this.currentConfiguration_ == null, 'device configured'); |
+ // TODO(reillyg): Assert that endpoint is valid. |
+ let data = new Array(packetLengths.reduce((a, b) => a + b, 0)); |
+ let dataOffset = 0; |
+ let packets = new Array(packetLengths.length); |
+ for (let i = 0; i < packetLengths.length; ++i) { |
+ for (let j = 0; j < packetLengths[i]; ++j) |
+ data[dataOffset++] = j & 0xff; |
+ packets[i] = { |
+ length: packetLengths[i], |
+ transferred_length: packetLengths[i], |
+ status: mojo.device.TransferStatus.OK |
+ }; |
+ } |
+ return Promise.resolve({ data: data, packets: packets }); |
+ } |
+ |
+ isochronousTransferOut(endpointNumber, data, packetLengths, timeout) { |
+ assert_true(this.opened_); |
+ assert_false(this.currentConfiguration_ == null, 'device configured'); |
+ // TODO(reillyg): Assert that endpoint is valid. |
+ let packets = new Array(packetLengths.length); |
+ for (let i = 0; i < packetLengths.length; ++i) { |
+ packets[i] = { |
+ length: packetLengths[i], |
+ transferred_length: packetLengths[i], |
+ status: mojo.device.TransferStatus.OK |
+ }; |
+ } |
+ return Promise.resolve({ packets: packets }); |
+ } |
+} |
+ |
+class FakeDeviceManager { |
+ constructor() { |
+ this.bindingSet_ = |
+ new mojo.bindings.BindingSet(mojo.deviceManager.DeviceManager); |
+ this.devices_ = new Map(); |
+ this.client_ = null; |
+ } |
+ |
+ addBinding(handle) { |
+ this.bindingSet_.addBinding(this, handle); |
+ } |
+ |
+ addDevice(info) { |
+ let device = { |
+ guid: this.nextGuid_++ + "", |
+ info: info, |
+ bindingArray: [] |
+ }; |
+ this.devices_.set(device.guid, device); |
+ if (this.client_) |
+ this.client_.onDeviceAdded(fakeDeviceInitToDeviceInfo(device.guid, info)); |
+ return device.guid; |
+ } |
+ |
+ removeDevice(guid) { |
+ let device = this.devices_.get(guid); |
+ for (var binding of device.bindingArray) |
+ binding.close(); |
+ this.devices_.delete(guid); |
+ if (this.client_) { |
+ this.client_.onDeviceRemoved( |
+ fakeDeviceInitToDeviceInfo(guid, device.info)); |
+ } |
+ } |
+ |
+ removeAllDevices() { |
+ this.devices_.forEach(device => { |
+ for (var binding of device.bindingArray) |
+ binding.close(); |
+ this.client_.onDeviceRemoved( |
+ fakeDeviceInitToDeviceInfo(device.guid, device.info)); |
+ }); |
+ this.devices_.clear(); |
+ } |
+ |
+ getDevices(options) { |
+ let devices = []; |
+ this.devices_.forEach(device => { |
+ devices.push(fakeDeviceInitToDeviceInfo(device.guid, device.info)); |
+ }); |
+ return Promise.resolve({ results: devices }); |
+ } |
+ |
+ getDevice(guid, request) { |
+ let device = this.devices_.get(guid); |
+ if (device) { |
+ let binding = new mojo.bindings.Binding( |
+ mojo.device.Device, new FakeDevice(device.info), request); |
+ binding.setConnectionErrorHandler(() => { |
+ if (g_closeListener) |
+ g_closeListener(guid); |
+ }); |
+ device.bindingArray.push(binding); |
+ } else { |
+ request.close(); |
+ } |
+ } |
+ |
+ setClient(client) { |
+ this.client_ = client; |
+ } |
+} |
+ |
+class FakeChooserService { |
+ constructor() { |
+ this.bindingSet_ = new mojo.bindings.BindingSet( |
+ mojo.chooserService.ChooserService); |
+ this.chosenDevice_ = null; |
+ this.lastFilters_ = null; |
+ } |
+ |
+ addBinding(handle) { |
+ this.bindingSet_.addBinding(this, handle); |
+ } |
+ |
+ setChosenDevice(guid) { |
+ this.chosenDeviceGuid_ = guid; |
+ } |
+ |
+ getPermission(deviceFilters) { |
+ this.lastFilters_ = convertMojoDeviceFilters(deviceFilters); |
+ let device = g_deviceManager.devices_.get(this.chosenDeviceGuid_); |
+ if (device) { |
+ return Promise.resolve({ |
+ result: fakeDeviceInitToDeviceInfo(device.guid, device.info) |
+ }); |
+ } else { |
+ return Promise.resolve({ result: null }); |
+ } |
+ } |
+} |
+ |
+class USBTest { |
+ constructor() {} |
+ |
+ initialize() { |
+ if (!g_initializePromise) { |
+ g_initializePromise = new Promise(resolve => { |
+ window.define = gin.define; // Mojo modules expect this. |
+ |
+ gin.define('WebUSB Test Mocks', [ |
ortuno
2017/03/29 00:12:30
This seems similar to what mojo_helpers.js has. Wh
Reilly Grant (use Gerrit)
2017/03/29 00:29:53
I duplicated the logic in mojo-helpers.js to avoid
ortuno
2017/03/29 20:56:00
Makes sense. Another question, mostly to figure ou
Reilly Grant (use Gerrit)
2017/03/29 21:04:14
Last time I checked we were not able to use this i
ortuno
2017/03/29 21:12:56
I talked to rockot and he mentioned that we it'll
|
+ 'content/public/renderer/frame_interfaces', |
+ 'device/usb/public/interfaces/chooser_service.mojom', |
+ 'device/usb/public/interfaces/device_manager.mojom', |
+ 'device/usb/public/interfaces/device.mojom', |
+ 'mojo/public/js/bindings', |
+ 'mojo/public/js/core', |
+ 'mojo/public/js/router', |
+ 'mojo/public/js/support', |
+ ], (frameInterfaces, chooserService, deviceManager, device, |
+ bindings, core, router, support) => { |
+ delete window.define; // Clean up. |
+ |
+ mojo = { |
+ frameInterfaces: frameInterfaces, |
+ chooserService: chooserService, |
+ deviceManager: deviceManager, |
+ device: device, |
+ bindings: bindings, |
+ core: core, |
+ router: router, |
+ support: support |
+ }; |
+ |
+ g_deviceManager = new FakeDeviceManager(); |
+ mojo.frameInterfaces.addInterfaceOverrideForTesting( |
+ mojo.deviceManager.DeviceManager.name, |
+ handle => g_deviceManager.addBinding(handle)); |
+ |
+ g_chooserService = new FakeChooserService(); |
+ mojo.frameInterfaces.addInterfaceOverrideForTesting( |
+ mojo.chooserService.ChooserService.name, |
+ handle => g_chooserService.addBinding(handle)); |
+ |
+ resolve(); |
+ }); |
+ }); |
+ } |
+ |
+ return g_initializePromise; |
+ } |
+ |
+ attachToWindow(otherWindow) { |
+ if (!g_deviceManager || !g_chooserService) |
+ throw new Error('Call initialize() before attachToWindow().'); |
+ |
+ return new Promise(resolve => { |
+ otherWindow.gin.define( |
+ 'WebUSB Test Frame Attach', [ |
+ 'content/public/renderer/frame_interfaces' |
+ ], frameInterfaces => { |
+ frameInterfaces.addInterfaceOverrideForTesting( |
+ mojo.deviceManager.DeviceManager.name, |
+ handle => g_deviceManager.addBinding(handle)); |
+ frameInterfaces.addInterfaceOverrideForTesting( |
+ mojo.chooserService.ChooserService.name, |
+ handle => g_chooserService.addBinding(handle)); |
+ resolve(); |
+ }); |
+ }); |
+ } |
+ |
+ addFakeDevice(deviceInit) { |
+ if (!g_deviceManager) |
+ throw new Error('Call initialize() before addFakeDevice().'); |
+ |
+ return g_deviceManager.addDevice(deviceInit); |
+ } |
+ |
+ removeFakeDevice(guid) { |
+ if (!g_deviceManager) |
+ throw new Error('Call initialize() before removeFakeDevice().'); |
+ |
+ return g_deviceManager.removeDevice(guid); |
+ } |
+ |
+ set ondeviceclose(func) { |
+ g_closeListener = func; |
+ } |
+ |
+ set chosenDevice(guid) { |
+ if (!g_chooserService) |
+ throw new Error('Call initialize() before setting chosenDevice.'); |
+ |
+ g_chooserService.setChosenDevice(guid); |
+ } |
+ |
+ get lastFilters() { |
+ if (!g_chooserService) |
+ throw new Error('Call initialize() before getting lastFilters.'); |
+ |
+ return g_chooserService.lastFilters_; |
+ } |
+ |
+ reset() { |
+ if (!g_deviceManager || !g_chooserService) |
+ throw new Error('Call initialize() before reset().'); |
+ |
+ g_deviceManager.removeAllDevices(); |
+ g_chooserService.setChosenDevice(null); |
+ g_closeListener = null; |
+ } |
+} |
+ |
+navigator.usb.test = new USBTest(); |
+ |
+})(); |